四数相加
题目关键是不用去重,并且要遍历4次数组(时间复杂度是n^4),如果用哈希表map来分开两组两组来遍历则能将时间复杂度降低为n的二次方
主要思想在于用map来存储前两组遍历完后的和的次数,然后在下一组用target-(c+d)来遍历剩下的两组,所以能满足不用去重的要求。
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int,int> umap;
int count = 0;
for(int a : nums1)
{
for(int b : nums2)
{
umap[a+b]++;
}
}
for(int c : nums3)
{
for(int d : nums4)
{
if(umap.find(0 - (c+d)) != umap.end())
{
count += umap[0 - (c+d)];
}
}
}
return count;
}
};
赎金信
关键判断b是否能包含a就行了,b是大集合,a是小集合
用哈希表来记数,key是字符串的元素,value是出现的次数,先遍历一遍大集合把所有字符出现的次数给统计一遍,再去小集合相减,当a出现了b不存在的字符时,用[key]来访问会默认给value为0,此时在判断前先–就能保证满足了当a出现了b不存在的字符时这种情况,并且也能满足a是小集合b是大集合的情况。
需要判断的两种情况:
- 小集合a中出现了b不存在的字符
- 小集合的字符多于大集合b中的字符
最优雅的解法:
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
unordered_map<char, int> umap;
for(int i=0; i < magazine.size(); i++)
{
umap[magazine[i]]++;
}
for(int i = 0; i < ransomNote.size(); i++) {
if (--umap[ransomNote[i]] < 0) {
return false;
}
}
return true;
}
};
接下来讲一个反面教材,应该直接用 [key] 来访问value而不是先find再用 [key] 来访问,因为find会先检查[key] 是否存在; 而直接用[key]来访问,当 [key] 不存在的时候则会初始化value的int为, 所以find的不满足当a出现了b中不存在的字符的情况,直接用[key]然后先 – 来判断就好了
!!!反面教材!!!
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
unordered_map<char, int> umap;
for(int i = 0; i < magazine.size(); i++) {
umap[magazine[i]]++;
}
for(int i = 0; i < ransomNote.size(); i++) {
//当ransomNote出现了magazine不存在的字符的时候就失灵了
if(umap.find(ransomNote[i]) != umap.end()) {
umap[ransomNote[i]]--;
}
}
for(const auto& pair : umap) {
if(pair.second < 0) {
return false;
}
}
return true;
}
};
正确解法
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
unordered_map<char, int> umap;
for(int i = 0; i < magazine.size(); i++) {
umap[magazine[i]]++;
}
for(int i = 0; i < ransomNote.size(); i++) {
umap[ransomNote[i]]--;
}
for(const auto& pair : umap) {
if(pair.second < 0) {
return false;
}
}
return true;
}
};
卡哥的代码
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int record[26] = {0};
//add
if (ransomNote.size() > magazine.size()) {
return false;
}
for (int i = 0; i < magazine.length(); i++) {
// 通过record数据记录 magazine里各个字符出现次数
record[magazine[i]-'a'] ++;
}
for (int j = 0; j < ransomNote.length(); j++) {
// 遍历ransomNote,在record里对应的字符个数做--操作
record[ransomNote[j]-'a']--;
// 如果小于零说明ransomNote里出现的字符,magazine没有
if(record[ransomNote[j]-'a'] < 0) {
return false;
}
}
return true;
}
};
三数之和
用三个指针来表示位置,关键是去重
- 看a的去重,举个例子,-2,-1,-1,0 ,当a指向下标0的时候-2值是合法的,指向下标1的时候-1值也是合法的,但是指向下标2的时候第二个-1值不合法,此时是出现了第二个-1了,而第一个-1已经参与使用了,因此第二个-1要和第一个-1来对比做去重,因此是当前的i要和i-1做去重。
- 看b和c的去重,b是左指针要往右移,c是右指针要往左移,所以需要判断b要和b+1对比,c要和c-1对比,一直移动到最后一个去重的元素,然后b++,c–指向下一个不同的元素。
还有一些细节的判断,类似于安全判断需要注意,请看代码。
比如a去重的时候要判断 i>0 ,不然就会检查nums[0] == nums[-1],nums[-1]超出了数组的范围,这会导致未定义行为(Undefined Behavior),可能会导致程序崩溃或其他错误。
b c去重的时候while要判断right>left
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(); i++)
{
if(nums[i] > 0)
{
return result;
}
if(i>0 && nums[i] == nums[i-1]) //a的去重
{
continue;
}
int left = i + 1;
int right = nums.size() - 1;
while(left < right)
{
if(nums[i] + nums[left] + nums[right] > 0)
{
right--;
}else if(nums[i] + nums[left] + nums[right] < 0)
{
left++;
}else if(nums[i] + nums[left] + nums[right] == 0)
{
result.push_back(vector<int>{nums[i],nums[left],nums[right]});
// 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
while(right > left && nums[left]==nums[left+1])
{
left++;
}
while(right > left && nums[right] == nums[right-1])
{
right--;
}
left++;
right--;
}
}
}
return result;
}
};
四数之和
多了一层for循环,并且target可能是负数的,所以剪枝操作不太一样,并且第二层的剪枝需要注意一下(要用前两个的和),毕竟剪枝是为了方便left和right的判断,要是符合剪枝条件了则没有必要再循环下去。
还有去重操作值得注意
第一层的 k>0是为了防止刚开始的nums[0]和nums[-1] ,访问-1就是越界了,正确写法 if(k >0 && nums[k] == nums[k-1])
第二层的 i>k+1 ,那么在内层第一次迭代时(即 i = k + 1),会比较nums[i]和nums[k]。这是不必要的,因为在i = k + 1时,我们没有前面的i可以比较。通过 i>k+1 确保了在 i 层循环时避免了nums[i]和nums[k]这种不必要的检查。
最后,四数之和记得转为long,不然提交的用例很大的话则通过不了。
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 && nums[k]>=0)
{
break;
}
//k>0是为了防止nums[0]和nums[-1]
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 if((long) nums[k] + nums[i] + nums[left] + nums[right] == target)
{
result.push_back(vector<int>{nums[k],nums[i],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;
}
};