一、題目:第一個只出現一次的字符
題目:在字符串中找出第一個只出現一次的字符。如輸入"abaccdeff",則輸出'b'。要求時間復雜度為O(n)。
最直觀的想法是從頭開始掃描這個字符串中的每個字符。當訪問到某字符時拿這個字符和后面的每個字符相比較,如果在后面沒有發現重復的字符,則該字符就是只出現一次的字符。如果字符串有n個字符,每個字符可能與后面的O(n)個字符相比較,因此這種思路的時間復雜度是O(n2),但是不滿足要求。
二、解題思路:以空間換時間
為了解決這個問題,我們可以定義一個哈希表(外部空間),其鍵值(Key)是字符,而值(Value)是該字符出現的次數。
同時我們還需要從頭開始掃描字符串兩次:
(1)第一次掃描字符串時,每掃描到一個字符就在哈希表的對應項中把次數加1。(時間效率O(n))
(2)第二次掃描時,每掃描到一個字符就能從哈希表中得到該字符出現的次數。這樣第一個只出現一次的字符就是符合要求的輸出。(時間效率O(n))
這樣算起來,總的時間復雜度仍然是O(n),滿足了題目要求,擦一擦汗,感嘆:這*裝得真有點技術!
裝完了B,開始將這個想法實現為代碼:
public static char FirstNotRepeatingChar(string str) { if(string.IsNullOrEmpty(str)) { return '\0'; } char[] array = str.ToCharArray(); const int size = 256; // 借助數組來模擬哈希表,只用1K的空間消耗 uint[] hastTable = new uint[size]; // 初始化數組 for (int i = 0; i < size; i++) { hastTable[i] = 0; } for (int i = 0; i < array.Length; i++) { hastTable[array[i]]++; } for (int i = 0; i < array.Length; i++) { if (hastTable[array[i]] == 1) { return array[i]; } } return '\0'; }
PS:字符(char)是一個長度為8的數據類型,因此總共有256種可能。(在C#中char則是長度為16位也就是2個字節)這里我們只列舉char是1個字節的情況,我們創建一個長度為256的數組來模擬哈希表,每個字母根據其ASCII碼值作為數組的下標對應數組的一個數字,而數組中存儲的是每個字符出現的次數。計算下來,它的大小是256*4字節(1個int類型在Windows下占4個字節)=1K。由於這個數組的大小是個常數,因此可以認為這種算法的空間復雜度是O(1)。
三、單元測試
3.1 測試用例
// 常規輸入測試,存在只出現一次的字符 [TestMethod] public void FirstCharTest1() { char actual = CharHelper.FirstNotRepeatingChar("google"); Assert.AreEqual(actual, 'l'); } // 常規輸入測試,不存在只出現一次的字符 [TestMethod] public void FirstCharTest2() { char actual = CharHelper.FirstNotRepeatingChar("aabccdbd"); Assert.AreEqual(actual, '\0'); } // 常規輸入測試,所有字符都只出現一次 [TestMethod] public void FirstCharTest3() { char actual = CharHelper.FirstNotRepeatingChar("abcdefg"); Assert.AreEqual(actual, 'a'); } // 魯棒性測試,輸入NULL [TestMethod] public void FirstCharTest4() { char actual = CharHelper.FirstNotRepeatingChar(null); Assert.AreEqual(actual, '\0'); }
3.2 測試結果
(1)測試通過情況
(2)代碼覆蓋率
四、總結擴展
如果需要判斷多個字符是不是在某個字符串里出現過或者統計多個字符在某個字符串中出現的次數,我們都可以考慮基於數組創建一個簡單的哈希表(或者使用基類庫中提供的現成的哈希表結構類型)。這樣可以用很小的空間消耗換來換取時間效率的提升。