数据结构与算法:动态规划dp:理论基础和相关力扣题(509.斐波那契数列、70.爬楼梯、62. 不同路径、63.不同路径Ⅱ、343.整数拆分)

1.0.理论基础

动态规划主要解决的问题种类有:

  • 背包问题
  • 打家劫舍
  • 股票问题
  • 子序列问题

解决步骤:

  • dp数组及其下标的意义
  • 递推公式
  • dp数组初始化
  • 遍历顺序
  • 打印dp数组

2.0.相关力扣题

509.斐波那契数列

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

效率:0ms,击败100.00%

状态压缩

再优化一下,因为每个斐波那契数只和它相邻的两个数有关,所以我们其实不需要存储三十多个长度,只需要保留2个数的信息即可。也就是状态压缩。

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

70.爬楼梯

509.斐波那契数列很像

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

效率:0ms,击败100.00%

状态压缩

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

756.使用最小代价爬楼梯

需要注意的是,这里的dp[i]代表着爬到台阶为i时所需的最小代价。

class Solution:
    def minCostClimbingStairs(self, cost: List[int]) -> int:
        n = len(cost)
        if n == 2:
            return min(cost[0],cost[1])
        dp = [0]*1005
        dp[0] = 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]

效率:2ms,击败83.69%

62. 不同路径

d[-1][-1]可以直接返回数组的右下角。而且因为只能向下或者向右,所以相当于第一行和第一列都是1,所以初始化全都为1

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

63. 不同路径 II(值得做一下)

这道题的考点在于初始化的部分。不能像62.不同路径一样全部初始化为0或为1,因为可能遇到以下情况:

  • 坐标原点就有石头挡住。
  • 第一行或第一列初始化时,遇到石头,那么石头及往后走的路线都应为0。而前面的路线应该为1

注意点:any() 是一个内置函数,它用于检查可迭代对象(如列表、元组、字符串、字典等)是否至少包含一个 True 值的元素。如果找到至少一个 True 值,any() 函数返回 True;如果所有元素都是 False 或者可迭代对象为空,则返回 False

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
        m = len(obstacleGrid)
        n = len(obstacleGrid[0])
        if obstacleGrid[0][0] == 1:
            # 初始台地有障碍物
            return 0
        if m == 1 or n == 1:
            if any(1 in row for row in obstacleGrid):
                # 只存在向下或者向右的一条路,还被石头挡住了
                return 0
            else:
                return 1
        dp = [[0]* n for _ in range(m)]
        for i in range(1,m):
            if obstacleGrid[i][0]==0:
                dp[i][0] = 1
            else:
                # 说明这列遇到石头了,后面的路都应为0
                break
        for j in range(1,n):
            if obstacleGrid[0][j]==0:
                dp[0][j] = 1
            else:
                break
        for i in range(1, m):
            for j in range(1, n):
                if obstacleGrid[i][j] == 0:
                    dp[i][j] = dp[i-1][j] + dp[i][j-1]
        return dp[-1][-1]

效率:0ms,击败100.00%

代码量简化

注意:这里所有的边界条件提前判断都可以合并到第一行和第一列的处理当中,只需要把range(1,m)和range(0,n)改为range(0,m)和range(0,n)。这样代码量会少很多,但是效率就低了很多。如下:

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
        m = len(obstacleGrid)
        n = len(obstacleGrid[0])
        if obstacleGrid[0][0] == 1:
            # 初始台地有障碍物
            return 0
        dp = [[0]* n for _ in range(m)]
        for i in range(0,m):
            if obstacleGrid[i][0]==0:
                dp[i][0] = 1
            else:
                # 说明这列遇到石头了,后面的路都应为0
                break
        for j in range(0,n):
            if obstacleGrid[0][j]==0:
                dp[0][j] = 1
            else:
                break
        for i in range(1, m):
            for j in range(1, n):
                if obstacleGrid[i][j] == 0:
                    dp[i][j] = dp[i-1][j] + dp[i][j-1]
        return dp[-1][-1]

效率:3ms,击败25.15%

343.整数拆分(值得做一下)

虽然这道题代码很短,但实际上值得做一下的。

首先我们需要明确一下dp[i]的含义:总和为i的两个或两个以上的数的乘积最大值。 也就是说dp[i]可能是两个数的乘积,可能是三个或者以上的数的乘积。

前者,两个数的乘积,假设其中一个为k,也就是dp[i]=k*(i-k)

如果是三个或者以上的数的乘积,我们先假设是三个数的乘积,其中一个为k,一个为m,也就是dp[i]=k*m*(i-k-m)。需要注意的是,根据dp[i]的定义,这又可以表示为dp[i]=(k+m)*dp[i-(k+m)]k+mn表示,这个n肯定比i小,也就是说dp[i]=n*dp[i-n]。同理,三个以上也可以用拆成两个乘数的dp形式表示。

所以dp[i]本质上要找到是dp[i]=k*(i-k)dp[i]=k*dp[i-k]哪个大,这里的k1遍历到i/2即可。为什么呢?为了去重,而且最大值根据均值不等式,不会在后半部分因子出现。

class Solution:
    def integerBreak(self, n: int) -> int:
        dp = [0]*60
        dp[2] = 1
        for i in range(3,n+1):
            for j in range(1,int(n/2)+1):
                dp[i] = max(j*(i-j), j*dp[i-j], dp[i]) 
        return dp[n]

你可能感兴趣的:(数据结构与算法,LeetCode,动态规划,leetcode,算法,dp,力扣,数据结构)