Leetcode 刷题笔记之:动态规划一二三

背景:

120. 三角形最小路径和

如同树问题一样,这道题可以使用DFS来解决。

对于树问题,因为子树与子树之间是没有重复元素的,所以可以使用DFS来做

对于这道题,会有重复的情况,所以适合使用动态规划来解决。

# 自顶向下
# 子问题:dp[i,j]为从[0,0]到[i,j]点的最短路径
# 递推式:dp[i,j] = min(dp[i-1][k] for k in range(j-1, j+2) + triangle[i][j])
# 初始值: dp[0,0] = triangle[0][0]
# 时间复杂度: O(n^2) n为三角形行数
# 空间复杂度:O(n^2)
class Solution:
    def minimumTotal(self, triangle: List[List[int]]) -> int:
        def getLastMin(dp, i, j):
            left = j - 1 if j > 0 else 0
            right = j if j < len(triangle[i]) - 1 else j - 1
            return min([dp[i - 1][k] + triangle[i][j] for k in range(left, right + 1)])

        if len(triangle) == 1:
            return triangle[0][0]
        
        # dp[i,j]为从[0,0]到[i,j]点的最短路径
        dp = [[] for _ in range(len(triangle))]
        for i in range(len(triangle)):
            for j in range(len(triangle[i])):
                dp[i].append(0)
        # for x in dp:
        #     print(x)
        
        # 递推式:
        # dp[i,j] = min(dp[i-1][k] for k in range(j-1, j+2) + triangle[i][j])
        # 初始值: dp[0,0] = triangle[0][0] 
        for i in range(len(triangle)):
            for j in range(len(triangle[i])):
                if i == 0 and j == 0:
                    dp[i][j] = triangle[0][0]
                else:
                    dp[i][j] = getLastMin(dp, i ,j)
        # for x in dp:
        #     print(x)
        return min(dp[-1])
# 自顶向下的动态规划 + 空间优化
# 我们发现dp[i]的值只与上一层的dp[i],dp[i-1]有关,所以我们可以只保留上一层的值
# 子问题:dp[0][i]为从顶点到前一层第i点的最短路径
#        dp[1][i]为从顶点到当前层第i点的最短路径

# 递推式:dp[1][i] = min( dp[0][i-1], dp[0][i] ) + traingle[cur_layer][i]
# 初始值: dp[0] = triangle[0], dp[1] = []
# 时间复杂度: O(n^2) n为三角形行数
# 空间复杂度:O(n)
class Solution:
    def minimumTotal(self, triangle: List[List[int]]) -> int:
        if not triangle:
            return 0
        # 初始化
        n = len(triangle)
        dp = [[] for _ in range(2)]
        dp[0] = triangle[0]

        for l in range(1, n): # l为当前层数,一直到最后一层(n-1)
            for i in range(l + 1): # 当前层的元素数量为当前层数+1
                if i == 0: # 左边界
                    dp[1].append(dp[0][0] + triangle[l][i])
                elif i == len(dp[0]): # 右边界
                    dp[1].append(dp[0][i-1] + triangle[l][i])
                else: 
                    dp[1].append(min(dp[0][i], dp[0][i-1]) + triangle[l][i])
            dp[0] = dp[1]
            dp[1] = []
            
        return min(dp[0])

5. 最长回文子串

子问题: d p [ i ] [ j ] = 从 索 引 i 到 索 引 j 的 字 符 串 是 否 为 回 文 子 串 dp[i][j] = 从索引i到索引j的字符串是否为回文子串 dp[i][j]=ij

递推式:

d p [ i , j ] = T r u e dp[i,j]=True dp[i,j]=True $ if s[i]==s[j] 且 j-1 = i$

d p [ i , j ] = = T r u e dp[i,j] == True dp[i,j]==True i f s [ i ] = s [ j ] if s[i]=s[j] ifs[i]=s[j] and d p [ i + 1 ] [ j − 1 ] = = T r u e dp[i+1][j-1]==True dp[i+1][j1]==True : $i = [0,n-3] $ , j = [ i + 2 , n − 1 ] j = [i+2, n-1] j=[i+2,n1]

