Day07-454.四数相加II、383. 赎金信、15. 三数之和、18. 四数之和

65 阅读6分钟

摘要

本文主要讲解了LeetCode中454.四数相加II、383. 赎金信、15. 三数之和、18. 四数之和的思路和代码,并在最后由ChatGPT对哈希表专题进行了总结。

1、454.四数相加II

1.1 思路

四数相加转换为两数之和 nums1与nums2相加组成map, key代表nums1与nums2的和,value代表相同和的次数 遍历nums3与nums4,查看是否满足 nums3[i] + nums4[j] + key = 0

1.2 代码

    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        Map<Integer, Integer> map = new HashMap<>();
        for(int item1 : nums1) {
            for(int item2 : nums2) {
                int key = item1 + item2; 
                int count = map.getOrDefault(key, 0);
                map.put(key, ++count);
            }
        }
​
       int count = 0; 
       for(int item3 : nums3) {
            for(int item4 : nums4) {
                int key = 0 - (item3 + item4);
                if(map.containsKey(key)) {
                    count += map.get(key);
                }
            }
        }
        return count;
    }

2、383.赎金信

2.1 思路

此题与242.有效的字母异位词解题方式类似 定义int数组,0-25分别表示a-z,数组元素的值代表次数 先遍历magazine,数组元素值+1,然后遍历ransomNote,数组元素值-1 最后遍历int数组,如果数组的元素都大于0则返回true,反之false

2.2 代码

    public boolean canConstruct(String ransomNote, String magazine) {
        int[] arr = new int[26];
​
        for(int i=0; i<magazine.length(); i++) {
            arr[magazine.charAt(i)-'a']++;
        }
​
        for(int j=0; j<ransomNote.length(); j++) {
            arr[ransomNote.charAt(j)-'a']--;
        }
​
        for(int item : arr) {
            if(item < 0) {
                return false;
            }
        }
        return true;
    }

3、15. 三数之和

3.1 思路

首先对数组nums进行排序,然后对象数组进行外层遍历,内层循环 定义3个指针p1,p2,p3,分别代表这三个数 p1表示外层遍历中的当前元素,内层循环中,初始化时,p2=p1+1,p3=nums.length()-1 若 p1+p2+p3=0 放入到结果中,并且p2,p3同时向对向移动,小于0时,p2向右移动,大于0时,p3向左移动 直到p2,p3重合跳出内层循环,继续下一次外层遍历

  • 外层遍历去重
  • 内存循环去重
  • 剪枝:数组已排序,如果 p1大于0,直接返回

3.2 代码

error-1

    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
​
        Arrays.sort(nums);
​
        for(int i=0; i<nums.length-2; i++) {
            int p1 = nums[i];
​
            int j = i+1;
            int k = nums.length-1;
            while(j < k) {
                int p2 = nums[j];
                int p3 = nums[k];
                
                int sum = p1 + p2 + p3;
                if(sum == 0) {
                    res.add(Arrays.asList(new Integer[]{p1, p2, p3}));
                    j++;
                    k--;
                } else if(sum < 0) {
                    j++;
                } else {
                    k--;
                }
            }
        }
        return res;
    }

测试用例:输入:nums = [-1,0,1,2,-1,-4];输出:[[-1,-1,2],[-1,0,1],[-1,0,1]];预期结果:[[-1,-1,2],[-1,0,1]]

原因:

  • 外层遍历中,num[i] = -1;使用了 2 次,导致了重复的结果

解决方式:

  • 外层遍历中,如果 nums[i] == nums[i-1] 则跳过,继续下一次循环

error-2

    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
​
        Arrays.sort(nums);
​
        for(int i=0; i<nums.length-2; i++) {
            int p1 = nums[i];
​
            // 外层遍历去重
            if(i > 0 && nums[i] == nums[i-1]) {
                continue;
            }
​
            int j = i+1;
            int k = nums.length-1;
            while(j < k) {
                int p2 = nums[j];
                int p3 = nums[k];
                
                int sum = p1 + p2 + p3;
                if(sum == 0) {
                    res.add(Arrays.asList(new Integer[]{p1, p2, p3}));
                    j++;
                    k--;
                } else if(sum < 0) {
                    j++;
                } else {
                    k--;
                }
            }
        }
        return res;
    }

