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();
    }
}

 

posted on 2018-03-15 14:19  f91og  阅读(212)  评论(0编辑  收藏  举报