*内容来自leetcode,今天起进入了中级算法的动态规划部分
1.跳跃游戏
题目要求
给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
示例 1:
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
示例 2:
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
提示:
1 <= nums.length <= 3 * 104
0 <= nums[i] <= 105
思路
从第一个下标跳至最后一个下标,只能往前跳或者原地不动,无论如何如果能够达到终点,最后跳跃的长度总归是n-1。处于任意一个位置时,可以确定能够跳跃的范围,如果最后范围能够囊括终点,则可以抵达。就有了如下的代码,如下代码对于简单数组是能够完成任务的,但是用例用有的例子过于复杂,存在如下的步数过长的问题,导致代码运行超时。。。
[6283,7996,1198,4197,8108,14510,9487,1502,7142,17669,4166,6511,7171,11336,11534,13144,10082,7433,8029,18659,2884,9662...]
class Solution:
def canJump(self, nums: List[int]) -> bool:
n = len(nums)
check = [0] * (n)
reached = [0]
while reached:
loc = reached.pop(0)
if loc == n-1:
return True
step = nums[loc]
while step > 0:
if loc + step < n and check[loc+step] == 0:
reached.append(loc + step)
check[loc + step] = 1
step -= 1
return False
在第二个循环里增加了一个判断,居然通过了,只是耗时非常感人。
class Solution:
def canJump(self, nums: List[int]) -> bool:
n = len(nums)
check = [0] * (n)
reached = [0]
while reached:
loc = reached.pop(0)
step = nums[loc]
while step >= 0:
if loc + step == n-1:
return True
if loc + step < n and check[loc+step] == 0:
reached.append(loc + step)
check[loc + step] = 1
step -= 1
return False
考虑换一种思路,之前做过的爬楼梯的题目,当前阶的前一步总是上一阶或者上上阶 ,由此可以一步一步往前回溯。这道题也可以用类似的思路,从能够抵达终点的点开始往前倒,如果最终能够倒回起点,则说明能够从起点到终点。写出来超时。。。
官方给出的思路是维护最大可达距离,如果最大可达超过最后一个数的位置,则说明可达,如果到最后还是小于最后一个数的位置,则说明不可达。
class Solution:
def canJump(self, nums: List[int]) -> bool:
n = len(nums)
max_reach = 0
for i in range(0,n):
if max_reach >= n-1:
return True
if max_reach >= i and max_reach < i + nums[i]:
max_reach = i + nums[i]
return False
2.不同路径
题目要求
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
示例 1:
输入:m = 3, n = 7 输出:28
示例 2:
输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下
思路
m为行,n为列。无论怎么走,向右的步数总为n-1,向下的步数总为m-1,不同的路径就在于向下和向右的搭配不同。以一个数组来看,向下为1,向右为2,那么数组中1和2的个数是一定的,只是顺序问题,所以只需要找到全部的排序方式就可以了,在数学上来说就是一个排列组合问题。如果需要每一个路径的全部过程,可以考虑回溯算法,但是此题只需要总的路径数。
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
def jieC(num):
res = 1
#此处开始用的是while循环来实现,但是时间花费会更大
#可能是用while操作更多
for i in range(1,num+1):
res = res * i
return res
routeNum = 0
total = m + n -2
routeNum = jieC(total) // (jieC(n-1)*jieC(m-1))
return routeNum
根据官方的基于动态规划的解答,本题可以视为是类似于爬楼梯的二维版本。
核心在于:f(i,j)=f(i−1,j)+f(i,j−1)
具体的:
我们用 f(i,j)表示从左上角走到 (i,j)的路径数量,其中 iii 和 jjj 的范围分别是 [0,m)和 [0,n)。
由于我们每一步只能从向下或者向右移动一步,因此要想走到 (i,j),如果向下走一步,那么会从 (i−1,j)(i-1, j)(i−1,j) 走过来;如果向右走一步,那么会从 (i,j−1)走过来。因此我们可以写出动态规划转移方程:
f(i,j)=f(i−1,j)+f(i,j−1)
需要注意的是,如果 i=0,那么 f(i−1,j)并不是一个满足要求的状态,我们需要忽略这一项;同理,如果 j=0,那么 f(i,j−1)并不是一个满足要求的状态,我们需要忽略这一项。
初始条件为 f(0,0)=1,即从左上角走到左上角有一种方法。
最终的答案即为 f(m−1,n−1)。
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
route = [[1 for _ in range(n)]] + [[1] + [0]*(n-1) for _ in range(m-1)]
for i in range(1,m):
for j in range(1,n):
route[i][j] = route[i-1][j] + route[i][j-1]
return route[m-1][n-1]