由斜杠划分区域 图解题解
这道题到底在问什么
- 输入
- grid = [" /","/ "]
- 输出
- 2
- 输入
- grid = [" /"," "]
- 输出
- 1
- 输入
- grid = ["/\\","\\/"]
- 输出
- 5 (本课就用这个 2×2 例子)
最优解:为什么这么做
一句话答案:LeetCode 959 由斜杠划分区域:把每个格子拆成上、右、下、左四个三角,斜杠决定格内哪两对三角相连、相邻格再把挨着的边并起来,最后数连通块个数就是区域数,时间 O(n²·α)。
斜线把网格切成几块,到底在数什么
给一个 n×n 的网格 grid,每格是斜杠 /、反斜杠 \ 或空格。斜线把所在方块从中间切开,相邻方块之间没有线挡着的部分连成一片,问整张图被切出多少块互不相连的区域。题面第三个例子 grid = ["/\\","\\/"] 是 2×2 网格,四条斜线交错切成 5 块,答案 5。反斜杠在字符串里写成两个 \\,读出来仍是一个字符。
为什么不能顺着线直接圈出一块块区域
斜线画在方块内部,一条 / 把格子劈成左上、右下两半,这两半各自还可能跟上下左右的邻居连通。一块区域常横跨好几个格子、形状犬牙交错,想沿着线一圈圈描出来难框准。难在『连通』是跨格子的、『切开』却发生在格子里,两件事缠在一起。
把一个格子拆成四个三角这一步
把每个格子沿两条对角线剁成上、右、下、左四个三角。一条斜线就变成『格内某两对三角归一堆、另两对归另一堆』的规则。斜杠 / 连上三角和左三角、连右三角和下三角;反斜杠 \ 反过来连上右、连下左;空格没有线,四个三角全通成一块。
谁和谁连通用并查集记——它能把互相到达的三角随时归到同一组。起手 4n² 个三角各自成组,每合并一次组数减一。最后剩多少组,就是多少个连通分量,也就是图里互相连得到的一撮三角,对应多少块区域。
格子内部连完,还要缝上相邻格的边
光连格内不够,相邻两格挨着那条边上的两个贴脸三角本就是同一片。四个三角定死编号 0 上、1 右、2 下、3 左:当前格下边有格子,就把下三角 2 并到下邻格上三角 0;右边有格子,就把右三角 1 并到右邻格左三角 3。每格只往下、往右看,同一条公共边就不会被两边重复缝。
拿题面那张 2×2 图亲手并一遍
四个格子铺开:(0,0) 是 /、(0,1) 是 \、(1,0) 是 \、(1,1) 是 /,共 16 个三角、16 组。先看 (0,0):下、右都有格子,跟下邻、右邻各并一次,16 压到 14;再按 / 把上左、右下各并一次,降到 12。
(0,1) 的 \:右边到头,跟下邻并一次,再按 \ 连上右、连下左,到 9。(1,0) 的 \:下边到头,跟右邻并一次,再内部连两次,到 6。(1,1) 的 /:本该连的一对一查已在同一组、空转,组数不动;再连另一对减到 5。走完剩 5 组,正是题面要的 5。
转义符看走眼,所有反斜杠分支就全废了
三角总数 4n²,每格只做常数次合并,并查集带路径压缩后每步近似 O(1),总时间 O(n²·α),α 是增长极慢、可当常数看的反阿克曼函数;再用数组存这 4n² 个三角的归属,空间 O(n²)。
这几处最容易写坏。反斜杠在字符串里是两个 \、读出来是单个 \,若拿它跟两个字符比,所有 \ 分支都不触发,反斜杠格全连错。格间那步最容易漏:只连格内、不缝相邻格的边,本该连成一片的会被拆成好几块,答案偏大。编号也得对死,跟斜线连法一错位,同块被劈开、异块被硬凑一起。
两个小边界顺带验:整张图就一个空格,四个三角全通、只算 1 块;一个格子里只有一条斜线,切成 2 块——贴外框的单条线封不出新区域,得靠格与格接力才切出更多块。
▶ 动画逐步走查(共 22 步)——想跟着动画一帧帧对照就展开
- 3核心一句话:每格拆 4 三角,格内按斜杠连、格间按相邻连,最后数连通块。下面每帧都在套它。
- 4左上 / · 右上 \ · 左下 \ · 右下 /先看清输入。四个格子各有一条斜线:左上和右下是斜杠 /,右上和左下是反斜杠 \。眼睛先想象一下这四条线把图切成了几块,等会用并查集把它精确数出来。
- 5每格 4 三角,共 16 个第一步:把每个格子拆成 4 个三角,围成一个菱形,上是 0、右是 1、下是 2、左是 3,整张图一共 16 个三角。一开始谁也不连谁,连通块数就是 16。接下来一格一格地合并。
- 6编号 0 1 2 3轮到第 (0,0) 个格子,它的四个三角编号是 0、1、2、3。这格写的是 斜杠 /,它把「上和左」连成一块、「右和下」连成另一块。先看它和邻居的连接,再按这条斜线连内部。
- 7连通块 16 → 15三角 2 是当前格的下三角,贴着下方格子的上三角 8,中间没有斜线,必然连通,合并。两块并成一块,连通块数从 16 减到 15。
- 8连通块 15 → 14三角 1 是当前格的右三角,贴着右边格子的左三角 7,中间没有斜线,必然连通,合并。两块并成一块,连通块数从 15 减到 14。
- 9连通块 14 → 13按这条斜线,格内三角 0 和 3 属于同一块,合并。两块并成一块,连通块数从 14 减到 13。
- 10连通块 13 → 12按这条斜线,格内三角 1 和 2 属于同一块,合并。两块并成一块,连通块数从 13 减到 12。
- 11编号 4 5 6 7轮到第 (0,1) 个格子,它的四个三角编号是 4、5、6、7。这格写的是 反斜杠 \,它把「上和右」连成一块、「下和左」连成另一块。先看它和邻居的连接,再按这条斜线连内部。
- 12连通块 12 → 11三角 6 是当前格的下三角,贴着下方格子的上三角 12,中间没有斜线,必然连通,合并。两块并成一块,连通块数从 12 减到 11。
- 13连通块 11 → 10按这条斜线,格内三角 4 和 5 属于同一块,合并。两块并成一块,连通块数从 11 减到 10。
- 14连通块 10 → 9按这条斜线,格内三角 6 和 7 属于同一块,合并。两块并成一块,连通块数从 10 减到 9。
- 15当前连通块 9上面一行两个格子处理完了,合并了几次之后,连通块数现在是 9。还剩下一行没处理,继续往下走。
- 16编号 8 9 10 11轮到第 (1,0) 个格子,它的四个三角编号是 8、9、10、11。这格写的是 反斜杠 \,它把「上和右」连成一块、「下和左」连成另一块。先看它和邻居的连接,再按这条斜线连内部。
- 17连通块 9 → 8三角 9 是当前格的右三角,贴着右边格子的左三角 15,中间没有斜线,必然连通,合并。两块并成一块,连通块数从 9 减到 8。
- 18连通块 8 → 7按这条斜线,格内三角 8 和 9 属于同一块,合并。两块并成一块,连通块数从 8 减到 7。
- 19连通块 7 → 6按这条斜线,格内三角 10 和 11 属于同一块,合并。两块并成一块,连通块数从 7 减到 6。
- 20编号 12 13 14 15轮到第 (1,1) 个格子,它的四个三角编号是 12、13、14、15。这格写的是 斜杠 /,它把「上和左」连成一块、「右和下」连成另一块。先看它和邻居的连接,再按这条斜线连内部。
- 21连通块仍 6要连三角 12 和 15,可一查它们已经在同一个连通块里了,这一步什么都不做,连通块数保持 6。这正是并查集省事的地方:重复的连接自动被忽略,不会重复计数。
- 22连通块 6 → 5按这条斜线,格内三角 13 和 14 属于同一块,合并。两块并成一块,连通块数从 6 减到 5。
- 23区域数 = 5四个格子全部处理完。屏幕上同色的三角属于同一个区域,数一数一共有 5 种颜色,也就是 5 个连通块。这张图被斜线切成了 5 块,答案就是 5。
- 24答案 = 5再回看一遍:每一种颜色就是一块独立区域。斜杠和反斜杠在中间交错,把网格切出了 5 块互不相连的区域。最终答案 5。
⚠️ 容易写错的地方
✗ 错:反斜杠当成一个字符 \ 来判断
✓ 对:注意字符串里反斜杠写成两个 \\,但取出的单字符仍是一个 \
搞错转义会让所有 \ 分支失效,区域数算错
✗ 错:只连格内、忘了连格间
✓ 对:相邻格的右↔左、下↔上必须合并
漏掉格间连接会把本该相连的区域算成多块,答案偏大
✗ 错:三角方向编号和斜线规则对不上
✓ 对:定死 0上1右2下3左:/ 连 0-3 与 1-2,\ 连 0-1 与 2-3
编号或连法错位,会把同块拆开或把异块并起来
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def regionsBySlashes(self, grid: List[str]) -> int:
def find(x):
if p[x] != x:
p[x] = find(p[x])
return p[x]
def union(a, b):
pa, pb = find(a), find(b)
if pa != pb:
p[pa] = pb
nonlocal size
size -= 1
n = len(grid)
size = n * n * 4
p = list(range(size))
for i, row in enumerate(grid):
for j, v in enumerate(row):
k = i * n + j
if i < n - 1:
union(4 * k + 2, (k + n) * 4)
if j < n - 1:
union(4 * k + 1, (k + 1) * 4 + 3)
if v == '/':
union(4 * k, 4 * k + 3)
union(4 * k + 1, 4 * k + 2)
elif v == '\\':
union(4 * k, 4 * k + 1)
union(4 * k + 2, 4 * k + 3)
else:
union(4 * k, 4 * k + 1)
union(4 * k + 1, 4 * k + 2)
union(4 * k + 2, 4 * k + 3)
return sizeC++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <optional>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
class Solution {
public:
vector<int> p;
int size;
int regionsBySlashes(vector<string>& grid) {
int n = grid.size();
size = n * n * 4;
p.resize(size);
for (int i = 0; i < size; ++i) p[i] = i;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
int k = i * n + j;
if (i < n - 1) merge(4 * k + 2, (k + n) * 4);
if (j < n - 1) merge(4 * k + 1, (k + 1) * 4 + 3);
char v = grid[i][j];
if (v == '/') {
merge(4 * k, 4 * k + 3);
merge(4 * k + 1, 4 * k + 2);
} else if (v == '\\') {
merge(4 * k, 4 * k + 1);
merge(4 * k + 2, 4 * k + 3);
} else {
merge(4 * k, 4 * k + 1);
merge(4 * k + 1, 4 * k + 2);
merge(4 * k + 2, 4 * k + 3);
}
}
}
return size;
}
void merge(int a, int b) {
int pa = find(a);
int pb = find(b);
if (pa == pb) return;
p[pa] = pb;
--size;
}
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
};Java
import java.util.*;
class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(){} TreeNode(int val){this.val=val;} TreeNode(int val, TreeNode left, TreeNode right){this.val=val;this.left=left;this.right=right;} }
class ListNode { int val; ListNode next; ListNode(){} ListNode(int val){this.val=val;} ListNode(int val, ListNode next){this.val=val;this.next=next;} }
class Solution {
private int[] p;
private int size;
public int regionsBySlashes(String[] grid) {
int n = grid.length;
size = n * n * 4;
p = new int[size];
for (int i = 0; i < p.length; ++i) {
p[i] = i;
}
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
int k = i * n + j;
if (i < n - 1) {
union(4 * k + 2, (k + n) * 4);
}
if (j < n - 1) {
union(4 * k + 1, (k + 1) * 4 + 3);
}
char v = grid[i].charAt(j);
if (v == '/') {
union(4 * k, 4 * k + 3);
union(4 * k + 1, 4 * k + 2);
} else if (v == '\\') {
union(4 * k, 4 * k + 1);
union(4 * k + 2, 4 * k + 3);
} else {
union(4 * k, 4 * k + 1);
union(4 * k + 1, 4 * k + 2);
union(4 * k + 2, 4 * k + 3);
}
}
}
return size;
}
private int find(int x) {
if (p[x] != x) {
p[x] = find(p[x]);
}
return p[x];
}
private void union(int a, int b) {
int pa = find(a);
int pb = find(b);
if (pa == pb) {
return;
}
p[pa] = pb;
--size;
}
}复杂度
时间
O(n²·α)
4n² 个三角,常数次合并,带路径压缩的并查集近似线性,α 为反阿克曼函数可视作常数
空间
O(n²)
父数组存 4n² 个三角的归属
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 由斜杠划分区域 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
不用并查集,还能怎么数这些区域?+
可以把每个格子放大成 3×3 的小方块,用 0 表示能走、1 表示斜线占住的格,整张图就变成一个 3n×3n 的 01 网格,再对连成片的 0 做洪水填充或广度优先搜索,数一数有几摊。思路更直白,代价是要多开九倍大的辅助矩阵,常数大不少。
三角编号非得 0 上 1 右 2 下 3 左吗?+
不非得,任选一套自洽的就行。但顺序一旦定死,斜杠该连哪两对三角、右邻格用哪个三角对上当前格,全得跟着这套编号重新推一遍;换了编号却忘了同步连法,就会把本不相连的三角并到一起,或把该并的漏掉。
空格为什么要把四个三角全连起来?+
空格里没有任何斜线挡路,格子内部是完全通着的一片,所以上、右、下、左四个三角属于同一块,得两两并成一组。要是漏了这步、让空格格的四个三角各自为政,就会凭空多数出几块根本不存在的区域,答案偏大。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 由斜杠划分区域 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。