提示:努力生活,开心、快乐的一天
文章目录
491. 递增子序列
💡解题思路
- 有序的递增子序列:所以不能对原数组进行排序,原来的去重不可使用,依旧是树层去重,树枝不去重;递增子序列中 至少有两个元素:所以取所有节点后,需要加一层判断
- 回溯三部曲
- 递归函数参数:startIndex,调整下一层递归的起始位置
- 终止条件:可以不加终止条件,startIndex每次都会加1,并不会无限递归,但本题收集结果有所不同,题目要求递增子序列大小至少为2
- 单层搜索逻辑:同一父节点下的同层上使用过的元素就不能再使用了所以,定义一个数组,对本层元素进行去重,还有一层判断,就是递增子序列,所以当前的nums[i]<path[path.length - 1]
🤔遇到的问题
- 终止条件:收集结果的时候,不能加return,因为要取树上的所有节点
- 因为原数组不是有序的,所以,不能使用原来的去重方式
💻代码实现
回溯
var findSubsequences = function (nums) {
let result = []
let path = []
const backtracking = (startIndex) => {
//递增子序列大小至少为2
if (path.length > 1) {
result.push([...path])
// 注意这里不要加return,因为要取树上的所有节点
}
//可以不加终止条件,startIndex每次都会加1,并不会无限递归
if (startIndex >= nums.length) return
//该数组对本层元素进行去重
let uset = []
for (let i = startIndex; i < nums.length; i++) {
//这次排序的去重,但此题是不能进行提前排序的,否则,所有的子序列就都是递增子序列了
// if (i > startIndex && nums[i] === nums[i - 1]) continue
//出现过的数字,放在数组的该位置上,+100的原因是,题目要求-100 <= nums[i] <= 100
if ((path.length > 0 && nums[i] < path[path.length - 1]) || uset[nums[i]+100]) continue
//出现过的数字,该位置置为true
uset[nums[i] + 100] = true
path.push(nums[i])
backtracking(i + 1)
path.pop()
//不需要对uset做回溯,因为uset定义在递归中,每次循环都是刷新,只记录该层的数据
}
}
backtracking(0)
return result
};
🎯题目总结
1.不能一直套用模版啦,因为此题不能排序,所以去重的方式需要进行替换,另外增加了别的判断条件
46. 全排列
💡解题思路
- 排列是有序的
- 回溯三部曲
- 递归函数参数:定义两个全局变量,二维数组result存放结果集,数组path存放符合条件的结果,因为是排列,有序的,每次从头取值,不需要startIndex,但需要一个数组used,标记已经选择的元素
- 递归终止条件:叶子节点,就是收割结果的地方。当收集元素的数组path的大小达到和nums数组一样大的时候,说明找到了一个全排列,也表示到达了叶子节点
- 单层搜索的逻辑:排列问题,每次都要从头开始搜索;used数组,其实就是记录此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次
🤔遇到的问题
- used数组也需要做回溯
💻代码实现
回溯
var permute = function (nums) {
let result = []
let path = []
//used数组,其实就是记录此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次
const backtracking = (uesed) => {
//当收集元素的数组path的大小达到和nums数组一样大的时候,
//说明找到了一个全排列,也表示到达了叶子节点
if (path.length === nums.length) {
result.push([...path])
return
}
//排列问题,每次都要从头开始搜索
for (let i = 0; i < nums.length; i++) {
if (uesed[i]) continue // path里已经收录的元素,直接跳过
uesed[i] = true
path.push(nums[i])
backtracking(uesed)
path.pop()
uesed[i] = false
}
}
backtracking([])
return result
};
🎯题目总结
排列的特点:
- 每层都是从0开始搜索而不是startIndex
- 需要used数组记录path里都放了哪些元素了
47. 全排列 II
💡解题思路
- 这道题目和46.全排列的区别在与给定一个可包含重复数字的序列,要返回所有不重复的全排列。去重一定要对元素进行排序,这样我们才方便通过相邻的节点来判断是否重复使用了
- 树层去重
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
🤔遇到的问题
- 去重,必须先去重
💻代码实现
回溯法
var permuteUnique = function (nums) {
let result = []
let path = []
nums.sort((a, b) => a - b)//去重必须先排序
const backtracking = (used) => {
if (path.length === nums.length) {
result.push([...path])
return
}
for (let i = 0; i < nums.length; i++) {
//树层去重
if (i > 0 && nums[i] === nums[i - 1] && !used[i - 1]) continue
if (used[i]) continue
used[i] = true
path.push(nums[i])
backtracking(used)
path.pop()
used[i] = false
}
}
backtracking([])
return result
};
🎯题目总结
- 一般来说:组合问题和排列问题是在树形结构的叶子节点上收集结果,而子集问题就是取树上所有节点的结果。
- 对于排列问题,树层上去重和树枝上去重,都是可以的,但是树层上去重效率更高!
树层上去重(used[i - 1] == false)
树枝上去重(used[i - 1] == true)
既然 used[i - 1] == false也行而used[i - 1] == true也行,那为什么还要写这个条件呢?NoNo
一定要加上 used[i - 1] == false或者used[i - 1] == true,因为 used[i - 1] 要一直是 true 或者一直是false 才可以,而不是 一会是true 一会又是false。 所以这个条件要写上
🎈今日心得
排列问题,初步接触,还是有自己没考虑到的点