可视化图解算法54:没有重复项数字的全排列

0 阅读5分钟

牛客网 面试笔试 TOP101 | LeetCode 46. 全排列

1. 题目

描述

给出一组没有重复的数字,返回该组数字的所有排列

例如:

[1,2,3]的所有排列如下 [1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2], [3,2,1]. (以数字在数组中的位置靠前为优先级,按字典序排列输出。)

数据范围:数字个数 0 <n≤6

要求:空间复杂度 O(n!),时间复杂度 O(n!)

示例1

输入:

[1,2,3]

返回值:

[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例2

输入:

[1]

返回值:

[[1]]

2. 解题思路

从本题开始,讲解回溯算法,下面需要重点来看一下什么是回溯算法:

回溯算法是一种通过试错法(试探+回退)解决约束满足问题的通用策略,常用于解决组合优化、排列、子集生成等问题。其核心思想是逐步构建解,若发现当前路径无法达成目标,则回退到上一步尝试其他可能性。


核心思想

  1. 试探:按条件扩展当前解的候选可能性。
  2. 验证:若当前路径不满足约束,立即放弃(剪枝)。
  3. 回退:撤销上一步选择,回到之前的状态继续尝试其他路径。

适用场景

  • 组合问题:如子集、排列、全排列(如 n 选 k)。
  • 约束满足问题:如数独、八皇后、数独。
  • 穷举搜索:当问题规模较小时,回溯可遍历所有可能解。

操作

也就是说解决一个回溯问题,实际上就是一个决策树的遍历过程。在这个过程中只需要思考三个问题: (1)路径:也就是已经做出的选择; (2)选择列表:也就是你当前可以做的选择; (3)结束条件:也就是到达决策树底层,无法再做选择的条件。

回溯算法模板:

result = [] #结果集
path=[] #路径
def backtracking(选择列表):
	# 2.递归终止条件
    if 满足结束条件:
		# 2.1 存放结果
        result.add(满足条件的路径) 
		# 2.2 返回 		
		return
	# 1.选择:在本层集合中遍历元素
	for 选择 in 选择列表:
		# 1.1 处理节点:做出选择
        做选择 
        # 1.2 递归(缩小数据范围。数据范围缩小到一定程度,会触发递归终止条件)
        backtracking(选择列表)
		# 1.3 回溯,撤销选择
        撤销选择 

注:核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」

回溯结构如下图所示:

回溯结构.png

对回溯算法有了了解之后,就可以套用回溯算法模板完成数字的全排列了,具体思路如下:

54-1.png

如果文字描述的不太清楚,你可以参考视频的详细讲解。

3. 编码实现

核心代码如下:

/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 *
 *
 * @param num int整型一维数组
 * @return int整型二维数组
 */
func permute(num []int) [][]int {
	// write code here
	result = make([][]int, 0)
	backtracking(num, 0)
	return result
}

var result [][]int

func backtracking(num []int, index int) {
	// 2.递归终止条件:分枝进入结尾,找到一种排列
	if index == len(num)-1 {
		//2.1 存放结果
		tmp := append([]int{}, num...) //将num结果复制一份(新建一个切片,如果是引用原来切片,是地址引用,num内容改变时,result数据也会发生改变)
		result = append(result, tmp)
		//2.2 返回
		return
	}

	//1.选择:在本层集合中遍历元素
	for i := index; i < len(num); i++ {
		//1.1 处理节点:元素互换【如:1,2,3(1放到最前面:1与1交换);2,1,3(2放到最前面:2与1交换);3,2,1(3放到最前面:3与1交换)】
		swap(num, i, index)
		// 1.2 递归:缩小数列区间【对第i+1个再进行相似的操作(递归),如1,2,3(1固定,再对剩余的2,3进行类似的操作)】
		backtracking(num, index+1)
		//1.3 回溯,撤销选择:将互换的元素还原【将交换的数据变换回来,再进行下一轮操作】
		swap(num, i, index)
	}
}

func swap(num []int, i int, index int) {

	num[i], num[index] = num[index], num[i]
}

具体完整代码你可以参考下面视频的详细讲解。

4.小结

数字项的全排列是回溯算法的典型应用,这时可以直接套用回溯算法模板完成。具体操作流程为:

  1. 选择:在本层集合中遍历元素。
    • 处理节点:做出选择;
    • 递归(缩小数据范围。数据范围缩小到一定程度,会触发递归终止条件);
    • 回溯,撤销选择。
  2. 递归终止。
    • 存放结果;
    • 返回。

分割线.png

《数据结构与算法》深度精讲课程正式上线啦!7 大核心算法模块全解析:

  ✅   链表

  ✅   二叉树

  ✅   二分查找、排序

  ✅   堆、栈、队列

  ✅   回溯算法

  ✅   哈希算法

  ✅   动态规划

无论你是备战笔试面试、提升代码效率,还是突破技术瓶颈,这套课程都将为你构建扎实的算法思维底座。🔥立即加入学习打卡,与千名开发者共同进阶!

对于数据结构与算法,我们总结了一套【可视化+图解】方法,依据此方法来解决相关问题,算法变得易于理解,写出来的代码可读性高也不容易出错。具体也可以参考视频详细讲解。

今日佳句:独学而无友,则孤陋而寡闻。