作者推荐
动态规划的时间复杂度优化
本文涉及知识点
分类讨论 割点
LeetCode1568. 使陆地分离的最少天数
给你一个大小为 m x n ,由若干 0 和 1 组成的二维网格 grid ,其中 1 表示陆地, 0 表示水。岛屿 由水平方向或竖直方向上相邻的 1 (陆地)连接形成。
如果 恰好只有一座岛屿 ,则认为陆地是 连通的 ;否则,陆地就是 分离的 。
一天内,可以将 任何单个 陆地单元(1)更改为水单元(0)。
返回使陆地分离的最少天数。
示例 1:
输入:grid = [[0,1,1,0],[0,1,1,0],[0,0,0,0]]
输出:2
解释:至少需要 2 天才能得到分离的陆地。
将陆地 grid[1][1] 和 grid[0][2] 更改为水,得到两个分离的岛屿。
示例 2:
输入:grid = [[1,1]]
输出:2
解释:如果网格中都是水,也认为是分离的 ([[1,1]] -> [[0,0]]),0 岛屿。
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 30
grid[i][j] 为 0 或 1
分类讨论
岛屿数只要不为1都是分离的陆地。
岛屿数 = 连通区域 - 水单元数。
一个岛屿只有一块陆地或两块陆地,无法分割,只能花一天或两天变成0岛屿。
3块陆地的岛屿只需要一天就可以分割。由于是4连接,无法两两相连。
4块陆地的岛屿一定可以两天分离,右上的那块陆地 右边和上边没有连接,最坏的情况把左下的两块陆地消掉,右上的陆地和余下的陆地就成了两个岛屿。
如何查看岛屿数量是否为1
并集查找后,看各陆地的是否是同一连通区域。
如果计算能否一天搞定
枚举各陆地,删除后看能否符合题意。
大致思路
一,岛屿数量是否为1,如果不是返回0.
二,枚举各陆地,删除,如果岛屿数量不为1,返回1。
三,返回2。
割点
一,岛屿数量是否为1,如果不是返回0.
二,如果只有1块或2块陆地,直接陆地数量。
三,如果存在割点,返回1。
四,返回2。
代码
核心代码
class CEnumGridEdge
{
public:
void Init()
{
for (int r = 0; r < m_r; r++)
{
for (int c = 0; c < m_c; c++)
{
Move(r, c, r + 1, c);
Move(r, c, r - 1, c);
Move(r, c, r, c + 1);
Move(r, c, r, c - 1);
}
}
}
const int m_r, m_c;
protected:
CEnumGridEdge(int r, int c) :m_r(r), m_c(c)
{
}
void Move(int preR, int preC, int r, int c)
{
if ((r < 0) || (r >= m_r))
{
return;
}
if ((c < 0) || (c >= m_c))
{
return;
}
OnEnumEdge(preR, preC, r, c);
};
virtual void OnEnumEdge(int preR, int preC, int r, int c) = 0;
};
//割点
class CCutPoint
{
public:
CCutPoint(const vector<vector<int>>& vNeiB) : m_iSize(vNeiB.size())
{
m_vTime.assign(m_iSize, -1);
m_vVisitMin.assign(m_iSize, -1);
for (int i = 0; i < m_iSize; i++)
{
if (-1 != m_vTime[i])
{
continue;
}
m_iRegionCount++;
dfs(i, -1, vNeiB);
}
}
int RegionCount()const
{
return m_iRegionCount;
}
vector<int> CutPoints()const
{
return m_vCutPoints;
}
protected:
void dfs(int cur, int parent, const vector<vector<int>>& vNeiB)
{
auto& curTime = m_vTime[cur];
auto& visitMin = m_vVisitMin[cur];
curTime = m_iTime++;
visitMin = curTime;
int iMax = -1;
int iChildNum = 0;
for (const auto& next : vNeiB[cur])
{
if (next == parent)
{
continue;
}
if (-1 != m_vTime[next])
{
visitMin = min(visitMin, m_vTime[next]);
continue;
}
iChildNum++;
dfs(next, cur, vNeiB);
visitMin = min(visitMin, m_vVisitMin[next]);
iMax = max(iMax, m_vVisitMin[next]);
}
if (-1 == parent)
{
if (iChildNum >= 2)
{
m_vCutPoints.emplace_back(cur);
}
}
else
{
if (iMax >= curTime)
{
m_vCutPoints.emplace_back(cur);
}
}
}
vector<int> m_vTime;//各节点到达时间,从0开始。 -1表示未处理
vector<int> m_vVisitMin;//
int m_iTime = 0;
int m_iRegionCount = 0;
vector<int> m_vCutPoints;
const int m_iSize;
};
class CNeiBo : public CEnumGridEdge
{
public:
CNeiBo(const vector<vector<int>>& grid, int r, int c):CEnumGridEdge(r,c), m_iMaskCount(r*c), m_grid(grid)
{
m_vNeiBo.resize(m_iMaskCount);
Init();
}
// 通过 CEnumGridEdge 继承
virtual void OnEnumEdge(int preR, int preC, int r, int c) override
{
if (m_grid[preR][preC] && m_grid[r][c])
{
const int iMask = m_c * r + c;
const int iPre = m_c * preR + preC;
m_vNeiBo[iPre].emplace_back(iMask);
}
}
const int m_iMaskCount;
vector<vector<int>> m_vNeiBo;
const vector<vector<int>>& m_grid;
};
class Solution {
public:
int minDays(vector<vector<int>>& grid) {
CNeiBo neiBo(grid, grid.size(), grid[0].size());
CCutPoint cut(neiBo.m_vNeiBo);
int iZeroCount = 0;
for (const auto& v : grid)
{
iZeroCount += std::count(v.begin(), v.end(), 0);
}
if (1 != cut.RegionCount()- iZeroCount)
{
return 0;
}
if (neiBo.m_iMaskCount - iZeroCount <= 2)
{
return neiBo.m_iMaskCount - iZeroCount;
}
if (cut.CutPoints().size())
{
return 1;
}
return 2;
}
};
测试用例
template<class T,class T2>
void Assert(const T& t1, const T2& t2)
{
assert(t1 == t2);
}
template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
if (v1.size() != v2.size())
{
assert(false);
return;
}
for (int i = 0; i < v1.size(); i++)
{
Assert(v1[i], v2[i]);
}
}
int main()
{
vector<vector<int>> grid;
{
Solution sln;
grid = { {0,1,1,0},{0,1,1,0},{0,0,0,0} };
auto res = sln.minDays(grid);
Assert(2, res);
}
{
Solution sln;
grid = { {0},{0} };
auto res = sln.minDays(grid);
Assert(0, res);
}
{
Solution sln;
grid = { {1},{1} ,{1} };
auto res = sln.minDays(grid);
Assert(1, res);
}
{
Solution sln;
grid = { {1},{1} };
auto res = sln.minDays(grid);
Assert(2, res);
}
{
Solution sln;
grid = { {1} };
auto res = sln.minDays(grid);
Assert(1, res);
}
}
2023年4月版
class Solution {
public:
int minDays(vector<vector>& grid) {
m_r = grid.size();
m_c = grid[0].size();
int iTotal = 0;
for (int r = 0; r < m_r; r++)
{
for (int c = 0; c < m_c; c++)
{
if (1 == grid[r][c])
{
iTotal++;
}
}
}
if (iTotal < 2)
{
return iTotal;
}
if (iTotal != AnyAUnionNum(grid))
{
return 0;
}
for (int r = 0; r < m_r; r++)
{
for (int c = 0; c < m_c; c++)
{
if (0 == grid[r][c])
{
continue;
}
grid[r][c] = 0;
if (iTotal != 1+AnyAUnionNum(grid))
{
return 1;
}
grid[r][c] = 1;
}
}
return 2;
}
int AnyAUnionNum(const vector<vector>& grid)
{
int iNum = 0;
for (int r = 0; r < m_r;r++ )
{
for (int c = 0; c < m_c; c++ )
{
if (1 == grid[r][c])
{
return NeiBNum(r, c, grid);
}
}
}
return 0;
}
int NeiBNum(int iR, int iC, const vector<vector>& grid)
{
std::unordered_set setRC;
queue<pair<int, int>> que;
setRC.emplace(iR*100+iC);
que.emplace(iR, iC);
while (que.size())
{
const auto it = que.front();
que.pop();
auto Add = [&](int r,int c)
{
if ((r < 0) || (r >= m_r))
{
return;
}
if ((c < 0) || (c >= m_c))
{
return;
}
if (1 != grid[r][c])
{
return;
}
int iRCMask = 100 * r + c;
if (setRC.count(iRCMask))
{
return;
}
setRC.emplace(iRCMask);
que.emplace(r, c);
};
Add(it.first + 1, it.second);
Add(it.first - 1, it.second);
Add(it.first, it.second + 1);
Add(it.first, it.second - 1);
}
return setRC.size();
}
int m_r, m_c;
vector<vector> m_vNeiNum;
};
扩展阅读
视频课程
有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
相关下载
想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653
我想对大家说的话 |
---|
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。