当前位置: 首页 > news >正文

C++算法训练营 Day7 哈希表及双指针

1.四数相加

  • LeetCode:454.四数相加II

给你四个整数数组nums1nums2nums3nums4,数组长度都是n,请你计算有多少个元组 (i, j, k, l) 能满足:

0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

示例 1:

输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释: 两个元组如下:

  1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
  2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0

示例 2:

输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
输出:1

  • 解题思路:

首先创建哈希表: unordered_map<int, int> res,其作用是存储所有可能的nums1[i] + nums2[j]和及其出现频次,其中keynums1[i] + nums2[j]的和,value为该和出现的次数。选择用unordered_map是因为其是基于哈希表实现的,能提供O(1)平均时间复杂度的查找。相比红黑树实现的map(O(log n))更高效。

然后设计双重循环统计nums1[i] + nums2[j]的和res[A + B]++

for(int A : nums1) {for(int B : nums2) {res[A + B]++;}
}

执行过程为:

1.遍历数组nums1中的每个元素,每次循环赋予A不同nums1的值
2.对于每个A,遍历数组nums2中的每个元素 B
3.计算A + B的和
4.在哈希表中增加该和的计数

当首次遇到某个和时,会自动创建键值对,当遇到重复和时,直接增加计数,然后再通过一个嵌套循环:

for(int C : nums3) {for(int D : nums4) {if(res.find(0 - (C + D)) != res.end()) {sum += res[0 - (C + D)];}}
}

其数学原理为

A + B + C + D = 0 ⇨ A + B = -(C + D)

查找过程:

1.遍历数组nums3中的每个元素,每次循环赋予C不同nums3的值
2.对于每个C,遍历数组nums4中的每个元素D
3.计算C + D的和
4.计算目标值 target = -(C +D) 在哈希表中查找该目标值

因此整体的输入流程为:
假设输入:

nums1 = [1, 2] nums2 = [-2, -1] nums3 = [-1, 2] nums4 = [0, 2]

步骤1: 计算nums1和nums2的和

1 + (-2) = -1 → res[-1] = 1
1 + (-1) = 0 → res[0] = 1
2 + (-2) = 0 →res[0] = 2 (更新)
2 + (-1) = 1 → res[1] = 1

哈希表:{-1:1, 0:2, 1:1}

步骤2: 计算nums3和nums4的和

-1 + 0 = -1 → 目标值 = 1 → 找到res[1]=1 → sum=1
-1 + 2 = 1 → 目标值 = -1 → 找到res[-1]=1 → sum=2
2 + 0 = 2 → 目标值 = -2 → 未找到
2 + 2 = 4 → 目标值 = -4 → 未找到

结果:sum = 2

整体代码如下:

class Solution {
public:int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {// 结果集,用来存储nums1和nums2中所有元素组合的和及其出现次数unordered_map<int, int> res;// 第一步:计算nums1和nums2中所有元素组合的和for(int A : nums1) {for(int B : nums2) {res[A + B]++;  // 统计和出现的次数}}int sum = 0;  // 统计满足条件的元组数量// 第二步:计算nums3和nums4中所有元素组合的和for(int C : nums3) {for(int D : nums4) {// 在哈希表中查找是否存在-(C+D)的值if(res.find(0 - (C + D)) != res.end()) {// 如果找到,累加该值出现的次数sum += res[0 - (C + D)];}}}return sum;}
};
  • LeetCode:383.赎金信

给你两个字符串:ransomNotemagazine,判断ransomNote能不能由magazine里面的字符构成。

如果可以,返回true;否则返回false

magazine中的每个字符只能在ransomNote中使用一次。

示例 1:

输入:ransomNote = “a”, magazine = “b”
输出:false

示例 2:

输入:ransomNote = “aa”, magazine = “ab”
输出:false

示例 3:

输入:ransomNote = “aa”, magazine = “aab”
输出:true

