动态规划题 leetcode

2023-05-16

1. 62. 不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

示例 1:


输入:m = 3, n = 7
输出:28

示例2:

输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右

解法1: 动态规划, 还是要发现其中的规律

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        # 隐含了 动态规划思想: dp(i, j) = dp(i-1, j) + dp(i, j-1)
        # O(mxn) O(mxn)
        dp = []
        for i in range(m):
            temp = []
            for j in range(n):
                if i == 0: # 第一行 都为1 
                    temp.append(1)
                elif j == 0: # 第一列 都为1
                    temp.append(1)
                else: # 其余先为0, 
                    temp.append(0)
            dp.append(temp)
        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]

解法2: 简化赋值部分

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        dp = [[0]*n for _ in range(m)]
        for i in range(m):
            for j in range(n):
                if i == 0 and j == 0:
                    dp[i][j] = 1  # 1x1 为1条路
                elif i == 0:
                    dp[i][j] = 1
                elif j == 0:
                    dp[i][j] = 1
                else:
                    dp[i][j] = dp[i-1][j] + dp[i][j-1]
        return dp[m-1][n-1]

63. 不同路径 II

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 1 和 0 来表示。

示例 1:

输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:
3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右

解法1: 动态规划

class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
        m = len(obstacleGrid)
        n = len(obstacleGrid[0])
        # dp[i][j]:表示到达到此处位置的路径个数; 若obstacleGrid[i][j]等于1,则到达到此处的路径0条。
        # [[0]],只有一个点时,无障碍物路径1条,有障碍物0条。
        dp = [[0]*n for _ in range(m)]  # 0 表示障碍物,到达不了,路径为0;
        for i in range(m):
            for j in range(n):
                if i == 0 and j == 0 and obstacleGrid[i][j] != 1:
                    dp[i][j] = 1  # 无障碍物1条路径。
                elif i == 0 and obstacleGrid[i][j] != 1: # 有障碍物为0;
                    dp[i][j] = dp[i][j-1] # 依赖于 前面到此的路径有无障碍物,不能为1;
                elif j == 0 and obstacleGrid[i][j] != 1:
                    dp[i][j] = dp[i-1][j]
                else:
                    if obstacleGrid[i][j] != 1: # 若当前位置有障碍物,则到达此位置的道路0条;
                        dp[i][j] = dp[i-1][j] + dp[i][j-1]
        return dp[m-1][n-1]

980. 不同路径 III

在二维网格 grid 上,有 4 种类型的方格:

  • 1 表示起始方格。且只有一个起始方格。
  • 2 表示结束方格,且只有一个结束方格。
  • 0 表示我们可以走过的空方格。
  • -1 表示我们无法跨越的障碍。

返回在四个方向(上、下、左、右)上行走时,从起始方格到结束方格的不同路径的数目

每一个无障碍方格都要通过一次,但是一条路径中不能重复通过同一个方格

示例 1:

输入:[[1,0,0,0],[0,0,0,0],[0,0,2,-1]]
输出:2
解释:我们有以下两条路径:
1. (0,0),(0,1),(0,2),(0,3),(1,3),(1,2),(1,1),(1,0),(2,0),(2,1),(2,2)
2. (0,0),(1,0),(2,0),(2,1),(1,1),(0,1),(0,2),(0,3),(1,3),(1,2),(2,2)

解法1:DFS + 回溯

class Solution:
    def uniquePathsIII(self, grid: List[List[int]]) -> int:
        # 有障碍不能通过, 无障碍方格都要通过一次,但 同一个方格不能通过两次;
        # 从  起始方格到结束方格 不通路径的数目;
        # 起始和结束方格 可以在 网格中的任意位置, 若无满足条件的路径 返回0;
        def dfs(grid, start_x, start_y, step):
            if start_x < 0 or start_x >= len(grid) or start_y < 0 or start_y >= len(grid[0]) or grid[start_x][start_y] == -1:
                return 0  # 路径为0
            if grid[start_x][start_y]==2:  # 找到结束标志
                if step == 0:  # 正好走完所有空格,路径有效
                    return 1
                else:
                    return 0  # 路径无效
            # 上下左右四个网络搜索 四种情况
            grid[start_x][start_y] = -1 # 走过的添加障碍物
            count = dfs(grid, start_x+1, start_y, step-1) + dfs(grid, start_x-1, start_y, step-1) + dfs(grid, start_x, start_y+1, step-1) + dfs(grid, start_x, start_y-1, step-1)
            grid[start_x][start_y] = 0  # 回溯
            return count 

        m = len(grid)
        n = len(grid[0])
        # 深度优先dfs吗
        step = 1  # 切记 初始值为1, 因为n个空格, 从start->finish,n+1步。
        start_x = 0  # start的位置坐标
        start_y = 0
        for i in range(m):
            for j in range(n):
                if grid[i][j] == 1: # 起始方格 开始搜索吗   但是需要将 所有空方格都走一遍且不重复;
                    start_x = i
                    start_y = j
                elif grid[i][j] == 0:
                    step += 1
        print(step)
        return dfs(grid, start_x, start_y, step)

