前端就该用 JS 刷算法10

100 阅读4分钟

每日一题 -- 树

二叉树中的最大路径和

124. 二叉树中的最大路径和

节点最大值和返回值不是一个值

  • 本题最需要注意的一点是,路径是可以从任意一点出发的,但是不能分叉拐弯,就是不能走回头路,和我们以前玩的迷宫一样,遇到分岔路你只能选一个走,然后求到最后一个最大值
  • 用 dfs 递归的写法可以很好的求得每一个节点当前可以遇到的情况,当前值,左树的单边最大值,右树的单边最大值;需要注意的是,dfs 返回的必须是单边最大值,因为它要保证能和它自己的父节点连接得上。
  • 而我们在每一个 dfs 中还得做一个判断,就是当前节点的真正最大值,就是以当前节点为根节点,左右树任意选,不需要考虑再往父节点引的时候,和已经保存起来的 max 最大值作比较。
  • 最开始做题的时候,很容易将节点的 真正最大值 当做返回值返回出去了,但是实际上为了能连接成一条路径,只能返回 单边最大值

// https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/description/

/**
 * 
 * @分析
 * 1. 这里有一个点值得注意的是,最大的返回值,和当前可以和 max 比较的最大值不是一个值
 * 2. 返回值只能是一边的,这样才能保证父节点获取这个子树的时候是一根线没有分叉
 * 3. 但是比较最大值的时候,你可以两边获取,这是当前这个节点可以达到的最大值
 */
var maxPathSum = function(root) {
    // 这里用最大负数,之前层级手残用0,但是这里没说节点都是正数啊
    let max = -Infinity
    // 这里返回值是单边最大值,所以你看到我都没有直接 return dfs(root) ,就晓得它不是真正的最大值了
    const dfs = root => {
        if(!root) return 0
        // 求的是左右树的单边最大值
        const l = dfs(root.left)
        const r = dfs(root.right)
        // 当前节点的真正最大值,只要左右树的值大于0,我就加上
        max = Math.max(max,root.val+Math.max(l,0)+Math.max(r,0))
        // 当前节点的单边最大值有三种情况,自己,自己+left,自己+right
        return Math.max(0,l,r) + root.val
    }
    dfs(root)
    return max
};

91算法 -- 滑动窗口

69.找到字符串中所有字母异位词

69.找到字符串中所有字母异位词(完成01.09 遗留问题)

吐槽

  • 字母异位词指字母相同,但排列不同的字符串。,题目是这么讲的,但是实际上是相同的子串也算在里面了
  • 第一波没有做出来,最开始也是知道用滑窗,然后用 rest 记录剩余的值,但是 rest 用的是字符串
  • 然后出现两个需要记录的值的时候就懵逼,比方说子串是"aba" ,p 是 ”abc“,那么用字符串 rest 怎么表示,所以第一轮out了
  • 然后还是改用 map 计算剩余的的值,一开始犯同样的错了,用 restLen 来记录还剩余匹配的值
  • 所以有时候就是想着用更简单的方式去做,反而一下子没做出来,最后老老实实用最暴力的做出来,然后再慢慢处理

分析

  • 首先先将窗口全部防止到 rest 中,最初的时候想着遍历第一个窗口的时候顺便记录 rest,后面发现取巧就是坑自己
  • 然后初始化第一个窗口的时候,只要是符合 p 的字符都可以减去 rest 对应 key 的值
  • 开始滑动的时候,只需要判断left移出去的字符和 right加进来的字符;
  • 只有当 left 和 right 不相等的时候,才需要做处理(ps:最开始用 left=== right 就 continue,忘记了要记录 res)
  • 当 left 属于 p 的时候,就在 rest 加上 left,当 right 属于 p 的时候,需要在 rest 删除 right
  • 最后的判定条件是,map 中的值是否都为 0;是则往 res 中+1
// https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/

/**
 * 
 * @分析
 * 1. 窗口大小就是 p.length
 */
var findAnagrams = function (s, p) {
    const res = []
    const len = p.length // 窗口大小
    const rest = new Map() // 保存 p 的字节
    for (let i = 0; i < p.length; i++) {
        const value = rest.get(p[i]) ? rest.get(p[i]) + 1 : 1
        rest.set(p[i], value)
    }
    for (let i = 0; i < s.length; i++) {
        if (i < len) {
            // 初始化窗口
            if (rest.has(s[i])) {
                rest.set(s[i], rest.get(s[i]) - 1)
            }
        } else {
            const left = s[i - len]
            const right = s[i]
            if (left !== right) {
                // 已经形成窗口,每次移动一个位置,需要判断left是否符合 p
                if (rest.has(left)) {
                    rest.set(left, rest.get(left) + 1)
                }
                //  right 是否符合
                if (rest.has(right)) {
                    rest.set(right, rest.get(right) - 1)
                }
            }

        }
        const finished = [...rest.values()].filter(i => i).length
        if (!finished) {
            res.push(i - len + 1)
        }
    }
    return res
};