Day7 哈希表 part2
四数相加
HashMap 存一个数组,如 A。然后计算三个数组之和,如 BCD。时间复杂度为:O(n)+O(n^3),得到 O(n^3).
HashMap 存三个数组之和,如 ABC。然后计算一个数组,如 D。时间复杂度为:O(n^3)+O(n),得到 O(n^3).
HashMap存两个数组之和,如AB。然后计算两个数组之和,如 CD。时间复杂度为:O(n^2)+O(n^2),得到 O(n^2).
我们以存 AB 两数组之和为例。首先求出 A 和 B 任意两数之和 sumAB,以 sumAB 为 key,sumAB 出现的次数为 value,存入 hashmap 中。
然后计算 C 和 D 中任意两数之和的相反数 sumCD,在 hashmap 中查找是否存在 key 为 sumCD。
public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
HashMap<Integer, Integer> result = new HashMap<>();
int count = 0;
for (int i = 0; i < nums1.length; i++) {
for (int j = 0; j < nums2.length; j++) {
int sumAB = nums1[i] + nums2[j];
if (result.containsKey(sumAB)) {
result.put(sumAB, result.get(sumAB) + 1);
} else {
result.put(sumAB, 1);
}
}
}
for (int i = 0; i < nums3.length; i++) {
for (int j = 0; j < nums4.length; j++) {
int sumCD = nums3[i] + nums4[j];
if (result.containsKey(-sumCD)) {
count += result.get(-sumCD);
}
}
}
return count;
}
赎金信
用一个长度为26的数组来记录magazine里字母出现的次数。
然后再用ransomNote 去验证这个数组是否包含了ransomNote 所需要的所有字母。
千万注意,magazine肯定是更长的,并且类似于字典,我们是统计他字母出现次数,后面需要的才能从里面减少
public boolean canConstruct(String ransomNote, String magazine) {
//题意条件 1 <= ransomNote.length, magazine.length <= 105
if (ransomNote.length() > magazine.length()) {
return false;
}
//不用map,因为空间开销大,用数组,储存a~z
int[] record = new int[26];
//遍历,统计字母出现次数
for (int i = 0; i < magazine.length(); i++) {
int index = magazine.charAt(i) - 'a';
record[index] += 1;
}
for (int i = 0; i < ransomNote.length(); i++) {
int index = ransomNote.charAt(i) - 'a';
record[index] -= 1;
}
// 如果数组中存在负数,说明ransomNote字符串中存在magazine中没有的字符
for (int i : record) {
if (i < 0) {
return false;
}
}
return true;
}
三数之和
固定最小元素指针k,i和j两个指针取值范围在(k,nums.length)两端
要注意如果nums[k]>0,那么就不可能有结果直接结束即可。因为nums[j]>nums[i]>nums[k]
然后开始移动,需要注意去重,当k>0并且nums[k-1]==nums[k]的时候,保留nums[k]就可以了
总和是小于0的,所以i指针不断右移
继续右移,此时i和j相等了,所以不行。
k右移,i重新回到k+1位置,j来到nums.length-1位置。
遇到重复的元素,跳过。此时和为0,记录k i j位置,然后跳过重复的元素,i右移,j左移
继续右移
i和 j相等,不合题意,继续
总和大于0,j左移,此时i等于j
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
// 首先对数组进行排序,这是双指针法的前提条件
Arrays.sort(nums);
// 创建结果列表,用于存储所有满足条件的三元组
List<List<Integer>> res = new ArrayList<>();
// 外层循环,固定第一个数nums[k]
for(int k = 0; k < nums.length - 2; k++){
// 如果固定的第一个数已经大于0,由于数组已排序,后面的数都更大,不可能三数之和为0
if(nums[k] > 0) break;
// 跳过重复的第一个数,避免重复的三元组
// 注意k>0的条件,避免数组越界
if(k > 0 && nums[k] == nums[k - 1]) continue;
// 设置双指针:i从k+1开始,j从数组末尾开始
int i = k + 1, j = nums.length - 1;
// 双指针遍历
while(i < j){
// 计算当前三个数的和
int sum = nums[k] + nums[i] + nums[j];
if(sum < 0){
// 和小于0,需要增大和,因此移动左指针i向右
// 使用while循环跳过重复的nums[i]
while(i < j && nums[i] == nums[++i]);
} else if (sum > 0) {
// 和大于0,需要减小和,因此移动右指针j向左
// 使用while循环跳过重复的nums[j]
while(i < j && nums[j] == nums[--j]);
} else {
// 找到满足条件的三元组,添加到结果列表
res.add(new ArrayList<Integer>(Arrays.asList(nums[k], nums[i], nums[j])));
// 跳过重复的nums[i]和nums[j],避免重复的三元组
while(i < j && nums[i] == nums[++i]);
while(i < j && nums[j] == nums[--j]);
}
}
}
// 返回所有满足条件的三元组
return res;
}
}