剑指Offer58-II.左旋转字符串 - 图1
参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

反转个字符串还有这么多用处?

题目:剑指Offer58-II.左旋转字符串

力扣题目链接

字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串”abcdefg”和数字2,该函数将返回左旋转两位得到的结果”cdefgab”。

示例 1:
输入: s = “abcdefg”, k = 2
输出: “cdefgab”

示例 2:
输入: s = “lrloseumgh”, k = 6
输出: “umghlrlose”

限制:
1 <= k < s.length <= 10000

思路

为了让本题更有意义,提升一下本题难度:不能申请额外空间,只能在本串上操作

不能使用额外空间的话,模拟在本串操作要实现左旋转字符串的功能还是有点困难的。

那么我们可以想一下上一题目字符串:花式反转还不够!中讲过,使用整体反转+局部反转就可以实现,反转单词顺序的目的。

这道题目也非常类似,依然可以通过局部反转+整体反转 达到左旋转的目的。

具体步骤为:

  1. 反转区间为前n的子串
  2. 反转区间为n到末尾的子串
  3. 反转整个字符串

最后就可以得到左旋n的目的,而不用定义新的字符串,完全在本串上操作。

例如 :示例1中 输入:字符串abcdefg,n=2

如图:

剑指Offer58-II.左旋转字符串 - 图2

最终得到左旋2个单元的字符串:cdefgab

思路明确之后,那么代码实现就很简单了

C++代码如下:

  1. class Solution {
  2. public:
  3. string reverseLeftWords(string s, int n) {
  4. reverse(s.begin(), s.begin() + n);
  5. reverse(s.begin() + n, s.end());
  6. reverse(s.begin(), s.end());
  7. return s;
  8. }
  9. };

是不是发现这代码也太简单了,哈哈。

总结

此时我们已经反转好多次字符串了,来一起回顾一下吧。

在这篇文章344.反转字符串,第一次讲到反转一个字符串应该怎么做,使用了双指针法。

然后发现541. 反转字符串II,这里开始给反转加上了一些条件,当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章。

后来在151.翻转字符串里的单词中,要对一句话里的单词顺序进行反转,发现先整体反转再局部反转 是一个很妙的思路。

最后再讲到本题,本题则是先局部反转再 整体反转,与151.翻转字符串里的单词类似,但是也是一种新的思路。

好了,反转字符串一共就介绍到这里,相信大家此时对反转字符串的常见操作已经很了解了。

题外话

一些同学热衷于使用substr,来做这道题。
其实使用substr 和 反转 时间复杂度是一样的 ,都是剑指Offer58-II.左旋转字符串 - 图3#card=math&code=O%28n%29),但是使用substr申请了额外空间,所以空间复杂度是剑指Offer58-II.左旋转字符串 - 图4#card=math&code=O%28n%29),而反转方法的空间复杂度是剑指Offer58-II.左旋转字符串 - 图5#card=math&code=O%281%29)。

如果想让这套题目有意义,就不要申请额外空间。

其他语言版本

Java:

  1. class Solution {
  2. public String reverseLeftWords(String s, int n) {
  3. int len=s.length();
  4. StringBuilder sb=new StringBuilder(s);
  5. reverseString(sb,0,n-1);
  6. reverseString(sb,n,len-1);
  7. return sb.reverse().toString();
  8. }
  9. public void reverseString(StringBuilder sb, int start, int end) {
  10. while (start < end) {
  11. char temp = sb.charAt(start);
  12. sb.setCharAt(start, sb.charAt(end));
  13. sb.setCharAt(end, temp);
  14. start++;
  15. end--;
  16. }
  17. }
  18. }

