首页

▼ 算法合集

▼ 加密算法

▼ 凯撒密码

凯撒密码

▼ Hill密码算法

Hill密码算法

▼ 多项式哈希算法

多项式哈希算法

▼ Rail Fence Cipher

Rail Fence Cipher

▼ Graph算法

▼ 关节点

关节点

▼ Bellman-Ford算法

Bellman-Ford算法

▼ 广度优先搜索

广度优先搜索

▼ GraphBridges

桥接模式

▼ 深度优先搜索

深度优先搜索

▼ 检测循环

检测循环

▼ Dijkstra算法

Dijkstra算法

▼ Eulerian Path

欧拉路径

▼ Floyd-Warshall算法

Floyd-Warshall算法

▼ HamiltonianCycle

Hamiltonian Cycle

▼ Kruskal算法

Kruskal算法

▼ Prim算法

Prim算法

▼ 强连通分量

强连通分量

▼ 拓扑排序

拓扑排序

▼ Travelling Salesman Problem

Travelling Salesman

▼ 图像处理算法

▼ Seam Carving算法

内容感知缩放算法

▼ 链表

▼ 反向遍历

反向遍历

▼ GraphTraversal

GraphTraversal

▼ 数学算法

▼ 二进制浮点数

BinaryFloatingPoint

▼ 位操作

位操作算法

▼ 复数

复数

▼ 欧几里得算法

Euclidean Algorithm

▼ Euclidean Distance

欧几里得距离

▼ 阶乘算法

阶乘算法

▼ 快速幂算法

快速幂算法

▼ Fibonacci数列

斐波那契数列

▼ 傅里叶变换

Fourier变换

▼ Horner法

霍纳法则

▼ 整数划分

整数划分

▼ 判断是否为2的幂

判断是否为2的幂

▼ 最小公倍数

最小公倍数

▼ Liu Hui

Liu Hui

▼ 矩阵

Matrix

▼ Pascal三角形

Pascal三角形

▼ Primality Test

素数测试

▼ 质因数

质因数

▼ 弧度计算

弧度计算

▼ 埃拉托色尼筛法

埃拉托色尼筛法

▼ SquareRoot

SquareRoot

▼ MachineLearning

▼ K均值算法

K均值算法

▼ K近邻算法

K近邻算法

▼ 搜索算法

▼ 二分查找算法

二分查找

▼ 插值搜索算法

插值搜索算法

▼ 跳跃搜索算法

跳跃搜索算法

▼ 线性搜索

线性搜索算法

▼ 集合

▼ 笛卡尔积

笛卡尔积

▼ 组合总和

组合总和

▼ 组合算法

组合算法

▼ Fisher-Yates洗牌算法

Fisher-Yates洗牌算法

▼ 背包问题

背包问题

▼ 最长公共子序列

最长公共子序列

▼ 最长递增子序列

最长递增子序列

▼ 最大子数组

最大子数组

▼ 排列组合

排列组合

▼ 幂集

幂集算法

▼ 最短公共超序列

最短公共超序列

▼ Sorting Algorithms

▼ 冒泡排序

冒泡排序

▼ 桶排序算法

桶排序算法

▼ 计数排序算法

计数排序

▼ 堆排序算法

堆排序

▼ 插入排序

插入排序

▼ 归并排序

归并排序

▼ 快速排序算法

快速排序算法

▼ 基数排序

基数排序

▼ 选择排序算法

选择排序算法

▼ 希尔排序

希尔排序

▼ 统计学

▼ 加权随机

加权随机算法

▼ 字符串算法

▼ Hamming距离

Hamming距离

▼ KnuthMorrisPratt算法

Knuth-Morris-Pratt算法

▼ LevenshteinDistance

Levenshtein距离

▼ 最长公共子串

最长公共子串

▼ 回文检测算法

回文检测算法

▼ Rabin-Karp算法

Rabin-Karp算法

▼ 正则表达式匹配

正则表达式匹配

▼ Z算法

Z算法

▼ Tree Data Structure

▼ 广度优先搜索

广度优先搜索

▼ 深度优先搜索

深度优先搜索

▼ 未分类

▼ 最佳买卖股票时机

最佳买卖股票时机

▼ 汉诺塔算法

HanoiTower

▼ 跳跃游戏算法

跳跃游戏

▼ KnightTour

骑士巡逻

▼ N皇后问题

N皇后问题

▼ 雨水收集

雨水收集

▼ 递归楼梯问题

递归楼梯问题

▼ 方阵旋转

方阵旋转

▼ 独特路径

UniquePaths

▼ 数据结构

▼ BloomFilter算法

