Leetcode专题必刷经典题目-动态规划

动态规划核心就是列出状态转移方程来求全局最优,故下面每道题目都列出相应的状态转移方程。

一.经典

0-1 背包问题:

题目:给你一个可装载重量为 W 的背包和 N 个物品,每个物品有重量和价值两个属性。其中第 i 个物品的重量为 wt[i],价值为 val[i],现在让你用这个背包装物品,最多能装的价值是多少?
举个简单的例子,输入如下:
N = 3, W = 4
wt = [2, 1, 3]
val = [4, 2, 3]

状态转移方程:
dp[i][w]的定义如下:对于前i个物品,当前背包的容量为w,这种情况下可以装的最大价值是dp[i][w]
如果你没有把这第i个物品装入背包,那么很显然,最大价值dp[i][w]=dp[i-1][w]。你不装嘛,那就继承之前的结果。
如果你把这第i个物品装入了背包,那么dp[i][w]=dp[i-1][w-wt[i-1]] + val[i-1]

二.必刷

509.斐波那契数

509.斐波那契数
dp[i] = dp[i - 1] + dp[i - 2]

322.零钱兑换

322.零钱兑换
在这里插入图片描述
F(i) 为组成金额 i 所需最少的硬币数量
F(i)=min F(i-coin) +1

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        dp=[float('inf')]*(amount+1)
        dp[0]=0

        for coin in coins:
            for x in range(coin,amount+1):
                dp[x]=min(dp[x],dp[x-coin]+1)

        return dp[amount] if dp[amount]!=float('inf') else -1

494.目标和

494.目标和
在这里插入图片描述
设置一个哈希表(字典),键是一个元祖,元祖第一位是目前的和,第二位是目前的位数。值是这个元祖推导到最后能有多少个解。

因为符号要么全正,要么全负,所以元祖第二位的取值范围是 -sum(nums) ~ sum(nums)
状态转移公式:
dp[(i,j)] = dp.get((i - 1, j - nums[i]), 0) + dp.get((i - 1, j + nums[i]), 0)

class Solution:
    def findTargetSumWays(self, nums: List[int], S: int) -> int:
        n=len(nums)
        d={(0,0):1}
        for i in range(1,n+1):
            for j in range(-sum(nums),sum(nums)+1):
                d[(i,j)]=d.get((i-1,j-nums[i-1]),0)+d.get((i-1,j+nums[i-1]),0)
        return d.get((n,S),0)

300.最长上升子序列

300.最长上升子序列
在这里插入图片描述
在这里插入图片描述

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        if not nums:
            return 0
        dp = []
        for i in range(len(nums)):
            dp.append(1)
            for j in range(i):
                if nums[i] > nums[j]:
                    dp[i] = max(dp[i], dp[j] + 1)
        return max(dp)

53.最大子序和

53.最大子序和

在这里插入图片描述

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        n=len(nums)
        dp=[-float('inf')]*n
        dp[0]=nums[0]
        for i in range(1,n):
            dp[i]=max(dp[i-1]+nums[i],nums[i])
        return max(dp)

416.分割等和子集

416.分割等和子集
在这里插入图片描述

在这里插入图片描述

class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        n=len(nums)
        target=sum(nums)
        if(target%2!=0):
            return False
        target//=2
        dp=[[False]*(target+1) for _ in range(n)]
        dp[0][0]=True
        for i in range(1,target+1):
            if(nums[0]==i):
                dp[0][i]=True
                break
        for i in range(1,n):
            for j in range(target+1):
                if(j>=nums[i]):
                    dp[i][j]=dp[i-1][j] or (dp[i-1][j-nums[i]])
                else:
                    dp[i][j]=dp[i-1][j]
        return dp[-1][-1]

518.零钱兑换II

518.零钱兑换II
在这里插入图片描述

class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        dp = [0] * (amount + 1)
        dp[0] = 1
        
        for coin in coins:
            for x in range(coin, amount + 1):
                dp[x] += dp[x - coin]
        return dp[amount]

72.编辑距离

