383、赎金信
题目描述
给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。如果可以构成,返回 true ;否则返回 false。
说明:magazine中的每个字符串都只能使用一次。
思路
- 要注意,magazine中的字符不能重复使用。
- 使用暴力解法逐个遍历,寻找相应字符。
- 哈希解法通过使用数组存储26个英文字母及其在magazine中出现的频数,遍历ransomNote,每出现一次某个字母,相应频数减一,若对应频数小于0时,说明magazine中的字符不足以组成ransomNote。
暴力解法
代码:
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
//针对极端情况优化
if (ransomNote.length() > magazine.length()) {
return false;
}
//外层循环遍历magazine,以此模拟magazine中每个字符只能用一次
for (int i = 0; i < magazine.length(); i++) {
//遍历ransomNote
for (int j = 0; j < ransomNote.length(); j++) {
if (magazine[i] == ransomNote[j]) {
ransomNote.erase(ransomNote.begin() + j);//在ransomNote中删除已经找到的字符
break;
}
}
}
//如果ransomNote为空,说明magazine中的字符可以组成ransomNote
if (ransomNote.length() == 0) {
return true;
}
return false;
}
};
时间复杂度:O(n2);双循环使用O(n2),erase函数也很耗时间。
空间复杂度:O(1);
哈希解法
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int count[26] = {0};//创建一个数组,存储26个字母在magazine中出现的相应频数
//针对极端请款进行优化
if (ransomNote.length() > magazine.length()) {
return false;
}
//遍历magazine,记录其中字母出现的频数
for (int i = 0; i < magazine.length(); i++) {
count[magazine[i] - 'a']++;//相应字母的频数加一
}
//遍历ransomNote
for (int j = 0; j < ransomNote.length(); j++) {
count[ransomNote[j] - 'a']--;//相应字母的频数减一
//如果相应字母的频数小于0,证明以上频数减一的操作时,该字母频数已经为0
if (count[ransomNote[j] - 'a'] < 0) {
return false;
}
}
return true;
}
};
时间复杂度:O(n);分别遍历ransomNote和magazine分别为O(n),因此总体时间复杂度为O(2n);
空间复杂度:O(1);只使用固定的额外空间。
15、三数之和
题目描述
给定一个整数数组nums,在nums中找到nums[i]、nums[j]、nums[k],使得三者相加等于0.
i、j、k互不相等。
返回所有符合要求的nums[i]、nums[j]、nums[k]三元组。
总思路
- 在一个数组中找到所有相加等于0的三元组。
- 三元组的值可以相等,但不能是同一个值。
- 结果集中的三元组不能重复。
- 分别用a,b,c代表三元组中的元素
哈希解法
思路:
- 使用两个for循环确定a和b,然后使用哈希结构set确定
0-(a+b)
是否再数组中存在过。
把符合条件的三元组放入vector中。
根据题目中不可以包含重复三元组的条件进行去重操作
代码:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;//用来存储结果
sort(nums.begin(),nums.end());//将nums从小到大排序
//a+b+c=0
//a = nums[i] b = nums[j] c = 0 - (a+b)
for (int i = 0; i < nums.size(); i++) {
//排序之后如果三元组的第一个数已经大于0,则不可能满足题目要求
if (nums[i] > 0) {
break;
}
//对三元组中的a去重
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
unordered_set<int> set;
for (int j = i + 1; j < nums.size(); j++) {
if (j > i + 2 && nums[j] == nums[j-1] && nums[j-1] == nums[j-2]) {
//三元组中的b去重
continue;
}
int c = 0 - (nums[i] + nums[j]);
//在集合中寻找有无符合要求的值,如果有
if (set.find(c) != set.end()) {
res.push_back({nums[i], nums[j], c});//把这个符合元素的三元组加入res中
set.erase(c);//c去重
} else {
set.insert(nums[j]);
}
}
}
return res;
}
};
时间复杂度:O(n2);
空间复杂度:O(n);额外的set开销。
双指针法
思路:
- 用下标i(for循环)寻找a,left指针寻找b,right寻找c
- 将原数组排序,以便之后根据顺序更好查找三元组。
- 不断有规律地移动三个指针,找到符合要求地三元组。
- 搜索过程中的一个重点:去重(目的是去除相同的三元组)
代码:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;//二维数组res存储三元组结果集
sort(nums.begin(),nums.end());//对数组排序
//开始搜索符合要求的三元组
for (int i = 0; i < nums.size(); i++) {
if (nums[i] > 0) return res;//如果每次搜索的三元组a(最小的元素)都大于0,证明没有符合要求的三元组
//去重
if (i > 0 && nums[i] == nums[i-1]) continue;//如果相邻两个元素相等,那么搜索到符合元素的三元组也一定相同,开始下一轮搜索
int left = i + 1, right = nums.size() - 1;//初始化指向b和c的指针
//开始搜索b和c
while (right > left) {
if (nums[i] + nums[left] + nums[right] > 0) right--;//移动right指针,搜索c
else if (nums[i] + nums[left] + nums[right] < 0) left++;//移动left指针,搜索b
else {
res.push_back(vector<int>{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++;
//找到答案时,双指针同时收缩,来继续寻找下一对b和c
right--;
left++;
}
}
}
return res;
}
};
时间复杂度:O(n2);
空间复杂度:O(1);
对于去重逻辑地思考
- 去重的目标
不能有重复的三元组。
- 什么情况下会形成重复的三元组?
对于a、b、c三个值中的每一个来说,在遍历时,只要(a/b/c)前后两个遍历到的值相同,那么形成的符合要求的三元组一定会出现相同的情况。
- 因此,要排除a、b、c中三个值中任意一个值前后相等的情况
即
nums[i]==nums[i+1]
ornum[i] == nums[i-1]
- 对a的情况去重:
i > 0 && nums[i] == nums[i-1]
- 对b的情况去重:
right > left && nums[right] == nums[right - 1]
- 对c的情况去重:
right > left && nums[left] == nums[left + 1]
四数之和
题目描述
给定一个包含n个整数的数组nums和一个目标值target。从nums中找出所有的[a,b,c,d]四元组使得a+b+c+d=target。
条件:
- 四个值的下标互不相同。
- 四元组不能重复(若两个四元组的元素一一对应则认为两个四元组重复)
思路
搜索:使用两层for循环确定a和b,使用双指针搜索c和d,找到符合要求的组合返回。
去重:对于四元组的每个元素,凡是前后重复出现的都需要去重,跳到nums中的下一个值开始遍历。
剪枝:对于target,凡是前面遍历到的值已经表现出不可能等于target时,就没有必要继续搜索了。(因为已经没有符合要求的四元组了)
代码
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
sort(nums.begin(),nums.end());
for (int i = 0; i < nums.size(); i++) {
//剪枝
if (nums[i] > target && nums[i] >= 0) break;//这里用break,统一到最后用return返回
//针对nums[i]进行去重
if (i > 0 && nums[i] == nums[i-1]) continue;
for (int j = i+1; j < nums.size();j++) {
//2级剪枝
if (nums[i] + nums[j] > target && nums[i] + nums[j] > 0) break;
//针对nums[j]进行去重
if (j > i+1 && nums[j] == nums[j-1]) continue;
//定义双指针
int left = j + 1;
int right = nums.size() - 1;
//搜索
while (right > left) {
if ((long) nums[i] + nums[j] + nums[left] + nums[right] > target) right--;
else if ((long) nums[i] + nums[j] + nums[left] + nums[right] < target) left++;
else {
res.push_back(vector<int>{nums[i],nums[j],nums[left],nums[right]});//把结果加入res
//对nums[left]和nums[right]去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
//找到答案后,双指针同时收缩,继续寻找
left++;
right--;
}
}
}
}
return res;
}
};
时间复杂度:O(n3);两个for循环使用O(n2),双指针共同搜索nums中剩下的值,使用O(n);总体使用O(n3)。
空间复杂度:O(1);