1. 程式人生 > >420. Strong Password Checker

420. Strong Password Checker

解法

參考這裡

主要是貪心= =

  1. 長度在6~20之間,通過插入和刪除操作解決
  2. 同時有大寫小寫和數字:通過插入、替換解決
  3. 不能連續重複大於等於3次:通過插入、刪除和替換解決

貪心的思路是,優先找能同時解決多個條件的操作。

  1. 長度小於6時:
    假設缺a個字元,並且缺k種字元,且同一字元連續重複次數在3~5次。
    首先,肯定要先新增min(a,k)個字元。

    1. 如果長度夠6了(min(a,k)=a),但是還差k-a種字元:

      • 如果重複次數小於等於4次,插入至少一個字元就一定能解決重複情況,所以新增min(a,k)
        個字元已經解決了。
      • 如果重複次數為5,那麼a必然等於1,插入一個字元會分割成2+3的情況,然後再把3裡面的k-a個字元替換操作就可以了。

      所以此時最小運算元為k

    2. 如果長度還沒有夠6(min(a,k)=k),這時候重複長度必然小於等於4,插入操作已經解決情況三,所以再隨便插入a-k個字元湊夠6個就可以了,此時最小運算元為a

    綜上所述,長度小於6時,最小運算元為max(a,k)

  2. 長度大於等於6時:
    這時候只可能是情況一、三,情況二、三一起解決了。
    當長度大於20時,我們假設超過了b個字元,它決定了我們至少有b個刪除操作。我們需要讓這b個刪除操作幫我們同時解決情況三。
    此外,假設我們還缺k種字元,最好的方法是通過替換同時解決情況二和三,它不會讓我們的長度不達標。

    • b>0時,假設某子串連續重複了r次,我們把它減少到3m+2個,這樣可以通過替換m個字元高效地解決
      如果所有的串都是3m+2長度了,那麼3個3個地減少,一直到b<=0為止,這樣b最小是-2,密碼長度為18,還是滿足條件的。

    • b<=0時,我們一定不會刪除,想想看:

      1. 對於3m長度的串,只用替換需要m次,結合上刪除,需要刪一次成3(m-1)+2,再用m-1次替換,結果還是一樣的。
      2. 對於3m+1的串,確實也需要替換m次,如果刪除一個字元變成3m3m的最小操作次數是m,那麼我們還多了一次刪除。

      所以當b<=0

      時,每個長為l的字串會需要l//3次替換

總結

總結一下整個邏輯:

  1. 當長度小於6時:次數為:max(i,k),i為缺少字元數,k為缺少字元種類數
  2. 當長度大於等於6時:
    1. 首先計算超過的字元數over
    2. 對於每個連續子串,當它的長度>=3時記錄(需要刪除字元數d,替換次數r)放入陣列repeat
    3. 每次刪除d個字元其實是為了減少一次替換,所以為了多減少替換,把d=1的優先於d=2的刪除
    4. 不斷刪除,計算刪除次數delete和替換次數replace,直到over<=delete或者len(repeat)==0
    5. 如果over>delete,說明還需要刪除,此時每刪除3個都能節省一次替換,我們算算最多需要減少的替換次數為:max(0,replace-k),至少要保留k次替換來滿足情況二。而剩下的刪除次數能減少的替換次數為:ceil((over-delete)/3),所以我們最終減少的替換次數為兩者的最小值,假設為save
    6. 最終結果是刪除max(over, delete)次,替換max(k, replace-save)
class Solution(object):
    def strongPasswordChecker(self, s):
        """
        :type s: str
        :rtype: int
        """
        n = len(s)
        repeat = []
        k = [1,1,1]
        beg = 0
        for i, c in enumerate(s):
            if c.isdigit():
                k[0] = 0
            elif c.isupper():
                k[1] = 0
            elif c.islower():
                k[2] = 0
            if i==0 or c!=s[i-1]:
                l = i-beg
                if l>=3:
                    d = (1 + (l % 3))%3
                    repeat.append((d, l // 3 if d != 0 else l // 3 + 1))
                beg = i
        else:
            l = n - beg
            if l >= 3:
                d = (1 + (l % 3)) % 3
                repeat.append((d, l // 3 if d!=0 else l//3+1))
        k = sum(k)
        if n<6:
            return max(6-n, k)
        repeat.sort()
        over = n-20
        delete = 0
        replace = 0
        while over>delete and len(repeat):
            d, r = repeat.pop(0)
            delete += d
            replace += (r-1)
        replace += reduce(lambda x, y: x + y[1], repeat, 0)
        if over>delete:
            save = min(max(0, replace-k),int(math.ceil((over-delete)*1.0/3)))
            delete += save*3
            replace -= save
        return max(over, delete)+max(k, replace)