2. LCP 19. 秋叶收藏集

小扣出去秋游,途中收集了一些红叶和黄叶,他利用这些叶子初步整理了一份秋叶收藏集 leaves, 字符串 leaves 仅包含小写字符 r 和 y, 其中字符 r 表示一片红叶,字符 y 表示一片黄叶。
出于美观整齐的考虑,小扣想要将收藏集中树叶的排列调整成「红、黄、红」三部分。每部分树叶数量可以不相等,但均需大于等于 1。每次调整操作,小扣可以将一片红叶替换成黄叶或者将一片黄叶替换成红叶。请问小扣最少需要多少次调整操作才能将秋叶收藏集调整完毕。

示例 1:

输入:leaves = "rrryyyrryyyrr"

输出:2

解释:调整两次,将中间的两片红叶替换成黄叶,得到 "rrryyyyyyyyrr"

示例2:

输入:leaves = "ryr"

输出:0

解释:已符合要求,不需要额外操作
  • 3 <= leaves.length <= 10^5
  • leaves 中只包含字符 'r' 和字符 'y'

解法1: 动态规划,(多看看这些题,动态规划题还是很难想到解法)

class Solution:
    def minimumOperations(self, leaves: str) -> int:
        #  最少需要多少调整, 最优问题
        # 动态规划, 即使不懂,也要会写答案
        n = len(leaves)
        dp = [[0]*n for i in range(3)]
        # dp[0][i]:  变为纯红
        # dp[1][i]: 红黄需要修改几次  
        # dp[2][i]: 红黄红需要修改几次
        for k, v in enumerate(leaves):
            if k == 0:
                dp[0][k] = int(v != 'r')  # 不为红,替换为红
            else:
                dp[0][k] = dp[0][k-1] + (v != 'r')
            if k == 0:
                dp[1][k] = dp[0][k]
            else:
                dp[1][k] = min(dp[0][k-1] + (v!='y'), dp[1][k-1]+(v!='y')) # 两种情况取最小值

            if k < 2:
                dp[2][k] = dp[1][k] # 不是 k-1
            else:
                dp[2][k] = min(dp[1][k-1] + (v!='r'), dp[2][k-1]+(v!='r')) # 两种情况
        # print(dp[0])
        # print(dp[1])
        # print(dp[2])
        return dp[2][-1]

3.343. 整数拆分

给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。

示例 1:

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。

示例 2:

输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。

说明: 你可以假设 不小于 2 且不大于 58。

解法1:  动态规划dp; 动态规划推导公式:dp[i]=max(dp[i], dp[j]*dp[i-j])

if n <= 3:  # 
    return 1*(n-1)
dp = [0]*(n+1)  # dp[i]表示i拆分成至少两个整数的和,这些整数的乘积最大值
dp[2:n+1] = list(range(2, n+1))  # 初始化为 原始值;
# 递归公式i拆分成j和i-j, dp[i]=dp[j]*dp[i-j]
for i in range(4, n + 1):
    for j in range(i):  # 求dp[i]遍历完i之前所有值
        dp[i] = max(dp[i], dp[j] * dp[i - j])  # 动态规划 总有这个条件;
return dp[n]

解法2:  动态规划dp; 动态规划还是不好理解, 需要对题目进行记忆,

对于的正整数 n,当n\geq 2时,可以拆分成至少两个正整数的和。令 k 是拆分出的第一个正整数,则剩下的部分是 n-kn-k 可以不继续拆分,或者继续拆分成至少两个正整数的和。

创建数组dp,其中 dp[i] 表示将正整数i拆分成至少两个正整数的和之后,这些正整数的最大乘积。特别地,0不是正整数,1是最小的正整数,0 和 1 都不能拆分,因此dp[0]=dp[1]=0

class Solution:
    def integerBreak(self, n: int) -> int:
        # 1. 动态规划方法一
        dp = [0]*(n+1)   # 0,1不能拆分,dp[0]=0, dp[1]=0
        for i in range(2, n+1):
            for j in range(i):   # 求出dp[i] # 最大值; 
                dp[i] = max(dp[i], j * (i-j), j*dp[i-j])   
        return dp[n]

