【力扣一刷】代码随想录day7(454、 383、15、18、哈希表总结)

【 454.四数相加II 】

方法一  看了思路之后自己写的

思路:将4个数组组合成2个,再利用两数之和的思路

1、获取nums1和nums2的各元素之和(key)出现的次数(value) map12

2、获取nums3和nums4的各元素之和(key)出现的次数(value) map34

3、使map12是短的一方,map34是长的一方(希望可以加快一点点)

4、map12和map34的key如果相加为0,则返回值加上对应value相乘的结果

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        Map<Integer, Integer> map12 = new HashMap<>();
        Map<Integer, Integer> map34 = new HashMap<>();

        // 获取nums1和nums2的各元素之和(key)出现的次数(value)
        for(int i = 0; i < nums1.length; i++){
            for(int j = 0; j < nums2.length; j++){
                int temp = nums1[i] + nums2[j];
                if (!map12.containsKey(temp)){
                    map12.put(temp, 1);
                }
                else{
                    map12.put(temp, map12.get(temp) + 1);
                }
            }
        }

        // 获取nums3和nums4的各元素之和(key)出现的次数(value)
        for(int i = 0; i < nums3.length; i++){
            for(int j = 0; j < nums4.length; j++){
                int temp = nums3[i] + nums4[j];
                if (!map34.containsKey(temp)){
                    map34.put(temp, 1);
                }
                else{
                    map34.put(temp, map34.get(temp) + 1);
                }
            }
        }

        // 使map12是短的一方,map34是长的一方
        Map<Integer, Integer> map;
        if (map12.size() > map34.size()){
            map = map12;
            map12 = map34;
            map34 = map;
        }

        // map12和map34的key如果相加为0,则返回值加上对应value相乘的结果
        int res = 0;
        for (int i: map12.keySet()){
            int temp = 0 - i;
            if (map34.containsKey(temp)){
                res += map12.get(i) * map34.get(temp);
            }
        }
        return res;
    }
}

时间复杂度: O(n²)

空间复杂度: O(n²)

方法二  优化后的版本

思路:可以在遍历nums3和nums4的时候直接计算方案数,简化代码,加快速度

注意:default V getOrDefault(Object key, V defaultValue)的使用可以免除键是否存在的判断,因为当键不存在时,getOrDefault函数会返回默认值defaultValue,而普通的get函数会返回null。

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        Map<Integer, Integer> map12 = new HashMap<>();
        Map<Integer, Integer> map34 = new HashMap<>();

        // 获取nums1和nums2的各元素之和(key)出现的次数(value)
        for(int i = 0; i < nums1.length; i++){
            for(int j = 0; j < nums2.length; j++){
                int temp = nums1[i] + nums2[j];
                map12.put(temp, map12.getOrDefault(temp, 0) + 1);
            }
        }

        // 在遍历nums3和nums4的时候寻找可行的组合
        int res = 0;
        for (int i = 0; i < nums3.length; i++){
            for (int j = 0; j < nums4.length; j++){
                res += map12.getOrDefault(0 - nums3[i] - nums4[j], 0);
            }
        }
        return res;
    }
}

时间复杂度: O(n²)

空间复杂度: O(n²)

【 383. 赎金信 】

方法一  利用HashMap

思路:

1、遍历magazine构建字母(key)和字母出现的次数(value)的映射map

2、遍历ransomNote,遍历到的字母在map中存在就-1,不存在就直接返回false

  • 如果map中的字母次数被用透支了,直接返回fasle
  • 如果map中的字母次数没被用完/刚好被用完,就返回true
