LeetCode
网格图中最少访问的格子数
题目链接:2617. 网格图中最少访问的格子数 - 力扣(LeetCode)
题目描述
给你一个下标从 0 开始的 m x n 整数矩阵 grid 。你一开始的位置在 左上角 格子 (0, 0) 。
当你在格子 (i, j) 的时候,你可以移动到以下格子之一:
- 满足
j < k <= grid[i][j] + j的格子(i, k)(向右移动),或者 - 满足
i < k <= grid[i][j] + i的格子(k, j)(向下移动)。
请你返回到达 右下角 格子 (m - 1, n - 1) 需要经过的最少移动格子数,如果无法到达右下角格子,请你返回 -1 。
示例 1:
输入:grid = [[3,4,2,1],[4,2,3,1],[2,1,0,0],[2,4,0,0]]
输出:4
解释:上图展示了到达右下角格子经过的 4 个格子。
示例 2:
输入:grid = [[3,4,2,1],[4,2,1,1],[2,1,1,0],[3,4,1,0]]
输出:3
解释:上图展示了到达右下角格子经过的 3 个格子。
示例 3:
输入:grid = [[2,1,0],[1,0,0]]
输出:-1
解释:无法到达右下角格子。
提示:
m == grid.lengthn == grid[i].length1 <= m, n <= 1051 <= m * n <= 1050 <= grid[i][j] < m * ngrid[m - 1][n - 1] == 0
思路
困难题,睡大觉,cv大法
代码
C++
class Solution {
public:
int minimumVisitedCells(vector<vector<int>> &grid) {
int m = grid.size(), n = grid[0].size(), mn;
vector<vector<pair<int, int>>> col_stacks(n); // 每列的单调栈
vector<pair<int, int>> row_st; // 行单调栈
for (int i = m - 1; i >= 0; i--) {
row_st.clear();
for (int j = n - 1; j >= 0; j--) {
int g = grid[i][j];
auto &col_st = col_stacks[j];
mn = i < m - 1 || j < n - 1 ? INT_MAX : 1;
if (g) { // 可以向右/向下跳
// 在单调栈上二分查找最优转移来源
auto it = lower_bound(row_st.begin(), row_st.end(), j + g, [](const auto &a, const int b) {
return a.second > b;
});
if (it < row_st.end()) mn = it->first + 1;
it = lower_bound(col_st.begin(), col_st.end(), i + g, [](const auto &a, const int b) {
return a.second > b;
});
if (it < col_st.end()) mn = min(mn, it->first + 1);
}
if (mn < INT_MAX) {
// 插入单调栈
while (!row_st.empty() && mn <= row_st.back().first) {
row_st.pop_back();
}
row_st.emplace_back(mn, j);
while (!col_st.empty() && mn <= col_st.back().first) {
col_st.pop_back();
}
col_st.emplace_back(mn, i);
}
}
}
return mn < INT_MAX ? mn : -1; // 最后一个算出的 mn 就是 f[0][0]
}
};
Java
class Solution {
public int minimumVisitedCells(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int mn = 0;
List<int[]>[] colStacks = new ArrayList[n]; // 每列的单调栈,为了能二分用 ArrayList
Arrays.setAll(colStacks, i -> new ArrayList<int[]>());
List<int[]> rowSt = new ArrayList<>(); // 行单调栈
for (int i = m - 1; i >= 0; i--) {
rowSt.clear();
for (int j = n - 1; j >= 0; j--) {
int g = grid[i][j];
List<int[]> colSt = colStacks[j];
mn = i < m - 1 || j < n - 1 ? Integer.MAX_VALUE : 1;
if (g > 0) { // 可以向右/向下跳
// 在单调栈上二分查找最优转移来源
int k = search(rowSt, j + g);
if (k < rowSt.size()) {
mn = rowSt.get(k)[0] + 1;
}
k = search(colSt, i + g);
if (k < colSt.size()) {
mn = Math.min(mn, colSt.get(k)[0] + 1);
}
}
if (mn < Integer.MAX_VALUE) {
// 插入单调栈
while (!rowSt.isEmpty() && mn <= rowSt.get(rowSt.size() - 1)[0]) {
rowSt.remove(rowSt.size() - 1);
}
rowSt.add(new int[]{mn, j});
while (!colSt.isEmpty() && mn <= colSt.get(colSt.size() - 1)[0]) {
colSt.remove(colSt.size() - 1);
}
colSt.add(new int[]{mn, i});
}
}
}
return mn < Integer.MAX_VALUE ? mn : -1; // 最后一个算出的 mn 就是 f[0][0]
}
// 开区间二分,见 https://www.bilibili.com/video/BV1AP41137w7/
private int search(List<int[]> st, int target) {
int left = -1, right = st.size(); // 开区间 (left, right)
while (left + 1 < right) { // 区间不为空
int mid = left + (right - left) / 2;
if (st.get(mid)[1] <= target) {
right = mid; // 范围缩小到 (left, mid)
} else {
left = mid; // 范围缩小到 (mid, right)
}
}
return right;
}
}