本题是使用哈希法的经典题目,四个独立的数组(不同于四数之和)
本题思想方法与字母异位词解题思路很像,但不同的是数组遍历的方式,
(1)使用下标遍历字符串s:
for (int i = 0; i < s.size(); i++) 可通过索引s[i]访问字符串s的元素
(2)使用范围-based for 循环遍历数组 A:
for(int a:A) 在循环体内,通过 a
访问数组 A
中的元素
主要区别:
- 第一种方式是使用下标遍历,适用于需要根据索引访问元素的情况,例如访问字符串中的每个字符。
- 第二种方式是使用范围-based for 循环,更简洁,适用于遍历容器类(如数组、向量等)中的元素,不需要关心索引。
class Solution
{
public:
int fourSumCount(vector<int>& A, vector<int>& B, vector<int>& C, vector<int>& D)
{
undordered_map<int,int> umap; // key:a+b,value:a+b出现的次数
// 遍历大A和大B数组,统计两个数组元素之和,和出现的次数
for (int a:A) // 遍历数组A中的每个元素
{
for (int b:B)
{
umap[a+b]++; // key:a+b 的value对应+1
}
}
int count = 0; // 统计a+b+c+d出现的次数
for (int c:C)
{
for (int d:D)
{
if (umap.find(0 - (c + d)) != umap.end()) // 如果umap中存在寻找的元素
{
count += umap[0 - (c + d)]; // 更新
}
}
}
return count;
}
}
一、暴力法
num.size() 指容器中元素的个数和nums.length()指字符串的长度,对于string 而言二者通用
class Solution
{
pubilc:
bool canConstruct(string ransomNote, string magazine)
{
for (int i; i < ransomNote.size(); i++)
{
for (int j; j < magazine.size(); j++)
{
// 在ransomNote中找到和magazine相同的字符
if (magazine[i] == ransomNote[j])
{
ransomNote.erase(ransomNote.begin() + j); // ransomNote删除这个字符
// ransomNote.begin()+j 表示ransomNote中的迭代器指向第j个字符的位置
break;
}
}
}
// 如果ransomNote为空,则说明magazine的字符可以组成ransoNote
if (ransomNote.length() == 0)
{
return true;
}
return false;
}
}
二、哈希表方法
class Solution
{
public:
bool canConstruct(string ransomNote, string magazine)
{
int record[26] = {0};
//add
if (ransomNote.size() > magazine.size())
{
return false;
}
// 遍历magazine
for (int i = 0; i < magazine.length(); i++)
{
// 判断magazine中字符出现次数,并且对应位置进行计数
record[magazine[i]-'a']++;
// 字符串的加减也就是对应ASCII值的加减
}
// 遍历ransomNote
for (int j = 0; j < ransomNote.length(); j++)
{
record[ransomNote[j]-'a']--;
// 判断ransomNote中的共有字母个数是否小于magazine中出现的次数
//如果大于的话,则不能拼成,因为magazine中每个字符只能使用一次
if(record[ransomNote[j]-'a'] < 0)
{
return false;
}
}
return true;
}
}
排序+双指针,细节去重
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;
}
// 对a进行去重
if (i > 0 && nums[i] == nums[i-1])
{
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 {
result.push_back(vector<int>{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;
}
};
剪枝:
在计算机科学和算法设计中,剪枝(Pruning)是一种优化技术,通过去除决策树或搜索树中的某些分支来减少搜索空间,从而提高算法的效率。剪枝的目的是减少不必要的计算,以便更快地找到解或达到目标。
常见的剪枝操作有两种主要类型:
-
搜索树剪枝:
- 在深度优先搜索(DFS)或广度优先搜索(BFS)等算法中,通过判断某一路径是否可能达到解,来避免不必要的搜索。
- 例如,在解决八皇后问题时,可以通过检查当前行放置皇后的位置是否与之前行的皇后产生冲突,如果冲突则不再继续搜索当前路径,从而减少搜索空间。
-
决策树剪枝:
- 在决策树算法中,剪枝可以通过停止树的生长或去除一些子树来提高算法的泛化能力。
- 例如,在随机森林中,通过限制树的深度或节点中包含的最小样本数,可以防止过拟合,提高模型的性能。
剪枝操作的核心思想是通过一些先验知识或条件判断,避免继续探索明显不会产生有用信息的部分,从而降低计算的复杂性。剪枝技术在各种算法中都有广泛的应用,包括搜索算法、机器学习算法和优化算法等。
本题的剪枝操作,是要在nums[k]>=0条件下进行的,这样确保后面的数都是正数
三数之和与四数之和思路相似
class Solution
{
pubilc:
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;
}
//对nums[k]去重
if (k > 0; && nums[k] == nums[k-1]){
continue;
}
for (i = k+1; k < nums.size(); k++){
//二级剪枝
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 (nums[k] + nums[i] + nums[left] + nums[right] > target) right--;
else if (nums[k] + nums[i] + nums[left] + nums[right] < target) left++;
else {
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;
}
};