算法刷题 6 哈希表与双指针经典题(四数相加+ 赎金信 + 三数之和【经典双指针】 + 四数之和)

1、四数相加

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

暴力:n的4次方

思路

对数组 ABCD 两两进行求和,对于 AB 两数组,求和后将值保存在哈希结构中,然后对 CD 数组求和后,到哈希结构中寻找是否有出现符合与其相加为 0 的值,如果存在,则将 AB 中相加等于该值的个数加入到总体的计数中

  • a+b存入map,key是值,value是出现次数,去map中找0-(c+d),如果有匹配的就把次数取出来加上
  • 用map,这题不用去重,根据key找出现次数,两两相加
  • map.put(sum1, map.getOrDefault(sum1, 0) + 1)获得多次数,getOrDefault(sum1,0), get(sum1)没有默认0!
  • map的API containsKey
  • 注意map喜欢用for each的循环
        // 注意点:1、判断元素是否在集合中出现过用哈希表,要value要次数用map
        //         2、次数不是加一,是加value

        Map<Integer, Integer> map = new HashMap<>();
        int sum1 = 0, sum2 = 0, result = 0;
        for (int i : nums1){
            for (int j : nums2){
                sum1 = i + j;
                //map用put存入,这个是叠加,getOrDefault(sum1,0),get(sum1)没有默认0!,不能直接put
                map.put(sum1, map.getOrDefault(sum1, 0) + 1);
            }
        }

        for (int i : nums3){
            for (int j : nums4){
                sum2 = 0 - (i + j);
                if (map.containsKey(sum2)){
                    result += map.get(sum2);
                }
            }
        }

        return result;

    }

2、赎金信

给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。如果可以构成,返回 true ;否则返回 false。

思路

  • 存入一个,另外一个扣数量,如果出现负数,说明不能构成
  • 注意顺序别反了!! 先存大的减小的;反的话肯定会超啊
  • 1、全是小写字母,用hash数组
  • 2、可以将字符串转成char数组,也可以遍历charAt得到每一位
  • 3、一个++一个--最后看看有没有负的即可
 public boolean canConstruct(String ransomNote, String magazine) {

        //加一个size判断,先考虑异常情况
        if (ransomNote.length() > magazine.length()) {
            return false;
        }

        //小写字母用数组存
        //存入一个,另外一个扣数量,如果出现负数,说明不能构成
        //注意顺序别反了!! 先存大的减小的;反的话肯定会超啊
        int[] res = new int[26];
        // for (char i : magazine.toCharArray()){
        //     res[i - 'a']++;
        // }

        // for (char i : ransomNote.toCharArray()){
        //         res[i - 'a']--;
        //     }

        for (int i = 0; i < magazine.length(); i++) {
            res[magazine.charAt(i) - 'a'] ++;
        }

        for (int i = 0; i < ransomNote.length(); i++) {
            res[ransomNote.charAt(i) - 'a'] --;
        }

        for (int i : res){
            if (i < 0){
                return false;
            }
        }
        return true;
    }

写法二

    public boolean canConstruct(String ransomNote, String magazine) {
        //小写字母用数组存
        //存入一个,另外一个扣数量,如果出现负数,说明不能构成
        //注意顺序别反了!! 先存大的减小的;反的话肯定会超啊
        int[] array = new int[26];
        for (char i : magazine.toCharArray()) {
            array[i - 'a']++;
        }
        for (char i : ransomNote.toCharArray()) {
            //对应字符转数字的位置+1
            array[i - 'a']--;
        }

        for (int i : array) {
            if (i < 0) {
                return false;
            }
        }
        return true;
    }

写法三:用map做

存入存出

map不能直接用foreach,用存入map的原始东西来遍历,比如数组啥的

    public boolean canConstruct(String ransomNote, String magazine) {
        //用map做下
        Map<Character, Integer> map = new HashMap<>();
        for (char i : magazine.toCharArray()) {
            map.put(i, map.getOrDefault(i, 0) + 1);
        }  
        for (char i : ransomNote.toCharArray()) {
            map.put(i, map.getOrDefault(i, 0) - 1);
        }
        for (char i : ransomNote.toCharArray()) {//我只关心ran那个能不能全部抵消
            if (map.get(i) < 0) {
                return false;
            }
        }
        return true;
    }

3、 三数之和----经典双指针

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意: 答案中不可以包含重复的三元组。

思路

  • 1、第一反应哈希表 但是不重复不好办 改双指针
  • 2、i遍历数组,left = i + 1, right = size - 1,三个相加和0比大小(前提是排序)
  • 3、细节去重 i的去重注意不能和i+1比;left和right去重
  • 4、2个if剪枝:当nums【i】 > 0时;当nums[i] == nums[i - 1]时

