- 第 187 篇 -
Date: 2026 - 03- 13 | 周五
Author: 郑龙浩(仟墨)
算法/技巧:哈希表,双指针,字符串交换处理
2026-03-13-算法打卡day21
按照「代码随想录」题单刷题
算法/技巧:哈希表,双指针,字符串交换处理
前三个题都是「代码随想录」中「哈希表」最后的三个题,但是 2 和 3 都是双指针,其实不是哈希表的题
然后后面的几个题都是「代码随想录」中「字符串」的题了
今天的课特别多,所以晚上才开始刷题,刷的题特少
文章目录
1-力扣383-赎金信
题目难度:简单
算法/技巧:哈希表
【题目】
给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。
如果可以,返回 true ;否则返回 false 。
magazine 中的每个字符只能在 ransomNote 中使用一次。
示例 1:
**输入:**ransomNote = "a", magazine = "b"
**输出:**false
示例 2:
**输入:**ransomNote = "aa", magazine = "ab"
**输出:**false
示例 3:
**输入:**ransomNote = "aa", magazine = "aab"
**输出:**true
提示:
1 <= ransomNote.length, magazine.length <= 105ransomNote和magazine由小写英文字母组成
【思路】
没什么技巧,就是将第二个字符串用map存储起来(charCnt),key是magazin中出现的字符,value是该字符出现的次数
然后循环便利ransomNote
- ch如果在magazine中没有,那么直接return false
- ch如果在magazine中出现过,但是字符数量为0,就说明此前使用过1次或多次该字符,此时字符不够用了,也要return false
循环的时候,每使用一次某字符,某字符都要将次数--
【代码】
cpp
/* 1-力扣383-赎金信
Author:郑龙浩
Date:2026-03-13
哈希表
用时:21min (charCnt[magazine[i]]++;不小心写成了=i,找了好久的错,没想到是这里误写了)
*/
#include "bits/stdc++.h"
using namespace std;
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
unordered_map <char, int> charCnt; // 存储magazine的字符和该字符出现过的次数
for (int i = 0; i < magazine.size(); i++) charCnt[magazine[i]]++;
for (char ch : ransomNote) {
auto ans = charCnt.find(ch);
// cout << ans->first << ' ' << ans->second << '\n'; // 检验
if (ans == charCnt.end() || ans->second == 0) { // 如果 本该字符在magazine中就不存在 || 在magazin找到对应下标 且 字符为0 (之前使用过magazine中的该字符,且该字符已经用完了)
return false;
}
ans->second -= 1; // 使用了一次magazine中的该字符,就要减去一次使用次数
}
// 如果一直没有返回false,就说明组成成功了
return true;
}
};
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
Solution sol;
cout << sol.canConstruct("aa", "aab");
return 0;
}
2-力扣15-三数之和
题目难度:中等
算法/技巧:双指针
【题目】
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != 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 。
提示:
3 <= nums.length <= 3000-105 <= nums[i] <= 105
【思路】
刚开始我是想的用「哈希表」,后来发现,哈希表很复杂, 对我来说不太好实现
看了题解后,才知道应该使用「双指针」,且该双指针有点像二分查找
大概思路如下:
- 排序预处理:先对数组排序,这是双指针能工作的前提,也方便去重。
- 固定第一个数 :遍历数组,固定第一个数
nums[i]。 - 双指针找剩下两个数 :在
i后面的区间[i+1, len-1]中,用左右指针left和right寻找和为和nums[i]加起来为0的两个数。 - 指针移动规则 :
- 三数之和
sum = 0:找到解,记录,然后跳过重复元素,两指针同时向中间移动- 这一步要进行去重,一定要找到解后再去重,避免出现第二个相同的解
- 这一步目的不是为了让同一个元组的数字不重复,而是为了避免出现两个相同的元素,也就是之前出现过的元组不可以再出现第二次了
- 三数之和
sum < 0:和太小,左指针右移(增大和) - 三数之和
sum > 0:和太大,右指针左移(减小和)
- 三数之和
- 去重处理 :
- 外层循环跳过相同的
nums[i],避免重复的三元组开头 - 找到解后,跳过相同的
nums[left]和nums[right],避免相同的三元组
- 外层循环跳过相同的
【代码】
我写的代码,因为懒得加注释了,所以注释是AI加上的
cpp
/* 2-力扣15-三数之和
Author:郑龙浩
Date:2026-03-13
算法:哈希 或 双指针
思路:
1. 排序数组,方便去重和双指针移动
2. 固定第一个数i,在i后面用双指针left和right寻找剩下两数
3. 当三数之和等于0时,记录结果并移动指针
4. 当三数之和大于0时,right左移(减小和)
5. 当三数之和小于0时,left右移(增大和)
注意:求的元组是组合不是排列,需要去重
刚开始想用哈希表去做,发现如果输出的是这种三元组出现的数量有多少个,
哈希表是很好去写的,但是如果是出现的元组具体是那些个,就很难写了,我就放弃了
然后去用的双指针去写的
这里的双指针有点像「二分查找」,但是不太一样
用时:47min
*/
#include "bits/stdc++.h"
using namespace std;
// 有两种去重的方法,保留其中一种就OK
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> ans; // 存储结果三元组
int len = nums.size(), sum; // 数组长度,三数之和
// 第一步:先排序,方便去重和双指针
sort(nums.begin(), nums.end());
// 外层循环,固定第一个数
for (int i = 0; i < len; i++) {
// 去重方法1:用if判断跳过重复的i
// 原理:如果当前i和前一个i相同,那么这个i能组成的组合前一个i已经覆盖了
// if (i > 0 && nums[i] == nums[i - 1]) continue;
int left = i + 1; // 左指针,从i后面开始
int right = len - 1; // 右指针,从末尾开始
// 内层双指针循环,寻找剩下两数
while (left < right) {
sum = nums[i] + nums[left] + nums[right]; // 计算三数之和
if (sum == 0) { // 找到符合条件的三元组
ans.push_back({nums[i], nums[left], nums[right]});
// 记录后,移动指针继续寻找
left++;
right--;
// 对left去重:跳过和当前left相同的值
// 注意:比较的是left和left-1,因为上面已经left++了
while (left < right && nums[left] == nums[left - 1]) left++;
// 对right去重:跳过和当前right相同的值
// 注意:比较的是right和right+1,因为上面已经right--了
while (left < right && nums[right] == nums[right + 1]) right--;
}
else if (sum > 0) { // 和太大,需要减小
right--; // 移动右指针
}
else { // sum < 0,和太小,需要增大
left++; // 移动左指针
}
}
// 去重方法2:用while跳过重复的i
// 这里用while让i停在最后一个相同值,for循环的i++会将其移到下一个不同值
// 我个人更习惯这种写法,逻辑更清晰
while (i + 1 < len && nums[i] == nums[i + 1]) i++;
}
return ans;
}
};
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
return 0;
}
3-力扣18-四数之和
题目难度:中等
算法:双指针
【题目】
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复 的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < na、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]]
提示:
1 <= nums.length <= 200-109 <= nums[i] <= 109-109 <= target <= 109
【思路】
和力扣15-三数之和是一样的思路
唯一的不同就是「多加了一个for循环」
主要的思路就是写两层循环,i 和 j 代表元组第一个和第二个,然后用双指针去寻找第三个和第四个
思路和前面是相同的
而且这两个题都要注意:去重,必须要去重,也就是要避免之前出现过的元组不可以再出现了
【代码】
我写的代码,因为懒得加注释了,所以注释是AI加上的
cpp
/* 3-力扣18-四数之和
Author:郑龙浩
Date:2026-03-13
算法:排序 + 双指针
思路:
1. 排序,方便去重和双指针移动
2. 固定前两个数i和j
3. 在i和j之后用双指针left和right寻找剩下两数
4. 注意去重和防止溢出
用时:30min
*/
#include "bits/stdc++.h"
using namespace std;
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> ans; // 存储结果
int len = nums.size();
sort(nums.begin(), nums.end()); // 先排序,方便去重和双指针
// 第一层循环,固定第一个数
for (int i = 0; i < len - 3; i++) {
// 第二层循环,固定第二个数
for (int j = i + 1; j < len - 2; j++) {
int left = j + 1; // 左指针
int right = len - 1; // 右指针
// 双指针寻找剩下两数
while (left < right) {
// 计算四数之和,用long long防止溢出
long long sum = (long long)nums[i] + nums[j] + nums[left] + nums[right];
if (sum == target) { // 找到符合条件
ans.push_back({nums[i], nums[j], nums[left], nums[right]});
// 找到后,移动指针继续寻找
left++;
right--;
while (left < right && nums[left] == nums[left - 1]) left++; // 对left去重,避免出现重复元组,也就是之前已经求出的元组不可以再有了
while (left < right && nums[right] == nums[right + 1]) right--;// 对right去重,避免出现重复元组,也就是之前已经求出的元组不可以再有了
}
else if (sum > target) { // 和太大,需要减小
right--; // 移动右指针
}
else { // 和太小,需要增大
left++; // 移动左指针
}
}
// 对j去重:跳过相同值的j
// 这里用while让j停在最后一个相同值,for循环的j++会将其移到下一个不同值
while (j + 1 < len && nums[j] == nums[j + 1]) j++;
}
// 对i去重:跳过相同值的i
// 同样用while让i停在最后一个相同值,for循环的i++会将其移到下一个不同值
while (i + 1 < len && nums[i] == nums[i + 1]) i++;
}
return ans; // 返回所有不重复的四元组
}
};
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
return 0;
}
4-力扣344-反转字符串
题目难度:简答
算法/技巧:没有技巧
【题目】
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须**原地修改输入数组**、使用 O(1) 的额外空间解决这一问题。
示例 1:
输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]
示例 2:
输入:s = ["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]
提示:
1 <= s.length <= 105s[i]都是 ASCII 码表中的可打印字符
【思路】
很简单的一个题,直接简单的双指针前后走就OK完活
【代码】
cpp
/* 4-力扣344-反转字符串
Author:郑龙浩
Date:2026-03-13
算法:1min 50s
用时:无
*/
#include "bits/stdc++.h"
using namespace std;
class Solution {
public:
void reverseString(vector<char>& s) {
int len = s.size();
int left = 0, right = len - 1;
char temp;
while (left < right) {
temp = s[left];
s[left] = s[right];
s[right] = temp;
left++, right--;
}
}
};
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
return 0;
}
5-力扣541-反转字符串II
题目难度:简单
算法/技巧:
【题目】
给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。
- 如果剩余字符少于
k个,则将剩余字符全部反转。 - 如果剩余字符小于
2k但大于或等于k个,则反转前k个字符,其余字符保持原样。
示例 1:
**输入:**s = "abcdefg", k = 2
输出:"bacdfeg"
示例 2:
**输入:**s = "abcd", k = 2
输出:"bacd"
提示:
1 <= s.length <= 104s仅由小写英文组成1 <= k <= 104
【思路】
没什么思路,就是注意边界的处理,边界的处理我刚开始有问题
【代码】
cpp
/* 5-力扣541-反转字符串II
Author:郑龙浩
Date:2026-03-13
算法:没什么算法,就是写的时候很麻烦,对于边界处理,特别容易出错,所以改了好久
*/
#include "bits/stdc++.h"
using namespace std;
class Solution {
public:
string reverseStr(string s, int k) {
int count = 0;
int len = s.size();
for (int i = 0; i < len; i++) {
count++;
if (count == k * 2) {
for (int left = i - (2 * k - 1), right = left + k - 1; left < right; left++, right--) {
swap(s[left], s[right]);
}
count = 0;
}
}
if (count > 0 && count < k) {
for (int left = len - count, right = len - 1; left < right; left++, right--) swap(s[left], s[right]);
} else if (count >= k && count < 2 * k){
for (int left = len - count, right = left + k - 1; left < right; left++, right--) swap(s[left], s[right]);
}
return s;
}
};
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
return 0;
}