回溯算法将集合问题一网打尽

234 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

回溯算法将集合问题一网打尽

1. 子集

1.1 问题描述

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

1.2 要求

示例 1:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

示例 2:

输入:nums = [0]
输出:[[],[0]]

1.3 思路

每一个元素都面临着两种选择,选或者不选index 作为添加递归层数的判断依据,如果 index === length 则说明子集的一种情况已经产生,将其加入 paths即可。求取子集问题,不需要任何剪枝!因为子集就是要遍历整棵树

image-20220531122253689

1.4 代码

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var subsets = function(nums) {
  const length = nums.length;
  const paths = [];
​
  const backtracking = (index, path = []) => {
    if (index === length) {
      paths.push([...path]);
      return;
    }
​
    path.push(nums[index]);
    backtracking(index + 1, path);
​
    path.pop();
    backtracking(index + 1, path);
  }
​
  backtracking(0);
​
  return paths;
};

2. 子集 II

2.1 问题描述

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

2.2 要求

示例 1:

输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]

示例 2:

输入:nums = [0]
输出:[[],[0]]

2.3 思路

如果你能理解上面的回溯法,那么包含重复元素的数组的子集,只不过一个小的改进。

比如说求 nums = [1,2,2] 的子集,那么对于子集 [1,2] 是选择了第一个 2,那么就不能再选第二个 2 来构成 [1,2] 了。所以,此时的改动点,就是先排序,每个元素 nums[i] 添加到 path 之前,判断一下 nums[i] 是否等于 nums[i - 1] ,如果相等就不添加到 path 中。

下面以[2,1,2,4,3]为例看看具体推导过程:

  • 将数组排序,如[2,1,2,4,3]得排成[1,2,2,3,4]

  • 第零层(特殊):[],剩余的子集为[1,2,2,3,4],故开启新的一层

  • 第一层(特殊元素为1):

    • 先向下深搜,有[1],[1,2],[1,2,2],[1,2,2,3],[1,2,2,3,4]
    • 现在开始回溯并去除元素4([1,2,2,3]),但4后面没有元素了,故继续回溯
    • 现在开始回溯并去除元素3([1,2,2]),然后3后面有子集合[4]
    • 再对[4]进行分层,有一层且该层只有4,故有[1,2,2,4]
    • 现在开始回溯并去除元素2([1,2]),然后2后面有子集合[3,4]
    • 现在开始回溯并去除元素2([1,2]),然后2后面有子集合[3,4]
    • 现在开始回溯并去除元素2([1]),然后2后面有集合[2,3,4],但因为求子集合不能有重复元素
    • 故子集合为[3,4],故有[1,3],[1,3,4],[1,4]
    • 现在开始回溯并去除元素1([]),然后有子集合[2,2,3,4],开启新的一层
  • 第二层(特殊元素为2):

    • 先向下深搜,有[2],[2,2],[2,2,3],[2,2,3,4]
    • 现在开始回溯并去除元素4([2,2,3]),但4后面没有元素了,故继续回溯
    • 现在开始回溯并去除元素3([2,2]),然后3后面有子集合[4]
    • 再对[4]进行分层,有一层且该层只有4,故有[2,2,4]
    • 现在开始回溯并去除元素2([2]),然后2后面有子集合[3,4]
    • 再对[3,4]进行分层,第一层为[3],[3,4],第二层为[4],故有[2,3],[2,3,4],[2,4]
    • 现在开始回溯并去除元素2([]),然后2后面有子集合[2,3,4]
    • 但是2重复了,故子集合为[3,4],开启新的一层
  • 后面也是以此类推

image-20220531125741579.png

2.4 代码

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var subsetsWithDup = function(nums) {
  const length = nums.length;
  const paths = [];
  nums.sort((a, b) => a - b);
​
  const backtracking = (index, path = []) => {
    paths.push([...path]);
​
    for(let i = index; i < length; i++) {
      if (i > index && nums[i] === nums[i - 1]) continue;
      path.push(nums[i]);
      backtracking(i + 1, path);
      path.pop();
    }
  }
​
  backtracking(0);
​
  return paths;
};