这是基础的二维数组动态规划,可惜的是,python并不能通过。

时间复杂度 : O ( n 2 ) O(n^2) O(n2)

空间复杂度: O ( n 2 ) O(n^2) O(n2)

当然,空间复杂度可以优化到 O ( n ) O(n) O(n),画图便知,这里就不赘述

class Solution:
    def longestPalindrome(self, s: str) -> str:
        if len(s) <= 1:
            return s
          
        # 子问题: dp[i,j]为索引i到索引j的字符串是否为回文子串
        n = len(s)
        dp = [[False] * n for _ in range(n)]
         
        max_index = (0, 0)
        i = n - 1
        while i >= 0:
            j = 0
            while j <= n - 1:
                # print(f'{i,j} is doing')
                if i > j:  # dp[i,j]为False if i > j  这一步不用写,只是为了展示思路
                    dp[i][j] = False  
                elif i == j:  # dp[i,j]为True if i = j 
                    dp[i][j] = True
                elif j - i == 1 and s[i] == s[j]:  # 如果向量两个字符相等, 则为True
                    dp[i][j] = True
                elif s[i] == s[j] and dp[i+1][j-1]: # 如果相隔两个以上,且中间为True,则查看两边元素是否相等
                    dp[i][j] = True
                # else:
                #     print(f"No idea {i,j}")
                
                # 更新最长的回文子串
                if dp[i][j] and j - i > max_index[1] - max_index[0]:
                    max_index = (i, j)
                    # print(f"update {s[i:j+1]}")
                    
                j += 1
             
            i -= 1
            # print('next')
            
        # for x in dp:
        #     print(x)
        return s[max_index[0]: max_index[1] + 1]         

注:

空间复杂度优化为 O ( 1 ) O(1) O(1)中心扩散法:

对每个起始的字符或者字符对向两边扩散,最后比较每次扩散后的长度

class Solution:
    def longestPalindrome(self, s: str) -> str:
        def expandFromCenter(left, right):
            while left >= 0 and right <= len(s) - 1 and s[left] == s[right]:
                left -= 1
                right += 1
            return left + 1, right - 1
            
        start, end = 0, 0
        for i in range(len(s)):
            left1, right1 = expandFromCenter(i, i)
            left2, right2 = expandFromCenter(i, i + 1)
            if right1 - left1 > end - start:
                start, end = left1, right1
            if right2 - left2 > end - start:
                start, end = left2, right2
        return s[start: end + 1]

什么时候用动态规划?

  1. 求最值
  2. 求是否可行
  3. 求可行的个数
  4. 满足 不能排序或者交换

四要素

  1. 子问题/状态
  2. 递归方程
  3. 初始化
  4. 最后答案

常见四种类型题目

四要素都在注释中

1. 矩阵类型(10%)

64. 最小路径和

class Solution:
    def minPathSum(self, grid: List[List[int]]) -> int:
        if not grid:
            return 0

        # 子问题
        # dp[i,j]为从左上角到grid[i,j]的最短路径
        m, n = len(grid), len(grid[0])
        if m * n == 1:
            return grid[0][0]
        dp = [[0] * n for _ in range(m)]
        
        # 递推式
        # if m > i > 0 and n > j > 0:
        # dp[i,j] = min(dp[i-1, j], dp[i, j-1]) + grid[i,j]
        # 初始化
        # dp[0][0] = grid[0][0]
        # dp[0][j] = dp[0][j-1] + grid[0][j]
        # dp[i][0] = dp[i-1][0] + grid[i][0]

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

        return dp[m-1][n-1]
# 2D dp 优化 1D dp
class Solution:
    def minPathSum(self, grid: List[List[int]]) -> int:
        if not grid:
            return 0
        m, n = len(grid), len(grid[0])
        if m * n == 1:
            return grid[0][0]

        # 子问题
        # dp[i]为从左上角到当前层的第i个元素的最短路径
        dp = [101 for _ in range(n)] # 比最大值100大就行

        # 初始化
        # dp[0] = grid[0][0]
        # 递推式
        # if 0 < i < n: dp[i] = min(dp[i - 1], dp[i]) + grid[cur_layer][i]
        
        dp[0] = 0
        for l in range(m):
            dp[0] = grid[l][0] + dp[0]
            for i in range(1, n):
                dp[i] = min(dp[i - 1], dp[i]) + grid[l][i]   
        return dp[n-1]

