题目链接:https://leetcode.cn/problems/reverse-string/description/
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组
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 码表中的可打印字符
一般看到题目要求反转数组、链表或者字符串等且必须**原地修改输入数组**、使用 O(1) 的额外空间解决这一问题,我们就应该想到双指针法,要形成条件反射,本题虽说是反转字符串,其实也是跟之前做过的反转数组一模一样,我们只需在数组的两端初始两个指针,然后不断地交换两个指针所指元素,并向中间移动,直到两个指针指向同一个位置结束,思路还是很简单,下面我们来手写代码。
(1)Python版本代码
class Solution:
def reverseString(self, s):
left = 0
right = len(s) - 1
while left < right:
s[left], s[right] = s[right], s[left]
left += 1
right -= 1
return s
if __name__ == "__main__":
s = list(input())
print(Solution().reverseString(s))
(2)C++版本代码
#include <iostream>
#include <vector>
class Solution {
public:
void reverseString(std::vector<char>& s) {
int left = 0;
int right = s.size() - 1;
while (left < right) {
std::swap(s[left], s[right]);
left++;
right--;
}
}
};
int main() {
std::string input;
std::getline(std::cin, input);
std::vector<char> s(input.begin(), input.end());
Solution().reverseString(s);
for (char c : s) {
std::cout << c;
}
return 0;
}
虽然本题简单,但是我们需要注意的是,基本每种语言都有直接的库函数实现反转操作,但是在面试中,面试官肯定是考察大家的基本功,而不是让你去调库(如果代码很复杂,部分小操作是可以直接调库解决的),因此大家在刷题时不要一时调库一时爽,还是要老老实实自己搞清楚实现原理。
题目链接:https://leetcode.cn/problems/reverse-string-ii/description/
给定一个字符串
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
这道题目在上一题的基础上,变换了花样,但是有一点不同的操作,我也是看了卡哥的讲解视频才发现的,我们如果刷题比较少就很容易形成思维定势,然后就会把代码写的分复杂,比如for循环里面i并不是必须要一次+1,在本题中我们是可以直接+2k的跳转,因为我们就是2k个一个循环。
下面是思维定势,还是使用i+=1的方法写的代码:
class Solution:
def reverseStr(self, s, k):
s = list(s)
n = len(s)
for i in range(n):
# 检查当前位置i是否是2k的倍数
if i % (2 * k) == 0:
# 计算当前反转区间的结束位置
end = min(i + k - 1, n - 1)
# 反转当前区间
start = i
while start < end:
s[start], s[end] = s[end], s[start]
start += 1
end -= 1
return ''.join(s)
这道题目其实如果我们跳出了思维定势就会变得没有那么麻烦,
class Solution:
def reverseStr(self, s, k):
s = list(s)
n = len(s)
for i in range(0, n , 2*k):
if i + k <= n:
s[i:i+k] = s[i:i+k][::-1]
continue
else:
s[i:] = s[i:][::-1]
return ''.join(s)
题目链接:https://kamacoder.com/problempage.php?pid=1064
题目描述
给定一个字符串 s,它包含小写字母和数字字符,请编写一个函数,将字符串中的字母字符保持不变,而将每个数字字符替换为number。 例如,对于输入字符串 “a1b2c3”,函数应该将其转换为 “anumberbnumbercnumber”。
输入描述
输入一个字符串 s,s 仅包含小写字母和数字字符。
输出描述
打印一个新的字符串,其中每个数字字符都被替换为了number
输入示例
a1b2c3
输出示例
anumberbnumbercnumber
提示信息
数据范围:
- 1 <= s.length < 10000。
(1)Python版本代码
为了解决这个问题,我们可以遍历输入字符串 s
,并检查每个字符,如果字符是数字,我们就用 “number” 替换它,如果字符是字母,我们就保持它不变,我们可以使用 Python 的字符串方法 isdigit()
来检查一个字符是否是数字。
def replaceNumbers(s):
result = "" # 用于存储结果的字符串
for char in s:
if char.isdigit(): # 如果字符是数字
result += "number" # 替换为"number"
else: # 如果字符不是数字
result += char # 保持原样
return result
def main():
s = input().strip() # 读取输入并去除两端空白字符
result = replaceNumbers(s)
print(result)
if __name__ == "__main__":
main()
我们还可以使用双指针法来解决这道题目。
我们可以设定两个指针:一个读指针(read
)和一个写指针(write
),读指针遍历整个字符串,而写指针只在需要写入字符时移动,当读指针指向的字符是数字时,写指针依次写入 “number”,然后移动相应的位置;当读指针指向的字符是字母时,写指针直接复制该字符,这种方法的好处是避免了使用额外的存储空间,可以在原字符串上直接进行修改(如果字符串是可变的)。
由于 Python 中的字符串是不可变的,我们可以先将字符串转换为字符列表,然后在列表上进行操作。操作完成后,再将字符列表转换回字符串。
class Solution:
def replaceNumbers(self, s):
# 将字符串转换为字符列表
s = list(s)
n = len(s)
# 预估替换后的长度
new_length = 0
for c in s:
if c.isdigit():
# 对于数字字符,长度增加 "number" 的长度
new_length += len("number")
else:
# 对于非数字字符,长度增加 1
new_length += 1
# 扩展列表长度
s.extend([''] * (new_length - n))
# 设置读指针和写指针
read, write = n - 1, new_length - 1
# 从后往前遍历字符
while read >= 0:
if s[read].isdigit():
# 替换数字字符为"number"
for c in reversed("number"):
s[write] = c
write -= 1
else:
# 复制字母字符
s[write] = s[read]
write -= 1
read -= 1
return ''.join(s)
def main():
s = input().strip()
sol = Solution()
print(sol.replaceNumbers(s))
if __name__ == "__main__":
main()
(2)C++版本代码
C++代码推荐大家去看卡哥的代码随想录中对于这题的讲解思路,下面我就贴出卡哥的代码。
#include<iostream>
using namespace std;
int main() {
string s;
while (cin >> s) {
int count = 0; // 统计数字的个数
int sOldSize = s.size();
for (int i = 0; i < s.size(); i++) {
if (s[i] >= '0' && s[i] <= '9') {
count++;
}
}
// 扩充字符串s的大小,也就是每个空格替换成"number"之后的大小
s.resize(s.size() + count * 5);
int sNewSize = s.size();
// 从后先前将空格替换为"number"
for (int i = sNewSize - 1, j = sOldSize - 1; j < i; i--, j--) {
if (s[j] > '9' || s[j] < '0') {
s[i] = s[j];
} else {
s[i] = 'r';
s[i - 1] = 'e';
s[i - 2] = 'b';
s[i - 3] = 'm';
s[i - 4] = 'u';
s[i - 5] = 'n';
i -= 5;
}
}
cout << s << endl;
}
}
题目链接:https://leetcode.cn/problems/reverse-words-in-a-string/description/
给你一个字符串
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
中 至少存在一个 单词**进阶:**如果字符串在你使用的编程语言中是一种可变数据类型,请尝试使用
O(1)
额外空间复杂度的 原地 解法。
本题反转字符串里的单词,其实就是先将整体字符串反转一遍,比如the sky is blue
,整体反转eulb si yks eht
,然后我们在对每个单词再反转一遍就行了,即blue is sky the
。问题的关键在于如何删除多余的空格,因为题目要求输入字符串 s
中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格,我们第一反应很容易就会想到分成三种情况,先删除最前面的空格,然后删除中间多余的空格,然后删除最后面的空格,这样的思路没错,但是实现很复杂,而且时间复杂度很高,那有没有一种O(n)的方法?
答案是肯定有,我们可以想到快慢指针(即双指针法),字符串里面的空格也是元素,相当于删除元素,说到这里有没有记起我们之前刷过的一道题目:27.移除元素
,操作和这道题目类似,下面我们来简单捋一下思路。
由于Python 中的字符串是不可变的,因此我们不能直接在原字符串上进行修改(即我们无法实现进阶操作),我们需要创建一个新的列表,然后我们通过两个嵌套的 while
循环遍历字符串,第一个 while
循环跳过空格,第二个 while
循环找到单词的结束位置,使用 s[start:i]
获取单词,并将其添加到 列表中,最后我们直接可以使用reversed()
函数反转单词的顺序,最好再拼接成字符串输出出来即可。
(1)Python版本代码:
class Solution:
def reverseWords(self, s):
words = []
i = 0
n = len(s)
while i < n:
while i < n and s[i] == ' ':
i += 1 # 跳过空格
if i < n:
start = i
while i < n and s[i] != ' ':
i += 1 # 找到单词的结束位置
words.append(s[start:i]) # 添加单词
return ' '.join(reversed(words)) # 翻转单词列表并用空格连接
if __name__ == "__main__":
sol = Solution()
print(sol.reverseWords(input()))
s
一次,以分割单词,这个过程的时间复杂度是 O(n)
,其中 n
是字符串 s
的长度,然后我们在将所有单词存储到列表 words
之后,我们对这个列表进行了翻转,翻转列表的时间复杂度是 O(w)
,其中 w
是列表中单词的数量,最后我们将翻转后的单词列表连接成一个字符串。这个操作的时间复杂度也是 O(n)
,因为需要遍历列表中的所有单词,并添加额外的空格,所以总的时间复杂度是 O(n)
。words
来存储分割出的单词,在最坏的情况下(输入字符串没有空格,或者每个字符都是一个单词),这个列表的大小可以达到 O(n)
,在最后我们创建了一个新的字符串来存储最终的结果,这个字符串的长度最大为 n
,所以这部分的空间复杂度也是 O(n)
,所以总的空间复杂度是 O(n)
(2)C++版本代码
#include <iostream>
#include <sstream>
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
string reverseWords(string s) {
vector<string> words;
stringstream ss(s);
string word;
// 使用 stringstream 分割字符串
while (ss >> word) {
words.push_back(word);
}
// 翻转单词列表
reverse(words.begin(), words.end());
// 拼接单词为结果字符串
string result;
if (!words.empty()) {
result = words[0];
for (size_t i = 1; i < words.size(); ++i) {
result += " " + words[i];
}
}
return result;
}
};
int main() {
Solution sol;
string s;
getline(cin, s);
cout << sol.reverseWords(s) << endl;
return 0;
}
下面是代码随想录中的代码:
class Solution {
public:
void reverse(string& s, int start, int end){ //翻转,区间写法:左闭右闭 []
for (int i = start, j = end; i < j; i++, j--) {
swap(s[i], s[j]);
}
}
void removeExtraSpaces(string& s) {//去除所有空格并在相邻单词之间添加空格, 快慢指针。
int slow = 0; //整体思想参考https://programmercarl.com/0027.移除元素.html
for (int i = 0; i < s.size(); ++i) { //
if (s[i] != ' ') { //遇到非空格就处理,即删除所有空格。
if (slow != 0) s[slow++] = ' '; //手动控制空格,给单词之间添加空格。slow != 0说明不是第一个单词,需要在单词前添加空格。
while (i < s.size() && s[i] != ' ') { //补上该单词,遇到空格说明单词结束。
s[slow++] = s[i++];
}
}
}
s.resize(slow); //slow的大小即为去除多余空格后的大小。
}
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;
}
};
题目链接:https://kamacoder.com/problempage.php?pid=1065
题目描述
字符串的右旋转操作是把字符串尾部的若干个字符转移到字符串的前面。给定一个字符串 s 和一个正整数 k,请编写一个函数,将字符串中的后面 k 个字符移到字符串的前面,实现字符串的右旋转操作。
例如,对于输入字符串 “abcdefg” 和整数 2,函数应该将其转换为 “fgabcde”。
输入描述
输入共包含两行,第一行为一个正整数 k,代表右旋转的位数。第二行为字符串 s,代表需要旋转的字符串。
输出描述
输出共一行,为进行了右旋转操作后的字符串。
输入示例
2 abcdefg
输出示例
fgabcde
提示信息
数据范围:
- 1 <= k < 10000,
- 1 <= s.length < 10000;
这题很像上面的反转字符串以及之前我们说的09年的408数据结构算法题,也就是划分需要反转的字符串,分成两部分,然后分别对两部分进行反转即可,如下图所示。
(1)Python版本代码
def main():
n = int(input()) # 读取整数
s = input() # 读取字符串
# 反转字符串的前n个字符和剩余的字符
s = s[:n][::-1] + s[n:][::-1]
print(s)
if __name__ == "__main__":
main()
(2)C++版本代码
#include<iostream>
#include<algorithm>
using namespace std;
int main() {
int n;
string s;
cin >> n;
cin >> s;
int len = s.size(); //获取长度
reverse(s.begin(), s.end()); // 整体反转
reverse(s.begin(), s.begin() + n); // 先反转前一段,长度n
reverse(s.begin() + n, s.end()); // 再反转后一段
cout << s << endl;
}