[LeetCode] Strong Password Checker 密碼強度檢查器
A password is considered strong if below conditions are all met:
- It has at least 6 characters and at most 20 characters.
- It must contain at least one lowercase letter, at least one uppercase letter, and at least one digit.
- It must NOT contain three repeating characters in a row ("...aaa..." is weak, but "...aa...a..." is strong, assuming other conditions are met).
Write a function strongPasswordChecker(s), that takes a string s as input, and return the MINIMUM change required to make s a strong password. If s is already strong, return 0.
Insertion, deletion or replace of any one character are all considered as one change.
這道題給了我們一個密碼串,讓我們判斷其需要多少步修改能變成一個強密碼串,然後給定了強密碼串的條件,長度為6到20之間,必須含有至少一個的小寫字母,大寫字母,數字,而且不能有連續三個相同的字元,給了我們三種修改方法,任意一個位置加入字元,刪除字元,或者是置換任意一個字元,讓我們修改最小的次數變成強密碼串。這道題定義為Hard真是名副其實,博主光是看大神的帖子都看了好久,這裡主要是參考了
1. 長度問題,當長度小於6的時候,我們要通過插入字元來補充長度,當長度超過20時,我們要刪除字元。
2. 缺失字元或數字,當我們缺少大寫,小寫和數字的時候,我們可以通過插入字元或者替換字元的方式來補全。
3. 重複字元,這個也是本題最大的難點,因為插入,刪除,或者置換都可以解決重複字元的問題,比如有一個字串"aaaaa",我們可以用一次置換,比如換掉中間的字元'a';或者兩次插入字元,在第二個a和第四個a後面分別插入一個非a字元;或者可以刪除3個a來解決重複字元的問題。由於題目要求我們要用最少的步驟,那麼顯而易見置換是最高效的去重複字元的方法。
我們通過舉例觀察可以知道這三種情況並不是相互獨立的,一個操作有時候可以解決多個問題,比如字串"aaa1a",我們在第二個a後面增加一個'B',變為"aaBa1a",這樣同時解決了三個問題,即增加了長度,又補充了缺失的大寫字母,又去掉了重複,所以我們的目標就是儘可能的找出這種能解決多種問題的操作。由於情況三(重複字元)可以用三種操作來解決,所以我們分別來看能同時解決情況一和情況三,跟同時解決情況二和情況三的操作。對於同時解決情況一和情況二的操作如果原密碼串長度小於6會有重疊出現,所以我們要分情況討論:
當密碼串長度小於6時,情況一和情況二的操作步驟可以完全覆蓋情況三,這個不難理解,因為這種情況下重複字元個數的範圍為[3,5],如果有三個重複字元,那麼增加三個字元的操作可以同時解決重複字元問題("aaa" -> "a1BCaa";如果有四個重複字元,那麼增加二個字元的操作也可以解決重複問題("aaaa" -> "aa1Baa");如果有五個重複字元,那麼增加和置換操作也同時解決重複問題("aaaaa" -> "aa1aaB")。所以我們就專心看最少多少步能同時解決情況一和情況二,首先我們計算出當前密碼串需要補幾個字元才能到6,補充字元的方法只能用插入字元操作,而插入字元操作也可以解決情況二,所以當情況二的缺失種類個數小於等於diff時,我們不用再增加操作,當diff不能完全覆蓋缺失種類個數時,我們還應加上二者的差值。
當密碼串長度大於等於6個的時候,這種情況就比較複雜了,由於目前字串的長度只可能超標不可能不達標,所以我們儘量不要用插入字元操作,因為這有可能會使長度超過限制。由於長度的不確定性,所以可能會有大量的重複字元,那麼解決情況三就變得很重要了,由於前面的分析,替換字元是最高效的解法,但是這種方法沒法解決情況一,因為長度超標了的話,再怎麼替換字元,也不會讓長度減少,但是我們也不能無腦刪除字元,這樣不一定能保證是最少步驟,所以在解決情況三的時候還要綜合考慮到情況一,這裡用到了一個trick (很膜拜大神能想的出來),對於重複字元個數k大於等於3的情況,我們並不是直接將其刪除到2個,而是先將其刪除到最近的(3m+2)個,那麼如果k正好被3整除,那麼我們直接變為k-1,如果k除以3餘1,那麼變為k-2。這樣做的好處是3m+2個重複字元可以最高效的用替換m個字元來去除重複。那麼下面我們來看具體的步驟,首先我們算出超過20個的個數over,我們先把over加到結果res中,因為無論如何這over個刪除操作都是要做的。如果沒超過,over就是0,用變數left表示解決重複字元最少需要替換的個數,初始化為0。然後我們遍歷之前統計字元出現個數的陣列,如果某個字元出現個數大於等於3,且此時over大於0,那麼我們將個數減為最近的3m+2個,over也對應的減少,注意,一旦over小於等於0,不要再進行刪除操作。如果所有重複個數都減為3m+2了,但是over仍大於0,那麼我們還要進一步的進行刪除操作,這回每次直接刪除3m個,直到over小於等於0為止,剩下的如果還有重複個數大於3的字元,我們算出置換字元需要的個數直接加到left中即可,最後我們比較left和missing,取其中較大值加入結果res中即可,參見程式碼如下:
class Solution { public: int strongPasswordChecker(string s) { int res = 0, n = s.size(), lower = 1, upper = 1, digit = 1; vector<int> v(n, 0); for (int i = 0; i < n;) { if (s[i] >= 'a' && s[i] <= 'z') lower = 0; if (s[i] >= 'A' && s[i] <= 'Z') upper = 0; if (s[i] >= '0' && s[i] <= '9') digit = 0; int j = i; while (i < n && s[i] == s[j]) ++i; v[j] = i - j; } int missing = (lower + upper + digit); if (n < 6) { int diff = 6 - n; res += diff + max(0, missing - diff); } else { int over = max(n - 20, 0), left = 0; res += over; for (int k = 1; k < 3; ++k) { for (int i = 0; i < n && over > 0; ++i) { if (v[i] < 3 || v[i] % 3 != (k - 1)) continue; v[i] -= k; over -=k; } } for (int i = 0; i < n; ++i) { if (v[i] >= 3 && over > 0) { int need = v[i] - 2; v[i] -= over; over -= need; } if (v[i] >= 3) left += v[i] / 3; } res += max(missing, left); } return res; } };
參考資料: