1. Jump Game

Given an array of non-negative integers, you are initially positioned at the first index of the array. Each element in the array represents your maximum jump length at that position.

Determine if you are able to reach the last index.

For example:
A = [2,3,1,1,4], return true.

A = [3,2,1,0,4], return false.

思路:首先还是来好好观察下吧。如果在能跳的范围内只有0的话那么便不能到达最后一个位置,隐隐约约感觉这题可以用dp和递归来求解。看了下解答,还是很有意思的这题。

首先先定义 good index为数组中能达到最后位置的index,如果不能则为bad index。然后问题就变成第一个位置也就是索引0是不是一个good index。

This is a dynamic programming question. Usually, solving and fully understanding a dynamic programming problem is a 4 step process:

  1. Start with the recursive backtracking solution (从递归回溯法入手)
  2. Optimize by using a memoization(记忆) table (top-down[3] dynamic programming)
  3. Remove the need for recursion (bottom-up dynamic programming)
  4. Apply final tricks to reduce the time / memory complexity

方法一:回溯法(会stack overflow)

简单粗暴,遍历所有的可能,从第一位置开始尝试能到达的所有可能,重复过程直至能达到最后位置,如果不能则向上一步回溯。(有点类似深度优先搜索)

public class Solution {
public boolean canJumpFromPosition(int position, int[] nums) {
if (position == nums.length - 1) {    //这个是最低层递归的终止条件
return true;
} int furthestJump = Math.min(position + nums[position], nums.length - 1);  //这步没想到
for (int nextPosition = position + 1; nextPosition <= furthestJump; nextPosition++) {
if (canJumpFromPosition(nextPosition, nums)) {  //如果递归最底层返回的是true,那么一层层向上返回结果就是true而如果是false,那么会继续for的下个循环
return true;
}
} return false;
} public boolean canJump(int[] nums) {
return canJumpFromPosition(0, nums);
}
}

一个快速的优化是从右到左检查 nextPosition而不是从左到右,也就是尝试跨最大步,这样能尽快到最后一个位置:

// Old
for (int nextPosition = position + 1; nextPosition <= furthestJump; nextPosition++)
// New
for (int nextPosition = furthestJump; nextPosition > position; nextPosition--)

方法二:从顶到下的dp[也会Stack Overflow]

记住每一个索引是不是 good index 从而避免重复计算。用memo数组来记录,值是GOOD,BAD,UNKNOWN中的一个。

dp步骤:

1. 将memo数组中的元素初始为UNKNOWN除了最后一个置为GOOD。

2. 修改回溯算法,递归的第一步检查这个index是GOOD还是BAD,返回相应的true或false,否则和之前一样继续递归。

3. 如果知道现在的index的好坏值,则存储到memo数组中。

enum Index {
GOOD, BAD, UNKNOWN
} public class Solution {
Index[] memo; public boolean canJumpFromPosition(int position, int[] nums) {
if (memo[position] != Index.UNKNOWN) {
return memo[position] == Index.GOOD ? true : false;
} int furthestJump = Math.min(position + nums[position], nums.length - 1);
for (int nextPosition = position + 1; nextPosition <= furthestJump; nextPosition++) {
if (canJumpFromPosition(nextPosition, nums)) {
memo[position] = Index.GOOD;
return true;
}
} memo[position] = Index.BAD;
return false;
} public boolean canJump(int[] nums) {
memo = new Index[nums.length];
for (int i = 0; i < memo.length; i++) {
memo[i] = Index.UNKNOWN;
}
memo[memo.length - 1] = Index.GOOD;
return canJumpFromPosition(0, nums);
}
}

方法三:自底向上的dp(会超时)

和上面的dp不同在于从右向左遍历数组,因为右边的索引的好坏已经知道,所以往左遍历的时候直接查询它右边索引的好坏即可,没有必要再递归。

