二维数组中的递归算法

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/L__ear/article/details/98495959

这两道题目分别来自爱奇艺 2017 年内推笔试题和 2019 年猿辅导算法题。

第一题

题目描述

Michael 喜欢滑雪这并不奇怪, 因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael 想知道在一个区域中最长的滑坡。区域由一个二维数组给出,数组的每个数字代表点的高度。下面是一个例子

 1  2  3  4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9

一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可滑行的滑坡为 24-17-16-1。当然 25-24-23-…-3-2-1 更长。事实上,这是最长的一条。

输入描述:
输入的第一行表示区域的行数 R 和列数 C(1 <= R,C <= 100)。下面是 R 行,每行有 C 个整数,代表高度 h,0<= h <=10000。

输出描述:
输出最长区域的长度。

示例:

输入:
5 5
1  2  3  4  5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
输出:
25

题目求解

一、递归解法
基于当前点,向四个方向进行递归搜索。假设函数 dfs(i, j) 表示以 (i, j) 为终点的最长滑坡长度。
在不考虑数组越界的情况下,有如下关系:
dfs(i, j) = max{dfs(i-1, j), dfs(i+1, j), dfs(i, j-1), dfs(i, j+1)} + 1。

public class Test {
	private static int dfs(int[][] matrix, int i, int j){
		int up = 0, down = 0, left = 0, right = 0;
		if(i - 1 >= 0 && matrix[i][j] > matrix[i-1][j]) up = dfs(matrix, i-1, j);
		if(i + 1 < matrix.length && matrix[i][j] > matrix[i+1][j]) down = dfs(matrix, i+1, j);
		if(j - 1 >= 0 && matrix[i][j] > matrix[i][j-1]) left = dfs(matrix, i, j-1);
		if(j + 1 < matrix[i].length && matrix[i][j] > matrix[i][j+1]) right = dfs(matrix, i, j+1);
		int x, y;
		return (x = left>right?left:right) > (y = up>down?up:down) ? x+1 : y+1;
	}
	public static void main(String[] args) {
		 int R = 5;
		 int C = 5;
		 int[][] matrix = {
				 {3, 16, 10, 28, 34},
				 {6, 8, 11, 14, 9},
				 {20, 1, 54, 27, 15},
				 {18, 4, 19, 30, 6},
				 {25, 16, 23, 33, 5}};
		 int max = 0;
		 for(int i = 0; i < R; ++i){
			 for(int j = 0; j < C; ++j){
				 int temp = dfs(matrix, i, j);
				 if(temp > max) max = temp;
			 }
		 }
		 System.out.println(max);
	}
}

这个递归算法还是很简洁直观的,形式上也具有一定的对称美。因为二维数组的每个点都有可能是最长滑坡的终点,所以需要对每个点都搜索一遍。可以很容易看到,由于没有保存之前的搜索结果,该算法会有大量重复的搜索,我们可以通过空间换时间的方法降低算法的时间复杂度。

import java.util.Arrays;
public class Test {
	private static int dfs(int[][] cache, int[][] matrix, int i, int j){
		if(cache[i][j] != 0) return cache[i][j];
		int up = 0, down = 0, left = 0, right = 0;
		if(i - 1 >= 0 && matrix[i][j] > matrix[i-1][j]){
			if(cache[i-1][j] == 0){
				up = dfs(cache, matrix, i-1, j);
				cache[i-1][j] = up;
			}else up = cache[i-1][j];
		}
		if(i + 1 < matrix.length && matrix[i][j] > matrix[i+1][j]){
			if(cache[i+1][j] == 0){
				down = dfs(cache, matrix, i+1, j);
				cache[i+1][j] = down;
			}else down = cache[i+1][j];
		}
		if(j - 1 >= 0 && matrix[i][j] > matrix[i][j-1]){
			if(cache[i][j-1] == 0){
				left = dfs(cache, matrix, i, j-1);
				cache[i][j-1] = left;
			}else left = cache[i][j-1];
		}
		if(j + 1 < matrix[i].length && matrix[i][j] > matrix[i][j+1]){
			if(cache[i][j+1] == 0){
				right = dfs(cache, matrix, i, j+1);
				cache[i][j+1] = right;
			}else right = cache[i][j+1];
		}
		int x, y;
		cache[i][j] = (x = left>right ? left : right) > (y = up>down ? up : down) ? x+1 : y+1;
		return cache[i][j];
	}
	public static void main(String[] args) {
		 int R = 5;
		 int C = 5;
		 int[][] matrix = {
				 {3, 16, 10, 28, 34},
				 {6, 8, 11, 14, 9},
				 {20, 1, 54, 27, 15},
				 {18, 4, 19, 30, 6},
				 {25, 16, 23, 33, 5}};
		 int[][] cache = new int[R][C];
		 int max = 0;
		 for(int i = 0; i < R; ++i){
			 for(int j = 0; j < C; ++j){
				 int temp = dfs(cache, matrix, i, j);
				 if(temp > max) max = temp;
			 }
		 }
		 System.out.println(Arrays.deepToString(cache));
		 System.out.println(max);
	}
}

