算法课第4周第1题——417. Pacific Atlantic Water Flow

题目描述:

Given an m x n matrix of non-negative integers representing the height of each unit cell in a continent, the "Pacific ocean" touches the left and top edges of the matrix and the "Atlantic ocean" touches the right and bottom edges.

Water can only flow in four directions (up, down, left, or right) from a cell to another one with height equal or lower.

Find the list of grid coordinates where water can flow to both the Pacific and Atlantic ocean.

Note:

  1. The order of returned grid coordinates does not matter.
  2. Both m and n are less than 150.

Example:

Given the following 5x5 matrix:

  Pacific ~   ~   ~   ~   ~ 
       ~  1   2   2   3  (5) *
       ~  3   2   3  (4) (4) *
       ~  2   4  (5)  3   1  *
       ~ (6) (7)  1   4   5  *
       ~ (5)  1   1   2   4  *
          *   *   *   *   * Atlantic

Return:

[[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]] (positions with parentheses in above matrix).

解法一(dfs):

程序代码1:

class Solution {
public:
	// 用于explore的dfs函数
	void dfs(int x, int y, int m, int n, vector<vector<bool>> &visit, vector<vector<int>>& matrix, int pre) {
		// 若超过边界、已经访问过、比前一个访问的值(即pre)小,都直接本结束函数
		if (x < 0 || x > m - 1 || y < 0 || y > n - 1 || visit[x][y] || matrix[x][y] < pre) {
			return;
		}

		// visit标记为true表示已经访问过,此外,visit传入的参数为pacificVisit或atlanticVisit
		// pacificVisit为true表示可以连接到pacific的边界
		// atlanticVisit为true表示可以连接到atlantic的边界(因为都是从边界出发开始dfs的)
		visit[x][y] = true;

		// 对四周的点进行dfs
		dfs(x - 1, y, m, n, visit, matrix, matrix[x][y]);
		dfs(x + 1, y, m, n, visit, matrix, matrix[x][y]);
		dfs(x, y - 1, m, n, visit, matrix, matrix[x][y]);
		dfs(x, y + 1, m, n, visit, matrix, matrix[x][y]);
	}


	vector<pair<int, int>> pacificAtlantic(vector<vector<int>>& matrix) {
		// 返回用的向量
		vector<pair<int, int>> result;

		// 特殊情况,判断matrix是否为空
		if (matrix.empty()) {
			return result;
		}

		// 得出图的高和宽
		int m = matrix.size();
		int n = matrix[0].size();

		// 两个visit数组,表示访问过的同时,还可以表示能与pacific或atlantic的边界相连
		vector<vector<bool>> pacificVisit;
		vector < vector<bool>> atlanticVisit;

		// 将visit数组置位空
		for (int i = 0; i < m; i++) {
			vector<bool> v;
			for (int j = 0; j < n; j++) {
				v.push_back(false);
			}
			pacificVisit.push_back(v);
			atlanticVisit.push_back(v);
		}

		// 从边界出发,因为边界的点一定连接pacific或atlantic
		// 之后每次dfs访问的点,如果能比前一访问点更大,就一定能连接到边界
		// 一开始传入pre的参数用-1,保证一开始的点符合大于前一值的要求(边界点没有前一个访问点)
		for (int i = 0; i < m; i++) {
			dfs(i, 0, m, n, pacificVisit, matrix, -1);
			dfs(i, n - 1, m, n, atlanticVisit, matrix, -1);
		}

		for (int i = 0; i < n; i++) {
			dfs(0, i, m, n, pacificVisit, matrix, -1);
			dfs(m - 1, i, m, n, atlanticVisit, matrix, -1);
		}

		// 做完dfs后,若pacificVisit和atlanticVisit都为true的点即可同时连到两个海
		for (int i = 0; i < m; i++) {
			for (int j = 0; j < n; j++) {
				if (pacificVisit[i][j] && atlanticVisit[i][j]) {
					pair<int, int> p(i, j);
					result.push_back(p);
				}
			}
		}
		return result;
	}
};

简要题解1:

(代码中的关键部分也有注释给出了部分解释)

本题是一道有关图的问题。我的解法一采用的是DFS的方法。

本题的题意大致就是:图中每个点代表一个高度,水只可以从高处流向等高或更低处。图的左上是太平洋(Pacific),右下是大西洋(Atlantic),求出图中所有可以同时连通到太平洋和大西洋的点。

其实一开始我写的是更“标准”一些的DFS算法,如下:

