【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
提示:
- 死亡列表
deadends
的長度範圍為[1, 500]
。 - 目標數字
target
不會在deadends
之中。 - 每個
deadends
和target
中的字串的數字會在 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,這樣對於搜尋會很快......