【算法打卡day20(2026-03-12 周四)算法/技巧:哈希表,双指针,字符串交换处理】5个题

- 第 187 篇 -
Date: 2026 - 03- 13 | 周五
Author: 郑龙浩(仟墨)
算法/技巧:哈希表,双指针,字符串交换处理

2026-03-13-算法打卡day21

按照「代码随想录」题单刷题

算法/技巧:哈希表,双指针,字符串交换处理

前三个题都是「代码随想录」中「哈希表」最后的三个题,但是 2 和 3 都是双指针,其实不是哈希表的题

然后后面的几个题都是「代码随想录」中「字符串」的题了
今天的课特别多,所以晚上才开始刷题,刷的题特少

文章目录

1-力扣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

提示:

  • 1 <= ransomNote.length, magazine.length <= 105
  • ransomNotemagazine 由小写英文字母组成

【思路】

没什么技巧,就是将第二个字符串用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 != 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 。

提示:

  • 3 <= nums.length <= 3000
  • -105 <= nums[i] <= 105

【思路】

刚开始我是想的用「哈希表」,后来发现,哈希表很复杂, 对我来说不太好实现

看了题解后,才知道应该使用「双指针」,且该双指针有点像二分查找

大概思路如下:

  • 排序预处理:先对数组排序,这是双指针能工作的前提,也方便去重。
  • 固定第一个数 :遍历数组,固定第一个数 nums[i]
  • 双指针找剩下两个数 :在 i后面的区间 [i+1, len-1]中,用左右指针 leftright寻找和为 和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 < n
  • abcd 互不相同
  • 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 <= 105
  • s[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 <= 104
  • s 仅由小写英文组成
  • 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;
}
相关推荐
陌夏2 小时前
双指针与滑动窗口
算法
MicroTech20252 小时前
MLGO微算法科技,推出革命性量子算法ANQITE,推动量子计算新时代
科技·算法·量子计算
样例过了就是过了2 小时前
LeetCode热题100 子集
数据结构·c++·算法·leetcode·dfs
I_LPL2 小时前
day52 代码随想录算法训练营 图论专题5
java·算法·图论·并查集
jing-ya2 小时前
day 49 图论part1
算法·深度优先·图论
想吃火锅10052 小时前
【leetcode】98.验证二叉搜索树
算法·leetcode·职场和发展
一叶落4382 小时前
【LeetCode 172】阶乘后的零(C语言详解 | 数学规律 + 对数时间复杂度)
c语言·数据结构·算法·leetcode·动态规划
自信150413057592 小时前
数据结构初阶——二叉树之——堆的实现
c语言·数据结构·算法
Barkamin2 小时前
(有头)链表的实现(Java)
java·数据结构·链表