10.3.2 (python) 动态规划数组类LeetCode题目 —— Decode Ways & Range Sum Query 2D

这几道题稍微加大难度,DP的框架是不变的,关键是分析问题的结构,分析状态转移关系。

91. Decode Ways

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.

题目解析:

首先对问题进行一下解读,对于一串数字来说,可能有多种解码结果,主要是因为10-19,20-26的存在,相邻的两个数字可能如21就有可能被解读为两种情况,因此一串数字的解码结果就会有更多种。

需要注意的是0,如“30” 返回结果是0,说明没有解码结果。

这一题的子问题形式也很明显,如“226”有3种结果,“2268”也有3种结果,两者之间结果是存在关系的。

下面的关键任务就是分析出这个关系,整个代码的框架,也都是围绕这个关系进行的。

这里采用由后往前的DP,这是我们应该扩展思路的地方~

我们假设 dp[i]代表i及其之后的数字的解码方式。以"2268"为例,dp[3] = 1, dp[2] = 1, dp[1]=2, dp[0]=3,我们要找到dp[i]和dp[i+1]之间的关系。

1. 当数字大于2时,dp[i] = dp[i+1], 该数字只能单独来解码;

2. 数字为1时,或者数字是2而且后面的数字是0-6时,是可以两个数字组合或者扔作为1或2来解码,注意关系式 dp[i] = dp[i+1] + dp[i+2];

3. 特别注意数字0,这里我们令 dp[i]=0。这样,当前一个数字大于2时,dp[i] = dp[i+1]=0,可以符合要求;前一个数字为1或2时,dp[i] = dp[i+1] + dp[i+2] = dp[i+2],也符合要求。

下面我们看代码,注意边界条件。

class Solution:
    def numDecodings(self, s: str) -> int:
        n = len(s)
        dp = [0] * (n+1)
        dp[-1] = 1
        ix = n-1
        while ix >= 0:
            dig = int(s[ix])            
            if dig >= 3:
                dp[ix] = dp[ix+1]
            elif dig == 2:
                if ix+1 < n and int(s[ix+1]) <= 6:
                    dp[ix] = dp[ix+1] + dp[ix+2]
                else:
                    dp[ix] = dp[ix+1]
            elif dig == 1:
                if ix+1 < n:
                    dp[ix] = dp[ix+1] + dp[ix+2]
                else:
                    dp[ix] = dp[ix+1]
            else: # 0
                dp[ix] = 0
                if int(s[ix-1]) >= 3 or ix == 0 or int(s[ix-1]) == 0:
                    return 0
            ix -= 1
        return dp[0]

这道题的思路很具有扩展性,应该好好理解一下。

顺便看一下 639. Decode Ways II

这里加上通配符"*",可以匹配1-9。这时我们再考虑解码方法时,动不动就会乘以9的,但是思路仍然可以参考上面的题目。

这里简要说明并附上代码。如下,转移关系仍然要分多种情况,大于2或等于0时最简单,其他情况(包括“*”)都要考虑后面的字符才能确定转移关系。其中"*"的关系最复杂,"**"时dp = pre1 * 9 + pre2 * (9+6) 即当前的"*"为1-9且单独解码时 9 * dp[i+1],想两个一起解码时,当前的"*"为1时有 pre2 *9种,为2时有 pre2 * 6种;后面各种情况都是类似考虑,建议自己写出几个例子研究。

class Solution:
    def numDecodings(self, s: str) -> int:
        l = len(s)
        
        pre2 = 1
        if s[-1] == "*":
            pre1 = 9
        elif s[-1] == "0":
            pre1 = 0
        else:
            pre1 = 1
        dp = pre1
        ix = l-2
        while ix >= 0:
            letter = s[ix]
            if letter == "*":
                if s[ix+1] == "*":
                    dp = pre1 * 9 + pre2 * (9+6)
                elif s[ix+1] == "0":
                    dp = 2 * pre2
                elif int(s[ix+1]) <= 6:
                    dp =  pre1 * 9 + pre2 * 2
                else:
                    dp =  pre1 * 9 + pre2 * 1
            elif letter == "0":
                dp = 0
                if ix == 0 or (s[ix-1] != "*" and int(s[ix-1]) >= 3):
                    return 0
            elif int(letter) == 1:
                if s[ix+1] == "*":
                    dp = pre1 + pre2 * 9
                else:
                    dp = pre1 + pre2
            elif int(letter) == 2:
                if s[ix+1] == "*":
                    dp = pre1 + pre2 * 6
                elif int(s[ix+1]) <= 6:
                    dp = pre1 + pre2
                else:
                    dp = pre1
            else:
                dp = pre1
            
            ix -= 1
            pre1, pre2 = dp, pre1
        return dp % (pow(10, 9) + 7)

 

304. Range Sum Query 2D - Immutable

Given a 2D matrix matrix, find the sum of the elements inside the rectangle defined by its upper left corner (row1, col1) and lower right corner (row2, col2).

Range Sum Query 2D
The above rectangle (with the red border) is defined by (row1, col1) = (2, 1) and (row2, col2) = (4, 3), which contains sum = 8.

题目解析:

数组不可变,可变的话用线段树啦,参考7.12.1 线段树原理及应用(上)

一维的问题想必难不倒大家,我们看这个二维的数组,我们还想用类似一维数组的方式解决二维问题。

引用评论区的解释:

Example:

 

image

 

To get the sum of area D, get the Whole Area sum of area A,B,C,D first. Then area D = Whole Area - area B - area C + area A because area B and area C both include A.

所以框架和转移关系就很明确了,在初始化时多作一些处理,请求结果的时候速度非常快。鄙人写的代码处理了许多边界问题,因为dp数组是 m*n的;这位大神十分厉害,dp为 (m+1)*(n+1)的,避免了一大堆边界处理,十分简洁。

class NumMatrix:

    def __init__(self, matrix: List[List[int]]):
        if not matrix or not matrix[0]: return None
        row = len(matrix)
        col = len(matrix[0])
        self.dp = [[0]*(col+1) for _ in range(row+1)]
        for i in range(1,row+1):
            for j in range(1,col+1):
                self.dp[i][j]=self.dp[i][j-1]+self.dp[i-1][j]-self.dp[i-1][j-1]+matrix[i-1][j-1]
				
    def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
        return self.dp[row2+1][col2+1]-self.dp[row2+1][col1]-self.dp[row1][col2+1]+self.dp[row1][col1]

dp的问题五花八门啊,关键是分析出状态转移关系。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值