首页 > 其他分享 >代码随想录第七天|哈希表part02--454.四数相加II、383. 赎金信、15. 三数之和、18. 四数之和

代码随想录第七天|哈希表part02--454.四数相加II、383. 赎金信、15. 三数之和、18. 四数之和

时间:2024-11-06 22:17:48浏览次数:3  
标签:四数 15 nums int 随想录 value right new left

资源引用:

leetcode题目:

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

383.赎金信(383. 赎金信 - 力扣(LeetCode)

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

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

例行碎碎念:

今天也追赶上了一些进度,虽然生病感冒,但今天很好的坚持了从早到晚的复习,秉承开源的精神我也将自己的复习资料整理出来分享给其他同学,这也给了我一些成就感!在晚上虽然很疲惫,但还是坚持完成了一天的代码随想录,哈希表的利弊变得更加清晰,数组的小而美也令我认知刷新,更重要的是自己能够很好的复用先前学到的技巧(尤其是双指针),继续加油!

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

题目分析:

四个长度为n的整数数组,分别从中取元素组成四项元组,问有多少元组的项之和为0,最后返回符合条件的元组数,这是讨论是否存在的问题,并返回存在数量的问题,需要存储key为元素值,value为元素个数,考虑使用HashMap。

解题思路:

四个数组的长度均为n,则元组最多有n^4个。边界条件是n为零,即四个数组均为空时,直接返回0。

关键在于是四个map,还是value是四个下标。

若采用四个下标的形式进行存储,则时间复杂度极高,与暴力解法没有差别,且key值不能重复,该解法无法实现。

考虑使用类似二分法的思想,将num1和num2作为一个组合,将num3和num4作为一个组合,每个组合分别具有n^2个元组,时间复杂度从n^4缩减到n^2,第一个组合用map1存储,key为num1和num2中元素和的不同情况,value为不同情况存在的组合数;第二个组合用map2存储同理。对map1中的每个key值,从map2中检查是否存在其相反数,若存在,则将二者的value值相乘并加到返回值res上。

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        if(nums1.length==0 || nums2.length==0 || nums3.length==0 || nums4.length==0){
            return 0;
        }
        Map<Integer,Integer> map1 = new HashMap<>();
        Map<Integer,Integer> map2 = new HashMap<>();
        int res=0;
        for(int i : nums1){
            for(int j : nums2){
                int sum1 = i+j;
                int new_value=0;
                if(map1.containsKey(sum1)){
                    new_value = map1.get(sum1)+1;
                }else{
                    new_value = 1;
                }
                map1.put(sum1,new_value);
            }
        }
        for(int k : nums3){
            for(int l : nums4){
                int sum2 = k+l;
                int new_value=0;
                if(map2.containsKey(sum2)){
                    new_value = map2.get(sum2)+1;
                }else{
                    new_value = 1;
                }
                map2.put(sum2,new_value);
            }
        }
        for (Map.Entry<Integer, Integer> entry : map1.entrySet()) {
            int sum1 = entry.getKey();
            if(map2.containsKey(-sum1)){
                res+=(entry.getValue()*map2.get(-sum1));
            }
        }
        return res;
    }
}

进一步简化:

c和d遍历时无需放入哈希表中,为减少开销,若存在(c,d)组合的两数之和的相反数存在于map1中,直接取map1对应的value加到计数res上即可。

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        if(nums1.length==0 || nums2.length==0 || nums3.length==0 || nums4.length==0){
            return 0;
        }
        Map<Integer,Integer> map1 = new HashMap<>();
        int res=0;
        for(int i : nums1){
            for(int j : nums2){
                int sum1 = i+j;
                int new_value=0;
                if(map1.containsKey(sum1)){
                    new_value = map1.get(sum1)+1;
                }else{
                    new_value = 1;
                }
                map1.put(sum1,new_value);
            }
        }
        for(int k : nums3){
            for(int l : nums4){
                int sum2 = k+l;
                if(map1.containsKey(-sum2)){
                    res+=map1.get(-sum2);
                }
            }
        }
        return res;
    }
}

再进一步简化:

map.getOrDefault(sum, 0) 的使用:如果存在key为sum的值则返回其value,否则返回默认值(此处参数将其规定为0)

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

383.赎金信(383. 赎金信 - 力扣(LeetCode)

题目分析:

两个字符串,判断ransomNote中的字符是否全部包含在magazine中,且每个字符是否有其一一对应,即magazine中的每个字符只能在ransomNote,由于是存在问题且不仅需要匹配元素值还要匹配个数,考虑用HashMap解决。

解题思路:

map的键值对存储magazine中的元素值及其出现次数,遍历ransomNote中的每个字符,如果该字符在map中存在,若其value值不为0则将其对应的value值减一,若其value值为0则直接返回false;若该字符不存在,也直接返回false。最终返回true。

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        Map<Character,Integer> map = new HashMap<>();
        for(char i : magazine.toCharArray()){
            map.put(i,map.getOrDefault(i,0)+1);
        }
        for(char j : ransomNote.toCharArray()){
            if(map.getOrDefault(j,0) == 0){
                return false;
            }else{
                map.put(j,map.getOrDefault(j,0)-1);
            }
        }
        return true;
    }
}