测试用例:输入:nums = [-2,0,0,2,2];输出:[[-2,0,2],[-2,0,2]];预期结果:[[-2,0,2]]

原因:

  • 内层循环中,没有去重,nums[j] == 0 使用了 2 次,nums[k] == 2 使用了 2次

解决方式:

  • 内层循环中,j, k 也需要去重判断

AC

    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
​
        Arrays.sort(nums);
​
        for(int i=0; i<nums.length-2; i++) {
            int p1 = nums[i];
            // 剪枝
            if(p1 > 0) {
                return res;
            }
​
            // 外层遍历去重
            if(i > 0 && nums[i] == nums[i-1]) {
                continue;
            }
​
            int j = i+1;
            int k = nums.length-1;
            while(j < k) {
                int p2 = nums[j];
                int p3 = nums[k];
                
                int sum = p1 + p2 + p3;
                if(sum == 0) {
                    res.add(Arrays.asList(new Integer[]{p1, p2, p3}));
​
                    // 内存循环去重
                    j++;
                    while(j < k && nums[j] == nums[j-1]) {
                        j++;
                    }
​
                    k--;
                    while(j < k && nums[k] == nums[k+1]) {
                        k--;
                    }
                } else if(sum < 0) {
                    j++;
                } else {
                    k--;
                }
            }
        }
        return res;
    }

4、18. 四数之和

4.1 思路

解题思路同三数之和,两次for循环+双指针,注意去重和剪枝 定义数组下标i, j, k,z,其相应的值为nums[i], nums[j], nums[k], nums[z]

  • 对 i 剪枝时,必须 nums[i] >= 0 && nums[i] > target
  • 对 j 剪枝时,必须 nums[i] + nums[j] >= 0 && nums[i] + nums[j]> target,并且不是返回,应该是 break

4.2 代码

error-1

    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> res = new ArrayList<>();
​
        Arrays.sort(nums);
​
        int len = nums.length;
        for(int i=0; i<len-3; i++) {
            // 剪枝
            if(nums[i] > target) {
                return res;
            }
            // i 去重
            if(i > 0 && nums[i] == nums[i-1]) {
                continue;
            }
​
            for(int j=i+1; j<len-2; j++) {
                // 剪枝
                if(nums[i] + nums[j]> target) {
                    return res;
                }
                // i 去重
                if(j > i+1 && nums[j] == nums[j-1]) {
                    continue;
                }
                
                int k = j + 1;
                int z = len - 1;
                while(k < z) {
                    int sum = nums[i] + nums[j] + nums[k] + nums[z];
                    
                    if(sum == target) {
                        res.add(Arrays.asList(new Integer[]{nums[i],  nums[j], nums[k], nums[z]}));
​
                        // k 去重
                        k++;
                        while(k < z && nums[k] == nums[k-1]) {
                            k++;
                        }
​
                        // z 去重
                        z--;
                        while(k < z && nums[z] == nums[z+1]) {
                            z--;
                        }
                    } else if (sum < target) {
                        k++;
                    } else {
                        z--;
                    }
                }
            }
        }
        return res;
    }