  • 解题思路:

本道题的本质是验证资源是否充足,magazine的字符资源是否足够满足ransomNote的需求,并且字符的数量关系比顺序更重要。本题中约束条件为:每个字符只能使用一次

首先,创建字符计数数组:

int charCount[26] = {0}; // 初始化为0

随后,索引计算:c - 'a',如:‘a’ → 0, ‘b’ → 1, …, ‘z’ → 25,然后遍历magazine的每个字符,并存储每个字符在magazine中出现的次数,即增加对应字符的计数:

for (char c : magazine) {charCount[c - 'a']++;
}

然后遍历ransomNote的每个字符,验证ransomNote,减少对应字符的计数,如果计数变为负数,则说明magazine中该字符不足,立即返回false

for (char c : ransomNote) {if (--charCount[c - 'a'] < 0) {return false;}
}

否则,所有字符都满足,返回true

完整代码如下:

class Solution {
public:bool canConstruct(string ransomNote, string magazine) {// 创建字符计数数组(26个小写字母)int charCount[26] = {0};// 统计magazine中的字符频次for (char c : magazine) {charCount[c - 'a']++;}// 检查ransomNote中的字符for (char c : ransomNote) {// 如果字符不足或不存在if (--charCount[c - 'a'] < 0) {return false;}}return true;}
};

举例:
(1)若可以构建:

ransomNote = “aa”
magazine = “aab”

统计magazine:
a:2, b:1

验证ransomNote:
第一个’a’:a:2-1=1 ≥0
第二个’a’:a:1-1=0 ≥0
结果:true

(2)不可创建:

ransomNote = “abc”
magazine = “aabb”

统计magazine:
a:2, b:2, c:0

验证ransomNote:
‘a’:a:2-1=1 ≥0
‘b’:b:2-1=1 ≥0
‘c’:c:0-1=-1 <0 →false

3.三数之和

  • LeetCode:15.三数之和

给你一个整数数组nums,判断是否存在三元组[nums[i], nums[j], nums[k]]满足i != ji != kj != k ,同时还满足nums[i] + nums[j] + nums[k] == 0。请你返回所有和为0且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

解释:

nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] +nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例 3:

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

  • 解题思路:

本题要求在数组中找到所有不重复的三元组 [a, b, c],使得 a + b + c = 0,且满足:

