题目描述
原题链接: 2684 矩阵中移动的最大次数
解题思路
- 每次只能向右侧的下一列紧挨着的三行严格大的格子移动;
- 能移动到i列代表能移动i次, 这取决于i-1列可到达的矩阵位置的状态, 即可以整列递推相邻列是否可移动到达;
- 两个方向递推的思路:
- 三个(col+1)位置的状态可以逆推出一个(col)位置状态, 即可以视为从右往左移动到严格小的位置来求解;
- 正常从左到右递推, 由(col)位置状态来确定三个(col+1)位置状态, 这种思路会重复处理(col+1)位置;
- 题解和提交结果图中有递归解法的代码, 思路是一样的就没自己写了。
PS: 第一个逆序思路是起床后边吃早饭边写完提交的, 不知道为什么会就想着逆序了, 当时吃完早饭要出门也就没多想。 回家后第二天想着继续优化一下换成正常从左到右递推, 又陷入递推一推三时会出现重复处理(col+1)位置的牛角尖, 直到整理这篇思路文字时才想明白完全也能从左到右三推一来防止重复, 只能说即使已经解决的题目最后再整理一篇简单随笔也说不定有新的收获了~
解题代码
-
初次提交通过的逆序递推版本:
/** * 按列逆序DP * 执行用时: 15 ms , 在所有 Java 提交中击败了 41.30% 的用户 * 内存消耗: 55.30 MB , 在所有 Java 提交中击败了 18.12% 的用户 */ public int maxMoves(int[][] grid) { int m = grid.length, n = grid[0].length; int ans = 0; int[] dp = new int[m]; int[] tmp = new int[m]; for (int col = n - 2; col >= 0; col--) { // 处理grid[0][col] if (grid[0][col] < grid[0][col + 1]) { tmp[0] = dp[0] + 1; } if (grid[0][col] < grid[1][col + 1]) { tmp[0] = Math.max(tmp[0], dp[1] + 1); } for (int row = 1; row < m - 1; row++) { for (int i = -1; i < 2; i++) { if (grid[row][col] < grid[row + i][col + 1]) { tmp[row] = Math.max(tmp[row], dp[row + i] + 1); } } } if (grid[m - 1][col] < grid[m - 1][col + 1]) { tmp[m - 1] = dp[m - 1] + 1; } if (grid[m - 1][col] < grid[m - 2][col + 1]) { tmp[m - 1] = Math.max(tmp[m - 1], dp[m - 2] + 1); } dp = tmp; tmp = new int[m]; } for (int i = 0; i < m; i++) { ans = Math.max(ans, dp[i]); } return ans; }
-
从左到右递推, 借助next数据忽略重复的(col+1)位置:
/** * 用Boolean数组从左到右处理DP数据 * 执行用时: 3 ms , 在所有 Java 提交中击败了 93.49% 的用户 * 内存消耗: 53.64 MB , 在所有 Java 提交中击败了 96.74% 的用户 */ public int maxMoves(int[][] grid) { int m = grid.length, n = grid[0].length; boolean[] dp = new boolean[m];// dp[i] 表示是否可以移动到当前列的i行位置 for (int i = 0; i < m; i++) { dp[i] = true; } boolean[] next = new boolean[m]; for (int col = 0; col < n - 1; col++) { boolean moveNext = false; for (int i = 0; i < m; i++) { if (!dp[i]) { continue; } int val = grid[i][col]; if (i > 0 && !next[i - 1] && grid[i - 1][col + 1] > val) { next[i - 1] = true; moveNext = true; } if (!next[i] && grid[i][col + 1] > val) { next[i] = true; moveNext = true; } if (i < m - 1 && !next[i + 1] && grid[i + 1][col + 1] > val) { next[i + 1] = true; moveNext = true; } } if (!moveNext) {// 当前列所有行都不能继续移动,直接返回结果 return col; } dp = next; next = new boolean[m]; } return n - 1; }
-
从左到右递推, 但是不用考虑下一列会重复判断的情况(整理随笔内容时才想到写出来的版本):
/** * 从左到右递推, 由(row-1, col)、(row, col)和(row+1, col)来确定(row, col+1), 不用考虑下一列重复判断 * 执行用时: 2 ms , 在所有 Java 提交中击败了 99.70% 的用户 * 内存消耗: 53.97 MB , 在所有 Java 提交中击败了 74.00% 的用户 */ public int maxMoves6(int[][] grid) { int m = grid.length, n = grid[0].length; boolean[] dp = new boolean[m];// dp[i] 表示是否可以移动到当前列的i行位置 for (int i = 0; i < m; i++) { dp[i] = true; } boolean[] next = new boolean[m]; for (int col = 0; col < n - 1; col++) { boolean moveNext = false; // 递推next[0]状态 if ((dp[0] && grid[0][col + 1] > grid[0][col]) || (dp[1] && grid[0][col + 1] > grid[1][col])) { next[0] = true; moveNext = true; } // 递推next[1...m-1]状态 for (int i = 1; i < m - 1; i++) { int val = grid[i][col + 1]; if ((dp[i - 1] && val > grid[i - 1][col]) || (dp[i] && val > grid[i][col]) || (dp[i + 1] && val > grid[i + 1][col])) { next[i] = true; moveNext = true; } } // 递推next[m-1]状态 if ((dp[m - 2] && grid[m - 1][col + 1] > grid[m - 2][col]) || (dp[m - 1] && grid[m - 1][col + 1] > grid[m - 1][col])) { next[m - 1] = true; moveNext = true; } if (!moveNext) {// 当前列所有行都不能继续移动,直接返回结果 return col; } dp = next; next = new boolean[m]; } return n - 1; }