Lexi's Leetcode solutions

Posts Tagged ‘subsets/permutation

If n = 4 and k = 2, a solution is:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
趁热打铁,这个题和上两道相比简单了不少,因为是固定长度所以不用stack了,用int[]加一个position variable。每次在这个pos上一个一个试数,每确定一个就在下一个pos处recurse。

public ArrayList<ArrayList<Integer>> combine(int n, int k) {
  ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
  if (k <= 0 || n < 1)
    return result;
  populateResult(1, n, 0, result, new int[k]);
  return result;
}
private void populateResult(int startNum, int endNum, int pos, 
            ArrayList<ArrayList<Integer>> result, int[] path) {
  if (pos == path.length) {
    ArrayList<Integer> combination = new ArrayList<Integer>();
    convertArrayToList(path, combination);
    result.add(combination);
    return;
  }
  for (int i = startNum; i <= endNum; i++) { //i is using which number
    path[pos] = i;
    populateResult(i + 1, endNum, pos + 1, result, path); //use next 
                                            //number on next position
    //as i++, use next number on same position
  }
}

比如 {1, 1, 1, 2, 3},x = 3, 结果就是{111}{12}{3}。这个题挺难想的,和上一题又不一样。正常stack,把1push进去,然后直接在2上recurse x=2的(因为1只能用一次)。这样一直做到x==0。这样就会有重复:当stack pop空了之后,回到第二个1上,又开始push,那stack的第0个又是1了,刚才已经做过stack第0个是1的一遍了(左边的一遍肯定包括了最多种的情况),所以现在再把1放里面就会重复。所以要在做“要不要在当前layer试一下下一个数?”的决定的时候,要看“下一个数和当前数是否相等?因为当前数已经做过了,同一层layer说明是同一个position,再放当前数就会重复)。

总结:

  • combination/set: 用stack,因为长度不固定。
  • permutation: 用int[],keep一个pos pointer。
  • 主函数里面不要循环,就一个以初始值call副函数的语句就行。
  • 副函数是循环里面call自己。
public ArrayList<ArrayList<Integer>> combinationSum2(int[] num, int target) {
  Arrays.sort(num);
  ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
  Stack<Integer> path = new Stack<Integer>();
  combinationSum(num, target, 0, path, result);
  return result;
}
private void combinationSum(int[] arr, int target, int start, Stack<Integer> path, ArrayList<ArrayList<Integer>> result) {
  if (target == 0) {
    ArrayList<Integer> list = new ArrayList<Integer>();
    list.addAll(path);
    result.add(list);
    return;
  }
  for (int i = start; i < arr.length; i++) {
    if (arr[i] > target)
      return;
    path.push(arr[i]);
    combinationSum(arr, target - arr[i], i + 1, path, result);
    path.pop();
    //do we want to place arr[i + 1] at the same position arr[i] has been?
    while (i + 1 < arr.length && arr[i] == arr[i + 1]) {
      i++;
    }
  }
}

比如{2, 3, 6, 7},x = 7,组合就是{2, 2, 3}和{7}。不能有重复,且必须是sorted order。

这个是标准DFS,一定要记住。先sort array,然后试图用2,用了2之后x相当于只剩5,然后下一层递归再用2,用到没法再用为止(当前值比x还大)。失败后stack pop出来,再试着在前面用了2, 2, 2的条件下用3, 挂,pop出2,在22的条件下用3,正好,输出。然后再pop一个2,再用3, {2, 3},再用下一个3。。在脑子里想一下和树的dfs的图是一样的。

public ArrayList<ArrayList<Integer>> combinationSum(int[] candidates, int target) {
  Arrays.sort(candidates);
  ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
  Stack<Integer> path = new Stack<Integer>();
  combinationSum(candidates, target, 0, path, result);
  return result;
}
private void combinationSum(int[] arr, int target, int start, Stack<Integer> path, ArrayList<ArrayList<Integer>> result) {
  if (target == 0) {
    ArrayList<Integer> list = new ArrayList<Integer>();
    list.addAll(path);
    result.add(list);
    return;
  }
  for (int i = start; i < arr.length; i++) {
    if (arr[i] > target) //这时候break可以保证不要再查后面那些越来越大的
      return;
    path.push(arr[i]);
    combinationSum(arr, target - arr[i], i, path, result); //start永远不会越界,所以一开始不用check
    path.pop();
   }
 }

subset 1:  array本身都是distinct number,所以往里一个一个插不用担心重复。{}, 放第一个,放第二个。。

//inintialize a {}, keep inserting, mark as used (distinct nums)
public ArrayList<ArrayList<Integer>> subsets(int[] S) {
    Arrays.sort(S);
    ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
    result.add(new ArrayList<Integer>());
    for (int i = 0; i < S.length; i++) {
        ArrayList<ArrayList<Integer>> temp = new ArrayList<ArrayList<Integer>>();
        for (ArrayList<Integer> prevSubset : result) {
            ArrayList<Integer> newSubset = new ArrayList<Integer>();
            newSubset.addAll(prevSubset);
            newSubset.add(S[i]);
            temp.add(newSubset);
        }
        result.addAll(temp);
    }
    return result;
}

