这几天一直在看了July写了【横空出世,席卷互联网 [评微软等公司数据结构+算法面试100题】,里面的好多算法都很不错,所以决定把看过掌握地并亲自试验一遍没有问题后再写到自己的博客中,当然算法的思想及实现都会注明来自何方。
文章来自:http://blog.csdn.net/v_JULY_v/archive/2011/04/14/6322882.aspx
算法一:左旋转字符串
题目描述:定义字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部。 如把字符串abcdef左旋转2位得到字符串cdefab。 请实现字符串左旋转的函数,要求对长度为n的字符串操作的时间复杂度为O(n),空间复杂度为O(1)。
思路一: 以下的4点文字分析,引自网友zhedahht(http://zhedahht.blog.163.com/blog/#m)。 分析: 1、如果不考虑时间和空间复杂度的限制,最简单的方法莫过于把这道题看成是把字符串分成前后两部分,通过旋转操作把这两个部分交换位置。 2、于是我们可以新开辟一块长度为n+1的辅助空间,把原字符串后半部分拷贝到新空间的前半部分,在把原字符串的前半部分拷贝到新空间的后半部分。不难看出,这种思路的时间复杂度是O(n),需要的辅助空间也是O(n)。 3、因此,我们另寻思路,咱们试着把字符串看成有两段组成的,记为XY。左旋转相当于要把字符串XY变成YX。 我们先在字符串上定义一种翻转的操作,即翻转字符串中字符的先后顺序:把X翻转后记为XT,显然有(XT)T=X。 我们首先对X和Y两段分别进行翻转操作,这样就能得到XTYT。 接着再对XTYT进行翻转操作,得到(XTYT)T=(YT)T(XT)T=YX。正好是我们期待的结果。 4、分析到这里我们再回到原来的题目:我们要做的仅仅是把字符串分成两段, 第一段为前面m个字符,其余的字符分到第二段。再定义一个翻转字符串的函数,按照前面的步骤翻转三次就行了。时间复杂度和空间复杂度都合乎要求。
// 字符串旋转算法.cpp : Defines the entry point for the console application. //copyright@ yiyibupt && July //已测试正确,yuucyf update,2011.04.20. #include "stdafx.h" #include <cstdio> #include <cstring> void Rotate(char *pszStart, char *pszEnd) { while(pszStart != NULL && pszEnd != NULL && pszStart < pszEnd) { char cTemp = *pszStart; *pszStart =*pszEnd; *pszEnd = cTemp; pszStart++; pszEnd --; } } void LeftRotate(char *pszStr, int nCnt) { if(NULL == pszStr) return ; int nLen = strlen(pszStr); if(nCnt > 0 && nCnt <= nLen) { char *pxFirst = NULL, *pxEnd = NULL; char *pyFirst = NULL, *pyEnd = NULL; pxFirst = pszStr; pxEnd = pszStr + nCnt - 1; pyFirst = pszStr + nCnt; pyEnd = pszStr + nLen - 1; Rotate(pxFirst, pxEnd); Rotate(pyFirst ,pyEnd); Rotate(pszStr, pszStr + nLen - 1); } } int _tmain(int argc, _TCHAR* argv[]) { char aszStr[] = "Modified by yuucyf"; LeftRotate(aszStr, 8); printf("%s/n",aszStr); return 0; }
flyinghearts: ① 动态分配一个同样长度的数组,将数据复制到该数组并改变次序,再复制回原数组。(最最普通的方法) ② 利用ba=(br)^T(ar)^T=(arbr)^T,通过三次反转字符串。(即上述思路一,首先对序列前部分逆序,再对序列后部分逆序,再对整个序列全部逆序) ③ 分组交换(尽可能使数组的前面连续几个数为所要结果): 若a长度大于b,将ab分成a0a1b,交换a0和b,得ba1a0,只需再交换a1 和a0。 若a长度小于b,将ab分成ab0b1,交换a和b0,得b0ab1,只需再交换a 和b0。 通过不断将数组划分,和交换,直到不能再划分为止。分组过程与求最大公约数很相似。 ④ 所有序号为 (i+t*k) % n (i为指定整数,t为任意整数),会构成一个循环链(共有gcd(n,k)个,gcd为n、k的最大公约数),每个循环链上的元素只要移动一个位置即可,总共交换了n次。
上面所说的是第二种方法,其中第一种是最普通的,这里不讲,接的讨论第三种方法: 思路二:
就是对m和n求最大公约数,然后将数组分为gcd(m,n)组分别进行循环移位。 代码如下: void Rotate2(string &str, int nRCnt) { int nLen = str.length(); int nNumOfGroup = Gcd(nLen, nRCnt); int nElemInSub = nLen / nNumOfGroup; // 整个数组分为nNumOfGroup组,每组有nElemInSub个元素 int i = 0, j = 0; for(i = 0; i < nNumOfGroup; i++) { char cTemp = str[i]; // 每组内循环nElemInSub-1次 for (j = 0; j < nElemInSub - 1; j++) str[(i + j * nRCnt) % nLen] = str[(i + (j + 1) * nRCnt) % nLen]; str[(i + j * nRCnt) % nLen] = cTemp; } } int _tmain(int argc, _TCHAR* argv[]) { //char aszStr[] = "Modified by yuucyf"; //LeftRotate(aszStr, 8); //printf("%s/n",aszStr); string strStr = "abcdefg"; Rotate2(strStr, 3); printf("%s/n",strStr.c_str()); return 0; }
思路三:
//对上述方案4的改写 //④ 所有序号为 (i+t*k) % n (i为指定整数,t为任意整数),.... //copyright@ hplonline && July 2011.04.18。 void my_rotate(int *begin, int *mid, int *end){ int n = end - begin ; int k = mid - begin ; int d = __gcd(n, k) ; int i, j ; // (i + k * j) % n % d == i % d for ( i = 0 ; i < d ; i ++ ) { int tmp = begin[i] ; int last = i ; for ( j = (i + k) % n ; j != i ; j++ ) { begin[last] = begin[j] ; last = j ; } begin[last] = tmp ; } } //前面的代码有问题,多谢zdbill指出. //下面贴出纠正后的程序. void my_rotate(char *begin, char *mid, char *end) { int n = end - begin; int k = mid - begin; int d = gcd(n, k); int i, j; for (i = 0; i < d; i ++) { int tmp = begin[i]; int last = i; //i+k为i右移k的位置,%n是当i+k>n时从左重新开始。 for (j = (i + k) % n; j != i; j = (j + k) % n) //多谢zdbill指正。 { begin[last] = begin[j]; last = j; } begin[last] = tmp; } }