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

news/2025/11/18 22:25:17/文章来源:https://www.cnblogs.com/tlnshuju/p/19239543

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

2025-11-18 22:21  tlnshuju  阅读(0)  评论(0)    收藏  举报

资源引用:

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 map1 = new HashMap<>();Map 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 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 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 map = new HashMap();//统计两个数组中的元素之和,同时统计出现的次数,放入mapfor (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 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> threeSum(int[] nums) {List> 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]) {  // 去重acontinue;}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> fourSum(int[] nums, int target) {Arrays.sort(nums);  // 排序数组List> 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;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/969406.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

以太网交换机的吞吐量

目录一、吞吐量的定义二、关键概念:三个决定吞吐量的核心指标1. 背板带宽(交换容量)2. 包转发率3. 吞吐量三、如何判断一台交换机的吞吐量是否合格?四、影响实际吞吐量的其他因素总结一、吞吐量的定义 简单来说,吞…

Traefik:Go 实现的云原生反向代理,微服务路由自动化利器

还在手动修改 Nginx 配置文件吗?每次新增服务都要 reload 担心出错?今天分享一个 Go 语言开发的反向代理项目 Traefik,它能监听容器平台的服务变化,自动生成路由规则,彻底告别手动配置。Traefik 是专为微服务和容…

罗盘

二十四山向三角形房子凶宅比较多

第一章 语法基础——语法基础

一、框架using namespace std;二、输入输出 cin>>a>>b cout<<a<<" "<<b<<\n 注意:自动判断数据类型 cin如果输入字符串遇到空格会停止,可以用 string s getline(cin,…

计算机网络中最短帧长的概念

目录一、核心问题:为什么需要最短帧长?二、解决方案:定义最短帧长三、具体数值是如何计算的?四、在现代网络中的意义总结这是一个在共享式以太网(如使用集线器的网络)中至关重要的概念,其核心目的是为了检测冲突…

Cypher语法

目标:掌握 Cypher 的基本语法规则,能独立完成 “创建 - 查询 - 更新 - 删除”(CRUD)操作,理解图数据的表达逻辑。 1. 先搞懂 3 个核心语法符号(基础中的基础) Cypher 语法高度可视化,记住这 3 个符号就能描述任…

2025江浙沪方向专线物流、(冷库)往返运输、智能仓储优选服务商推荐:深耕江苏苏州、高邮、镇江,覆盖全国及国际线路,供应链定制方案/当日往返物流/智能共享仓储/分拨中心

随着区域经济一体化加速,高效精准的物流服务成为商贸企业降本增效的关键。在2025年物流市场中,山东大金物流有限公司(以下简称“大金物流”)凭借近二十年的线路深耕与标准化服务体系,成为江浙沪及多区域专线物流的…

【Wireshark数据分析实战】 - 指南

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

【贪心】P9525 [JOIST 2022] 团队竞技 / Team Contest 题解

Problem \(\text{Description}\) 给定 \(n\) 个含有三元组 \((x_i ,y_i ,z_i)\),需要从中选出三个不同的对编号 \(i\),\(j\),\(k\),使得 \(x_i +y_j +z_k\) 最大,并且 \(x_i > \max\{x_j ,x_k\} ,y_j > \ma…

noip9

11.1611.16 感觉大家这场挂分比较严重啊,我都到rk4了。 顺带一提,这场是原场,洛谷上都有原题(但数据太水了,不如原数据) t1 模拟题。 赛时没算时间复杂度,用了个set以为对了,赛后才发现若卡满还不如暴力跳(多…

常见的steam游戏的营销错误

https://howtomarketagame.com/wp-content/uploads/2023/05/Zukowski_60MistakesEbookV1.pdf 翻译版下载 https://files-cdn.cnblogs.com/files/sanyejun/%E5%B8%B8%E8%A7%81%E7%9A%84steam%E8%90%A5%E9%94%80%E9%94%9…

MX Round 26 解题报告

T1 场上 没有头绪,打了挂大分的 \(O(n^2)\) 暴力。 T2 场上 没有头绪,打了挂打分的 \(O(n^2\log n)\) 暴力。 T3 切了。 一个观察:操作二只会往前走。 状态定义为“到每一个位置的期望花费”,我只想得到 \(O(n^3)\…

linux c 编译命令

在 Linux 系统中,C 语言的编译通常使用 gcc(GNU C Compiler)来进行编译。下面是一些常见的 C 语言编译命令和相关说明:1. 编译单个源文件 gcc -o output_file source_file.c-o output_file:指定输出文件名(例如 …

N8N工作流中文转换神器!一键转中文

无论是 N8N 官方的工作流模板,还是社区开源的工作流都是英文的,这对国内用户来说是非常不友好的。 因此,我花了一些时间开发了一个「N8N 工作流中文转换器」,一键将英文工作流转换为中文,先看效果吧。 这是原版工…

今天学习黑马的Java基础

今天学习黑马的Java基础,进行补充生成一个随机数Random r = new Random(); int number = r.nextInt(10);//生成[0,10)之间的一个随机整数,左开右闭Math.random()//会随机生成[0,1)的小数生成验证码实验import java.u…

linux c 线程编程

在 Linux 系统中进行 C 线程编程,通常使用 POSIX 线程(pthread),这是 Linux 系统中标准的线程库。下面我将为你提供一些常见的 C 线程编程示例和解释,帮助你理解如何在 Linux 环境下进行线程编程。? 一、C 线程编…

容器网络虚拟化

参考资料:深入理解Linux网络 相关实际问题 1、容器中的eth0和母机上的eth0是一个东西吗? 2、veth设备是什么,它是如何工作的? 3、Linux是如何实现虚拟网络环境的? 4、Linux如何保证同宿主机上多个虚拟网络环境中的…

整体二分学习笔记

整体二分学习笔记 整体二分,就是对所有的操作进行一个整体的二分答案,需要数据结构题满足以下性质:询问的答案具有可二分性。 修改对判定答案的贡献相对独立,修改之间互不影响效果。 修改如果对判定答案有贡献,则…

CF1721F Matching Reduction

CF1721F Matching Reduction 题目 给定一个二分图,第一部分有 \(n_1\) 个顶点,第二部分有 \(n_2\) 个顶点,共有 \(m\) 条边。该图的最大匹配是指选取尽可能多的边,使得没有任何一个顶点被多于一条选中的边连接。 你…