# DFS、BFS

  • 深度优先遍历
  • 广度优先遍历
  • 求根到叶子节点数字之和(DFS)
  • 路径总和
  • 岛屿数量
  • 岛屿最大面积
  • 单词接龙(BFS)
  • N叉树的最大深度(BFS)
  • 打家劫舍三(DFS)

# 深度优先,广度优先(层次遍历)

//深度优先的递归实现
const deepTraversal = function(root) {
    const res = [];
    const dfs = function(node) {
        res.push(node.val);
        for (let i = 0; i < node.children.length; i++) {
            dfs(node.children[i]);
        }
    }
    dfs(root);
    return res;
}

// 深度优先非递归
const deepTraversal = function(root) {
    const res = [];
    const stack = [root];
    while (stack.length) {
        const tmp = stack.pop();
        res.push(tmp.val);
        for (let i = tmp.children.length - 1; i >= 0; i--) {
            stack.push(tmp.children[i]);
        }
    }
    return res;
};

// 广度非递归
const wideTraversal = function(root) {
    const res = [];
    const queue = [root];
    while (queue.length) {
        const tmp = queue.shift();
        res.push(tmp.val);
        for (let i = 0; i < tmp.children.length; i++) {
            queue.push(tmp.children[i]);
        }
    }
    return res;
};

# 求根到叶子节点数字之和

给定一个二叉树,它的每个结点都存放一个 0-9 的数字,每条从根到叶子节点的路径都代表一个数字。 例如,从根到叶子节点路径 1->2->3 代表数字 123。 计算从根到叶子节点生成的所有数字之和。 说明: 叶子节点是指没有子节点的节点。

示例 1: 输入: [1,2,3] 1 /
2 3 输出: 25 解释: 从根到叶子节点路径 1->2 代表数字 12. 从根到叶子节点路径 1->3 代表数字 13. 因此,数字总和 = 12 + 13 = 25.

示例 2: 输入: [4,9,0,5,1] 4 /
9 0 /
5 1 输出: 1026 解释: 从根到叶子节点路径 4->9->5 代表数字 495. 从根到叶子节点路径 4->9->1 代表数字 491. 从根到叶子节点路径 4->0 代表数字 40. 因此,数字总和 = 495 + 491 + 40 = 1026.

  • 从根节点开始,遍历每个节点,如果遇到叶子节点,则将叶子节点对应的数字加到数字之和。如果当前节点不是叶子节点,则计算其子节点对应的数字,然后对子节点递归遍历。
var sumNumbers = function(root) {
    const dfs = function(root, num) {
        // 若节点是 null 直接返回 0
        if (!root) {
            return 0;
        }
        // 不是空节点就计算一下加到目前的值
        num = num * 10 + root.val;
        // 若是根节点返回该计算值,一条路就走完了
        if (root.left === null && root.right === null) {
            return num;
        }
        // 不是根节点则递归遍历
        else {
            return dfs(root.left, num) + dfs(root.right, num);
        }
    }
    return dfs(root, 0);
};

// 另一种写法
var sumNumbers = function(root) {
    let res = 0;
    const fn = function(root, sum) {
        // 到叶子节点即结算一次
        if (!root.left && !root.right) {
            res += sum;
            return;
        }
        if (root.left) {
            fn(root.left, sum * 10 + root.left.val);
        }
        if (root.right) {
            fn(root.right, sum * 10 + root.right.val);
        }
    };
    fn(root, root.val);
    return res;
}

# 路径总和

var hasPathSum = function(root, targetSum) {
    const res = [];
    const dfs = function(root, num) {
        if (!root) {
            return;
        }
        num = num + root.val;
        if (root.left === null && root.right === null) {
            res.push(num);
        }
        else {
            dfs(root.left, num);
            dfs(root.right, num);
        }
    }
    dfs(root, 0);
    return res.includes(targetSum);
};

// 另一种写法
var hasPathSum = function(root, targetSum) {
    if (!root) return false;
    const res = [];
    const fn = function(root, sum) {
        if (!root.left && !root.right) {
            res.push(sum);
        }
        if (root.left) {
            fn(root.left, sum + root.left.val);
        }
        if (root.right) {
            fn(root.right, sum + root.right.val);
        }
    };
    fn(root, root.val);
    return res.includes(targetSum);
}

# 岛屿数量

var numIslands = function(grid) {
    const m = grid.length;
    const n = grid[0].length;
    const dfs = function(i, j) {
        if (i < 0 || j < 0 || i > m - 1 || j > n - 1 || grid[i][j] === '0') return;
        grid[i][j] = '0';
        dfs(i - 1, j);
        dfs(i + 1, j);
        dfs(i, j + 1);
        dfs(i, j - 1);
    };

    let count = 0;
    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            if (grid[i][j] === '1') {
                dfs(i, j);
                count++;
            }
        }
    }
    return count;
};