测试用例 输入:nums = [-5,-4,-3,-2,-1,0,0,1,2,3,4,5] 输出:[[-5,-4,4,5],[-5,-3,3,5],[-5,-2,2,5],[-5,-2,3,4],[-5,-1,1,5],[-5,-1,2,4],[-5,0,0,5],[-5,0,1,4],[-5,0,2,3],[-4,-3,2,5],[-4,-3,3,4],[-4,-2,1,5],[-4,-2,2,4],[-4,-1,0,5],[-4,-1,1,4],[-4,-1,2,3],[-4,0,0,4],[-4,0,1,3],[-3,-2,0,5],[-3,-2,1,4],[-3,-2,2,3],[-3,-1,0,4],[-3,-1,1,3],[-3,0,0,3],[-3,0,1,2],[-2,-1,0,3],[-2,-1,1,2],[-2,0,0,2]] 预期结果 [[-5,-4,4,5],[-5,-3,3,5],[-5,-2,2,5],[-5,-2,3,4],[-5,-1,1,5],[-5,-1,2,4],[-5,0,0,5],[-5,0,1,4],[-5,0,2,3],[-4,-3,2,5],[-4,-3,3,4],[-4,-2,1,5],[-4,-2,2,4],[-4,-1,0,5],[-4,-1,1,4],[-4,-1,2,3],[-4,0,0,4],[-4,0,1,3],[-3,-2,0,5],[-3,-2,1,4],[-3,-2,2,3],[-3,-1,0,4],[-3,-1,1,3],[-3,0,0,3],[-3,0,1,2],[-2,-1,0,3],[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

原因:

  • i,j 的剪枝错误

解决方式:

  • 对 i 剪枝时,必须 num[i] >= 0;
  • 对 j 剪枝时,必须 nums[i] + nums[j] >= 0,并且不是返回,应该是 break

AC

    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> res = new ArrayList<>();
​
        Arrays.sort(nums);
​
        int len = nums.length;
        for(int i=0; i<len-3; i++) {
            // 剪枝
            if(nums[i] >= 0 && nums[i] > target) {
                return res;
            }
            // i 去重
            if(i > 0 && nums[i] == nums[i-1]) {
                continue;
            }
​
            for(int j=i+1; j<len-2; j++) {
                // 剪枝
                if(nums[i] + nums[j] >= 0 && nums[i] + nums[j]> target) {
                     break;
                }
                // i 去重
                if(j > i+1 && nums[j] == nums[j-1]) {
                    continue;
                }
                
                int k = j + 1;
                int z = len - 1;
                while(k < z) {
                    int sum = nums[i] + nums[j] + nums[k] + nums[z];
                    
                    if(sum == target) {
                        res.add(Arrays.asList(new Integer[]{nums[i],  nums[j], nums[k], nums[z]}));
​
                        // k 去重
                        k++;
                        while(k < z && nums[k] == nums[k-1]) {
                            k++;
                        }
​
                        // z 去重
                        z--;
                        while(k < z && nums[z] == nums[z+1]) {
                            z--;
                        }
                    } else if (sum < target) {
                        k++;
                    } else {
                        z--;
                    }
                }
            }
        }
        return res;
    }

5、总结

哈希表是计算机科学中的常见数据结构,它基于哈希函数将数据映射到一个固定大小的数组中,以便快速访问、插入和删除数据。以下是一些LeetCode题目,涵盖了哈希表专题的不同方面,并对其进行了总结:

  1. 242. 有效的字母异位词

    • 题目要求判断两个字符串是否是字母异位词,即它们包含的字母相同但排列不同。
    • 使用哈希表记录每个字符的出现次数,然后比较两个字符串的字符计数是否相同。
  2. 349. 两个数组的交集

    • 题目要求找出两个数组的交集元素。
    • 使用一个哈希集记录一个数组中的元素,然后遍历另一个数组,判断是否在哈希集中存在。
  3. 202. 快乐数

    • 题目要求判断一个数字是否是快乐数。
    • 使用哈希集来记录每次计算的结果,如果出现重复的结果且不等于1,就说明不是快乐数。
  4. 1. 两数之和

    • 题目要求找出数组中两个数的和等于目标值的下标。
    • 使用哈希表记录已经遍历过的元素及其下标,然后遍历数组,查找目标值与当前元素的差值是否在哈希表中。
  5. 454. 四数相加II

    • 题目要求找出四个数组中元素相加等于零的组合数量。
    • 将四个数组的元素两两相加并存储在两个哈希表中,然后查找其中一个哈希表中元素的相反数是否在另一个哈希表中。
  6. 383. 赎金信

    • 题目要求判断一个字符串是否能由另一个字符串中的字符构成。
    • 使用两个哈希表分别记录两个字符串中字符的出现次数,然后比较是否能满足条件。
  7. 15. 三数之和

    • 题目要求找出数组中三个数的和等于零的所有不重复组合。
    • 先对数组排序,然后使用双指针和哈希表来处理,避免重复组合的产生。
  8. 18. 四数之和

    • 题目要求找出数组中四个数的和等于目标值的所有不重复组合。
    • 类似于三数之和,使用双指针和哈希表来处理,避免重复组合的产生。

总结:哈希表在这些题目中被广泛应用,用于记录元素的出现次数、快速查找元素或存储中间结果。掌握哈希表的使用方法对解决这些问题非常有帮助。同时,这些题目也展示了在不同问题背景下如何巧妙地利用哈希表来解决算法问题。

参考资料

代码随想录-四数相加II

代码随想录-赎金信

代码随想录-三数之和

代码随想录-四数之和