4. 70. 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

示例 1:

输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1.  1 阶 + 1 阶
2.  2 阶

示例 2:

输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1.  1 阶 + 1 阶 + 1 阶
2.  1 阶 + 2 阶
3.  2 阶 + 1 阶

解法1: 动态规划

class Solution:
    def climbStairs(self, n: int) -> int:
        # 爬楼梯 还有一道 中等题 总结
        # n=1, dp[i]=1
        # n=2, dp[1]=1, dp[2]=1+1=2
        # dp[i] = dp[i-1] + dp[i-2] # 有多少中方法 应该是+, 

        if n == 0:
            return 0
        if n == 1:
            return 1
        if n == 2:
            return 1+1
        dp = [0]*(n+1)
        dp[1] = 1
        dp[2] = 2   # 背下来 自己揣摩
        for i in range(3, n+1): # 到达楼顶 楼顶是n
            dp[i] = dp[i-1] + dp[i-2]
        return dp[n]

 

5. 746. 使用最小花费爬楼梯

数组的每个索引作为一个阶梯,第 i个阶梯对应着一个非负数的体力花费值 cost[i](索引从0开始)。

每当你爬上一个阶梯你都要花费对应的体力花费值,然后你可以选择继续爬一个阶梯或者爬两个阶梯。

您需要找到达到楼层顶部的最低花费。在开始时,你可以选择从索引为 0 或 1 的元素作为初始阶梯。

示例 1:

输入: cost = [10, 15, 20]
输出: 15
解释: 最低花费是从cost[1]开始,然后走两步即可到阶梯顶,一共花费15。

 示例 2:

输入: cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
输出: 6
解释: 最低花费方式是从cost[0]开始,逐个经过那些1,跳过cost[3],一共花费6。

注意:

  1. cost 的长度将会在 [2, 1000]
  2. 每一个 cost[i] 将会是一个Integer类型,范围为 [0, 999]

解法1:    官方 动态规划dp

假设数组cost长度为n,则n个阶梯分别对应下标0到n-1,楼层顶部对应下标n,问题等价于计算到下标n的最小花费。

创建长度为n+1的数组dp,其中dp[i]表示达到下标i的最小花费。

由于可以选择下标0或1作为初始阶梯,因此有dp[0]=dp[1]=0.

2 \leq i \leq n时,可以从下标i-1使用cost[i-1]的花费达到下标i,或者从i-2使用cost[i-2]的花费到达下标i。为了使总的花费最小,dp[i]应取上述两项的最小值,

因此状态转移方程如下:

dp[i]=min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2])

依次计算dp中的每一项的值,最终得到的dp[n]即为到达楼顶顶部的最小花费。

class Solution:
    def minCostClimbingStairs(self, cost: List[int]) -> int:
        # dp[i] = min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2])
        # 离开 某一层 阶梯 才有花费, 而不是到达某层阶梯,就有此层阶梯的花费;
        dp = [0]*(len(cost)+1)
        dp[0] = dp[1] = 0 # 作为初始阶梯
        cost_len = len(cost)
        i = 2
        sum_cost = 0
        while i <= cost_len:  # 到达n的花费 
            dp[i] = min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2])
            i += 1
        # print(dp)
        return dp[cost_len]

6. 面试题 08.01. 三步问题

三步问题。有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶或3阶。实现一种方法,计算小孩有多少种上楼梯的方式。结果可能很大,你需要对结果模1000000007。

示例1:

 输入:n = 3 
 输出:4
 说明: 有四种走法

示例2:

 输入:n = 5
 输出:13

提示:

  1. n范围在[1, 1000000]之间

解法1:  动态规划

class Solution:
    def waysToStep(self, n: int) -> int:
        # 经典动态规划, 和爬楼梯相似
        if n == 0:
            return 0
        if n == 1:
            return 1
        if n == 2:
            return 1 + 1
        if n == 3:
            return 4
        dp = [0] * (n+1) # 爬 + 到n
        dp[1] = 1
        dp[2] = 2
        dp[3] = 4  
        for i in range(4, n+1):  # O(n) 超时吗 
            # 注意递推公式
            dp[i] = (dp[i-1] + dp[i-2] + dp[i-3])% 1000000007  # 每次先 取模
        return dp[n] 

7 5. 最长回文子串

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。

示例 2:

输入: "cbbd"
输出: "bb"

解法1: 官方 动态规划

