LeetCode. 被围绕的区域(深搜 / 广搜 / 并查集)

给定一个二维的矩阵,包含 ‘X’ 和 ‘O’(字母 O)。
找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。

示例:
X X X X
X O O X
X X O X
X O X X
运行你的函数后,矩阵变为:
X X X X
X X X X
X X X X
X O X X

#include <vector>
#include <map>
#include <set>
using namespace std;
const int DirectionX[4] = { 0 , 0 , 1 , -1 };
const int DirectionY[4] = { 1 , -1 , 0 , 0 };
class Solution
{
//方法1:广搜/(深搜)
//1、首选BFS广搜矩阵边缘的数据,标记处与边缘'O'相连的所有'O',使用'.'标记
//2、遍历矩阵,将所有非'.'的修改为'X',将所有'.'的修改为'O'
public:
	void solve_1(vector<vector<char> >& board)
	{
		if (board.empty())return;

		//遍历左右边界,从上到下
		for (int i = 0; i < board.size(); ++i)
		{
			BFS(board, i, 0);
			BFS(board, i, board[0].size() - 1);
		}
		//遍历上下边界,从左到右
		for (int j = 0; j < board[0].size(); ++j)
		{
			BFS(board, 0, j);
			BFS(board, board.size() - 1, j);
		}

		//替换字符
		for (int i = 0; i < board.size(); i++)
			for (int j = 0; j < board[i].size(); j++)
				if (board[i][j] == '.')
					board[i][j] = 'O';
				else board[i][j] = 'X';
	}

private:
	void BFS(vector<vector<char> > & board, int x, int y) // 注意对board进行传引用,不然形参board的修改无法传到实参中
	{
		//坐标(x,y)是否在矩阵内
		//坐标(x,y)字符是否是'O'
		if (x >= 0 && x < board.size() && y >= 0 && y < board[x].size()
			&& board[x][y] == 'O')
		{
			board[x][y] = '.';
			for (int index = 0; index < 4; index++)
				BFS(board, x + DirectionX[index], y + DirectionY[index]);
		}
	}

//--------------------------------------------------------------
//方法2: 并查集(测试发现效率并不高,但如果数据量超级大的话,效率会比深搜广搜高吧)
//1、使用并查集合并所有相连接的‘O’,只需遍历一遍即可
//2、记录边界的‘O’的坐标在集合中的集合ID,存在set<pair<int, int>> boundOSet
//3、遍历非边界的所有‘O’,使用set<pair<int, int>> boundOSet.find()判断‘O’是否和边界的‘O’在同一个集合
public:
	void solve(vector<vector<char> > & board)
	{
		if (board.empty())return;
		//并查集,将相邻的'O'链接成一个集合
		DisJointSet disJointSet(board);
		for (int i = 0; i < board.size(); ++i)
			for (int j = 0; j < board[i].size(); ++j) {
				if (board[i][j] != 'O')continue;
				for (int index = 0; index < 4; ++index)
				{
					int newI = i + DirectionX[index];
					int newJ = j + DirectionY[index];
					if (newI < 0 || newJ < 0 || newI >= board.size() || newJ >= board[i].size())continue;
					if (board[newI][newJ] == 'O') 
						disJointSet.Union(make_pair(i, j), make_pair(newI, newJ));
				}
			}

		//记录边界上有'O'的坐标 在并查集中的 ID
		set<pair<int, int>> boundOSet;
		//遍历左右边界,从上到下
		for (int i = 0; i < board.size(); ++i)
		{
			if (board[i][0] == 'O')
				boundOSet.insert(disJointSet.Find(make_pair(i, 0)));
			if (board[i][board[0].size() - 1] == 'O')
				boundOSet.insert(disJointSet.Find(make_pair(i, board[0].size() - 1)));
		}
		//遍历上下边界,从左到右
		for (int j = 0; j < board[0].size(); ++j)
		{
			if (board[0][j] == 'O')
				boundOSet.insert(disJointSet.Find(make_pair(0, j)));
			if (board[board.size() - 1][j] == 'O')
				boundOSet.insert(disJointSet.Find(make_pair(board.size() - 1, j)));
		}

		//遍历非边缘的'O',判断与边缘'O'是否是一个集合
		for (int i = 1; i < board.size() - 1; ++i)
			for (int j = 1; j < board[i].size() - 1; ++j)
				if (board[i][j] == 'O')
					if (boundOSet.find(disJointSet.Find(make_pair(i, j))) == boundOSet.end())
						board[i][j] = 'X';
	}
};
//并查集类
class DisJointSet
{
public:
	DisJointSet(const vector<vector<char> >& board) {
		for (int i = 0; i < board.size(); ++i)
			for (int j = 0; j < board[i].size(); ++j) {
				_coordSet[make_pair(i, j)] = make_pair(i, j);
				_count[make_pair(i, j)] = 1;
			}
	}
	pair<int, int> Find(pair<int, int> coord) {
		while (_coordSet[coord] != coord)
		{
			_coordSet[coord] = _coordSet[_coordSet[coord]];
			coord = _coordSet[coord];
		}
		return coord;
	}
	void Union(pair<int, int> coord1, pair<int, int> coord2) {
		auto&& ID1 = Find(coord1);
		auto&& ID2 = Find(coord2);
		if (ID1 == ID2)return;
		if (_count[ID1] < _count[ID2]) {
			_coordSet[ID1] = ID2;
			_count[ID2] += _count[ID1];
		}
		else {
			_coordSet[ID2] = ID1;
			_count[ID1] += _count[ID2];
		}
		return;
	}

private:
	map<pair<int, int>, pair<int, int>> _coordSet;//(坐标,集合ID(以坐标为值))
	map<pair<int, int>, int> _count;			  //(集合ID(以坐标为值),个数)
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值