python:

  1. # 方法一:可以使用切片方法
  2. class Solution:
  3. def reverseLeftWords(self, s: str, n: int) -> str:
  4. return s[n:] + s[0:n]
  1. # 方法二:也可以使用上文描述的方法,有些面试中不允许使用切片,那就使用上文作者提到的方法
  2. class Solution:
  3. def reverseLeftWords(self, s: str, n: int) -> str:
  4. s = list(s)
  5. s[0:n] = list(reversed(s[0:n]))
  6. s[n:] = list(reversed(s[n:]))
  7. s.reverse()
  8. return "".join(s)
  1. # 方法三:如果连reversed也不让使用,那么自己手写一个
  2. class Solution:
  3. def reverseLeftWords(self, s: str, n: int) -> str:
  4. def reverse_sub(lst, left, right):
  5. while left < right:
  6. lst[left], lst[right] = lst[right], lst[left]
  7. left += 1
  8. right -= 1
  9. res = list(s)
  10. end = len(res) - 1
  11. reverse_sub(res, 0, n - 1)
  12. reverse_sub(res, n, end)
  13. reverse_sub(res, 0, end)
  14. return ''.join(res)
  15. # 同方法二
  16. # 时间复杂度:O(n)
  17. # 空间复杂度:O(n),python的string为不可变,需要开辟同样大小的list空间来修改
  1. #方法四:考虑不能用切片的情况下,利用模+下标实现
  2. class Solution:
  3. def reverseLeftWords(self, s: str, n: int) -> str:
  4. new_s = ''
  5. for i in range(len(s)):
  6. j = (i+n)%len(s)
  7. new_s = new_s + s[j]
  8. return new_s

Go:

  1. func reverseLeftWords(s string, n int) string {
  2. b := []byte(s)
  3. // 1. 反转前n个字符
  4. // 2. 反转第n到end字符
  5. // 3. 反转整个字符
  6. reverse(b, 0, n-1)
  7. reverse(b, n, len(b)-1)
  8. reverse(b, 0, len(b)-1)
  9. return string(b)
  10. }
  11. // 切片是引用传递
  12. func reverse(b []byte, left, right int){
  13. for left < right{
  14. b[left], b[right] = b[right],b[left]
  15. left++
  16. right--
  17. }
  18. }

JavaScript:

  1. var reverseLeftWords = function(s, n) {
  2. const length = s.length;
  3. let i = 0;
  4. while (i < length - n) {
  5. s = s[length - 1] + s;
  6. i++;
  7. }
  8. return s.slice(0, length);
  9. };

版本二(在原字符串上操作):

  1. /**
  2. * @param {string} s
  3. * @param {number} n
  4. * @return {string}
  5. */
  6. var reverseLeftWords = function (s, n) {
  7. /** Utils */
  8. function reverseWords(strArr, start, end) {
  9. let temp;
  10. while (start < end) {
  11. temp = strArr[start];
  12. strArr[start] = strArr[end];
  13. strArr[end] = temp;
  14. start++;
  15. end--;
  16. }
  17. }
  18. /** Main code */
  19. let strArr = s.split('');
  20. let length = strArr.length;
  21. reverseWords(strArr, 0, length - 1);
  22. reverseWords(strArr, 0, length - n - 1);
  23. reverseWords(strArr, length - n, length - 1);
  24. return strArr.join('');
  25. };

TypeScript:

  1. function reverseLeftWords(s: string, n: number): string {
  2. /** Utils */
  3. function reverseWords(strArr: string[], start: number, end: number): void {
  4. let temp: string;
  5. while (start < end) {
  6. temp = strArr[start];
  7. strArr[start] = strArr[end];
  8. strArr[end] = temp;
  9. start++;
  10. end--;
  11. }
  12. }
  13. /** Main code */
  14. let strArr: string[] = s.split('');
  15. let length: number = strArr.length;
  16. reverseWords(strArr, 0, length - 1);
  17. reverseWords(strArr, 0, length - n - 1);
  18. reverseWords(strArr, length - n, length - 1);
  19. return strArr.join('');
  20. };

Swift:

  1. func reverseLeftWords(_ s: String, _ n: Int) -> String {
  2. var ch = Array(s)
  3. let len = ch.count
  4. // 反转区间[0, n - 1]
  5. reverseString(&ch, startIndex: 0, endIndex: n - 1)
  6. // 反转区间[n, len - 1]
  7. reverseString(&ch, startIndex: n, endIndex: len - 1)
  8. // 反转区间[0, len - 1],也就是整个字符串反转
  9. reverseString(&ch, startIndex: 0, endIndex: len - 1)
  10. return String(ch)
  11. }
  12. func reverseString(_ s: inout [Character], startIndex: Int, endIndex: Int) {
  13. var start = startIndex
  14. var end = endIndex
  15. while start < end {
  16. (s[start], s[end]) = (s[end], s[start])
  17. start += 1
  18. end -= 1
  19. }
  20. }

剑指Offer58-II.左旋转字符串 - 图6