62. 不同路径

# 2D DP
class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        # 子问题
        # dp[i,j] 从左上角到[i,j]的路径个数
        # 初始化
        # dp[0,j]=1, dp[i,0]=1
        # 递推式
        # dp[i,j] = dp[i-1][j] + dp[i][j-1]

        dp = [[1] * n for _ in range(m)]
        
        for i in range(m):
            for j in range(n):
                if i == 0 or j == 0:
                    dp[i][j] = 1
                else:
                    dp[i][j] = dp[i-1][j] + dp[i][j-1]
        return dp[m-1][n-1]
class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        # 子问题
        # dp[i] 从左上角到当前层第i个点的路径个数
        # 初始化
        # dp[0]=1
        # 递推式
        # dp[i] = dp[i-1] + dp[i]

        dp = [1 for _ in range(n)]
        
        for l in range(1, m):
            for i in range(1, n):
                dp[i] = dp[i-1] + dp[i]
        return dp[n-1]

63. 不同路径 II

# 1D dp
class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
        # 子问题
        # dp[i] 从左上角到当前层第i个点的路径个数
        # 初始化
        # dp[0]=1    dp[i]=0如果有障碍物
        # 递推式
        # dp[i] = dp[i-1] + dp[i], 有障碍物就不加
        if not obstacleGrid:
            return 0
        m, n = len(obstacleGrid), len(obstacleGrid[0])
        if m * n == 1:
            return 1 - obstacleGrid[0][0]
        dp = [0 for _ in range(n)]
        block = False
        # 第一行,遇到障碍物后,后面全是0
        for i in range(len(obstacleGrid[0])):
            if obstacleGrid[0][i] == 0:
                dp[i] = 1
            else:
                break

        for l in range(1, m):
            if obstacleGrid[l][0] == 1 or dp[0] == 0:
                dp[0] = 0
            for i in range(1, n):
                if obstacleGrid[l][i] == 1:
                    dp[i] = 0
                else:
                    dp[i] = dp[i-1] + dp[i]
        return dp[n-1]

矩阵类型题目总结

2D矩阵,当前值来自于上下左右方向(一般是两个方向),大概率有来自上方。

所以2D的dp几乎都可以优化成1D的dp,从左至右,与dp[i]和dp[i-1]

2. 零钱和背包(10%)

python### 322. 零钱兑换

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        if amount == 0: # 金额为0
            return 0
        if not coins: # 没有硬币
            return -1
        if len(coins) == 1: # 只有一个硬币的情况
            if amount % coins[0] == 0:
                return  amount // coins[0]
            else:
                return -1
        # 子问题
        # dp[i]为组成金额i所需最小的银币数量
        dp = [-1 for _ in range(amount + 1)]
        dp[0] = 0
        coins = [c for c in coins if c <= amount] # 剔除大于amount的硬币
        
        for c in coins:
            dp[c] = 1
        
        for i in range(1, amount+1):
            if dp[i] != 1:
                temp = [dp[i-c] for c in coins if dp[i-c] != -1 and i-c >= 0]
                if temp:
                    dp[i] = min(temp) + 1
                    # print(f"{i} 块 {dp[i]} 个硬币")
        return dp[amount]

backpack

class Solution:
    """
    @param m: An integer m denotes the size of a backpack
    @param A: Given n items with size A[i]
    @return: The maximum size
    """
    def backPack(self, m, A):
        
        n = len(A)
        
        dp = [0] * (m + 1)
        dp_new = [0] * (m + 1)
        
        for i in range(n):
            for j in range(1, m + 1):
                use_Ai = 0 if j - A[i] < 0 else dp[j - A[i]] + A[i]
                dp_new[j] = max(dp[j], use_Ai)
            
            dp, dp_new = dp_new, dp
        
        return dp[-1]

3. 序列类型(40%)

70. 爬楼梯

