|
| 1 | +# [2684. 矩阵中移动的最大次数](https://2xiao.github.io/leetcode-js/problem/2684.html) |
| 2 | + |
| 3 | +🟠 <font color=#ffb800>Medium</font>  🔖  [`数组`](/tag/array.md) [`动态规划`](/tag/dynamic-programming.md) [`矩阵`](/tag/matrix.md)  🔗 [`力扣`](https://leetcode.cn/problems/maximum-number-of-moves-in-a-grid) [`LeetCode`](https://leetcode.com/problems/maximum-number-of-moves-in-a-grid) |
| 4 | + |
| 5 | +## 题目 |
| 6 | + |
| 7 | +You are given a **0-indexed** `m x n` matrix `grid` consisting of **positive** |
| 8 | +integers. |
| 9 | + |
| 10 | +You can start at **any** cell in the first column of the matrix, and traverse |
| 11 | +the grid in the following way: |
| 12 | + |
| 13 | +- From a cell `(row, col)`, you can move to any of the cells: `(row - 1, col + 1)`, `(row, col + 1)` and `(row + 1, col + 1)` such that the value of the cell you move to, should be **strictly** bigger than the value of the current cell. |
| 14 | + |
| 15 | +Return _the **maximum** number of **moves** that you can perform._ |
| 16 | + |
| 17 | +**Example 1:** |
| 18 | + |
| 19 | + |
| 20 | + |
| 21 | +> Input: grid = [[2,4,3,5],[5,4,9,3],[3,4,2,11],[10,9,13,15]] |
| 22 | +> |
| 23 | +> Output: 3 |
| 24 | +> |
| 25 | +> Explanation: We can start at the cell (0, 0) and make the following moves: |
| 26 | +> |
| 27 | +> - (0, 0) -> (0, 1). |
| 28 | +> - (0, 1) -> (1, 2). |
| 29 | +> - (1, 2) -> (2, 3). |
| 30 | +> |
| 31 | +> It can be shown that it is the maximum number of moves that can be made. |
| 32 | +
|
| 33 | +**Example 2:** |
| 34 | + |
| 35 | +>  |
| 36 | +> |
| 37 | +> **Input:** grid = [[3,2,4],[2,1,9],[1,1,7]] |
| 38 | +> |
| 39 | +> Output: 0 |
| 40 | +> |
| 41 | +> Explanation: Starting from any cell in the first column we cannot perform any moves. |
| 42 | +
|
| 43 | +**Constraints:** |
| 44 | + |
| 45 | +- `m == grid.length` |
| 46 | +- `n == grid[i].length` |
| 47 | +- `2 <= m, n <= 1000` |
| 48 | +- `4 <= m * n <= 10^5` |
| 49 | +- `1 <= grid[i][j] <= 10^6` |
| 50 | + |
| 51 | +## 题目大意 |
| 52 | + |
| 53 | +给你一个下标从 **0** 开始、大小为 `m x n` 的矩阵 `grid` ,矩阵由若干 **正** 整数组成。 |
| 54 | + |
| 55 | +你可以从矩阵第一列中的 **任一** 单元格出发,按以下方式遍历 `grid` : |
| 56 | + |
| 57 | +- 从单元格 `(row, col)` 可以移动到 `(row - 1, col + 1)`、`(row, col + 1)` 和 `(row + 1, col + 1)` 三个单元格中任一满足值 **严格** 大于当前单元格的单元格。 |
| 58 | + |
| 59 | +返回你在矩阵中能够 **移动** 的 **最大** 次数。 |
| 60 | + |
| 61 | +**示例 1:** |
| 62 | + |
| 63 | + |
| 64 | + |
| 65 | +> **输入:** grid = [[2,4,3,5],[5,4,9,3],[3,4,2,11],[10,9,13,15]] |
| 66 | +> |
| 67 | +> **输出:** 3 |
| 68 | +> |
| 69 | +> **解释:** 可以从单元格 (0, 0) 开始并且按下面的路径移动: |
| 70 | +> |
| 71 | +> - (0, 0) -> (0, 1). |
| 72 | +> - (0, 1) -> (1, 2). |
| 73 | +> - (1, 2) -> (2, 3). |
| 74 | +> |
| 75 | +> 可以证明这是能够移动的最大次数。 |
| 76 | +
|
| 77 | +**示例 2:** |
| 78 | + |
| 79 | +>  |
| 80 | +> |
| 81 | +> **输入:** grid = [[3,2,4],[2,1,9],[1,1,7]] |
| 82 | +> |
| 83 | +> **输出:** 0 |
| 84 | +> |
| 85 | +> **解释:** 从第一列的任一单元格开始都无法移动。 |
| 86 | +
|
| 87 | +**提示:** |
| 88 | + |
| 89 | +- `m == grid.length` |
| 90 | +- `n == grid[i].length` |
| 91 | +- `2 <= m, n <= 1000` |
| 92 | +- `4 <= m * n <= 10^5` |
| 93 | +- `1 <= grid[i][j] <= 10^6` |
| 94 | + |
| 95 | +## 解题思路 |
| 96 | + |
| 97 | +可以使用动态规划来计算从矩阵第一列的任一单元格出发,能够移动的最大次数。 |
| 98 | + |
| 99 | +### 思路一:动态规划 |
| 100 | + |
| 101 | +1. **初始化状态**: |
| 102 | + 创建一个 `dp` 数组,其中 `dp[i][j]` 表示从单元格 `(i, j)` 开始能够移动的最大次数。初始时,所有单元格的最大移动次数都设为 `0`,因为矩阵的最后一列没有后续的单元格可移动,移动次数就是 `0`。 |
| 103 | + |
| 104 | +2. **倒序遍历列**: |
| 105 | + |
| 106 | + 从矩阵的倒数第二列开始遍历,直到第一列。在每一列中,我们都要计算从这一列每个单元格可以向右移动到下一列的次数。 |
| 107 | + |
| 108 | +3. **检查可移动条件**: |
| 109 | + |
| 110 | + - 对于每个单元格 `(i, j)`,检查以下三个方向是否满足移动条件: |
| 111 | + - 移动到上右 `(i - 1, j + 1)`,如果 `i > 0` 并且 `grid[i][j] < grid[i - 1][j + 1]`,则可以移动,更新移动次数。 |
| 112 | + - 移动到右 `(i, j + 1)`,如果 `grid[i][j] < grid[i][j + 1]`,则可以移动,更新移动次数。 |
| 113 | + - 移动到下右 `(i + 1, j + 1)`,如果 `i < m - 1` 并且 `grid[i][j] < grid[i + 1][j + 1]`,则可以移动,更新移动次数。 |
| 114 | + |
| 115 | +4. **获取结果**: |
| 116 | + - 最后, `dp[i][0]` 中存储的就是从第一列任一单元格出发,能够移动的最大次数,从中获取最大的移动次数,作为结果返回。 |
| 117 | + |
| 118 | +#### 复杂度分析 |
| 119 | + |
| 120 | +- **时间复杂度**:`O(m * n)`,其中 `m`、`n` 分别是矩阵的行数和列数,需要遍历整个矩阵。 |
| 121 | +- **空间复杂度**:`O(m * n)`,用于存储 `dp` 数组。 |
| 122 | + |
| 123 | +### 思路二:压缩状态的动态规划 |
| 124 | + |
| 125 | +1. **初始化状态**: |
| 126 | + 使用一个一维数组 `dp`,长度为 `m`,表示从每一行的某一列出发,能够到达的最大移动次数。最开始,`dp[i]` 表示从最后一列的第 `i` 行出发时的移动次数,因为没有后续的单元格可移动,初始值为 `0`。 |
| 127 | + |
| 128 | +2. **倒序遍历列**: |
| 129 | + |
| 130 | + 从矩阵的倒数第二列开始遍历,直到第一列。在每一列中,我们都要计算从这一列每个单元格可以向右移动到下一列的次数。 |
| 131 | + |
| 132 | +3. **检查可移动条件**: |
| 133 | + |
| 134 | + - 对于每个单元格 `(i, j)`,检查以下三个方向是否满足移动条件: |
| 135 | + - 移动到上右 `(i - 1, j + 1)`,如果 `i > 0` 并且 `grid[i][j] < grid[i - 1][j + 1]`,则可以移动,更新移动次数。 |
| 136 | + - 移动到右 `(i, j + 1)`,如果 `grid[i][j] < grid[i][j + 1]`,则可以移动,更新移动次数。 |
| 137 | + - 移动到下右 `(i + 1, j + 1)`,如果 `i < m - 1` 并且 `grid[i][j] < grid[i + 1][j + 1]`,则可以移动,更新移动次数。 |
| 138 | + |
| 139 | +4. **更新状态数组**: |
| 140 | + |
| 141 | + - 因为后续计算还依赖下一列的移动次数,所以需要通过临时数组 `newDp` 存储当前列的移动次数,在遍历完当前列后,更新 `dp` 为 `newDp`。 |
| 142 | + |
| 143 | +5. **获取结果**: |
| 144 | + - 最后, `dp` 数组中存储的就是从第一列任一单元格出发,能够移动的最大次数,从 `dp` 数组中获取最大的移动次数,作为结果返回。 |
| 145 | + |
| 146 | +#### 复杂度分析 |
| 147 | + |
| 148 | +- **时间复杂度**:`O(m * n)`,其中 `m`、`n` 分别是矩阵的行数和列数,需要遍历整个矩阵。 |
| 149 | +- **空间复杂度**:`O(m)`,只需要一个一维数组 `dp` 存储状态。 |
| 150 | + |
| 151 | +## 代码 |
| 152 | + |
| 153 | +::: code-tabs |
| 154 | +@tab 动态规划 |
| 155 | + |
| 156 | +```javascript |
| 157 | +/** |
| 158 | + * @param {number[][]} grid |
| 159 | + * @return {number} |
| 160 | + */ |
| 161 | +var maxMoves = function (grid) { |
| 162 | + const m = grid.length, |
| 163 | + n = grid[0].length; |
| 164 | + // 创建 dp 数组 |
| 165 | + const dp = new Array(m).fill(0).map((i) => new Array(n).fill(0)); |
| 166 | + |
| 167 | + // 倒序遍历列 |
| 168 | + for (let j = n - 2; j >= 0; j--) { |
| 169 | + for (let i = 0; i < m; i++) { |
| 170 | + // 检查上右、右、下右三个方向 |
| 171 | + if (i > 0 && grid[i][j] < grid[i - 1][j + 1]) { |
| 172 | + dp[i][j] = Math.max(dp[i][j], dp[i - 1][j + 1] + 1); |
| 173 | + } |
| 174 | + if (grid[i][j] < grid[i][j + 1]) { |
| 175 | + dp[i][j] = Math.max(dp[i][j], dp[i][j + 1] + 1); |
| 176 | + } |
| 177 | + if (i < m - 1 && grid[i][j] < grid[i + 1][j + 1]) { |
| 178 | + dp[i][j] = Math.max(dp[i][j], dp[i + 1][j + 1] + 1); |
| 179 | + } |
| 180 | + } |
| 181 | + } |
| 182 | + // 获取结果 |
| 183 | + let maxMoves = 0; |
| 184 | + for (let i = 0; i < m; i++) { |
| 185 | + maxMoves = Math.max(maxMoves, dp[i][0]); |
| 186 | + } |
| 187 | + return maxMoves; |
| 188 | +}; |
| 189 | +``` |
| 190 | + |
| 191 | +@tab 压缩状态的动态规划 |
| 192 | + |
| 193 | +```javascript |
| 194 | +/** |
| 195 | + * @param {number[][]} grid |
| 196 | + * @return {number} |
| 197 | + */ |
| 198 | +var maxMoves = function (grid) { |
| 199 | + const m = grid.length, |
| 200 | + n = grid[0].length; |
| 201 | + // 初始化 dp 数组 |
| 202 | + let dp = new Array(m).fill(0); |
| 203 | + |
| 204 | + for (let j = n - 2; j >= 0; j--) { |
| 205 | + // 记录当前列的 dp 数组 |
| 206 | + let newDp = []; |
| 207 | + for (let i = 0; i < m; i++) { |
| 208 | + // 当前单元格的移动次数 |
| 209 | + let moves = 0; |
| 210 | + // 检查三个可移动方向 |
| 211 | + if (i > 0 && grid[i][j] < grid[i - 1][j + 1]) { |
| 212 | + moves = Math.max(moves, dp[i - 1] + 1); |
| 213 | + } |
| 214 | + if (grid[i][j] < grid[i][j + 1]) { |
| 215 | + moves = Math.max(moves, dp[i] + 1); |
| 216 | + } |
| 217 | + if (i < m - 1 && grid[i][j] < grid[i + 1][j + 1]) { |
| 218 | + moves = Math.max(moves, dp[i + 1] + 1); |
| 219 | + } |
| 220 | + // 更新当前列的移动次数 |
| 221 | + newDp.push(moves); |
| 222 | + } |
| 223 | + // 更新 dp 数组 |
| 224 | + dp = newDp; |
| 225 | + } |
| 226 | + // 返回最大的移动次数 |
| 227 | + return Math.max(...dp); |
| 228 | +}; |
| 229 | +``` |
| 230 | + |
| 231 | +::: |
0 commit comments