【代码训练营】day7 | 454.四数相加II & 383. 赎金信 & 15. 三数之和 & 18. 四数之和

98 阅读1分钟

四数相加II LeetCode 454

题目链接:四数相加II LeetCode 454 - 中等

思路

四数相加相当于 两个两数相加

    int sum = 0;
    // hash表key用来存nums1+nums的所有情况,value存这些情况出现的次数
    Map<Integer,Integer> map = new HashMap<>();
    // 记录
    for (int i = 0; i < nums1.length; i++) {
        for (int j = 0; j < nums2.length; j++) {
            int temp = nums1[i] + nums2[j];
            if (!map.containsKey(temp)){
                map.put(temp, 1);
            }else {
                map.put(temp, map.get(temp) + 1);
            }
        }
    }

    // 判断nums3+nums4 ?= -nums1+num2,是就记录一次
    for (int i = 0; i < nums3.length; i++) {
        for (int j = 0; j < nums4.length; j++) {
            int temp = nums3[i] + nums4[j];
            if (map.containsKey(0 - temp)){
                // 存在一次不是+1,是加num1+num2总共的次数,相当于相乘的关系
                sum += map.get(-temp);
            }
        }
    }

    return sum;
}

总结

如果直接两两相加之后再相加的话本题是会超时的,时间复杂度为O(4n^2),所以我们把nums1和nums2的和存在一个map里面,再遍历另外两个数的和,若nums1+nums2 = -nums3+nums4,就相当于和为0。记nums1+nums2=a,nums3+nums4=b,每次遍历出现b=-a的情况,我们的sum += a,这里不是+1!因为b=-a的每一种情况都对应着nums1+nums2的a种情况。

赎金信 LeetCode 383

题目链接:赎金信 LeetCode 383 - 简单

思路

直接用一个map存magazine里面出现的每一个字母,出现一次给他+1,然后在遍历ransomNote,判断是不是每个字母都在map里面,是就-1,若map中存的该数字个位为0了或是不存在这个数都返回false。

public boolean canConstruct(String ransomNote, String magazine) {
    // 用一个hash表来存magazine种出现的所有字母和次数
    Map<Character, Integer> map = new HashMap<>();
    // 第一个for循环存
    for (Character c : magazine.toCharArray()){
        if (!map.containsKey(c)){
            map.put(c, 1);
        }else {
            map.put(c,map.get(c) + 1);
        }
    }

    // 第二个for循环来消
    for (Character c : ransomNote.toCharArray()){
        // map种没有ransomNote的元素只返回false
        if (!map.containsKey(c)){
            return false;
        }else if (map.get(c) == 0){ // map中需要的元素用完了也返回false
            return false;
        }else { // map还剩有就将数量-1
            map.put(c, map.get(c) - 1);
        }
    }
    // 恰好全部消完或者还剩有都返回true;
    return true;
}

总结

这题和之前做的有效字母异为词差不多。看到这种题一开始就想到用hash来做,但是没有想到数据量比较少可以用数组来充当哈希表来做,这样的话会快很多,因为map底层要维护红黑树或哈希表,所有空间非常大,这样不划算!

示例代码:

// 用数组效率更高
public boolean canConstruct(String ransomNote, String magazine) {
    int[] hash = new int[26];
    for (char c : magazine.toCharArray()){
        hash[c - 'a']++;
    }
    for (char c : ransomNote.toCharArray()){
        hash[c - 'a']--;
    }
    for (char c : ransomNote.toCharArray()){
        // hash表里可以还剩数据,就是magazine数据更多,但是小于0了一定是少了或者不存在某个字母
        if (hash[c - 'a'] < 0){
            return false;
        }
    }
    return true;
}

三数之和 LeetCode 15

题目链接:三数之和 LeetCode 15 - 中等

思路

两数之和再加一个数,但多了一个去重的操作,可用双指针来解决。

public List<List<Integer>> threeSum(int[] nums) {
    List<List<Integer>> res = new ArrayList<>();
    // 先从小到大排序
    Arrays.sort(nums);
    for (int i = 0; i < nums.length; i++) {
        // 对i去重
        if (nums[i] > 0){
            break;
        }
        if (i >0 && nums[i] == nums[i-1]){
            continue;
        }
        // 每次得重置left与right,所有在for里面定义
        int left = i + 1;
        int right = nums.length - 1;

        while (left < right){
            int sum = nums[left] + nums[right] + nums[i];
            if (sum < 0){
                left++;
            }else if(sum > 0){
                right--;
            }else {
                res.add(Arrays.asList(nums[i],nums[left],nums[right]));
                // 去重left 往右靠
                while (left<right && nums[left] == nums[left+1]){
                    left++;
                }
                // 去重right 往左靠
                while (left<right && nums[right] == nums[right-1]){
                    right--;
                }

                // 完成一轮,左右指针都往中靠近一步
                left++;
                right--;
            }
        }
    }
    return res;

总结

自己在完成这道题的时候大题的思路知道,但是细节不太会,也就是去重不太熟悉,经常考虑不周导致进入死循环,第二就是对api不熟练,对Arrays.asList(nums[i],nums[left],nums[right])这种创建list方式不熟,应多多复习!

四数之和 LeetCode 18

题目链接: 四数之和 LeetCode 18 - 中等

思路

就是上面三数之和(已去重)再多加一个数,可直接多一层for循环解决

public List<List<Integer>> fourSum(int[] nums, int target) {
    List<List<Integer>> res = new ArrayList<>();
    // 一定要先排序
    Arrays.sort(nums);
    // 第一个数 nums[i]
    for (int i = 0; i < nums.length; i++) {
        // nums[i] > 0 正数往右才是越来越大,加起来更大,负数加起来更小
        if (nums[i] > 0 && nums[i] > target) break;
        // i去重 i剪纸
        if (i>0 && nums[i] == nums[i-1]) continue;
        // 第二个数 nums[j],从i+1开始
        for (int j = i + 1; j < nums.length; j++) {
            // j去重 j剪枝 ==> 必须满足j在i的右边
            if (j > i + 1 && nums[j] == nums[j-1]) continue;

            int left = j + 1;
            int right = nums.length - 1;
            while (left < right){
                // 数字可能比较大,int可能会溢出!所以强转为long型
                long sum =(long) nums[i] + nums[j] + nums[left] + nums[right];
                if (sum > target) {
                    right--;
                }else if (sum < target) {
                    left++;
                }else {
                    res.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));
                    // left与right去重
                    while (left < right && nums[left] == nums[left+1]) left++;
                    while (left < right && nums[right] == nums[right-1]) right--;

                    left++;
                    right--;
                }
            }
        }
    }
    return res;
}

总结

本题若没有考虑(long) nums[i] + nums[j] + nums[left] + nums[right]这一步会出错,因为四个数相加会超过int的范围。另外就是对i剪枝时if (nums[i] > 0 && nums[i] > target) break;若没写nums[i] > 0就可能会直接跳出,因为正数是越加越大,负数越加越小。