  1. 三个元素来自不同下标
  2. 三元组不能重复
  3. 元素不能重复使用

首先为了方便去重(相同元素相邻)、启用双指针技巧并提前终止无效搜索(当首元素>0时)要进行排序预处理:

sort(nums.begin(), nums.end());

然后外层循环:固定第一个数 (a),当a > 0时,由于数组已排序,不可能找到a+b+c=0。跳过重复的a值,避免重复三元组:

for (int i = 0; i < nums.size(); i++) {if (nums[i] > 0) break; // 提前终止if (i > 0 && nums[i] == nums[i - 1]) continue; // a去重// ...
}

然后寻找b和c,此时有两种处理方式:
(1)哈希表法

unordered_set<int> set;
for (int k = i + 1; k < nums.size(); k++) {int target = 0 - (nums[i] + nums[k]);if (set.find(target) != set.end()) {result.push_back({nums[i], target, nums[k]});set.erase(target); // 防止重复使用相同b值} else {set.insert(nums[k]); // 当前数作为候选b值}
}

哈希表法是将遍历过的数作为b值存入集合,用c = -(a+b)公式查找,但也存在一个问题:b值去重不充分,可能导致重复三元组。
(2)双指针法

int left = i + 1, right = nums.size() - 1;
while (left < right) {int sum = nums[i] + nums[left] + nums[right];if (sum < 0) left++;else if (sum > 0) right--;else {result.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--;}
}

输入:nums = [-1,0,1,2,-1,-4]
排序后:[-4,-1,-1,0,1,2]

执行过程:

1.i=0 (a=-4)

left=1, right=5 → -4 + -1 + 2 = -3 < 0 → left++
left=2, right=5 → -4 + -1 + 2 = -3 < 0 → left++
… 无解

2.i=1 (a=-1)

left=2 (b=-1), right=5 (c=2) → -1-1+2=0 → 记录[-1,-1,2]
left=3, right=4 → -1+0+1=0 → 记录[-1,0,1]
去重后结束

  1. i=2 (a=-1) → 重复跳过
  1. i=3 (a=0) → 无解

结果:[[-1,-1,2],[-1,0,1]]

完整代码如下:

class Solution {
public:vector<vector<int>> threeSum(vector<int>& nums) {// 存储最终结果的二维数组vector<vector<int>> result;// 如果输入数组元素不足3个,直接返回空结果if (nums.size() < 3) return result;// 关键步骤1:对数组进行排序// 排序目的:// 1. 方便后续使用双指针技巧// 2. 使相同元素相邻排列,便于去重sort(nums.begin(), nums.end());// 外层循环:遍历数组,固定第一个元素(a)for (int i = 0; i < nums.size() - 2; i++) {// 提前终止条件:如果当前元素大于0// 解释:由于数组已排序,后续元素都大于0,不可能找到a+b+c=0的组合if (nums[i] > 0) break;// 去重处理:跳过重复的第一个元素// 解释:避免产生重复的三元组(如[-1,-1,0,1]中避免两个[-1,0,1])if (i > 0 && nums[i] == nums[i - 1]) continue;// 初始化双指针:int left = i + 1;    // 左指针指向当前元素的下一个位置(b)int right = nums.size() - 1; // 右指针指向数组末尾(c)// 内层循环:双指针扫描剩余数组while (left < right) {// 计算当前三元组的和int sum = nums[i] + nums[left] + nums[right];if (sum < 0) {// 情况1:和太小,需要增大 -> 左指针右移left++;// 跳过重复的左指针值(优化)// 注意:这里不是必须的去重,但可以减少不必要的迭代while (left < right && nums[left] == nums[left - 1]) left++;} else if (sum > 0) {// 情况2:和太大,需要减小 -> 右指针左移right--;// 跳过重复的右指针值(优化)while (left < right && nums[right] == nums[right + 1]) right--;} else {// 情况3:找到有效三元组result.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 result;}
};

4. 四数之和

  • LeetCode:四数之和

给你一个由n个整数组成的数组nums,和一个目标值target 。请你找出并返回满足下述全部条件且不重复的四元组[nums[a], nums[b], nums[c], nums[d]](若两个四元组元素一一对应,则认为两个四元组重复):

0 <= a, b, c, d < n
a、b、c d互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。

示例 1:

输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

示例 2:

输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]

  • 解题思路

首先,为使相同元素相邻,便于去重,且提供剪枝条件,我们需要进行排序预处理

sort(nums.begin(), nums.end());

然后就进行剪枝处理,当当前值或当前和超过目标值且为非负数时,后续所有值只会使和更大。为这里什么要求非负?因为负数相加可能变小,所以不能简单剪枝。

// 剪枝1:单个元素已超过目标值
if (nums[k] > target && nums[k] >= 0) break;// 剪枝2:前两个元素和已超过目标值
if ((long)nums[k] + nums[i] > target && (long)nums[k] + nums[i] >= 0) break;

注意:使用long防止整数溢出。

为避免重复的四元组,在四个位置分别进行去重处理:

// 去重1:第一个数
if (k > 0 && nums[k] == nums[k - 1]) continue;// 去重2:第二个数
if (i > k + 1 && nums[i] == nums[i - 1]) continue;// 去重3:第三个数
while (right > left && nums[right] == nums[right - 1]) right--;// 去重4:第四个数
while (right > left && nums[left] == nums[left + 1]) left++;

注意:去重2的条件i > k + 1确保不跳过第一个有效组合。

利用双指针进行求解:

while (right > left) {long sum = (long)nums[k] + nums[i] + nums[left] + nums[right];if (sum > target) right--;     // 和太大 → 减小和else if (sum < target) left++; // 和太小 → 增大和else {// 找到解后的处理}
}

此处使用long类型计算和,避免整数溢出。

最后找到解后的处理:
1.添加当前解到结果集
2.跳过所有重复的右指针值
3.跳过所有重复的左指针值
4.移动双指针继续搜索

result.push_back({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++;

完整代码如下:

class Solution {
public:vector<vector<int>> fourSum(vector<int>& nums, int target) {vector<vector<int>> result; //先对数组进行排序,这是双指针方法的前提sort(nums.begin(), nums.end());//第一层循环:固定第一个数(k)for (int k = 0; k < nums.size(); k++) {//剪枝处理1:如果当前数已经大于target且是非负数//说明后续所有数都会使和变大,不可能等于targetif (nums[k] > target && nums[k] >= 0) {break; }//去重处理1:跳过重复的第一个数//避免产生相同的四元组if (k > 0 && nums[k] == nums[k - 1]) {continue;}//第二层循环:固定第二个数(i)for (int i = k + 1; i < nums.size(); i++) {//剪枝处理2:如果前两个数的和已经大于target且是非负数//说明后续所有组合都会使和更大,不可能等于targetif ((long)nums[k] + nums[i] > target && (long)nums[k] + nums[i] >= 0) {break;}//去重处理2:跳过重复的第二个数if (i > k + 1 && nums[i] == nums[i - 1]) {continue;}//初始化双指针int left = i + 1;    //左指针指向第三个数的位置int right = nums.size() - 1; //右指针指向最后一个数//双指针遍历剩余数组while (right > left) {//计算四数之和(使用long防止整数溢出)long sum = (long)nums[k] + nums[i] + nums[left] + nums[right];if (sum > target) {//和太大,需要减小,右指针左移right--;} else if (sum < target) {//和太小,需要增大,左指针右移left++;} else {//找到符合条件的四元组result.push_back({nums[k], nums[i], nums[left], nums[right]});//去重处理3:跳过重复的左指针值while (right > left && nums[right] == nums[right - 1]) right--;//去重处理4:跳过重复的右指针值while (right > left && nums[left] == nums[left + 1]) left++;right--;left++;}}}}return result;}
};

相关文章:

  • 《汇编语言》第14章 端口——实验14 访问CMOS RAM
  • OpenCV C++ 心形雨动画
  • 灰狼优化算法MATLAB实现,包含种群初始化和29种基准函数测试
  • 从零开始:用Tkinter打造你的第一个Python桌面应用
  • JVMTI 在安卓逆向工程中的应用
  • 解决 WebAssembly 错误:Incorrect response MIME type (Expected ‘application/wasm‘)
  • 【已解决】电脑端 划词时出现腾讯元宝弹窗问题
  • SQL 中 NOT IN 的陷阱?
  • 固定ip和非固定ip的区别是什么?如何固定ip地址
  • AI助力Java开发:减少70%重复编码,实战效能提升解析
  • Python多线程与多进程
  • 那些Java 线程中断的实现方式
  • Git的使用技巧
  • qt的智能指针
  • MuLogin浏览器如何使用Loongproxy?
  • 深入解析 Java ClassLoader:揭开 JVM 动态加载的神秘面纱
  • 海康网络摄像头实时取帧转Opencv数组格式(h,w,3),已实现python、C#
  • intense-rp-api开源程序是一个具有直观可视化界面的 API,可以将 DeepSeek 非正式地集成到 SillyTavern 中
  • 【多线程初阶】wait() notify()
  • Spring AI 项目实战(五):Spring AI + DeepSeek + Redis 实现聊天应用上下文记忆功能(附完整源码)
  • 做期货浏览哪些网站/推荐几个靠谱的网站
  • 小说网站做编辑器/seo搜索是什么意思
  • 网站建设审核需要多长时间/360广告投放平台
  • 做鞋子批发网站有哪些/个人博客模板
  • 网站点击弹出下载框 怎么做/网盘网页版登录入口
  • wordpress管理账户/太原建站seo