算法_水位上升的泳池中游泳
记录算法博客主要是记录学习过程中,目前阶段还是不能主动地去写出算法。
1 题目来源 leetcode
2 leetcode官方方法一
System.out代码是为了从控制台打印出算法执行的数据路线,从而达到能很好理解算法。
import java.util.*; class Solution { public int swimInWater(int[][] grid) { int N = grid.length; // 记录走过的点 Set<Integer> seen = new HashSet(); // 记录对于当前点来说,将要和之前因为某个选择未走的点,需要在这个数组中获取最小的值,然后重新走 // (k1, k2) -> grid[k1 / N][k1 % N] - grid[k2 / N][k2 % N]是lambda表达式,是int compare(T o1, T o2);的方法实现 // 换言之如果想比较列队中k1,k2的大小,则实际比较的是grid[k1 / N][k1 % N],grid[k2 / N][k2 % N]的大小 // 因为行坐标*N+列坐标的值能确定数组中的一点,所以列队中存的是行坐标*N+列坐标值,从而根据列队中的值也能推导出来数组中的点 PriorityQueue<Integer> pq = new PriorityQueue<Integer>((k1, k2) -> grid[k1 / N][k1 % N] - grid[k2 / N][k2 % N]); pq.offer(0); // 记录水位 int ans = 0; // 控制当前点可以走的下一个点的位置,即上下左右 int[] dr = new int[]{1, -1, 0, 0}; int[] dc = new int[]{0, 0, 1, -1}; while (!pq.isEmpty()) { // 获取最小值,为当前需要走的点 int k = pq.poll(); // r为行下标,c为列下标 int r = k / N, c = k % N; // ans记录的是已经走的点的最大值 ans = Math.max(ans, grid[r][c]); if (r == N-1 && c == N-1) return ans;//达到终点结束遍历 // 取当前点的上下左右四个点存到待走的点里面 System.out.println("开始搜索:"); for (int i = 0; i < 4; ++i) { // cr为数组行下标,cc为数组列下标 int cr = r + dr[i], cc = c + dc[i]; // ck记录该点 cr * N + cc的值能确定一个点 int ck = cr * N + cc; if (0 <= cr && cr < N && 0 <= cc && cc < N && !seen.contains(ck)) { // 将要走的点放入列队中 pq.offer(ck); // 走的点记录下来 seen.add(ck); System.out.print("当前所在点:["+r+","+c+"]"+"搜索方向:["+dr[i]+","+dc[i]+"]"+"搜索结果:["+cr+","+cc+"]"+"存入列队的点:"+ck+","+"存入列队的点对应的数组值:"+grid[cr][cc]+","); System.out.print("目前列队存储的全部点对应的值:["); for(Integer a:pq){ System.out.print(grid[a/N][a%N]+","); } System.out.print("]"); System.out.println(); } } } throw null; } }
思考:
1) 列队中存储的是 行下标*N+列下标的值?
2) 如果根据列队中的存储的数据来比较对应的数组中对应的点的数据。
3) 算法思路:选择走的点是所有待走点的最小值,最后的结果就是在所有已经走过的点中取最大值。
4) PriorityQueue 优先队列的使用,比较器的使用,拿出来的值一定是最小值。
测试数据 int[][] a={{0,4,1},{2,8,7},{3,6,5}};
输出结果 最后一行的6就是答案,前面的控制台打印出来的都是数据路线
开始搜索: 当前所在点:[0,0]搜索方向:[1,0]搜索结果:[1,0]存入列队的点:3,存入列队的点对应的数组值:2,目前列队存储的全部点对应的值:[2,] 当前所在点:[0,0]搜索方向:[0,1]搜索结果:[0,1]存入列队的点:1,存入列队的点对应的数组值:4,目前列队存储的全部点对应的值:[2,4,] 开始搜索: 当前所在点:[1,0]搜索方向:[1,0]搜索结果:[2,0]存入列队的点:6,存入列队的点对应的数组值:3,目前列队存储的全部点对应的值:[3,4,] 当前所在点:[1,0]搜索方向:[-1,0]搜索结果:[0,0]存入列队的点:0,存入列队的点对应的数组值:0,目前列队存储的全部点对应的值:[0,4,3,] 当前所在点:[1,0]搜索方向:[0,1]搜索结果:[1,1]存入列队的点:4,存入列队的点对应的数组值:8,目前列队存储的全部点对应的值:[0,4,3,8,] 开始搜索: 开始搜索: 当前所在点:[2,0]搜索方向:[0,1]搜索结果:[2,1]存入列队的点:7,存入列队的点对应的数组值:6,目前列队存储的全部点对应的值:[4,8,6,] 开始搜索: 当前所在点:[0,1]搜索方向:[0,1]搜索结果:[0,2]存入列队的点:2,存入列队的点对应的数组值:1,目前列队存储的全部点对应的值:[1,8,6,] 开始搜索: 当前所在点:[0,2]搜索方向:[1,0]搜索结果:[1,2]存入列队的点:5,存入列队的点对应的数组值:7,目前列队存储的全部点对应的值:[6,8,7,] 开始搜索: 当前所在点:[2,1]搜索方向:[0,1]搜索结果:[2,2]存入列队的点:8,存入列队的点对应的数组值:5,目前列队存储的全部点对应的值:[5,8,7,] 开始搜索: 当前所在点:[0,0]搜索方向:[1,0]搜索结果:[1,0]存入列队的点:3,存入列队的点对应的数组值:2,目前列队存储的全部点对应的值:[2,] 当前所在点:[0,0]搜索方向:[0,1]搜索结果:[0,1]存入列队的点:1,存入列队的点对应的数组值:4,目前列队存储的全部点对应的值:[2,4,] 开始搜索: 当前所在点:[1,0]搜索方向:[1,0]搜索结果:[2,0]存入列队的点:6,存入列队的点对应的数组值:3,目前列队存储的全部点对应的值:[3,4,] 当前所在点:[1,0]搜索方向:[-1,0]搜索结果:[0,0]存入列队的点:0,存入列队的点对应的数组值:0,目前列队存储的全部点对应的值:[0,4,3,] 当前所在点:[1,0]搜索方向:[0,1]搜索结果:[1,1]存入列队的点:4,存入列队的点对应的数组值:8,目前列队存储的全部点对应的值:[0,4,3,8,] 开始搜索: 开始搜索: 当前所在点:[2,0]搜索方向:[0,1]搜索结果:[2,1]存入列队的点:7,存入列队的点对应的数组值:6,目前列队存储的全部点对应的值:[4,8,6,] 开始搜索: 当前所在点:[0,1]搜索方向:[0,1]搜索结果:[0,2]存入列队的点:2,存入列队的点对应的数组值:1,目前列队存储的全部点对应的值:[1,8,6,] 开始搜索: 当前所在点:[0,2]搜索方向:[1,0]搜索结果:[1,2]存入列队的点:5,存入列队的点对应的数组值:7,目前列队存储的全部点对应的值:[6,8,7,] 开始搜索: 当前所在点:[2,1]搜索方向:[0,1]搜索结果:[2,2]存入列队的点:8,存入列队的点对应的数组值:5,目前列队存储的全部点对应的值:[5,8,7,] 6
3 leetcode官方方法二
System.out代码是为了从控制台打印出算法执行的数据路线,从而达到能很好理解算法。
自己的理解:算法是二分搜索,因为题目中已经明确说出 grid[i][j] 位于区间 [0, ..., N*N - 1] 内,即我们知道数组中的值最小值为0,最大值为N*N - 1,可以通过试错的方法,并且这个试错方法是二分算法,
第一次深度遍历时,发现有一条路线比(n*n-1)/2小,则调整比较的数字范围,做第二次深度遍历,发现有一条路线比(n*n-1)/2/2小,则继续调整比较的范围,如此进行。遍历时,如果发现没有比猜测值小的,则将比较值做相反的调整。
import java.util.*; class Solution { public int swimInWater(int[][] grid) { int N = grid.length; int lo = grid[0][0], hi = N * N; while (lo < hi) {// 判断猜测的结束 System.out.println("猜测开始||||||||||||||||||||||||||||||||||||||"); // 定义猜测值 int mi = lo + (hi - lo) / 2; System.out.println("当次遍历猜测值为:"+mi); if (!possible(mi, grid)) { lo = mi + 1;// 没有比猜测值小的,猜测值需要往大猜测 } else { hi = mi;// 有比猜测值小的,猜测值需要往小猜测 } System.out.println("猜测结束||||||||||||||||||||||||||||||||||||||"); System.out.println(); System.out.println(); System.out.println(); } return lo; } public boolean possible(int T, int[][] grid) { int N = grid.length; Set<Integer> seen = new HashSet(); seen.add(0);// 存储访问过的点 int[] dr = new int[]{1, -1, 0, 0};// 定义行走的方向 int[] dc = new int[]{0, 0, 1, -1};// 定义行走的方向 Stack<Integer> stack = new Stack(); stack.add(0);// 深度遍历协助的栈结构,达到深度遍历的效果 System.out.println("开始遍历========="); // 深度遍历 while (!stack.empty()) { // 栈找那个存储的是cr * N + cc值,返回来也可以推测出是哪个点 int k = stack.pop(); int r = k / N, c = k % N; System.out.println("当前行:"+r+","+"当前列:"+c+"当前值:"+grid[r][c]); if (r == N-1 && c == N-1) { System.out.println("有一条路径的值都比预测值小"); return true;// 如果返回的是true说明至少有一条路线,值都比猜测值小 } for (int i = 0; i < 4; ++i) { int cr = r + dr[i], cc = c + dc[i]; int ck = cr * N + cc; // grid[cr][cc] <= T 决定一条路径是否能走到头,与 if (r == N-1 && c == N-1)配合完成程序 if (0 <= cr && cr < N && 0 <= cc && cc < N && !seen.contains(ck) && grid[cr][cc] <= T) { stack.add(ck); seen.add(ck); } } } System.out.println("结束遍历========="); // 从程序可以看出如果一条路径中的一个点比T大,那么这条路径是无法走到头的,能走到头的即满足r == N-1 && c == N-1条件的就是至少有一条路线值都比T小 // 如果返回false的话那就是所有的路径都至少有一个点比T大,那么猜测的值肯定是猜测小了的,需要往大猜测。 System.out.println("在所有路径中,对于一个路径至少有一个点比T值大"); return false;// 如果返回值为false说明没有一条路径,最大值比猜测值小 } }
测试数据 int[][] a={{0,4,1},{2,8,7},{3,6,5}};
输出结果 最后一行的6就是答案,前面的控制台打印出来的都是数据路线
第一个猜测解析: 遍历0->4->1 ,想遍历7时发现比预测值大,不进行遍历,即不存入栈中,也就不可能做下面的遍历;遍历0->4时,准备走8,也不符合;遍历0->2>3,准备走6时,发现又不合符,此时栈内为空,无法走下去,没有走到终点,程序返回false.
其它的猜测都很好理解,都能走点终点.当lo >= hi时,此时就不必猜测,因为会陷入无限的相同输出结果中,自己可以试验一下,自己设置循环次数,当然这个循环次数要比找到正确结果的循环次数大。
猜测开始|||||||||||||||||||||||||||||||||||||| 当次遍历猜测值为:4 开始遍历========= 当前行:0,当前列:0当前值:0 当前行:0,当前列:1当前值:4 当前行:0,当前列:2当前值:1 当前行:1,当前列:0当前值:2 当前行:2,当前列:0当前值:3 结束遍历========= 在所有路径中,对于一个路径至少有一个点比T值大 猜测结束|||||||||||||||||||||||||||||||||||||| 猜测开始|||||||||||||||||||||||||||||||||||||| 当次遍历猜测值为:7 开始遍历========= 当前行:0,当前列:0当前值:0 当前行:0,当前列:1当前值:4 当前行:0,当前列:2当前值:1 当前行:1,当前列:2当前值:7 当前行:2,当前列:2当前值:5 有一条路径的值都比预测值小 猜测结束|||||||||||||||||||||||||||||||||||||| 猜测开始|||||||||||||||||||||||||||||||||||||| 当次遍历猜测值为:6 开始遍历========= 当前行:0,当前列:0当前值:0 当前行:0,当前列:1当前值:4 当前行:0,当前列:2当前值:1 当前行:1,当前列:0当前值:2 当前行:2,当前列:0当前值:3 当前行:2,当前列:1当前值:6 当前行:2,当前列:2当前值:5 有一条路径的值都比预测值小 猜测结束|||||||||||||||||||||||||||||||||||||| 猜测开始|||||||||||||||||||||||||||||||||||||| 当次遍历猜测值为:5 开始遍历========= 当前行:0,当前列:0当前值:0 当前行:0,当前列:1当前值:4 当前行:0,当前列:2当前值:1 当前行:1,当前列:0当前值:2 当前行:2,当前列:0当前值:3 结束遍历========= 在所有路径中,对于一个路径至少有一个点比T值大 猜测结束|||||||||||||||||||||||||||||||||||||| 6
思考:
1) 二分查找概念。
2) 程序如何实现二分查找的。