1. 程式人生 > >【leetcode】探索佇列和棧

【leetcode】探索佇列和棧

 1.迴圈佇列的實現

python的列表真是好用啊,因為它是動態儲存的,所以迴圈佇列對於它來說,簡單的幾行程式碼就可以實現了。不像C的,指標需要指來指去。

迴圈佇列的目的就是為了不浪費儲存,而動態的列表恰恰就完美的符合這個要求,再有切片操作就可以輕鬆的取首元素和尾元素。強調一下:佇列是--FIFO,先進先出。

class MyCircularQueue:
    

    def __init__(self, k):
        """
        Initialize your data structure here. Set the size of the queue to be k.
        :type k: int
        """
        self.list1=[]
        self.size=k

    def enQueue(self, value):
        """
        Insert an element into the circular queue. Return true if the operation is successful.
        :type value: int
        :rtype: bool
        """
        if(self.isFull()):
            return False
        self.list1.append(value)
        return True

    def deQueue(self):
        """
        Delete an element from the circular queue. Return true if the operation is successful.
        :rtype: bool
        """
        if(self.isEmpty()):
            return False
        del self.list1[0]
        return True

    def Front(self):
        """
        Get the front item from the queue.
        :rtype: int
        """
        if(self.isEmpty()):
            return -1
        else:
            return self.list1[0]

    def Rear(self):
        """
        Get the last item from the queue.
        :rtype: int
        """
        if(self.isEmpty()):
            return -1
        else:
            return self.list1[-1]

    def isEmpty(self):
        """
        Checks whether the circular queue is empty or not.
        :rtype: bool
        """
        return len(self.list1)==0
            

    def isFull(self):
        """
        Checks whether the circular queue is full or not.
        :rtype: bool
        """
        return len(self.list1)==self.size

2.佇列和廣度優先搜尋

廣度優先搜尋(BFS)的一個常見應用是找出從根結點到目標結點的最短路徑

1. 結點的處理順序是什麼?

在第一輪中,我們處理根結點。在第二輪中,我們處理根結點旁邊的結點;在第三輪中,我們處理距根結點兩步的結點;等等等等。

與樹的層序遍歷類似,越是接近根結點的結點將越早地遍歷

如果在第 k 輪中將結點 X 新增到佇列中,則根結點與 X 之間的最短路徑的長度恰好是 k。也就是說,第一次找到目標結點時,你已經處於最短路徑中。

2. 佇列的入隊和出隊順序是什麼?

如上面的動畫所示,我們首先將根結點排入佇列。然後在每一輪中,我們逐個處理已經在佇列中的結點,並將所有鄰居新增到佇列中。值得注意的是,新新增的節點不會

立即遍歷,而是在下一輪中處理。

結點的處理順序與它們新增到佇列的順序是完全相同的順序,即先進先出(FIFO)。這就是我們在 BFS 中使用佇列的原因。

虛擬碼:

/**
 * Return the length of the shortest path between root and target node.
 */
int BFS(Node root, Node target) {
    Queue<Node> queue;  // store all nodes which are waiting to be processed
    int step = 0;       // number of steps neeeded from root to current node
    // initialize
    add root to queue;
    // BFS
    while (queue is not empty) {
        step = step + 1;
        // iterate the nodes which are already in the queue
        int size = queue.size();
        for (int i = 0; i < size; ++i) {
            Node cur = the first node in queue;
            return step if cur is target;
            for (Node next : the neighbors of cur) {
                add next to queue;
            }
            remove the first node from queue;
        }
    }
    return -1;          // there is no path from root to target
}

2.1 島嶼的個數

給定一個由 '1'(陸地)和 '0'(水)組成的的二維網格,計算島嶼的數量。一個島被水包圍,並且它是通過水平方向或垂直方向上相鄰的陸地連線而成的。你可以假設網格的四個邊均被水包圍。

示例 1:

輸入:
11110
11010
11000
00000

輸出: 1

示例 2:

輸入:
11000
11000
00100
00011

輸出: 3

我的python實現(渣渣):