# 空间复杂度 O(n)
class Solution:
    def climbStairs(self, n: int) -> int:
        if n <= 0:
            return 1
        # 子问题
        # dp[i]:爬到i阶的方法数
        dp = [1 for _ in range(n+1)]

        # 递推式
        # dp[i] = dp[i-1] + dp[i-2]
        # 初始化dp[1] = 1 dp[2] = 2 
        for i in range(2, n+1):
            if i == 2:
                dp[2] = 2
            else:
                dp[i] = dp[i-1] + dp[i-2]
        return dp[n]
class Solution:
    def climbStairs(self, n: int) -> int:
        if n <= 1:
            return 1
        
        # 子问题
        # dp:爬到当前阶的方法数
        # dp_1: 爬到当前的前一阶的方法数
        # dp_2: 爬到当前的前二阶的方法数
        # 初始化
        dp_1, dp_2 = 1, 1

        # 递推式
        # dp = dp_1 + dp_2
        # 从第二阶开始
        for i in range(2, n+1):
            temp = dp_1
            dp_1 = dp_1 + dp_2
            dp_2 = temp
            # print(f"{i} 阶")
        return dp_1

55. 跳跃游戏

虽说动态规划可以解决一切贪心算法可以解决的问题…但有些时候会造成计算复杂度太大,…

# 动态规划 (超时)
class Solution:
    def canJump(self, nums) -> bool:
        if len(nums) < 2:
            return True

        # 子问题
        # dp[i] 表示能否调到位置i
        dp = [False for _ in range(len(nums))]
        # print(dp)
        # 递推式
        next_index = [i for i in range(nums[0])]
        # print(f"初始化 {next_index}")

        while next_index:
            next_index_new = []
            for i in next_index:
                dp[i] = True
                # print(f"{i} 可达 ")
                temp = [i + k for k in range(nums[i]+1) if i+k < len(nums)]
                for index in temp:
                    if not dp[index]:
                        dp[index] = True
                        next_index_new.append(index)
                        # print(f"从 {i} 到 {index} 可达 ")
            next_index = next_index_new
        return dp[-1]
# 贪心算法
class Solution:
    def canJump(self, nums: List[int]) -> bool:
        n = len(nums)
        rightmost = 0
        for i in range(n):
            if i <= rightmost:
                rightmost = max(rightmost, i + nums[i])
                if rightmost >= n - 1:
                    return True
        return False

45. 跳跃游戏 II

class Solution:
    def jump(self, nums: List[int]) -> int:
        if len(nums)<=1:
            return 0
        if nums[0] >= len(nums)-1:
            return 1
        step = 1
        left, right = 0, nums[0]
        rightmost = 0
        while right >= left:
            for i in range(left, right + 1):
                if i + nums[i] > rightmost:
                    rightmost = i + nums[i]
                    # print(f"从 {i} 开始,最远 {rightmost}")
                    if rightmost >= len(nums)-1:
                        return step+1
            left = right+1
            right = rightmost
            step+=1
               

从左到右,每次选取能走到的最远的点作为下一步的起点

class Solution:
    def jump(self, nums: List[int]) -> int:
        n = len(nums)
        step = 0
        cur_max = 0
        step_max = 0
        for i in range(n-1):
            if cur_max >= i:
                cur_max = max(nums[i]+i, cur_max)
                if i == step_max: # 一直到i达到上一step的max,说明该下一步了
                    step += 1
                    step_max = cur_max
        return step

132. 分割回文串 II

分为两个部分。表层的问题其实便是到索引i的分割次数,这是一个dp, 里层还有一个判断是否为回文串,时间复杂度为 n 2 2 {n^2}^2 n22,我们注意到里层的判断回文串有重复计算的部分,可以提前存储使得读取结果复杂度降维O(1),所以复杂度可以降到 O ( n 2 ) O(n^2) O(n2)

