返回

LeetCode 全排列 II 算法详解

前端

LeetCode算法学习之旅--Recursion--47. 全排列 II

在本文中,我们将踏上一个激动人心的LeetCode之旅,深入探索第47题:全排列II。这是一个中等难度的问题,非常适合初级算法学习者巩固他们的基本功。

问题

给你一个包含数字 1 - n 的数组 nums,其中某些数字是重复的。请输出所有可能的全排列。

分析

这道题考查的是排列组合的思想,我们需要找到所有可能的排列组合,并剔除其中重复的元素。我们可以使用递归的方法来解决这个问题。

解法一:递归

递归的核心思想是将一个复杂的问题分解成一系列较小的子问题,然后对这些子问题递归求解,最终组合出问题的解。

  1. 基本情况: 当数组nums为空时,返回一个空列表[]。
  2. 递归步骤: 对于数组nums中的每个元素num,将num插入到当前排列的所有可能位置中,然后递归求解剩余元素的全排列。
  3. 剪枝优化: 由于数组中存在重复元素,我们需要剪枝重复的排列。当我们插入一个元素num时,如果该元素与前一个元素相等,则跳过当前插入位置。

代码示例:

def permuteUnique(nums):
    result = []
    nums.sort()  # 对数组排序,以便剪枝重复排列
    backtrack(nums, [], result)
    return result

def backtrack(nums, permutation, result):
    if not nums:
        result.append(permutation.copy())
        return
    for i in range(len(nums)):
        # 剪枝重复排列
        if i > 0 and nums[i] == nums[i - 1]:
            continue
        permutation.append(nums[i])
        backtrack(nums[:i] + nums[i + 1:], permutation, result)
        permutation.pop()

复杂度分析

  • 时间复杂度:O(n * n!)。对于每个元素,我们有n个插入位置,并且有n!个排列。
  • 空间复杂度:O(n),用于递归调用栈。

解法二:暴力

暴力解法比较直观,我们可以枚举所有可能的排列组合,然后检查每个排列是否包含重复元素。如果包含重复元素,则将其丢弃。

代码示例:

def permuteUnique(nums):
    result = []
    visited = set()
    permutation = []
    backtrack(nums, visited, permutation, result)
    return result

def backtrack(nums, visited, permutation, result):
    if len(permutation) == len(nums):
        result.append(permutation.copy())
        return
    for i in range(len(nums)):
        if i in visited:
            continue
        visited.add(i)
        permutation.append(nums[i])
        backtrack(nums, visited, permutation, result)
        visited.remove(i)
        permutation.pop()

复杂度分析

  • 时间复杂度:O(n^n)。我们有n个元素,每个元素可以放置在n个位置,因此共有n^n个排列。
  • 空间复杂度:O(n),用于递归调用栈。

总结

本题考察了递归和剪枝的思想,以及暴力解法的实现。在实际应用中,我们通常使用递归解法,因为它可以避免重复计算,提高效率。