动态规划题 leetcode

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] 

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. 买卖股票系列问题

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值