class Solution:
    def minCut(self, s: str) -> int:
        n = len(s)
        dp_pal = [[False] * n for _ in range(n)]
        # 首先构建回文子串的查询表,确保之后的查询时间复杂度为O(1)
        for right in range(n):
            for left in range(right+1):
                if s[left] == s[right] and (right - left < 2 or dp_pal[left+1][right-1]):
                    dp_pal[left][right] = True
            
        # 子问题: dp[i] 到索引i的字符串的最小分割次数
        # 递推式: dp[i] = min(dp[j]+1 for j in range(0,i) if s[j+1:i] 是回文)
        # 初始化: dp[i] 最多切i次, 最少不切
        dp = [0 for _ in range(n)]
        for i in range(n):
            dp[i] = i

        for i in range(1, n):
            if dp_pal[0][i]:
                dp[i] = 0
            else:
                dp[i] = min(dp[j] + 1 for j in range(i) if dp_pal[j+1][i])
                
        return dp[n-1]
        

300. 最长上升子序列

动态规划可解,但是时间复杂度为 n 2 n^2 n2

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        # 子问题:
        # dp[i]: 从0到i, 以i结尾的的最长上升子序列长度
        # 递推式:
        # dp[i] = max(dp[i-k] for k in range(i) if nums[i] >= nums[i-k])
        # 初始化:
        # dp = [1]*n
        if not nums:
            return 0
        n = len(nums)
        dp = [1] * n
        for i in range(n):
            temp = [dp[k]+1 for k in range(i) if nums[i] > nums[k]]
            if temp:
                dp[i] = max(temp)
            # print(f"{i} : {dp[i]}")
        return max(dp)

解法二:贪心算法 + 动态规划

跟动态规划一样同样是针对每个元素进行更新,不同的是之前的动态规划,针对dp[k]我们都要计算一次k之前的元素,实际上是重复计算了很多。如果要优化,我们可以想到二分查找将这个O(N)的过程优化成O(logN)。

但是二分查找需要数组有序,我们需要构建一个有序的数组存出结果,于是我们可以构建一个tails, tails[k]表明长度为k+1的最长子序列的末尾元素。贪心的规则是我们要尽可能使得这个末尾元素小,经过证明可以得到最优解。

 class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        def updateTails(tails, num):
            # 用二分查找更新tails
            if not tails or num > tails[-1]: # 添加tails元素
                tails.append(num)
            else: # 更新tails元素
                left, right = 0, len(tails)-1
                while left <= right:
                    mid = (left + right) // 2
                    if tails[mid] == num:
                        left = mid
                        break
                    elif tails[mid] < num:
                        left = mid + 1
                    else:
                        right = mid - 1
                tails[left] = num
                
        if not nums:
            return 0
        # 贪心 + 二分查找
        n = len(nums)
        # tails[k]表示目前为止长度为k+1的上升子序列的末值
        tails = []
        for num in nums:
            updateTails(tails, num)
        return len(tails)

139. 单词拆分

class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        # 状态 dp[i]为从0到i是否可拆分
        # 递推式 dp[i] = [dp[k] for k in range(i) if s[k:i+1] in wordDict and dp[k]]
         
        n = len(s)
        dp = [False for _ in range(n)]
        # 初始化
        for i in range(n):
            if s[:i+1] in wordDict:
                dp[i] = True
                # print(s[:i+1])
        # print(f"初始值 {dp}")
        # 递推式 dp[i] = [dp[k] for k in range(i) if s[k:i+1] in wordDict and dp[k]]
        for i in range(n):
            if not dp[i]:
                # print(f"现在是{i}")
                for k in range(i):
                    if dp[k] and s[k+1:i+1] in wordDict:
                        dp[i] = True
                        # print(s[k+1: i+1])
                        # print(f"更新{dp}")
        return dp[-1]

4.Two Sequences DP(40%)

一般都是两个字符串的问题,这里我们要记住,初始化的时候记得要多padding一行一列,考虑好padding的值,后面会方便很多

1143. 最长公共子序列

注意这里我们给左上角加了padding,这样就方便一些