class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        Map<Character, Integer> map = new HashMap<>();

        // 遍历magazine构建字母(key)和字母出现的次数(value)的映射map
        for (int i = 0; i < magazine.length(); i++){
            char c = magazine.charAt(i);
            map.put(c, map.getOrDefault(c, 0) + 1);
        }

        // 遍历ransomNote,遍历到的字母在map中存在就-1,不存在就直接返回false
        for (int i = 0; i < ransomNote.length(); i++){
            char c = ransomNote.charAt(i);
            if (!map.containsKey(c)){
                return false;
            }
            else{
                map.put(c, map.get(c) - 1);
                if (map.get(c) < 0) return false;  // 如果map中的字母次数被用透支了,直接返回fasle
            }
        }
        return true;  // 如果没用完/刚好用完,就返回true
    }
}

时间复杂度: O(m+n),m是字符串magazine的长度,n是字符串ransomNote的长度

空间复杂度: O(m),HashMap的长度最大等于字符串magazine的长度

方法二  利用数组构建字母哈希表

思路:

1、利用字母的排序构建哈希表的索引,字母出现的次数作为值,哈希表为长度26的数组

2、遍历字符串magazine,填充哈希表

3、遍历字符串ransomNote,哈希表对应字母次数-1

如果次数小于0,则直接返回false

如果遍历完,就返回true

优化:

可以先判断字符串ransomNote的长度是否大于字符串magazine,大于则肯定不够用,直接返回false即可

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        int[] hashtable = new int[26];

        // 优化,通过长度判断加快速度
        if (ransomNote.length() > magazine.length()) return false;

        // 遍历magazine,填充哈希表
        for (int i = 0; i < magazine.length(); i++){
            char c = magazine.charAt(i);
            hashtable[c - 'a'] += 1;
        }

        // 遍历randomNote,令哈希表对应字母次数-1,如果-1后出现负值,则直接返回false
        for (int i = 0; i < ransomNote.length(); i++){
            char c = ransomNote.charAt(i);
            hashtable[c - 'a'] -= 1;
            if (hashtable[c - 'a'] < 0) return false;
        }

        // 如果遍历完randomNote,对应字母处都没有出现负值,则返回true
        return true;
    }
}

时间复杂度: O(m+n),m是字符串magazine的长度,n是字符串ransomNote的长度

空间复杂度: O(1),hashtable为固定长度的数组,和输入数据的长度无关

【 15. 三数之和 】

方法  双指针

思路:

1、排序

令数据升序排序,方便判断后续左右指针的移动

2、遍历数组

  • 判断当前遍历的值是否已经大于0,如果大于0就可以直接返回结果了
  • 判断当前遍历的值是否和上一个遍历的值相等,相等就可以跳过操作,实现【去重操作1】
  • 定义双指针的初始指向,左指针left指向遍历元素的后一位,右指针right指向数组的最后一位元素

3、根据 int s = nums[i] + nums[left] + nums[right]的值的大小,进行结果记录和指针操作

  • s=0:记录结果,同时移动左右指针,判断左右指针是否与旧值相等,相等则继续移动,实现【去重操作2】;
  • s>0:证明s太大了,要减小,只有左移 right 才能减小;
  • s<0:证明s太小了,要增大,只有右移 left 才能增大;

注意:

涉及的两次去重操作,都是为了避免对返回值resLists进行去重,两次去重操作分别是针对遍历元素的指针和左右指针进行的。

优化:

1、对数组排序时,可以直接使用 Arrays 工具类提供的 sort 函数简化代码,加快速度,其时间复杂度为O(nlogn)

2、将数组转换为 List 集合时,可以直接使用 Arrays 工具类提供的 asList 函数简化代码,asList返回的List的元素类型为数组元素类型的包装类,如数组的元素是int型,则转换后的List的元素是Integer型。

3、在做题时要多思考循环的截止条件,例如此题如果遍历到的值已经大于0,那么后面的元素肯定大于当前遍历的值,三个数相加不可能等于0,就可以停止遍历,直接返回结果了。

