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

記錄算法博客主要是記錄學習過程當中,目前階段仍是不能主動地去寫出算法。java

 

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+列下標的值?ui

2) 若是根據列隊中的存儲的數據來比較對應的數組中對應的點的數據。spa

3) 算法思路:選擇走的點是全部待走點的最小值,最後的結果就是在全部已經走過的點中取最大值。code

4) PriorityQueue 優先隊列的使用,比較器的使用,拿出來的值必定是最小值。blog

 

測試數據     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) 程序如何實現二分查找的。

相關文章
相關標籤/搜索