class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        # 状态: dp[i][j]为截止到text1的j和text2的i的公共子序列长度
 
        # dp[i][j] = dp[i-1][j-1] + 1 if text1[j-1]==text2[i-1] else max(dp[i-1][j], dp[i][j-1])
        # 初始化: dp[i][j] = 0
        if not text1 or  not text2:
            return 0
        m, n = len(text1)+1, len(text2)+1
        dp = [[0] * m for _ in range(n)]
        
        for i in range(1, n):
            for j in range(1, m):
                if text1[j-1] == text2[i-1]:
                   dp[i][j] = dp[i-1][j-1] + 1
                else:
                    dp[i][j] = max(dp[i-1][j], dp[i][j-1])
                   
        # for x in dp:
        #     print(x)
        return dp[-1][-1]

注意到上面的2D DP是可以优化为O(N)空间复杂度的

class Solution:
    def longestCommonSubsequence(self, text1: str, text2: str) -> int:
        # 状态: dp[j]当前i行 截止到text1的j和text2的i的公共子序列长度
        # 递推式: dp_new[j] = dp[j-1] + 1 if text1[j]==text2[i] else dp_new[j] = max(dp[j], dp_new[j-1])
        # 初始化: dp[0][j] = 0 dp[i][0] = 0
        if not text1 or  not text2:
            return 0
        m, n = len(text1)+1, len(text2)+1
        dp = [0] * m
        
        for i in range(1, n):
            dp_new = [0] * m
            for j in range(1, m):
                if text1[j-1] == text2[i-1]:
                    dp_new[j] = dp[j-1] + 1
                else:
                    dp_new[j] = max(dp[j], dp_new[j-1])
            dp, dp_new = dp_new, dp
                   
        # for x in dp:
        #     print(x)
        return dp[-1]

72. 编辑距离

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        # 状态 dp[i][j]是word1[:i]到word2[:j]的编辑距离
        # 递推式:
        # if word1[i] == word2[j]: dp[i][j] = min(dp[i-1][j-1], dp[i-1][j]+1, dp[i][j-1]+1)
        # if word1[i] != word2[j]: dp[i][j] = min(dp[i-1][j-1]+1, dp[i-1][j]+1, dp[i][j-1]+1)
        # 初始值:
        # all set 0
        # dp[i][0] = dp[i-1][0] + 1
        # dp[0][j] = dp[0][j-1] + 1
        if not word1 and not word2:
            return 0
        if not word1 or not word2:
            return 1

        m, n = len(word1) + 1, len(word2) + 1
        # 初始值
        dp = [[0] * m for _ in range(n)]
        for i in range(n):
            dp[i][0] = i
        for j in range(m):
            dp[0][j] = j
                    
        for i in range(1, n):
            for j in range(1, m):
                # print(f"比较{word2[i]}和{word1[j]}")
                # print(f"比较 {i,j}")
                if word2[i-1] == word1[j-1]:
                     dp[i][j] = min(dp[i-1][j-1], dp[i-1][j]+1, dp[i][j-1]+1)
                else:
                     dp[i][j] = min(dp[i-1][j-1]+1, dp[i-1][j]+1, dp[i][j-1]+1)
                # for x in dp:
                #     print(x)
        # for x in dp:
        #     print(x)
        return dp[-1][-1]

优化空间:

class Solution:
    def minDistance(self, word1: str, word2: str) -> int:
        if not word1 and not word2:
            return 0
        if not word1 or not word2:
            return 1

        m, n = len(word1) + 1, len(word2) + 1
        # 初始值
        dp = [0] * m
        for j in range(m):
            dp[j] = j
        dp_new = [0] * m      
        for i in range(1, n):
            dp_new[0] = i
            for j in range(1, m):
                # print(f"比较{word2[i]}和{word1[j]}")
                # print(f"比较 {i,j}")
                if word2[i-1] == word1[j-1]:
                     dp_new[j] = min(dp[j-1], dp[j]+1, dp_new[j-1]+1)
                else:
                     dp_new[j] = min(dp[j-1]+1, dp[j]+1, dp_new[j-1]+1)
            dp, dp_new = dp_new, dp
                # for x in dp:
                #     print(x)
        
        return dp[-1]

总结:

总体来说,时候用动态规划的题目都有一个明显的特点,就是最优解明显依赖于其子问题的解,这在应对求最值求是否可行求可行的个数, 满足 不能排序或者交换的问题特别有用,只需要按照状态 -》递归 -》初始值的顺序做就可以

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值