Leet Code题解 —— 1559. Detect Cycles in 2D Grid 检测二维无向图中的环
前言
找找感觉,记录一下个人解题思路系列。记录一下力扣里面的经典题目的个人想法。
一、题目描述
https://leetcode.com/problems/detect-cycles-in-2d-grid/
在二维无向图中检测环。如果有多个环,只要判断任意一个。
给定一个m*n的二维字符数组,请找出该数组中,是否存在由相同值构成的环。
一个环应该是一个长度不少于4的路径,该路径的起点和终点都在同一格内。
对任意给定格子来说,我们只能移动到其相邻格(仅限上、下、左、右四个方向,不包括斜角方向),同时,该相邻格应具有同当前格相同的值。
同时,你不能移动到你上一次曾移动过的各自中,例如:(1, 1) -> (1, 2) -> (1,1)不属于有效的环,因为对于(1, 2)来说,(1, 1)是上次访问过的格。
若存在相同值构成的环,则返回true,否则返回false。
1. 例一
输入: grid = [[“a”,“a”,“a”,“a”],[“a”,“b”,“b”,“a”],[“a”,“b”,“b”,“a”],[“a”,“a”,“a”,“a”]]
输出: true
解释: 如下图,有两个环。
2. 例二
输入: grid = [[“c”,“c”,“c”,“a”],[“c”,“d”,“c”,“c”],[“c”,“c”,“e”,“c”],[“f”,“c”,“c”,“c”]]
输出: true
解释: 如下图,有一个环。
3. 例三
输入: grid = [[“a”,“b”,“b”],[“b”,“z”,“b”],[“b”,“b”,“a”]]
输出: false
限制:
m == grid.length
n == grid[i].length
1 <= m <= 500
1 <= n <= 500
格内仅包含小写字母。
二、思路整理
1. 审题
可以看到,给的判断函数的原型是:bool containsCycle(vector<vector>& grid) {}
其中,输入的可以看做是一个二维的数组,数组内填充的是小写字母。
输出要求的只是判断是否存在环的结果。
一般环的判断,我们是基于联通图来做的。所以,这个二维的数组我们先需要把它转为连通图。那么整题的步骤分为:
- 将二维数组处理为连通图
- 判断连通图中是否有环
2. 分布实现步骤
2.1 将二维数组处理为连通图
由于原数组是二维数组,为了简化我们的处理逻辑,我们可以将二维的图处理为一维图。
我们可以采用一维链表来存储,并根据原图内数据(字母)处理其连通性,若:
m == grid.length
n == grid[i].length
i, j为原二维数组下标
新一维数组下标 index = i * n + j;
如下图,原表示为:
vector<vector> grid1 =
{
{'a','a','a','a'},
{'a','b','b','a'},
{'a','b','b','a'},
{'a','a','a','a'}
};
处理后的连通图为
0 : 1, 4;
1 : 0, 2;
2 : 1, 3;
3 : 2, 7;
4 : 0, 8;
5 : 9, 6;
6 : 5, 10;
7 : 3, 11;
8 : 4, 12;
9 : 5, 10;
10 : 6, 9;
11 : 7, 15;
12 : 8, 13;
13 : 12, 14;
14 : 13, 15;
15 : 11, 14;
2.2 判断连通图中是否有环
得到连通图后,本题即转换为基于一个无向图判断有环的问题。
判断图中的环,我们可以采用 DFS,深度优先检索来判断是否有环。
深度优先的通常写法是递归型的,即对现有的节点进行判断,并遍历其子节点,然后依次对子节点进行深度优先搜索。
遍历上述连通图,在遍历的过程中,我们只需判断某节点是否有一条边指向已访问过的节点,如果有,则表示存在环。但需注意:
- 判断已访问过的节点,不应该是当前节点的父节点(无向图两个节点间是互相可联通的,但这种父子节点间的连通性不被认为是环);
- 一个节点有三种状态,因此我们的判断应该是,遍历过程中如果发现某节点存在一条边指向非父节点的已被访问的灰色节点,则存在环;
- 已遍历完全(该节点所有的路径(或子节点)均已被遍历) —— 黑色
- 已被访问但未遍历完全 —— 灰色
- 未被访问 —— 白色
- 由于遍历是递归的,因此在某一次的子递归函数中存在环,则需要向上传递该判定结果
三、实现样例
class Solution {
private:
bool dfs(vector<vector<int>> &connected_graph, int &start_node){
bool has_cycle = false;
// flag the node has been visited
reached[start_node] = 1;
for (auto child : connected_graph[start_node]) {
if (reached[child] == 1 && child != parent_node[start_node]) {
has_cycle = true;
break;
}
else if (reached[child] == 0) {
parent_node[child] = start_node;
// pass the has_cycle result to upper layer
has_cycle = dfs(connected_graph, child);
if (has_cycle == true)
{
break;
}
}
}
// all the path is visited
reached[start_node] = 2;
return has_cycle;
}
// represent the all the nodes can be reached to this node
vector<int> reached;
vector<int> parent_node;
public:
bool containsCycle(vector<vector<char>>& grid) {
vector<vector<int>> connected_graph;
/******************************************/
/* Step 1. initialize the connected graph */
/******************************************/
connected_graph.resize(grid.size()*grid[0].size());
reached.resize(grid.size()*grid[0].size());
parent_node.resize(grid.size()*grid[0].size());
for(auto var : parent_node)
{
var = grid.size()*grid[0].size();
}
int i = 0;
int j = 0;
for(i=0; i<grid.size(); i++)
{
for(j=0; j<grid[0].size(); j++)
{
// calculator how many ways each block can access
if(i>0)
{
if(grid[i][j] == grid[i-1][j])
{
connected_graph[i*grid[0].size()+j].push_back((i-1)*grid[0].size()+j);
}
}
if(j>0)
{
if(grid[i][j] == grid[i][j-1])
{
connected_graph[i*grid[0].size()+j].push_back(i*grid[0].size()+j-1);
}
}
if(i<grid.size()-1)
{
if(grid[i][j] == grid[i+1][j])
{
connected_graph[i*grid[0].size()+j].push_back((i+1)*grid[0].size()+j);
}
}
if(j<grid[0].size()-1)
{
if(grid[i][j] == grid[i][j+1])
{
connected_graph[i*grid[0].size()+j].push_back(i*grid[0].size()+j+1);
}
}
}
}
/******************************************/
/* Step 2. judge there has cycle */
/******************************************/
bool has_cycle = false;
for (int k = 0; k < reached.size(); k++)
{
/* begin the dfs search beginning node */
if(reached[k] == 0)
{
// bfs search from node-k
has_cycle = dfs(connected_graph, k);
}
else
{
continue;
}
if(has_cycle == true){
return true;
}
}
return has_cycle;
}
};