昨天在写一个旋转字符串的函数时,写着写着发现有好多种方法,最简单的莫过于替换然后覆盖再插入。不要小看这种小的算法,其实这其中蕴含着很多容易忽略的编程的细节。下面就跟随着我的文字来由浅入深进行巩固和再学习。总结下来此问题的算法大约有五个,这是在分得很细的情况下,前面的两个是自己想的,后面的三个参考了一个叫July的大神的思路。其实这些算法总体的思路大同小异,但这些细节问题也让我的思维有了很大的开阔。下面就由浅入深一一分析:

思路一:

此思路是最容易想到的,就是进行简单的替换,覆盖和插入操作。不好描述,直接见代码:其中需要注意的地方都已标注出来。

 1 /*   思路一:正常思路,循环左移
 2  *   注意K的处理,K有可能比N大,K 等价于 K %= N;
 3  *   算法的时间复杂度为O(N^2);
 4  */
 5 void RightShift(char * pArr, int N, int K)
 6 {
 7     assert(NULL != pArr);      //断言判断
 8     if(NULL == pArr)
 9         return;
10
11     K %= N;          //K有可能比N大,考虑周到了
12     while(K--){
13         char pTemp = pArr[0];
14         for(int i = 0; i < N; i ++){
15             pArr[i] = pArr[i + 1];
16         }
17         pArr[N - 1] = pTemp;
18     }
19 }

当然你也可以C++的String库来写,建议以后编程多用C++的string库,至少不会出现(char *)中出现的很多令人蛋疼的指针问题,不过各有各的好处,因人而异。

上面的思路最简单,但时间复杂度却不是很理想。下面是改进的算法,实现三次交换,而不是双重循环。交换的时间复杂度是线性的。

思路二:

这个也是比较容易想到的,E.g:"abcd1234" ,将之分为两部分,"abcd"和"1234",将两者交换-->"dcba"和"4321",在对整体交换-->"1234abcd",OK!是不是很简单,大部分人想到这里就应该会放弃了,包括我也是这样,但解决问题的方式永远不止一两种,只有少部分人相信了这种话,所以,相信的现在都变大神了,大神July就是这样的,下面的几种思路保证让你大开眼界,所以,以后思考问题应该多多抱着一种批判的思想,层层深入,如此方能凿到金子。看思路二的代码:

 1 /*    思路二:三次反转
 2  *    e.g: "abcd1234"
 3  *    第一次反转:"dcba",第二次反转:"4321",第三次反转:“1234abcd”
 4  *    算法的时间复杂度降到线性级为O(N);
 5  */
 6 void Reverse(char *pArr, int M, int N)    //反转函数
 7  {
 8      //M、N代表字符串区域边界上的两个点
 9      while(M < N){
10         char pTemp = pArr[N];
11         pArr[N] = pArr[M];
12         pArr[M] = pTemp;
13         ++ M;
14         -- N;
15      }
16  }
17 //三次反转
18  char * ThreeReverse(char * pArr, int N, int K)
19  {
20      K %= N;      //同样对K进行处理
21      Reverse(pArr, 0, K - 1);
22      Reverse(pArr, N - K, N - 1);
23      Reverse(pArr, 0, N - 1);
24      return pArr;
25  }

上面N表示字符串的长度,K表示要循环移动的位数,注意对K的处理上,K有可能比N大,如果K == N,刚好回到原来的字符串,即没有移动,所以,我们可以用K %= N来代替K,效果是一样的。

思路三:

将所要旋转的字符串当做一个整体,然后集体移动,如果是左循环,就进行右移动,右循环就左移动。举个例子,E.g:“abcdefghijk”实行左循环,将“abc”移动最后,则有:

“abcdefghijk” --> "defabcghijk" --> "defghiabcjk",到这里,就没法再移动了,这个时候,刚好反过来,将"jk"前移 --> "defghijkabc",这其中会用到交换Swap函数,如下:

