每日一题 -- 树
894. 所有可能的满二叉树
分析
- 对于满二叉树而已,结束条件就是如果给的 N 是偶数,就返回[]
- 这里要求返回所有符合要求的满二叉树,就是要返回一个含有根节点的数组,那么什么时候可以造树呢
- 每一次递归都是返回数组的,那么 N 这个值也是可以随着二叉树的深度而减小的,知道 N 为0和1的时候就可以出现单个子树了
- 然后自底向上的给出每一个 N 值的递归函数返回的可能的子树节点,然后再拼装起来即可
// https://leetcode-cn.com/problems/all-possible-full-binary-trees/description/
// 894. 所有可能的满二叉树
var allPossibleFBT = function (N) {
if (N % 2 === 0) return []
if (N === 1) return [new TreeNode(0)]
const res = []
for (let i = 0; i < N; i++) {
const left_num = i, right_num = N - i - 1
const lefts = allPossibleFBT(left_num)
const rights = allPossibleFBT(right_num)
if(lefts.length && rights.length){
lefts.forEach(left => {
rights.forEach(right => {
const root = new TreeNode(0)
root.left = left
root.right = right
res.push(root)
})
})
}
}
return res
};
91算法 -- 背包问题
416. 分割等和子集(补21)
21 的时候没做出来超时了,这次看了题解做的。。
深度遍历
- 树的老司机一看,dfs然后配合回溯,解决,啪一声,超时
- 一开始其实 dfs 只带一个 sum 参数,后面发现超时了,就想两个袋子都带着,只要有一个袋子超了就关闭,这样剪枝了可能就过来呢。
- 然后啪的一声,还是挂了
// 416. 分割等和子集
// https://leetcode-cn.com/problems/partition-equal-subset-sum/
/**
* 深度遍历+回溯+剪枝的方式 --- 超时了
* @分析 :
* 1. 先求总和,然后根据奇偶来判断并求出真正需要分割出来的 target 值
* 2. 一开始的时候只用了一个袋子来装球,把剩下的当另外一个袋子,但是这样剪枝优化相当于少了一半
*/
var canPartition = function (nums) {
const rest = nums.reduce((pre, cur) => pre + cur, 0) // 一次遍历
if (rest % 2) return false // 如果是奇数,则不能平分
const target = rest/2 //取中间值
nums=nums.sort((a,b) => b-a) //递减
if(target<nums[0]) return false;
const dfs = (index, left,right) => {
if (left > target || right > target || index === nums.length) return false
if (left === target || right === target) {
return true
}
return dfs(index + 1, left + nums[index],right) || dfs(index + 1, left,right+nums[index])
}
return dfs(0, 0)
};
dp
- y1s1,变形的 dp 是真的我最怕的题,然后这个看着答案,想了半天也没搞懂,最后还算是捋清楚了
- dp[x][y] 这两个状态的定义一直有点懵逼,在这里就是当取到下标为 x 的 nums 的值时,目标值 y 能否取到
- dp[x][y] 这个状态是由上一个状态决定的,如果 nums[x] <= y 的时候,上一个状态就有两种情况可以到达,
- 一种是 dp[x-1][y] 直接目标值都没变就过来了
- 另外一个中是 dp[x-1][y-nums[x]],这一次是加上了值才到达的,
- 这两种状态主要有一种是 true,这当前状态就是 true
var canPartition = function (nums) {
const rest = nums.reduce((pre, cur) => pre + cur, 0) // 一次遍历
if (rest % 2) return false // 如果是奇数,则不能平分
const target = rest >>> 1 //取中间值
const len = nums.length
let dp = Array.from(nums).map(()=> {
return new Array(target+1).fill(false)
})
for(let i =0;i<len;i++){
// 如果要去值为 0,不取即可,当然可以实现
// 这里也是 dp 能够判断符合条件的最初条件,即能够使得获取的值达到 target
dp[i][0] = true
}
for(let i=1;i<len;i++){
for(let j = 1;j<target+1;j++){
// 判断当前值能不能取
// 能取则有两种情况,取 nums[i] 达到当前这个状态,或者上一次已经是这个状态了
dp[i][j] = j-nums[i]>=0?dp[i-1][j] || dp[i-1][j-nums[i]]:dp[i-1][j]
}
}
return dp[len-1][target]
}
dp 优化
- 每一次状态都是跟前面一次做对比,所以其实下标的状态是可以省略降维的
- 由于我们的值是从高往低取,所以值 j 也要从高往低取,保证 dp[j-xxx] 的值是上一个下标的状态
- 然后就结束了
var canPartition = function (nums) {
const rest = nums.reduce((pre, cur) => pre + cur, 0) // 一次遍历
if (rest % 2) return false // 如果是奇数,则不能平分
const target = rest >>> 1 //取中间值
const len = nums.length
let dp = new Array(target+1).fill(false)
dp[0] = true
for(let i=0;i<len;i++){
for(let j = target+1;j>0;j--){
// 每一次都更新一下当前下标下的状态,由于值状态是由高往低差的,所以值遍历也应该从高往低走
// 保证取到的状态是上一次的
dp[j] = j - nums[i]>=0?dp[j] || dp[j-nums[i]]:dp[j]
}
}
return dp[target]
}