class Solution {
public:
	// 用于dfs的函数1,表示能连通Pacific
	bool canToPacific(int x, int y, int m, int n, vector<vector<bool>> &visit, vector<vector<int>>& matrix) {
		// 若访问过,则不需再访问
		if (visit[x][y]) {
			return false;
		}

		// 设为访问过
		visit[x][y] = true;
		if (x == 0 || y == 0) {
			// visit置回false
			visit[x][y] = false;
			return true;
		}
		else {
			// 四个flag值分别表示该点四周的点dfs后得到的bool值,初始化为false
			bool flag1, flag2, flag3, flag4;
			flag1 = flag2 = flag3 = flag4 = false;

			// 注意做边界处理
			if (x != m - 1 && matrix[x + 1][y] <= matrix[x][y]) {
				flag1 = canToPacific(x + 1, y, m, n, visit, matrix);
			}

			if (y != n - 1 && matrix[x][y + 1] <= matrix[x][y]) {
				flag2 = canToPacific(x, y + 1, m, n, visit, matrix);
			}

			if (matrix[x - 1][y] <= matrix[x][y]) {
				flag3 = canToPacific(x - 1, y, m, n, visit, matrix);
			}

			if (matrix[x][y - 1] <= matrix[x][y]) {
				flag4 = canToPacific(x, y - 1, m, n, visit, matrix);
			}
			
			// visit置回false
			visit[x][y] = false;
			return flag1 || flag2 || flag3 || flag4;
		}
	}

	// 用于dfs的函数2,表示能连通Atlantic
	bool canToAtlantic(int x, int y, int m, int n, vector<vector<bool>> &visit, vector<vector<int>>& matrix) {
		// 若访问过,则不需再访问
		if (visit[x][y]) {
			return false;
		}

		// 设为访问过
		visit[x][y] = true;
		if (x == m - 1 || y == n - 1) {
			// visit置回false
			visit[x][y] = false;
			return true;
		}
		else {
			bool flag1, flag2, flag3, flag4;
			flag1 = flag2 = flag3 = flag4 = false;

			if (x != 0 && matrix[x - 1][y] <= matrix[x][y]) {
				flag1 = canToAtlantic(x - 1, y, m, n, visit, matrix);
			}

			if (y != 0 && matrix[x][y - 1] <= matrix[x][y]) {
				flag2 = canToAtlantic(x, y - 1, m, n, visit, matrix);
			}

			if (matrix[x + 1][y] <= matrix[x][y]) {
				flag3 = canToAtlantic(x + 1, y, m, n, visit, matrix);
			}

			if (matrix[x][y + 1] <= matrix[x][y]) {
				flag4 = canToAtlantic(x, y + 1, m, n, visit, matrix);
			}

			// visit置回false
			visit[x][y] = false;
			return flag1 || flag2 || flag3 || flag4;
		}
	}

	vector<pair<int, int>> pacificAtlantic(vector<vector<int>>& matrix) {
		vector<pair<int, int>> result;

		// 特殊处理,若矩阵为空
		if (matrix.empty()) {
			return result;
		}

		// 得出图的高和宽
		int m = matrix.size();
		int n = matrix[0].size();

		// 初始化visit数组
		vector<vector<bool>> visit;
		for (int i = 0; i < m; i++) {
			vector<bool> v;
			for (int j = 0; j < n; j++) {
				v.push_back(false);
			}
			visit.push_back(v);
		}

		// dfs判断能否同时到达pacific和atlantic
		for (int i = 0; i < m; i++) {
			for (int j = 0; j < n; j++) {
				if (canToPacific(i, j, m, n, visit, matrix) && canToAtlantic(i, j, m, n, visit, matrix)) {
					pair<int, int> p(i, j);
					result.push_back(p);
				}
			}
		}

		return result;
	}
};

在这里我用了通常的dfs算法,图中每一个点进行遍历并用dfs进行处理,然后对每一个点判断能否同时连通两个海。这个算法应该是没有什么问题,但是效率不够高,超时了。于是我就开始考虑更有效率的解法,最终得到的就是程序代码1中的内容。

我的考虑大概是这样的:原算法之所以效率不够,就是因为对每个点都运用了dfs(因为要找出所有可以连通两个海的点,而不是只找出一条最短路径,因此在dfs中每次都要将那些置为true的visit值重新置为false,这也导致了每个点基本上都会被访问多次)。而实际上,找到一个连通到海的点a,它四周的某个点b若比这个点a更高,则点b一定可以连通到这个海,因此每次运用dfs时应该充分利用之前dfs得到的结果,来使算法更为效率。

于是我重新改进了dfs函数,在dfs函数中增加了一个pre值,表示前一个访问点的高度。然后,将之前对图中所有点遍历开始dfs,改为从横向和纵向的边界处开始dfs(因为边界处的点至少都肯定能连通到其中一个海),对于边界处的点,其传入dfs函数的pre值为-1,保证其值比实际上不存在的“前一个访问点”更高。从边界点开始,依次向四周访问,只要新的一个访问点比前一个访问点更高或更高(而前一个点能连通到一个海),就说明这个新的访问点同样能连通到这个海。在dfs函数中,若一个点超过了图的边界、已经访问过、或是比前一个访问值更小,都会结束dfs函数。此外,这样改进后的,传入dfs函数的visit数组除了表示访问过某点,还可以表示该点能连通到某个海,因此我设置了两个visit数组,分别是pacificVisit和atlanticVisit,这样经过dfs的递归后,最终若某点的这两个visit值都为true,则说明该点可以同时连接到两个海。


