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


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

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

  • 相关阅读:
    mybatis公用代码抽取到单独的mapper.xml文件
    mysql与oracle常用函数及数据类型对比00持续补充
    人民币在岸 离岸 中间价的含义与关系
    mysql hang and srv_error_monitor_thread using 100% cpu(已解决)
    long和BigDecimal引发的管理思考
    mybatis 3的TypeHandler深入解析(及null值的处理)
    mysql 5.7.17发布
    rabbitmq connection/channel/consumer/queue的数量关系详细分析
    rabbitMQ publish丢包分析
    INFO: task java:27465 blocked for more than 120 seconds不一定是cache太大的问题
  • 原文地址:https://www.cnblogs.com/S-Mustard/p/11829732.html
Copyright © 2020-2023  润新知