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

猜你喜欢

转载自blog.csdn.net/qq_29567851/article/details/84347868
今日推荐