1 反转字符串 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
思路:
- 通过循环每隔
2k
个字符遍历字符串s
。 - 在循环中,根据剩余字符的数量分三种情况处理:
- 若剩余字符数量大于等于
k
,则反转前k
个字符。 - 若剩余字符数量少于
k
但大于0
,则反转剩余所有字符。 - 若剩余字符数量为
0
,则说明已经处理完所有字符,跳出循环并返回结果
- 若剩余字符数量大于等于
代码:
class Solution {
public:
string reverseStr(string s, int k) {
for (int i = 0; i < s.size(); i += (2 * k)) {
// 1. 每隔 2k 个字符的前 k 个字符进行反转
// 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符
if (i + k <= s.size()) {
reverse(s.begin() + i, s.begin() + i + k );
} else {
// 3. 剩余字符少于 k 个,则将剩余字符全部反转。
reverse(s.begin() + i, s.end());
}
}
return s;
}
};
2反转字符串中的单词
给你一个字符串 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 <= 104
s
包含英文大小写字母、数字和空格' '
s
中 至少存在一个 单词
思路:
解题思路分为以下三个步骤:
-
移除多余空格:
- 使用两个指针,一个快指针用于遍历字符串,一个慢指针用于记录有效字符的位置。
- 先去除字符串前面的空格,然后遍历字符串中间部分,去除冗余的空格,最后去除末尾的空格。
- 根据空格的位置移动慢指针,将有效字符复制到合适的位置。
- 最后重新设置字符串大小,确保末尾没有空格。
-
将整个字符串反转:
- 使用
reverse
函数将整个字符串进行反转,即将字符串首尾对应位置的字符互换。
- 使用
-
将每个单词反转:
- 使用两个指针
start
和end
来标识每个单词的起始和结束位置。 - 遍历字符串,当遇到空格或字符串末尾时,将当前单词的区间
[start, end]
进行反转。 - 更新
start
指针为下一个单词的起始位置。
- 使用两个指针
代码:
class Solution {
public:
// 函数功能:翻转字符串s中从start到end的字符
void reverse(string& s, int start, int end){ //翻转,区间写法:左闭右闭 []
for (int i = start, j = end; i < j; i++, j--) {
swap(s[i], s[j]);
}
}
// 函数功能:去除字符串s中的多余空格
void removeExtraSpaces(string& s) {
int slowIndex = 0, fastIndex = 0; // 定义快指针,慢指针
// 去掉字符串前面的空格
while (s.size() > 0 && fastIndex < s.size() && s[fastIndex] == ' ') {
fastIndex++;
}
for (; fastIndex < s.size(); fastIndex++) {
// 去掉字符串中间部分的冗余空格
if (fastIndex - 1 > 0
&& s[fastIndex - 1] == s[fastIndex]
&& s[fastIndex] == ' ') {
continue;
} else {
s[slowIndex++] = s[fastIndex];
}
}
if (slowIndex - 1 > 0 && s[slowIndex - 1] == ' ') { // 去掉字符串末尾的空格
s.resize(slowIndex - 1);
} else {
s.resize(slowIndex); // 重新设置字符串大小
}
}
// 函数功能:翻转字符串s中的单词
string reverseWords(string s) {
removeExtraSpaces(s); //去除多余空格,保证单词之间之只有一个空格,且字符串首尾没空格。
reverse(s, 0, s.size() - 1);
int start = 0; //removeExtraSpaces后保证第一个单词的开始下标一定是0。
for (int i = 0; i <= s.size(); ++i) {
if (i == s.size() || s[i] == ' ') { //到达空格或者串尾,说明一个单词结束。进行翻转。
reverse(s, start, i - 1); //翻转,注意是左闭右闭 []的翻转。
start = i + 1; //更新下一个单词的开始下标start
}
}
return s;
}
};
3反转链表
给你单链表的头节点 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
思路:
只需要改变链表的next指针的指向,直接将链表反转
- 定义三个指针
cur
、pre
和temp
,分别代表当前节点、前一个节点和临时节点。 - 将
cur
指针初始化为头节点head
,pre
初始化为nullptr
,表示反转后的链表的尾节点为nullptr
。 - 使用
while
循环遍历链表,直到当前节点cur
为nullptr
,即遍历到链表尾部。 - 在循环中,先将
cur
的下一个节点暂存到temp
中,以免丢失后续节点。然后将cur
的next
指针指向pre
,实现反转操作。 - 接着更新
pre
和cur
的位置,将pre
移动到cur
的位置,cur
移动到temp
的位置,继续下一轮循环。 - 当循环结束时,链表已经完成了反转,最后返回
pre
,即为反转后的头节点
重点部分:
首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。
为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。
代码:
class Solution {
public:
// 函数功能:反转链表
ListNode* reverseList(ListNode* head) {
ListNode* temp;
ListNode* cur = head; // 当前节点指针
ListNode* pre = nullptr; // 前一个节点指针
while (cur) {
temp = cur->next; // 暂存当前节点的下一个节点
cur->next = pre; // 当前节点的下一个节点指向前一个节点,实现反转
pre = cur; // 前一个节点指针后移
cur = temp; // 当前节点指针后移
}
return pre; // 返回反转后的头节点
}
};
4删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2 输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1 输出:[]
示例 3:
输入:head = [1,2], n = 1 输出:[1]
提示:
- 链表中结点的数目为
sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
思路:
- 首先,定义了两个指针
fast
和slow
,初始都指向虚拟头结点。 - 然后,
fast
指针先向前移动n + 1
步。这里为什么是n + 1
呢?因为要保证slow
指向要删除节点的前一个节点,方便后续删除操作。 - 接着,
fast
和slow
同时向前移动,直到fast
指针指向链表的末尾。 - 最后,删除
slow
指向的下一个节点,即为倒数第n
个节点。
重点部分:
fast
首先走 n + 1
步的重点在于确保 slow
指针能够指向待删除节点的前一个节点,这样在删除操作时就能够方便地处理。举个例子,如果 fast
先行 n
步而不是 n + 1
步,那么 slow
就会指向待删除节点本身,而不是它的前一个节点,这会使得删除操作变得复杂。因此,通过让 fast
先行 n + 1
步,可以确保 slow
正好指向待删除节点的前一个节点,从而简化删除操作的实现。
代码部分:
// 快指针先走n步,使得快指针和慢指针相距n个节点
while (n-- && fast != nullptr) {
fast = fast->next;
}
fast = fast->next; // 快指针再提前走一步,因为需要让慢指针指向删除节点的上一个节点
完整代码:
class Solution {
public:
// 函数功能:删除倒数第N个节点
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode* dummyHead = new ListNode(0); // 创建虚拟头节点,简化边界情况处理
dummyHead->next = head; // 将虚拟头节点指向原始头节点
ListNode* slow = dummyHead; // 慢指针,指向待删除节点的前一个节点
ListNode* fast = dummyHead; // 快指针,用于遍历链表
// 快指针先走n步,使得快指针和慢指针相距n个节点
while (n-- && fast != nullptr) {
fast = fast->next;
}
fast = fast->next; // 快指针再提前走一步,因为需要让慢指针指向删除节点的上一个节点
// 快慢指针一起移动,直到快指针到达链表尾部
while (fast != nullptr) {
fast = fast->next;
slow = slow->next;
}
// 删除倒数第N个节点
slow->next = slow->next->next;
return dummyHead->next; // 返回删除节点后的头节点
}
};