算法DAY7 | 454.四数相加II / 383.赎金信 / 15.三数之和 / 18.四数之和

454.四数相加II

题目链接

  • 标签:哈希表
  • 难度:5.0

类比两数之和,这题的思路就比较简单了。

先求其中的两个数组都能组成哪些和,存在HashMap里,其中key是都有哪些和,value是这个和出现的次数。

再求剩下的两个数组能组成哪些和,每求出一个和,找key里是否存在它的相反数,如果存在,则累加value

public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
    // 创建一个map,其中key是前两个数组都有哪些和,value是这个和出现的次数
    Map<Integer,Integer> map1 = new HashMap();

    // 遍历前两个数组,求和,放进map里
    for(int i = 0; i < nums1.length; i++){
        for(int j = 0; j < nums2.length; j++){
            int sum = nums1[i] + nums2[j];
            // 如果map里有这个和,则value++
            // 没有,就把这个和放进去,value置为1
            if(map1.containsKey(sum)){
                map1.put(sum,map1.get(sum) + 1);
            }else{
                map1.put(sum,1);
            }
        }
    }

    // res存满足要求的元组个数
    int res = 0;

    // 遍历后两个数组,求和,找map里是否有值为和的相反数的
    for(int i = 0; i < nums3.length; i++){
        for(int j = 0; j < nums4.length; j++){
            int sum = nums3[i] + nums4[j];
            // 如果有,累加res
            if(map1.containsKey(0-sum)){
                res += map1.get(0-sum);
            }
        }
    }
    return res;
}

383.赎金信

题目链接

  • 标签:哈希表
  • 难度:4.5

这个题和有效的字母异位词很像,不同的是这次我选用了HashMap。当然代码还有优化的空间,因为我忘了HashMap的API了,但不得不承认整体写法比较繁琐。这种简单的题就不要杀鸡用牛刀了,直接开一个长度26的数组用作哈希表

来看代码随想录对HashMap和数组的选择:

一些同学可能想,用数组干啥,都用map完事了,其实在本题的情况下,使用map的空间消耗要比数组大一些的,因为map要维护红黑树或者哈希表,而且还要做哈希函数,是费时的!数据量大的话就能体现出来差别了。 所以数组更加简单直接有效!

方法一:HashMap(不推荐)
public boolean canConstruct(String ransomNote, String magazine) {
    Map<Character,Integer> magazineMap = new HashMap();
    for(int i = 0; i < magazine.length(); i++){
        Character magazineChar = magazine.charAt(i);
        if(!magazineMap.containsKey(magazineChar)){
            magazineMap.put(magazineChar,1);
        }else{
            magazineMap.put(magazineChar,magazineMap.get(magazineChar) + 1);
        }
    }

    for(int i = 0; i < ransomNote.length(); i++){
        Character ransomNoteChar = ransomNote.charAt(i);
        if(!magazineMap.containsKey(ransomNoteChar)){
            return false;
        }
        if(magazineMap.get(ransomNoteChar) > 0){
            magazineMap.put(ransomNoteChar,magazineMap.get(ransomNoteChar) - 1);
        }else{
            return false;
        }
    }
    return true;
}
方法二:用数组做哈希表
public boolean canConstruct(String ransomNote, String magazine) {
    // 定义一个哈希映射数组
    int[] record = new int[26];

    // 遍历
    for(char c : magazine.toCharArray()){
        record[c - 'a'] += 1;
    }

    for(char c : ransomNote.toCharArray()){
        record[c - 'a'] -= 1;
    }

    // 如果数组中存在负数,说明ransomNote字符串总存在magazine中没有的字符
    for(int i : record){
        if(i < 0){
            return false;
        }
    }

    return true;
}

15.三数之和

题目链接

  • 标签:双指针
  • 难度:6.0

这个题类比两数之和的逻辑。两数之和是把其中一个数加入到哈希表里,再次遍历数组的同时查找哈希表。三数之和在此基础上,引入了双指针的思想,固定其中一个元素,让另两个元素所在的指针不断移动

