全排列,求所有子集--------回溯算法学习

1,求 {1,2,3} 数组的全排列。

每次回溯我们可以选择一个元素,也可以不选。每次回溯时都要对当前所有可能的选择依次进行遍历,全排列就是 从 0到 n-1 下标元素遍历所有没有被选择的元素一次,如果该元素已选择,那就跳过,如果没选就选择它,更新 visitedi=true,然后进行下次回溯;也可以不选这个元素,设置 visitedi==false,撤销上次选择结果 path = path:len(path)-1 ,然后进行下次回溯。回溯结束判断就是当前排列个数是否等于所有元素个数。

为什么在 for 里结束执行 backtrace(index + 1) 又撤销选择后不再次进行 backtrace(index + 1)?因为它会导致死循环且没有任何意义。当遍历某一个元素时,不选择,在下次回溯中它还是什么都不选,再在下次回溯中它还是什么都不选.......最终死循环,而且我们也不需要这个可能,在全排列中我们每次都必须选择一个,然后撤销选择,恢复原状,这就算是没选择这个元素 X 了,其他元素还可以在它们的回溯中继续选择 X。

Go 复制代码
func findAllPaiLie(nums []int) [][]int {
	if nums == nil || len(nums) == 0 {
		return make([][]int, 0)
	}
	var backtrace func(index int)
	var path []int
	var res [][]int
	visited := make([]bool, len(nums))

	backtrace = func(index int) {
		if len(path) == len(nums) {
			tmp := make([]int, len(nums))
			copy(tmp, path)
			res = append(res, tmp)
			return
		}
		for i := 0; i < len(nums); i++ {
			if !visited[i] {
				path = append(path, nums[i])
				visited[i] = true
				backtrace(index + 1)
				path = path[:len(path)-1]
				visited[i] = false
			}
		}
	}
	backtrace(0)
	return res
}

2,求 {1,2,3} 数组的 所有子集。

从下标 0 回溯到 n-1,可以选择这个元素,然后进行下一次回溯;也可以撤销上次遍历结果,什么都不选择就进行下次回溯。

它和求全排列的区别是在 backtrace() 里没有 for 循环,且执行两次 backtrace(index + 1)。为什么呢?因为即便记录了本次回溯哪些元素是否已被选择,都不是很重要,因为它是根据 index 从 0 到 n-1 线性回溯,每次回溯执行 两次 backtrace(index + 1) ,一共执行 2^n 次,它每次只有两种选择,使用 for 循环然后跳过已选择的元素就是多此一举,生搬硬套会产生很多重复的子集。

Go 复制代码
func findAllSubset(nums []int) [][]int {
	var res [][]int
	var subset []int
	var backtrace func(index int)
	backtrace = func(index int) {
		if index == len(nums) {
			tmp := make([]int, len(subset))
			copy(tmp, subset)
			res = append(res, tmp)
			return
		}
		subset = append(subset, nums[index])
		backtrace(index + 1)
		subset = subset[:len(subset)-1]
		backtrace(index + 1)
	}
	backtrace(0)
	return res
}

也可以改为经典的回溯算法模版,backtrace 内部使用 for 循环从 i 开始,backtrace 内部只执行一次 backtrace 而不是两次。

Go 复制代码
func findAllSubset3(nums []int) [][]int {
	var res [][]int
	var subset []int

	// 改造后的 backtrace 函数:index 表示当前可选元素的起始索引
	var backtrace func(index int)
	backtrace = func(index int) {
		// 进入递归就保存当前子集(每一步的 subset 都是有效子集)
		tmp := make([]int, len(subset))
		copy(tmp, subset)
		res = append(res, tmp)

		// 核心:for 循环遍历从 index 开始的所有元素
		for i := index; i < len(nums); i++ {
			// 1. 选择当前元素 nums[i]
			subset = append(subset, nums[i])
			// 2. 仅一次回溯调用:处理下一个起始位置(i+1),不再调用两次
			backtrace(i + 1)
			// 3. 回溯:撤销选择当前元素
			subset = subset[:len(subset)-1]
		}
	}

	backtrace(0)
	return res
}

总结:

这就是回溯的模板,它就是多叉树遍历算法,决策树遍历问题。

java 复制代码
func backTrack(参数) {
    if (终止条件) {
        存放结果;
        return;
    }
    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backTrack(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}
相关推荐
不会就选b2 分钟前
算法日常・每日刷题--<二分查找>1
算法
「維他檸檬茶」7 分钟前
大模型算法学习2026.6.13
学习·算法
叫我:松哥11 分钟前
基于Python的共享单车租赁数据分析与预测系统,技术栈flask+boostrap+随机森林+XGBoost
人工智能·python·深度学习·算法·随机森林·数据分析·flask
BAGAE23 分钟前
星链卫星数据获取:从太空安全到实时通信的技术革命
网络·数据结构·数据库·算法·云计算·hbase
happymaker062627 分钟前
LeetCodeHor100——438.找到字符串中所有的字母异位词
算法
西安邮电大学33 分钟前
有关栈的经典算法题
java·后端·其他·算法·面试
代码续发35 分钟前
AI Agent的学习记录
学习
h_a_o777oah1 小时前
【算法专项】扩展域并查集:原理详解及解决大部分种类并查集问题(洛谷P5937 P2024 C++代码)
数据结构·c++·算法·acm·并查集·扩展域·逻辑建模
兰令水1 小时前
leecodecode【单调栈】【2026.6.12打卡-java版本】
java·开发语言·算法
TMT星球1 小时前
魔法原子上交会首秀VLA K02大模型,完成具身智能从“执行”到“理解”的能力跃迁
人工智能·算法·机器学习