代码注意点

  1. list的new后面是ArrayList<>()
  2. 排序:Array.sort(nums)
  3. 数组注意空指针的限制,一个是i>0,一个是left > right
  4. 对i的去重只能nums[i] == nums[i - 1],因为往后移如果和前面相同就不用再比了,如果是i+1就变成数组内部比较了
  5. 2维list的添加有两种方法:1、result.add(Arrays.asList(...)) 2、new一个一维数组list,在list里add进去后,再add到二维数组result里
  6. 当sum = 0的情况添加到数组后,左右指针都要向中心移动一位,别忘了
  7. 当left和right向中间移动遇到相同值的情况,while判断条件里也要left
  8. if (i > 0 && nums[i] == nums[i - 1]) continue; //大于0的这个条件得放前面

    public List<List<Integer>> threeSum(int[] nums) {
        //不重复不用哈希,三指针搞上
        //i遍历,left = i + 1, right = size - 1,三个相加和0比大小(前提是排序)
        //细节去重 i的去重注意不能和i+1比;left和right去重
        List<List<Integer>> result = new ArrayList<>();//arraylist
        Arrays.sort(nums);//排序
        for (int i = 0; i < nums.length; i++){
            if (nums[i] > 0) return result;//说明后面不可能有了,直接返回
            if (i > 0 && nums[i] == nums[i - 1]) continue;//要加i>0限制
            int left = i + 1;
            int right = nums.length - 1;
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum > 0) right--;
                else if (sum < 0) left++;
                else {
                    result.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    // List<Integer> list = new ArrayList<>();
                    // list.add(nums[i]);
                    // list.add(nums[left]);
                    // list.add(nums[right]);
                    // result.add(list);
                    while (left < right && nums[right] == nums[right -1]) right--;
                    while (left < right && nums[left] == nums[left + 1]) left++;
                    //别忘了更新啊,相等之后肯定两个都要动一位才有可能实现结果
                    right--;
                    left++;
                }
            }
        }
        return result;
    }

时间复杂度o(n2)

4、 四数之和

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

思路

  • 三数之和的翻版,多套一个for
  • 2for算和,while两指针比较
  • 2个for都要去重,注意j限制条件跟着i变,不再是0了!! 但是不要随便剪枝了,要两个条件: >target和 >0  
class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        //三数之和的翻版,多套一个for
        //剪枝 >target >0  
        //2for算和,while两指针比较
        //2个for都要去重
        List<List<Integer>> result = new ArrayList<>();
        Arrays.sort(nums);
        for (int i = 0; i < nums.length; i++) {
            //注意target可以是任意值,不能随便剪枝
            if (nums[i] > target && nums[i] > 0) return result;//剪枝
            if (i > 0 && nums[i] == nums[i - 1]) continue;//去重
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[i] + nums[j] > target && nums[j] > 0) continue;//这只能说跳出这个循环不能返回结果了
                if (j > i + 1 && nums[j] == nums[j - 1]) continue;//j不能再用0限制了,要根据i变
                int left = j + 1, right = nums.length -1;
                while (left < right) {
                    long sum = (long)nums[i] + nums[j] + nums[left] + nums[right];
                    if (sum > target) right--;
                    if (sum < target) left++;
                    if (sum == target){
                        result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
                        while(left < right && nums[right] == nums[right - 1]) right--;
                        while(left < right && nums[left] == nums[left + 1]) left++;
                        right--;
                        left++;
                    }
                }            
            }
        }
        return result;
    }
}

🈶溢出判断,所以四个数相加的和要转化为long型的

可以不用剪纸

5、总结

  1. 对于continue和break在三数之和的区别

    continue是下一个i 还存在有可能的情况, break是无论后面多少个i我们是确定不会再出现这样的情况了

  2. 数组作为哈希表

    242.有效的字母异位词 (opens new window)中,我们提到了数组就是简单的哈希表,但是数组的大小是受限的!这道题目包含小写字母,那么使用数组来做哈希最合适不过。

    383.赎金信 (opens new window)中同样要求只有小写字母,那么就给我们浓浓的暗示,用数组!

  3. set作为哈希表

    349. 两个数组的交集 (opens new window)中我们给出了什么时候用数组就不行了,需要用set。这道题目没有限制数值的大小,就无法使用数组来做哈希表了。              在202.快乐数 (opens new window)中,我们再次使用了unordered_set来判断一个数是否重复出现过

  4. map作为哈希表

    1.两数之和 (opens new window)中map正式登场。

    来说一说:使用数组和set来做哈希法的局限。数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。map是一种<key, value>的结构,本题可以用key保存数值,用value在保存数值所在的下标。所以使用map最为合适。

  5. 不可用哈希表

    四数相加为四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑重复问题,而18. 四数之和 (opens new window)15.三数之和 (opens new window)是一个数组(集合)里找到和为0的组合,可就难很多了!

!!记住,遇到需要判断一个元素是否出现过的场景应该第一时间想到哈希法!!

拜拜~~

  • 39
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值