# 岛屿最大面积

var maxAreaOfIsland = function(grid) {
    const m = grid.length;
    const n = grid[0].length;
    const dfs = function(i, j) {
        if (i < 0 || j < 0 || i > m - 1 || j > n - 1 || grid[i][j] === 0) {
            return 0;
        }
        grid[i][j] = 0;
        let area = 1;
        area += dfs(i - 1, j);
        area += dfs(i + 1, j);
        area += dfs(i, j + 1);
        area += dfs(i, j - 1);
        return area;
    };

    let res = 0;
    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            if (grid[i][j] === 1) {
                res = Math.max(dfs(i, j), res);
            }
        }
    }
    return res;
};

# 单词接龙

给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:

每次转换只能改变一个字母。 转换过程中的中间单词必须是字典中的单词。 说明:

如果不存在这样的转换序列,返回 0。 所有单词具有相同的长度。 所有单词只由小写字母组成。 字典中不存在重复的单词。 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。

示例 1: 输入: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"] 输出: 5 解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。

示例 2: 输入: beginWord = "hit" endWord = "cog" wordList = ["hot","dot","dog","lot","log"] 输出: 0 解释: endWord "cog" 不在字典中,所以无法进行转换。

  • 抽象为图,若两个单词可以转换,则连一条双向边,BFS求起点到终点最短路径
  • 优化为:求图的最短路径,使用队列 + BFS 实现
  • 参考题解:BFS的应用 (opens new window)
var ladderLength = function(beginWord, endWord, wordList) {
    if (!wordList.includes(endWord)) return 0
    let queue = []
    // 用一个对象维护当前节点是否访问过
    let visitedObj = {
        beginWord: true
    }
    // 用 level 记录当前路径长度
    queue.push({ word: beginWord, level: 1 })
    // 队列实现 BFS
    while (queue.length) {
        let { word, level } = queue.shift()
        if (visitedObj[word]) continue
        for (let i = 0; i < wordList.length; i++) {
            // 若只有一个字母不同则证明是相邻两边,入队列
            if (isOneDiff(word, wordList[i])) {
                if (wordList[i] == endWord) {
                    return level + 1
                }
                queue.push({ word: wordList[i], level: level + 1 })
                visitedObj[word] = true
            }
        }
    }
    return 0
}


// 判断两个单词是否只有一个字母不同
function isOneDiff(source, target) {
    let diff = 0
    for (let i = 0; i < source.length; i++) {
        if (source[i] !== target[i]) {
            diff ++
        }
    }
    if (diff === 1) {
        return true
    } else {
        return false
    }
}

# N叉树的最大深度

给定一个 N 叉树,找到其最大深度。

最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。

// BFS
var maxDepth = function(root) {
    if (root === null) return 0
    if (root.children.length === 0) return 1
    let queue = []
    let depthArr = []
    queue.push({ node: root, depth: 1 })
    while (queue.length) {
        const { node, depth } = queue.shift()
        for (let i = 0; i < node.children.length; i++) {
            if (node.children[i].children.length === 0) {
                depthArr.push(depth + 1)
                continue
            }
            queue.push({ node: node.children[i], depth: depth + 1 })
        }
    }
    return Math.max(...depthArr)
};

// 递归
var maxDepth = function(root) {
    if (!root) {
        return 0;
    }
    let depth = 0;
    for (let i = 0; i < root.children.length; i++) {
        depth = Math.max(depth, maxDepth(root.children[i]));
    }
    return 1 + depth;
};

# 打家劫舍iii(DFS)

  • Leetcode337 (opens new window)
  • 每个节点都设置:[不偷, 偷]
  • 当前节点被偷时,其左右孩子不能偷
  • 当前节点未被偷时,其左右孩子可以偷,也可以不偷,取 [不偷, 偷] 的较大者
var rob = function(root) {
    let res = dfs(root);
    return Math.max(res[0], res[1]);
};

function dfs(root) {
    // [不偷, 偷]
    let res = [0,0];
    if (root === null) return res;
    // left 为 root 左侧子节点的[不偷值, 偷值]
    let left = dfs(root.left); 
  	// right 为 root 右侧子节点的[不偷值, 偷值]
    let right = dfs(root.right); 
    // 每一个节点的不偷值都是: 左侧子节点的最大值 + 右侧子节点的最大值
    res[0] = Math.max(...left) + Math.max(...right);
    // 每一个节点的偷值都是:左侧子节点的不偷值 + 右侧子节点的不偷值 + 该节点的值
    res[1] = root.val + left[0] + right[0];
    return res;
}
最后更新时间: 3/31/2023, 5:00:31 PM