Hello, 大家好,我又来分享算法题目了, 细心的朋友们应该可以看到这一次的画面更加美观了
没错, 就是因为以前的做法感觉太随便了,而且不够美观 对于一个程序员怎么可以那么没有要求呢?
顺便附带上GitHub的代码地址:LeetCode代码集合-持续更新中
我们来回忆一下,昨天晚上的例子
如果你没看这一一篇的话, 记得要去观看来张飞机票LeetCode 55 - 跳跃游戏 - 解题思路记录(递归+动态规划法) - Golang
这一节课讲的主要是, 递归以及动态规划的例子, 那么很明显的问题就是...效率极低 那么我们也没有什么思路可以让效率更高一些呢?
那么今天就来讲解另外2种思路
「1. 动态规划」
「2. 优化版动态规划(优化的是空间复杂度)」
那我们就顺着这个思路往下走, 先来了解一下动态规划的算法去取代掉递归的算法
动态规划版
大家应该忘记,我们动态规划是需要一个表来存储以往的记录的吧,在这个算法里, 我们照样需要使用到这个数组
那么这个做法就是不使用递归, 用for循环做, 我们继续看代码
func nonRecurrentJump(nums[] int, dp *[]int) bool{
for i:= len(nums)-2;i>=0;i-- {
maxJump := min(i+nums[i], len(nums)-1) //获取跳跃的步数
for j := i + 1; j<=maxJump; j++{
if (*dp)[j] == 1{
(*dp)[i] = 1
break
}
}
}
if (*dp)[0] == 1{
return true
}
return false
}
在这个算法中, 跟递归的很像,唯一的差别就是在于在第二个For的循环中,我们不在是进入递归了, 而是直接查看
「dp[j]==1」是否成立, 而这段代码中我们的思路其实转变了, 递归的思想是从头开始找, 而这段代码是从后往前
我们把问题简化一下, 从需要到达「dp[len(nums)-1]」 变成 「dp[len(nums)-i]」 个
因为只要我们能到达 「2」 这个元素,说明我们可以达到终点, 那么从2往前的元素,只需要知道自己能不能到达2
这就是这个算法的核心所在, 如果可以到达 则把 「dp[i]=1」 并且跳出内层循环,直到最后的结果应该是这样
「中间的 1 和 0 是不会被赋值的, 因为它们无法到达 2」
最后, 我们判断 「dp[0]==1」 是否成立,如果成立代表从第0个出发,可以到达终点, 很明显我们比起递归而言要快将近「5倍」的时间
优化动态规划
我们在想深一层次, 我们真的需要这个数组吗?
其实不需要对吧, 为什么? 因为我们一直都是在与 「最新」 的一个元素做对比, 那我们为什么还需要一个数组去存储呢?
这个的优化思路跟它LeetCode 53 - 最大子序和 - 解题思路记录(附带动态规划) - GoLang很像
也就是说,我们可以去掉数组,将代码简化成这样...
func optimizeNonRecurrentJump(nums[] int) bool{
aim := len(nums)-1 //终点
for i:= len(nums)-2;i>=0;i-- {
maxJump := min(i+nums[i], len(nums)-1)
for j := i + 1; j<=maxJump; j++{
if j == aim{
aim = i //更新最近的终点
}
}
}
if aim == 0{
return true
}
return false
}
新建 一个变量叫aim 永远拿它指向最后一个终点, 如果有比它更前的元素接近终点, 则更新它
最后我们判断 「aim==0」 是否成立,就知道我们第一个元素是否可以到达终点拉
这个方案比之前的省下了「0.2MB」的空间 又一次的进步。
创作不易, 希望留下你们的小赞赞,是最大的鼓励