class Solution:
    def longestPalindrome(self, s: str) -> str:
        # 动态规划,
        # 方程 j-i>=2, P(i, j)=P(i+1,j-1)^(Si==Sj)
        # j==i(一个字母) -> 必是, j=i+1(两个字母), Si==Sj+1
        s_len = len(s)
        dp = [[False]*s_len for _ in range(s_len)]
        ans=""
        # 先遍历长度, j表示长度
        for lg in range(s_len):
            # 遍历起始位置
            for i in range(s_len): # dp[i][j] 表示 从i~j是回文串
                j = i + lg  # 看i~j的区间 是否是回文串
                if j >= s_len:
                    break
                if lg==0:
                    dp[i][j]=True
                elif lg==1:
                    dp[i][j] = (s[i]==s[j])
                # elif lg >= 2:
                else:
                    dp[i][j] = (dp[i+1][j-1] and s[i]==s[j])
                if dp[i][j] and lg+1 > len(ans):
                    ans = s[i:j+1]
        return ans

8. 64. 最小路径和

 给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例 1:

输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。

示例2: 

输入:grid = [[1,2,3],[4,5,6]]
输出:12

解法1: 动态规划

class Solution:
    def minPathSum(self, grid: List[List[int]]) -> int:
        # 动态规划
        m = len(grid)
        n = len(grid[0])
        dp = [[0]*n for _ in range(m)]
        for i in range(0, m):
            for j in range(0, n):
                if i == 0:
                    if j == 0:  # (0,0)位置 总和不变
                        dp[i][j] = grid[i][j]
                    else:  # 第一行 数值总和 为 左加到右上
                        dp[i][j] = dp[i][j-1]+grid[i][j]
                else: # i > 0
                    if j == 0: # 第1列,数值之和,从上加到下;
                        dp[i][j] = grid[i][j] + dp[i-1][j]
                    else:  # 非 第一行 第一列,递归公式如下:
                        # dp[i][j] = min(dp[i-1][j]+grid[i][j], dp[i][j-1]+grid[i][j])
                        dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
        return dp[m-1][n-1]

9. 198. 打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。

示例 2:

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
     偷窃到的最高金额 = 2 + 9 + 1 = 12 。

提示:

  • 0 <= nums.length <= 100
  • 0 <= nums[i] <= 400

解法1: 动态规划  注意递推公式 仔细揣摩

class Solution:
    def rob(self, nums: List[int]) -> int:
        n = len(nums)
        if n == 0:
            return 0
        if n == 1:
            return nums[0]
        if n == 2:
            return max(nums[0], nums[1])
        dp  = [0]*(n)
        dp[0] = nums[0]
        dp[1] = max(nums[0], nums[1])
        for i in range(2, n):
            dp[i] = max(dp[i-2]+nums[i], dp[i-1])  # 递推公式
        return dp[n-1]

213. 打家劫舍 II

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。

给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,能够偷窃到的最高金额。

解法1:

class Solution:
    def rob(self, nums: List[int]) -> int:
        # 第一个和最后一个 是相邻的, 和I不同之处
        if len(nums) <= 3:
            return max(nums)
        # 动态规划 
        # 循环转变为 单排, 1. 不偷第一个房子  2. 不偷最后一个房子  3. 两种情况的 最大值
        # 这样将 第一个和最后一个相邻, 不成两种 首尾不相邻的情况;
        def single(nums):  # 不循环的情况
            n = len(nums)
            dp = [0]*(n)
            dp[0] = nums[0]
            dp[1] = max(nums[0], nums[1])
            for i in range(2, n):  # dp[i] 代表前i个房子在满足条件下的能偷窃到的最高金额。
                dp[i] = max(dp[i-1], dp[i-2]+nums[i])
            return dp[-1]

        return max(single(nums[:-1]), single(nums[1:]))

10. 面试题 17.16. 按摩师

一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,因此她不能接受相邻的预约。给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟数。

注意:本题相对原题稍作改动

示例 1:

输入: [1,2,3,1]
输出: 4
解释: 选择 1 号预约和 3 号预约,总时长 = 1 + 3 = 4。

示例 2:

输入: [2,7,9,3,1]
输出: 12
解释: 选择 1 号预约、 3 号预约和 5 号预约,总时长 = 2 + 9 + 1 = 12。

示例 3:

输入: [2,1,4,5,3,1,1,3]
输出: 12
解释: 选择 1 号预约、 3 号预约、 5 号预约和 8 号预约,总时长 = 2 + 4 + 3 + 3 = 12。

解法1: 动态规划

