一、题目
541. 反转字符串 II
给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。
- 如果剩余字符少于
k个,则将剩余字符全部反转。 - 如果剩余字符小于
2k但大于或等于k个,则反转前k个字符,其余字符保持原样。
示例 1:
输入:s = "abcdefg", k = 2
输出:"bacdfeg"
示例 2:
输入:s = "abcd", k = 2
输出:"bacd"
2题目思路
解决这道题的核心是按照规则分段处理字符串,每段长度为 2k,并对每段的前 k 个字符进行反转。具体思路如下:
1. 明确处理规则
题目要求按 2k 个字符为一组分段处理,每段的处理逻辑:
- 若段内字符数 ≥ k :反转前
k个字符,剩余字符不变(即使超过k但不足2k)。 - 若段内字符数 < k:将剩余字符全部反转。
2. 确定分段方式
- 从字符串开头(索引
0)开始,每次跳过2k个字符,处理下一段(即段的起始索引依次为0, 2k, 4k, ...)。 - 对每一段,先确定需要反转的范围:
- 反转的起点:当前段的起始索引
i。 - 反转的终点:
i + k - 1(前k个字符的最后一个),但如果剩余字符不足k个,终点就取字符串的最后一个索引(n-1,n为字符串长度)。
- 反转的起点:当前段的起始索引
3. 实现反转操作
对每个确定的反转范围 [起点, 终点],通过双指针法反转字符:
- 左指针从起点开始,右指针从终点开始。
- 交换左右指针指向的字符,然后左指针右移、右指针左移,直到两指针相遇(左指针 ≥ 右指针)。
4. 流程总结
- 计算字符串长度
n,初始化当前段的起始索引i = 0。 - 循环处理每一段(直到
i超出字符串长度):- 计算当前段反转的终点
end = min(i + k - 1, n - 1)(确保不越界)。 - 用双指针反转
[i, end]范围内的字符。 - 将
i增加2k,进入下一段。
- 计算当前段反转的终点
- 返回处理后的字符串。
举例理解(以 s = "abcdefg", k = 2 为例)
- 字符串长度
n = 7,分段步长2k = 4。 - 第一段
i = 0:- 终点
end = min(0 + 2 - 1, 6) = 1,反转[0,1]→s变为"bacdefg"。 i更新为0 + 4 = 4。
- 终点
- 第二段
i = 4:- 终点
end = min(4 + 2 - 1, 6) = 5,反转[4,5]→s变为"bacdfeg"。 i更新为4 + 4 = 8,超出n=7,循环结束。
- 终点
- 最终结果:
"bacdfeg",符合示例。
通过这种分段处理 + 双指针反转的思路,能高效满足题目要求,时间复杂度为 O(n)(每个字符最多被反转一次),空间复杂度为 O(1)(仅用常数空间)。
三、代码
1.while方法
cpp
class Solution {
public:
string reverseStr(string s, int k) {
int n = s.size(); // 先获取字符串长度,避免重复计算
int i = 0;
while (i < n) {
// 1. 计算当前需要反转的终点(确保不超过字符串末尾)
int end = i + k - 1;
if (end >= n) { // 如果终点越界,修正为最后一个字符的索引
end = n - 1;
}
// 2. 反转[i, end]区间的字符(用双指针交换,确保left < right)
int left = i;
int right = end;
while (left < right) { // 只有当left在right左边时才交换
swap(s[left], s[right]);
left++;
right--;
}
// 3. 移动到下一个2k区间的起点
i += 2 * k;
}
return s;
}
};
2.for方法
cpp
class Solution {
public:
string reverseStr(string s, int k) {
int n = s.size();
// 每次跳过2k个字符,处理一个区间
for (int i = 0; i < n; i += 2 * k) {
// 确定反转的终点:取i+k-1和n-1中的较小值(避免越界)
int end = min(i + k - 1, n - 1);
// 反转从i到end的字符
reverse(s.begin() + i, s.begin() + end + 1);
}
return s;
}
};
3.哪个更好?
在本题场景下,for 循环更合适,原因如下:
- 逻辑匹配度高 :本题需要按固定步长
2k遍历(从0开始,每次加2k,直到超出字符串长度),for循环的语法结构(init; cond; step)完美契合这种 "已知起点、终点、步长" 的遍历需求。 - 代码更紧凑:将循环变量初始化、终止条件、步长更新集中在一行,减少了分散的代码片段,可读性更高(他人一眼就能理解遍历规则)。
- 不易出错 :
while循环需要手动在循环内更新步长(i += 2k),若遗漏则会导致死循环;而for循环的步长写在固定位置,更不容易疏忽。
4.总结
for循环适合固定步长、已知循环范围的场景(如本题),代码更简洁,意图更明确。while循环适合条件复杂、循环次数不确定的场景(如根据动态输入决定是否继续循环)。
四、reverse 函数解释
reverse(s.begin() + i, s.begin() + end + 1) 中的两个参数是 迭代器(iterator) ,用于指定 reverse 函数需要反转的字符范围。这两个迭代器的计算方式和含义如下:
1.先理解 reverse 函数的特性
C++ 标准库的 reverse 函数用于反转一个容器中 [first, last) 区间内的元素(左闭右开区间),即:
- 包含
first指向的元素 - 不包含
last指向的元素 - 最终会反转
first到last-1之间的所有元素
2.两个参数的含义
假设字符串 s 的字符索引为 0, 1, 2, ..., n-1(n 是字符串长度),我们需要反转 从索引 i 到 end 的字符 (包含 i 和 end)。
此时:
-
第一个参数
s.begin() + i:s.begin()是字符串的起始迭代器(指向索引0),+i后指向索引i的位置,即反转的起始位置(包含该位置)。 -
第二个参数
s.begin() + end + 1:s.begin() + end指向索引end的位置,+1后指向索引end+1的位置,即反转的结束位置的下一个位置(不包含该位置)。
这样,[s.begin() + i, s.begin() + end + 1) 就刚好覆盖了 索引 i 到 end 的所有字符 ,符合 reverse 函数左闭右开的要求。
举例说明
假设字符串 s = "abcdef"(索引 0-5),需要反转 i=1 到 end=3 的字符(即 b, c, d):
s.begin() + 1指向索引1(字符b)s.begin() + 3 + 1 = s.begin() + 4指向索引4(字符e)reverse函数会反转[1,4)区间,即索引1,2,3的字符,结果为adcbef(b,c,d反转为d,c,b)。
总结
reverse 函数的参数是通过 起始迭代器 + 偏移量 计算的,目的是精准指定需要反转的范围:
- 第一个参数:
s.begin() + i→ 反转的起点(包含) - 第二个参数:
s.begin() + end + 1→ 反转的终点的下一个位置(不包含)