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,这样对于搜索会很快......