You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

240 lines
7.4 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package class39;
// 来自京东
// 给定一个二维数组matrixmatrix[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("对数器测试结束");
}
}