• 【DP-04】动态规划算法题目解析


    目录

    1. 121. 买卖股票的最佳时机/面试题63. 股票的最大利润
    2. 面试题19. 正则表达式匹配/10. 正则表达式匹配
    3. 剑指 Offer 60. n个骰子的点数
    4. 剑指 Offer 46. 把数字翻译成字符串

         

    一、121. 买卖股票的最佳时机/面试题63. 股票的最大利润

    1.1 问题:

    给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

    如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。

    注意:你不能在买入股票前卖出股票。

    示例 1:

    输入: [7,1,5,3,6,4]

    输出: 5

    解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。

    注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

    示例 2:

    输入: [7,6,4,3,1]

    输出: 0

    解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

    1.2 求解:

    1)步骤一:定义子问题

    和明显,使用dp[i],i表示前i天的最大化,i从取n表示原始问题,i取k表示子问题,子问题比较容易扩展成原始问题。

    2)写出子问题的递推关系

    初始值:dp = [0] * n ,边界情况 if n == 0: return 0

    3)确定 DP 数组的计算顺序

    启发式的计算顺序,代码见1.3代码一

    4 )空间优化(可选)

    因为只用到了dp[i-1],则可以将其空间压缩,将dp[i-1]更换成max_temp

    1.3 代码

    代码一:

    class Solution:

        def maxProfit(self, prices: List[int]) -> int:

            n = len(prices)

            if n == 0return 0

            dp = [0]*n

            minprice = prices[0]

            for i in range(n):

                minprice = min(minprice,prices[i])

                dp[i] = max(dp[i-1],prices[i]-minprice)

            return dp[-1]

    代码二:

    class Solution:

        def maxProfit(self, prices: List[int]) -> int:

            n = len(prices)

            if n == 0return 0

            max_temp = 0

            minprice = prices[0]

            for i in range(n):

                minprice = min(minprice,prices[i])

                max_temp  = max(max_temp,prices[i]-minprice)

            return max_temp

    二、面试题19. 正则表达式匹配/10. 正则表达式匹配

    2.1 问题:

    给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.'  '*' 的正则表达式匹配。

    '.' 匹配任意单个字符

    '*' 匹配零个或多个前面的那一个元素

    所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

    说明:

    可能为空,且只包含从 a-z 的小写字母。

    可能为空,且只包含从 a-z 的小写字母,以及字符 .  *

    示例 1:

    输入:

    s = "aa"

    p = "a"

    输出: false

    解释: "a" 无法匹配 "aa" 整个字符串。

    示例 2:

    输入:

    s = "aa"

    p = "a*"

    输出: true

    解释因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。

    示例 3:

    输入:

    s = "ab"

    p = ".*"

    输出: true

    解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。

    示例 4:

    输入:

    s = "aab"

    p = "c*a*b"

    输出: true

    解释因为 '*' 表示零个或多个,这里 'c' 0 , 'a' 被重复一次。因此可以匹配字符串 "aab"

    示例 5:

    输入:

    s = "mississippi"

    p = "mis*is*p*."

    输出: false

    2.2 求解:

    1)步骤一:定义子问题

    可以看着二维矩阵的形式,前面[i][j]规范好了,后面增加部分只要能匹配就可以,最终得到[m][n]最终情况。

    2)写出子问题的递推关系

    转移方程

    现在如果已知了 dp[i-1][j-1] 的状态,我们该如何确定 dp[i][j] 的状态呢?我们可以分三种情况讨论,其中,前两种情况考虑了所有能匹配的情况,剩下的就是不能匹配的情况了:

    1. s[i] == p[j] or p[j] == '.':比如 abb abb,或者 abb ab. ,很容易得到 dp[i][j] = dp[i-1][j-1] = True。因为 ab ab 是匹配的,如果后面分别加一个 b,或者 s 加一个 b p 加一个 . ,仍然是匹配的。
    2. p[j] == '*':当 p[j] 为星号时,由于星号与前面的字符相关,因此我们比较星号前面的字符 p[j-1]根据星号前面的字符 s[i] 是否相等,分两种情况:
    • 相等p[j-1] == s[i] or p[j-1] == '.':星号前面的字符可以与 s[i] 匹配,这种情况下,星号可能匹配了前面的字符的 0 个,也可能匹配了前面字符的多个,当匹配 0 个时,如 ab abb*,或者 ab ab.* ,这时我们需要去掉 p 中的 b* .* 后进行比较,即 dp[i][j] = dp[i][j-2];当匹配多个时,如 abbb ab*,或者 abbb a.*,我们需要将 s[i] 前面的与 p 重新比较,即 dp[i][j] = dp[i-1][j]
    • 不相等p[j-1] != s[i]:如果星号前一个字符匹配不上,星号匹配了 0 次,应忽略这两个字符,看 p[j-2] s[i] 是否匹配。 这时 dp[i][j] = dp[i][j-2]
    1. 其他情况:以上两种情况把能匹配的都考虑全面了,所以其他情况为不匹配,即 dp[i][j] = False

    将以上进行归纳得到状态转移方程:

    初始化:初始化定义第一行的值(若出现星号,可以考虑删除,退化到找前推2个(dp[0][c] = dp[0][c - 2]))

    dp = [[False for _ in range(n)] for _ in range(m)]

    # 初始状态

    dp[0][0] = True

    dp[0][1] = False

    for c in range(2, n):

    j = c - 1

    if p[j] == '*':

    dp[0][c] = dp[0][c - 2]

    3)确定 DP 数组的计算顺序

    启发式形式,定义好第一行,从上到下进行处理。具体代码见2.3代码一

    4 )空间优化(可选)

    问题较难,没有优化了。

    2.3 代码

    代码一:

    class Solution:

        def isMatch(self, s: str, p: str) -> bool:

            边界条件,考虑 s  p 分别为空的特殊情况情况  

            if not p: return not s

            if not s and len(p) == 1return False

            m,n = len(s)+1len(p)+1

            dp = [[False for _ in range(n)] for _ in range(m)]  #(m+1)(n+1)的矩阵

            #initializtion

            dp[0][0] = True

            dp[0][1] = False

            for c in range(2,n):

                j = c -1

                if p[j] == '*' :

                    dp[0][c] = dp[0][c-2]

            for r in range(1,m):

                i = r -1   #比较的是前置项,而且出现次数较多,故单独令下

                for c in range(1,n):

                    j = c -1

                    if s[i] == p[j] or p[j] == ".":

                        dp[r][c] = dp[r-1][c-1]

                    elif p[j] == '*' :

                        if p[j-1] == s[i]  or p[j-1] ==".":

                            dp[r][c] = dp[r-1][c]  or dp[r][c-2]  #注意这里有分了两种情况,第二个的举例:ab  ab.* 

                        else:dp[r][c] = dp[r][c-2]

                    else:dp[r][c] == False

            return dp[-1][-1]

    代码二:回溯法

    class Solution:

        def isMatch(self, s: str, p: str) -> bool:

            if not p: return not s

            第一个字母是否匹配

            first_match = bool(s and p[0in {s[0],'.'})

            如果 p 第二个字母是 *

            if len(p) >= 2 and p[1] == "*":

                return self.isMatch(s, p[2:]) or  first_match and self.isMatch(s[1:], p)

            else:

                return first_match and self.isMatch(s[1:], p[1:])

    三、剑指 Offer 60. n个骰子的点数

    3.1 问题:

    n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。

       

    示例 1:

    输入: 1

    输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]

    示例 2:

    输入: 2

    输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]

    3.2 求解:

    1)步骤一:定义子问题

    子问题是和原问题相似,但规模较小的问题,可以利用小规模子问题刻画其结构特征。

    说明:创建一个二维数组,保存每一个子问题的结果,具体创建一维数组还是二维数组看题目而定

    注意:子问题的解一旦求出就会被保存,所以每个子问题只需求 解一次。

    2)写出子问题的递推关系

    总体思路:先求n个骰子点数的和为s的排列情况总数(见下),再除以6^n得到各自的概率。

    • 确定问题解的表达式。可将f(n, s) 表示n个骰子点数的和为s的排列情况总数
    • 确定状态转移方程。n个骰子点数和为s的种类数只与n-1个骰子的和有关。因为一个骰子有六个点数,那么第n个骰子可能出现16的点数。所以第n个骰子点数为1的话,f(n,s)=f(n-1,s-1),当第n个骰子点数为2的话,f(n,s)=f(n-1,s-2),…,依次类推。在n-1个骰子的基础上,再增加一个骰子出现点数和为s的结果只有这6种情况!那么有:f(n,s)=f(n-1,s-1)+f(n-1,s-2)+f(n-1,s-3)+f(n-1,s-4)+f(n-1,s-5)+f(n-1,s-6)
    • 上面就是状态转移方程,已知初始阶段的解为:当n=1, f(1,1)=f(1,2)=f(1,3)=f(1,4)=f(1,5)=f(1,6)=1

    3)确定 DP 数组的计算顺序

    从自底向上的 dp 数组

    4 )空间优化(可选)

    3.3 代码

    代码一:

    class Solution:

        def twoSum(self, n: int) -> List[float]:

            dp = [ [0 for _ in range(6*n+1)] for _ in range(n+1)]

            for i in range(1,7):   #初始化

                dp[1][i] = 1

       

            for i in range(2,n+1):   #次数循环,从2开始。从上到下的遍历

                for j in range(i,i*6+1): #从第 i 开始进行计数,一直到 6*I,表示可能出现的和的情况;从做到右的遍历

                    for k in range(1,7): #状态转移方程

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

       

            res = []

            for i in range(n,n*6+1):   #回到问题,计算概率

                res.append(dp[n][i]*1.0/6**n)

            return res

    四、剑指 Offer 46. 把数字翻译成字符串

    4.1 问题

    给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 "a" 1 翻译成 "b",……,11 翻译成 "l",……,25 翻译成 "z"。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

       

    示例 1:

    输入: 12258

    输出: 5

    解释: 122585种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi""mzi"

    4.2 求解:

    1)步骤一:定义子问题

    去掉一个对整体没有问题结构不造成影响,可以求出前面的情况,依次得出最终的。

    2)写出子问题的递推关系

    我们有两种选择:

    只翻译自己;

    和前面的数字组合翻译,前提是组合的数在 10-25之间。

    3)确定 DP 数组的计算顺序

    从底向上形式

    4 )空间优化(可选)

    不考虑

    4.3 代码

    class Solution:

        def translateNum(self, num: int) -> int:

            str_num = str(num)

            n = len(str_num)

            dp = [1 for _ in range(n + 1)] # n+1

            for i in range(2, n + 1):#注意n >= 2,

                if str_num[i - 2] == '1' or (str_num[i - 2] == '2' and str_num[i - 1] < '6'):

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

                else:

                    dp[i] = dp[i - 1]

            return dp[n]

     

    参考文章:

    1https://leetcode-cn.com/

  • 相关阅读:
    [转]list的交集,差集,并集
    [转]$.post() 和 $.get() 如何同步请求
    [转]Jsoup(一)Jsoup详解(官方)
    [转]Kindeditor图片粘贴上传(chrome)
    [转]kindeditor隐藏上传图片框网络图片或本地上传的功能
    微信公众号平台上传文件返回错误代码:40005 invalid file type
    [转]spring MultipartFile 转 File
    [转]客户端js判断文件类型和文件大小即限制上传大小
    java list排序
    spring security oauth2.0 实现
  • 原文地址:https://www.cnblogs.com/yifanrensheng/p/13510817.html
Copyright © 2020-2023  润新知