LeeCode三百题-贪心算法
[TOC]
代码工程获取
git clone --depth=1 https://gitee.com/SevDaisy/LeeCode300.git |
#55 跳跃游戏
算法思想:贪心/BFS
关键:
Integer可以作为HashMap的Key么?- 在工程实践中,不建议这样做
- 但是在刷题的时候,单纯从语法角度上讲,是可以的
查看源码,可以发现
Integer作为包装类- 内有一个属性是
private final int value Interger所对应的那个int值
- 内有一个属性是
Integer的equals方法,判等的条件是value == ((Integer)obj).intValue();Integer的hashCode方法,是return value;
所以,
Integer在作为Key的时候,是可以满足,该相等相等、该不等不等 的基本要求的。关键:
Set在初次add时会返回true,而在重复add时会返回false因此,我们直接用
visited.add(i)代替- 先
if-else visited.contains(i)再visited.add(i)
- 先
关于超时:
一开始,用
Stack来决定下一个去哪个位置,但是会超时后来,优化
Stack为 优先队列PriorityQueue- 每次都贪心地先去索引最大的最远的节点
- 也还是会超出时间限制
仔细想想,我用
Stack的时候- 本来也就是最后才
push最大的节点 - 和用优先队列效果差不了很多。
/* 用 Stack */
class Solution {
public boolean canJump(int[] nums) {
if (nums == null || nums.length < 1) return false;
int iMax = nums.length;
int rightMax = 0;
Stack<Integer> stack = new Stack<Integer>();
Set<Integer> visited = new HashSet<>(iMax);
stack.push(0);
while (!stack.isEmpty()) {
int cur = stack.pop().intValue();
int step = nums[cur];
for (int i = cur - step; i <= cur + step; i++) {
if (i > 0 && i < iMax && visited.add(i)) {
stack.push(i);
rightMax = Math.max(rightMax, i);
}
}
}
return rightMax + 1 == iMax;
}
}
/* 用 PriorityQueue */
class Solution {
public boolean canJump(int[] nums) {
if (nums == null || nums.length < 1) return false;
int iMax = nums.length;
int rightMax = 0;
Queue<Integer> queue = new PriorityQueue<>(iMax);
Set<Integer> visited = new HashSet<>(iMax);
queue.add(0);
while (!queue.isEmpty()) {
int cur = queue.poll();
int step = nums[cur];
for (int i = cur - step; i <= cur + step; i++) {
if (i > 0 && i < iMax && visited.add(i)) {
queue.add(i);
rightMax = Math.max(rightMax, i);
}
}
}
return rightMax + 1 == iMax;
}
}- 本来也就是最后才
优化:仔细想想:
- 我们需要做全遍历么?或者说,
- 我们有回头走的必要么?—— 没有
因此,我们不需要
Stack或者PriorityQueue,也不需要visited我们只需要一直往前走就好了。
记录当前可达的最右索引为
rightMax,然后对于i in [0,iMax) 更新 rightMax如果
rightMax+1 ≥ iMax,则return true否则,
return falsefor (int i = 0; i < iMax; i++) {
/* 在能走的范围里 */
if (i <= rightMax) {
/* 更新最远可达的位置 */
rightMax = Math.max(rightMax, i + nums[i]);
/* 如果最远已经达到或者超过终点了,返回成功 */
if (rightMax + 1 >= iMax) return true;
} else {
/* 否则,失败了就结束吧 */
break;
}
}最终代码
2ms=>83.72%
package Order300; |
#45 跳跃游戏 II
- 在 跳跃游戏T55 的基础上,补充计步器即可
// T55 核心代码 |
计步器,显然应该从
0开始关键:计步器应该在什么时候
++?- 答:在当前
i已经是上一个rightMax的时候
- 答:在当前
此时,再继续往前走,就意味着使用某个格子来更新了当前正在使用的
rightMax使用了的是哪个格子,并不重要
巧妙的就是,只要我们不得不更新当前正在使用的
rightMax,就意味着走出了一步关键:
i是 遍历[0,iMax-1)而不是[0,iMax)因为,如果 在
i == iMax-1的时候,i已经在终点了- 正巧那时,
curRightMax也是iMax-1,然后就会计步器再++ - 那就走到终点后面去了。那就了多走了一步了。
- 正巧那时,
题目保证了一定会到达终点,所以只要能走到
iMax-2,即i==iMax-2时- 如果 正好
curRightMax == iMax-2那么就stepCnt++下一步就是终点没问题 - 否则
curRightMax > iMax-2,即curRightMax ≥ iMax-1所以这一步就足够到达终点
- 如果 正好
package Order300; |
#1306 跳跃游戏 III
- 优化路径:
- 递归+
HashSet——9ms => 31.71% C++实现 ——52ms- 迭代+
HashSet+Stack——17ms - 迭代+
HashSet+LinkedList——14~16ms - 递归+
boolean[]——4ms—— 用boolean[]代替HashSet也勉强算是 状态压缩 了 - 递归+
boolean[]+转移递归出口到递归入口 ——3ms => 96.72%—— 仅仅减去了一部分叶子节点的剪枝
package Order300; |
#1345 跳跃游戏 IV
point:
queue的入队操作,用offer感觉更帅 —— 而且真的会少一层函数调用哦在源码优先队列
PriorityQueue:public boolean add(E e) {
return offer(e);
}
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);
siftUp(i, e);
size = i + 1;
return true;
}
绝妙关键:
BFSvisited数组元素是boolean如何维护stepCnt?- 使用
levelSize记录每次的当前这个层次总共有多少节点 levelSize用完以后,先维护stepCnt:stepCnt++- 再更新:
levelSize = queue.size()
int stepCnt = 0;
int levelSize;
queue.add(0);
while (!queue.isEmpty()) {
levelSize = queue.size();
stepCnt++;
while (levelSize-- > 0) {
/* Normally BFS Work Here */
}
}- 使用
AStar搜索不适用于本题剪枝优化 合并连续相同项为两项 48ms => 94.36%
- 因为至少要跳一步,所以是合并为两项而不是一项
写了乱七八糟一堆优化,都没用。就一个剪枝优化有用。感觉浪费了我的6、7个小时。难受啊。
package Order300; |