【数据结构】哈希表 | 几数之和去重

125 阅读2分钟

题目一 1. 两数之和 - 力扣(LeetCode)

image.png

image.png

思路

二刷这道题有印象,是用hashmap存储target-nums[i]和下标,然后寻找nums[j]在hashmap中,尝试写一下代码:

代码

问题1:怎么取出hashmap的键和值 问题2:怎么保证不出现2+2等于4的情况(这里的2为同一个元素)

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Hashmap<Integer> record = new Hashmap<>();
        for(int i = 0;i < nums.length;i++) {
            record.add(target - nums[i],i);
        }
        for(int i = 0;i < nums.length;i++) {
            record.contains(nums[i]);
        }
    }
}

改动后:

  • 去重在这里是通过一个指针i实现的
class Solution {
    public int[] twoSum(int[] nums, int target) {
        // 哈希表record
        Map<Integer,Integer> record = new HashMap<>();
        // 结果数组result
        int[] result = new int[2];
        for(int i = 0;i < nums.length;i++) {
            //一定要先判断再改动record
            if(record.containsKey(nums[i])) {
                result[0] = record.get(nums[i]);
                result[1] = i;
            }
            // 记录互补数字和下标
            record.put(target - nums[i],i);
        }
        return result;
    }
}

知识点

HashMap存储键值,键是唯一标识,值可以更新

  • containsKey(nums[i]):HashMap判断键是否存在的方法
  • get(nums[i]):HashMap获取值的方法
  • put(target - nums[i],i):HashMap添加键值对的方法

题目二454. 四数相加 II - 力扣(LeetCode)

image.png

image.png

思路

二刷,两个hashmap,一个哈希表存储前两个的和,另一个哈希表存储后两个的和,搜索两个hashmap,看看有无相反数。

  • 问题1:哈希表的键是两个数字之和,值是下标数组,但是键可能对应多组下标
    解答1:哈希表的键是两个数字之和,值是组合数,如nums1和nums2中有两组{i,j}的和为40,则(40,2)
  • 问题2:怎样保证{i,j}或{k,l}组合不会重复
    解答2:双重for循环遍历
  • 问题3:怎样计算总元组数{i,j,k,l}
    解答3:若nums1和nums2中有两组{i,j}的和为40,则(40,2),nums3和nums4中有3组{k,l}的和为-40,则(-40,3);由此result_num += 2*3

代码

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        Map<Integer,Integer> hashmap1 = new HashMap<>();
        // Map<Integer,Integer> hashmap2 = new HashMap<>();
        int result_cnt = 0;
        for(int i = 0;i<nums1.length;i++) {
            for(int j = 0;j<nums2.length;j++) {
                int sum1 = nums1[i] + nums2[j];
                if(hashmap1.containsKey(sum1)){
                    hashmap1.put(sum1,hashmap1.get(sum1)+1);
                }else {
                    hashmap1.put(sum1,1);
                }
            }
        }
        // 不用两个hashmap,只需要一个就可以
        for(int i = 0;i<nums3.length;i++) {
            for(int j = 0;j<nums4.length;j++) {
                int sum2 = -nums3[i]-nums4[j];
                if(hashmap1.containsKey(sum2)){
                    result_cnt += hashmap1.get(sum2);
                }
            }
        }
        return result_cnt;
    }
}

改进代码:

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        Map<Integer, Integer> map = new HashMap<>();
        int result = 0;
        int count = 0;
        //统计两个数组中的元素之和,同时统计出现的次数,放入map
        for(int i : nums1){
            for(int j : nums2){
                result = i + j;
                if(map.containsKey(result)){
                    map.put(result,map.get(result)+1);
                }else{
                    map.put(result,1);
                }
            }
        }
        //统计剩余的两个元素的和,在map中找是否存在相加为0的情况,同时记录次数
        for(int i : nums3){
            for(int j : nums4){
                int temp = 0-(i + j);
                if(map.containsKey(temp)){
                    count += map.get(temp);
                }
            }
        }
        return count;
    }
}

知识点

  • 双重循环遍历求和
for(int i : nums1){
     for(int j : nums2){
         result = i + j;
         }
     }

题目三15. 三数之和 - 力扣(LeetCode)

image.png

image.png

思路

