目录
一、454.四数相加II
1.解题思路
题目给了四个数组,要求从每个数组中选取一个元素,然后四个元素相加,计算有多少组四数之和为0。
暴力求解:直接上四个for循环,遍历四数之和为0,然后计数器++,时间复杂度为O(n^4)。
哈希表求解:利用unordered_map哈希表结构,将前两个数组两两之和(a+b)的各种可能保存到umap(unordered_map)中,(注意,这里key值为a + b,value值为a+b两数之和出现的次数),然后再遍历后面两个数组,只要从umap中寻找是否有值为“0 - (c + d)”,如果查找到了,就将value值加到计数器里。时间复杂度为O(n^2)
2.代码实现
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
//先遍历前两个数组,将元素之和存放到unordered_map
unordered_map<int,int> umap;
for(int a : nums1){
for(int b : nums2){
umap[a+b]++;//记录a+b出现了多少次,
}
}
//开始遍历后两个数组,然后在unorder_map中查找 0-(c+d)
int count = 0;//count用来记录出现了多少次 0-(c+d)
for(int c : nums3){
for(int d : nums4){
int target = 0 - (c + d);
count += umap[target];
}
}
return count;
}
};
二、 383. 赎金信
1.暴力求解
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
//暴力算法
//两个for循环遍历,判断是否magazine中有ransomNote中的字符
//这里需要注意的两个for循环顺序
for(int i = 0;i < magazine.size();i++){
for(int j = 0;j < ransomNote.size();j++){
//如果在ransomNote中找到和magazine相同的字符,就删除掉(因为不能重复使用magazine中字符)
if(ransomNote[j] == magazine[i]){
ransomNote.erase(ransomNote.begin() + j);
//既然已经删除了,就break跳出本轮循环
break;
}
}
}
//如果最后ransomNote中没有字符了,说明magazine中包含其所有的字符
if(ransomNote.length() == 0){
return true;
}
else
return false;
}
};
2.哈希解法
分析:字符串中都是小写英文字母,只有26个且有序,所以可以用数组来记录。将magazine中的字符出现次数记录,然后ransomNote中的字符在这个数组中对应做减减操作,如果这个字符次数<0,说明magazine中的字符不够组成救赎信中的字符。
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int record[26] = {0};//本题字符串中为小写英文字母,可以用哈希数组
//将magazine中的字符映射到数组中
for(int i = 0;i < magazine.length();i++){
record[magazine[i] - 'a']++;
}
//因为magazine中字符只能用一次,ransomNote中的字符在数组中对应--,
for(int j = 0;j < ransomNote.length();j++){
record[ransomNote[j] - 'a']--;
//如果出现负数,说明magazine中的字符不够组成ransomNote
if(record[ransomNote[j] - 'a'] < 0)
return false;
}
return true;
}
};
三、15. 三数之和
1.解题思路(双指针法)
①先将数组排序(升序),定义一个二维数组,用来存放三元组结果集。
② for循环遍历,i表示第一个数,然后定义双指针left,right。left初始化为i的后一个元素,right初始化为数组最后一个元素。
③进行剪枝操作。如果nums[i]的值>0,而此数组又是升序排列,所以left和right肯定也是大于0,此时数组中不可能存在三元组使得三数之和=0,直接return结果集就行。
④进行去重操作。题目要求三元组不能重复,所以这里判断一下:
if(i > 0 && nums[i] == nums[i - 1])
continue;
需要注意的是,这里对下标为i-1的元素进行操作,所以判断语句中要对i的值进行判断:i > 0。
还需要注意,这里不能写成nums[i] == nums[i + 1]。如果这样写,对于-1,-1,2这种情况就忽略掉了。
⑤如果三数之和大于0,此时right指向的值最大,就进行right--操作;如果三数之和小于0,就进行left++操作;如果三数之和等于0,则将三元组存放到结果集中。
⑥还需要对left和right进行去重操作。去重结束后,左右指针分别++--,寻找以i为第一个数的下一个三元组,最后返回结果集
2.代码实现(双指针法)
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;//定义一个二维数组,用来存放结果集
//一定要先将nums数组排序
sort(nums.begin(),nums.end());
for(int i = 0;i < nums.size();i++){
//数组目前是升序,如果第一个数就大于0了,那么后面不可能再出现三数之和等于0的三元组
if(nums[i] > 0)
return result;
//去重操作
if(i > 0 && nums[i] == nums[i - 1]){
//注意这里不能写nums[i] == nums[i + 1],这样写会漏掉 -1,-1,2的情况
continue;//还要注意这里的判断条件里面要加 i > 0
}
//定义双指针,left代表第二个数,right代表第三个数,i代表第一个数
int left = i + 1;
int right = nums.size() - 1;
while(left < right){//题目要求三个数的下标不相同,所以这里不能写left <= right
if(nums[i] + nums[left] + nums[right] > 0){//要将三数之和变小,就是将right往左移
right--;
}
else if(nums[i] + nums[left] + nums[right] < 0){//要将三数之和变大,就是将left右移
left++;
}
else{//此时三数之和 = 0
//存放到结果集result中
result.push_back(vector<int>{nums[i],nums[left],nums[right]});
//此时找到了一组符合题目要求的三元组,开始寻找以这个i开头的,其他三元组
//先去重
while(left < right && nums[left] == nums[left + 1])
left++;
while(left < right && nums[right] == nums[right - 1])
right--;
//去重之后,双指针分别进行++--操作
left++;
right--;
}
}
}
return result;
}
};
四、18. 四数之和
1.解题思路(双指针法)
本题和“15. 三数之和 ”有异曲同工之处,在三数之和的基础上,外面再套一个for循环。但是其中也是有几点需要注意的:
①在一级和二级剪枝操作时,不可以像三数之和一样如果第一个数大于target,就直接返回。因为这里target不是三数之和里的0,如果为负数,这样写就错误了。
因此,在这做剪枝操作时,要加一个前提条件,nums[k] > 0。
②去重操作要记得加防止数组越界的条件
③判断四数之和的时候,强制转化成长整型long,不然数值溢出了。
2.代码实现(双指针法)
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++){
//一级剪枝,这里注意target可能是负数,不能像三元组那样剪枝
if(nums[k] > target && nums[k] > 0)
break;
//一级去重,注意这里下标越界和如何如何去重
if(k > 0 && nums[k] == nums[k - 1])
continue;
for(int i = k + 1;i < nums.size();i++){
//二级剪枝
if(nums[k] + nums[i] > target && nums[k] + nums[i] > 0)
break;
//二级去重
if(i > k + 1 && nums[i] == nums[i - 1])
continue;
//定义双指针
int left = i + 1;
int right = nums.size() - 1;
while(left < right){
if((long)nums[k] + nums[i] + nums[left] + nums[right] > target)
right--;
else if((long)nums[k] + nums[i] + nums[left] + nums[right] < target)
left++;
else {//此时四数之和=target,存放到结果集,并寻找以k,i开头的其他四元组
result.push_back(vector<int>{nums[k],nums[i],nums[left],nums[right]});
//对left和right进行去重
while(left < right && nums[left] == nums[left + 1])
left++;
while(left < right && nums[right] == nums[right - 1])
right--;
//更新left和right位置,寻找下一个四元组
left++;
right--;
}
}
}
}
return result;
}
};
五、总结
三数之和和四数之和的问题,题目要求进行去重,再用哈希表来求解便很难,这里两道题用双指针法,进行剪枝去重操作。四数之和就是在三数之和的基础上,多套了一个for循环。