easy
1
70. 爬楼梯
题目链接
难度:简单 1 类型: 动态规划
题目:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
class Solution:
def climbStairs(self, n: int) -> int:
res=[0,1,2]
for i in range(3,n+1,1):
res.append(res[i-1]+res[i-2])
return res[n]
392. 判断子序列
题目链接
难度:简单1 类型: 贪心
题目:给定字符串 s 和 t ,判断 s 是否为 t 的子序列。s和t中仅包含英文小写字母。字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。
解题思路:当我们从前往后匹配,可以发现每次贪心地匹配靠前的字符是最优决策。
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
if s=="":
return True
if t=="":
return False
s_len=len(s)
j=0
t_len=len(t)
for i in range(t_len):
if s[j]==t[i]:
j+=1
if j==s_len:
return True
return False
746. 使用最小花费爬楼梯
题目链接
难度:简单1 类型: 动态规划
题目:数组的每个索引作为一个阶梯,第 i个阶梯对应着一个非负数的体力花费值 cost[i] (索引从0开始)。每当你爬上一个阶梯你都要花费对应的体力花费值,然后你可以选择继续爬一个阶梯或者爬两个阶梯。您需要找到达到楼层顶部的最低花费。在开始时,你可以选择从索引为 0 或 1 的元素作为初始阶梯。
解题思路:当前阶梯的花费等于上个阶梯和上上个阶梯的最小值。
class Solution:
def minCostClimbingStairs(self, cost: List[int]) -> int:
cost_len=len(cost)
if cost_len==0:
return 0
if cost_len==1:
return cost[0]
df=[0]*cost_len
df[0]=cost[0]
df[1]=cost[1]
for i in range(2,cost_len):
df[i]=min(df[i-1],df[i-2])+cost[i]
return min(df[cost_len-1],df[cost_len-2])
剑指 Offer 42. 连续子数组的最大和
题目链接
难度:简单1 类型: 动态规划
题目:输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。要求时间复杂度为O(n)。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
nums_len=len(nums)
if nums_len==0:
return 0
df=[0]*nums_len
df[0]=nums[0]
result=nums[0]
for i in range(1,nums_len):
df[i]=max(df[i-1]+nums[i],nums[i])
result = max(result,df[i])
return result
面试题 16.17. 连续数列
难度:简单1 类型: 动态规划
题目:给定一个整数数组,找出总和最大的连续数列,并返回总和。
解题思路:和上一题解题思路完全一样。
面试题 08.01. 三步问题
难度:简单1 类型: 动态规划
题目:三步问题。有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶或3阶。实现一种方法,计算小孩有多少种上楼梯的方式。结果可能很大,你需要对结果模1000000007。
解题思路:这个问题的关键是模运算的分配率。
class Solution:
def waysToStep(self, n: int) -> int:
if n == 1 :
return 1
if n == 2:
return 2
tmp1, tmp2, tmp3 = 1,2, 4
for i in range( 3, n ):
tmp1, tmp2 , tmp3 = tmp2, tmp3, tmp1 + tmp2 + tmp3
tmp1 = tmp1 % 1000000007
tmp2 = tmp2 % 1000000007
tmp3 = tmp3 % 1000000007
return tmp3
时间复杂度为O(n),空间复杂度为O(1)。
面试题 17.16. 按摩师
难度:简单1 类型: 动态规划
题目:一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接。在每次预约服务之间要有休息时间,因此她不能接受相邻的预约。给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回总的分钟数。
class Solution:
def massage(self, nums: List[int]) -> int:
nums_size=len(nums)
if nums_size==0:
return 0
if nums_size==1:
return nums[0]
if nums_size==2:
return max(nums[0],nums[1])
dp=[0]*nums_size
dp[0]=nums[0]
result=nums[0]
dp[1]=max(nums[0],nums[1])
for i in range(2,nums_size):
dp[i]=max(dp[i-2]+nums[i],dp[i-1])
result=max(result,dp[i])
return result
3
53. 最大子序和
题目链接
难度:简单3 类型: 动态规划
题目:给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
解题思路:以第i个数结尾的「连续子数组的最大和」,是前一个以第i-1个数结尾的「连续子数组的最大和」或者是 第i-1个数结尾的「连续子数组的最大和」+nums[i]。遍历一遍,顺便把最大值求出来。dp[i]表示nums中以nums[i]结尾的最大子序和,dp[0]=nums[0],dp[i]=max(dp[i-1]+nums[i],nums[i])。
复杂度:时间复杂度o(n),空间复杂度o(1)。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
numsSize = len(nums)
dp=[]
dp.append(nums[0])
result = dp[0]
for i in range(1,numsSize):
dp.append(max(dp[i-1]+nums[i],nums[i]))
result = max(result, dp[i])
return result
198. 打家劫舍
题目链接
难度:简单3 类型: 动态规划
题目:你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
解题思路:
针对一个屋子有偷和不偷两种选择。
偷窃第k间房屋,那么就不能偷窃第k-1间房屋,偷窃总金额为前k−2间房屋的最高总金额与第k间房屋的金额之和。
不偷窃第k间房屋,偷窃总金额为前k−1间房屋的最高总金额。
在两个选项中选择偷窃总金额较大的选项,该选项对应的偷窃总金额即为前k间房屋能偷窃到的最高总金额。
用dp[i]表示前i间房屋能偷窃到的最高总金额,那么就有如下的状态转移方程:
dp[i]=max(dp[i−2]+nums[i],dp[i−1])
class Solution:
def rob(self, nums: List[int]) -> int:
nums_size=len(nums)
if nums_size==0:
return 0
if nums_size==1:
return nums[0]
if nums_size==2:
return max(nums[0],nums[1])
dp=[0]*nums_size
dp[0]=nums[0]
result=nums[0]
dp[1]=max(nums[0],nums[1])
for i in range(2,nums_size):
dp[i]=max(dp[i-2]+nums[i],dp[i-1])
result=max(result,dp[i])
return result
middle
1
62. 不同路径
题目链接
难度:中等1 类型: 动态规划
题目:一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。问总共有多少条不同的路径?
解题思路:
复杂度:时间复杂度o(n2)。
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
df= [[0 for i in range(m)] for i in range(n)]
for i in range(m):
df[0][i]=1
for i in range(n):
df[i][0]=1
for i in range(1,n):
for j in range(1,m):
df[i][j]=df[i-1][j]+df[i][j-1]
return df[n-1][m-1]
64. 最小路径和
题目链接
难度:中等1 类型: 动态规划
题目:给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。说明:每次只能向下或者向右移动一步。
解题思路:
复杂度:时间复杂度o(nm)
class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
height, width = len(grid),len(grid[0])
df = [[0]*width for i in range(height)]
for m in range(height):
for n in range(width):
if m == n == 0:
df[m][n] = grid[0][0]
else:
a = df[m-1][n] if m!=0 else 0
b = df[m][n-1] if n!=0 else 0
df[m][n] = (min(a,b)+grid[m][n]) if (n!=0 and m!=0) else (a+b+grid[m][n])
return df[height-1][width-1]
221. 最大正方形
题目链接
难度:中等1 类型: 动态规划
题目:在一个由 ‘0’ 和 ‘1’ 组成的二维矩阵内,找到只包含 ‘1’ 的最大正方形,并返回其面积。
解题思路:和机器人走路一个思路。
复杂度:时间复杂度o(
n
2
n^2
n2)。
class Solution:
def maximalSquare(self, matrix: List[List[str]]) -> int:
high=len(matrix)
if high==0:
return 0
wide=len(matrix[0])
for i in range(high):
for j in range(wide):
a= 0 if i==0 else matrix[i-1][j] # 左
b= 0 if j==0 else matrix[i][j-1] # 上
c= 0 if (i-1<0 or j-1<0) else matrix[i-1][j-1] # 左上
matrix[i][j] = min(a,b,c)+1 if matrix[i][j]=='1' else 0
return max(map(max,matrix))**2
1277. 统计全为 1 的正方形子矩阵
class Solution:
def countSquares(self, matrix: List[List[int]]) -> int:
high=len(matrix)
if high==0:
return 0
wide=len(matrix[0])
for i in range(high):
for j in range(wide):
a= 0 if i==0 else matrix[i-1][j] # 左
b= 0 if j==0 else matrix[i][j-1] # 上
c= 0 if (i-1<0 or j-1<0) else matrix[i-1][j-1] # 左上
matrix[i][j] = min(a,b,c)+1 if matrix[i][j]==1 else 0
return sum(sum(i) for i in matrix)
剑指 Offer 47. 礼物的最大价值
516. 最长回文子序列
题目链接
难度:中等1 类型: 动态规划
题目:给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。
解题思路:这个是广义的回文。
当s[i]==s[j]时:dp[i][j]=dp[i+1][j-1]+2
当s[i]!=s[j] 时:dp[i][j]=max(dp[i+1][j],dp[i][j-1])
思考的时候是红色箭头的方向,写代码时是红色箭头的反方向。
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
n = len(s)
if n<1:
return 0
dp = [[0]*n for _ in range(n)]
for i in range(n):
dp[i][i] = 1
for i in range(n-2,-1,-1):
for j in range(i+1,n):
if s[i]==s[j]:
dp[i][j]=dp[i+1][j-1]+2
else:
dp[i][j]=max(dp[i][j-1],dp[i+1][j])
return dp[0][-1]
2
63. 不同路径 II
题目链接
难度:中等2 类型: 动态规划
题目:一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
解题思路:
复杂度:时间复杂度o(nm)
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
height, width = len(obstacleGrid),len(obstacleGrid[0])
df = [[0]*width for i in range(height)]
for m in range(height):
for n in range(width):
if not obstacleGrid[m][n]:
if m == n == 0:
df[m][n] = 1
else:
a = df[m-1][n] if m!=0 else 0
b = df[m][n-1] if n!=0 else 0
df[m][n] = a+b
return df[height-1][width-1]
3
152. 乘积最大子数组
题目链接
难度:中等3 类型: 动态规划
题目:给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
解题思路:
复杂度:时间复杂度O(n)
class Solution:
def maxProduct(self, nums: List[int]) -> int:
nums_len=len(nums)
imax = 1
imin = 1
max_re=-sys.maxsize
for i in range(nums_len):
if nums[i] < 0:
imax, imin = imin, imax
imax = max(imax*nums[i], nums[i])
imin = min(imin*nums[i], nums[i])
max_re = max(max_re, imax)
return max_re
279. 完全平方数
题目链接
难度:中等3 类型: 动态规划
题目:给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。即给定一个完全平方数列表和正整数 n,求出完全平方数组合成 n 的组合,要求组合中的解拥有完全平方数的最小个数。注:可以重复使用列表中的完全平方数。
解题思路:用公式来表述这个问题:
numSquares(n)=min(numSquares(n-k) + 1) ∀k∈square numbers
一:递归方法:
class Solution(object):
def numSquares(self, n):
square_nums = [i**2 for i in range(1, int(math.sqrt(n))+1)]
def minNumSquares(k):
""" recursive solution """
# bottom cases: find a square number
if k in square_nums:
return 1
min_num = float('inf')
# Find the minimal value among all possible solutions
for square in square_nums:
if k < square:
break
new_num = minNumSquares(k-square) + 1
min_num = min(min_num, new_num)
return min_num
return minNumSquares(n)
上面的解决方案可以适用于较小的正整数 n。对于中等大小的数字(例如 55),很快会遇到超出时间限制的问题。这是由于过度递归,产生堆栈溢出。
方法二:动态规划:
使用暴力枚举法会超出时间限制的原因很简单,因为我们重复的计算了中间解。解决递归中堆栈溢出的问题的一个思路就是使用动态规划(DP)技术,该技术建立在重用中间解的结果来计算终解的思想之上。
算法:
几乎所有的动态规划解决方案,首先会创建一个一维或多维数组 DP 来保存中间子解的值,以及通常数组最后一个值代表最终解。
我们还需要预计算小于给定数字 n 的完全平方数列表(即 square_nums)。
在主要步骤中,我们从数字 1 循环到 n,计算每个数字 i 的解(即 numSquares(i))。每次迭代中,我们将 numSquares(i) 的结果保存在 dp[i] 中。
在循环结束时,我们返回数组中的最后一个元素作为解决方案的结果。
class Solution:
def numSquares(self, n: int) -> int:
square_nums = [i**2 for i in range(0, int(math.sqrt(n))+1)]
dp = [float('inf')] * (n+1)
dp[0] = 0
for i in range(1, n+1):
for square in square_nums:
if i < square:
break
dp[i] = min(dp[i], dp[i-square] + 1)
return dp[-1]
264. 丑数 II
题目链接
难度:中等3 类型: 动态规划
题目:编写一个程序,找出第 n 个丑数。丑数就是质因数只包含 2, 3, 5 的正整数。
解题思路:从堆中包含一个数字开始:1,去计算下一个丑数。将1从堆中弹出然后将三个数字添加到堆中:1×2,1×3,和 1×5。现在堆中最小的数字是 2。为了计算下一个丑数,要将 2 从堆中弹出然后添加三个数字:2×2,2×3,和2×5。重复该步骤计算所有丑数。在每个步骤中,弹出堆中最小的丑数 k,并在堆中添加三个丑数:k×2, k×3,和k×5。
class Solution:
def nthUglyNumber(self, n: int) -> int:
heap = []# 堆
heappush(heap, 1)# 放进一个元素
nums = []
seen = {1, }
for _ in range(1690):
curr_ugly = heappop(heap)# 取出一个元素
nums.append(curr_ugly)
for i in [2, 3, 5]:
new_ugly = curr_ugly * i
if new_ugly not in seen:
seen.add(new_ugly)
heappush(heap, new_ugly)
return nums[n - 1]
309. 最佳买卖股票时机含冷冻期
题目链接
难度:中等3 类型: 动态规划
题目:给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
思路:
有三种不同的状态:
- 我们目前持有一支股票,对应的「累计最大收益」记为 f[i][0];
- 我们目前不持有任何股票,并且处于冷冻期中,对应的「累计最大收益」记为 f[i][1];
- 我们目前不持有任何股票,并且不处于冷冻期中,对应的「累计最大收益」记为f[i][2]。
计算最大收益时最后一天(n-1天)时就不应该持有股票,因为持有股票是对收益的损耗。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
if not prices:
return 0
n = len(prices)
f = [[-prices[0], 0, 0]] + [[0] * 3 for _ in range(n - 1)]
for i in range(1, n):
f[i][0] = max(f[i - 1][0], f[i - 1][2] - prices[i])
f[i][1] = f[i - 1][0] + prices[i]
f[i][2] = max(f[i - 1][1], f[i - 1][2])
return max(f[n - 1][1], f[n - 1][2])
122. 买卖股票的最佳时机 II
题目链接
难度:中等3 类型: 动态规划
题目:给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
思路:与309思路一致。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
if not prices:
return 0
n = len(prices)
f = [[-prices[0], 0]] + [[0] * 2 for _ in range(n - 1)]
for i in range(1, n):
f[i][0] = max(f[i - 1][0], f[i - 1][1] - prices[i])
f[i][1] = max(f[i - 1][1], f[i - 1][0] + prices[i])
return f[n - 1][1]
714. 买卖股票的最佳时机含手续费
题目链接
难度:中等3 类型: 动态规划
题目:给定一个整数数组 prices,其中第 i 个元素代表了第 i 天的股票价格 ;非负整数 fee 代表了交易股票的手续费用。你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。返回获得利润的最大值。注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。
思路:与309思路一致。
复杂度:时间复杂度:O(n),其中n是prices 数组的长度。空间复杂度:O(n),空间复杂度是可优化的。
class Solution:
def maxProfit(self, prices: List[int], fee: int) -> int:
if not prices:
return 0
n = len(prices)
count=0
f = [[-prices[0], 0]] + [[0] * 2 for _ in range(n - 1)]
for i in range(1, n):
f[i][0] = max(f[i - 1][0], f[i - 1][1] - prices[i])
f[i][1] = max(f[i - 1][1], f[i - 1][0] + prices[i]-fee)
return f[n - 1][1]
300. 最长上升子序列
题目链接
难度:中等3 类型: 动态规划
题目:给定一个无序的整数数组,找到其中最长上升子序列的长度。
复杂度:O(n2) 。
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)
213. 打家劫舍 II
题目链接
难度:中等3 类型: 动态规划
题目:你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,能够偷窃到的最高金额。
思路:环状排列意味着第一个房子和最后一个房子中只能选择一个偷窃,因此可以把此环状排列房间问题约化为两个单排排列房间子问题:
- 在不偷窃第一个房子的情况下(即 nums[1:]),最大金额是 p1;
- 在不偷窃最后一个房子的情况下(即 nums[:n-1]),最大金额是p2。
综合偷窃最大金额:为以上两种情况的较大值,即 max(p1,p2)。
class Solution:
def rob(self, nums: List[int]) -> int:
def my_rob(nums):
cur, pre = 0, 0
for num in nums:
cur, pre = max(pre + num, cur), cur
return cur
return max(my_rob(nums[:-1]),my_rob(nums[1:])) if len(nums) != 1 else nums[0]
120. 三角形最小路径和
题目链接
难度:中等2 类型: 动态规划
题目:给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。相邻的结点在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。
复杂度:时间复杂度o(
n
2
n^2
n2);由于是在原数组上修改,空间复杂度o(1)。
class Solution:
def minimumTotal(self, triangle: List[List[int]]) -> int:
high = len(triangle)
for i in range(1,high):
triangle[i][0] = triangle[i - 1][0] + triangle[i][0]
for j in range(1,i):
triangle[i][j] = min(triangle[i-1][j-1],triangle[i-1][j]) +triangle[i][j]
triangle[i][i] = triangle[i - 1][i - 1] + triangle[i][i]
return min(triangle[high-1])
322. 零钱兑换
题目链接
难度:中等3 类型: 动态规划
题目:给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -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
面试题 08.11. 硬币
题目链接
难度:中等3 类型: 动态规划
题目:硬币。给定数量不限的硬币,币值为25分、10分、5分和1分,编写代码计算n分有几种表示法。(结果可能会很大,你需要将结果模上1000000007)。
class Solution:
def waysToChange(self, n: int) -> int:
mod = 10**9 + 7
coins = [25, 10, 5, 1]
f = [1] + [0] * n
for coin in coins:
for i in range(coin, n + 1):
f[i] += f[i - coin]
return f[n] % mod
516. 最长回文子序列
题目链接
难度:中等3 类型: 动态规划
题目:给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。
思路:
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
n = len(s)
if n<1:
return 0
dp = [[0]*n for _ in range(n)]
for i in range(n):
dp[i][i] = 1
for i in range(n-2,-1,-1):
for j in range(i+1,n):
if s[i]==s[j]:
dp[i][j]=dp[i+1][j-1]+2
else:
dp[i][j]=max(dp[i][j-1],dp[i+1][j])
return dp[0][-1]
647. 回文子串
题目链接
难度:中等3 类型: 动态规划
题目:给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
思路:dp[i][j]的值表示,从字符串s的第i个元素到第j个元素是否能够组成回文串。
- 若s[i] == s[j],且j-i<2,表示ij组成了一个回文中心,那么dp[i][j]为True;
- 若s[i] == s[j],且j-i>=2,那么dp[i][j]的值等同于dp[i+1][j-1]。
class Solution:
def countSubstrings(self, s: str) -> int:
if not s:
return 0
length = len(s)
dp = [[False for i in range(length)] for i in range(length)]
count = length
for i in range(length):
dp[i][i] = True
for i in range(length-2, -1, -1):
for j in range(i+1, length):
if s[i] == s[j]:
if j-i == 1:
dp[i][j] = True
else:
dp[i][j] = dp[i+1][j-1]
else:
dp[i][j] = False
if dp[i][j]:
count += 1
return count
486. 预测赢家
题目链接
难度:中等3 类型: 动态规划
题目:给定一个表示分数的非负整数数组。 玩家 1 从数组任意一端拿取一个分数,随后玩家 2 继续从剩余数组任意一端拿取分数,然后玩家 1 拿,…… 。每次一个玩家只能拿取一个分数,分数被拿取之后不再可取。直到没有剩余分数可取时游戏结束。最终获得分数总和最多的玩家获胜。给定一个表示分数的数组,预测玩家1是否会成为赢家。你可以假设每个玩家的玩法都会使他的分数最大化。
思路:定义二维数组dp,其行数和列数都等于数组的长度,dp[i][j] 表示当数组剩下的部分为下标i到下标j时,当前玩家与另一个玩家的分数之差的最大值,注意当前玩家不一定是先手。
只有当i≤j时,数组剩下的部分才有意义,因此当i>j 时,dp[i][j]=0。
当 i=j 时,只剩一个数字,当前玩家只能拿取这个数字,因此对于所有 0≤i<nums.length,都有dp[i][i]=nums[i]。
当i<j 时,当前玩家可以选择nums[i] 或 nums[j],然后轮到另一个玩家在数组剩下的部分选取数字。在两种方案中,当前玩家会选择最优的方案,使得自己的分数最大化。因此可以得到如下状态转移方程:
dp[i][j]=max(nums[i]−dp[i+1][j],nums[j]−dp[i][j−1])
最后判断 dp[0][nums.length−1] 的值,如果大于或等于 0,则先手得分大于或等于后手得分,因此先手成为赢家,否则后手成为赢家。
class Solution:
def PredictTheWinner(self, nums: List[int]) -> bool:
n = len(nums)
dp = [[0] * n for _ in range(n)]
for i, num in enumerate(nums):
dp[i][i] = num
for i in range(n-2, -1, -1):
for j in range(i+1, n):
dp[i][j] = max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1])
return dp[0][n - 1] >= 0
877. 石子游戏
题目链接
难度:中等3 类型: 动态规划
题目:亚历克斯和李用几堆石子在做游戏。偶数堆石子排成一行,每堆都有正整数颗石子 piles[i] 。
游戏以谁手中的石子最多来决出胜负。石子的总数是奇数,所以没有平局。
亚历克斯和李轮流进行,亚历克斯先开始。 每回合,玩家从行的开始或结束处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止,此时手中石子最多的玩家获胜。
假设亚历克斯和李都发挥出最佳水平,当亚历克斯赢得比赛时返回 true ,当李赢得比赛时返回 false 。
解题思路:和486. 预测赢家完全一样,代码也一样。
343. 整数拆分
题目:给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。
思路:对于的正整数 n,当 n≥2 时,可以拆分成至少两个正整数的和。令 k 是拆分出的第一个正整数,则剩下的部分是 n-k,n-k 可以不继续拆分,或者继续拆分成至少两个正整数的和。由于每个正整数对应的最大乘积取决于比它小的正整数对应的最大乘积,因此可以使用动态规划求解。
创建数组 dp,其中dp[i] 表示将正整数 i 拆分成至少两个正整数的和之后,这些正整数的最大乘积。特别地,0 不是正整数,1 是最小的正整数,0 和 1 都不能拆分,因此dp[0]=dp[1]=0。
当i≥2 时,假设对正整数 i 拆分出的第一个正整数是 j(1≤j<i),则有以下两种方案:
将 i 拆分成 j 和 i−j 的和,且 i−j 不再拆分成多个正整数,此时的乘积是 j×(i−j);
将 i 拆分成 j 和i−j 的和,且 i−j 继续拆分成多个正整数,此时的乘积是 j×dp[i−j]。
因此,当 j 固定时,有 dp[i]=max(j×(i−j),j×dp[i−j])。由于 j 的取值范围是 1 到 i−1,需要遍历所有的 j 得到dp[i] 的最大值。
最终得到 dp[n] 的值即为将正整数 n 拆分成至少两个正整数的和之后,这些正整数的最大乘积。
时间复杂度:O(n^2)。
空间复杂度:O(n)。
class Solution:
def integerBreak(self, n: int) -> int:
dp = [0] * (n + 1)
for i in range(2, n + 1):
for j in range(1,i):
dp[i] = max(dp[i], j * (i - j), j * dp[i - j])
return dp[n]
剑指 Offer 14- I. 剪绳子
题目:给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
思路: 和343. 整数拆分思路完全一样,代码也一样。
剑指 Offer 14- II. 剪绳子 II
题目:给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
思路: 此题与 剑指 Offer 14- I. 剪绳子等价,唯一不同在于本题目涉及 “大数越界情况下的求余问题” 。