LeetCode 第47题:全排列 II

111 阅读3分钟

LeetCode 第47题:全排列 II

题目描述

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

难度

中等

题目链接

点击在LeetCode中查看题目

示例

示例 1:

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

示例 2:

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

提示

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

解题思路

回溯算法 + 剪枝

这道题是"全排列"的进阶版本,需要在原有回溯算法的基础上增加对重复元素的处理。关键是要理解如何避免生成重复的排列。

关键点:

  1. 首先对数组进行排序,使相同的数字相邻
  2. 使用标记数组记录每个位置的数字是否被使用
  3. 对于重复的数字,只有在前一个数字已经被使用的情况下才能使用当前数字
  4. 需要在回溯时正确恢复状态

具体步骤:

  1. 对输入数组进行排序
  2. 初始化结果列表和标记数组
  3. 定义回溯函数,增加重复元素的判断
  4. 遍历数组时跳过不合法的选择
  5. 递归生成所有合法排列

图解思路

算法步骤分析表

步骤当前排列可选数字操作说明
初始[][1,1,2]排序数组已排序
选择1[1][1,2]选择第一个1第一个数字
选择2[1,1][2]选择第二个1允许使用重复数字
完成[1,1,2][]记录得到一个排列
回溯[1][1,2]跳过重复避免重复排列

状态/情况分析表

情况输入输出说明
无重复[1,2,3]6种排列同全排列I
两数重复[1,1,2]3种排列需要去重
全部重复[1,1,1]1种排列只有一种可能

代码实现

C# 实现

public class Solution {
    private IList<IList<int>> result = new List<IList<int>>();
    private bool[] used;
    
    public IList<IList<int>> PermuteUnique(int[] nums) {
        Array.Sort(nums); // 先排序,使相同的数字相邻
        used = new bool[nums.Length];
        Backtrack(nums, new List<int>());
        return result;
    }
    
    private void Backtrack(int[] nums, List<int> current) {
        if (current.Count == nums.Length) {
            result.Add(new List<int>(current));
            return;
        }
        
        for (int i = 0; i < nums.Length; i++) {
            // 如果当前数字已经使用过,跳过
            if (used[i]) continue;
            
            // 如果当前数字与前一个数字相同,且前一个数字未使用,跳过
            if (i > 0 && nums[i] == nums[i-1] && !used[i-1]) continue;
            
            used[i] = true;
            current.Add(nums[i]);
            
            Backtrack(nums, current);
            
            current.RemoveAt(current.Count - 1);
            used[i] = false;
        }
    }
}

执行结果

  • 执行用时:128 ms
  • 内存消耗:42.8 MB

代码亮点

  1. 🎯 通过排序和剪枝高效去除重复排列
  2. 💡 巧妙利用used数组判断重复元素的使用情况
  3. 🔍 剪枝条件的设计既保证正确性又提高效率
  4. 🎨 代码结构清晰,易于理解和维护

常见错误分析

  1. 🚫 忘记对数组进行排序
  2. 🚫 剪枝条件写错,导致漏掉合法排列
  3. 🚫 重复元素的处理逻辑错误
  4. 🚫 回溯时状态恢复不完整

解法对比

解法时间复杂度空间复杂度优点缺点
普通回溯O(n!×n)O(n)简单直观有重复结果
排序+剪枝O(n!×n)O(n)无重复结果需要排序
HashSet去重O(n!×n)O(n!×n)实现简单空间消耗大

相关题目