1 void Swap(char *pArr, int M, int N)    //交换函数
2 {
3     char pTemp = pArr[N];
4     pArr[N] = pArr[M];
5     pArr[M] = pTemp;
6 }

那么如何来控制待处理的串(如"abc")的移动呢?用两个临界指针不久解决了吗,保证P2 - P1 = K即可,移动中要对P2进行判断,如果(P2 + K - 1)超过了 N(串长),就停止。对于"abcdefghijk",停止时 P1-->'a' , P2 --> 'j',因为这个时候(P2 + K1 - 1)> N,控制P2的停止,这个地方有个小技巧,就是设一个变量 Index = (N - K) - (N % K),当Index == 0时,P2不在移动。这个很好理解,比判断P2是否越界要好处理得多。见代码:

 1 /*     思路三:将要循环左移的字符串当做一个整体(两个指针控制),依次右移
 2   *     e.g:“abcdefghijk”,将abc移到最右边-->"defghijkabc"
 3   *     第一次移动-->"defabcghijk",第二次移动-->"defghiabcjk"
 4   *     再将jk往前移K位-->"defghijkabc"
 5   *     算法的时间复杂度也是线性的
 6   */
 7 void pConReverseFirst(char *pArr, int N, int K)
 8 {
 9     assert(NULL != pArr);      //断言判断
10     if(NULL == pArr)
11         return;
12
13     K %= N;
14     if(K == 0)
15         return;
16
17     //将待处理的串往后移
18     int p1 = 0, p2 = K;
19     int pIndex = (N - K) - (N % K);    //小技巧:pIndex表示p2所能指示的最大区域
20
21     while(pIndex --){
22         Swap(pArr, p1, p2);
23         ++ p1;
24         ++ p2;
25     }
26
27     //将剩余的串往前移
28     int pR = N % K;   //计算剩余的单出来的数,将这些数统一向前移,pR也可以= N - p2;
29     while(pR --){
30         char pTemp = pArr[p2];
31         for(int i = p2; i > p1; i --)
32             pArr[i] = pArr[i - 1];
33         pArr[p1] = pTemp;
34         ++ p2;
35         ++ p1;
36     }
37 }

思路四:

前面部分的算法和思路三一样,在后面剩余串的处理上,本思路是将待处理串中剩余的部分往后移,E.g:"abcdefghijk" -- > "defghiabcjk" -- > "defghi j bc a k" -- > "defghi j k c a b",将'c'往后移 -- > "defghijk abc"。见代码:

 1 /*     思路四:和思路三一样,只在后面多余数据的处理上不一样,刚好和思路三相反
 2  *     只要 *p2 != '\0',就交换p1和p2;然后将前面多余的数单独移到最后
 3  *     e.g:"defghiabcjk" --> "defghijkcab" --> "defghijkabc"
 4  *     同样的时间复杂度为线性的
 5  */
 6  void pConReverseSecond(char * pArr, int N, int K)
 7  {
 8     assert(NULL != pArr);      //断言判断
 9     if(NULL == pArr)
10         return;
11
12     K %= N;
13     if(K == 0)
14         return;
15
16     int p1 = 0, p2 = K;
17     while(p2 < N){
18         Swap(pArr, p1,p2);
19         ++ p1;
20         ++ p2;
21     }
22
23     int pR = K - (N % K);      //计算前面p1所指范围内剩余的数,e.g:"defghijkcab"剩余'c'
24     while(pR --){
25         for(int i = p1; i < p2 - 1; i ++)
26             Swap(pArr, i, i + 1);
27     }
28  }

思路五:

和思路三前面部分的算法也是一样的,后面的部分则采用递归处理。代码中有说明,相见代码:

 1 /*     思路五:递归求解,前面的思路和思路三是一样的,只是对于后面的要递归处理
 2   *     e.g:"abcdefghijk" --> "defghiabcjk" 此时,对于"abcjk"
 3   *     N = K + N % K = 5; K = N % K = 2; 将"jk"左移 --> "ajkbc",此时,对于"ajk"
 4   *     N = K + N % K = 3; K = N % K = 1; 将'a' 右移 --> "jka";
 5   *     算法的时间复杂度也是线性的
 6   */
 7 void RecurReverse(char * pArr, int N, int K, int pHead, int pTail, bool pFlag)
 8 {
 9     /* pHead = 待处理的头元素,pTail = 待处理的尾元素
10      * pFlag = 左循还是右循的标志
11      */
12          assert(NULL != pArr);      //断言判断
13     if(NULL == pArr)
14         return;
15
16     K %= N;
17     if(pHead == pTail || K == 0)  //递归出口
18         return;
19
20     //左循右移
21     if(pFlag == true){
22         int p1 = pHead, p2 = pHead + K;
23         int pLeft = N - K - (N % K);
24
25         for(; pLeft > 0; -- pLeft, ++ p1, ++p2)
26             Swap(pArr, p1, p2);
27
28         //递归,pFLag == FALSE
29         RecurReverse(pArr, K + N % K, N % K, p1, pTail, false);
30     }
31
32     //右循左移
33     //p2指向最右边第一个
34     else{
35         int p2 = pTail, p1 = pTail - K;
36         int pRight = N - K - (N % K);
37
38         for(; pRight > 0; -- pRight, -- p1, -- p2)
39             Swap(pArr, p1, p2);
40         //递归,pFLag == TRUE
41         RecurReverse(pArr, K + N % K, N % K, p1, p2, true); //"ajk" , p1指向'a',p2指向'k'
42     }
43 }

OK,以上所有代码都严格经过测试成立。

以上的算法思想,是非常低级的,一切没有涉及数据结构的算法都是非常低级的算法,但这些算法或多或少在不同的程度上打开了我们的思维,对以后的学习会有很多的帮助。以上的代码有好多种写法,每个人的写法都不一样,关键是懂得这种思想,学会层层深入地思考问题。