请添加图片描述

这个方法不是很好想,写起来也有很多细节要注意。一个重要的问题就在于去重的实现。代码随想录提到:

如果我们的写法是 这样:

if (nums[i] == nums[i + 1]) { // 去重操作
    continue;
}

那就我们就把 三元组中出现重复元素的情况直接pass掉了。 例如{-1, -1 ,2} 这组数据,当遍历到第一个-1 的时候,判断 下一个也是-1,那这组数据就pass了。

我们要做的是 不能有重复的三元组,但三元组内的元素是可以重复的!

所以这里是有两个重复的维度。

那么应该这么写:

if (i > 0 && nums[i] == nums[i - 1]) {
    continue;
}

这么写就是当前使用 nums[i],我们判断前一位是不是一样的元素,在看 {-1, -1 ,2} 这组数据,当遍历到 第一个 -1 的时候,只要前一位没有-1,那么 {-1, -1 ,2} 这组数据一样可以收录到结果集里。

public List<List<Integer>> threeSum(int[] nums) {
    // 保存结果的数组
    List<List<Integer>> res = new ArrayList<>();

    int i = 0;

    // 先排序
    Arrays.sort(nums);

    // 固定第一个指针
    for(i = 0; i < nums.length; i++){
        // 如果最小的那个数都大于0,那直接return空数组
        if(nums[i] > 0){
            return res;
        }
        // 如果和上一个数相同,则跳过,防止重复
        if(i > 0 && nums[i] == nums[i-1]){
            continue;
        }
        // 左右指针
        int left = i+1;
        int right = nums.length - 1;
        // 当右大于左的时候,调整左右指针
        while(right > left){
            // 如果三个指针之和大于0,则向左调整右指针
            if(nums[i] + nums[left] + nums[right] > 0){
                right--;
                // 如果三个指针之和小于0,则向右调整左指针
            }else if(nums[i] + nums[left] + nums[right] < 0){
                left++;
                // 如果三个指针之和等于0
            }else{
                // 加入结果集
                res.add(Arrays.asList(nums[i], nums[left], nums[right]));
                // 同时移动左右指针,也要考虑重复的问题
                while (right > left && nums[right] == nums[right - 1]) right--;
                while (right > left && nums[left] == nums[left + 1]) left++;
                right--; 
                left++;
            }
        }
    }
    return res;
}

18.四数之和

题目链接

  • 标签:双指针
  • 难度:6.5

这是七天以来第一道难度超过6.0的题目。和上道题一样,难点还是在去重和剪枝,要类比来看。

public List<List<Integer>> fourSum(int[] nums, int target) {
    List<List<Integer>> result = new ArrayList<>();
    Arrays.sort(nums);

    for (int i = 0; i < nums.length; i++) {

        // 区别一:target不知道是大于还是小于0,所以剪枝操作改为nums[i] > target
        if (nums[i] > 0 && nums[i] > target) {
            return result;
        }

        if (i > 0 && nums[i - 1] == nums[i]) {
            continue;
        }
		// 区别二:四数之和要固定两个指针
        for (int j = i + 1; j < nums.length; j++) {

            if (j > i + 1 && nums[j - 1] == nums[j]) {
                continue;
            }

            int left = j + 1;
            int right = nums.length - 1;
            while (right > left) {
                long sum = (long) nums[i] + nums[j] + nums[left] + nums[right];
                if (sum > target) {
                    right--;
                } else if (sum < target) {
                    left++;
                } else {
                    result.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));

                    while (right > left && nums[right] == nums[right - 1]) right--;
                    while (right > left && nums[left] == nums[left + 1]) left++;

                    left++;
                    right--;
                }
            }
        }
    }
    return result;
}

15.三数之和双指针法就是将原本暴力O(n3)的解法,降为O(n2)的解法,四数之和双指针法就是将原本暴力O(n4)的解法,降为O(n3)的解法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值