布隆过滤器

▼ 不相交集数据结构

Disjoint Set

▼ 双向链表

双向链表

▼ Graph

Graph算法

▼ 哈希表

哈希表

▼ Heap数据结构

Heap数据结构

▼ 链表

链表

▼ LRU缓存

LRU缓存

▼ 优先队列

优先队列

▼ 队列

队列

▼ 栈结构

栈结构

▼ Tree Data Structure

树结构

▼ AVL树

AVL树

▼ 二叉搜索树

二叉搜索树

▼ Fenwick树

Fenwick树

▼ 红黑树

红黑树

▼ 线段树

SegmentTree

▼ Trie数据结构

Trie数据结构

跳跃游戏

问题

给定一个非负整数数组,您最初位于数组的第一个索引。数组中的每个元素代表您在该位置的最大跳跃长度。

确定您是否能够到达最后一个索引。

示例 #1

输入: [2,3,1,1,4]
输出: true
解释: 从索引 0 跳跃 1 步到索引 1,然后跳跃 3 步到达最后一个索引。

示例 #2

输入: [3,2,1,0,4]
输出: false
解释: 无论如何,您总是会到达索引 3。其最大跳跃长度为 0,这使得无法到达最后一个索引。

命名

我们将数组中的一个位置称为"好索引",如果从该位置开始,我们可以到达最后一个索引。否则,该索引被称为"坏索引"。问题随后简化为索引 0 是否是"好索引"。

解决方案

方法 1:回溯

这是低效的解决方案,我们尝试每一种可能的跳跃模式,这些模式将我们从第一个位置带到最后一个位置。我们从第一个位置开始,并跳转到可以到达的每一个索引。重复此过程直到到达最后一个索引。当卡住时,回溯。

参见 backtrackingJumpGame.js 文件

时间复杂度:O(2^n)。 从第一个位置到最后一个位置有 2^n(上界)种跳跃方式,其中 n 是数组 nums 的长度。

辅助空间复杂度O(n)。 递归需要额外的栈帧来存储。

方法 2:动态规划自顶向下

自顶向下的动态规划可以被认为是优化的回溯。它依赖于这样一个观察结果:一旦我们确定了某个索引是好的/坏的,这个结果将永远不会改变。这意味着我们可以存储结果,而不需要在每次重新计算时都重新计算。

因此,对于数组中的每个位置,我们记住这个位置是好的还是坏的。让我们称这个数组为 memo,并且它的值要么是 GOOD,BAD,UNKNOWN 中的一个。这种技术被称为 memoization。

参见 dpTopDownJumpGame.js 文件

时间复杂度:O(n^2)。 对于数组中的每个元素,比如 i,我们正在查看其右侧的 nums[i] 元素,以寻找一个好的索引。nums[i] 最多可以是 n,其中 n 是数组 nums 的长度。

辅助空间复杂度O(2 * n) = O(n)。 首先 n 来自递归。其次 n 来自 memo 表的使用。

方法 3:动态规划自底向上

通过消除递归,从自顶向下的方法转换为自底向上的方法。实际上,这样做可以获得更好的性能,因为我们不再有方法的堆栈开销,并且甚至可能从中受益于一些缓存。更重要的是,这一步为未来的优化开辟了可能性。通常通过尝试逆转自顶向下方法中的步骤顺序来消除递归。

这里的观察是,我们只跳转到右边。这意味着如果我们从数组的右侧开始,每次我们查询其右侧的位置时,那个位置已经被确定为是好是坏。这意味着我们不再需要递归了,因为我们会始终击中 memo 表。

参见 dpBottomUpJumpGame.js 文件

时间复杂度:O(n^2)。 对于数组中的每个元素,比如 i,我们正在查看其右侧的 nums[i] 元素,以寻找一个好的索引。nums[i] 最多可以是 n,其中 n 是数组 nums 的长度。

辅助空间复杂度O(n)。 这来自 memo 表的使用。

方法 4:贪心

一旦我们在底部状态中有了代码,我们可以做出一个最后的、重要的观察。从给定的位置开始,当我们试图看看我们是否能跳到一个好位置时,我们只使用一个 - 最左边的一个。换句话说,最左边的一个。如果我们跟踪这个最左边的好位置作为一个单独的变量,我们就可以避免在数组中搜索它。不仅如此,而且我们可以完全停止使用数组。

参见 greedyJumpGame.js 文件

时间复杂度:O(n)。 我们对 nums 数组进行单次遍历,因此有 n 步,其中 n 是数组 nums 的长度。

辅助空间复杂度O(1)。 我们没有使用任何额外的内存。

参考资料