1. 程式人生 > >[LeetCode] Strong Password Checker 密碼強度檢查器

[LeetCode] Strong Password Checker 密碼強度檢查器

A password is considered strong if below conditions are all met:

  1. It has at least 6 characters and at most 20 characters.
  2. It must contain at least one lowercase letter, at least one uppercase letter, and at least one digit.
  3. 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真是名副其實,博主光是看大神的帖子都看了好久,這裡主要是參考了

大神fun4LeetCode的帖子,個人感覺這個算是講的十分清楚的了,這裡就照搬過來吧。首先我們來看非強密碼串主要有的三個問題:

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;
    }
};

參考資料: