454.四数相加II
本题是 使用map 巧妙解决的问题,好好体会一下 哈希法 如何提高程序执行效率,降低时间复杂度,当然使用哈希法 会提高空间复杂度,但一般来说我们都是舍空间 换时间, 工业开发也是这样。
给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。
为了使问题简单化,所有的 A, B, C, D 具有相同的长度 N,且 0 ≤ N ≤ 500 。所有整数的范围在 -2^28 到 2^28 - 1 之间,最终结果不会超过 2^31 - 1 。
思路
1.我们可以将四个数组分成两部分,A 和 B 为一组,C 和 D 为另外一组。
2.对于 A 和 B,我们使用二重循环对它们进行遍历,得到所有 A[i]+B[j] 的值并存入哈希映射中。对于哈希映射中的每个键值对,每个键表示一种 A[i]+B[j],对应的值为 A[i]+B[j] 出现的次数。
3.对于 C 和 D,我们同样使用二重循环对它们进行遍历。当遍历到 C[k]+D[l] 时,如果 −(C[k]+D[l]) 出现在哈希映射中,那么将 −(C[k]+D[l]) 对应的值累加进答案中。
最终即可得到满足 A[i]+B[j]+C[k]+D[l]=0 的四元组数目。
C++代码如下:
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int,int> hashmap;//key:s1+s2的数值,value:s1+s2数值出现的次数
//遍历s1和s2数组,统计两个数组元素之和还有出现的次数,放到map中
for(int a:nums1){
for(int b:nums2){
hashmap[a+b]++;
}
}
int count=0;//统计a+b+c+d=0 出现的次数
//遍历s3和s4数组,如果找到-(c+d)在map中出现过的话,就把map中key对应的value累加到count中
for(int c:nums3){
for(int d:nums4){
if(hashmap.find(-(c+d))!=hashmap.end()){
count+=hashmap[-(c+d)];
}
}
}
return count;
}
};
注意点:搞明白哈希表的key和value分别代表什么?
383.赎金信
本题 和 242.有效的字母异位词 是一个思路 ,算是拓展题
题目: 给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。如果可以构成,返回 true ;否则返回 false。
思路
这道题目和242有效的字母异位词很像,242相当于求 字符串a 和 字符串b 是否可以相互组成 ,而这道题目是求 字符串a能否组成字符串b,而不用管字符串b 能不能组成字符串a。
C代码如下:
bool canConstruct(char* ransomNote, char* magazine) {
//创建哈希表统计两个字符串中每个字母出现的次数
int recordNote[26]={0};
int recordZine[26]={0};
for(int i=0;i<strlen(ransomNote);i++){
recordNote[ransomNote[i]-'a']++;
}
for(int i=0;i<strlen(magazine);i++){
recordZine[magazine[i]-'a']++;
}
for(int i=0;i<26;i++){
if(recordNote[i]>recordZine[i]){//如果成立,说明ransomNote中某个字母个数比magazine多
return false;
}
}
return true;
}
15.三数之和
本题虽然和 两数之和 很像,也能用哈希法,但用哈希法会很麻烦,双指针法才是正解
题目:给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请
你返回所有和为 0
且不重复的三元组。
思路
1.首先对数组进行排序,这样方便后续的操作。
2.遍历排序后的数组,使用双指针法来寻找满足要求的三元组。
3.固定一个数(假设为nums[i]),然后使用双指针left和right分别指向i+1和末尾元素。
4.如果nums[i]大于零,由于数组是有序的,后续的元素都会大于零,所以不存在满足条件的三元组,可以直接返回结果。
5.如果i大于0且nums[i]等于nums[i-1],说明已经考虑过这个数,为了避免重复,我们跳过这个数。
6.根据nums[i]、nums[left]和nums[right]的和来判断如何调整指针:
如果和大于零,那么将right指针左移一位。
如果和小于零,那么将left指针右移一位。
如果和等于零,将这个三元组加入结果数组中,并同时将left和right指针向内移动,跳过重复元素。
7.重复步骤3至6,直到遍历完数组所有元素。
8.返回结果数组。
C++代码如下:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
int n=nums.size();
sort(nums.begin(),nums.end());//对数组进行排序
vector<vector<int>> answer;//存储结果的二维向量
for(int i=0;i<n-2;i++){//遍历数组,固定第一个元素
//三元组元素a去重
if(i>0 && nums[i]==nums[i-1]){
continue;
}
int left=i+1;//左指针指向固定元素的下一位
int right=n-1;//右指针指向数组末尾
while(left<right){
int sum=nums[i]+nums[left]+nums[right];//计算三个元素的和
if(sum<0){//如果和小于0,说明需要增大和,左指针右移一位
left++;
}
else if(sum>0){//如果和大于0,说明需要减小和,右指针左移一位
right--;
}
else{//和等于零,找到满足条件的三元组
answer.push_back({nums[i],nums[left],nums[right]});//将三元组添加到结果中
//避免重复的左指针元素
while(left<right && nums[left]==nums[left+1]){
left++;
}
//避免重复的右指针元素
while(left<right && nums[right]==nums[right-1]){
right--;
}
//找到答案时,双指针同时收缩
left++;//左指针右移一位
right--;//右指针左移一位
}
}
}
return answer;
}
};
难点:三元组元素(a,b,c)如何去重以及在程序的哪个位置去进行去重。
18.四数之和
要比较一下,本题和 454.四数相加II 的区别,为什么454.四数相加II会简单很多,这个想明白了,对本题理解就深刻了. 本题思路整体和三数之和一样的,都是双指针,但写的时候 有很多小细节,需要注意.
题目: 给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。
思路
四数之和,和15三数之和是一个思路,都是使用双指针法, 基本解法就是在三数之和的基础上再套一层for循环
三数之和的双指针解法是一层for循环num[i]为确定值,然后循环内有left和right下标作为双指针,找到nums[i] + nums[left] + nums[right] == 0。
四数之和的双指针解法是两层for循环nums[k] + nums[i]为确定值,依然是循环内有left和right下标作为双指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况.
C++代码如下
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;//存放结果
sort(nums.begin(),nums.end());//排序
for(int k=0;k<nums.size();k++){//遍历第一个数
if(nums[k]>target && target>=0)break;//k剪枝(如果target是负数,负数相加会变小)
if(k>0 && nums[k]==nums[k-1])continue;//k去重
for(int i=k+1;i<nums.size();i++){//i遍历第二个数
if(nums[k]+nums[i]>target && target>=0)break;//二级i剪枝
if(i>k+1 && nums[i]==nums[i-1])continue;//二级i去重
int left=i+1;
int right=nums.size()-1;
while(left<right){
//sum =nums[k]+nums[i]+nums[left]+nums[right];会溢出
long sum =(long)nums[k]+nums[i]+nums[left]+nums[right];
if(sum<target)left++;
else if(sum>target)right--;
else{
result.push_back({nums[k],nums[i],nums[left],nums[right]});//存放到结果
while(left<right && nums[left]==nums[left+1])left++;//left去重
while(left<right && nums[right]==nums[right-1])right--;//right去重
left++;
right--;
}
}
}
}
return result;
}
};
难点:搞明白四元组(a,b,c,d)分别如何剪枝加去重,以及在程序里什么位置进行。
写在最后:今天哈希表的题目难度比昨天高了一大截,也只是大概了解了每个题的解题思路,具体细节还需要再慢慢消化吸收。