class Solution:
    def massage(self, nums: List[int]) -> int:
        # 和 小 偷 偷东西是一样的
        nums_len = len(nums)
        if nums_len == 0:
            return 0
        if nums_len == 1:
            return nums[0]
        dp = [0] * nums_len
        dp[0] = nums[0]
        dp[1] = max(nums[0], nums[1])
        for i in range(2, nums_len):
            dp[i] = max(dp[i-2]+nums[i], dp[i-1])
        
        return dp[nums_len-1]

11. 392. 判断子序列

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。

进阶:

如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。在这种情况下,你会怎样改变代码?

致谢:

特别感谢 @pbrother 添加此问题并且创建所有测试用例。

示例 1:

输入:s = "abc", t = "ahbgdc"
输出:true

示例 2:

输入:s = "axc", t = "ahbgdc"
输出:false

提示:

  • 0 <= s.length <= 100
  • 0 <= t.length <= 10^4
  • 两个字符串都只由小写字符组成。

解法1:  双指针

class Solution:
    def isSubsequence(self, s: str, t: str) -> bool:
        # 双指针试试
        t_len = len(t)
        s_len = len(s)
        t_left = 0
        t_right = t_len - 1
        s_left = 0
        s_right = s_len - 1
        while s_left <= s_right and t_left < t_right:
            
            while t[t_left] != s[s_left] and t_left < t_right:
                t_left += 1
            if t[t_left] == s[s_left]:
                t_left += 1
                s_left += 1
            while t[t_right] != s[s_right] and t_left < t_right:
                t_right -= 1
            if t[t_right] == s[s_right]:
                t_right -= 1
                s_right -= 1
        if s_left > s_right or (t_left==t_right and s[s_left]==t[t_left]):  # 判断条件很微妙
            return True
        return False

解法2: 双指针

class Solution:
    def isSubsequence(self, s: str, t: str) -> bool:
        # 双指针试试
        s_len = len(s)
        t_len = len(t)
        i = 0
        j = 0
        while i < s_len and j < t_len:
            if s[i] == t[j]:
                i = i + 1
            j = j + 1
        if i == s_len:
            return True
        return False

解法3:  动态规划

12. 1025. 除数博弈

爱丽丝和鲍勃一起玩游戏,他们轮流行动。爱丽丝先手开局。

最初,黑板上有一个数字 N 。在每个玩家的回合,玩家需要执行以下操作:

选出任一 x,满足 0 < x < N 且 N % x == 0 。
用 N - x 替换黑板上的数字 N 。
如果玩家无法执行这些操作,就会输掉游戏。

只有在爱丽丝在游戏中取得胜利时才返回 True,否则返回 False。假设两个玩家都以最佳状态参与游戏。

示例 1:

输入:2
输出:true
解释:爱丽丝选择 1,鲍勃无法进行操作。

示例 2:

输入:3
输出:false
解释:爱丽丝选择 1,鲍勃也选择 1,然后爱丽丝无法进行操作。

解法1:  数学规律 详解,仔细揣摩

N为奇数时,先手必败; N为偶数时,先手必胜;

class Solution:
    def divisorGame(self, N: int) -> bool:
        return N % 2 == 0

解法2: 动态规划 详解

class Solution:
    def divisorGame(self, N: int) -> bool:
        # 动态规划, 初始化 数组长度(N or N+1), 初始边界, 递推公式,三样重点;
        # 最后返回结果时, 返回N or N-1, 仔细判断
        if N == 1:
            return False
        dp = [0] * (N+1)
        dp[1] = False
        dp[2] = True
        for i in range(3, N+1):
            for j in range(1, (i+1)//2):  #  为什么取一半呢, N之前的都要计算出来;
                # 此处是对i执行操作,先手胜,次手败;
                if i % j == 0 and not dp[i-j]:  # i - j  动态规划 
                    dp[i] = True
                    break
        return dp[N]

13. 303. 区域和检索 - 数组不可变

给定一个整数数组  nums,求出数组从索引 i 到 ji ≤ j)范围内元素的总和,包含 i两点。

实现 NumArray 类:

NumArray(int[] nums) 使用数组 nums 初始化对象
int sumRange(int i, int j) 返回数组 nums 从索引 i 到 j(i ≤ j)范围内元素的总和,包含 i、j 两点(也就是 sum(nums[i], nums[i + 1], ... , nums[j]))

解法1: 动态规划; 题目看似简单, 加上优化 都不简单;

class NumArray:

    def __init__(self, nums: List[int]):
        self.nums = [0]  # 多一个长度, 求区间范围的和, 求法 优化;
        for i in range(0, len(nums)):  # 也算动态规划
            self.nums.append(self.nums[-1] + nums[i])     
        print(nums)
        print(self.nums)


    def sumRange(self, i: int, j: int) -> int:
        return self.nums[j+1] - self.nums[i]  # 注意索引细节
        
