代码随想录第七天 | Leecode 454.四数相加II 、383. 赎金信 、15. 三数之和 、18. 四数之和
Leecode 454. 四数相加II
题目描述
- 给你四个整数数组
nums1
、nums2
、nums3
和nums4
,数组长度都是n
,请你计算有多少个元组(i, j, k, l)
能满足: 0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
- 示例 1:
- 输入:
nums1 = [1,2]
,nums2 = [-2,-1]
,nums3 = [-1,2]
,nums4 = [0,2]
- 输出:
2
- 解释:
- 两个元组如下:
(0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
(1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0
- 两个元组如下:
- 示例 2:
- 输入:
nums1 = [0]
,nums2 = [0]
,nums3 = [0]
,nums4 = [0]
- 输出:
1
解题思路与代码展示
考虑使用unordered_map
容器,首先统计前两个数组求和得到的每个结果的数量,再对后两个数组求和,并取和的负数在容器中已经统计的数量求和。这样即可计算得到四个数组四数之和为0的数量。具体代码如下:
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int,int> map; // 初始化哈希映射键值对
int count = 0; // 初始化满足条件的四数之和的数量为0
for(int num1 : nums1){
for(int num2 : nums2){
map[num1 + num2]++; // 计算前两个数组中求和后的结果并统计数量,将每个出现的求和作为键将其对应的值+1
}
}
for(int num3 : nums3){
for(int num4 : nums4){
if(map.find(-(num3+num4)) != map.end()) // 如果后两个数组求和结果的负值作为键能够在map容器中找到对应的值
count += map[-(num3+num4)]; // 则将该值进行累加计数
}
}
return count;
}
};
使用上面代码,即可计算得到四数之和为0的组合数量。分析上面代码,由于题目已经给定说明四个数组的长度都是\(n\),那么可以推算时间复杂度为\(O(n^2)\).
Leecode 383. 赎金信
题目描述
给你两个字符串:ransomNote
和 magazine
,判断 ransomNote
能不能由 magazine
里面的字符构成。
如果可以,返回 true
;否则返回 false
。
magazine
中的每个字符只能在 ransomNote
中使用一次。
- 示例 1:
- 输入:
ransomNote = "a"
,magazine = "b"
- 输出:
false
- 输入:
- 示例 2:
- 输入:
ransomNote = "aa"
,magazine = "ab"
- 输出:
false
- 输入:
- 示例 3:
- 输入:
ransomNote = "aa"
,magazine = "aab"
- 输出:
true
- 输入:
解题思路及代码展示
本题需要判断能否用字符串magazine
中的字符组成ransomNote
中的字符,一开始乍一看没反应过来这两个字符串的长度大小关系。后来突然注意到本题的标题“赎金信”对应字符串ransomNote
,而字符串magazine
表示杂志。只要联想场景绑匪为了不暴露自己的字迹,通过剪下杂志字符串中的字符来拼凑组成赎金信。那么这种情况下每个字符只能使用一次,即字符串ransomNote
中的字符应该是真包含于字符串magazine
中的。同时又需要注意这里并非表示集合,因为字符串中的字符是可以重复多次出现的。
在理解了题目之后,我们来思考如何解决这个问题。其实如果代入“绑匪”视角,为了判断能否组成赎金信,当然首先整理并统计一下目前杂志中每个字符都出现了多少次。随后根据赎金信中所需要用到字符逐一取出并使用。如果在需要某个字符的时候,整理统计好的字符中已经全部用完,那么则说明字符串不够。如果最终顺利组装完成了赎金信,则说明可以完成。
那么根据上面思想,我们可以写出下面代码:
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
unordered_map<char,int> umap; // 初始化map键值对,可以根据字符类型的键在O(1)时间内搜索到存储的对应的int类型的值
for(int i = 0; i < magazine.size(); i++){ // 遍历杂志中所有的字符
umap[magazine[i]]++; // 将统计所有的字符,每个字符对应的数量+1
}
for(int i = 0; i < ransomNote.size(); i++){ // 遍历赎金信
if(umap[ransomNote[i]]-- <= 0) return false; // 赎金信每用一个字符就将统计的键值对中对应的值-1;如果此时值小于等于0,则说明不够,返回false
}
return true; // 如果遍历完赎金信都还没有出现字符不够的情况,则说明可以组成赎金信,返回true
}
};
上面代码可以看出,时间复杂度在\(O(m+n)\),即将两个字符串各遍历一遍即可完成这个算法。
Leecode 15. 三数之和
题目描述
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请你返回所有和为 0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
- 示例 1:
- 输入:
nums = [-1,0,1,2,-1,-4]
- 输出:
[[-1,-1,2],[-1,0,1]]
- 解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0
。nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0
。nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0
。- 不同的三元组是
[-1,0,1]
和[-1,-1,2]
。 - 注意,输出的顺序和三元组的顺序并不重要。
- 输入:
- 示例 2:
- 输入:
nums = [0,1,1]
- 输出:
[]
- 解释:唯一可能的三元组和不为
0
。
- 输入:
- 示例 3:
- 输入:
nums = [0,0,0]
- 输出:
[[0,0,0]]
- 解释:唯一可能的三元组和为
0
。
- 输入:
解法一,暴力求解
首先是一个使用四层for循环的,很纯粹的暴力解。即先对数组进行排序,再遍历三个指针的所有可能的不重复的取值,如果三个值求和相等则取出。取出之后再对已经找到的解矩阵中使用一层for循环来判断当前数组是否重复。如果重复则丢弃,不重复则放入当前结果矩阵中。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(),nums.end()); // 先进行一次快排
for(int i = 0; i < nums.size()-2; i++){ // 第一个指针i取值从0到长度-3
for(int j = i + 1; j < nums.size()-1; j++){ // 第二个指针 j 取值从 i + 1到长度-2
for(int k = j + 1; k < nums.size(); k++){ // 第三个指针 k 取值从 j + 1到长度-1
if(nums[i]+ nums[j] + nums[k] == 0) { // 如果三数之和满足条件
bool exist = false; // 初始化当前值是否重复的布尔变量
vector<int> newVec({nums[i], nums[j], nums[k]}); // 先将当前三个值组成vector数组
for(int l = 0; l < result.size(); l++){ // 逐一判断已经找到的解集,是否已经有当前找到的vector
if(newVec == result[l]) exist = true; // 如果已经找到,则更改exist为true
}
if(exist) continue; // 如果当前vector已经找到,则直接继续循环查找其他值
result.push_back(newVec); // 如果当前vector并不存在与解集中,则将其放入解集
}
}
}
}
return result;
}
};
上面算法最差情况下的时间复杂度为\(O(n^4)\),最好情况也有\(O(n^3)\)。都是非常高的时间复杂度。而且该算法也不能通过Leecode网站的所有测试用例(因为会超时)。因此我们再考虑使用更高效的算法。
解法二,双指针
双指针法根据已知过大还是过小的信息来确定指针查找的方向,以此来降低搜索满足条件的解的算法的时间复杂度。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result; // 初始化解集
sort(nums.begin(),nums.end()); // 对输入的vector进行排序
for(int a = 0; a < nums.size() - 2; a++){ // 遍历第一个指针a可能取到从0到n-3
if(a > 0 && nums[a] == nums[a-1]) continue;
// 除了当前a在初始起点的时候之外,如果当前值和上一个值相同,说明后续找到的另外两个指针对应的值也相同,故直接跳过
int left = a + 1; // left指针起始值从当前a的下一个开始开始往右搜索
int right = nums.size() - 1; // right指针从最右侧往左搜索
while(left < right){ // 当left指针和right指针还未相碰时,则一直进行循环
int sum2 = nums[left] + nums[right]; // 对后两个指针对应的值求和
if( sum2 == -nums[a]) { // 如果求和的值是a对应的值的相反数,则说明求和为0
result.push_back(vector<int>({nums[a], nums[left++], nums[right-- ]})); // 则将当前数组记录,同时左右指针都需要移动一位
while(left < nums.size() && nums[left] == nums[left-1]) left++; // 如果左右指针在刚移动了一位之后,当前值和上一步的值还相同,需要一直继续移动
while(right > left && nums[right] == nums[right+1]) right--; // 直至左右指针都找到一个新的取值时再进入下一次循环
}
else if(sum2 > -nums[a]) right--; // 如果当前三个值求和大于0,说明right指针需要减小
else left++; // 如果当前三个值求和小于0,说明left指针需要变大
}
}
return result;
}
};
使用上面算法,即可在去重的前提下,找到所有的三数之和为0的组合。时间复杂度为\(O(n^2)\).
Leecode 18. 四数之和
题目描述
给你一个由
n
个整数组成的数组nums
,和一个目标值target
。请你找出并返回满足下述全部条件且不重复的四元组[nums[a], nums[b], nums[c], nums[d]]
(若两个四元组元素一一对应,则认为两个四元组重复):0 <= a, b, c, d < n
a
、b
、c
和d
互不相同nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。示例 1:
- 输入:nums = [1,0,-1,0,-2,2], target = 0
- 输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
示例 2:
- 输入:nums = [2,2,2,2,2], target = 8
- 输出:[[2,2,2,2]]
解题思路与代码展示
本题思路和上一题非常类似,都是使用双指针,同时根据当前求和偏大还是偏小来相应地调整两个指针的搜索方向。以此来减少所需要遍历的可能解的数量。但和上一题的区别在于,需要多加一层循环,以此来表示多一个指针的可能取值。
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result; // 定义result作为解集存放所有解
if(nums.size() < 4) return result; // 如果当前长度小于4,则四数之和无意义,直接返回result
sort(nums.begin(),nums.end()); // 对vector容器进行排序
for(int a = 0; a < nums.size() - 3; a++){ // 第一个指针a的取值从0到数组长度n-4
if(a != 0 && nums[a] == nums[a-1]) continue; // 避免重复查找,如果第一个指针在移动过后,和上一次的数值还相同,则需要继续移动
for(int b = a+1; b < nums.size() - 2; b++){ // 第二个指针b的取值从a+1到数组长度n-3
if(b != a+1 && nums[b] == nums[b-1]) continue; // 同样,如果指针b在移动过后,和上一次的数组还相同,则需要继续移动
int left = b + 1; // 左指针可取的最小值为b+1
int right = nums.size()-1; // 右指针可取值为数组中最后一位,即长度n-1
while(left < right){ // 只要左右指针还未相碰,则一直遍历搜索
long sum = (long)nums[a] + (long)nums[b] + (long)nums[left] + (long)nums[right]; // 计算四数之后,此时int类型会溢出,因此转换为long类型
if(sum == target){ // 如果求和满足条件
result.push_back(vector<int>({nums[a],nums[b],nums[left++],nums[right--]})); // 将找到的数组成数组存放入解集,并同时移动left和right指针
while(left < right && nums[right] == nums[right+1]) right--; // 如果左右指针移动过后,取值还和刚才相等,需要一直移动到值不相等
while(left < right && nums[left] == nums[left-1]) left++;
}
else if(sum > target) right--; // 如果四数之和太大,则需要令right指针减小
else left++; // 如果四数之后太小,则需要让left指针变大
}
}
}
return result;
}
};
本题上面算法之比上一题多了一层for循环,其余过程都非常相近。因此可以知道时间复杂度为\(O(n^3)\).
今日总结
今天继续学习了哈希表部分的题目,更进一步熟悉了unordered_set
容器和unordered_map
容器。
代码随想录第七天 | Leecode 454.四数相加II 、383. 赎金信 、15. 三数之和 、18. 四数之和的更多相关文章
- 代码随想录算法训练营day07 | leetcode 454.四数相加II 383. 赎金信 15. 三数之和 18. 四数之和
LeetCode 454.四数相加II 分析1.0 这个最直接暴力法,不过过于暴力了,害怕.jpg 失误 读题理解失误:题目要求的是四元组的个数,读完题到我这里成了输出四元组,悲哉 分析2.0 记录有 ...
- 【算法训练营day7】LeetCode454. 四数相加II LeetCode383. 赎金信 LeetCode15. 三数之和 LeetCode18. 四数之和
[算法训练营day7]LeetCode454. 四数相加II LeetCode383. 赎金信 LeetCode15. 三数之和 LeetCode18. 四数之和 LeetCode454. 四数相加I ...
- 代码随想录第七天| 454.四数相加II、383. 赎金信 、15. 三数之和 、18. 四数之和
第一题454.四数相加II 给你四个整数数组 nums1.nums2.nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足: 0 <= i, ...
- Java实现 LeetCode 454 四数相加 II
454. 四数相加 II 给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0. 为 ...
- Leetcode 454.四数相加II
四数相加II 给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0. 为了使问题简单 ...
- 【哈希表】leetcode454——四数相加II
编号454:四数相加II 给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0. 为 ...
- 四数相加II & 赎金信 & 三数之和 & 四数之和
一.四数相加Ⅱ 454. 四数相加 II 1.方法概述 首先定义一个map,key放a和b两数之和,value 放a和b两数之和出现的次数.遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到 ...
- LeetCode454. 四数相加 II
题目 给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0. 分析 关键是如何想到用 ...
- 454. 四数相加 II
给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0. 为了使问题简单化,所有的 A ...
- LeetCode 454.四数相加 II(C++)
给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0. 为了使问题简单化,所有的 A ...
随机推荐
- C# TorchSharp 图像分类实战:VGG大规模图像识别的超深度卷积网络
目录 图像分类 | VGG大规模图像识别的超深度卷积网络 数据集 直接下载 opendatalab 数据集社区 自定义数据集 模型训练 教程名称:使用 C# 入门深度学习 作者:痴者工良 教程地址: ...
- Typora Emoji图标
转自: https://www.cnblogs.com/wangjs-jacky/p/12011208.html People :smile: :laughing: :blush: :sm ...
- Typecho如何添加微博表情包
自从添加了蛆音娘表情包就想着去爬点其他地方的表情包- 使用教程跟蛆音娘一样 :点我查看 #表情包代码: "微博":{ "type": "usr&quo ...
- NCS开发学习笔记-基础篇-第 1 课 – nRF Connect SDK 简介
第 1 课 – nRF Connect SDK 简介 目标 了解 nRF Connect SDK 的结构和内容 在内部,nRF Connect SDK 代码分为四个主要存储库: nrf – 应用程序. ...
- PHP对表单提交特殊字符的过滤和处理方法汇总
http://www.jb51.net/article/46921.htm PHP关于表单提交特殊字符的处理方法做个汇总,主要涉及htmlspecialchars/addslashes/stripsl ...
- 原生开发,使用C语言调用Windows API 开发软件思路分享
Githu: https://github.com/vladelaina/Catime 作者是一个高度依赖计时器功能的人,但是市面上的软件都不能满足个性化的需求,所以打算自己动手开发,同时采用c语言来 ...
- helm Error: INSTALLATION FAILED: cannot re-use a name that is still in use
前言 使用helm安装服务报错,修改chat后重新安装报错:安装失败:无法重复使用仍在使用的名称 解决方法 1.查找安装失败的服务 helm -n {namespace} ls -a 2.删除安装失败 ...
- 关闭windows计划重启
前言 windows 总是自动计划更新 解决方案 需要禁用服务 "Windows Update" 和 "更新 Orchestrator 服务" 首先去这里下载P ...
- golang 使用goto进行多错误处理
goto 语句介绍 在 Go 语言中,可以通过goto语句跳转到标签,进行代码间的无条件跳转.另外,goto语句在快速跳出循环.避免重复退出方面可以简化代码实现过程,但在结构化程序设计中一般不主张使用 ...
- Django实战项目-学习任务系统-发送短信通知
接着上期代码内容,继续完善优化系统功能. 本次增加发送短信通知功能,学习任务系统发布的任务,为了更加及时通知到学生用户,再原有发送邮件通知基础上,再加上手机短信通知功能. 第一步:开通短信通知服务 目 ...