回溯算法-全排列 II

389 阅读2分钟

「这是我参与11月更文挑战的第29天,活动详情查看:2021最后一次更文挑战

Hope is a good thing, maybe the best of things. And no good thing ever dies.

前言

回溯算法是对树形或者图形结构执行一次深度优先遍历,实际上类似枚举的搜索尝试过程,在遍历的过程中寻找问题的解。

深度优先遍历有个特点:当发现已不满足求解条件时,就返回,尝试别的路径。此时对象类型变量就需要重置成为和之前一样,称为「状态重置」

题目

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

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

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

思路分析

回溯算法-全排列 的差异主要是:数字序列是可以重复的

本题难点在于正确、高效的去重

通常处理包含重复的序列,为了方便在迭代中识别出重复,先对 nums 中数字升序排序,使得相同的数字相邻。

  1. 我们使用一个 used 数组记录使用过的数字,使用过了就不再使用。
  2. 如果当前的选项nums[i],与同一层的前一个选项nums[i-1]相同,且nums[i-1]存在,且没有被使用过,则忽略选项nums[i]
  3. 经过了充分的剪枝,当数字选够了,就可以直接加入解集,结束递归

解题

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var permuteUnique = function(nums) {
    const ans = [];
    const used = new Array(nums.length).fill(false); // 记录使用过的数字
    const backtrack = (idx, perm) => {
        if (idx === nums.length) { // 执行结束加入解集
            ans.push(perm.slice());
            return;
        }
        for (let i = 0; i < nums.length; ++i) {
            if (used[i] || (i > 0 && nums[i] === nums[i - 1] && !used[i - 1])) {
                continue; // 结束当前for循环,进入下一个for循环
            }
            perm.push(nums[i]);
            used[i] = true;
            // 递归
            backtrack(idx + 1, perm);
            used[i] = false;
            perm.pop();
        }
    }
    // 进行排序从小到大
    nums.sort((x, y) => x - y);
    backtrack(0, []);
    return ans;
};

附:leetcode-cn.com/problems/pe…

结语

如果这篇文章帮到了你,欢迎点赞👍和关注⭐️。

文章如有错误之处,希望在评论区指正🙏🙏

欢迎关注我的微信公众号,一起交流技术,微信搜索 🔍 :「 五十年以后