全排列

依据回溯算法构建,需要预先将数据排序

PS: 求全排列的问题一般规模都不大,所以可以有限考虑使用递归简单实现,如果超时或者栈溢出,可以考虑替换为迭代版本(基于栈数据结果实现)。

递归版本

  1. function permutation(d) {
  2. let data = [...d].sort((a, b) => a - b);
  3. let ans = [];
  4. // 记录哪些元素已经使用过
  5. let visited = new Array(d.length).fill(false);
  6. const bk = (t) => {
  7. if (t.length === d.length) {
  8. ans.push([...t]);
  9. return;
  10. }
  11. for (let i = 0; i < data.length; i++) {
  12. if (!visited[i]) {
  13. visited[i] = true;
  14. t.push(data[i]);
  15. bk(t);
  16. t.pop();
  17. visited[i] = false;
  18. }
  19. }
  20. };
  21. bk([]);
  22. return ans;
  23. }

下一个排列

思路:降序序列最大

  • 从后向前寻找满足降序规则d[i] >= d[i+1]的第一个元素i,那么i-1下标比i下标元素要小,i-1就是要调整的目标下标位置,由于向前循环,i--,所以循环退出时,i已经减一。
  • (i,len)区间内,从后向前向左寻找第一个d[i] >= d[j]的下标j,但循环跳出后j位置的元素一定大于i-1位置的元素。
  • 交换ij下标对应的元素。
  • i之后的元素反转为升序,满足与目标d的字母序的差距最小。

    1. function next_permutation(d) {
    2. let i = d.length - 2;
    3. while (i >= 0 && d[i] >= d[i+1]){ i--; }
    4. if (i < 0) return false;
    5. let j = d.length - 1;
    6. while (j >= i + 1 && d[i] >= d[j]) { j--; }
    7. swap(d, i, j);
    8. reverse(d, i + 1, d.length - 1);
    9. return true;
    10. }

    上一个排列

    思路:升序序列最小

  • 从后向前寻找满足升序规则d[i-1] > d[i]的第一个元素i,那么i-1下标比i下标元素要大,i-1这个位置就是要调整的目标。

  • [i, len)内向右寻找最后一个d[j]使得d[j] < d[i-1],这样循环退出时,j-1就是满足条件的下标,与i-1交换,使i-1位置的元素变小。
  • 重排[i, len)内的元素,使后面的元素能够变成降序.

    1. function prev_permutation(d) {
    2. let i = d.length - 1;
    3. while (i > 0 && d[i-1] <= d[i]) {i--;}
    4. if (i <= 0) return false;
    5. let j = i;
    6. while (j < d.length && d[j] < d[i-1]) { j++;}
    7. swap(d, i-1, j-1);
    8. reverse(d, i, d.length - 1);
    9. return true;
    10. }

子集

思路:求排列的时候是所有元素都在是加入结果集,那么子集就是每次都加入结果集,不过要处理集合的重复性

function subsets(d) {
  let data = [...d].sort((a, b) => a - b);
  let ans = [];
  // 记录哪些元素已经使用过
  let visited = new Array(d.length).fill(false);

  // 记录哪些子集已经添加
  let ct = new Set();

  const compute = (masks) => {
    let r = 0;
    for (let i = 0; i < marks.length; i++) {
      r += (1 << masks[i]);
    }
    return r;
  };

  const bk = (t) => {
    if (t.length <= d.length) {
      let c = compute(t);
      if (!ct.has(c)) {
          ans.push([...t]);
        ct.add(c);
      }
    }
    for (let i = 0; i < data.length; i++) {
      if (!visited[i]) {
        visited[i] = true;
        t.push(data[i]);
        bk(t);
        t.pop();
        visited[i] = false;
      }
    }
  };

  bk([]);
  return ans;
}

后续优化思路:
1、使用迭代版本;
2、visited和ct都是记录当前使用了的数组下标,可以考虑合并状态,有个注意实现事项是,t中保存的数据可以更换为下标。