zoukankan      html  css  js  c++  java
  • 【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/

  • 相关阅读:
    前沿技术解密——VirtualDOM
    Ques核心思想——CSS Namespace
    Unix Pipes to Javascript Pipes
    Road to the future——伪MVVM库Q.js
    聊聊CSS postproccessors
    【译】十款性能最佳的压缩算法
    Kafka Streams开发入门(9)
    Kafka Streams开发入门(8)
    【译】Kafka Producer Sticky Partitioner
    【译】99th Percentile Latency at Scale with Apache Kafka
  • 原文地址:https://www.cnblogs.com/yifanrensheng/p/13510817.html
Copyright © 2011-2022 走看看