4、双指针使得时间复杂度由暴力解的O(n^3)减小为O(n^2)。

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        // 冒泡排序:O(n²)
        // for (int i = nums.length - 1; i > 0; i--){
        //     for (int j = 1; j <= i; j++){
        //         if (nums[j] < nums[j-1]){
        //             int temp = nums[j];
        //             nums[j] = nums[j - 1];
        //             nums[j - 1] = temp;
        //         }
        //     }
        // }

        // Arrays类提供的快速排序算法: O(nlogn)
        Arrays.sort(nums);  

        List<List<Integer>> resLists = new ArrayList<>();
        for (int i = 0; i < nums.length - 2; i++){
            // 优化
            if (nums[i] > 0) return resLists;

            // 当前遍历值=上一遍历值,【去重操作1】
            if (i > 0 && nums[i] == nums[i - 1]) continue; 

            int left = i + 1;
            int right = nums.length - 1;
            while (left < right){
                int s = nums[i] + nums[left] + nums[right];
                if (s == 0){
                    // 记录结果
                    // List<Integer> resList = new ArrayList<>();
                    // resList.add(nums[i]);
                    // resList.add(nums[left]);
                    // resList.add(nums[right]);
                    // resLists.add(resList);
                    resLists.add(Arrays.asList(nums[i], nums[left], nums[right]));  // 减少内存消耗

                    // 同时操作左右指针
                    left++;
                    right--;

                    // 【去重操作2】
                    while (left < right && nums[left] == nums[left - 1]){
                        left++;
                    }
                    while (left < right && nums[right] == nums[right + 1]){
                        right--;
                    }

                }
                // 太大了,right左移让s小一点
                else if (s > 0){
                    right--;
                }
                // 太小了,left右移让s大一点
                else{
                    left++;
                }
            }
        }
        return resLists;
    }
}

时间复杂度: O(n²),for循环内部嵌套了while

空间复杂度: O(1),返回值不需要考虑空间复杂度

【18. 四数之和 】

方法  双指针

思路:

1、排序

2、两层for循环+左右指针遍历

  • 每层for开始时,都要判断当前遍历值与上一个遍历值是否相等,进行【去重操作1】
  • s=0,左右指针同时移动的时候,要进行【去重操作2】
class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        // 1、排序,O(nlogn)
        Arrays.sort(nums);

        // 2、两层for循环+左右指针
        List<List<Integer>> resLists = new ArrayList<>();
        // 第一层for循环
        for (int i = 0; i < nums.length - 3; i++){
            // 优化/剪枝
            if (nums[i] > target && nums[i] > 0) return resLists;
            
            // 去重操作1
            if (i > 0 && nums[i] == nums[i - 1]) continue;

            // 第二层for循环
            for (int j = i + 1; j < nums.length - 2; j++){
                // 去重操作1
                if (j > i + 1 && nums[j] == nums[j-1]) continue;

                // 左右指针初始化
                int left = j + 1;
                int right = nums.length - 1;

                // 左右指针遍历
                while (left < right){
                    int s = nums[i] + nums[j] + nums[left] + nums[right];
                    if (s > target){
                        right--;
                    }
                    else if (s < target){
                        left++;
                    }
                    else{ // s = target
                        resLists.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
                        
                        // 同时移动左右指针
                        left++;
                        right--;

                        // 去重操作2
                        while (left < right && nums[left] == nums[left - 1]){
                            left++;
                        }
                        while (left < right && nums[right] == nums[right + 1]){
                            right--;
                        }
                    }
                }
            }
        }
        return resLists;
    }
}

总结:

1、双指针法将时间复杂度:O(n^4)的解法优化为 O(n^3)的解法。

2、此题相加的和不是0,而是任意值target,如果target<0,nums[i]>target,nums[i]加上后面的值可能会更小,即还有可能达到题目条件的组合未遍历完。只有当nums[i] > target,同时nums[i]>0,才能截止遍历返回结果。

时间复杂度: O(n^3),两层for循环内部嵌套了while

空间复杂度: O(1),返回值不需要考虑空间复杂度

【 总结  】

1、数组比Map构建哈希表更有效

2、双指针相比暴力法,可以将时间复杂度降低一个级别

3、重点理解数组、集合、映射三者构成哈希表的区别

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值