反思总结:

灵活运用了for-each循环和map.getOrDefault()方法

然而,map的空间消耗和哈希函数的调用消耗导致:不论是时间层面还是空间层面,该题使用map都不如使用数组,使用数组更加简单有效!

也不要忘了控制边界条件!

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        // 边界条件
        if (ransomNote.length() > magazine.length()) {
            return false;
        }
        // 定义一个哈希映射数组
        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.三数之和(15. 三数之和 - 力扣(LeetCode)

题目分析:

由于这题要返回的是元组本身,故使用哈希表很不方便,carl哥提示用双指针法。

该题仅有一个数组nums,若长度小于3则直接返回空的List<List<Interger>> res。

关键在于怎样在遍历过程中保证i,j,k两两不相等,且返回元组不可重复。

妙解:

为了保证元组不可重复,需要在遍历过程中避免重复使用值相同的元素,所以将数组排序!

初始化:假设已经从小到大排序,下标i从0开始遍历,left=i+1,right=nums.length-1(1.注意针对i的“去重” )

寻址:若三数之和大于0,说明偏大,right左移1位;若三数之和小于0,说明偏小,left右移1位。重复“寻址”过程,直到left=right为止,在这过程中遇到三数和为0的添入List(全程注意去重!)。

关于去重:
1.i的去重:

由于已经经过排序,如果可以减少对nums[i]相同情况的遍历,但应当注意判断条件应为nums[i] != nums[i-1],而非nums[i] != nums[i+1],因为元组不能重复,但元组内的元素值可以重复!

2.三数之和为0时,left和right相遇前的去重:

参考i的去重,可知应为nums[left]!=nums[left-1],nums[right]!=nums[right+1]吗?注意处理下标防止越界——right+1存在越界风险,且由于left和right不是锚定数所以前后去重无本质区别,考虑方便实现即可。

总结反思:

1.双指针法一定要排序

2.边界条件处理:①数组长度检查②排序后,nums[i]为正数时,则三数之和不可能为0,直接返回当前result。

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new 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]) {  // 去重a
                continue;
            }
            int left = i + 1;
            int right = nums.length - 1;
            while (right > left) {
                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]));
            		    // 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
                    while (right > left && nums[right] == nums[right - 1]) right--;
                    while (right > left && nums[left] == nums[left + 1]) left++;
                    
                    right--; 
                    left++;
                }
            }
        }
        return result;
    }
}

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

题目分析:

/*与454.四数相加Ⅱ不同之处在于:①在同一个数组中取数,由于下标不可重复,因此相较于对4个数组操作的难度增大②返回值是满足条件的四元组,而非元组数*/

在15.三数之和的基础上仍然使用双指针。

解题思路:

先将数组排序,a从0下标开始遍历,注意剪枝和去重

嵌套循环b从a+1开始遍历,注意剪枝和去重

初始化left=b+1,right=nums.length-1,

循环不变量是right>left。

b在nums.length-3处为最后一次,a在nums.lengh-4处为最后一次

其他步骤参考15.三数之和。

tips:同样注意检查剪枝条件:①nums数组长度不小于4②此题target并非确定的值,需要进一步约束剪枝条件为nums[i] > target && (nums[i] >=0 || target >= 0)

总结反思:

1.双指针法就是将类似N数之和的问题的时间复杂度从O(n^N)降为O(n^(N-1))

2.注意数值大小,为防止溢出,需要使用长整型long记录sum