二、非递归解法
非递归解法需要对数组元素排序,所以效率没有改进后的递归算法高。
思路就是,开辟一个辅助数组 cache,cache[i][j] 表示以 (i, j) 为终点的最长滑坡长度,按照原数组中值从小到大的顺序来处理 cache 数组对应位置的值。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

class Index implements Comparable<Index>{ // 定义存放数组值以及对应下标的可比较类
	int i, j, value;
	public Index(int i, int j, int value) {
		this.i = i;
		this.j = j;
		this.value = value;
	}
	@Override
	public int compareTo(Index o) {
		return value-o.value;
	}
}
public class Test {
	// 按照 matrix 数组中值从小到大的顺序,处理 cache 数组
	private static int pathLength(List<Index> indexs, int [][] cache, int r, int c){
		int max = 0;
		for(Index e : indexs){
			int i = e.i, j = e.j;
			int up = 0, down = 0, left = 0, right = 0;
			if(i-1 >= 0) up = cache[i-1][j];
			if(i+1 < r) down = cache[i+1][j];
			if(j-1 >= 0) left = cache[i][j-1];
			if(j+1 < c) right = cache[i][j+1];
			int x, y;
			cache[i][j] = (x = left>right?left:right) > (y = up>down?up:down) ? x+1 : y+1; 
			if(cache[i][j] > max) max = cache[i][j];
		}
		return max;
	}
	public static void main(String[] args) {
		int R = 5;
		 int C = 5;
		 int[][] matrix = {
				 {3, 16, 10, 28, 34},
				 {6, 8, 11, 14, 9},
				 {20, 1, 54, 27, 15},
				 {18, 4, 19, 30, 6},
				 {25, 16, 23, 33, 5}};
		 List<Index> indexs = new ArrayList<>();
		 for(int i = 0; i < R; ++i){
			 for(int j = 0; j < C; ++j){
				 indexs.add(new Index(i, j, matrix[i][j]));
			 }
		 }
		 Collections.sort(indexs);
		 int[][] cache = new int[R][C];
		 System.out.println(pathLength(indexs, cache, R, C));
	}
}

第二题

题目描述

有一个 N*M 的迷宫矩阵,迷宫的每个格子有一个数值(a[i][j] < 10^9)。小猿在迷宫中发现,它只能朝着上下左右四个方向的相邻格子前进,并且只能进入到比当前格子数值更大的格子。但是小猿有一个紧急呼救按钮,他可以通过按下按钮,强行进入到不满足数值大小要求的相邻格子,可惜这个按钮只能按 K 次。请问小猿从这个迷宫任远一个格子出发,在紧急按钮的帮助下,最多能走多少步(开始位置计入步数,即站在起点的步数为 1)。

输入描述:
第一行输入三个数 N,M,K。接下来 N 行,每行 M 个数,表示迷宫每个格子的值。
1 <= N <= 500
1 <= M <= 500
0 <= K <= 10

输出描述:
输出小猿能在迷宫中走的最大步数。

示例:

输入:
3	3	1
1	3	3
2	4	9
8	9	2
输出:
6

题目求解

可以看出,本题是上一题的升级版,使用递归求解如下:

public class Test {
	private static int dfs(int[][] matrix, int k, int i, int j){
		int up = 0, down = 0, left = 0, right = 0;
		if(i-1 >= 0){
			int up0 = 0, up1 = 0;
			if(matrix[i][j] < matrix[i-1][j]){
				up0 = dfs(matrix, k, i-1, j);
			}else if(k > 0){
				up1 = dfs(matrix, k-1, i-1, j);
			}
			up = up0>up1 ? up0 : up1;
		}
		if(i+1 <= matrix.length-1){
			int down0 = 0, down1 = 0;
			if(matrix[i][j] < matrix[i+1][j]){
				down0 = dfs(matrix, k, i+1, j);
			}else if(k > 0){
				down1 = dfs(matrix, k-1, i+1, j);
			}
			down = down0>down1 ? down0 : down1;
		}
		if(j-1 >= 0){
			int left0 = 0, left1 = 0;
			if(matrix[i][j] < matrix[i][j-1]){
				left0 = dfs(matrix, k, i, j-1);
			}else if(k > 0){
				left1 = dfs(matrix, k-1, i, j-1);
			}
			left = left0>left1 ? left0 : left1;
		}
		if(j+1 <= matrix[i].length-1){
			int right0 = 0, right1 = 0;
			if(matrix[i][j] < matrix[i][j+1]){
				right0 = dfs(matrix, k, i, j+1);
			}else if(k > 0){
				right1 = dfs(matrix, k-1, i, j+1);
			}
			right = right0>right1 ? right0 : right1;
		}
		int x, y;
		return (x = right>left?right:left) > (y = up>down?up:down) ? x+1 : y+1;
	}
	public static void main(String[] args) {
		int N = 3;
		int M = 3;
		int K = 1;
		int[][] matrix = {{1,3,3},{2,4,9},{8,9,2}};
		int max = 0;
		for(int i = 0; i < N; ++i){
			for(int j = 0; j < M; ++j){
				int temp = dfs(matrix, K, i, j);
				if(temp > max) max = temp;
			}
		}
		System.out.println(max);
	}
}

