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]

猜你喜欢

转载自blog.csdn.net/weixin_42001089/article/details/83116287