subset 2————————————-
先sort,然后往后加。重点是怎么保证不重复?比如array[] = {1, 2, 2}

  1. {}
  2. {}, {1}
  3. {}, {1}, {2}, {12}
  4. {}, {1}, {2}, {12} 这时要是还往{}和{1}里面插2就会产生重复。所以要想办法把他们skip过去。skip到哪为止呢?毕竟还是想往{2}和{12}里插的。
  5. 所以是arr[i] == arr[i + 1]的时候,不要往arr[i]之前那一层插了。要往i这一层新插的里面插。
public ArrayList<ArrayList<Integer>> subsetsWithDup(int[] num) {
  Arrays.sort(num);
  ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
    result.add(new ArrayList<Integer>());
  int start = 0;
  for (int i = 0; i < num.length; i++) {
    int prevSize = result.size();
    //上一层的size,是数字可以用来循环,避免modification exception
    for (int j = start; j < prevSize; j++) {
      ArrayList<Integer> newSubset = new ArrayList<Integer>();
      newSubset.addAll(result.get(j));
      newSubset.add(num[i]);
      result.add(newSubset);
    }
    if (i + 1 < num.length && num[i] == num[i + 1])
      start = prevSize;
      //i + 1那层越过i - 1这层的所有subsets,因为第i层已经往i - 1层的所有subsets里插过了
    else
      start = 0;
  }
  return result;
}

平时的permutation有一个boolean used[],如果arr[i]在之前的layer里用过了,就不能再用了。比如{1, 2, 1},到pos2的时候,看即使arr[2]是“1”,但是这个1不是第一个用过的1,所以还可以放在当前pos当中。但是在pos0的时候,如果想用第二个1,就不行了(当前pos已经有人用过数字1了,即使用的是arr[0]不是当前想用的arr[2],但是是同一个数字就不行),所以skip掉。因此,算法是“在每个position存一个hashset记录在这个position放过那些数字,若是以前放过了,这次就别放了。“

public ArrayList<ArrayList<Integer>> permuteUnique(int[] num) {
  ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
    if (num.length == 0)
  return result;
  permute(num, 0, new int[num.length], new boolean[num.length], result);
  return result;
}
private void permute(int[] num, int pos, int[] path, boolean[] used, ArrayList<ArrayList<Integer>> result) {
  if (pos == path.length) {
    result.add(convertToIntegerList(path));
    return;
  }
  Set<Integer> layerTried = new HashSet<Integer>();
  for (int i = 0; i < num.length; i++) {
    if (!layerTried.contains(num[i]) && used[i] == false) {
      path[pos] = num[i];
      used[i] = true;
      permute(num, pos + 1, path, used, result);
      layerTried.add(num[i]);
      used[i] = false;
    }
  }
}

这个思路本来很简单,如果用recursion来enumeration的话,即用set,每次第i个位置用了set的第j个元素,然后set remove j, recurse on i + 1。非常straight forward的做法,但是用java这么做有好几个问题:

  • 如果input有重复,如aabc,set没法处理同时存在2个a的情况。
  • 在recurse on set的每个元素的时候,因为for循环是interate thru set itself,但里面还有set.add(), set.remove这些改变set本身的语句,所以会有modify iterator exception。还得弄个copy先。
  • 代码巨长。越写越没信心。

所以还是CC150上的另一种思路:每次取一个元素,对于之前(上一层循环的list结果)的每个permutation, 在能插的地方就插这个新元素,然后生成新的permutation,放入这一层的结果。这样代码相对简单,而且不用recursion。有几个要点:

  1. 最开始要传一个空的permutation进去,否则没处可insert第一个元素。
  2. 这种做法不能防止duplication(1212可能有好几种insert成它的方法)。
public ArrayList<ArrayList<Integer>> permute(int[] num) {
  ArrayList<ArrayList<Integer>> lastLayer = new ArrayList<ArrayList<Integer>>();
  lastLayer.add(new ArrayList<Integer>());
  for (int i = 0; i < num.length; i++) {
    ArrayList<ArrayList<Integer>> newLayer = new ArrayList<ArrayList<Integer>>();
    for (int j = 0; j < lastLayer.size(); j++) {
      ArrayList<Integer> curr = lastLayer.get(j);
      insertEverywhere(curr, num[i], newLayer);
    }
    lastLayer = newLayer;
  }
  return lastLayer;
}
private void insertEverywhere(ArrayList<Integer> curr, int num, ArrayList<ArrayList<Integer>> newLayer) {
  for (int i = 0; i <= curr.size(); i++) {
    ArrayList<Integer> newList = new ArrayList<Integer>(curr);
    newList.add(i, num);
    newLayer.add(newList);
   }
}