最近在傳智論壇遇到一個算法的問題,想了一下,有一個我認爲比較有趣的解法. 下面算 法或許不是最優的,可是能夠參考一下.算法
=======================================================================
給定兩個字符串,僅由小寫字母組成,它們包含了相同字符。求把第一個字符串變
成第二個字符串的最小操做次數,且每次操做只能對第一個字符串中的某個字符移
動到此字符串中的開頭。數組
例如給定兩個字符串「abcd" "bcad" ,輸出:2,由於須要操做2次才能把"abcd"變
成「bcad" ,方法是:abcd->cabd->bcad。數據結構
我開始用廣度優先搜索作。但是好比abcdefg 和 gfedcba 遞歸下去就是7的7次方的
時間複雜度 而後再在變換位置的函數裏循環 循環次數報表。函數
而後我用List記錄 abcdefg出現的東東 把重複的去掉了,也就是7*6*5*4*3*2
可是每次判斷list.contain()在內部貌似又要循環時間和上面差很少.優化
有沒有比較好的解決方式.
========================================================================spa
這個問題比較有趣,這裏我使用C#來完成,主要是考慮微軟提供了許多數據結構 不用再從新實現數據結構和相關方法了. code
開始拿到這個問題,先分析一下須要作的事兒:就是將一個字符串,變成一個指 定的字符串. 抽象一下,將目標字符串當作一個有序集的結構,能夠從新一個有序集. blog
再將原來的字符串當作一個等待排序的數據結構,就能夠將問題簡化爲簡單的排序算
法了. 即:指定序的排序問題. 排序
舉個例子:將字符串"abcd"變成"bcad". 那麼字符集"bcad"構成一個有序集,能夠獲得映射表,以下:
{ ('b', 0), ('c', 1), ('a', 2), ('d', 3) } 所以須要排序的字符串就變成了一個數字集:{ 2, 0, 1, 3 }. 即 須要將這個數據集排序爲 { 0, 1, 2, 3 },須要求最小的步驟. 因
此將問題轉化爲排序最小步驟問題. 遞歸
另外一方面,問題考慮了算法實現的要求,即每次只能將一個數據 取出,插入到開始的地方. 也就是說具體操做就是一個固定的函數:
將第i項插入到0項,原來前i-1項的數據依次後移一位. 故方法很簡單:
1 // 注意到須要處理的是字符串,因爲字符串是不可變的 2 // 這裏考慮使用字符數組 3 public void jk_remove(char[] s, int index) { 4 char temp = s[index]; 5 for (int i = index - 1; i >= 0; i--) { 6 s[i + 1] = s[i]; 7 } 8 s[0] = temp; 9 }
那麼這個函數調用幾回,就表示操做了幾回.
下面考慮另外一個問題. 就是何時調用這個函數. 也就是如何操做纔算是較優. 這個問題有點麻煩. 起初我打算從現成的算法中找一個. 不過現成的都不太友好. 想到
了使用"逆序"的方法.
所謂最少的步驟. 就是不要移動多餘的步驟. 在前面已經提到,問題已經劃歸成排 序問題,下面姑且使用升序.
要將一個數字序列排序,又只能將數據往前放,那麼最小的步驟就是每次移動一個 數據而且這個數據就是一個良序的,也就是說這個數字不會再次移動. 那麼一個長度爲
n的字符串,最多移動n-1次. 我想這個應該是最少的步驟了吧!!!
下面看看怎麼移動會比較好.
首先考慮移動的特徵就是每次移動數據都放在最開始的地方. 同時下一次移動開始 後這個已經被移動過的數據就會自動擠到後面去. 那麼再也不重複移動這個數據的辦法就
是首先移動最大的數. 那麼在移動剩下的最大數,就能夠保證移動完成之後結果就是良
序的了.
那麼什麼樣的數據須要移動呢?很簡單逆序(注1)的. 舉個例子:
延續前面的例子,數字2013. 第一個數字不須要考慮,第二個數字0與2構成一個逆 序,所以0是逆序數. 第三個數字1與2構成逆序,即1爲逆序數. 那麼首先移動較大的,
移動1獲得1203. 因爲數據排列發生變化,須要從新計算逆序. 所以獲得0爲逆序數,移
動0,獲得結果0123.
這裏就獲得一個結論,就是判斷逆序,移動最大的逆序數便可. 由此獲得僞代碼:
1 // 這裏依舊使用升序排列 2 public void jk_sort(char[] s) { 3 char temp = '\0'; // 用來存放最大的逆序數 4 // 因爲第一個數字不須要考慮逆序,所以從1開始循環 5 for(int i = 1; i < s.length; i++) { 6 for(int j = 0; j < i; j++) { 7 // 後一個數比前一個數小,那麼構成逆序 8 if(s[i] < s[j]) { 9 // 判斷記錄最大的逆序數 10 if(temp == '\0' || temp < s[i]) { 11 temp = s[i]; 12 } 13 // 跳出循環,看下一個數是否爲逆序 14 break; 15 } 16 } 17 } 18 // 移動逆序數 19 if(temp != '\0') { 20 jk_remove(s, temp); 21 // 若是存在逆序數就遞歸繼續判斷,不然離開函數排序完成 22 jk_sort( s ); 23 } 24 }
這裏使用了遞歸,算法複雜度提高了,具體的優化能夠之後再說. 那麼有了這個思
路之後,就能夠實現了.
第一種狀況,字符串中沒有重複的字符
首先寫一個類
1 public class JKSort { 2 3 }
提供靜態方法,移動數據
1 private static void jk_remove(char[] s, int index) { 2 char temp = s[index]; 3 for (int i = index - 1; i >= 0; i--) { 4 s[i + 1] = s[i]; 5 } 6 s[0] = temp; 7 }
提供映射,能夠考慮使用一個鍵值對
1 private Dictionary<char, int> dic; // 使用構造函數初始化 2 private JKSort(char[] chs) { 3 dic = new Dictionary<char, int>(); 4 for(int i = 0; i < chs.Length; i++) { 5 dic.Add(chs[i], i); 6 } 7 }
提供方法實現排序
1 private static void internal_jk_sort(char[] s, Dictionary<char, int> dic) { 2 int temp = -1; // 記錄最大逆序數的索引 3 4 for(int i = 1; i < s.Length; i++) { 5 for(int j = 0; j < i; j++) { 6 7 if(dic[s[i]] < dic[s[j]]) { 8 9 if(temp == -1 || dic[s[temp]] < dic[s[i]]) { 10 temp = i; 11 } 12 13 break; 14 } 15 } 16 } 17 // 移動逆序數 18 if(temp != -1) { 19 jk_remove(s, temp); 20 // 若是存在逆序數就遞歸繼續判斷,不然離開函數排序完成 21 internal_jk_sort( s, dic ); 22 } 23 }
實現對外公開的方法
1 public static string JK_Sort(string str, string strObj) { 2 char[] chs = str.ToCharArray(); 3 char[] obj = strObj.ToCharArray(); 4 5 JKSort j = new JKSort(obj); 6 7 internal_jk_sort(chs, j.dic); 8 9 10 return new string(chs); 11 }
那麼就完成了排序. 這裏提供的算法沒有實現優化,如想了解優化與帶有重複字符
串的解決辦法,請等待下文.
睡覺,2013年12月18日凌晨0時.
注1 逆序:在一個數字排列中,若是其中的兩個數前面的一個比後面的一個大,那麼就稱 它們構成一個逆序. 相關逆序的結論與問題,能夠參考《高等代數》或《線性代數》 的教材.