同理,空间换时间。

import java.util.Arrays;
public class Test {
	private static int dfs(int[][][] cache, int[][] matrix, int k, int i, int j){
		if(cache[k][i][j] != 0) return cache[k][i][j];
		int up = 0, down = 0, left = 0, right = 0;
		if(i-1 >= 0){
			int up0 = 0, up1 = 0;
			if(matrix[i][j] < matrix[i-1][j]){
				if(cache[k][i-1][j] == 0){
					up0 = dfs(cache, matrix, k, i-1, j);
					cache[k][i-1][j] = up0;					
				}else up0 = cache[k][i-1][j];
			}else if(k > 0){
				if(cache[k-1][i-1][j] == 0){
					up1 = dfs(cache, matrix, k-1, i-1, j);
					cache[k-1][i-1][j] = up1;
				}else up1 = cache[k-1][i-1][j];
			}
			up = up0>up1 ? up0 : up1;
		}
		if(i+1 <= matrix.length-1){
			int down0 = 0, down1 = 0;
			if(matrix[i][j] < matrix[i+1][j]){
				if(cache[k][i+1][j] == 0){
					down0 = dfs(cache, matrix, k, i+1, j);
					cache[k][i+1][j] = down0;
				}else down0 = cache[k][i+1][j];
			}else if(k > 0){
				if(cache[k-1][i+1][j] == 0){					
					down1 = dfs(cache, matrix, k-1, i+1, j);
					cache[k-1][i+1][j] = down1;
				}else down1 = cache[k-1][i+1][j];
			}
			down = down0>down1 ? down0 : down1;
		}
		if(j-1 >= 0){
			int left0 = 0, left1 = 0;
			if(matrix[i][j] < matrix[i][j-1]){
				if(cache[k][i][j-1] == 0){					
					left0 = dfs(cache, matrix, k, i, j-1);
					cache[k][i][j-1] = left0;
				}else left0 = cache[k][i][j-1];
			}else if(k > 0){
				if(cache[k-1][i][j-1] == 0){					
					left1 = dfs(cache, matrix, k-1, i, j-1);
					cache[k-1][i][j-1] = left1;
				}else left1 = cache[k-1][i][j-1];
			}
			left = left0>left1 ? left0 : left1;
		}
		if(j+1 <= matrix[i].length-1){
			int right0 = 0, right1 = 0;
			if(matrix[i][j] < matrix[i][j+1]){
				if(cache[k][i][j+1] == 0){
					right0 = dfs(cache, matrix, k, i, j+1);
					cache[k][i][j+1] = right0;
				}else right0 = cache[k][i][j+1];
			}else if(k > 0){
				if(cache[k-1][i][j+1] == 0){
					right1 = dfs(cache, matrix, k-1, i, j+1);
					cache[k-1][i][j+1] = right1;
				}else right1 = cache[k-1][i][j+1];
			}
			right = right0>right1 ? right0 : right1;
		}
		int x, y;
		cache[k][i][j] = (x = right>left ? right : left) > (y = up>down ? up : down) ? x+1 : y+1;
		return cache[k][i][j];
	}
	public static void main(String[] args) {
		int N = 3;
		int M = 3;
		int K = 1;
		int[][] matrix = {{1,3,3},{2,4,9},{8,9,2}};
		int[][][] cache = new int[k+1][N][M];
		int max = 0;
		for(int i = 0; i < N; ++i){
			for(int j = 0; j < M; ++j){
				int temp = dfs(cache, matrix, K, i, j);
				if(temp > max) max = temp;
			}
		}
		System.out.println(Arrays.deepToString(cache));
		System.out.println(max);
	}
}

总结

公司的算法题都比较冗长,题目里往往包含多余的人物和故事,不像 leetcode 那样简单直接。我猜测可能是考虑到在线笔试时,如果题目出的太直接,会比较容易搜到答案或思路。做这些题目时一般都有比较紧张的时间限制,容不得太发散的思考,像这种题目,首先考虑递归(因为递归相对简洁直观),靠谱就写,不能太花时间琢磨非递归的算法,而且就算琢磨出来了,效率也不一定有递归算法高。

猜你喜欢

转载自blog.csdn.net/L__ear/article/details/98495959