enum Index {
GOOD, BAD, UNKNOWN
} public class Solution {
public boolean canJump(int[] nums) {
Index[] memo = new Index[nums.length];
for (int i = 0; i < memo.length; i++) {
memo[i] = Index.UNKNOWN;
}
memo[memo.length - 1] = Index.GOOD; for (int i = nums.length - 2; i >= 0; i--) {
int furthestJump = Math.min(i + nums[i], nums.length - 1);
for (int j = i + 1; j <= furthestJump; j++) {
if (memo[j] == Index.GOOD) {
memo[i] = Index.GOOD;
break;
}
}
} return memo[0] == Index.GOOD;
}
}

方法四:贪心

从上面的方法观察得知,从一个给定的位置,当我们试着是否能从这个位置跳到 GOOD 位置,只需要看它能不能到达它最左边的第一个GOOD位置。所以当我们从右向左遍历数组时,只需要判断

currPosition + nums[currPosition] >= leftmostGoodIndex

即当前位置加上它能达到的最大位置是否比最左边的第一个GOOD index 的索引要大。如果是则这个位置本身是GOOD的并且更新它为新的最左边的GOOD index。

public class Solution {
public boolean canJump(int[] nums) {
int lastPos = nums.length - 1;
for (int i = nums.length - 1; i >= 0; i--) {
if (i + nums[i] >= lastPos) {
lastPos = i;
}
}
return lastPos == 0;
}
}

2. Merge Intervals

Given a collection of intervals, merge all overlapping intervals.

For example,
Given [1,3],[2,6],[8,10],[15,18],
return [1,6],[8,10],[15,18].

思路:首先还是基于观察,如果我们把每个interval当作是图结构中的节点,将有重叠的部分连接起来,那么最后图中连在一起的节点可以并为一个interval。

方法一:Connected Components [Time Limited Exceeded]

基于上面直觉上的观察,首先用邻接表来代表这个图,在两个方向上都插入有向边来模拟无向边。然后从任意节点遍历整个图来确定哪个节点是连接到哪个组件的,直至所有节点都被访问了。思路是有了,接下来是怎么转为程序,首先需要有一个集合 Set 来存储所有访问过的节点。最后我们需要将每个连接好的组件合成一个interva,取组件中的最小值作为start,最大值作为end即可。一个要注意的是,因为这个过程中要经常比较两个interval是不是overlap的,所以最好为这个单独写一个方法来判断。然后就是怎么利用邻接表来构建这个图,以及插入有向边,然后要根据这个邻接表来构建组件。看了下答案,感觉程序还是有点复杂的

class Solution {
private Map<Interval, List<Interval> > graph; // 图的邻接表表示,key是节点,value是所有与这个节点相邻的节点
private Map<Integer, List<Interval> > nodesInComp; // key表明是第几个compnont,从0开始,value是具体的compnont
private Set<Interval> visited; // return whether two intervals overlap (inclusive)
private boolean overlap(Interval a, Interval b) {
return a.start <= b.end && b.start <= a.end;
} // merges all of the nodes in this connected component into one interval.
private Interval mergeNodes(List<Interval> nodes) {
int minStart = nodes.get(0).start;
for (Interval node : nodes) {
minStart = Math.min(minStart, node.start);
} int maxEnd = nodes.get(0).end;
for (Interval node : nodes) {
maxEnd= Math.max(maxEnd, node.end);
} return new Interval(minStart, maxEnd);
} // 1. 构建graph
private void buildGraph(List<Interval> intervals) {
graph = new HashMap<>();
for (Interval interval : intervals) {
graph.put(interval, new LinkedList<>());
} for (Interval interval1 : intervals) {
for (Interval interval2 : intervals) {
if (overlap(interval1, interval2)) {
graph.get(interval1).add(interval2);
graph.get(interval2).add(interval1);
}
}
}
} // 2. 构建components
private void buildComponents(List<Interval> intervals) {
nodesInComp = new HashMap();
visited = new HashSet();
int compNumber = 0; for (Interval interval : intervals) {
if (!visited.contains(interval)) {
markComponentDFS(interval, compNumber);
compNumber++;
}
}
} // 使用深度优先搜索来构建nodesInComp
private void markComponentDFS(Interval start, int compNumber) {
Stack<Interval> stack = new Stack<>();
stack.add(start); while (!stack.isEmpty()) {
Interval node = stack.pop();
if (!visited.contains(node)) {
visited.add(node); if (nodesInComp.get(compNumber) == null) {  // 如果没有则创建
nodesInComp.put(compNumber, new LinkedList<>());
}
nodesInComp.get(compNumber).add(node); for (Interval child : graph.get(node)) {
stack.add(child);
}
}
}
} public List<Interval> merge(List<Interval> intervals) {
buildGraph(intervals);
buildComponents(intervals); // for each component, merge all intervals into one interval.
List<Interval> merged = new LinkedList<>();
for (int comp = 0; comp < nodesInComp.size(); comp++) {
merged.add(mergeNodes(nodesInComp.get(comp)));
} return merged;
}
}

