20210331 LeetCode 每日一题(子集 II)

279 阅读1分钟

题目描述

原题链接:子集 II

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

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

示例

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

1 <= nums.length <= 10 -10 <= nums[i] <= 10

解答

本题是LeetCode-78. 子集的变体,仅仅是由条件中 无重复元素 改为了 可能包含重复元素

这类题用回溯法来解会更简单些,这里贴一个笔者常用的回溯模板:

function backtracking(list) {
  const result = [] // 定义结果搜集数组
  const temp = [] // 定义辅助数组或变量
  //  定义辅助函数
  function helper(idx) {
    if (符合条件) {
      result.push([...temp]) //  如果是一个可行解就添加的结果搜集数组
    }
    // 根据索引遍历待搜索的区域
    for (let item of list.slice(idx)) {
      temp.push(item) //  辅助数组结合新成员改变状态
      if (符合条件) {
        helper(list.indexOf(item) + 1) //  如果符合特定条件则继续深入搜索
      }
      temp.pop() //  回归原来的状态
    }
  }

  helper(0) //  调用辅助函数

  return result //  返回结果
}

在题LeetCode-78. 子集中,因为不存在重复数,所以一定可以继续深入搜索,这里贴下 78 题的解法:

var subsets = function (nums) {
  const res = []
  const tempRes = []
  //    从索引 idx 开始获取
  const helper = idx => {
    res.push([...tempRes])
    for (let i = idx; i < nums.length; i++) {
      tempRes.push(nums[i])
      helper(i + 1)
      tempRes.pop()
    }
  }
  helper(0)
  return res
}

但在本题中,因为可能包含重复数,所以在继续深入搜索时需判断这个数是否重复了,这里我们采用先排序,并定义一个 Set 类型的变量,以辅助数组转换为字符串后作为一个 value 存到 set 中,仅当 set 不包含同一个 value 才继续递归搜索:

var subsetsWithDup = function (nums) {
  let res = []
  let tempRes = []
  let set = new Set()
  nums.sort((a, b) => a - b)
  const helper = idx => {
    res.push([...tempRes])
    for (let i = idx; i < nums.length; i++) {
      tempRes.push(nums[i])
      const value = tempRes.join('-')
      if (!set.has(value)) {
        set.add(value)
        helper(i + 1)
      }
      tempRes.pop()
    }
  }
  helper(0)
  return res
}

复杂度分析:

  • 时间复杂度:排序数组复杂度 O(NlogN),递归搜索中的最坏情况下是 nums 中无重复元素,需要枚举其所有 2^n 个子集,每个子集加入答案时需要拷贝一份,耗时 O(n),即复杂度为 O(n * 2^n)。