算法_水位上升的泳池中游泳

记录算法博客主要是记录学习过程中,目前阶段还是不能主动地去写出算法。

 

1 题目来源 leetcode

https://leetcode-cn.com/problems/swim-in-rising-water/solution/shui-wei-shang-sheng-de-yong-chi-zhong-you-yong-by/

 

 

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) 程序如何实现二分查找的。

 

posted @ 2019-11-18 22:53  S-Mustard  阅读(403)  评论(0编辑  收藏  举报