[{"data":1,"prerenderedAt":2472},["ShallowReactive",2],{"\u002F2026":3,"surround-\u002F2026":2462},{"id":4,"title":5,"body":6,"categories":2342,"date":2344,"description":2345,"draft":2346,"extension":2347,"image":2348,"meta":2349,"navigation":2446,"path":2447,"permalink":2348,"published":2348,"readingTime":2448,"recommend":2348,"references":2348,"seo":2453,"sitemap":2455,"stem":2456,"tags":2457,"type":2460,"updated":2344,"__hash__":2461},"content\u002Fposts\u002F2026\u002F[图论]-广度优先遍历及其拓展.md","图论-广度优先遍历及其拓展",{"type":7,"value":8,"toc":2287},"minimark",[9,13,20,23,55,75,93,96,100,130,134,171,201,223,226,243,246,249,288,459,461,465,472,475,485,493,497,508,512,658,660,664,673,676,683,747,751,762,766,782,784,788,970,973,979,1009,1012,1016,1029,1040,1044,1059,1079,1081,1085,1113,1116,1210,1214,1227,1230,1258,1266,1276,1279,1285,1288,1294,1303,1306,1312,1315,1321,1324,1330,1333,1338,1342,1637,1640,1671,1675,1678,1689,1693,1781,1876,1878,1882,1888,1892,1898,1902,1913,1917,1920,1940,1944,1965,1974,1976,1980,1986,1991,1998,2008,2010,2014,2020,2033,2041,2187,2189,2192,2277,2282,2284],[10,11,12],"h2",{"id":12},"引言",[14,15,16],"quote",{},[17,18,19],"p",{},"广度优先遍历（Breadth-First Search, BFS）是图论和树论中基本的查找搜索算法，是广大图算法的基石。",[17,21,22],{},"BFS 的细节和可玩性极高。它是很多上层算法的原型：",[24,25,26],"card-list",{},[27,28,29,37,43,49],"ul",{},[30,31,32,33],"li",{},"拓扑排序的 ",[34,35,36],"strong",{},"Kahn 算法",[30,38,39,40],{},"最小生成树的 ",[34,41,42],{},"Prim 算法",[30,44,45,46],{},"单源最短路径的 ",[34,47,48],{},"Dijkstra 算法",[30,50,51,52],{},"网络流的 ",[34,53,54],{},"Edmonds-Karp 算法",[56,57,60],"alert",{"title":58,"type":59},"本篇涵盖","info",[17,61,62,63,68,69,68,72],{},"朴素 BFS、按层 BFS、多源 BFS、01-BFS、双向 BFS、剪枝策略，外加两个经典应用：连通分量、判环。\n",[64,65,67],"badge",{":round":66},"true","Java"," ",[64,70,71],{":round":66},"图论",[64,73,74],{":round":66},"搜索",[56,76,79],{"title":77,"type":78},"前置知识","warning",[17,80,81,84,85,88,89,92],{},[34,82,83],{},"数据结构","：队列、双端队列。\n",[34,86,87],{},"树","：经典 BFS、按层 BFS（即树的层序遍历）。\n",[34,90,91],{},"编程语言","：由于该文最早在CSDN写下， 那个时候我使用 Java ，因此本文主要使用 Java 演示， 补充C++",[94,95],"hr",{},[10,97,99],{"id":98},"bfs-的用途与适用条件","BFS 的用途与适用条件",[24,101,102],{},[27,103,104,107,110,117,127],{},[30,105,106],{},"遍历图中所有的顶点",[30,108,109],{},"求两点之间所有的路径",[30,111,112,113,116],{},"求两点之间的",[34,114,115],{},"最短路径","（限定条件，下面详述）",[30,118,119,120,123,124],{},"求图的",[34,121,122],{},"连通性","与",[34,125,126],{},"连通分量",[30,128,129],{},"各种图算法（Kahn、Prim、Dijkstra…）的底层支持",[131,132,133],"h3",{"id":133},"求最短路径的适用条件",[56,135,138,145,168],{"title":136,"type":137},"🚨 必须记住的限制","error",[17,139,140,141,144],{},"BFS 求最短路径的适用范围",[34,142,143],{},"比 Dijkstra\u002FSPFA\u002FBellman-Ford 都要小","：",[146,147,148,154],"ol",{},[30,149,150,153],{},[34,151,152],{},"无权图"," → 等价于求最短边数。",[30,155,156,159,160,163,164,167],{},[34,157,158],{},"带权图"," → 边权必须",[34,161,162],{},"非负","且",[34,165,166],{},"所有边权相同","（不要求统一为 1，但必须全部相等）。",[17,169,170],{},"不满足以上条件，请绕道 Dijkstra 等更通用的最短路径算法。",[14,172,173,174,177,178,180,184,188,192,195,198],{},"BFS 的\"最短\"本质上是",[34,175,176],{},"层数最浅","——从源点出发先扩展到的点一定层数最浅，这是它的根基。",[94,179],{},[10,181,183],{"id":182},"朴素-bfs","朴素 BFS",[131,185,187],{"id":186},"图解从节点-f-出发","图解：从节点 f 出发",[10,189,191],{"id":190},"bfs-的扩散过程","BFS 的扩散过程",[17,193,194],{},"先看 f 的邻居：\nb、e、c、g。",[17,196,197],{},"再看这四个的邻居：\na、d 浮出水面。",[17,199,200],{},"f 是第一层，\nb·e·c·g 是第二层，\na、d 是第三层——\n广度优先树就此长成。",[56,202,204],{"title":203,"type":59},"核心容器",[27,205,206,212],{},[30,207,208,211],{},[34,209,210],{},"队列","：先进先出，保证近的节点先被处理",[30,213,214,222],{},[34,215,216,217,221],{},"哈希集合 \u002F ",[218,219,220],"code",{"code":220},"visited"," 数组","：记忆化，防止重复访问，避免环中死循环",[131,224,225],{"id":225},"算法流程",[24,227,228],{},[27,229,230,237,240],{},[30,231,232,233,236],{},"给定源节点 ",[218,234,235],{"code":235},"s","，加入队列，标记为已访问",[30,238,239],{},"弹出队头节点，扫描它的所有邻居：未访问的入队并标记",[30,241,242],{},"重复直至队列为空；弹出时可执行任意处理（打印、收集、更新答案……）",[131,244,245],{"id":245},"三种建图方式实现",[17,247,248],{},"任选一种你喜欢的方式即可。三种方式数据结构不同，但 BFS 的主体逻辑完全一致。",[250,251,253,268,278],"tab",{":tabs":252},"[\"链式前向星\", \"邻接矩阵\", \"邻接表\"]",[254,255,257],"template",{"v-slot:tab1":256},"",[258,259,266],"pre",{"className":260,"code":262,"filename":263,"language":264,"meta":265},[261],"language-java","package BFS;\n\npublic class Code01_BFS {\n    public static int MAXN = 1001;\n    public static int MAXM = 2003;\n    public static int[] head = new int[MAXN];\n    public static int[] next = new int[MAXM];\n    public static int[] to = new int[MAXM];\n    public static int cnt;\n    public static int[] queue = new int[MAXN];\n    public static int l, r;\n    public static boolean[] visited = new boolean[MAXN];\n\n    \u002F\u002F 把 0 编号空出来\n    public static void build(int n) {\n        for (int i = 1; i \u003C= n; i++) {\n            head[i] = 0;\n            visited[i] = false;\n        }\n        cnt = 1;\n    }\n\n    \u002F\u002F 添加一条有向边 u -> v\n    \u002F\u002F 无向边就交换参数多调一次\n    public static void addEdge(int u, int v) {\n        next[cnt] = head[u];\n        to[cnt] = v;\n        head[u] = cnt++;\n    }\n\n    public static void bfs(int start) {\n        if (start == 0) return;\n        queue[r++] = start;\n        visited[start] = true;\n        while (l != r) {\n            int cur = queue[l++];\n            System.out.print(cur + \" \");\n            for (int ei = head[cur]; ei != 0; ei = next[ei]) {\n                int dst = to[ei];\n                if (!visited[dst]) {\n                    queue[r++] = dst;\n                    visited[dst] = true;\n                }\n            }\n        }\n    }\n\n    public static void main(String[] args) {\n        build(6);\n        addEdge(1, 2); addEdge(1, 3);\n        addEdge(2, 4); addEdge(2, 5);\n        addEdge(3, 6);\n        System.out.println(\"BFS traversal starting from node 1:\");\n        bfs(1);\n    }\n}\n","Code01_BFS.java","java","icon=tabler:list-tree",[218,267,262],{"__ignoreMap":256},[254,269,270],{"v-slot:tab2":256},[258,271,276],{"className":272,"code":273,"filename":274,"language":264,"meta":275},[261],"package BFS;\n\npublic class Code02_BFS_Matrix {\n    public static int MAXN = 101;\n    public static int[][] graph = new int[MAXN][MAXN];\n    public static int[] queue = new int[MAXN];\n    public static int l, r;\n    public static boolean[] visited = new boolean[MAXN];\n    public static int n;\n\n    public static void build() {\n        for (int i = 1; i \u003C= n; i++) {\n            for (int j = 1; j \u003C= n; j++) graph[i][j] = 0;\n            visited[i] = false;\n        }\n    }\n\n    public static void addEdge(int u, int v) {\n        graph[u][v] = 1;\n    }\n\n    public static void bfs(int start) {\n        if (start == 0) return;\n        queue[r++] = start;\n        visited[start] = true;\n        while (l != r) {\n            int cur = queue[l++];\n            System.out.print(cur + \" \");\n            for (int next = 1; next \u003C= n; next++) {\n                if (graph[cur][next] == 1 && !visited[next]) {\n                    queue[r++] = next;\n                    visited[next] = true;\n                }\n            }\n        }\n    }\n\n    public static void main(String[] args) {\n        n = 6;\n        build();\n        addEdge(1, 2); addEdge(1, 3);\n        addEdge(2, 4); addEdge(2, 5);\n        addEdge(3, 6);\n        System.out.println(\"BFS traversal starting from node 1:\");\n        bfs(1);\n    }\n}\n","Code02_BFS_Matrix.java","icon=tabler:grid-dots",[218,277,273],{"__ignoreMap":256},[254,279,280],{"v-slot:tab3":256},[258,281,286],{"className":282,"code":283,"filename":284,"language":264,"meta":285},[261],"package BFS;\n\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.HashSet;\nimport java.util.Queue;\n\npublic class Code03_BFS_List {\n    public static ArrayList\u003CArrayList\u003CInteger>> graph = new ArrayList\u003C>();\n\n    public static void build(int n) {\n        graph.clear();\n        for (int i = 0; i \u003C= n; i++) graph.add(new ArrayList\u003C>());\n    }\n\n    public static void addEdge(int u, int v) {\n        graph.get(u).add(v);\n    }\n\n    public static void bfs(int start) {\n        if (start == 0) return;\n        Queue\u003CInteger> queue = new ArrayDeque\u003C>();\n        HashSet\u003CInteger> set = new HashSet\u003C>();\n        queue.add(start);\n        set.add(start);\n        while (!queue.isEmpty()) {\n            int cur = queue.poll();\n            System.out.print(cur + \" \");\n            for (int next : graph.get(cur)) {\n                if (!set.contains(next)) {\n                    set.add(next);\n                    queue.add(next);\n                }\n            }\n        }\n    }\n\n    public static void main(String[] args) {\n        build(6);\n        addEdge(1, 2); addEdge(1, 3);\n        addEdge(2, 4); addEdge(2, 5);\n        addEdge(3, 6);\n        System.out.println(\"BFS traversal starting from node 1:\");\n        bfs(1);\n    }\n}\n","Code03_BFS_List.java","icon=tabler:list",[218,287,283],{"__ignoreMap":256},[56,289,292],{"title":290,"type":291},"三种建图怎么选？","question",[27,293,294,300,453],{},[30,295,296,299],{},[34,297,298],{},"链式前向星","：竞赛\u002F算法题首选，数组实现快、内存可控",[30,301,302,305,306,397,398,452],{},[34,303,304],{},"邻接矩阵","：稠密图（边数 ≈ ",[307,308,311,341],"span",{"className":309},[310],"katex",[307,312,315],{"className":313},[314],"katex-mathml",[316,317,319],"math",{"xmlns":318},"http:\u002F\u002Fwww.w3.org\u002F1998\u002FMath\u002FMathML",[320,321,322,336],"semantics",{},[323,324,325],"mrow",{},[326,327,328,332],"msup",{},[329,330,331],"mi",{},"V",[333,334,335],"mn",{},"2",[337,338,340],"annotation",{"encoding":339},"application\u002Fx-tex","V^2",[307,342,345],{"className":343,"ariaHidden":66},[344],"katex-html",[307,346,349,354],{"className":347},[348],"base",[307,350],{"className":351,"style":353},[352],"strut","height:0.8141em;",[307,355,358,363],{"className":356},[357],"mord",[307,359,331],{"className":360,"style":362},[357,361],"mathnormal","margin-right:0.2222em;",[307,364,367],{"className":365},[366],"msupsub",[307,368,371],{"className":369},[370],"vlist-t",[307,372,375],{"className":373},[374],"vlist-r",[307,376,379],{"className":377,"style":353},[378],"vlist",[307,380,382,387],{"style":381},"top:-3.063em;margin-right:0.05em;",[307,383],{"className":384,"style":386},[385],"pstrut","height:2.7em;",[307,388,394],{"className":389},[390,391,392,393],"sizing","reset-size6","size3","mtight",[307,395,335],{"className":396},[357,393],"）或需要 ",[307,399,401,427],{"className":400},[310],[307,402,404],{"className":403},[314],[316,405,406],{"xmlns":318},[320,407,408,424],{},[323,409,410,413,418,421],{},[329,411,412],{},"O",[414,415,417],"mo",{"stretchy":416},"false","(",[333,419,420],{},"1",[414,422,423],{"stretchy":416},")",[337,425,426],{"encoding":339},"O(1)",[307,428,430],{"className":429,"ariaHidden":66},[344],[307,431,433,437,441,445,448],{"className":432},[348],[307,434],{"className":435,"style":436},[352],"height:1em;vertical-align:-0.25em;",[307,438,412],{"className":439,"style":440},[357,361],"margin-right:0.0278em;",[307,442,417],{"className":443},[444],"mopen",[307,446,420],{"className":447},[357],[307,449,423],{"className":450},[451],"mclose"," 查询某条边是否存在",[30,454,455,458],{},[34,456,457],{},"邻接表","：工程代码、节点稀疏、写起来直观",[94,460],{},[10,462,464],{"id":463},"按层-bfslevel-order-bfs","按层 BFS（Level-Order BFS）",[17,466,467,468,471],{},"朴素 BFS 不区分层，",[34,469,470],{},"按层 BFS"," 显式地把层切分出来。这是树的层序遍历的图论推广。",[131,473,474],{"id":474},"关键技巧",[14,476,477,478,481,482,484],{},"在每轮 while 循环开始时，先记录当前队列大小 ",[218,479,480],{"code":480},"size","，然后只处理这 ",[218,483,480],{"code":480}," 个节点——它们就是同一层。",[258,486,491],{"className":487,"code":488,"filename":489,"language":264,"meta":490},[261],"public static int bfsByLevel(int start) {\n    Queue\u003CInteger> queue = new ArrayDeque\u003C>();\n    boolean[] visited = new boolean[MAXN];\n    queue.add(start);\n    visited[start] = true;\n\n    int level = 0;\n    while (!queue.isEmpty()) {\n        int size = queue.size();      \u002F\u002F ⭐ 锁定当前层节点数\n        for (int i = 0; i \u003C size; i++) {\n            int cur = queue.poll();\n            \u002F\u002F 处理同层节点 cur，level 即它所在层\n            for (int next : graph.get(cur)) {\n                if (!visited[next]) {\n                    visited[next] = true;\n                    queue.add(next);\n                }\n            }\n        }\n        level++;                      \u002F\u002F ⭐ 这一层处理完，层号 +1\n    }\n    return level;\n}\n","按层模板","icon=tabler:layers-subtract",[218,492,488],{"__ignoreMap":256},[131,494,496],{"id":495},"例题leetcode-102-二叉树的层序遍历","例题：LeetCode 102. 二叉树的层序遍历",[498,499,501],"folding",{"title":500,":open":66},"🌲 LeetCode 102 完整代码",[258,502,506],{"className":503,"code":504,"filename":505,"language":264,"meta":256},[261],"class Solution {\n    public List\u003CList\u003CInteger>> levelOrder(TreeNode root) {\n        List\u003CList\u003CInteger>> ans = new ArrayList\u003C>();\n        if (root == null) return ans;\n        Deque\u003CTreeNode> queue = new ArrayDeque\u003C>();\n        queue.offer(root);\n        while (!queue.isEmpty()) {\n            int size = queue.size();\n            List\u003CInteger> level = new ArrayList\u003C>();\n            for (int i = 0; i \u003C size; i++) {\n                TreeNode cur = queue.poll();\n                level.add(cur.val);\n                if (cur.left != null) queue.offer(cur.left);\n                if (cur.right != null) queue.offer(cur.right);\n            }\n            ans.add(level);\n        }\n        return ans;\n    }\n}\n","LC102.java",[218,507,504],{"__ignoreMap":256},[131,509,511],{"id":510},"例题leetcode-127-单词接龙","例题：LeetCode 127. 单词接龙",[498,513,515,529,651],{"title":514},"🔤 LeetCode 127 思路+代码",[17,516,517,520,521,524,525,528],{},[34,518,519],{},"思路","：把每个单词当作图的节点，相差一个字母的两个单词之间有一条边。从 ",[218,522,523],{"code":523},"beginWord"," 到 ",[218,526,527],{"code":527},"endWord"," 的最少转换次数 = BFS 的最少层数 + 1。",[17,530,531,534,535,638,639,642,643,646,647,650],{},[34,532,533],{},"优化建图","：不要两两枚举单词建图（",[307,536,538,569],{"className":537},[310],[307,539,541],{"className":540},[314],[316,542,543],{"xmlns":318},[320,544,545,566],{},[323,546,547,549,551,558,561,564],{},[329,548,412],{},[414,550,417],{"stretchy":416},[326,552,553,556],{},[329,554,555],{},"N",[333,557,335],{},[414,559,560],{},"⋅",[329,562,563],{},"L",[414,565,423],{"stretchy":416},[337,567,568],{"encoding":339},"O(N^2 \\cdot L)",[307,570,572,626],{"className":571,"ariaHidden":66},[344],[307,573,575,579,582,585,615,619,623],{"className":574},[348],[307,576],{"className":577,"style":578},[352],"height:1.0641em;vertical-align:-0.25em;",[307,580,412],{"className":581,"style":440},[357,361],[307,583,417],{"className":584},[444],[307,586,588,592],{"className":587},[357],[307,589,555],{"className":590,"style":591},[357,361],"margin-right:0.109em;",[307,593,595],{"className":594},[366],[307,596,598],{"className":597},[370],[307,599,601],{"className":600},[374],[307,602,604],{"className":603,"style":353},[378],[307,605,606,609],{"style":381},[307,607],{"className":608,"style":386},[385],[307,610,612],{"className":611},[390,391,392,393],[307,613,335],{"className":614},[357,393],[307,616],{"className":617,"style":362},[618],"mspace",[307,620,560],{"className":621},[622],"mbin",[307,624],{"className":625,"style":362},[618],[307,627,629,632,635],{"className":628},[348],[307,630],{"className":631,"style":436},[352],[307,633,563],{"className":634},[357,361],[307,636,423],{"className":637},[451],"）。改用",[34,640,641],{},"虚拟节点","：单词 ",[218,644,645],{"code":645},"hot"," 与 ",[218,648,649],{"code":649},"*ot\u002Fh*t\u002Fho*"," 三个虚拟节点相连。",[258,652,656],{"className":653,"code":654,"filename":655,"language":264,"meta":256},[261],"class Solution {\n    public int ladderLength(String beginWord, String endWord, List\u003CString> wordList) {\n        Set\u003CString> dict = new HashSet\u003C>(wordList);\n        if (!dict.contains(endWord)) return 0;\n\n        Queue\u003CString> queue = new ArrayDeque\u003C>();\n        Set\u003CString> visited = new HashSet\u003C>();\n        queue.offer(beginWord);\n        visited.add(beginWord);\n\n        int level = 1;\n        while (!queue.isEmpty()) {\n            int size = queue.size();\n            for (int i = 0; i \u003C size; i++) {\n                String cur = queue.poll();\n                if (cur.equals(endWord)) return level;\n                char[] chars = cur.toCharArray();\n                for (int j = 0; j \u003C chars.length; j++) {\n                    char origin = chars[j];\n                    for (char c = 'a'; c \u003C= 'z'; c++) {\n                        if (c == origin) continue;\n                        chars[j] = c;\n                        String next = new String(chars);\n                        if (dict.contains(next) && !visited.contains(next)) {\n                            visited.add(next);\n                            queue.offer(next);\n                        }\n                    }\n                    chars[j] = origin;\n                }\n            }\n            level++;\n        }\n        return 0;\n    }\n}\n","LC127.java",[218,657,654],{"__ignoreMap":256},[94,659],{},[10,661,663],{"id":662},"多源-bfsmulti-source-bfs","多源 BFS（Multi-Source BFS）",[56,665,667],{"title":666,"type":59},"什么是多源 BFS？",[17,668,669,672],{},[34,670,671],{},"多个起点同时出发","，求所有节点到\"任意一个起点\"的最短距离（边数）。",[131,674,675],{"id":675},"核心技巧",[14,677,678,679,682],{},"把所有源点",[34,680,681],{},"一次性全部入队","，然后跑普通 BFS。神奇的是——队列里同时存在的节点们，\"距离最近的某个源点的层数\"是相同的。",[17,684,685,686,717,718,746],{},"为什么这是对的？想象虚拟超级源点 ",[307,687,689,703],{"className":688},[310],[307,690,692],{"className":691},[314],[316,693,694],{"xmlns":318},[320,695,696,701],{},[323,697,698],{},[329,699,700],{},"S",[337,702,700],{"encoding":339},[307,704,706],{"className":705,"ariaHidden":66},[344],[307,707,709,713],{"className":708},[348],[307,710],{"className":711,"style":712},[352],"height:0.6833em;",[307,714,700],{"className":715,"style":716},[357,361],"margin-right:0.0576em;","，它向所有真实源点连一条权 0 的边。从 ",[307,719,721,734],{"className":720},[310],[307,722,724],{"className":723},[314],[316,725,726],{"xmlns":318},[320,727,728,732],{},[323,729,730],{},[329,731,700],{},[337,733,700],{"encoding":339},[307,735,737],{"className":736,"ariaHidden":66},[344],[307,738,740,743],{"className":739},[348],[307,741],{"className":742,"style":712},[352],[307,744,700],{"className":745,"style":716},[357,361]," 出发的 BFS = 把所有真实源点同时入队的 BFS。",[131,748,750],{"id":749},"例题leetcode-994-腐烂的橘子","例题：LeetCode 994. 腐烂的橘子",[498,752,754],{"title":753,":open":66},"🍊 LeetCode 994 完整代码",[258,755,760],{"className":756,"code":757,"filename":758,"language":264,"meta":759},[261],"class Solution {\n    public int orangesRotting(int[][] grid) {\n        int m = grid.length, n = grid[0].length;\n        Deque\u003Cint[]> queue = new ArrayDeque\u003C>();\n        int fresh = 0;\n\n        \u002F\u002F ⭐ 把所有腐烂橘子一次性入队\n        for (int i = 0; i \u003C m; i++) {\n            for (int j = 0; j \u003C n; j++) {\n                if (grid[i][j] == 2) queue.offer(new int[]{i, j});\n                else if (grid[i][j] == 1) fresh++;\n            }\n        }\n\n        if (fresh == 0) return 0;\n        int[][] dirs = {{-1,0},{1,0},{0,-1},{0,1}};\n        int minutes = 0;\n\n        while (!queue.isEmpty() && fresh > 0) {\n            int size = queue.size();\n            for (int k = 0; k \u003C size; k++) {\n                int[] cur = queue.poll();\n                for (int[] d : dirs) {\n                    int ni = cur[0] + d[0], nj = cur[1] + d[1];\n                    if (ni >= 0 && ni \u003C m && nj >= 0 && nj \u003C n && grid[ni][nj] == 1) {\n                        grid[ni][nj] = 2;\n                        fresh--;\n                        queue.offer(new int[]{ni, nj});\n                    }\n                }\n            }\n            minutes++;\n        }\n        return fresh == 0 ? minutes : -1;\n    }\n}\n","LC994.java","icon=tabler:bug",[218,761,757],{"__ignoreMap":256},[131,763,765],{"id":764},"例题leetcode-542-01-矩阵","例题：LeetCode 542. 01 矩阵",[498,767,769,775],{"title":768},"🧊 LeetCode 542 思路+代码",[17,770,771,774],{},[34,772,773],{},"逆向思考","：求每个 1 到最近 0 的距离 = 把所有 0 当源点，多源 BFS 一次扩散。",[258,776,780],{"className":777,"code":778,"filename":779,"language":264,"meta":256},[261],"class Solution {\n    public int[][] updateMatrix(int[][] mat) {\n        int m = mat.length, n = mat[0].length;\n        int[][] dist = new int[m][n];\n        Deque\u003Cint[]> queue = new ArrayDeque\u003C>();\n\n        for (int i = 0; i \u003C m; i++) {\n            for (int j = 0; j \u003C n; j++) {\n                if (mat[i][j] == 0) queue.offer(new int[]{i, j});\n                else dist[i][j] = -1;     \u002F\u002F 用 -1 标记未访问\n            }\n        }\n\n        int[][] dirs = {{-1,0},{1,0},{0,-1},{0,1}};\n        while (!queue.isEmpty()) {\n            int[] cur = queue.poll();\n            for (int[] d : dirs) {\n                int ni = cur[0] + d[0], nj = cur[1] + d[1];\n                if (ni >= 0 && ni \u003C m && nj >= 0 && nj \u003C n && dist[ni][nj] == -1) {\n                    dist[ni][nj] = dist[cur[0]][cur[1]] + 1;\n                    queue.offer(new int[]{ni, nj});\n                }\n            }\n        }\n        return dist;\n    }\n}\n","LC542.java",[218,781,778],{"__ignoreMap":256},[94,783],{},[10,785,787],{"id":786},"_01-bfszero-one-bfs","01-BFS（Zero-One BFS）",[56,789,791],{"title":790,"type":78},"适用场景",[17,792,793,796,797,900,901,969],{},[34,794,795],{},"边权只有 0 和 1 两种值","的最短路径问题。Dijkstra 可以做，但 ",[307,798,800,838],{"className":799},[310],[307,801,803],{"className":802},[314],[316,804,805],{"xmlns":318},[320,806,807,835],{},[323,808,809,811,813,815,817,820,823,825,828,831,833],{},[329,810,412],{},[414,812,417],{"stretchy":416},[414,814,417],{"stretchy":416},[329,816,331],{},[414,818,819],{},"+",[329,821,822],{},"E",[414,824,423],{"stretchy":416},[329,826,827],{},"log",[414,829,830],{},"⁡",[329,832,331],{},[414,834,423],{"stretchy":416},[337,836,837],{"encoding":339},"O((V+E) \\log V)",[307,839,841,866],{"className":840,"ariaHidden":66},[344],[307,842,844,847,850,854,857,860,863],{"className":843},[348],[307,845],{"className":846,"style":436},[352],[307,848,412],{"className":849,"style":440},[357,361],[307,851,853],{"className":852},[444],"((",[307,855,331],{"className":856,"style":362},[357,361],[307,858],{"className":859,"style":362},[618],[307,861,819],{"className":862},[622],[307,864],{"className":865,"style":362},[618],[307,867,869,872,875,878,882,891,894,897],{"className":868},[348],[307,870],{"className":871,"style":436},[352],[307,873,822],{"className":874,"style":716},[357,361],[307,876,423],{"className":877},[451],[307,879],{"className":880,"style":881},[618],"margin-right:0.1667em;",[307,883,886,887],{"className":884},[885],"mop","lo",[307,888,890],{"style":889},"margin-right:0.0139em;","g",[307,892],{"className":893,"style":881},[618],[307,895,331],{"className":896,"style":362},[357,361],[307,898,423],{"className":899},[451]," 略慢；01-BFS 可以做到 ",[34,902,903],{},[307,904,906,930],{"className":905},[310],[307,907,909],{"className":908},[314],[316,910,911],{"xmlns":318},[320,912,913,927],{},[323,914,915,917,919,921,923,925],{},[329,916,412],{},[414,918,417],{"stretchy":416},[329,920,331],{},[414,922,819],{},[329,924,822],{},[414,926,423],{"stretchy":416},[337,928,929],{"encoding":339},"O(V + E)",[307,931,933,957],{"className":932,"ariaHidden":66},[344],[307,934,936,939,942,945,948,951,954],{"className":935},[348],[307,937],{"className":938,"style":436},[352],[307,940,412],{"className":941,"style":440},[357,361],[307,943,417],{"className":944},[444],[307,946,331],{"className":947,"style":362},[357,361],[307,949],{"className":950,"style":362},[618],[307,952,819],{"className":953},[622],[307,955],{"className":956,"style":362},[618],[307,958,960,963,966],{"className":959},[348],[307,961],{"className":962,"style":436},[352],[307,964,822],{"className":965,"style":716},[357,361],[307,967,423],{"className":968},[451],"。",[131,971,972],{"id":972},"核心思想",[17,974,975,976,144],{},"普通 BFS 用队列，01-BFS 用",[34,977,978],{},"双端队列",[24,980,981],{},[27,982,983,997],{},[30,984,985,986,989,990,68,993,996],{},"走",[34,987,988],{},"权 0 边","：邻居加到",[34,991,992],{},"队头",[218,994,995],{"code":995},"addFirst","（不增加距离，优先处理）",[30,998,985,999,989,1002,68,1005,1008],{},[34,1000,1001],{},"权 1 边",[34,1003,1004],{},"队尾",[218,1006,1007],{"code":1007},"addLast","（增加 1 距离，正常排队）",[14,1010,1011],{},"这样可以保证：双端队列中元素的距离值始终单调不减——和 Dijkstra 的优先队列性质等价，但不需要堆。",[131,1013,1015],{"id":1014},"例题leetcode-1368-使网格图至少有一条有效路径的最小代价","例题：LeetCode 1368. 使网格图至少有一条有效路径的最小代价",[56,1017,1019],{"title":1018,"type":291},"题意",[17,1020,1021,1022,524,1025,1028],{},"网格中每个格子有一个箭头方向（1 右、2 左、3 下、4 上）。从 ",[218,1023,1024],{"code":1024},"(0,0)",[218,1026,1027],{"code":1027},"(m-1,n-1)","，沿箭头方向走代价 0，逆箭头走代价 1，求最小代价。",[498,1030,1032],{"title":1031,":open":66},"🎯 LeetCode 1368 完整代码",[258,1033,1038],{"className":1034,"code":1035,"filename":1036,"language":264,"meta":1037},[261],"class Solution {\n    public int minCost(int[][] grid) {\n        int m = grid.length, n = grid[0].length;\n        int[][] dist = new int[m][n];\n        for (int[] row : dist) Arrays.fill(row, Integer.MAX_VALUE);\n        dist[0][0] = 0;\n\n        \u002F\u002F 方向编号 1234 对应 dirs[1..4]\n        int[][] dirs = {{0,0}, {0,1}, {0,-1}, {1,0}, {-1,0}};\n\n        Deque\u003Cint[]> deque = new ArrayDeque\u003C>();\n        deque.offerFirst(new int[]{0, 0});\n\n        while (!deque.isEmpty()) {\n            int[] cur = deque.pollFirst();\n            int x = cur[0], y = cur[1];\n            for (int k = 1; k \u003C= 4; k++) {\n                int nx = x + dirs[k][0], ny = y + dirs[k][1];\n                if (nx \u003C 0 || nx >= m || ny \u003C 0 || ny >= n) continue;\n                int cost = (grid[x][y] == k) ? 0 : 1;\n                int nd = dist[x][y] + cost;\n                if (nd \u003C dist[nx][ny]) {\n                    dist[nx][ny] = nd;\n                    if (cost == 0) deque.offerFirst(new int[]{nx, ny}); \u002F\u002F ⭐\n                    else deque.offerLast(new int[]{nx, ny});             \u002F\u002F ⭐\n                }\n            }\n        }\n        return dist[m-1][n-1];\n    }\n}\n","LC1368.java","icon=tabler:arrow-bear-right",[218,1039,1035],{"__ignoreMap":256},[131,1041,1043],{"id":1042},"例题leetcode-2290-到达角落需要移除障碍物的最小数目","例题：LeetCode 2290. 到达角落需要移除障碍物的最小数目",[498,1045,1047,1052],{"title":1046},"🧱 LeetCode 2290 思路+代码",[17,1048,1049,1051],{},[34,1050,519],{},"：空格走代价 0，障碍走代价 1 → 完美匹配 01-BFS。",[258,1053,1057],{"className":1054,"code":1055,"filename":1056,"language":264,"meta":256},[261],"class Solution {\n    public int minimumObstacles(int[][] grid) {\n        int m = grid.length, n = grid[0].length;\n        int[][] dist = new int[m][n];\n        for (int[] row : dist) Arrays.fill(row, Integer.MAX_VALUE);\n        dist[0][0] = 0;\n\n        int[][] dirs = {{-1,0},{1,0},{0,-1},{0,1}};\n        Deque\u003Cint[]> dq = new ArrayDeque\u003C>();\n        dq.offerFirst(new int[]{0, 0});\n\n        while (!dq.isEmpty()) {\n            int[] cur = dq.pollFirst();\n            int x = cur[0], y = cur[1];\n            if (x == m - 1 && y == n - 1) return dist[x][y];\n            for (int[] d : dirs) {\n                int nx = x + d[0], ny = y + d[1];\n                if (nx \u003C 0 || nx >= m || ny \u003C 0 || ny >= n) continue;\n                int nd = dist[x][y] + grid[nx][ny];\n                if (nd \u003C dist[nx][ny]) {\n                    dist[nx][ny] = nd;\n                    if (grid[nx][ny] == 0) dq.offerFirst(new int[]{nx, ny});\n                    else dq.offerLast(new int[]{nx, ny});\n                }\n            }\n        }\n        return dist[m-1][n-1];\n    }\n}\n","LC2290.java",[218,1058,1055],{"__ignoreMap":256},[56,1060,1062],{"title":1061,"type":137},"易错点：dist 必须比较再更新",[17,1063,1064,1065,1068,1069,1072,1073,1076,1077,969],{},"01-BFS 中同一个节点",[34,1066,1067],{},"可能被多次入队","（先以较大距离入，后被更短路径\"覆盖\"）。所以",[34,1070,1071],{},"每次出队要再判断一次","当前的 ",[218,1074,1075],{"code":1075},"dist"," 是否就是最优值，否则跳过——或者像上面这样，在入队前比较 ",[218,1078,1075],{"code":1075},[94,1080],{},[10,1082,1084],{"id":1083},"优先-bfs与堆结合","优先 BFS（与堆结合）",[56,1086,1088,1095,1102,1105],{"title":1087,"type":59},"核心区别",[17,1089,1090,1091,1094],{},"普通 BFS 使用的是 ",[34,1092,1093],{},"FIFO 队列","，谁先入队谁先出队，因此它天然按照“层数”扩散。",[17,1096,1097,1098,1101],{},"优先 BFS 则把普通队列换成了 ",[34,1099,1100],{},"优先级队列 \u002F 堆","，每次都取当前优先级最高的节点进行扩展。",[17,1103,1104],{},"简单说：",[258,1106,1111],{"className":1107,"code":1109,"language":1110,"meta":256},[1108],"language-text","普通 BFS：按入队顺序扩展\n优先 BFS：按优先级扩展\n","text",[218,1112,1109],{"__ignoreMap":256},[131,1114,675],{"id":1115},"核心技巧-1",[24,1117,1118],{},[27,1119,1120,1149,1173,1190],{},[30,1121,1122,1125,1128,1129,1137,1139,1140,1146,1148],{},[34,1123,1124],{},"队列换成堆",[1126,1127],"br",{},"普通 BFS：",[258,1130,1135],{"className":1131,"code":1133,"language":1134,"meta":256},[1132],"language-cpp","queue\u003CNode> q;\n","cpp",[218,1136,1133],{"__ignoreMap":256},[1126,1138],{},"优先 BFS：",[258,1141,1144],{"className":1142,"code":1143,"language":1134,"meta":256},[1132],"priority_queue\u003CNode> heap;\n",[218,1145,1143],{"__ignoreMap":256},[1126,1147],{},"如果每次要取最小值，一般使用小根堆。",[30,1150,1151,1154,1156,1157,1163,1165,1166,1169,1170,969],{},[34,1152,1153],{},"BFS 框架不变",[1126,1155],{},"优先 BFS 仍然保留 BFS 的基本流程：",[258,1158,1161],{"className":1159,"code":1160,"language":1110,"meta":256},[1108],"取出一个点\n枚举它的上下左右 \u002F 相邻节点\n没访问过就加入容器\n",[218,1162,1160],{"__ignoreMap":256},[1126,1164],{},"区别只是容器从 ",[218,1167,1168],{"code":1168},"queue"," 换成了 ",[218,1171,1172],{"code":1172},"priority_queue",[30,1174,1175,1178,1180,1181,1183,1184],{},[34,1176,1177],{},"优先级怎么定？",[1126,1179],{},"由题目决定。",[1126,1182],{},"比如：",[258,1185,1188],{"className":1186,"code":1187,"language":1110,"meta":256},[1108],"最短路问题：距离越小，优先级越高\n接雨水 II：边界高度越低，优先级越高\n最小代价问题：总代价越小，优先级越高\n",[218,1189,1187],{"__ignoreMap":256},[30,1191,1192,1195,1201,1203,1204],{},[34,1193,1194],{},"本质理解",[258,1196,1199],{"className":1197,"code":1198,"language":1110,"meta":256},[1108],"优先 BFS = BFS 扩展框架 + 堆控制扩展顺序\n",[218,1200,1198],{"__ignoreMap":256},[1126,1202],{},"它经常和 Dijkstra 很像，因为二者都在做：",[258,1205,1208],{"className":1206,"code":1207,"language":1110,"meta":256},[1108],"每次从当前最优状态继续扩展\n",[218,1209,1207],{"__ignoreMap":256},[131,1211,1213],{"id":1212},"例题leetcode-接雨水-ii","例题：Leetcode 接雨水 II",[56,1215,1217,1220],{"title":1216,"type":59},"木桶效应",[17,1218,1219],{},"二维接雨水的关键是：水能不能存住，取决于当前边界里最低的那块板。",[17,1221,1222,1223,1226],{},"所以我们不能随便扩展，而是必须每次从",[34,1224,1225],{},"当前最低边界","开始向内扩展。",[131,1228,1229],{"id":1229},"思路流程",[24,1231,1232],{},[27,1233,1234,1237,1240,1243,1246,1249],{},[30,1235,1236],{},"先把矩阵最外圈全部加入小根堆",[30,1238,1239],{},"最外圈无法接水，因为水会从边界流出去",[30,1241,1242],{},"每次弹出当前最低的边界",[30,1244,1245],{},"用这个边界去检查上下左右四个格子",[30,1247,1248],{},"如果邻居比当前水线低，就说明它可以接水",[30,1250,1251,1252],{},"邻居入堆时，它的新高度要变成：",[258,1253,1256],{"className":1254,"code":1255,"language":1134,"meta":256},[1132],"max(heightMap[nx][ny], waterLine)\n",[218,1257,1255],{"__ignoreMap":256},[131,1259,1261,1262,1265],{"id":1260},"为什么是-maxheight-waterline","为什么是 ",[218,1263,1264],{"code":1264},"max(height, waterLine)","？",[17,1267,1268,1269,1272,1273,969],{},"假设当前最低边界高度是 ",[218,1270,1271],{"code":1271},"w","，邻居原本高度是 ",[218,1274,1275],{"code":1275},"h",[17,1277,1278],{},"如果：",[258,1280,1283],{"className":1281,"code":1282,"language":1110,"meta":256},[1108],"h \u003C w\n",[218,1284,1282],{"__ignoreMap":256},[17,1286,1287],{},"说明这个位置可以接水：",[258,1289,1292],{"className":1290,"code":1291,"language":1110,"meta":256},[1108],"接水量 = w - h\n",[218,1293,1291],{"__ignoreMap":256},[17,1295,1296,1297,1299,1300,1302],{},"但是它被水填平以后，对后面的格子来说，它已经不是高度 ",[218,1298,1275],{"code":1275}," 了，而是高度 ",[218,1301,1271],{"code":1271}," 的“新边界”。",[17,1304,1305],{},"所以入堆高度是：",[258,1307,1310],{"className":1308,"code":1309,"language":1134,"meta":256},[1132],"max(h, w)\n",[218,1311,1309],{"__ignoreMap":256},[17,1313,1314],{},"也就是：",[258,1316,1319],{"className":1317,"code":1318,"language":1110,"meta":256},[1108],"新边界高度 = max(自身高度, 当前水线高度)\n",[218,1320,1318],{"__ignoreMap":256},[131,1322,1323],{"id":1323},"通用模板",[258,1325,1328],{"className":1326,"code":1327,"language":1134,"meta":256},[1132],"while (!heap.empty()) {\n    auto [w, x, y] = heap.top();\n    heap.pop();\n\n    for (四个方向) {\n        int nx = x + dx;\n        int ny = y + dy;\n\n        if (越界 || 已访问) continue;\n\n        visited[nx][ny] = true;\n\n        int h = heightMap[nx][ny];\n\n        if (h \u003C w) {\n            ans += w - h;\n        }\n\n        heap.push({max(h, w), nx, ny});\n    }\n}\n",[218,1329,1327],{"__ignoreMap":256},[131,1331,1332],{"id":1332},"总结",[14,1334,1335],{},[17,1336,1337],{},"优先 BFS 就是用堆控制扩展顺序的 BFS。普通 BFS 按层扩展，优先 BFS 按“当前最优状态”扩展。",[10,1339,1341],{"id":1340},"双向-bfsbidirectional-bfs","双向 BFS（Bidirectional BFS）",[56,1343,1345],{"title":1344,"type":59},"动机",[17,1346,1347,1348,1426,1427,1456,1457,1485,1486,1525,1526,1636],{},"朴素 BFS 的搜索空间是 ",[307,1349,1351,1377],{"className":1350},[310],[307,1352,1354],{"className":1353},[314],[316,1355,1356],{"xmlns":318},[320,1357,1358,1374],{},[323,1359,1360,1362,1364,1372],{},[329,1361,412],{},[414,1363,417],{"stretchy":416},[326,1365,1366,1369],{},[329,1367,1368],{},"b",[329,1370,1371],{},"d",[414,1373,423],{"stretchy":416},[337,1375,1376],{"encoding":339},"O(b^d)",[307,1378,1380],{"className":1379,"ariaHidden":66},[344],[307,1381,1383,1387,1390,1393,1423],{"className":1382},[348],[307,1384],{"className":1385,"style":1386},[352],"height:1.0991em;vertical-align:-0.25em;",[307,1388,412],{"className":1389,"style":440},[357,361],[307,1391,417],{"className":1392},[444],[307,1394,1396,1399],{"className":1395},[357],[307,1397,1368],{"className":1398},[357,361],[307,1400,1402],{"className":1401},[366],[307,1403,1405],{"className":1404},[370],[307,1406,1408],{"className":1407},[374],[307,1409,1412],{"className":1410,"style":1411},[378],"height:0.8491em;",[307,1413,1414,1417],{"style":381},[307,1415],{"className":1416,"style":386},[385],[307,1418,1420],{"className":1419},[390,391,392,393],[307,1421,1371],{"className":1422},[357,361,393],[307,1424,423],{"className":1425},[451],"（分支因子 ",[307,1428,1430,1443],{"className":1429},[310],[307,1431,1433],{"className":1432},[314],[316,1434,1435],{"xmlns":318},[320,1436,1437,1441],{},[323,1438,1439],{},[329,1440,1368],{},[337,1442,1368],{"encoding":339},[307,1444,1446],{"className":1445,"ariaHidden":66},[344],[307,1447,1449,1453],{"className":1448},[348],[307,1450],{"className":1451,"style":1452},[352],"height:0.6944em;",[307,1454,1368],{"className":1455},[357,361],"，深度 ",[307,1458,1460,1473],{"className":1459},[310],[307,1461,1463],{"className":1462},[314],[316,1464,1465],{"xmlns":318},[320,1466,1467,1471],{},[323,1468,1469],{},[329,1470,1371],{},[337,1472,1371],{"encoding":339},[307,1474,1476],{"className":1475,"ariaHidden":66},[344],[307,1477,1479,1482],{"className":1478},[348],[307,1480],{"className":1481,"style":1452},[352],[307,1483,1371],{"className":1484},[357,361],"）。从两端同时搜，每边只需要搜深度 ",[307,1487,1489,1509],{"className":1488},[310],[307,1490,1492],{"className":1491},[314],[316,1493,1494],{"xmlns":318},[320,1495,1496,1506],{},[323,1497,1498,1500,1504],{},[329,1499,1371],{},[329,1501,1503],{"mathvariant":1502},"normal","\u002F",[333,1505,335],{},[337,1507,1508],{"encoding":339},"d\u002F2",[307,1510,1512],{"className":1511,"ariaHidden":66},[344],[307,1513,1515,1518,1521],{"className":1514},[348],[307,1516],{"className":1517,"style":436},[352],[307,1519,1371],{"className":1520},[357,361],[307,1522,1524],{"className":1523},[357],"\u002F2","，总规模降为 ",[307,1527,1529,1563],{"className":1528},[310],[307,1530,1532],{"className":1531},[314],[316,1533,1534],{"xmlns":318},[320,1535,1536,1560],{},[323,1537,1538,1540,1542,1544,1546,1558],{},[329,1539,412],{},[414,1541,417],{"stretchy":416},[333,1543,335],{},[414,1545,560],{},[326,1547,1548,1550],{},[329,1549,1368],{},[323,1551,1552,1554,1556],{},[329,1553,1371],{},[329,1555,1503],{"mathvariant":1502},[333,1557,335],{},[414,1559,423],{"stretchy":416},[337,1561,1562],{"encoding":339},"O(2 \\cdot b^{d\u002F2})",[307,1564,1566,1590],{"className":1565,"ariaHidden":66},[344],[307,1567,1569,1572,1575,1578,1581,1584,1587],{"className":1568},[348],[307,1570],{"className":1571,"style":436},[352],[307,1573,412],{"className":1574,"style":440},[357,361],[307,1576,417],{"className":1577},[444],[307,1579,335],{"className":1580},[357],[307,1582],{"className":1583,"style":362},[618],[307,1585,560],{"className":1586},[622],[307,1588],{"className":1589,"style":362},[618],[307,1591,1593,1597,1633],{"className":1592},[348],[307,1594],{"className":1595,"style":1596},[352],"height:1.138em;vertical-align:-0.25em;",[307,1598,1600,1603],{"className":1599},[357],[307,1601,1368],{"className":1602},[357,361],[307,1604,1606],{"className":1605},[366],[307,1607,1609],{"className":1608},[370],[307,1610,1612],{"className":1611},[374],[307,1613,1616],{"className":1614,"style":1615},[378],"height:0.888em;",[307,1617,1618,1621],{"style":381},[307,1619],{"className":1620,"style":386},[385],[307,1622,1624],{"className":1623},[390,391,392,393],[307,1625,1627,1630],{"className":1626},[357,393],[307,1628,1371],{"className":1629},[357,361,393],[307,1631,1524],{"className":1632},[357,393],[307,1634,423],{"className":1635},[451]," —— 指数级提速。",[131,1638,675],{"id":1639},"核心技巧-2",[24,1641,1642],{},[27,1643,1644,1655,1662,1665],{},[30,1645,1646,1647,1650,1651,1654],{},"维护两个集合 ",[218,1648,1649],{"code":1649},"beginSet"," 和 ",[218,1652,1653],{"code":1653},"endSet","，分别存当前层的节点",[30,1656,1657,1658,1661],{},"每轮",[34,1659,1660],{},"总是扩展规模较小的那个集合","（这是关键优化）",[30,1663,1664],{},"扩展时如果触碰到对方集合 → 找到了，返回当前层数",[30,1666,1667,1668,1670],{},"用 ",[218,1669,220],{"code":220}," 集合去重（双向都要标）",[131,1672,1674],{"id":1673},"例题leetcode-127-单词接龙双向版","例题：LeetCode 127. 单词接龙（双向版）",[14,1676,1677],{},"同样是 LC127，朴素 BFS 处理大数据集会超时——双向 BFS 才是面试官期待的标准答案。",[498,1679,1681],{"title":1680,":open":66},"🚀 LeetCode 127 双向 BFS 完整代码",[258,1682,1687],{"className":1683,"code":1684,"filename":1685,"language":264,"meta":1686},[261],"class Solution {\n    public int ladderLength(String beginWord, String endWord, List\u003CString> wordList) {\n        Set\u003CString> dict = new HashSet\u003C>(wordList);\n        if (!dict.contains(endWord)) return 0;\n\n        Set\u003CString> beginSet = new HashSet\u003C>();\n        Set\u003CString> endSet = new HashSet\u003C>();\n        Set\u003CString> visited = new HashSet\u003C>();\n        beginSet.add(beginWord);\n        endSet.add(endWord);\n\n        int level = 1;\n        while (!beginSet.isEmpty() && !endSet.isEmpty()) {\n            \u002F\u002F ⭐ 每轮选小的一边扩展\n            if (beginSet.size() > endSet.size()) {\n                Set\u003CString> tmp = beginSet; beginSet = endSet; endSet = tmp;\n            }\n\n            Set\u003CString> nextLevel = new HashSet\u003C>();\n            for (String word : beginSet) {\n                char[] chars = word.toCharArray();\n                for (int i = 0; i \u003C chars.length; i++) {\n                    char origin = chars[i];\n                    for (char c = 'a'; c \u003C= 'z'; c++) {\n                        if (c == origin) continue;\n                        chars[i] = c;\n                        String next = new String(chars);\n                        if (endSet.contains(next)) return level + 1;  \u002F\u002F ⭐ 相遇！\n                        if (dict.contains(next) && !visited.contains(next)) {\n                            nextLevel.add(next);\n                            visited.add(next);\n                        }\n                    }\n                    chars[i] = origin;\n                }\n            }\n            beginSet = nextLevel;\n            level++;\n        }\n        return 0;\n    }\n}\n","LC127_Bidirectional.java","icon=tabler:arrows-exchange",[218,1688,1684],{"__ignoreMap":256},[131,1690,1692],{"id":1691},"例题leetcode-752-打开转盘锁","例题：LeetCode 752. 打开转盘锁",[498,1694,1696,1778],{"title":1695},"🔒 LeetCode 752 思路",[17,1697,1698,1700,1701,524,1704,1707,1708,1711,1712,1777],{},[34,1699,519],{},"：每个 4 位密码是一个状态节点，相邻状态相差一位（每位 ±1，共 8 种邻居）。从 ",[218,1702,1703],{"code":1703},"\"0000\"",[218,1705,1706],{"code":1706},"target","，避开 ",[218,1709,1710],{"code":1710},"deadends","。状态空间 ",[307,1713,1715,1735],{"className":1714},[310],[307,1716,1718],{"className":1717},[314],[316,1719,1720],{"xmlns":318},[320,1721,1722,1732],{},[323,1723,1724],{},[326,1725,1726,1729],{},[333,1727,1728],{},"10",[333,1730,1731],{},"4",[337,1733,1734],{"encoding":339},"10^4",[307,1736,1738],{"className":1737,"ariaHidden":66},[344],[307,1739,1741,1744,1747],{"className":1740},[348],[307,1742],{"className":1743,"style":353},[352],[307,1745,420],{"className":1746},[357],[307,1748,1750,1754],{"className":1749},[357],[307,1751,1753],{"className":1752},[357],"0",[307,1755,1757],{"className":1756},[366],[307,1758,1760],{"className":1759},[370],[307,1761,1763],{"className":1762},[374],[307,1764,1766],{"className":1765,"style":353},[378],[307,1767,1768,1771],{"style":381},[307,1769],{"className":1770,"style":386},[385],[307,1772,1774],{"className":1773},[390,391,392,393],[307,1775,1731],{"className":1776},[357,393]," 不大但双向 BFS 仍然能显著加速。",[17,1779,1780],{},"实现模板与 LC127 完全一致，只把\"换字母\"换成\"转拨号\"即可。",[56,1782,1784],{"title":1783,"type":78},"什么时候不要用双向 BFS？",[27,1785,1786,1870,1873],{},[30,1787,1788,1789,1869],{},"状态空间小（",[307,1790,1792,1813],{"className":1791},[310],[307,1793,1795],{"className":1794},[314],[316,1796,1797],{"xmlns":318},[320,1798,1799,1810],{},[323,1800,1801,1804],{},[414,1802,1803],{},"\u003C",[326,1805,1806,1808],{},[333,1807,1728],{},[333,1809,1731],{},[337,1811,1812],{"encoding":339}," \u003C 10^4",[307,1814,1816,1831],{"className":1815,"ariaHidden":66},[344],[307,1817,1819,1823,1827],{"className":1818},[348],[307,1820],{"className":1821,"style":1822},[352],"height:0.5782em;vertical-align:-0.0391em;",[307,1824,1803],{"className":1825},[1826],"mrel",[307,1828],{"className":1829,"style":1830},[618],"margin-right:0.2778em;",[307,1832,1834,1837,1840],{"className":1833},[348],[307,1835],{"className":1836,"style":353},[352],[307,1838,420],{"className":1839},[357],[307,1841,1843,1846],{"className":1842},[357],[307,1844,1753],{"className":1845},[357],[307,1847,1849],{"className":1848},[366],[307,1850,1852],{"className":1851},[370],[307,1853,1855],{"className":1854},[374],[307,1856,1858],{"className":1857,"style":353},[378],[307,1859,1860,1863],{"style":381},[307,1861],{"className":1862,"style":386},[385],[307,1864,1866],{"className":1865},[390,391,392,393],[307,1867,1731],{"className":1868},[357,393],"）：朴素 BFS 已经够快",[30,1871,1872],{},"终点未知或终点是\"任意满足条件的状态\"（无法从终点反向扩展）",[30,1874,1875],{},"边权不一致（双向 BFS 假设搜索代价对称）",[94,1877],{},[10,1879,1881],{"id":1880},"bfs-剪枝策略","BFS 剪枝策略",[17,1883,1884,1885,969],{},"剪枝的本质：",[34,1886,1887],{},"不入队那些不可能更优的节点",[131,1889,1891],{"id":1890},"_1-状态去重","1. 状态去重",[17,1893,1894,1895,1897],{},"最常见的剪枝。",[218,1896,220],{"code":220}," 数组\u002F哈希集合就是它。",[131,1899,1901],{"id":1900},"_2-距离剪枝","2. 距离剪枝",[17,1903,1904,1905,1908,1909,1912],{},"如果已知答案上界 ",[218,1906,1907],{"code":1907},"best","，跳过 ",[218,1910,1911],{"code":1911},"dist[next] >= best"," 的扩展。",[131,1914,1916],{"id":1915},"_3-状态压缩","3. 状态压缩",[17,1918,1919],{},"当节点状态包含多个维度（位置 + 持有钥匙集合等），把状态打包成 int\u002Flong 入队。",[498,1921,1923,1932],{"title":1922},"🔑 例题：LeetCode 864. 获取所有钥匙的最短路径",[17,1924,1925,144,1928,1931],{},[34,1926,1927],{},"状态",[218,1929,1930],{"code":1930},"(x, y, keysBitmask)","。同一坐标但持钥匙不同算不同状态。",[258,1933,1938],{"className":1934,"code":1935,"filename":1936,"language":264,"meta":1937},[261],"class Solution {\n    public int shortestPathAllKeys(String[] grid) {\n        int m = grid.length, n = grid[0].length();\n        int sx = 0, sy = 0, allKeys = 0;\n        for (int i = 0; i \u003C m; i++) {\n            for (int j = 0; j \u003C n; j++) {\n                char c = grid[i].charAt(j);\n                if (c == '@') { sx = i; sy = j; }\n                else if (c >= 'a' && c \u003C= 'f') allKeys |= (1 \u003C\u003C (c - 'a'));\n            }\n        }\n\n        \u002F\u002F 状态: (x \u003C\u003C 16) | (y \u003C\u003C 8) | keys\n        Deque\u003Cint[]> queue = new ArrayDeque\u003C>();\n        Set\u003CInteger> visited = new HashSet\u003C>();\n        queue.offer(new int[]{sx, sy, 0});\n        visited.add((sx \u003C\u003C 16) | (sy \u003C\u003C 8));\n\n        int[][] dirs = {{-1,0},{1,0},{0,-1},{0,1}};\n        int step = 0;\n        while (!queue.isEmpty()) {\n            int size = queue.size();\n            for (int k = 0; k \u003C size; k++) {\n                int[] cur = queue.poll();\n                int x = cur[0], y = cur[1], keys = cur[2];\n                if (keys == allKeys) return step;\n                for (int[] d : dirs) {\n                    int nx = x + d[0], ny = y + d[1];\n                    if (nx \u003C 0 || nx >= m || ny \u003C 0 || ny >= n) continue;\n                    char c = grid[nx].charAt(ny);\n                    if (c == '#') continue;\n                    int nKeys = keys;\n                    if (c >= 'a' && c \u003C= 'f') nKeys |= (1 \u003C\u003C (c - 'a'));\n                    if (c >= 'A' && c \u003C= 'F' && (keys & (1 \u003C\u003C (c - 'A'))) == 0) continue;\n                    int state = (nx \u003C\u003C 16) | (ny \u003C\u003C 8) | nKeys;\n                    if (visited.add(state)) queue.offer(new int[]{nx, ny, nKeys});\n                }\n            }\n            step++;\n        }\n        return -1;\n    }\n}\n","LC864.java","icon=tabler:key",[218,1939,1935],{"__ignoreMap":256},[131,1941,1943],{"id":1942},"_4-启发式剪枝a-的雏形","4. 启发式剪枝（A* 的雏形）",[17,1945,1946,1947,68,1950,1952,1953,1960,1961,1964],{},"为每个状态估计一个",[34,1948,1949],{},"到目标的下界",[218,1951,1275],{"code":1275},"，在队列中",[34,1954,1955,1956,1959],{},"优先扩展 ",[218,1957,1958],{"code":1958},"dist + h"," 较小者","。这就是 A* 算法。BFS 是 ",[218,1962,1963],{"code":1963},"h ≡ 0"," 的特例。",[56,1966,1968],{"title":1967,"type":59},"一句话总结剪枝思路",[17,1969,1970,1973],{},[34,1971,1972],{},"少入队、晚出队","——能不入队的状态绝不入队，能延后处理的状态延后处理。",[94,1975],{},[10,1977,1979],{"id":1978},"应用一无向图连通分量","应用一：无向图连通分量",[56,1981,1983],{"title":1982},"题目：LeetCode 323（会员）",[17,1984,1985],{},"求无向图的连通分量个数。",[17,1987,1988,1990],{},[34,1989,519],{},"：遍历所有顶点，遇到未访问的就 BFS 一次染色，BFS 次数即连通分量数。",[258,1992,1996],{"className":1993,"code":1994,"filename":1995,"language":264,"meta":256},[261],"class Solution {\n    public int countComponents(int n, int[][] edges) {\n        List\u003CList\u003CInteger>> graph = new ArrayList\u003C>();\n        for (int i = 0; i \u003C n; i++) graph.add(new ArrayList\u003C>());\n        for (int[] e : edges) {\n            graph.get(e[0]).add(e[1]);\n            graph.get(e[1]).add(e[0]);\n        }\n        boolean[] visited = new boolean[n];\n        int components = 0;\n        Deque\u003CInteger> queue = new ArrayDeque\u003C>();\n\n        for (int i = 0; i \u003C n; i++) {\n            if (visited[i]) continue;\n            queue.offer(i);\n            visited[i] = true;\n            while (!queue.isEmpty()) {\n                int cur = queue.poll();\n                for (int next : graph.get(cur)) {\n                    if (!visited[next]) {\n                        visited[next] = true;\n                        queue.offer(next);\n                    }\n                }\n            }\n            components++;     \u002F\u002F ⭐ 每发起一次 BFS，分量数 +1\n        }\n        return components;\n    }\n}\n","BFS_Components.java",[218,1997,1994],{"__ignoreMap":256},[56,1999,2001],{"title":2000,"type":59},"另一种解法",[17,2002,2003,2004,2007],{},"连通分量也可以用",[34,2005,2006],{},"并查集","做（每条边合并两端点，最后剩余集合数即答案），代码更短、常数更小。详见文末推荐阅读区。",[94,2009],{},[10,2011,2013],{"id":2012},"应用二判环问题","应用二：判环问题",[56,2015,2017],{"title":2016},"题目：LeetCode 684. 冗余连接",[17,2018,2019],{},"给一棵树多加了一条边变成图，找出这条多余的边。",[17,2021,2022,2024,2025,2028,2029,2032],{},[34,2023,519],{},"：对每条新边 ",[218,2026,2027],{"code":2027},"(u, v)","，加入前先 BFS 查询 ",[218,2030,2031],{"code":2031},"u → v"," 是否已经连通。已连通 ⇒ 该边形成环。",[258,2034,2039],{"className":2035,"code":2036,"filename":2037,"language":264,"meta":2038},[261],"class Solution {\n    public int[] findRedundantConnection(int[][] edges) {\n        int n = edges.length;\n        List\u003CList\u003CInteger>> graph = new ArrayList\u003C>();\n        for (int i = 0; i \u003C= n; i++) graph.add(new ArrayList\u003C>());\n\n        for (int[] edge : edges) {\n            int u = edge[0], v = edge[1];\n            if (isConnected(graph, u, v, n)) return edge;\n            graph.get(u).add(v);\n            graph.get(v).add(u);\n        }\n        return null;\n    }\n\n    private boolean isConnected(List\u003CList\u003CInteger>> graph, int u, int v, int n) {\n        boolean[] visited = new boolean[n + 1];\n        Deque\u003CInteger> queue = new ArrayDeque\u003C>();\n        queue.offer(u);\n        visited[u] = true;\n        while (!queue.isEmpty()) {\n            int cur = queue.poll();\n            if (cur == v) return true;\n            for (int next : graph.get(cur)) {\n                if (!visited[next]) {\n                    visited[next] = true;\n                    queue.offer(next);\n                }\n            }\n        }\n        return false;\n    }\n}\n","LC684_BFS.java","icon=tabler:link-off",[218,2040,2036],{"__ignoreMap":256},[56,2042,2044,2184],{"title":2043,"type":78},"判环：BFS 不是最优",[27,2045,2046,2113],{},[30,2047,2048,2051,2052,2112],{},[34,2049,2050],{},"无向图判环","：并查集是最优解（",[307,2053,2055,2083],{"className":2054},[310],[307,2056,2058],{"className":2057},[314],[316,2059,2060],{"xmlns":318},[320,2061,2062,2080],{},[323,2063,2064,2066,2068,2071,2073,2076,2078],{},[329,2065,412],{},[414,2067,417],{"stretchy":416},[329,2069,2070],{},"α",[414,2072,417],{"stretchy":416},[329,2074,2075],{},"n",[414,2077,423],{"stretchy":416},[414,2079,423],{"stretchy":416},[337,2081,2082],{"encoding":339},"O(\\alpha(n))",[307,2084,2086],{"className":2085,"ariaHidden":66},[344],[307,2087,2089,2092,2095,2098,2102,2105,2108],{"className":2088},[348],[307,2090],{"className":2091,"style":436},[352],[307,2093,412],{"className":2094,"style":440},[357,361],[307,2096,417],{"className":2097},[444],[307,2099,2070],{"className":2100,"style":2101},[357,361],"margin-right:0.0037em;",[307,2103,417],{"className":2104},[444],[307,2106,2075],{"className":2107},[357,361],[307,2109,2111],{"className":2110},[451],"))","）。",[30,2114,2115,2118,2119,969],{},[34,2116,2117],{},"有向图判环","：用拓扑排序——Kahn 算法（BFS）或 Tarjan 算法（DFS），都是 ",[307,2120,2122,2145],{"className":2121},[310],[307,2123,2125],{"className":2124},[314],[316,2126,2127],{"xmlns":318},[320,2128,2129,2143],{},[323,2130,2131,2133,2135,2137,2139,2141],{},[329,2132,412],{},[414,2134,417],{"stretchy":416},[329,2136,331],{},[414,2138,819],{},[329,2140,822],{},[414,2142,423],{"stretchy":416},[337,2144,929],{"encoding":339},[307,2146,2148,2172],{"className":2147,"ariaHidden":66},[344],[307,2149,2151,2154,2157,2160,2163,2166,2169],{"className":2150},[348],[307,2152],{"className":2153,"style":436},[352],[307,2155,412],{"className":2156,"style":440},[357,361],[307,2158,417],{"className":2159},[444],[307,2161,331],{"className":2162,"style":362},[357,361],[307,2164],{"className":2165,"style":362},[618],[307,2167,819],{"className":2168},[622],[307,2170],{"className":2171,"style":362},[618],[307,2173,2175,2178,2181],{"className":2174},[348],[307,2176],{"className":2177,"style":436},[352],[307,2179,822],{"className":2180,"style":716},[357,361],[307,2182,423],{"className":2183},[451],[17,2185,2186],{},"暴力 BFS\u002FDFS 判环只是基础解法，面试遇到优先用并查集或拓扑排序。",[94,2188],{},[10,2190,1332],{"id":2191},"总结-1",[56,2193,2195,2199],{"title":2194,"type":59,":card":66},"🎀 BFS 全景速记",[254,2196,2197],{"v-slot:title":256},[17,2198,2194],{},[2200,2201,2202,2218],"table",{},[2203,2204,2205],"thead",{},[2206,2207,2208,2212,2215],"tr",{},[2209,2210,2211],"th",{},"变种",[2209,2213,2214],{},"关键改动",[2209,2216,2217],{},"典型用途",[2219,2220,2221,2232,2244,2255,2266],"tbody",{},[2206,2222,2223,2226,2229],{},[2224,2225,183],"td",{},[2224,2227,2228],{},"队列 + visited",[2224,2230,2231],{},"遍历、连通性",[2206,2233,2234,2236,2241],{},[2224,2235,470],{},[2224,2237,2238,2239],{},"每轮记 ",[218,2240,480],{"code":480},[2224,2242,2243],{},"层序遍历、最短转换次数",[2206,2245,2246,2249,2252],{},[2224,2247,2248],{},"多源 BFS",[2224,2250,2251],{},"所有源点同时入队",[2224,2253,2254],{},"网格距离扩散",[2206,2256,2257,2260,2263],{},[2224,2258,2259],{},"01-BFS",[2224,2261,2262],{},"双端队列，0 头 1 尾",[2224,2264,2265],{},"边权 ∈ {0,1} 的最短路",[2206,2267,2268,2271,2274],{},[2224,2269,2270],{},"双向 BFS",[2224,2272,2273],{},"两端同时扩展，扩小集合",[2224,2275,2276],{},"状态空间巨大、终点已知",[14,2278,2279],{},[17,2280,2281],{},"BFS 是最朴素的图算法，也是最深刻的图算法——拓扑排序、最小生成树、最短路径的种子都埋在它的队列里。",[94,2283],{},[10,2285,2286],{"id":2286},"推荐阅读",{"title":256,"searchDepth":2288,"depth":2288,"links":2289},4,[2290,2292,2296,2299,2303,2308,2313,2318,2327,2332,2338,2339,2340,2341],{"id":12,"depth":2291,"text":12},2,{"id":98,"depth":2291,"text":99,"children":2293},[2294],{"id":133,"depth":2295,"text":133},3,{"id":182,"depth":2291,"text":183,"children":2297},[2298],{"id":186,"depth":2295,"text":187},{"id":190,"depth":2291,"text":191,"children":2300},[2301,2302],{"id":225,"depth":2295,"text":225},{"id":245,"depth":2295,"text":245},{"id":463,"depth":2291,"text":464,"children":2304},[2305,2306,2307],{"id":474,"depth":2295,"text":474},{"id":495,"depth":2295,"text":496},{"id":510,"depth":2295,"text":511},{"id":662,"depth":2291,"text":663,"children":2309},[2310,2311,2312],{"id":675,"depth":2295,"text":675},{"id":749,"depth":2295,"text":750},{"id":764,"depth":2295,"text":765},{"id":786,"depth":2291,"text":787,"children":2314},[2315,2316,2317],{"id":972,"depth":2295,"text":972},{"id":1014,"depth":2295,"text":1015},{"id":1042,"depth":2295,"text":1043},{"id":1083,"depth":2291,"text":1084,"children":2319},[2320,2321,2322,2323,2325,2326],{"id":1115,"depth":2295,"text":675},{"id":1212,"depth":2295,"text":1213},{"id":1229,"depth":2295,"text":1229},{"id":1260,"depth":2295,"text":2324},"为什么是 max(height, waterLine)？",{"id":1323,"depth":2295,"text":1323},{"id":1332,"depth":2295,"text":1332},{"id":1340,"depth":2291,"text":1341,"children":2328},[2329,2330,2331],{"id":1639,"depth":2295,"text":675},{"id":1673,"depth":2295,"text":1674},{"id":1691,"depth":2295,"text":1692},{"id":1880,"depth":2291,"text":1881,"children":2333},[2334,2335,2336,2337],{"id":1890,"depth":2295,"text":1891},{"id":1900,"depth":2295,"text":1901},{"id":1915,"depth":2295,"text":1916},{"id":1942,"depth":2295,"text":1943},{"id":1978,"depth":2291,"text":1979},{"id":2012,"depth":2291,"text":2013},{"id":2191,"depth":2291,"text":1332},{"id":2286,"depth":2291,"text":2286},[2343],"技术","2026-04-30 21:20:24","图论宽度优先遍历， 朴素，按层，01, 双向，应用。",false,"md",null,{"aside":2350,"slots":2353},[2351,2352],"toc","meta-aside-foo",{"aside-foo":2354},{"props":2355,"type":7,"value":2357},{":card":66,"card":256,"title":2356},"📚 进阶路径",[2358,2361,2387,2409,2411,2414,2418,2435,2438],[17,2359,2360],{},"本文未深入展开但密切相关：",[27,2362,2363,2364,2363,2369,2363,2375,2363,2381,2363],{},"\n",[30,2365,2366,2368],{},[34,2367,2006],{},"：连通分量与无向图判环的最优解",[30,2370,2371,2374],{},[34,2372,2373],{},"拓扑排序","（Kahn 算法）：BFS 在 DAG 上的优化版",[30,2376,2377,2380],{},[34,2378,2379],{},"Dijkstra","：边权非负但不限 0\u002F1 的最短路",[30,2382,2383,2386],{},[34,2384,2385],{},"A* 算法","：BFS + 启发式估价的进化形态\n::",[56,2388,2390,2393,2406],{"title":2389,"type":291},"🔗 并查集解法补充",[17,2391,2392],{},"连通分量与判环的并查集解法这里只用一句话提一下：",[27,2394,2363,2395,2363,2400,2363],{},[30,2396,2397,2399],{},[34,2398,126],{},"：每条边合并两端点，最后剩余集合数即答案。",[30,2401,2402,2405],{},[34,2403,2404],{},"判环","：合并两端点前，若已在同一集合 ⇒ 这条边构成环。",[17,2407,2408],{},"完整代码可移步并查集专题。",[94,2410],{},[10,2412,2413],{},"结语",[2415,2416,2417],"blur",{},"本篇是 BFS 的入门与拓展。",[17,2419,2420,2421,2424,2425,2424,2428,2431,2432],{},"由于篇幅有限，BFS 在 ",[34,2422,2423],{},"A*","、",[34,2426,2427],{},"ARC（自动机正则约束搜索）",[34,2429,2430],{},"Meet in the Middle"," 等场景下还有更精彩的玩法，后续有空再写。 ",[64,2433,2434],{":round":66},"共勉",[17,2436,2437],{},"如有问题欢迎留言交流！💖",[258,2439,2444],{"className":2440,"code":2442,"language":2347,"meta":2443},[2441],"language-md","\u003C!-- 你可以在此处书写大纲，并在上方完成文章 -->\n","wrap",[218,2445,2442],{"__ignoreMap":256},true,"\u002F2026",{"text":2449,"minutes":2450,"time":2451,"words":2452},"26 min read",25.405,1524300,5081,{"title":2454,"description":2345},[5],{"loc":2447},"posts\u002F2026\u002F[图论]-广度优先遍历及其拓展",[2458,67,71,2459],"C++","算法","tech","3YJNlvmmEnrAdk1IxuaefXl8qspjKI7imNPykqlODGo",[2463,2468],{"title":2464,"path":2465,"stem":2466,"date":2467,"type":2460,"children":-1},"内存池仿Nginx_C++实现","\u002F2026\u002Fnginx_c++","posts\u002F2026\u002F内存池仿Nginx_C++实现","2026-05-24 18:33:28",{"title":2469,"path":2447,"stem":2470,"date":2471,"type":2460,"children":-1},"二叉树专题重置","posts\u002F2026\u002F二叉树专题重置","2026-05-01 22:06:47",1779619501647]