46. 全排列

97 阅读4分钟

【题目】

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

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

示例 2:

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

示例 3:

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

提示:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同

【题目解析】

解题思路

全排列问题是回溯算法的经典应用之一。回溯算法通过试错的思想,尝试分步去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答时,它将取消上一步甚至是上几步的计算,再通过其他的可能的分步解答再次尝试寻找问题的答案。

算法步骤
  1. 路径:也就是已经做出的选择。
  2. 选择列表:也就是你当前可以做的选择。
  3. 结束条件:也就是到达决策树底层,无法再做选择的条件。

具体到全排列问题,我们可以定义一个递归函数,通过参数传递当前的路径和选择列表,每次从选择列表中选取一个元素加到路径中,并将其从选择列表移除,然后递归调用这个函数,直到选择列表为空,表示找到了一种全排列,将其添加到结果列表中。

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        def backtrack(path, choices):
            # 结束条件:如果选择列表为空,说明找到了一种全排列
            if not choices:
                res.append(path)
                return
            for i in range(len(choices)):
                # 做选择
                backtrack(path + [choices[i]], choices[:i] + choices[i+1:])
                
        res = []
        backtrack([], nums)
        return res

执行效率

截屏2024-02-17 22.23.18.png

【总结】

适用问题类型

回溯算法适用于需要遍历所有可能情况来寻找所有解的问题,特别是在解空间为树状结构时。它广泛应用于排列、组合、选数、棋盘问题(如八皇后问题)等,其中全排列问题是最直观的应用之一。

解决算法:回溯算法

  • 核心思想:深度优先搜索(DFS)加上状态重置。通过逐步构建解决方案的方式,并在确认当前解决方案不可行时撤销上一步或几步的操作(即回溯),继续尝试其他可能的解决方案。
  • 算法流程:定义两个关键概念——路径(已经做出的选择)和选择列表(当前可以做的选择)。算法通过递归地进行选择和回溯,每到达决策树的一层,都对所有选项进行遍历,尝试放入路径中,并进入下一层决策。

算法特点

  • 通用性:回溯算法框架可以解决多种类型的搜索问题,通过修改状态空间的遍历策略,可以应用于不同的问题中。
  • 灵活性:通过改变参数和状态定义,可以轻易地对算法进行调整,以适应不同的问题需求。
  • 效率性:虽然回溯算法的时间复杂度可能达到指数级别,但通过剪枝(如排除不可能的选择)可以大幅度减少搜索空间,提高算法效率。

算法优化与实践意义

  • 优化策略:在实际应用中,优化回溯算法的关键在于如何有效剪枝。例如,在全排列问题中,可以通过记录已经选择的元素来避免重复选择,或者利用问题的特性来减少不必要的递归调用。
  • 实践意义:回溯算法不仅对算法竞赛选手有重要的意义,也在软件开发、人工智能、运筹学等领域有广泛的应用。掌握回溯算法,能够帮助开发者更好地解决那些需要系统地探索多种可能性的问题。

总之,回溯算法通过一个通用的框架提供了一种强大的解决方案,用以处理需要穷举所有可能性的问题。虽然面对特别大的解空间时效率可能不高,但通过智能地剪枝,它能够在可接受的时间内解决复杂的问题,展示了算法设计中的美学和力量。

题目链接

全排列