# Your NumArray object will be instantiated and called as such:
# obj = NumArray(nums)
# param_1 = obj.sumRange(i,j)

14. 276. 栅栏涂色

有 k 种颜色的涂料和一个包含 n 个栅栏柱的栅栏,每个栅栏柱可以用其中一种颜色进行上色。

你需要给所有栅栏柱上色,并且保证其中相邻的栅栏柱 最多连续两个 颜色相同。然后,返回所有有效涂色的方案数。

注意:
n 和 k 均为非负的整数。

示例:

输入: n = 3,k = 2
输出: 6
解析: 用 c1 表示颜色 1,c2 表示颜色 2,所有可能的涂色方案有:

            柱 1    柱 2   柱 3     
 -----      -----  -----  -----       
   1         c1     c1     c2 
   2         c1     c2     c1 
   3         c1     c2     c2 
   4         c2     c1     c1  
   5         c2     c1     c2
   6         c2     c2     c1

解法1:  动态规划 思路:

当n=1时, dp[1]=k;当n=2时, dp[2]=dp[1]*k种;

当n>3时, 分两种情况:

(1) 第n个和第n-1个柱子涂不同颜色,这种情况不用管n-1之前的柱子什么颜色,第n个可选颜色k-1种, 此时方案总数dp[n-1]*(k-1);

(2) 第n个和第n-1个柱子涂相同颜色,则第n-2个柱子不能和第n-1个柱子相同颜色,此时第n-1个柱子可选颜色k-1种, 第n个柱子可选颜色1种,此时方案总数dp[n-2]*(k-1)*1;

递推式: n > 3, dp[n]=dp[n-1]*(k-1) + dp[n-2]*(k-1)*1

class Solution:
    def numWays(self, n: int, k: int) -> int:
        if n == 0 or k == 0:
            return 0 
        if n == 1:
            return k
        dp = [0] * (n+1)
        dp[1] = k
        dp[2] = k * k  # 细节 初始条件
        for i in range(3, n+1):  # 递推公式 起始索引
            dp[i] = dp[i-1]*(k-1) + dp[i-2]*(k-1)*1
        return dp[n]

解法2 :  没看明白这种思路,先记下 慢慢消化;

class Solution:
    def numWays(self, n: int, k: int) -> int:
        # 看不太明白,先记下
        if n == 0 or k == 0:
            return 0 
        if n == 1:
            return k
        same = k    # ??? 
        diff = k * (k-1)
        for i in range(2, n):  # 此处不包括n;
            temp = diff
            diff = (same + diff) * (k-1)
            same = temp
        return same + diff
        

15. 买卖股票系列问题

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

