前端就该用 JS 刷算法23

119 阅读3分钟

每日一题 -- 树

894. 所有可能的满二叉树

894. 所有可能的满二叉树

分析

  1. 对于满二叉树而已,结束条件就是如果给的 N 是偶数,就返回[]
  2. 这里要求返回所有符合要求的满二叉树,就是要返回一个含有根节点的数组,那么什么时候可以造树呢
  3. 每一次递归都是返回数组的,那么 N 这个值也是可以随着二叉树的深度而减小的,知道 N 为0和1的时候就可以出现单个子树了
  4. 然后自底向上的给出每一个 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)

416. 分割等和子集

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]
}