標籤:
問題描述:字串的左旋轉操作定義為:把字串前面n個字元移動到字串的尾部;如取n=3,把字串asdfghj左旋轉3位得到字串fghjasd。請實現字串左旋轉的函數,要求對長度為length的字串操作的時間複雜度為O(length),空間複雜度為O(1)。
實現字串左旋轉很簡單,但是有時間複雜度和空間複雜度的要求。
思路一:暴力旋轉法
對於前面的n個字串,每次左旋一位,整個字串也緊跟著移動向左移動一位,以此迴圈,直至左旋n個字串。
假設字串char S[length+1],額外開闢一個字串變數char b,S=‘asdfghj‘,length = 7,n=3,m=length-n=4。該方法的如下:
該演算法思路簡單明了,但是時間複雜度比較高,為n*m,代碼如下:
void left_mov(char s[],int n){int i,j;char b;for(i=0;i<n;i++){b = s[0];j = 0;while(NULL != s[j+1]){s[j] = s[j+1];j++;}s[j] = b;}cout<<s;}思路二:一次到位法
對此,引出一些別的方法,如指標翻轉法、遞迴轉換法(詳情參考July的部落格 http://blog.csdn.net/v_JULY_v/article/details/6322882)。指標反轉法,遞迴轉換法,這些方法,都沒有把字串S中的元素進行一步到位的移動,這就使得移動的次數很多。假如對於字串S中的每個元素,能夠計算出字串左旋m位之後,每個元素的具體位置,那樣就可以一次性的移動到該位置,這樣子就可以大大降低時間複雜度了,針對這個思路,提出了以下演算法:
int num 作為迴圈停止的一個判斷條件,當num=length-1的時候停止迴圈;int k 表示執行每一步step之後,字串中空出的元素的下標為k,即下一個step就要填補這第k個元素的位置;int m = length-n 表示剩餘的m個要左移的元素(當然了,前面那n個元素要補在這m個元素的後面,要不怎麼叫做左移),為了便於區別,我們這裡做一下定義,S_n表示由字串S的前面n個元素組成的一個子串(S[0-(n-1)]),S_m表示由字串S的後面m個元素組成的一個子串(S[n-length])。
剛開始的時候,將S[0]轉移給b,即S[0] -> b,此時,令k = 0; num = 0; 知道了k之後,怎麼求出下一個k,這是一個比較難的問題,也是這個方法的核心。因為k是當前字串空出的元素的下標,那麼,這個位置應該放什麼元素呢?當k=0的時候,那麼,S[k]將要存放的肯定是S_m的第一個元素,為什麼呢,因為S的前面的n個元素都要被左旋轉到S的後面了,那麼旋轉後,S[0]存放的肯定就是S_m的第一個元素;同理可以知道,當k=1的時候,S[1]存放的肯定就是S_m的第二個元素......可是,問題又來了,當超過S_m的容量的時候怎麼辦呢,因此,得到k之後,要做一下判斷,判斷S[k]將要存放的是S_m中的元素還是S_n中的元素,如果是S_m中的,那存放的又是S_m中的哪一個元素,如果是S_n中的,存放的又是S_n中的哪一個元素;仔細推敲一下,就可以得到以下的結論:
if(k<m)
{
s[k] = s[lenth-(m-k)];
k = lenth-(m-k);
}
else
{
s[k] = s[n-(lenth-k)];
k = n-(lenth-k);
}
由此,我們可以得到如的基本思路,藍的的數字表示相應的step;轉移過程表示字串中元素的轉移,S[0] -> b表示將S[0]中的字串存放在b中,同理S[3] -> S[0]表示將S[3]存放在S[0]中:num從零開始,字串S中更新(存入替代原來的)一個元素,就自加1;k的轉移過程根據上面的方法來進行判斷;step 7 進行完畢之後,num=6,表示S中已經有6個字串進行更新了,那麼此時要結束迴圈,把存放在b中的那個元素放在S[k]中,這就是step 8。
代碼如下:
void left_mov1(char s[],int lenth, int n){int k,m;char b;n = n%lenth;//當n>length的時候,模數k = 0;b = s[0];m = lenth-n;int num = 0; k = 0;while(num<(lenth-1)){ if(k<lenth-n){s[k] = s[lenth-(m-k)];k = lenth-(m-k);}else{s[k] = s[n-(lenth-k)];k = n-(lenth-k);}num++;}s[k] = b; cout<<s;}
演算法分析:k的取值順序為0->3->6->2->5->1->4,把S中所有的元素都遍曆更新了一遍,時間複雜度為O(length),空間複雜度為O(1),而且只需要進行n+1次轉移操作,與指標翻轉法、遞迴轉換法這些方法相比,有一定的優勢。加入對於所有的長度為length的字串,進行左旋n位操作之後,那麼k是不是可以遍曆完S中所有的元素呢?這個就需要進行驗證了,為此,在此做了一下測試,輸入length 和 n,輸出k的遍曆順序:
void left1(int lenth, int n){int k,m;char b;n = n%lenth;//當n>length的時候,模數k = 0;m = lenth-n;int num = 0; k = 0;cout<<k<<" ";while(num<(lenth-1)){ if(k<lenth-n){k = lenth-(m-k);}else{k = n-(lenth-k);}cout<<k<<" ";num++;}cout<<endl;}int main(){int lenth,n;cout<<"lenth=";cin>>lenth;cout<<"n=";cin>>n;cout<<"遍曆的順序依次為:"<<endl;left1(lenth,n);return 0;}
一些測試如下:
問題出現了,果然就像之前所說的那樣,k不一定能夠遍曆完S中所有的元素,出現了迴圈。為此,又查看了July的部落格(http://blog.csdn.net/v_JULY_v/article/details/6322882)。
第一個和第二個測試實驗,可以遍曆完所有的元素,對於第三個和第四個,不可以遍曆完所有的元素,那是因為length和n的最大公約數不是1,它們兩個不是互質的;為此,輸入資料之後,要判斷一下length和n的最大公約數gcd(length,n)。對於第三個測試實驗,當n=3的時候,那麼,猜測會出現3個環,因為0->3->6->9->12,這裡只有五個元素,因此,還有兩個這樣子的環;為此,需要對演算法進行改進,當回到原點的時候,要對k進行加1,目測這樣子可以可以遍曆完S中所有的元素。於是推出如下的改進方法:
int gcd(int m, int n)//傳回值n是m和n的最大公約數{int r;while(0 != m%n){r = m%n;m = n;n = r;}return n;}void left3(int lenth, int n){int k,m,num;k = 0;m = lenth-n;n = n%lenth;int gcd_n= gcd(lenth,n); if(1 == gcd_n){int k,m,num;k = 0;m = lenth-n;num = 0;cout<<k<<" ";while(num<lenth-1){if(k<m){k = lenth - (m-k);cout<<k<<" ";}else{k = n - (lenth-k);cout<<k<<" ";}num++;}cout<<endl;}else{for(int kk=0;kk<gcd_n;kk++){k = kk;int num = 0;cout<<kk<<" ";while(num<(lenth/gcd_n-1)){ if(k<m){k = lenth-(m-k);cout<<k<<" ";}else{k = n-(lenth-k);cout<<k<<" ";}num++;}}cout<<endl;}}
一些實驗結果:
第一行的結果是上一個版本的,第二行的結果是改進之後的版本的,這次終於沒有問題了;
最後,改進的左旋字串代碼如下:
int gcd(int m, int n)//傳回值n是m和n的最大公約數{int r;while(0 != m%n){r = m%n;m = n;n = r;}return n;}void left_mov5(char s[],int lenth, int n){int k,m;char b;n = n%lenth;//avoid that lenth < nk = 0;b = s[0];m = lenth-n;int gcd_n= gcd(lenth,n); if(1 == gcd_n){int num = 0; k = 0;while(num<(lenth-1)){ if(k<m){s[k] = s[lenth-(m-k)];k = lenth-(m-k);}else{s[k] = s[n-(lenth-k)];k = n-(lenth-k);}num++;}s[k] = b; }else {for(int kk=0;kk<gcd_n;kk++){k = kk;int num = 0;b = s[k];cout<<"b(";cout<<kk<<")"<<" ";while(num<(lenth/gcd_n-1)){ if(k<m){cout<<k<<"(";s[k] = s[lenth-(m-k)];k = lenth-(m-k);cout<<k<<")"<<" ";}else{cout<<k<<"(";s[k] = s[n-(lenth-k)];k = n-(lenth-k);cout<<k<<")"<<" ";}num++;}s[k] = b; cout<<k<<"(b) ";}}cout<<endl;cout<<"The left_mov string a is :";cout<<s;}int main(){const int Max = 100;int n,lenth=0;char a[40];cout<<"Please input the string a :";cin>>a;while(NULL != a[lenth]) lenth++;cout<<"The lenth of string a is : "<<lenth<<endl;;cout<<"Please input the number n :";cin>>n;left_mov5(a,lenth,n);cout<<endl;return 0;}
一些實驗結果:
這次終於成功了,噢耶!
左旋轉字串