旋转字符串算法由浅入深相关推荐

  1. 程序员面试题精选100题(21)-左旋转字符串[算法]

    题目:定义字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部.如把字符串abcdef左旋转2位得到字符串cdefab.请实现字符串左旋转的函数.要求时间对长度为n的字符串操作的复杂度为O( ...

  2. 算法Day8|字符串专题二 剑指 Offer 58 - II. 左旋转字符串,28. 找出字符串中第一个匹配项的下标,459. 重复的子字符串

    剑指 Offer 58 - II. 左旋转字符串 解题思路: 反转区间为前n的子串 反转区间为n到末尾的子串 反转整个字符串 class Solution {public String reverse ...

  3. 程序员编程艺术(算法卷):第一章、左旋转字符串

    第一章.左旋转字符串 作者:July,yansha. 时间:二零一一年四月十四日. 说明:(狂想曲,有三层意思:1.思绪纷飞,行文杂乱无章,想到什么,记下什么.2.简单问题深入化,复杂问题精细化,不惧 ...

  4. 经典算法面试题目-判断s2是否是s1的旋转字符串(1.8)

    题目 Assume you have a method isSubstring which checks if one word is a substring of another. Given tw ...

  5. 微软算法100题26 左旋转字符串

    26.左旋转字符串 题目: 定义字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部. 如把字符串abcdef 左旋转2 位得到字符串cdefab.请实现字符串左旋转的函数. 要求时间对长度 ...

  6. [牛客算法总结]:旋转字符串

    标签: 字符串 题目: 字符串旋转: 给定两字符串A和B,如果能将A从中间某个位置分割为左右两部分字符串(可以为空串),并将左边的字符串移动到右边字符串后面组成新的字符串可以变为字符串B时返回true ...

  7. 代码随想录算法训练营第08天 | LeetCode 344.反转字符串,541. 反转字符串2,剑指Offer 05.替换空格,151.翻转字符串里的单词,剑指Offer58-II.左旋转字符串

    LeetCode [344. 反转字符串] 题目:编写一个函数,其作用是将输入的字符串反转过来.输入字符串以字符数组 s 的形式给出. 不要给另外的数组分配额外的空间,你必须**原地修改输入数组**. ...

  8. 代码随想录算法训练营第八天|344.反转字符串 541. 反转字符串II 剑指Offer 05.替换空格 151.翻转字符串里的单词 剑指Offer58-II.左旋转字符串

    一.344.反转字符串 题目:编写一个函数,其作用是将输入的字符串反转过来.输入字符串以字符数组 char[] 的形式给出. 不要给另外的数组分配额外的空间,你必须原地修改输入数组.使用 O(1) 的 ...

  9. 【LeetCode】剑指 Offer 58 - II. 左旋转字符串

    [LeetCode]剑指 Offer 58 - II. 左旋转字符串 文章目录 [LeetCode]剑指 Offer 58 - II. 左旋转字符串 一.字符串切片 二.列表遍历拼接 三.字符串遍历拼 ...

最新文章

  1. 云计算和大数据时代网络技术揭秘(八)数据中心存储FCoE
  2. python中的引用法总结_python模块调用总结
  3. 《Java 7程序设计入门经典》一3.7 for循环
  4. JAVA使用正则表达式给字符串添加分隔符
  5. 计算机无法使用光驱启动,电脑BIOS怎么设置光盘启动 三种类型BIOS设置光驱启动的图文详解教程...
  6. Treasure Island CodeForces - 1214D(dfs)
  7. spring react_使用Spring Cloud Gateway保护React式微服务
  8. 多浏览器下,CSS截断功能。
  9. 【转】优化WebLogic 服务器性能参数
  10. 中科院自动化所王金桥:深耕AI中台引擎,助力AI场景化、多元化落地...
  11. Elasticsearch 7.x 最详细安装及配置
  12. Python爬虫防封杀方法集合
  13. 酒店计算机管理系统维护合同,酒店计算机管理系统维护合同协议书范本.pdf
  14. js导出excel格式错误的问题
  15. 【阿里云产品使用教程】1. 阿里云VPC ECS SLB NAT初体验 - 上
  16. 高三班主任写给学生的一封信
  17. [笔记]NFC笔记——WUP_REQ 和 WUP_RES 消息结构
  18. java编写一个学生类和教师类_JAVA:1、编写一个学生类,类名为Student,包含如下成员:...
  19. 我的第一次黑苹果安装,还挺顺利!!!!
  20. AC多模匹配+完整实现源码

热门文章

  1. 【Windows 逆向】CE 地址遍历工具 ( CE 结构剖析工具 | 遍历查找后坐力数据 | 尝试修改后坐力数据 )
  2. 【Java 虚拟机原理】Class 字节码二进制文件分析 七 ( 局部变量表分析 )
  3. 【Flutter】Future 异步编程 ( 简介 | then 方法 | 异常捕获 | async、await 关键字 | whenComplete 方法 | timeout 方法 )
  4. 【Qt】Qt 开发桌面程序 ( Qt 版本 5.14.2 | 编辑 Qt 桌面按钮控件 | 修改按钮文本 | 为按钮添加点击事件 | 系统调用 | 去掉系统调用命令窗口 )
  5. 【鸿蒙 HarmonyOS】UI 组件 ( Text 组件 )
  6. 【运筹学】线性规划数学模型 ( 单纯形法原理 | 单纯形法流程 | 查找初始基可行解 )
  7. redis-sentinel 主从复制高可用
  8. selenium webdriver python 环境搭建
  9. Android中自定义属性(attrs.xml,TypedArray的使用)
  10. (剑指Offer)面试题19:二叉树的镜像