题目要求返回不重复的三元组,和题目二类似,哈希法

  • 哈希表键值是什么?
    答:键是前两个数字的和nums[i] + nums[j],值是数组[i,j]
  • 如何判断是否满足条件?
    答:遍历map,寻找键为-nums[k]的数
  • 怎么保证i,j,k互不相等?
    答:不太清楚,先尝试写一下,再看一下题解

去重问题

尝试之后发现哈希法思路很清晰,但去重操作让代码变得很复杂
所以,这道题目可以换一个思路,用双指针法来解决

双指针法

  • 先对数组排序,升序Arrays.sort(nums);
  • 定义i、left、right,初始化为0,1,len-1
  • 外层循环遍历i
  • 内层循环固定i,移动left、right使得三数之和等于0

注意:由于排序之后的数组有隐形条件,使得左右指针的移动有规则,因此这里降低了去重的难度

代码

二维数组result存储符合条件的三元组,用数组[a,b,c]表示
对于a,b,c来说,去重逻辑是不一样的,举两个例子就不难理解了

nums(排序后)去重方法
[-1,-1,2,7]/i指向第一个-1时,不需要去重,否则永远不存在结果[-1,-1,2]
[-1,-1,0,1]ai指向第二个-1时,才需要去重;即nums[i] == nums[i-1]
[-1,0,0,0,1,1]bnums[right] == nums[right-1],右指针需要左移right--去重
cnums[left] == nums[left+1],左指针需要右移left++去重
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> result = new ArrayList<>();
        // [-1,-1,0,1]怎么去重
        for(int i = 0;i<nums.length-2;i++) {
            if(nums[i] > 0) return result;
            if(i>0 && nums[i] == nums[i-1]){
                // 在下一轮for循环会i+1
                continue;
            }
            int left = i+1;
            int right = nums.length - 1;
            while(left < right) {
                // System.out.println("1");
                if(nums[i] + nums[left] + nums[right] == 0) {
                    result.add(Arrays.asList(nums[i],nums[left],nums[right]));
                    //[-1,0,0,0,1,1,1] 
                    // b c 如何去重
                    while(right > left && nums[right] == nums[right-1]) right--;
                    while(right > left && nums[left] == nums[left+1]) left++;
                    // continue;
                    // 去重之后要移动左指针或右指针,否则左右指针一直停留在满足条件的位置
                    // 形成死循环
                    right--;
                    left++;
                }else if(nums[i] + nums[left] + nums[right] > 0) {
                    // 移动右指针
                    right--;
                    // continue;
                }else{
                    // 移动左指针
                    left++;
                    // continue;
                } 
            }
        }
        return result;
    }
}

题目四18. 四数之和 - 力扣(LeetCode)

image.png

image.png

思路

排序后,四个指针移动
i,j双重循环固定,left,right双指针
最大的问题还是去重,举几个例子来想一想:
【2,2,2,2,2】i和i-1比较是否相等
【2,2,3,3,3,4,4】-->【2,2,4,4】j也是和j-1比较
感觉不太对劲,应该是(i,j)组成 的组合去重,试一下

代码

  • i去重,if(i > 0 && nums[i] == nums[i-1])
  • j去重,if(j > i+1 && nums[j] == nums[j-1])
  • 双指针去重逻辑和三数之和相同
class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> result = new ArrayList<>();
        if(nums.length < 4) return result;
        Arrays.sort(nums);
        for(int i = 0;i<nums.length - 3;i++){
            // i剪枝
            // i去重
            if(i > 0 && nums[i] == nums[i-1]) continue;
            for(int j = i+1;j< nums.length - 2;j++){
                // j剪枝
                // j去重
                if(j > i+1 && nums[j] == nums[j-1]) continue;
                
                int left = j+1;
                int right = nums.length - 1;
                while(left < right) {
                    if(nums[i] + nums[j] + nums[left] + nums[right] > target) {
                        right--;
                    }else if(nums[i] + nums[j] + nums[left] + nums[right] < target) {
                        left++;
                    }else {
                        result.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));
                        while(left < right && nums[left] == nums[left+1]) left++;
                        while(left < right && nums[right] == nums[right-1]) right--;
                        left++;
                        right--;
                    }
                }
            }   
        }
        return result;
    }
}
  • 错误原因:整形Integer溢出了
  • 解决办法:加一个剪枝操作

image.png