1. 程式人生 > 實用技巧 >Leetcode752題-開啟轉盤鎖問題(BFS求解)

Leetcode752題-開啟轉盤鎖問題(BFS求解)

首先介紹一下題目,網址如右:https://leetcode-cn.com/problems/open-the-lock/

你有一個帶有四個圓形撥輪的轉盤鎖。每個撥輪都有10個數字: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' 。每個撥輪可以自由旋轉:例如把 '9' 變為 '0','0' 變為 '9' 。每次旋轉都只能旋轉一個撥輪的一位數字。

鎖的初始數字為 '0000' ,一個代表四個撥輪的數字的字串。

列表 deadends 包含了一組死亡數字,一旦撥輪的數字和列表裡的任何一個元素相同,這個鎖將會被永久鎖定,無法再被旋轉。

字串 target 代表可以解鎖的數字,你需要給出最小的旋轉次數,如果無論如何不能解鎖,返回 -1。

  為什麼會想到利用BFS,即廣度優先搜尋解決?首先分析題目,密碼鎖每處於一個狀態時,下一次都有八種可能的狀態去轉換(每一位都有兩種變化),可以將每一個字串狀態看成是樹的一個節點,下面都連線著八個後繼節點。

  那麼好,問題轉化成樹了,緊接著就會想到深度優先搜尋(DFS)和廣度優先搜尋(BFS),為什麼用BFS呢,因為題目中求得是“最小旋轉次數”,一旦涉及到最小的問題都應該想到用BFS求解。因為對於樹來說,BFS是一層一層的去遍歷,一旦在某一層找到結果就會直接返回,而DFS是一次搜尋完一個分支,最終搜尋完所有分支才得到結果,所以只要最優解不在樹的最後一層,那麼BFS所用的時間就會比DFS更少(因為訪問的節點數更少).

  好了,既然解法有了,應該使用BFS,具體應該怎麼做呢?

  BFS儲存的資料結構最好用佇列實現,佇列裡每次儲存的是一層的樹節點,訪問完一層就將其全部推出繼續加入下一層。一般來說,這類BFS求解的題目都有一個模板,大致如下:

  

int solution(){
        Queue<XX> qu=new LinkedList<>();
        qu.offer(初始XX);  //初始值輸入
        visited[];//儲存已經訪問過的狀態,按照情況選擇布林陣列或者set集合儲存
        int res=0;   //初始化結果
        while
(!qu.isEmpty()){ int sz=qu.size(); for(int i=0;i<sz;i++){ XX xx=qu.poll(); 訪問xx的後繼狀態,如果visited包含它或者有其他限制,則continue; 否則將其加入qu; 同時加入visited中; } res++; //這個時候res自增 } return res; }

  模板已經有了,稍微思考一下就能得到下面的解法:

public int openLock(String[] deadends, String target) {
        Queue<String> qu=new LinkedList<>();
        qu.offer("0000");//初始狀態入隊
        Set<String> vis=new HashSet<>();//儲存已經到達過的狀態
        Set<String> dead=new HashSet<>();  //儲存死亡狀態
        for(String s:deadends) dead.add(s);
        int res=0;  //儲存步數
        while(!qu.isEmpty()){
            int sz=qu.size();
            for(int i=0;i<sz;i++){
                String ss=qu.poll();
                if(dead.contains(ss))
                    continue;
                if(ss.equals(target))
                    return res;
                for(int j=0;j<4;j++){    //關於後繼狀態如何增加,不同的題目不一樣,要學會轉化條件
                    String s1=upper(ss,j);
                    String s2=down(ss,j);
                    if(!vis.contains(s1)){
                        vis.add(s1);
                        qu.offer(s1);
                    }
                    if(!vis.contains(s2)){
                        vis.add(s2);
                        qu.offer(s2);
                    }
                }
            }
            res++;
        }
        return -1;
    }

    //轉盤鎖順時針旋轉
    String upper(String s,int idx){
        char[] c=s.toCharArray();     //注意如何完成String與char陣列的互相轉化,以後可能還會用到
        if(c[idx]=='9') c[idx]='0';
        else c[idx]++;
        return new String(c);
    }

    //轉盤鎖逆時針旋轉
    String down(String s,int idx){
        char[] c=s.toCharArray();
        if(c[idx]=='0') c[idx]='9';
        else c[idx]--;
        return new String(c);
    }

  該解法確實通過了Leetcode所有案例,雖然或許時間複雜度不是最優,但是應該是最為普遍,最廣為適用的解法了。

  以上就是我對於這一題的解法,如有不足請在評論區批評指正。