class Solution:
    def numIslands(self, grid):
        """
        :type grid: List[List[str]]
        :rtype: int
        """
        if len(grid)==0:
            return 0
        visited_dict={}#因為搜尋太慢,不給通過,所以試試用字典(雜湊表)
        queue=[]#注意!!!不用特地宣告為二維列表,[[]]反而會佔據一個儲存空間,不是空列表
        x1=[]
        x2=[]
        num=0
        height=len(grid)
        width=len(grid[0])
        #square=height*width
        for i in range(height):
            for j in range(width):
                x1=[i,j]
                if (str(x1) in visited_dict) or grid[i][j]=='0':
                    continue
                else:
                    queue.append(x1)
                    visited_dict[str(x1)]=1       
                    while len(queue)!=0:
                        size=len(queue)
                        for k in range(size):
                            x=queue[0][0]
                            y=queue[0][1]
                            if x>0:
                                x2=[x-1,y]
                                if str(x2) not in visited_dict and grid[x2[0]][x2[1]]=='1':
                                    queue.append(x2)
                                    visited_dict[str(x2)]=1
                            if y>0:
                                x2=[x,(y-1)]
                                if str(x2) not in visited_dict and grid[x2[0]][x2[1]]=='1':
                                    queue.append(x2)
                                    visited_dict[str(x2)]=1
                            if y<(width-1):
                                x2=[x,y+1]
                                if str(x2) not in visited_dict and grid[x2[0]][x2[1]]=='1':
                                    queue.append(x2)
                                    visited_dict[str(x2)]=1
                            if x<(height-1):
                                x2=[x+1,y]
                                if str(x2) not in visited_dict and grid[x2[0]][x2[1]]=='1':
                                    queue.append(x2)
                                    visited_dict[str(x2)]=1
                            del queue[0]
                    num=num+1
        return num

真是滿滿的面對程式設計的思維,沒有想到用遞迴這個思想。再看看大神的程式碼,簡潔又有力量,執行速度為64ms,而我的要300ms:

class Solution:
    def numIslands(self, grid):
        """
        :type grid: List[List[str]]
        :rtype: int
        """
        if not grid or not grid[0]:
            return 0
        def fd(grid,x,y,r,c):
            grid[x][y]='-1'
            if x>0 and grid[x-1][y]=='1':
                fd(grid,x-1,y,r,c)
            if x<r-1 and grid[x+1][y]=='1':
                fd(grid,x+1,y,r,c)
            if y>0 and grid[x][y-1]=='1':
                fd(grid,x,y-1,r,c)
            if y<c-1 and grid[x][y+1]=='1':
                fd(grid,x,y+1,r,c)
        r,c=len(grid),len(grid[0])
        cnt=0
        for x in range(r):
            for y in range(c):
                if grid[x][y]=='1':
                    fd(grid,x,y,r,c)
                    cnt+=1
        return cnt

可是仔細一看這個程式碼,它的實現用的好像是深度優先搜尋+棧的思想......

2.2 開啟轉盤鎖

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

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

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

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

示例 1:

輸入:deadends = ["0201","0101","0102","1212","2002"], target = "0202"
輸出:6
解釋:
可能的移動序列為 "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202"。
注意 "0000" -> "0001" -> "0002" -> "0102" -> "0202" 這樣的序列是不能解鎖的,
因為當撥動到 "0102" 時這個鎖就會被鎖定。

示例 2:

輸入: deadends = ["8888"], target = "0009"
輸出:1
解釋:
把最後一位反向旋轉一次即可 "0000" -> "0009"。

示例 3:

輸入: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888"
輸出:-1
解釋:
無法旋轉到目標數字且不被鎖定。

示例 4:

輸入: deadends = ["0000"], target = "8888"
輸出:-1

 

提示:

  1. 死亡列表 deadends 的長度範圍為 [1, 500]
  2. 目標數字 target 不會在 deadends 之中。
  3. 每個 deadendstarget 中的字串的數字會在 10,000 個可能的情況 '0000''9999' 中產生。

我的程式碼,執行用了1740ms,真是震驚......

