- 第 188 篇 -
Date: 2026 - 03- 14 | 周六
Author: 郑龙浩(仟墨)
今日算法or技巧:双指针 & 链表
2026-03-14-算法打卡day22
今天依旧按照「代码随想录」的顺序去刷题
今日算法/技巧:双指针 & 链表
刷着刷着刷错题了,不小心将后面的题刷成了链表中题了,双指针的第6个题做成了链表中的题了,也就是下面的第9个题,刷错题了,不过也好,正好练一练我不拿手的链表,就是写链表的时候废了很多时间, 还有五个双指针没有做,明天务必把这五个双指针做完
今日刷题:
- 卡码网54-替换数字
- 力扣151-反转字符串中的单词
- 卡码网55-右旋字符串
- 力扣28-找出字符串中第一个匹配项的下标
- 力扣459-重复的子字符串
- 力扣27-移除元素
- 力扣344-反转字符串
- 力扣206-反转链表
- 力扣24-两两交换链表中的节点
文章目录
- 2026-03-14-算法打卡day22
1-卡码网54-替换数字
难度:简单
算法/技巧:无
【题目】
题目描述
给定一个字符串 s,它包含小写字母和数字字符,请编写一个函数,将字符串中的字母字符保持不变,而将每个数字字符替换为number。 例如,对于输入字符串 "a1b2c3",函数应该将其转换为 "anumberbnumbercnumber"。
输入描述
输入一个字符串 s,s 仅包含小写字母和数字字符。
输出描述
打印一个新的字符串,其中每个数字字符都被替换为了number
输入示例
a1b2c3
输出示例
anumberbnumbercnumber
提示信息
数据范围:
1 <= s.length < 10000。
【思路】
没什么技巧
【代码】
cpp
/* 2026-03-14-算法打卡day22
1-卡码网54-替换数字
Author:郑龙浩
Date:2026-03-14
用时:1min 43s
算法:没什么算法
*/
#include "bits/stdc++.h"
using namespace std;
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
string s;
cin >> s;
for (char ch : s) {
if (ch >= '0' && ch <= '9') {
cout << "number";
} else cout << ch;
}
return 0;
}
2-力扣151-反转字符串中的单词(需要复习)
难度:中等
算法/技巧:双指针法-快慢指针
【题目】
给你一个字符串 s ,请你反转字符串中 单词 的顺序。
单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。
返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。
**注意:**输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。
示例 1:
**输入:**s = "the sky is blue"
输出:"blue is sky the"
示例 2:
**输入:**s = " hello world "
输出: "world hello"
**解释:**反转后的字符串中不能存在前导空格和尾随空格。
示例 3:
**输入:**s = "a good example"
输出: "example good a"
**解释:**如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。
提示:
1 <= s.length <= 104s包含英文大小写字母、数字和空格' 's中 至少存在一个 单词
【思路】
这道题我还是用了挺长时间的,需要复习
主要还是在边界处理和处理空格的地方用了很长时间修改毛病,回想起来,我也不知道为什么会用这么长时间
大概的思路就是,用两个指针去定位
- 用end指针定位单词的末尾
- 用start指针定位单词的开头的前面的空格
' ' - 要从后往前寻找单词,只要找到了单词就插入新的s2中,保证了将后面的单词放到了前面
代码步骤:
- 写一个s2存储倒转后的单词
- 从len开始向0遍历字符(且for循环中只负责创建end并赋初值 + 判断是否越界,end的变化在循环体的内容进行)
- 写一个while循环让end指向单词末尾:首先要保证end指向的位置是单词的末尾,所以要将所有的空格跳过,直接让end指向单词末尾
- 如果此时end是<0的,就braek
- 如果end是0,要break,避免越界访问,也是因为end都是<0的了,就不可能存在单词了,后面执行s2.append(s.substr(start + 1, end - start) + ' ')的时候保准会报错,所以就不要执行了
- 创建start = end,让start从end开始
- 写一个while循环让start指向单词开头前面的空格处:只要是字母,就start++,直到start处是空格,就停止
- 然后将start + 1 到 end的单词 + 空格 插入到新的s2
- 最后将下一个单词的末尾更新为start当前单词的前面空格处,让end重新开始寻找单词末尾
- 最后,在return s2之前,要先将末尾的单词的空格去掉,否则会多出一个后置空格(前提是s2非空,如果是空的,就不去删除)
【代码】
cpp
/* 2026-03-14-算法打卡day22
2-力扣151-反转字符串中的单词
Author:郑龙浩
Date:2026-03-14
用时:1h 17min(刚开始用错方法了,又重新写的, 小毛病也是改了又改,主要是在边界处理的问题上,边界处理我掌握的一直很差劲)
算法:快慢指针(双指针法)
一个指针寻找单词的末尾,一个指针寻找单词的开头的前面
*/
#include "bits/stdc++.h"
using namespace std;
class Solution {
public:
string reverseWords(string s) {
string s2;
int len = s.size();
for (int end = len - 1; end >= 0;) {
while (end >= 0 && s[end] == ' ') end--; // 去除后置空格,让单词的最后一个字符从字母开始,而不是从空格开始
if (end < 0) break; // 如果end是0,要break,避免越界访问
int start = end; // 让开头从单词的前面开始
while (start >= 0 && s[start] != ' ') start--; // 找到单词的第一个字符的前面的 空格的位置
s2.append(s.substr(start + 1, end - start) + ' '); // 将找到的单词存入s2中,并在最后加上' '
end = start; // 让下一个单词的结尾从上一个单词的开头的前面位置算起
}
if (!s2.empty()) s2.pop_back(); // 如果s2是非空的,那么前面存储单词的时候会在最后一个单词后面多存放一个空格,所以要删除掉
// 如果是空的,那么就不要删除
return s2;
}
};
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
Solution sol;
string s = " Bob Loves Alice ";
cout << sol.reverseWords(s);
return 0;
}
3-卡码网55-右旋字符串
难度:简单
算法/技巧:m
【题目】
题目描述
字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。
例如,对于输入字符串 "abcdefg" 和整数 2,函数应该将其转换为 "fgabcde"。
输入描述
输入共包含两行,第一行为一个正整数 k,代表右旋转的位数。第二行为字符串 s,代表需要旋转的字符串。
输出描述
输出共一行,为进行了右旋转操作后的字符串。
输入示例
2
abcdefg
输出示例
fgabcde
提示信息
数据范围:
1 <= k < 10000,
1 <= s.length < 10000;
【思路】
基础题,没有算法技巧
【代码】
cpp
/* 2026-03-14-算法打卡day22
3-卡码网55-右旋字符串
Author:郑龙浩
Date:2026-03-14
算法/技巧:没有什么算法技巧
*/
#include "bits/stdc++.h"
using namespace std;
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int k; cin >> k;
string s; cin >> s;
int len = s.size();
cout << s.substr(len - k, k) + s.substr(0, len - k);
return 0;
}
4-力扣28-找出字符串中第一个匹配项的下标
难度:简单
算法/技巧:NULL
【题目】
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。
示例 1:
**输入:**haystack = "sadbutsad", needle = "sad"
**输出:**0
解释: "sad" 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。
示例 2:
**输入:**haystack = "leetcode", needle = "leeto"
输出: -1
解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1 。
提示:
1 <= haystack.length, needle.length <= 104haystack和needle仅由小写英文字符组成
【思路】
没什么技巧,普通题
【代码】
cpp
/* 2026-03-14-算法打卡day22
4-力扣28-找出字符串中第一个匹配项的下标
Author:郑龙浩
Date:2026-03-14
算法/技巧:没什么技巧,主要就是查字符串
用时:11min
*/
#include "bits/stdc++.h"
using namespace std;
class Solution {
public:
int strStr(string S1, string S2) {
int len1 = S1.size(), len2 = S2.size();
if (len2 > len1) return -1;
for (int i = 0; i < len1; i++) {
if (S1[i] == S2[0]) {
bool f = true; // 假设找到了,如果后面发现有单词不匹配,就改为false
for (int j = 0; j < len2; j++) {
if (S1[i + j] != S2[j]) {
f = false;
break;
}
}
// 如果没有找到不同,就说明全部相同,此时就说明找到了needle,就可以return下标i了
if (f == true) return i;
}
}
return -1;
}
};
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int k; cin >> k;
string s; cin >> s;
int len = s.size();
cout << s.substr(len - k, k) + s.substr(0, len - k);
return 0;
}
5-力扣459-重复的子字符串
难度:简单
算法/技巧:移动匹配法(专门用于用于判断字符串是否由重复子串构成的)
【题目】
给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。
示例 1:
输入: s = "abab"
输出: true
解释: 可由子串 "ab" 重复两次构成。
示例 2:
输入: s = "aba"
输出: false
示例 3:
输入: s = "abcabcabcabc"
输出: true
解释: 可由子串 "abc" 重复四次构成。 (或子串 "abcabc" 重复两次构成。)
提示:
1 <= s.length <= 104s由小写英文字母组成
【思路】
错误思路:
- 遍历字符串,找到第一个与字符串首字符
s[0]重复出现的字符,记录其索引为i。 - 从索引0到
i-1的子串s[0:i]即为候选的重复单元word。 - 从索引
i开始,每次向后截取长度为len(word)的子串,判断是否与word相等。 - 如果每次截取的子串都等于
word,并且能恰好覆盖字符串剩余部分,则返回true。 - 否则返回
false。
这个是我原本的思路,但是力扣怎么都不通过,我以为是有部分逻辑错误或者什么小失误
*我最初的代码
cpp
class Solution {
public:
bool repeatedSubstringPattern(string s) {
int len = s.size();
int WordLen = 0;
string word;
for (int i = 1; i < len; i++) {
if (s[i] == s[0]) {
word = s.substr(0, i);
WordLen = i;
break;
}
}
for (int i = WordLen; i < len;) {
if (s.substr(i, WordLen) != word) return false;
i += WordLen;
}
if (WordLen == 0) return false;
return true;
}
};
我的代码只能通过部分案例,很多案例都不通过,底层逻辑就错了
AI告诉我,我的逻辑本身就是错的,我假设的不成立的条件本身就是个错误
【AI说我的错误在哪里】
你的缺陷在于假设条件不成立。你的逻辑建立在两个错误假设上:
错误假设1:重复子串的第一个字符在字符串中只出现在每个子串的开头位置
- 你认为:找到下一个与
s[0]相同字符的位置,就是子串长度 - 但实际:重复子串可能有多个与首字符相同的字符
- 例子:
"abacabac"- 你的代码:找到第二个
'a'在位置2,认为WordLen=2,子串是"ab" - 实际:子串是
"abac"长度4 - 第一个子串内就有两个
'a',你找到的是子串内部的'a',不是下一个子串开头的'a'
- 你的代码:找到第二个
错误假设2:字符串必然有与首字符相同的字符在别的位置
- 当
WordLen=0时,你直接返回false - 但
"aaaa"这种情况:子串是"a",你的逻辑就错了
错误假设3:找到第一个相同字符位置就一定是正确的子串长度
- 即使能找到,也无法保证这就是正确的重复单元
- 例子:
"abaababaab"- 首字符
'a',下一个'a'在位置2 - 你的代码会认为子串是
"ab",但实际上子串是"abaab"
- 首字符
根本问题
重复子串的判定是全局的,不能只通过局部特征(第一个字符何时再次出现)来决定。必须:
- 检查长度整除关系
- 验证整个字符串是否由候选子串重复构成
你的方法试图用一个局部条件替代全局验证,这是算法设计中的概念错误,不是实现细节问题
正确思路
有两个思路一个移动匹配法(专门用于用于判断字符串是否由重复子串构成的),一个是KMP
KMP不会,且蓝桥杯考的概率低,就先不学了
移动匹配是只针对于判断字符串是否是重复子字符串构成的一个方法
就是将两个字符串s拼接起来
看中间是否还会出现一个除了自己本身的s(前提去掉开头结尾,避免将本来的s算作s)
【代码】
cpp
/* 2026-03-14-算法打卡day22
5-力扣459-重复的子字符串
Author:郑龙浩
Date:2026-03-14
算法/技巧:移动匹配法(专门用于用于判断字符串是否由重复子串构成的)or KMP(我不会,也没学,在刷题阶段,尽量节省时间做其他高频算法题吧)
*/
#include "bits/stdc++.h"
using namespace std;
/*// 这是个错误代码
class Solution {
public:
bool repeatedSubstringPattern(string s) {
int len = s.size();
int WordLen = 0;
string word;
for (int i = 1; i < len; i++) {
if (s[i] == s[0]) {
word = s.substr(0, i);
WordLen = i;
break;
}
}
for (int i = WordLen; i < len;) {
if (s.substr(i, WordLen) != word) return false;
i += WordLen;
}
if (WordLen == 0) return false;
return true;
}
};
*/
class Solution {
public:
bool repeatedSubstringPattern(string s) {
string s2 = s + s;
s2.erase(s2.begin());
s2.pop_back();
if (s2.find(s) != std::string::npos) return true;
return false;
}
};
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
Solution sol;
sol.repeatedSubstringPattern("abab");
return 0;
}
6-力扣27-移除元素
难度:简单
算法/技巧:二分查找 和 双指针法(快慢指针)
【题目】
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。
假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:
- 更改
nums数组,使nums的前k个元素包含不等于val的元素。nums的其余元素和nums的大小并不重要。 - 返回
k。
用户评测:
评测机将使用以下代码测试您的解决方案:
int[] nums = [...]; // 输入数组
int val = ...; // 要移除的值
int[] expectedNums = [...]; // 长度正确的预期答案。
// 它以不等于 val 的值排序。
int k = removeElement(nums, val); // 调用你的实现
assert k == expectedNums.length;
sort(nums, 0, k); // 排序 nums 的前 k 个元素
for (int i = 0; i < actualLength; i++) {
assert nums[i] == expectedNums[i];
}
如果所有的断言都通过,你的解决方案将会 通过。
示例 1:
输入: nums = [3,2,2,3], val = 3
输出: 2, nums = [2,2,_,_]
解释: 你的函数应该返回 k = 2, 并且 nums 中的前两个元素均为 2。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。
示例 2:
输入: nums = [0,1,2,2,3,0,4,2], val = 2
输出: 5, nums = [0,1,4,0,3,_,_,_]
解释: 你的函数应该返回 k = 5,并且 nums 中的前五个元素为 0,0,1,3,4。
注意这五个元素可以任意顺序返回。
你在返回的 k 个元素之外留下了什么并不重要(因此它们并不计入评测)。
提示:
0 <= nums.length <= 1000 <= nums[i] <= 500 <= val <= 100
【思路】
【AI根据我的代码整理的思路,我懒得写思路了】
方法1:二分查找法
-
排序数组 :先用
sort排序,使二分查找有效 -
定位目标值:
-
lower_bound找到第一个等于val的位置 -
upper_bound找到第一个大于val的位置
-
-
计算数量 :
cnt = end - first得到val的个数 -
处理特殊情况:
- 如果
cnt=0,说明数组中没有val,直接返回原长度
- 如果
-
覆盖删除:
-
用
copy将val后面的所有元素向前复制,覆盖掉val -
返回新长度
len - cnt
-
注意:此方法改变了元素相对顺序
方法2:双指针法(快慢指针)
-
双指针定义:
-
慢指针
cur:指向下一个有效元素存放位置 -
快指针
i:遍历数组检查每个元素
-
-
遍历过程:
-
快指针遍历所有元素
-
当元素不等于val时,复制到慢指针位置,然后慢指针右移
-
-
结果:
-
遍历结束时,
cur的值就是新数组长度 -
数组前
cur个元素都是不等于val的有效元素
-
【代码】
cpp
/* 2026-03-14-算法打卡day22
6-力扣27-移除元素
Author:郑龙浩
Date:2026-03-14
算法/技巧:方法1二分查找,方法2双指针
二分查找用时:8min(刚开始忘记了C++中的二分函数和copy函数的参数是什么了)
双指针法用时:5min
*/
#include "bits/stdc++.h"
using namespace std;
class Solution {
public:
/*方法1:二分查找法
int removeElement(vector<int>& nums, int val) {
int len = nums.size();
sort(nums.begin(), nums.end()); // 排序,使用二分函数前提:nums有序
auto first = lower_bound(nums.begin(), nums.end(), val); // 第一个val的位置
auto end = upper_bound(nums.begin(), nums.end(), val); // 最后一个val位置后
int cnt = end - first; // val数量
if (cnt == 0) { // 如果val数量是0,执行copy会报错,因为cnt是0,意味着nums无val,此时firt和end是nums.end(),而且既然是val了,那么也就无需更改nums数组了,且原长度无需变
return len;
} else {
copy(end, nums.end(), first); //将val后面的所有元素copy to 第一个 val
return len - cnt;
}
}
*/
// 方法2:双指针法(高效)- 快慢指针
int removeElement(vector<int>& nums, int val) {
int len = nums.size();
int cur = 0; // 慢指针:表示需要放置元素的位置
for (int i = 0; i < len; i++) { // 快指针:表示检查的位置
if (nums[i] != val) { // 如果快指针不是val就放到需要存储元素的位置
nums[cur++] = nums[i]; // 只有cur位置发生了复制的时候,才可以将cur++到下一个需要放置元素的位置
}
}
return cur; //
}
};
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
return 0;
}
7-力扣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 码表中的可打印字符
【思路】
没什么技巧,就是双指针,而且就是前后遍历即可
【代码】
cpp
/* 2026-03-14-算法打卡day22
7-力扣344-反转字符串
Author:郑龙浩
Date:2026-03-14
算法/技巧:双指针法
用时:2 min
*/
#include "bits/stdc++.h"
using namespace std;
class Solution {
public:
void reverseString(vector<char>& s) {
int left = 0;
int right = s.size() - 1;
while (left < right) {
swap(s[left], s[right]);
left++, right--;
}
}
};
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
return 0;
}
8-力扣206-反转链表
难度:简单
算法/技巧:链表 & 三个指针的双指针
【题目】
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
示例 1:

