|
|
package class39;
|
|
|
|
|
|
// 来自京东
|
|
|
// 给定一个二维数组matrix,matrix[i][j] = k代表:
|
|
|
// 从(i,j)位置可以随意往右跳<=k步,或者从(i,j)位置可以随意往下跳<=k步
|
|
|
// 如果matrix[i][j] = 0,代表来到(i,j)位置必须停止
|
|
|
// 返回从matrix左上角到右下角,至少要跳几次
|
|
|
// 已知matrix中行数n <= 5000, 列数m <= 5000
|
|
|
// matrix中的值,<= 5000
|
|
|
// 最弟弟的技巧也过了。最优解 -> dp+枚举优化(线段树,体系学习班)
|
|
|
public class Code04_JumpGameOnMatrix {
|
|
|
|
|
|
// 暴力方法,仅仅是做对数器
|
|
|
// 如果无法到达会返回系统最大值
|
|
|
public static int jump1(int[][] map) {
|
|
|
return process(map, 0, 0);
|
|
|
}
|
|
|
|
|
|
// 当前来到的位置是(row,col)
|
|
|
// 目标:右下角
|
|
|
// 当前最大能跳多远,map[row][col]值决定,只能向右、或者向下
|
|
|
// 返回,到达右下角,最小跳几次?
|
|
|
// 5000 * 5000 = 25000000 -> 2 * (10 ^ 7)
|
|
|
public static int process(int[][] map, int row, int col) {
|
|
|
if (row == map.length - 1 && col == map[0].length - 1) {
|
|
|
return 0;
|
|
|
}
|
|
|
// 如果没到右下角
|
|
|
if (map[row][col] == 0) {
|
|
|
return Integer.MAX_VALUE;
|
|
|
}
|
|
|
// 当前位置,可以去很多的位置,next含义:
|
|
|
// 在所有能去的位置里,哪个位置最后到达右下角,跳的次数最少,就是next
|
|
|
int next = Integer.MAX_VALUE;
|
|
|
// 往下能到达的位置,全试一遍
|
|
|
for (int down = row + 1; down < map.length && (down - row) <= map[row][col]; down++) {
|
|
|
next = Math.min(next, process(map, down, col));
|
|
|
}
|
|
|
// 往右能到达的位置,全试一遍
|
|
|
for (int right = col + 1; right < map[0].length && (right - col) <= map[row][col]; right++) {
|
|
|
next = Math.min(next, process(map, row, right));
|
|
|
}
|
|
|
// 如果所有下一步的位置,没有一个能到右下角,next = 系统最大!
|
|
|
// 返回系统最大!
|
|
|
// next != 系统最大 7 + 1
|
|
|
return next != Integer.MAX_VALUE ? (next + 1) : next;
|
|
|
}
|
|
|
|
|
|
// 优化方法, 利用线段树做枚举优化
|
|
|
// 因为线段树,下标从1开始
|
|
|
// 所以,该方法中所有的下标,请都从1开始,防止乱!
|
|
|
public static int jump2(int[][] arr) {
|
|
|
int n = arr.length;
|
|
|
int m = arr[0].length;
|
|
|
int[][] map = new int[n + 1][m + 1];
|
|
|
for (int a = 0, b = 1; a < n; a++, b++) {
|
|
|
for (int c = 0, d = 1; c < m; c++, d++) {
|
|
|
map[b][d] = arr[a][c];
|
|
|
}
|
|
|
}
|
|
|
SegmentTree[] rowTrees = new SegmentTree[n + 1];
|
|
|
for (int i = 1; i <= n; i++) {
|
|
|
rowTrees[i] = new SegmentTree(m);
|
|
|
}
|
|
|
SegmentTree[] colTrees = new SegmentTree[m + 1];
|
|
|
for (int i = 1; i <= m; i++) {
|
|
|
colTrees[i] = new SegmentTree(n);
|
|
|
}
|
|
|
rowTrees[n].update(m, m, 0, 1, m, 1);
|
|
|
colTrees[m].update(n, n, 0, 1, n, 1);
|
|
|
for (int col = m - 1; col >= 1; col--) {
|
|
|
if (map[n][col] != 0) {
|
|
|
int left = col + 1;
|
|
|
int right = Math.min(col + map[n][col], m);
|
|
|
int next = rowTrees[n].query(left, right, 1, m, 1);
|
|
|
if (next != Integer.MAX_VALUE) {
|
|
|
rowTrees[n].update(col, col, next + 1, 1, m, 1);
|
|
|
colTrees[col].update(n, n, next + 1, 1, n, 1);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
for (int row = n - 1; row >= 1; row--) {
|
|
|
if (map[row][m] != 0) {
|
|
|
int up = row + 1;
|
|
|
int down = Math.min(row + map[row][m], n);
|
|
|
int next = colTrees[m].query(up, down, 1, n, 1);
|
|
|
if (next != Integer.MAX_VALUE) {
|
|
|
rowTrees[row].update(m, m, next + 1, 1, m, 1);
|
|
|
colTrees[m].update(row, row, next + 1, 1, n, 1);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
for (int row = n - 1; row >= 1; row--) {
|
|
|
for (int col = m - 1; col >= 1; col--) {
|
|
|
if (map[row][col] != 0) {
|
|
|
// (row,col) 往右是什么范围呢?[left,right]
|
|
|
int left = col + 1;
|
|
|
int right = Math.min(col + map[row][col], m);
|
|
|
int next1 = rowTrees[row].query(left, right, 1, m, 1);
|
|
|
// (row,col) 往下是什么范围呢?[up,down]
|
|
|
int up = row + 1;
|
|
|
int down = Math.min(row + map[row][col], n);
|
|
|
int next2 = colTrees[col].query(up, down, 1, n, 1);
|
|
|
int next = Math.min(next1, next2);
|
|
|
if (next != Integer.MAX_VALUE) {
|
|
|
rowTrees[row].update(col, col, next + 1, 1, m, 1);
|
|
|
colTrees[col].update(row, row, next + 1, 1, n, 1);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return rowTrees[1].query(1, 1, 1, m, 1);
|
|
|
}
|
|
|
|
|
|
// 区间查询最小值的线段树
|
|
|
// 注意下标从1开始,不从0开始
|
|
|
// 比如你传入size = 8
|
|
|
// 则位置对应为1~8,而不是0~7
|
|
|
public static class SegmentTree {
|
|
|
private int[] min;
|
|
|
private int[] change;
|
|
|
private boolean[] update;
|
|
|
|
|
|
public SegmentTree(int size) {
|
|
|
int N = size + 1;
|
|
|
min = new int[N << 2];
|
|
|
change = new int[N << 2];
|
|
|
update = new boolean[N << 2];
|
|
|
update(1, size, Integer.MAX_VALUE, 1, size, 1);
|
|
|
}
|
|
|
|
|
|
private void pushUp(int rt) {
|
|
|
min[rt] = Math.min(min[rt << 1], min[rt << 1 | 1]);
|
|
|
}
|
|
|
|
|
|
private void pushDown(int rt, int ln, int rn) {
|
|
|
if (update[rt]) {
|
|
|
update[rt << 1] = true;
|
|
|
update[rt << 1 | 1] = true;
|
|
|
change[rt << 1] = change[rt];
|
|
|
change[rt << 1 | 1] = change[rt];
|
|
|
min[rt << 1] = change[rt];
|
|
|
min[rt << 1 | 1] = change[rt];
|
|
|
update[rt] = false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 最后三个参数是固定的, 每次传入相同的值即可:
|
|
|
// l = 1(固定)
|
|
|
// r = size(你设置的线段树大小)
|
|
|
// rt = 1(固定)
|
|
|
public void update(int L, int R, int C, int l, int r, int rt) {
|
|
|
if (L <= l && r <= R) {
|
|
|
update[rt] = true;
|
|
|
change[rt] = C;
|
|
|
min[rt] = C;
|
|
|
return;
|
|
|
}
|
|
|
int mid = (l + r) >> 1;
|
|
|
pushDown(rt, mid - l + 1, r - mid);
|
|
|
if (L <= mid) {
|
|
|
update(L, R, C, l, mid, rt << 1);
|
|
|
}
|
|
|
if (R > mid) {
|
|
|
update(L, R, C, mid + 1, r, rt << 1 | 1);
|
|
|
}
|
|
|
pushUp(rt);
|
|
|
}
|
|
|
|
|
|
// 最后三个参数是固定的, 每次传入相同的值即可:
|
|
|
// l = 1(固定)
|
|
|
// r = size(你设置的线段树大小)
|
|
|
// rt = 1(固定)
|
|
|
public int query(int L, int R, int l, int r, int rt) {
|
|
|
if (L <= l && r <= R) {
|
|
|
return min[rt];
|
|
|
}
|
|
|
int mid = (l + r) >> 1;
|
|
|
pushDown(rt, mid - l + 1, r - mid);
|
|
|
int left = Integer.MAX_VALUE;
|
|
|
int right = Integer.MAX_VALUE;
|
|
|
if (L <= mid) {
|
|
|
left = query(L, R, l, mid, rt << 1);
|
|
|
}
|
|
|
if (R > mid) {
|
|
|
right = query(L, R, mid + 1, r, rt << 1 | 1);
|
|
|
}
|
|
|
return Math.min(left, right);
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 为了测试
|
|
|
public static int[][] randomMatrix(int n, int m, int v) {
|
|
|
int[][] ans = new int[n][m];
|
|
|
for (int i = 0; i < n; i++) {
|
|
|
for (int j = 0; j < m; j++) {
|
|
|
ans[i][j] = (int) (Math.random() * v);
|
|
|
}
|
|
|
}
|
|
|
return ans;
|
|
|
}
|
|
|
|
|
|
// 为了测试
|
|
|
public static void main(String[] args) {
|
|
|
// 先展示一下线段树的用法,假设N=100
|
|
|
// 初始化时,1~100所有位置的值都是系统最大
|
|
|
System.out.println("线段树展示开始");
|
|
|
int N = 100;
|
|
|
SegmentTree st = new SegmentTree(N);
|
|
|
// 查询8~19范围上的最小值
|
|
|
System.out.println(st.query(8, 19, 1, N, 1));
|
|
|
// 把6~14范围上对应的值都修改成56
|
|
|
st.update(6, 14, 56, 1, N, 1);
|
|
|
// 查询8~19范围上的最小值
|
|
|
System.out.println(st.query(8, 19, 1, N, 1));
|
|
|
// 以上是线段树的用法,你可以随意使用update和query方法
|
|
|
// 线段树的详解请看体系学习班
|
|
|
System.out.println("线段树展示结束");
|
|
|
|
|
|
// 以下为正式测试
|
|
|
int len = 10;
|
|
|
int value = 8;
|
|
|
int testTimes = 10000;
|
|
|
System.out.println("对数器测试开始");
|
|
|
for (int i = 0; i < testTimes; i++) {
|
|
|
int n = (int) (Math.random() * len) + 1;
|
|
|
int m = (int) (Math.random() * len) + 1;
|
|
|
int[][] map = randomMatrix(n, m, value);
|
|
|
int ans1 = jump1(map);
|
|
|
int ans2 = jump2(map);
|
|
|
if (ans1 != ans2) {
|
|
|
System.out.println("出错了!");
|
|
|
}
|
|
|
}
|
|
|
System.out.println("对数器测试结束");
|
|
|
}
|
|
|
|
|
|
}
|