编辑距离
在这里插入图片描述
在这里插入图片描述

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        n1 = len(word1)
        n2 = len(word2)
        dp = [[0] * (n2 + 1) for _ in range(n1 + 1)]
        # 第一行
        for j in range(1, n2 + 1):
            dp[0][j] = dp[0][j-1] + 1
        # 第一列
        for i in range(1, n1 + 1):
            dp[i][0] = dp[i-1][0] + 1
        for i in range(1, n1 + 1):
            for j in range(1, n2 + 1):
                if word1[i-1] == word2[j-1]:
                    dp[i][j] = dp[i-1][j-1]
                else:
                    dp[i][j] = min(dp[i][j-1], dp[i-1][j], dp[i-1][j-1] ) + 1
        #print(dp)      
        return dp[-1][-1]

887. 鸡蛋掉落

鸡蛋掉落
在这里插入图片描述
转换思想,让dp[i][j]表示,有i个鸡蛋,扔j次,最大能确定的楼层数。
dp[i][j] = dp[i-1][j-1] + dp[i][j-1] + 1
因为扔了一次,所以后面还剩j-1次机会。碎了的话只有i-1个鸡蛋(用i-1个鸡蛋,j-1次机会实验这层楼之下的,看能确定几层),没碎还有i个鸡蛋(用i个鸡蛋,j-1次实验机会,实验这层楼之上的,看有几层), 然后总得i个鸡蛋,j次机会能确定的楼数,就是下面的楼数加上面的再加本次的1.
时间复杂度:O(KN)
空间复杂度:O(KN) O(K)(因为状态只和左边和左上有关,因此可以向左打,简化为1维数组,然后遍历的时候倒序就行了(使得dp[i]是原来的dp[i][j-1], dp[i-1]是dp[i-1][j-1], 根据这俩算新的dp[i],新的dp[i]就相当于dp[i][j])

class Solution:
    def superEggDrop(self, K: int, N: int) -> int:
        dp = [0] * (K+1)
        cnt = 0
        while dp[-1]<N:
            for i in reversed(range(1,K+1)):
                dp[i] = dp[i]+dp[i-1]+1
            # print(dp)
            cnt+=1

        return cnt

312.戳气球

戳气球
在这里插入图片描述

class Solution:
    def maxCoins(self, nums: List[int]) -> int:
        # reframe problem as before
        nums = [1] + nums + [1]
        n = len(nums)

        # dp will store the results of our calls
        dp = [[0] * n for _ in range(n)]

        # iterate over dp and incrementally build up to dp[0][n-1]
        for left in range(n-2, -1, -1):
            for right in range(left+2, n):
                # same formula to get the best score from (left, right) as before
                dp[left][right] = max(nums[left] * nums[i] * nums[right] + dp[left][i] + dp[i][right] for i in range(left+1, right))

        return dp[0][n-1]

最长公共子序列

最长公共子序列
在这里插入图片描述

class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        m, n = len(text1), len(text2)
        # 构建 DP table 和 base case
        dp = [[0] * (n + 1) for _ in range(m + 1)]
        # 进行状态转移
        for i in range(1, m + 1):
            for j in range(1, n + 1):
                if text1[i - 1] == text2[j - 1]:
                    # 找到一个 lcs 中的字符
                    dp[i][j] = 1 + dp[i-1][j-1]
                else:
                    dp[i][j] = max(dp[i-1][j], dp[i][j-1])

        return dp[-1][-1]

最长回文子序列

最长回文子序列
在这里插入图片描述

class Solution:
    def longestPalindromeSubseq(self, s: str) -> int:
        #dp[j][i]从j到i最长回文子序列的长度
        n = len(s)
        dp = [[0] * n for _ in range(n)]
        for i in range(n):
            dp[i][i] = 1
            for j in range(i - 1, -1, -1):
                if s[j] == s[i]:
                    dp[j][i] = 2 + dp[j + 1][i - 1]
                else:
                    dp[j][i] = max(dp[j + 1][i], dp[j][i - 1])
        return dp[0][n - 1]
  • 3
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值