输入: head = [1,2,3,4,5]
输出: [5,4,3,2,1]
示例 2:

输入: head = [1,2]
输出: [2,1]
示例 3:
输入: head = []
输出: []
提示:
- 链表中节点的数目范围是
[0, 5000] -5000 <= Node.val <= 5000
【思路】
双指针,不过这次的双指针是连着的
有三个指针,一个指针指向当前,一个指针指向前一个节点,一个指针指向后一个节点
不断的遍历cur就好,然后将cur 指向前一个节点pre,并且用next记录原cur的下一个节点,避免cur->next更改指向后,丢失了原next(原来的下一个节点)
【代码】
懒得加注释了,我写的代码,AI帮我加的注释
cpp
/* 2026-03-14-算法打卡day22
8-力扣206-反转链表
Author:郑龙浩
Date:2026-03-14
算法/技巧:双指针
方法1:简单的思路就是,将链表从头到尾都遍历一遍,存入vector中然后逆序遍历vector,再重新做一个链表并且return就OK了
方法2:试着从原链表遍历,将链表的箭头(指向)反转 -> 使用的双指针
用时:11min
*/
#include "bits/stdc++.h"
using namespace std;
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
class Solution {
public:
ListNode* reverseList(ListNode* head) {
// 初始化三个指针
ListNode* pre = NULL; // 指向当前节点的前一个节点,初始为NULL
ListNode* cur = head; // 指向当前要处理的节点,初始为头节点
ListNode* next; // 临时保存当前节点的下一个节点,防止断链
// 遍历链表
while (cur != NULL) {
// 1. 先保存当前节点的下一个节点
next = cur->next;
// 2. 反转当前节点的指向,指向前一个节点
cur->next = pre;
// 3. 更新pre和cur指针,准备处理下一个节点
pre = cur; // pre移动到当前节点
cur = next; // cur移动到下一个待处理节点
}
// 循环结束时,cur指向NULL,pre指向新的头节点(原链表的尾节点)
return pre;
}
};
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
return 0;
}
9-力扣24-两两交换链表中的节点
难度:中等
算法:三指针法
【题目】
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:

输入: head = [1,2,3,4]
输出: [2,1,4,3]
示例 2:
输入: head = []
输出: []
示例 3:
输入: head = [1]
输出: [1]
提示:
- 链表中节点的数目在范围
[0, 100]内 0 <= Node.val <= 100
【思路】
设连着的四个节点是 pre, first, second, next
那么要做的就是将这个顺序改为
pre, second, first, next
那么设置四个指针,分别指向这连着的四个节点,去转换中间的second和first即可
- next存在的作用是避免second指向first后,原本的next会丢失,因为next要作为first的下一个节点
- pre存在的作用是将second作为pre的下一个节点,要更改这个指向
优化一下,其实next指针变量没必要存在,因为有next的存在,还需要额外去判断next是否为空,这反而增加了代码的复杂性和出错的概率。实际上我们可以直接在交换过程中更新指针,完全不需要这个中间变量
pre存储前要交换的first 和 second的前节点,也就是first前面的节点
循环条件是pre->next 和 pre->next->next 都不是nullptr(保证first和second可以正常赋值,也就是节点的话才能保证可以交换)
因为循环条件已经判断了first和second是否是节点,所以在循环中大可放心赋值
然后让曾经pre->first->second->end改为pre->second->first->end
不断循环这个过程即可
(对了,我之前在考虑如果end是最后一个节点->next->next(nullptr->next)怎么办,其实是多想了,因为前面已经保证了first和second是节点,所以end顶多顶多是个nullptr,不会出现nullptr->next的情况的
【代码】
c
/* 2026-03-14-算法打卡day22
9-力扣24-两两交换链表中的节点
Author:郑龙浩
Date:2026-03-14
算法/技巧:双指针
用时:1h 修修改改了一个小时,链表还是太生疏了,数据结构的知识忘了好多
*/
#include "bits/stdc++.h"
using namespace std;
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
/* 第一版,使用了next,比较复杂,出错代码->四指针版本
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if (head == nullptr || head->next == nullptr) return head; // 如果链表为空 或 只有1个节点,直接返回head
// 只有链表>=2的时候,执行*next = second->next才不会报错,因为next才能存储数据,要么链表长度为3存储nullptr,要么存储某个节点的地址
ListNode* dummy = new ListNode(0, head); // 虚拟头结点(不存储任何数据)
ListNode* pre = dummy, *first = head, *second = first->next, *next = second->next;
while (first != nullptr && second != nullptr) {
// 更改链表指向
pre->next = second;
second->next = first;
first->next = next;
// 更新下一批需要更改指向的节点
pre = next;
first = pre->next;
second = first->next;
next = next->next->next;
}
return dummy->next;
}
};
*/
// 三指针版本 - 使用pre,first 和 second
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if (head == nullptr || head->next == nullptr) return head; // 如果链表长度是0或1,直接return head
ListNode* dummy = new ListNode(0, head);
ListNode* pre = dummy; // 虚拟头结点,为了用dummy->next存储真正的头结点
while (pre->next != nullptr && pre->next->next != nullptr) {
// 1. 定义当前要交换的两个节点
ListNode* first = pre->next; // 第一个节点
ListNode* second = first->next; // 第二个节点
// 2. 执行交换
first->next = second->next; // first指向下一对的开始
second->next = first; // second指向first
pre->next = second; // pre指向新的第一个节点(second)
// 3. 移动pre指针到下一对的前一个位置
pre = first; // first现在是交换后的第二个节点
}
ListNode* newHead = dummy->next;
delete dummy;
return newHead;
}
};
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
return 0;
}