这两道题目分别来自爱奇艺 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 那样简单直接。我猜测可能是考虑到在线笔试时,如果题目出的太直接,会比较容易搜到答案或思路。做这些题目时一般都有比较紧张的时间限制,容不得太发散的思考,像这种题目,首先考虑递归(因为递归相对简洁直观),靠谱就写,不能太花时间琢磨非递归的算法,而且就算琢磨出来了,效率也不一定有递归算法高。