动态规划学习

本篇博文主要记录动态规划DP这一块的学习。

印章(蓝桥杯6.27)

问题描述:共有n种图案的印章,每种图案的出现概率相同。小A买了m张印章,求小A集齐n种印章的概率。(1≤n,m≤20)
输入格式:一行两个正整数n和m
输出格式:一个实数P表示答案,保留4位小数。

思考1:
概率论?!分类讨论
1)当m<n时,集齐的概率为0
2)当m>n时,买的m张印章,每张有n种可能,分母:n的m次方;分子:(C_m^n) * (C_n m-n)(m张印章中,有n张是图案不同的,剩下的m-n张随意)。

思考2:
动态规划解题步骤:

1)设置状态,即数组
dp[i][j] = 印章数目为i、集齐j种印章的概率
dp[1][1] = 1
dp[i][1] = (1/n)^(i-1)
i < j时,dp = 0

2)确定状态转移方程
找状态之间的关系,从手里有(i-1)枚印章到 i 枚印章时,有两种情况,第一种,拿到的这枚印章种类手里已经有了,此时 dp[i][j] 表示手里已经有 j 种印章了,因此上个状态也只有 j 种印章,即 dp[i-1][j] 。dp[i][j] = dp[i-1][j] * j/n;第二种,拿到的这枚印章种类手里还没有,所以上个状态表示为dp[i-1][j-1],一共有n种印章,前面已经取了(j-1)种,而现在取的和前面没有重复,因此取的是n-(j-1)中的其中一个。

3)代码实现

n,m = map(int,input().split())
dp = [[0 for i in range(n+1)]for i in range(m+1)]
for i in range(1,m+1):
    for j in range(1,n+1):
        if (i < j):
            dp[i][j] = 0
        elif (j == 1):
            dp[i][j] = (1/n)**(i-1)
        else:
            dp[i][j] = (dp[i-1][j])*(j*1.0/n) + (dp[i-1][j-1])*((n-j+1)*1.0/n)
print("%.4f"%(dp[m][n]))   

拿金币(蓝桥杯6.28)

问题描述:有一个N x N的方格,每一个格子都有一些金币,只要站在格子里就能拿到里面的金币。你站在最左上角的格子里,每次可以从一个格子走到它右边或下边的格子里。请问如何走才能拿到最多的金币。
输入格式:第一行输入一个正整数n。以下n行描述该方格。金币数保证是不超过1000的正整数。
输出格式:最多能拿金币数量。

思考1:
n*n的方格应该用一个数组表示,dp[i][j]表示i行j列的金币数量,想要求出能拿金币最多的数量,每次移动做选择的时候,比较右边还是下边的金币数量大,哪边大就往哪边移动,直到不能移动为止(没有向右也没有向下的选择,即边界处)

思考2:
状态转移方程
dp[i][j] = max((dp[i][j-1]+dp[i][j]),(dp[i-1][j]+dp[i][j]))

代码

n = int(input())
rect = []

for i in range(n):
    rect.append(list(map(int, input().split())))
    
#将边界的数单独算出来    
for i in range(1,n):
    rect[0][i] =rect[0][i] + rect[0][i-1]
    
for i in range(1,n):
    rect[i][0] = rect[i][0] + rect[i-1][0]
    
for i in range(1,n):
    for j in range(1,n):
        rect[i][j] = max(rect[i][j-1],rect[i-1][j]) + rect[i][j]
                
print(rect[-1][-1])

最长回文子串(力扣6.29)

在这里插入图片描述
思考1:
1)关于判断是否为回文数,之前写过博客,可直接s==s[::-1]
2)字串可通过双循环得到
3)回文字串的长度最大!?
但这种判断肯定复杂度很高,而且这道题是动态规划那一p的,所以这个思路应该不太对。

思考2:
1)s[0…j]是回文子串则s[1…j-1]必定是回文子串,而且s[0]==s[j]
2)dp[i][j]为“s[i…j]是否为回文字符串”,如果是回文字符串,则dp[i][j]=true,且dp[i][j]=dp[i+1][j-1] && s[i]==s[j](状态转移方程)
3)问题边界,有1个字符时,dp=true;有两个字符,即i+1=j时,如果s[i]==s[j],则dp[i][j]=true

代码如下:

class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        if n < 2:
            return s
        
        max_len = 1
        begin = 0
        # dp[i][j] 表示 s[i..j] 是否是回文串
        dp = [[False] * n for _ in range(n)]
        for i in range(n):
            dp[i][i] = True
        
        # 递推开始
        # 先枚举子串长度
        for L in range(2, n + 1):
            # 枚举左边界,左边界的上限设置可以宽松一些
            for i in range(n):
                # 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
                j = L + i - 1
                # 如果右边界越界,就可以退出当前循环
                if j >= n:
                    break
                    
                if s[i] != s[j]:
                    dp[i][j] = False 
                else:
                    if j - i < 3:
                        dp[i][j] = True
                    else:
                        dp[i][j] = dp[i + 1][j - 1]
                
                # 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
                if dp[i][j] and j - i + 1 > max_len:
                    max_len = j - i + 1
                    begin = i
        return s[begin:begin + max_len]

最大子数组和(力扣6.30)

在这里插入图片描述
思考1:
dp[i][j]表示nums中从 i 到 j 位置的和。
dp[i][i]表示nums本身
状态转移方程:
i < j 时,dp[i][j] = dp[i][j-1] + nums[j]
返回dp中最大的值。

思考2:
二维数组表示最大值反而复杂了,一维数组即可。
dp[i]:表示以 nums[i] 结尾的连续子数组的最大和。
如果dp[i - 1] > 0,那么可以把nums[i]直接接在dp[i - 1]表示的那个数组的后面,得到和更大的连续子数组;
如果dp[i - 1] <= 0,那么nums[i]加上前面的数dp[i - 1]以后值不会变大。此时单独的一个nums[i]的值,就是dp[i]。

代码:

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        n = len(nums)
        dp = [0] * n
        dp[0] = nums[0]

        for i in range(1,n):
            dp[i] = max(dp[i-1]+nums[i],nums[i])

        return max(dp)

爬楼梯(力扣7.1)

在这里插入图片描述
思考1:
设置状态和边界条件:
dp[i]表示到i阶有dp[i]种方法;dp[1]=1,dp[2] = 2,dp[3]=3

状态转移方程:
dp[i] = dp[i-1] + dp[i-2]

class Solution:
    def climbStairs(self, n: int) -> int:
        dp = [0] * n
        dp[0] = 1
        ways = 0
        if n == 1:
            ways = 1
        elif n == 2:
            ways = 2
        else: 
            dp[1] = 2
            for i in range(2,n):
                dp[i] = dp[i-1] + dp[i-2]
            ways = dp[n-1]
        return ways

杨辉三角(力扣7.2)

在这里插入图片描述
思考:
二维数组dp[i][j]表示第i行第j列的数字,每行的第一个和最后一个数字都是1。
状态转移方程:
当j=0或i时,dp[][]=1
其他情况下,dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
二维数组的创建?!

class Solution:
    def generate(self, numRows: int) -> List[List[int]]:
        dp = [[0] * i for i in range(1,numRows+1)]

        dp[0][0] = 1

        if numRows==1:
            dp = [[1]]
    
        elif numRows==2:
            dp = [[1],[1,1]]
    
        else:
            for i in range(numRows):
                for j in range(i+1):
                    if (j==0)or(j==i):
                        dp[i][j] = 1
                    else:
                        dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
        return dp

官方答案:

class Solution:
    def generate(self, numRows: int) -> List[List[int]]:
        ret = list()
        for i in range(numRows):
            row = list()
            for j in range(0, i + 1):
                if j == 0 or j == i:
                    row.append(1)
                else:
                    row.append(ret[i - 1][j] + ret[i - 1][j - 1])
            ret.append(row)
        return ret

买卖股票的最佳时机(力扣7.3)

在这里插入图片描述
思考2:
法一:暴力求解法
前i天的最大收益 = max{前i-1天的最大收益,第i天的价格-前i-1天中的最小价格}
直接max(prices[j] - prices[i])

prices = [7,1,5,3,6,4]
n = len(prices)
ans = 0
for i in range(n):
    for j in range(i+1,n):
        ans = max(ans,prices[j]-prices[i])
print(ans)

法二:
(有地方不是很懂)
找出最低点买入,最高点卖出
max(prices) - min(prices)

class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        inf = int(1e9)
        minprice = inf
        maxprofit = 0
        for price in prices:
            maxprofit = max(price - minprice, maxprofit)
            minprice = min(price, minprice)
        return maxprofit

比特位计数(力扣7.4)

在这里插入图片描述


判断子序列(力扣7.5)

在这里插入图片描述
思考1:
dp[i]表示s中到第i个子序列是否为t的子序列。
t[i] = s[i] in t
dp[i] = dp[i-1] and t[i]?

思考2:
方法一(官方答案)
初始化两个指针i 和 j,分别指向 s 和 t 的初始位置。每次贪心地匹配,匹配成功则 i 和 j 同时右移,匹配 s 的下一个位置,匹配失败则 j 右移,i不变,尝试用 t 的下一个字符匹配 s。最终如果 i 移动到 s 的末尾,就说明 s 是 t 的子序列。

class Solution:
    def isSubsequence(self, s: str, t: str) -> bool:
        n, m = len(s), len(t)
        i = j = 0
        while i < n and j < m:
            if s[i] == t[j]:
                i += 1
            j += 1
        return i == n

斐波那契数(力扣7.6)

在这里插入图片描述

class Solution:
    def fib(self, n: int) -> int:
        dp = [0] * (n+1)
        if n < 2:
            return n
        dp[0] = 0
        dp[1] = 1
        for i in range(2,n+1):
            dp[i] = dp[i-1] + dp[i-2]
        return dp[n]

官方

class Solution:
    def fib(self, n: int) -> int:
        if n < 2:
            return n
        
        p, q, r = 0, 0, 1
        for i in range(2, n + 1):
            p, q = q, r
            r = p + q
        
        return r

使用最小花费爬楼梯(力扣7.7)

在这里插入图片描述
思考1:
dp[i]表示达到下标i的最小花费。
dp[i] = min(dp[i-1]+cost[i-1], dp[i-2] + cost[i-2])

class Solution:
    def minCostClimbingStairs(self, cost: List[int]) -> int:
        n = len(cost)
        dp = [0] * (n+1)
        dp[0] = dp[1] = 0
        for i in range(2,n+1):
            dp[i] = min(dp[i-1]+cost[i-1], dp[i-2] + cost[i-2])
        return dp[n]

除数博弈(力扣7.12)

在这里插入图片描述
思考1:
n为奇数的时候,爱丽丝必输;n为偶数的时候,爱丽丝必赢。

思考2:(某网友)
1.将所有的小于等于 N 的解都找出来,基于前面的,递推后面的。
2.如果 i 的约数里面有存在为 False 的(即输掉的情况),则当前 i 应为 True;如果没有,则False。
代码:

class Solution:
    def divisorGame(self, N: int) -> bool:
        target = [0 for i in range(N+1)]
        target[1] = 0 #若爱丽丝抽到1,则爱丽丝输
        if N<=1:
            return False
        else:
        
            target[2] = 1 #若爱丽丝抽到2,则爱丽丝赢
            for i in range(3,N+1):
                for j in range(1,i//2):
                    # 若j是i的余数且target[i-j]为假(0)的话,则代表当前为真(1)
                    if i%j==0 and target[i-j]==0:
                        target[i] = 1
                        break
            return target[N]==1

第 N 个泰波那契数(力扣7.12)

在这里插入图片描述
思考:
跟斐波那契数列差不多,比较简单

class Solution:
    def tribonacci(self, n: int) -> int:
        if n == 0:
            return 0
        if 0 < n < 3:
            return 1
        q = 0
        r = 1
        t = 1
        for i in range(2,n):
            p,q,r = q,r,t
            t = p+q+r
        return t

获取生成数组中的最大值(力扣7.13、简单)

在这里插入图片描述
思考:
生成数组的规则已经明确,比较num[i+1]和num[i-1],最大值即为num[i] + max(num[i+1],num[i-1])
分奇偶?
思考2:
直接max(nums)
找出规律,一个nums[i]与nums[i//2]的式子即可表示

代码:

class Solution:
    def getMaximumGenerated(self, n: int) -> int:
        if n == 0:
            return 0
        nums = [0] * (n + 1)
        nums[1] = 1
        for i in range(2, n + 1):
            nums[i] = nums[i // 2] + i % 2 * nums[i // 2 + 1]
        return max(nums)

括号生成(力扣7.13、中等)

在这里插入图片描述
思考:
输入:n=1
输出:[“()”]

输入:n=2
输出:[“(())”,“()()”]

输入:n=3
输出:[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”]

如何生成n=4的输出,大括号往上套,1+1+3+3(重复一个)+4
括号放一侧,2+2+2+2+1
如何更换以前的列表内容!

思考2:
把括号()作为一个整体插入,要比前面的思考方式简单,用集合还可以直接去重
代码:

class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        if n == 1:
            return list({'()'})
        res = set()
        for i in self.generateParenthesis(n - 1):
            for j in range(len(i) + 2):
                res.add(i[0:j] + '()' + i[j:])
        return list(res)  

跳跃游戏(力扣7.14 难度中等)

在这里插入图片描述
思考1:
数组的所有和等于数组的长度len

思考2:
如果x+nums[x]>=y,那么位置y可以到达。
可以用 x + nums[x] 更新 最远可以到达的位置
如果最远可以到达的位置>=数组的最后一个位置,那么就返回true

官方代码:

class Solution:
    def canJump(self, nums: List[int]) -> bool:
        n, rightmost = len(nums), 0
        for i in range(n):
            if i <= rightmost:
                rightmost = max(rightmost, i + nums[i])
                if rightmost >= n - 1:
                    return True
        return False

跳跃游戏 II(力扣7.14 难度中等)

在这里插入图片描述
思考1:
直接在前一题的基础上改,但注意的是不访问最后一个元素。

代码:

class Solution:
    def jump(self, nums: List[int]) -> int:
        n, rightmost = len(nums), 0
        end = 0
        j = 0
        for i in range(n-1):
            if i <= rightmost:
                rightmost = max(rightmost, i + nums[i])
                if i == end:
                    end = rightmost
                    j += 1
        return j

不同路径(力扣7.15 难度中等)

在这里插入图片描述
思考1:
二维数组dp[i][j]表示到达改位置最多的路径,dp[0][0]到达dp[m-1][n-1],边界条件,i<m,j<n 而且dp[0][j]或者dp[i][0]都是1。
状态转移dp[i][j]=dp[i][j-1]+dp[i-1][j]过来的

其实也就是杨辉三角

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        dp = [[1 for i in range(n+2)]for i in range(m+2)]
        for i in range(2,m+1):
            for j in range(2,n+1):
                dp[i][j] = dp[i][j-1] + dp[i-1][j]
        return dp[m][n]

不同路径2(力扣7.15 难度中等)

最小路径和(7.16)

在这里插入图片描述
跟蓝桥杯拿金币是一摸一样的。

class Solution:
    def minPathSum(self, grid: List[List[int]]) -> int:
        m = len(grid)
        n = len(grid[0])

        for i in range(1,n):
            grid[0][i] = grid[0][i-1] + grid[0][i]

        for j in range(1,m):
            grid[j][0] = grid[j-1][0] + grid[j][0]

        for i in range(1,m):
            for j in range(1,n):
                grid[i][j] = grid[i][j] + min(grid[i-1][j],grid[i][j-1])

        return grid[-1][-1]

解码方法(7.16)

在这里插入图片描述
由于只需要输出方法的总数,所以应该并不需要去做数字和字母的一一映射,只需要把数字拆分,看可以有几种拆分方法,拆分出来的数字只能在1-26之间。

dp[i]表示到i位置有几种拆分方法,状态转移dp[i] = dp[i-1] * ds[i]。

ds[i]如果不是最后一个位置,则当为0时,ds[i]为1,当为1时,ds[i]为2,当为2时,判断i+1位置的数字是否为0到6之间,是的话,ds[i]为2。

class Solution:
    def numDecodings(self, s: str) -> int:
        n = len(s)
        f = [1] + [0] * n
        for i in range(1, n + 1):
            if s[i - 1] != '0':
                f[i] += f[i - 1]
            if i > 1 and s[i - 2] != '0' and int(s[i-2:i]) <= 26:
                f[i] += f[i - 2]
        return f[n]

三角形最小路径和(7.19)

在这里插入图片描述
思考1:

dp[i]表示到第i行的最小路径和,假设第i行的最小值取在(i,j)dp[i+1] = dp[i] + min(triangle[i+1][j],triangle[i+1][j+1])

自己写的代码:

class Solution:
    def minimumTotal(self, triangle: List[List[int]]) -> int:
        n = len(triangle)
        dp =[0] * n
        dp[0] = triangle[0]
        for i in range(1,n):
            j = (triangle[i-1]).index(min(triangle[i-1])) 
            dp[i] = dp[i-1] + min(triangle[i][j],triangle[i][j+1])
        return dp[n-1]

在这里插入图片描述
上面的代码思路有两个很大的问题:
一个是在求j的时候,忽略了j应该是上一行最小值的相邻结点。
另一个就是题目是让求最小路径和,每行最小值再求和这种思路不对。

思考2:
看完一个博主的讲解视频后,发现自己前面的思路很有问题。
边界条件:
当j=0的时候,triangle[i][j] = triangle[i][j] + triangle[i-1][j]
当j=i的时候,triangle[i][j] = triangle[i][j] + triangle[i-1][j-1]
其他时候,triangle[i][j] = triangle[i][j] + min(triangle[i-1][j],triangle[i-1][j-1])

class Solution:
    def minimumTotal(self, triangle: List[List[int]]) -> int:
        n = len(triangle)
        if n == 0:
            return 0
        for i in range(1,n):
            for j in range(i+1):
                if j == 0:
                    triangle[i][j] = triangle[i][j] + triangle[i-1][j]
                elif j == i:
                    triangle[i][j] = triangle[i][j] + triangle[i-1][j-1]
                else:
                    triangle[i][j] = triangle[i][j] + min(triangle[i-1][j],triangle[i-1][j-1])
        
        return min(triangle[n-1])

单词拆分(7.20)

在这里插入图片描述
某博主的代码

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:       
        n=len(s)
        dp=[False]*(n+1)
        dp[0]=True
        for i in range(n):
            for j in range(i+1,n+1):
                if(dp[i] and (s[i:j] in wordDict)):
                    dp[j]=True
        return dp[-1]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值