import java.util.*;
public class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        Arrays.sort(nums);  // 排序数组
        List<List<Integer>> result = new ArrayList<>();  // 结果集
        for (int k = 0; k < nums.length-3; k++) {
            // 剪枝处理
            if (nums[k] > target && nums[k] >= 0) {
                break;
            }
            // 对nums[k]去重
            if (k > 0 && nums[k] == nums[k - 1]) { //注意当k为0时k-1越界,故做此处理
                continue;
            }
            for (int i = k + 1; i < nums.length-2; i++) {
                // 第二级剪枝
                if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {
                    break;
                }
                // 对nums[i]去重
                if (i > k + 1 && nums[i] == nums[i - 1]) { //与k为0时的处理类似,但重点是i为k+1时不必去重
                    continue;
                }
                int left = i + 1;
                int right = nums.length - 1;
                while (right > left) {
                    long sum = (long) nums[k] + nums[i] + nums[left] + nums[right];
                    if (sum > target) {
                        right--;
                    } else if (sum < target) {
                        left++;
                    } else {
                        result.add(Arrays.asList(nums[k], nums[i], nums[left], nums[right]));
                        // 对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 result;
    }
}

标签:四数,15,nums,int,随想录,value,right,new,left
From: https://blog.csdn.net/csy031117/article/details/143581236

相关文章

  • 代码随想录算法训练营第十八天|leetcode530.二叉搜索树的最小绝对差、leetcode501.二
    1leetcode530.二叉搜索树的最小绝对差题目链接:530.二叉搜索树的最小绝对差-力扣(LeetCode)文章链接:代码随想录视频链接:你对二叉搜索树了解的还不够!|LeetCode:98.验证二叉搜索树_哔哩哔哩_bilibili思路:定义一个极大值作为结果,然后在中序遍历过程中进行比较出结果1.1自己的......
  • NOIP2024 模拟赛 #15 总结
    Larunatrecy:信心赛。赛时T1求中位数,想起前两天做过的[ABC203D]Pond,考虑了二分答案。看出二分答案后不会做了,罚坐\(20\)min。然后发现我傻逼了,选出一个区间翻转,可以通过钦定右端点,找到最优的左端点得到,神仙Heldivis就出过一道这样的题。写完后调了下二分边界过了大样......
  • chapter15
    relocation.py参数第一题问题用种子1、2和3运行,并计算进程生成的每个虚拟地址是处于界限内还是界限外?如果在界限内,请计算地址转换。种子为1时:种子为2时:种子为3时:第二题问题使用以下标志运行:-s0-n10。为了确保所有生成的虚拟地址都处于边界内,要将-l(界限寄......
  • macOS15.1及以上系统bug:开发者证书无法打开,钥匙串访问无法打开一直出现图标后立马闪退
    团队紧跟苹果最新系统发现bug:今日设备信息如下,希望能带给遇到这个问题的开发者一点帮助。错误图如下:点击证书文件后,先出现钥匙串访问图标,后立马闪退消失中间试过很多方法,都是一样的表现,最后好在解决了,看网上也没有相关的帖子,这里直接写解决办法和导致原因。&......
  • KBP315-ASEMI小功率高耐压整流桥3A 1500V
    编辑:llKBP315-ASEMI小功率高耐压整流桥3A1500V型号:KBP315品牌:ASEMI封装:KBP-4安装方式:直插批号:2024+现货:50000+正向电流(Id):3A反向耐压(VRRM):1600V正向浪涌电流:50A正向电压(VF):1.10V引脚数量:4芯片个数:4芯片尺寸:MIL功率(Pd):小功率工作温度:-55°C~150°C类型:整流扁桥、插......
  • 150道MySQL高频面试题,学完吊打面试官--InnoDB索引与MyISAM索引实现的区别+一个表中如
    前言本专栏为150道MySQL大厂高频面试题讲解分析,这些面试题都是通过MySQL8.0官方文档和阿里巴巴官方手册还有一些大厂面试官提供的资料。MySQL应用广泛,在多个开发语言中都处于重要地位,所以最好都要掌握MySQL的精华面试题,这也是面试官最喜欢问的,现在面试官在面试的时候更关......
  • P1534 不高兴的津津(升级版)
    P1534不高兴的津津(升级版)通过37.47k时间限制1.00s内存限制125.00MB提交答案加入题单做题计划(首页)个人题单团队题单保存题目提供者情到深处人孤独难度入门历史分数100 提交记录  查看题解标签洛谷原创 查看算法标签进入讨论版相关讨论 查看讨论推荐题......
  • 关于我、重生到500年前凭借C语言改变世界科技vlog.15——深入理解指针(4)
    文章目录1.回调函数的介绍2.qsort使用实例2.1qsort函数介绍2.2使用qsort函数排序整型数据2.3使用qsort排序结构数据3.qsort的模拟实现希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动力!1.回调函数的介绍回调函数就是一个通过函数指针调用的......
  • GS-Blur数据集:首个基于3D场景合成的156,209对多样化真实感模糊图像数据集。
    2024-10-31,由韩国首尔国立大学的研究团队创建的GS-Blur数据集,通过3D场景重建和相机视角移动合成了多样化的真实感模糊图像,为图像去模糊领域提供了一个大规模、高覆盖度的新工具,显著提升了去模糊算法在真实世界场景中的泛化能力。数据集地址:GS-Blur|图像去模糊数据集|图像处理......
  • CMU_15445_P2_Extendible_Hash_Table
    到Project2,我们依然在处理数据库存储相关的部分,从Project1中我们应该Get到两个概念:数据库底层数据操作的基本单元是Page.buffer_pool_manager是管理以及组织数据单元Page的工具,在Project2的第一部分,我们还新增了页面守护(PageGuard)的机制更加优雅的获取以及释放......