动态规划题 leetcode 的相关文章

  • SQL Server数据库同步Oracle数据

    第一步 配置Oracle服务 电脑上需要安装Oracle客户端 xff08 因为需要Oracle的驱动 xff09 Oracle win64 11gR2 client 配置Oracle服务 第二步 配置SQL Server链接服务器 xff
  • SR04声波传感器原理详解及其Arduino编程——人人都能玩硬件

    通过前两篇文章 xff0c 我们已经熟悉了Arduino的基本GPIO编程和PC与Arduino的串口通信 接下来我们将开始一边学习各种传感器操作 xff0c 一边深入学习Arduino编程 再开始编程之前 xff0c 首先我们需要熟悉SR
  • 从UV位置图获得3D人脸

    1 这部分主要写从UV位置图获得3D人脸 xff0c 并不包括如何产生UV位置图 我已经获取一张图片的UV位置图 xff0c 如下图 xff1a xff08 a 裁剪后的人脸区域 xff09 xff08 b UV位置图 xff08 仅用来展
  • Ubuntu matplotlib 中文乱码问题

    1 下载字体 Microsoft YaHei 放到 matplotlib库文件夹字体下 lib python3 6 site packages matplotlib mpl data fonts ttf 自己的机器上路径 自己更改 字体下载
  • faster R-CNN中anchors 的生成过程代码

    import numpy as np def whctrs anchor 34 34 34 将 anchor 四个坐标的形式 转化成 宽 xff0c 高 xff0c 中心点横坐标 xff0c 中心点纵坐标 的形式 Return width
  • 5461. 仅含 1 的子串数

    给你一个二进制字符串 s xff08 仅由 39 0 39 和 39 1 39 组成的字符串 xff09 返回所有字符都为 1 的子字符串的数目 由于答案可能很大 xff0c 请你将它对 10 9 43 7 取模后返回 示例 1 xff1a
  • print end=“lr“

    print 34 epoch 34 format i end 61 34 r 34
  • python 调试 PDB

    https aistudio baidu com aistudio projectdetail 69987
  • pcl求取三维模型每个点的曲率以及法向量

    转载 xff1a http blog csdn net lming 08 article details 18360329 做个笔记 xff0c 写得很不错
  • 3D人脸重建: BFM结合表情模型

    MATLAB代码 xff1a addpath genpath pwd gt model 载入原始的BFM模型 load 39 raw 01 MorphableModel mat 39 载入3dffa中 BFM信息 load 39 3ddfa
  • 构建副对角线全为1的矩阵,

    突然用到 副对角线 全为1的矩阵 xff0c 不知道怎么调用numpy xff0c 自己写了一个 xff1a import numpy as np 副对角线 全为 1 def get subdiag n value ar 61 for i
  • day1: 357. 计算各个位数不同的数字个数

    给定一个非负整数 n xff0c 计算各位数字都不同的数字 x 的个数 xff0c 其中 0 x lt 10n 示例 输入 2 输出 91 解释 答案应为除去 11 22 33 44 55 66 77 88 99 外 xff0c 在 0 1
  • day1: 二叉树

    1 二叉树的层序遍历 给你一个二叉树 xff0c 请你返回其按 层序遍历 得到的节点值 xff08 即逐层地 xff0c 从左到右访问所有节点 xff09 示例 xff1a 二叉树 xff1a 3 9 20 null null 15 7 3
  • 求正整数的平方根

    34 34 34 求正整数的平方根 二分查找 34 34 34 def sqrt val t if val lt 0 or t lt 0 return 0 left 61 0 right 61 val mid 61 left 43 righ
  • 对BN的理解

    BN原理与使用过程详解 内部协变量偏移 Internal Covariate Shift 和批归一化 Batch Normalization 对于BN层的理解 关于BN防止过拟合的分析 BN Batch Normalization 原理与使
  • 感受野的计算方法

    卷积神经网络基础题 如何计算多层卷积 池化网络每一层的感受野 Receptive Field
  • day2:215. 数组中的第K个最大元素 快速排序 python代码

    34 34 34 快速排序 思想 xff1a 基本思想是 xff1a 通过一趟排序将要排序的数据分割成独立的两部分 xff0c 其中一部分的所有数据都比另外一部分的所有数据都要小 xff0c 然后再按此方法对这两部分数据分别进行快速排序 x
  • 梯度消失和梯度爆炸原因及其解决方案

    梯度消失和梯度爆炸原因及其解决方案
  • day3: 剑指 Offer 48. 最长不含重复字符的子字符串

    剑指 Offer 48 最长不含重复字符的子字符串 请从字符串中找出一个最长的不包含重复字符的子字符串 xff0c 计算该最长子字符串的长度 示例 1 输入 34 abcabcbb 34 输出 3 解释 因为无重复字符的最长子串是 34 a
  • idea中java版本设置

    1 打开file gt Project structure gt project Settings gt Project gt Project SDK中设置 2 设置IDEA本身的jdk版本 打开file gt settings gt ja

