1. 程式人生 > >DP動態規劃--例題Decode Ways 、 Longest Palindromic Substring詳解

DP動態規劃--例題Decode Ways 、 Longest Palindromic Substring詳解

1題目:

A message containing letters from A-Z is being encoded to numbers using the following mapping:

'A' -> 1
'B' -> 2
...
'Z' -> 26

Given a non-empty string containing only digits, determine the total number of ways to decode it.

Example 1:

Input: "12"
Output: 2
Explanation: It could be decoded as "AB" (1 2) or "L" (12).

Example 2:

Input: "226"
Output: 3
Explanation: It could be decoded as "BZ" (2 26), "VF" (22 6), or "BBF" (2 2 6).

1思路:

假設使用dp[i]表示0到i這一子字串有多少可能的結果,那麼在判斷dp[i+1]時即0到i+1這一子字串時可分為以下情況:

如果s[i]和s[i+1]組合符合解碼條件,那麼就是說有兩種情況即dp[i+1]=dp[i]+dp[i-1]

否則dp[i+1] = dp[i]

舉例來說假如dp[i-1]= 4, dp[i]=4, s[i]='1',s[i+1]='2' 可以看到 s[i]和s[i+1]組合是可以的,所以可以斷定至少有dp[i-1]種,又因為s[i+1]對應的2也可以單獨解碼,所以又可以有dp[i]=4種,所以一共有dp[i]+dp[i-1]=4+4=8種

說假如dp[i-1]= 4, dp[i]=4, s[i]='3',s[i+1]='2' 可以看到 s[i]和s[i+1]組合是不可以的,所以只剩下s[i+1]對應的2單獨解碼這一種可能,所以只能有dp[i]=4種這一種可能,所以dp[i+1] = dp[i]=4

好了利用dp[i]= 4, dp[i+1]=4, s[i+1]='1',s[i+2]='2' 接著往下找dp[i+2],直到結束,這裡其實就是動態規劃的核心即遞迴

注意:這裡有個情況需要單獨考慮即0這個元素

舉例來說假如dp[i-1]= 4, dp[i]=4, s[i]='1',s[i+1]='0’ 現在我們要找dp[i+1]可以看到s[i]和s[i+1]組合是可以的(10),但是s[i+1]是不可以單獨解碼的即0是不可以單獨解碼的

(1)所以當s[i+1]==0時有以下兩種情況:

當s[i]和s[i+1]可以組合時(0<s[i]+s[i+1]<=26,z之所以大於零是思考到s[i]==0,s[i+1]==0這種情況):

dp[i+1] = dp[i-1]

當s[i]和s[i+1]不可以組合時:即假如s[i]='4',s[i+1]='0’ ,那麼這是沒有解碼結果的!!!!!!,直接返回0即可

(2)同理在s[i+1]!=0時我們在利用1思路進行遞迴時也應該考慮到s[i]=='0'這種特殊情形下的遞迴

即當dp[i-1]= 4, dp[i]=4, s[i]='0',s[i+1]='3’ 時,那麼此時只有一種情況即dp[i+1] = dp[i]

初始化問題:我們先將dp[1]初始化為1(注意這裡的1代表的就是s的第一個元素,即s[0]),那麼我們為什麼不用dp[0]這樣不是統一了下標嗎?哈哈,想一想如果這樣的話,那麼我們在判斷dp[1]的時候有可能s[1]和s[0]能組合,按照思路1,我們應該要用到dp[1-2]即dp[-1]是不是就出錯了,所以我們用dp[1]對應到s的第一位,那麼dp[1]初始化為1不難理解,就是一個數嘛!那麼s[0]初始化為多少呢?很簡單啦,試想一下什麼時候用到s[0]呢?就是我們上面所說的那種情況比如s='123',現在我們i=1即判斷s[i]='2'時,我們要用到dp[0],當其是1時才滿足我們需求(這裡12明顯對應是有2種情況,即dp[1]+dp[0]=2,現在dp[1]已經初始化為1啦,當然dp[0]初始化為1啦!!!!!!!)

好了說了這麼多,其實對應到動態規劃的部分最核心的東西就是思路1的部分,後面只是0的這個元素對應的幾個特殊情況和初始化問題

最後給一下全部程式碼

class Solution:
    def numDecodings(self, s):
        """
        :type s: str
        :rtype: int
        """
        if s[0]=='0':
            return 0
        dp = []
        dp.append(1)
        dp.append(1)
        
        for i in range(1,len(s)):
            if s[i]=='0' :
                if int(s[i-1]+s[i])>26 or int(s[i-1]+s[i])==0 :
                    return 0
                else:
                    dp.append(dp[i-1])
            else:
                if s[i-1]=='0':
                    dp.append(dp[i])
                elif int(s[i-1]+s[i])>26:
                    dp.append(dp[i])
                else:
                    dp.append(dp[i]+dp[i-1])
        return dp[len(dp)-1]

2題目:

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example 1:

Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.

Example 2:

Input: "cbbd"
Output: "bb"

2思路:

暴力方法也是最容易想到的方法:一個一個元素暴力搜尋過去,即以當前元素為中心向兩邊延伸看是迴文數前提下最大能達到的長度,最後篩選整個元素對應的迴文長度,返回即可。但是該演算法時間複雜度明顯較高,即最外面一個大的for迴圈,裡面還有一個for迴圈用來遍歷當前元素兩邊的元素。

細想一下這裡面的事其實會發現很多時候我們做的事情都是冗餘的,比如我們之前遍歷過一個字字串,但是到下一個中心元素時,我們還有可能冗餘的再次遍歷一下這個子字串,是吧,基於此Manacher演算法也叫馬拉車演算法提出來了

下面大概講一下原理:

其同樣使用了動態規劃,首先要解決的是奇偶問題,例如aba 和 cbbc這兩種迴文情況,前者遍歷是從s[1]開始看兩邊,而後者則是比較s[1]和s[2]然後依次向兩邊延伸,所以情況不一樣,所以這裡在進行之前先要轉化一下即假設s為aba那麼轉化後為#a#b#a#,s為cbbc時同理轉化為#c#b#b#c#,可以看到轉化後的新字串都是奇數。同時在轉化後的字串首尾都各加一個特殊字元,防止越界,具體原因後面說明

我們用一個列表p來記錄元素i對應的迴文數長度,即p[2]=4就是代表以s[2]為中心的迴文數長度是4,注意這是在轉化後的基礎上說的,對應到原始的字串上長度就是該長度-1即3,好啦,假設i以前都判斷好了,現在進行i的判斷即p[i]的值

首先明確幾個變數,Center是i以前擁有迴文數最長長度額中心元素位置,max_long對應的是以Center為中心時迴文數長度,mx 是記錄i之前回文串中最右面的位置,id 是其該回文數對應的中心元素位置,j是i關於id對稱的位置(j=2*id-i),Center是當前好了看看下面情況吧:

(1)當i<mx:

當p[j]<mx-i時:即以j為中心的迴文數左半面長度在綠色之內時,那麼其實p[i]=p[j]

當p[j].>=mx-i時:即超出綠色部分,超出部分我們還沒有遍歷過即mx右面的元素沒有遍歷過,所以我們從mx開始遍歷(注意我們這裡以i為中心進行遍歷時右半部分是從mx開始遍歷,而不是暴力的從i+1開始)

(2)當i>=mx時,我們就老老實實暴力遍歷吧

-------------------------------------------------------------------------------------------------------------------------------------------------------------------

總結:從上面可以看到其實我們降低時間複雜度的部分是降低在(1)了這個地方。

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

在演算法具體實現過程中其實我們可以將上面的情況進行合併,

即當i<mx時,讓p[i] = min(p[j],mx-i),當p[j].>=mx-i時讓p[i]=1(本身長度為1),然後後面統一以i為中心,左右各從距離i p[i]的位置進行遍歷即比較s[i-p[i]]和s[i+p[i]]是否相等,一旦符合就給p[i]加1,繼續下一個比較,如果不符合當即結束即可(這裡就是一個while迴圈)

試想當i<mx且p[j]<mx-i時,那麼進行p[i] = min(p[j],mx-i)後必定p[i]=p[j],後面遍歷時只進行一次就必定不滿足情況結束,即還是p[i]=p[j],達到我們要求的情況,當i<mx且p[j].>=mx-i時,那麼進行p[i] = min(p[j],mx-i)後必定p[i]=mx-i,那麼後面遍歷時右面也正好是從mx進行遍歷的,也符合我們的要求,噹噹i>=mx時,此時p[i]=1,那麼後面遍歷時正好也是從i+1開始的,也符合我們的要求

需要注意的就是加入轉化後的是#b#a#b#,可以看到在以a為中心進行遍歷的時候,因為該字串整個就是迴文數,所以while會一直迴圈下去,即左右位置到達0和6,還會迴圈下去,這時候就會因為越界報錯,解決辦法就是上面說的在轉化後的字串首尾都各加一個特殊字元,比如$#b#a#b#@這樣的話就可以啦,這裡的特殊字元可以隨便,!#b#a#b#~又或者是*#b#a#b#%都可以的。

最後分別更新id ,mx,Center,max_long即可

---------------------------------------------------------------------------------------------------------------------------------------------------------------

最後附上程式碼吧,這時候再看應該很簡單啦:

class Solution(object):
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        temp='$#'
        for c in s:
            temp+=c
            temp+='#'
        temp+='@'
        mx = 0
        max_long=0
        Center = 0
        p=[0]
        id =0
        for i in range(1,len(temp)-1):
            if i<mx:
                p.append(min(p[2*id-i],mx-i))
            else:
                p.append(1)
            while temp[i-p[i]]==temp[i+p[i]]:
                p[i]+=1
            if i+p[i]>mx:
                mx=i+p[i]
                id = i
            if p[i]>max_long:
                max_long = p[i]
                Center = i
        return s[(Center-max_long)/2:(Center+max_long)/2-1]