做完这题,可以做一下力扣887题石子游戏,基本上套路是一样的。
方法:动态规划:
这次从递归开始到记忆化递归,再到动态规划
动态规划问题,弄清楚三点:
1、重复子问题;
2、最优子结构;
3、无后效性。
动态规划:
1、状态定义;
2、状态转移方程;
3、初始化;base case
4、输出;
5、思考状态压缩。
可以用递归去求,但是会存在重叠子问题,加个备忘录可以解决重复问题。
方法1:递归:
定义一个helper()函数,功能是返回数组[i:j]之间当前做选择的玩家赢过对手的分数。如果大于零,
则代表他在这个子问题中赢了。
递归函数内部:当前选择的分数,减去,往后对手赢过自己的分数(剩余数组的递归结果)。因为选择有两种,
所以在两个差值中取较大的。
代码如下:
class Solution:
def PredictTheWinner(self, nums: List[int]) -> bool:
#递归
def helper(i,j): #i,j为两端的索引
if i == j: #递归出口,只有一个元素时
return nums[i]
left = nums[i] - helper(i+1,j) #选择左端
right = nums[j] - helper(i,j-1) #选择右端
return max(left,right) #取较大值
return helper(0,len(nums) - 1) >= 0
方法2:记忆化递归:递归+备忘录
如果不加备忘录,会有重复子问题,因此,加备忘录
代码如下:
class Solution:
def PredictTheWinner(self, nums: List[int]) -> bool:
#记忆化递归
n = len(nums)
memo = [[None] * n for i in range(n)]
def helper(i,j):
if memo[i][j] != None:
return memo[i][j]
if i == j:
memo[i][j] = nums[i]
return nums[i]
left = nums[i] - helper(i+1,j)
right = nums[j] - helper(i,j-1)
memo[i][j] = max(left,right)
return memo[i][j]
return helper(0,n-1) >= 0
方法3:动态规划:
状态定义:
p[i][[j]:当前玩家在数组[i:j]中先手,赢过对方的分数
状态转移方程:
d[i][j] = max(nums[i] - dp[i+1][j], nums[j] - dp[i][j-1])
dp table的方向是由下往上,由左往右
base case:
也就是只考虑表的右上角,初始值是对角线上的值为数组中相应值,即dp[i][i] = nums[i]
代码如下:
class Solution:
def PredictTheWinner(self, nums: List[int]) -> bool:
#动态规划
n = len(nums)
dp = [[0] * n for _ in range(n)]
#base case
for i in range(n):
dp[i][i] = nums[i]
for i in range(n-2,-1,-1):
for j in range(i+1,n):
left = nums[i] - dp[i+1][j]
right = nums[j] - dp[i][j-1]
dp[i][j] = max(left,right)
return dp[0][n-1] >= 0
方法4:优化:状态压缩
class Solution:
def PredictTheWinner(self, nums: List[int]) -> bool:
#动态规划,状态压缩
n = len(nums)
dp = [0] * n
#base case
for i in range(n):
dp[i]= nums[i]
for i in range(n-2,-1,-1):
for j in range(i+1,n):
left = nums[i] - dp[j]
right = nums[j] - dp[j-1]
dp[j] = max(left,right)
return dp[n-1] >= 0
方法1:
时间复杂度:O(N^2),N是数组的长度
空间复杂度:O(N),栈空间
方法2:
时间复杂度:O(N^2),N是数组的长度
空间复杂度:O(N^2), 备忘录的大小
方法3:
时间复杂度:O(N^2),N是数组的长度
空间复杂度:O(N^2)
方法4:
时间复杂度:O(N^2),N是数组的长度
空间复杂度:O(N)