方法二:Sorting [Accepted]

按照start值来排序所有interval,首先把第一个interval插入到merged集合中,然后按如下思路循环考虑,如果现在的interval是在上一个的end之后开始的则不overlap,将这个interval插入到merged集合中,否则overlap,和上一个interval merge,更新end。

class Solution {
  // 注意这里比较器的用法
private class IntervalComparator implements Comparator<Interval> {
@Override
public int compare(Interval a, Interval b) {
return a.start < b.start ? -1 : a.start == b.start ? 0 : 1;
}
} public List<Interval> merge(List<Interval> intervals) {
    // 利用自带的比较器进行排序
Collections.sort(intervals, new IntervalComparator()); LinkedList<Interval> merged = new LinkedList<Interval>();
for (Interval interval : intervals) {
// if the list of merged intervals is empty or if the current
// interval does not overlap with the previous, simply append it.
if (merged.isEmpty() || merged.getLast().end < interval.start) { // merged中的最后一个,也就是最新的那一个就是上一个interval
merged.add(interval);
}
// otherwise, there is overlap, so we merge the current and previous
// intervals.
else {
merged.getLast().end = Math.max(merged.getLast().end, interval.end);
}
} return merged;
}
}

注意上面比较器的用法。

3. Permutation Sequence

The set [1,2,3,…,n] contains a total of n! unique permutations.

By listing and labeling all of the permutations in order,
We get the following sequence (ie, for n = 3):

  1. "123"
  2. "132"
  3. "213"
  4. "231"
  5. "312"
  6. "321"

Given n and k, return the kth permutation sequence. Note: Given n will be between 1 and 9 inclusive.

思路:一开始想用之前的递归思路,求出所有可能的组合,再取第k个。不过写代码时还是感觉很苦手,这里有一个更简单的方法。首先要明确一点是,以 1,2,3,4为例,如果第一个数字是3,那么剩下的数字1,2,4构成的所有可能的组合应该是6,也就是n!个,知道这点就好办了。假如现在 k=13,也就是要取第13个排列组合,那我们知道以1为开始的组合有6个,然后是以2为首的6个排列组合,然后是3为首的6个排列组合,至此可以确定第13个排列组合的第一个数字应该是3,也就是通过k/(n-1)!。那么第二个数字是多少呢?直接求解子问题就行了,在3为首的组合前面有12个组合,13减去12等于1,也就是说现在的问题变成了再1,2,4中取第一个组合是什么,1除以2!,结果是0。需要将0~n-1 映射成 1~n ,每取一个后就从1~n中remove掉这个。

class Solution {
public String getPermutation(int n, int k) {
int[] nums=new int[n+1];
nums[0]=1;
for(int i=1;i<=n;i++){
nums[i]=i*nums[i-1];
}
List<Integer> digitals=new ArrayList();
for(int i=1;i<=n;i++){
digitals.add(i);
}
StringBuilder sb=new StringBuilder();
k--; // 求第k个排列,那么它前面应该有k-1个排列
for(int i=1;i<=n;i++){
int index=k/nums[n-i]; // 注意这里是先取index,再取具体数字
sb.append(digitals.get(index)+"");
digitals.remove(index); // 取完后要将这个数字移出,这也是为什么我们要先取索引再取值
k=k-index*nums[n-i];
}
return sb.toString();
}
}