解法二(bfs):

程序代码2:

class Solution {
public:
	// bfs函数
	void bfs(int m, int n, vector<vector<bool>> &visit, vector<vector<int>>& matrix, queue<pair<int, int>> q) {
		// visit标记为true表示已经访问过,此外,visit传入的参数为pacificVisit或atlanticVisit
		// pacificVisit为true表示可以连接到pacific的边界
		// atlanticVisit为true表示可以连接到atlantic的边界(因为都是从边界出发开始dfs的)
		while (!q.empty()) {
			pair<int, int> p = q.front();
			q.pop();
			int x = p.first;
			int y = p.second;

			// 入队需要未访问过、不超过边界、比前一个访问的值大
			if (x + 1 < m && !visit[x + 1][y] && matrix[x + 1][y] >= matrix[x][y]) {
				visit[x + 1][y] = true;
				pair<int, int> p2(x + 1, y);
				q.push(p2);
			}

			if (x - 1 >= 0 && !visit[x - 1][y] && matrix[x - 1][y] >= matrix[x][y]) {
				visit[x - 1][y] = true;
				pair<int, int> p2(x - 1, y);
				q.push(p2);
			}				
			
			if (y + 1 < n && !visit[x][y + 1] && matrix[x][y + 1] >= matrix[x][y]) {
				visit[x][y + 1] = true;
				pair<int, int> p2(x, y + 1);
				q.push(p2);
			}

			if (y - 1 >= 0 && !visit[x][y - 1] && matrix[x][y - 1] >= matrix[x][y]) {
				visit[x][y - 1] = true;
				pair<int, int> p2(x, y - 1);
				q.push(p2);
			}
		}
	}


	vector<pair<int, int>> pacificAtlantic(vector<vector<int>>& matrix) {
		// 返回用的向量
		vector<pair<int, int>> result;

		// 特殊情况,判断matrix是否为空
		if (matrix.empty()) {
			return result;
		}

		// 得出图的高和宽
		int m = matrix.size();
		int n = matrix[0].size();

		// 两个visit数组,表示访问过的同时,还可以表示能与pacific或atlantic的边界相连
		vector<vector<bool>> pacificVisit;
		vector < vector<bool>> atlanticVisit;

		// 将visit数组置位空
		for (int i = 0; i < m; i++) {
			vector<bool> v;
			for (int j = 0; j < n; j++) {
				v.push_back(false);
			}
			pacificVisit.push_back(v);
			atlanticVisit.push_back(v);
		}

		// 两个队列用于bfs
		queue<pair<int, int>> q1;
		queue<pair<int, int>> q2;
		for (int i = 0; i < m; i++) {
			// 开始点的visit值置位true
			pacificVisit[i][0] = true;
			atlanticVisit[i][n - 1] = true;

			pair<int, int> p1(i, 0);
			pair<int, int> p2(i, n - 1);
			q1.push(p1);
			q2.push(p2);
		}
		
		for (int i = 0; i < n; i++) {
			// 开始点的visit值置位true
			pacificVisit[0][i] = true;
			atlanticVisit[m - 1][i] = true;

			pair<int, int> p1(0, i);
			pair<int, int> p2(m - 1, i);
			q1.push(p1);
			q2.push(p2);
		}

		bfs(m, n, pacificVisit, matrix, q1);
		bfs(m, n, atlanticVisit, matrix, q2);


		// 做完bfs后,若pacificVisit和atlanticVisit都为true的点即可同时连到两个海
		for (int i = 0; i < m; i++) {
			for (int j = 0; j < n; j++) {
				if (pacificVisit[i][j] && atlanticVisit[i][j]) {
					pair<int, int> p(i, j);
					result.push_back(p);
				}
			}
		}
		return result;
	}
};

简要题解2:

有了上面dfs的解法思路,很容易也就可以得到bfs版本的题解。同样是从边界点开始,将边界点放入两个队列(两个队列分别存放两个海的边界点)并将边界点的pacificVisit或atlanticVisit值置为true(哪个置为true取决于该边界点是哪个海的边界),之后就可以使用bfs进行处理,注意每次将新点放入队列时,要判断其必须未访问过、不超过边界、比前一个访问的值大才能入队。其他思路大致与解法一的dfs相同。


本题算是一道有一些难度的题目,我也少见的遇到了算法超时的问题。做这题让我复习了图、dfs、bfs的同时,也让我学会了如何优化算法以及如何用多种方法解题,而这个优化的过程往往是有一定难度的,也是解题时最有价值的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值