class Solution:
    def openLock(self, deadends, target):
        """
        :type deadends: List[str]
        :type target: str
        :rtype: int
        """
        #加一個程式碼deadends=set(deadends),時間就變成了684ms。可見set的好處,它是一個無序不重複元素集,它的搜尋屬於雜湊表搜尋,所以會很快!!
        visited={}
        queue=['0000']
        visited['0000']=1
        steps=0
        if '0000' in deadends:
            return -1
        while len(queue)!=0:
            if target in queue:
                return steps
            for i in range(len(queue)):
                x1=queue[0][0]
                x2=queue[0][1]
                x3=queue[0][2]
                x4=queue[0][3]
                y=str((int(x1)+1)%10)+x2+x3+x4
                if y not in visited and y not in deadends:
                    queue.append(y)
                    visited[y]=1
                y=str((int(x1)-1)%10)+x2+x3+x4
                if y not in visited and y not in deadends:
                    queue.append(y)
                    visited[y]=1
                y=x1+str((int(x2)+1)%10)+x3+x4
                if y not in visited and y not in deadends:
                    queue.append(y)
                    visited[y]=1
                y=x1+str((int(x2)-1)%10)+x3+x4
                if y not in visited and y not in deadends:
                    queue.append(y)
                    visited[y]=1
                y=x1+x2+str((int(x3)+1)%10)+x4
                if y not in visited and y not in deadends:
                    queue.append(y)
                    visited[y]=1
                y=x1+x2+str((int(x3)-1)%10)+x4
                if y not in visited and y not in deadends:
                    queue.append(y)
                    visited[y]=1
                y=x1+x2+x3+str((int(x4)+1)%10)
                if y not in visited and y not in deadends:
                    queue.append(y)
                    visited[y]=1
                y=x1+x2+x3+str((int(x4)-1)%10)
                if y not in visited and y not in deadends:
                    queue.append(y)
                    visited[y]=1
                del queue[0]
            steps=steps+1
        return -1

visited和deadends改用set(),變成了680ms:

class Solution:
    def openLock(self, deadends, target):
        """
        :type deadends: List[str]
        :type target: str
        :rtype: int
        """
        deadends=set(deadends)
        visited={}
        queue=['0000']
        visited=set()
        visited.add('0000')
        steps=0
        if '0000' in deadends:
            return -1
        while len(queue)!=0:
            if target in queue:
                return steps
            for i in range(len(queue)):
                x1=queue[0][0]
                x2=queue[0][1]
                x3=queue[0][2]
                x4=queue[0][3]
                y=str((int(x1)+1)%10)+x2+x3+x4
                if y not in visited and y not in deadends:
                    queue.append(y)
                    visited.add(y)
                y=str((int(x1)-1)%10)+x2+x3+x4
                if y not in visited and y not in deadends:
                    queue.append(y)
                    visited.add(y)
                y=x1+str((int(x2)+1)%10)+x3+x4
                if y not in visited and y not in deadends:
                    queue.append(y)
                    visited.add(y)
                y=x1+str((int(x2)-1)%10)+x3+x4
                if y not in visited and y not in deadends:
                    queue.append(y)
                    visited.add(y)
                y=x1+x2+str((int(x3)+1)%10)+x4
                if y not in visited and y not in deadends:
                    queue.append(y)
                    visited.add(y)
                y=x1+x2+str((int(x3)-1)%10)+x4
                if y not in visited and y not in deadends:
                    queue.append(y)
                    visited.add(y)
                y=x1+x2+x3+str((int(x4)+1)%10)
                if y not in visited and y not in deadends:
                    queue.append(y)
                    visited.add(y)
                y=x1+x2+x3+str((int(x4)-1)%10)
                if y not in visited and y not in deadends:
                    queue.append(y)
                    visited.add(y)
                del queue[0]
            steps=steps+1
        return -1

再來看看大神的程式碼,可是其中的程式碼原理比較玄學,可以說是抓住了這個問題的關鍵,就是遇到障礙就要+1、-1.只用了44ms:

class Solution:

    def openLock(self, deadends, target):
        """
        :type deadends: List[str]
        :type target: str
        :rtype: int
        """

        # 檢視是否有解 ####
        if "0000" in deadends:
            return -1

        lst = []
        for i, n in enumerate(list(target)):
            b = list(target)
            if n == "9":
                b[i] = "0"
            else:
                b[i] = str(int(n) + 1)
            c = "".join(b)
            lst.append(c)
            if n == "0":
                b[i] = "9"
            else:
                b[i] = str(int(n) - 1)
            c = "".join(b)
            lst.append(c)

        dead_flag = True
        for i in lst:
            if i not in deadends:
                dead_flag = False
                break
        if dead_flag:
            return -1
        ###########

        # 計算最小步數
        num = 0
        for i, n in enumerate(target):
            if int(n) > 5:
                num += (10 - int(n))
            else:
                num += int(n)

        # 檢視是否能夠使用最小步數
        lst2 = set()
        for i, n in enumerate(list(target)):
            b = list(target)
            if int(n) > 5:
                if n == "9":
                    b[i] = "0"
                else:
                    b[i] = str(int(n) + 1)
            else:
                if n == "0":
                    b[i] = "0"
                else:
                    b[i] = str(int(n) - 1)
            c = "".join(b)
            lst2.add(c)

        if target in lst2:
            lst2.remove(target)

        min_flag = False
        for i in lst2:
            if i not in deadends:
                min_flag = True
                break

        return num if min_flag else num + 2

再看看這個160ms的大神,我覺得這裡面的思想和我的類似,但是人家可以這麼快的實現,肯定有很多技術細節值得學習,所以在這貼出來進行參考:

class Solution:
    def openLock(self, deadends, target):
        """
        :type deadends: List[str]
        :type target: str
        :rtype: int
        """
        dSet = set(deadends)
        
        
        if "0000" in deadends:
            return -1
        
        setOne = set()#儲存較小的set
        setOne.add("0000")
        setTwo = set()#儲存較大的set
        setTwo.add(target)
        visitedHash = set()#用於標示已經遍歷過的情況,不然會死迴圈
        visitedHash.add("0000")
        
        res = 0
        while len(setOne) != 0 and len(setTwo)!=0:
            #先把較小的換到setOne來
            if len(setOne)>len(setTwo):
                tmp = setOne
                setOne = setTwo
                setTwo = tmp
            
            print(setOne)
            tmpSet = set()
            #把所有的set中都先進行BFS,新增的儲存在tmpSet中
            for s in setOne:
                for i in range(len(target)):#對每個數字進行廣度遍歷
                    l = list(s)
                    tmpAdd = str((int(s[i])+10+1)%10)
                    tmpMin = str((int(s[i])+10-1)%10)
                    l[i] = tmpAdd
                    addStr = ''.join(l)
                    l[i] = tmpMin
                    minStr = ''.join(l)
                    
                    if minStr in setTwo or addStr in setTwo:#已經在另外一個集合中,那麼就可以到達了
                        return res+1
                    #判斷是否已經遍歷過,以及是否在deadends裡面
                    if addStr not in dSet and addStr not in visitedHash:
                        tmpSet.add(addStr)
                        visitedHash.add(addStr)
                    if minStr not in dSet and minStr not in visitedHash:
                        tmpSet.add(minStr)
                        visitedHash.add(minStr)
            setOne = tmpSet
            res+=1
                        
        return -1

我覺得這個程式碼比我強的地方就是,它的程式碼中只要是關於搜尋的,全都是用的set()這個集合資料結構。像我的程式碼中搜索target在queue中就耗費了我很多時間,因為經過BFS搜尋之後,queue裡面的元素是比較多的,所以用列表這個資料結構的搜尋會比較慢,更何況是這麼多次迭代都在搜尋。所以最好像他這樣設計好了,所有關於搜尋的資料結構都用set(),因為set()是屬於雜湊表,搜尋會很快。

還有最重要的是setOne和setTwo的運用,我的level太低不能很好的領悟,感覺就像由target和0000開始BFS搜尋,然後從兩端向中間逼近一樣......這樣它用的集合數好像是從頭到尾的1/2,這樣對於搜尋會很快......