LeetCode解题报告—— Jump Game & Merge Intervals & Permutation Sequence的更多相关文章

  1. LeetCode解题报告:Linked List Cycle && Linked List Cycle II

    LeetCode解题报告:Linked List Cycle && Linked List Cycle II 1题目 Linked List Cycle Given a linked ...

  2. leetcode解题报告(2):Remove Duplicates from Sorted ArrayII

    描述 Follow up for "Remove Duplicates": What if duplicates are allowed at most twice? For ex ...

  3. LeetCode 解题报告索引

    最近在准备找工作的算法题,刷刷LeetCode,以下是我的解题报告索引,每一题几乎都有详细的说明,供各位码农参考.根据我自己做的进度持续更新中......                        ...

  4. LeetCode解题报告汇总! All in One!

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 把自己刷过的所有题目做一个整理,并且用简洁的语言概括了一下思路,汇总成了一个表格. 题目 ...

  5. 解题报告 之 HDU5288 OO&#39; s Sequence

    解题报告 之 HDU5288 OO' s Sequence Description OO has got a array A of size n ,defined a function f(l,r) ...

  6. leetcode解题报告(10):Merge Two Sorted Lists

    描述 Merge two sorted linked lists and return it as a new list. > The new list should be made by sp ...

  7. LeetCode解题报告—— Swap Nodes in Pairs & Divide Two Integers & Next Permutation

    1. Swap Nodes in Pairs Given a linked list, swap every two adjacent nodes and return its head. For e ...

  8. 【Leetcode】【Hard】Merge Intervals

    Given a collection of intervals, merge all overlapping intervals. For example,Given [1,3],[2,6],[8,1 ...

  9. LeetCode解题报告—— 1-bit and 2-bit Characters & 132 Pattern & 3Sum

    1. 1-bit and 2-bit Characters We have two special characters. The first character can be represented ...

随机推荐

  1. NOIP系列

    NOIP2015运输计划 唉 真是 这题 卡死我了 tarjan离线lca复杂度O(n) 最后各种卡常,多交几遍才A(洛谷104ms) %%%zk学长609ms 注意二分的时候左边界要定成0 根据题意 ...

  2. [CQOI2011]放棋子

    想到了50%吧算是. f[i][j][k]表示,前i种,占了j行k列.方案数. 发现,转移要处理:“用c个棋子,占据n行m列”的方案数. 设g[i][j][k]表示,i行j列用k个棋子占的方案数.直接 ...

  3. SpringMVC配置详解(转)

    要想灵活运用Spring MVC来应对大多数的Web开发,就必须要掌握它的配置及原理. 一.Spring MVC环境搭建:(Spring 2.5.6 + Hibernate 3.2.0) 1. jar ...

  4. JQuery学习六

    <JQuery cookie>插件 cookie是保存在浏览器上的内容,用户在这次浏览页面的时候向cookie中保存文本内容.下次再访问页面的时侯就可以取出来上次保存的内容.这样可以得到上 ...

  5. jq 由name获取那个radio选中了的

    $("input[name='approve']:checked").val() //获取radio选中的值;var radio_checked_val = $("#fo ...

  6. JAVA 枚举单例模式

     1.枚举单例模式的实现 public enum Singleton { INSTANCE { @Override protected void read() { System.out.println ...

  7. 代码Review发现问题

    FrmMain.cs中存在问题 1. int i=0 设定为了全局常量且未在类顶部,出现问题时不好查找 i 属于常用临时变量,设定全局变量容易引起混乱 2.定义的全局变量但仅在一处方法中使用,定义全局 ...

  8. Hadoop 介绍

    1.Hadoop简介 Hadoop[hædu:p]实现了一个分布式文件系统(Hadoop Distributed File System),简称HDFS.HDFS有高容错性的特点,并且设计用来部署在低 ...

  9. mybatis中association和collection的column传入多个参数值

    在使用 association和collection 进行关联查询的时候 column 参数可能会有多个,如下: 注意: parameterType 一定要是 java.util.Map

  10. ZooKeeper入门(四)

    入门:使用ZooKeeper的协调分布式应用 这个文档使你对ZooKeeper快速入门,它主要针对想尝试它的开发者.并且包含简单的单机的ZooKeeper服务的安装说明,一些验证是否运行的命令,和一个 ...