leetcode-master/problems/0055.跳跃游戏.md

300 lines
8.5 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<p align="center">
<a href="https://www.programmercarl.com/xunlian/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/>
</a>
<p align="center"><strong><a href="./qita/join.md">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们受益!</strong></p>
# 55. 跳跃游戏
[力扣题目链接](https://leetcode.cn/problems/jump-game/)
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
示例  1:
- 输入: [2,3,1,1,4]
- 输出: true
- 解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
示例  2:
- 输入: [3,2,1,0,4]
- 输出: false
- 解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 所以你永远不可能到达最后一个位置。
## 算法公开课
**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html)[贪心算法,怎么跳跃不重要,关键在覆盖范围 | LeetCode55.跳跃游戏](https://www.bilibili.com/video/BV1VG4y1X7kB),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
## 思路
刚看到本题一开始可能想:当前位置元素如果是 3我究竟是跳一步呢还是两步呢还是三步呢究竟跳几步才是最优呢
其实跳几步无所谓,关键在于可跳的覆盖范围!
不一定非要明确一次究竟跳几步,每次取最大的跳跃步数,这个就是可以跳跃的覆盖范围。
这个范围内,别管是怎么跳的,反正一定可以跳过来。
**那么这个问题就转化为跳跃覆盖范围究竟可不可以覆盖到终点!**
每次移动取最大跳跃步数(得到最大的覆盖范围),每移动一个单位,就更新最大覆盖范围。
**贪心算法局部最优解:每次取最大跳跃步数(取最大覆盖范围),整体最优解:最后得到整体最大覆盖范围,看是否能到终点**
局部最优推出全局最优,找不出反例,试试贪心!
如图:
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20230203105634.png)
i 每次移动只能在 cover 的范围内移动每移动一个元素cover 得到该元素数值(新的覆盖范围)的补充,让 i 继续移动下去。
而 cover 每次只取 max(该元素数值补充后的范围, cover 本身范围)。
如果 cover 大于等于了终点下标,直接 return true 就可以了。
C++代码如下:
```CPP
class Solution {
public:
bool canJump(vector<int>& nums) {
int cover = 0;
if (nums.size() == 1) return true; // 只有一个元素,就是能达到
for (int i = 0; i <= cover; i++) { // 注意这里是小于等于cover
cover = max(i + nums[i], cover);
if (cover >= nums.size() - 1) return true; // 说明可以覆盖到终点了
}
return false;
}
};
```
* 时间复杂度: O(n)
* 空间复杂度: O(1)
## 总结
这道题目关键点在于:不用拘泥于每次究竟跳几步,而是看覆盖范围,覆盖范围内一定是可以跳过来的,不用管是怎么跳的。
大家可以看出思路想出来了,代码还是非常简单的。
一些同学可能感觉,我在讲贪心系列的时候,题目和题目之间貌似没有什么联系?
**是真的就是没什么联系,因为贪心无套路**!没有个整体的贪心框架解决一系列问题,只能是接触各种类型的题目锻炼自己的贪心思维!
## 其他语言版本
### Java
```Java
class Solution {
public boolean canJump(int[] nums) {
if (nums.length == 1) {
return true;
}
//覆盖范围, 初始覆盖范围应该是0因为下面的迭代是从下标0开始的
int coverRange = 0;
//在覆盖范围内更新最大的覆盖范围
for (int i = 0; i <= coverRange; i++) {
coverRange = Math.max(coverRange, i + nums[i]);
if (coverRange >= nums.length - 1) {
return true;
}
}
return false;
}
}
```
### Python
```python
class Solution:
def canJump(self, nums: List[int]) -> bool:
cover = 0
if len(nums) == 1: return True
i = 0
# python不支持动态修改for循环中变量,使用while循环代替
while i <= cover:
cover = max(i + nums[i], cover)
if cover >= len(nums) - 1: return True
i += 1
return False
```
```python
## for循环
class Solution:
def canJump(self, nums: List[int]) -> bool:
cover = 0
if len(nums) == 1: return True
for i in range(len(nums)):
if i <= cover:
cover = max(i + nums[i], cover)
if cover >= len(nums) - 1: return True
return False
```
```python
## 基于当前最远可到达位置判断
class Solution:
def canJump(self, nums: List[int]) -> bool:
far = nums[0]
for i in range(len(nums)):
# 要考虑两个情况
# 1. i <= far - 表示 当前位置i 可以到达
# 2. i > far - 表示 当前位置i 无法到达
if i > far:
return False
far = max(far, nums[i]+i)
# 如果循环正常结束,表示最后一个位置也可以到达,否则会在中途直接退出
# 关键点在于,要想明白其实列表中的每个位置都是需要验证能否到达的
return True
```
### Go
```go
// 贪心
func canJump(nums []int) bool {
cover := 0
n := len(nums)-1
for i := 0; i <= cover; i++ { // 每次与覆盖值比较
cover = max(i+nums[i], cover) //每走一步都将 cover 更新为最大值
if cover >= n {
return true
}
}
return false
}
func max(a, b int ) int {
if a > b {
return a
}
return b
}
```
### JavaScript
```Javascript
var canJump = function(nums) {
if(nums.length === 1) return true
let cover = 0
for(let i = 0; i <= cover; i++) {
cover = Math.max(cover, i + nums[i])
if(cover >= nums.length - 1) {
return true
}
}
return false
};
```
### Rust
```Rust
impl Solution {
pub fn can_jump(nums: Vec<i32>) -> bool {
if nums.len() == 1 {
return true;
}
let (mut i, mut cover) = (0, 0);
while i <= cover {
cover = (i + nums[i] as usize).max(cover);
if cover >= nums.len() - 1 {
return true;
}
i += 1;
}
false
}
}
```
### C
```c
#define max(a, b) (((a) > (b)) ? (a) : (b))
bool canJump(int* nums, int numsSize){
int cover = 0;
int i;
// 只可能获取cover范围中的步数所以i<=cover
for(i = 0; i <= cover; ++i) {
// 更新cover为从i出发能到达的最大值/cover的值中较大值
cover = max(i + nums[i], cover);
// 若更新后cover可以到达最后的元素返回true
if(cover >= numsSize - 1)
return true;
}
return false;
}
```
### TypeScript
```typescript
function canJump(nums: number[]): boolean {
let farthestIndex: number = 0;
let cur: number = 0;
while (cur <= farthestIndex) {
farthestIndex = Math.max(farthestIndex, cur + nums[cur]);
if (farthestIndex >= nums.length - 1) return true;
cur++;
}
return false;
}
```
### Scala
```scala
object Solution {
def canJump(nums: Array[Int]): Boolean = {
var cover = 0
if (nums.length == 1) return true // 如果只有一个元素,那么必定到达
var i = 0
while (i <= cover) { // i表示下标当前只能够走cover步
cover = math.max(i + nums(i), cover)
if (cover >= nums.length - 1) return true // 说明可以覆盖到终点,直接返回
i += 1
}
false // 如果上面没有返回就是跳不到
}
}
```
### C#
```csharp
public class Solution
{
public bool CanJump(int[] nums)
{
int cover = 0;
if (nums.Length == 1) return true;
for (int i = 0; i <= cover; i++)
{
cover = Math.Max(i + nums[i], cover);
if (cover >= nums.Length - 1) return true;
}
return false;
}
}
```
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
</a>