随机推荐

  • 剑指 Offer 59 - II. 队列的最大值

    剑指 Offer 59 II 队列的最大值 请定义一个队列并实现函数 max value 得到队列里的最大值 xff0c 要求函数max value push back 和 pop front 的均摊时间复杂度都是O 1 若队列为空 xff
  • 剑指 Offer 46. 把数字翻译成字符串

    剑指 Offer 46 把数字翻译成字符串 给定一个数字 xff0c 我们按照如下规则把它翻译为字符串 xff1a 0 翻译成 a xff0c 1 翻译成 b xff0c xff0c 11 翻译成 l xff0c xff0c 25 翻译成
  • python 异或的应用

    符号 描述 运算规则 amp 与 两个位都为1时 xff0c 结果才为1 xff08 统计奇数 xff09 全1为1 或 两个位都为0时 xff0c 结果才为0 xff08 统计偶数 xff09 全0为0 异或 两个位相同为0 xff0c
  • day4: 剑指 Offer 64. 求1+2+…+n

    剑指 Offer 64 求1 43 2 43 43 n 求 1 43 2 43 43 n xff0c 要求不能使用乘除法 for while if else switch case等关键字及条件判断语句 xff08 A B C xff09
  • day5: 链表

    1 剑指 Offer 22 链表中倒数第k个节点 输入一个链表 xff0c 输出该链表中倒数第k个节点 为了符合大多数人的习惯 xff0c 本题从1开始计数 xff0c 即链表的尾节点是倒数第1个节点 例如 xff0c 一个链表有6个节点
  • LCP 18. 早餐组合

    小扣在秋日市集选择了一家早餐摊位 xff0c 一维整型数组 staple 中记录了每种主食的价格 xff0c 一维整型数组 drinks 中记录了每种饮料的价格 小扣的计划选择一份主食和一款饮料 xff0c 且花费不超过 x 元 请返回小扣
  • 第 208 场周赛

    1 5523 文件夹操作日志搜集器 class Solution def minOperations self logs List str gt int 栈 xff0c 先进 后出 zhan 61 39 main 39 for log in
  • day 5: 字符串

    1 剑指 Offer 38 字符串的排列 输入一个字符串 xff0c 打印出该字符串中字符的所有排列 你可以以任意顺序返回这个字符串数组 xff0c 但里面不能有重复元素 示例 xff1a 输入 xff1a s 61 34 abc 34 输
  • day6: 数组

    1 18 四数之和 给定一个包含 n 个整数的数组 nums 和一个目标值 target xff0c 判断 nums 中是否存在四个元素 a xff0c b xff0c c 和 d xff0c 使得 a 43 b 43 c 43 d 的值与
  • day7: 剑指 Offer 44. 数字序列中某一位的数字

    1 剑指 Offer 44 数字序列中某一位的数字 数字以0123456789101112131415 的格式序列化到一个字符序列中 在这个序列中 xff0c 第5位 xff08 从下标0开始计数 xff09 是5 xff0c 第13位是1
  • casbin 使用说明记录

    本文简单记录casbin 安装步骤 使用 Casbin 作为 ThinkPHP 的权限控制中间件 PHP Casbin 是一个强大的 高效的开源访问控制框架 xff0c 它支持基于各种访问控制模型的权限管理 Think Casbin 是一个
  • 844. 比较含退格的字符串

    给定 S 和 T 两个字符串 xff0c 当它们分别被输入到空白的文本编辑器后 xff0c 判断二者是否相等 xff0c 并返回结果 代表退格字符 注意 xff1a 如果对空文本输入退格字符 xff0c 文本继续为空 示例 1 xff1a
  • 笔试题记录

    1 逆波兰表达式 是称为 后缀表达式 xff0c 把运算量写在前面 xff0c 把算符写在后面 写出a b c d 43 e f g h 43 i j k 的逆波兰表达式 拆开写各个部分的 xff1a 按优先级 xff08 1 xff09
  • 牛客网 赛码在线编程中数据读取问题

    一 数据读取的方式 xff08 python3 xff09 1 input 读取输入数据 while True try inputs 61 input except break 2 网站的数据输入是是一个含有多行数据的input文件 in文
  • 贪心算法 leetcode编程题

    1 452 用最少数量的箭引爆气球 在二维空间中有许多球形的气球 对于每个气球 xff0c 提供的输入是水平方向上 xff0c 气球直径的开始和结束坐标 由于它是水平的 xff0c 所以纵坐标并不重要 xff0c 因此只要知道开始和结束的横
  • 排序题 LeetCode题

    1 1370 上升下降字符串 给你一个字符串 s xff0c 请你根据下面的算法重新构造字符串 xff1a 从 s 中选出 最小 的字符 xff0c 将它 接在 结果字符串的后面 从 s 剩余字符中选出 最小 的字符 xff0c 且该字符比
  • BP算法公式推导

    令表示第层的第个神经元到第层的第个神经元的连接权值 xff0c 表示第层第个神经元的输入 xff0c 表示第层第个神经元的输出 xff0c 表示层第个神经元的偏置 xff0c C表示代价函数 则有 xff1a 其中 表示激活函数 训练多层网
  • 卷积神经网络中的Separable Convolution_转载

    卷积神经网络在图像处理中的地位已然毋庸置疑 卷积运算具备强大的特征提取能力 相比全连接又消耗更少的参数 xff0c 应用在图像这样的二维结构数据中有着先天优势 然而受限于目前移动端设备硬件条件 xff0c 显著降低神经网络的运算量依旧是网络
  • 二分查找方法 leetcode题

    这里用来 记录 使用二分查找方法 的题目 1 704 二分查找 给定一个 n 个元素有序的 xff08 升序 xff09 整型数组 nums 和一个目标值 target xff0c 写一个函数搜索 nums 中的 target xff0c
  • 动态规划题 leetcode

    1 62 不同路径 一个机器人位于一个 m x n 网格的左上角 xff08 起始点在下图中标记为 Start xff09 机器人每次只能向下或者向右移动一步 机器人试图达到网格的右下角 xff08 在下图中标记为 Finish xff09