队列&栈学习

最近跟着LeetCode看算法相关知识,刚刚把队列&栈这一部分看完,稍微进行总结一下。

队列

在这里插入图片描述
队列简单来说就是先进先出的数据结构,在Python中虽然List也可以实现,但是效率没有使用collections中的deque效率高,所以实现时多使用deque

为了实现空间的有效利用,书中引入了循环队列
在这里插入图片描述
关于循环列表的构建,在【622. 设计循环队列】中给出了比较详细的过程。
大致思路肯定是利用两个指针,一个指向head另一个指向tail,然后根据命令执行pop和push。

官方在题解中说:

设计数据结构的关键是如何设计属性,好的设计属性数量更少。

  • 属性数量少说明属性之间冗余更低。
  • 属性冗余度越低,操作逻辑越简单,发生错误的可能性更低。
  • 属性数量少,使用的空间也少,操作性能更高。

但是,也不建议使用最少的属性数量一定的冗余可以降低操作的时间复杂度,达到时间复杂度和空间复杂度的相对平衡

回顾我之前想用head指针、rear指针和length三个变量来实现,但是逻辑不清晰,导致中间出了不少问题,下面是官方提供的思路:

class MyCircularQueue {
    
    private int[] data;
    private int head;
    private int tail;
    private int size;

    /** Initialize your data structure here. Set the size of the queue to be k. */
    public MyCircularQueue(int k) {
        data = new int[k];
        head = -1;
        tail = -1;
        size = k;
    }
    
    /** Insert an element into the circular queue. Return true if the operation is successful. */
    public boolean enQueue(int value) {
        if (isFull() == true) {
            return false;
        }
        if (isEmpty() == true) {
            head = 0;
        }
        tail = (tail + 1) % size;
        data[tail] = value;
        return true;
    }
    
    /** Delete an element from the circular queue. Return true if the operation is successful. */
    public boolean deQueue() {
        if (isEmpty() == true) {
            return false;
        }
        if (head == tail) {
            head = -1;
            tail = -1;
            return true;
        }
        head = (head + 1) % size;
        return true;
    }
    
    /** Get the front item from the queue. */
    public int Front() {
        if (isEmpty() == true) {
            return -1;
        }
        return data[head];
    }
    
    /** Get the last item from the queue. */
    public int Rear() {
        if (isEmpty() == true) {
            return -1;
        }
        return data[tail];
    }
    
    /** Checks whether the circular queue is empty or not. */
    public boolean isEmpty() {
        return head == -1;
    }
    
    /** Checks whether the circular queue is full or not. */
    public boolean isFull() {
        return ((tail + 1) % size) == head;
    }
}

感觉官方思路比较清晰,和自己的思路对比后感觉有几个点特别好:

  1. 在入列和出列时是调用函数isFull()isEmpty()做的,和我开始时没有意识到使用相比,对于面向对象编程、分块分类分功能实现的步骤更加清晰;
  2. 为了判断队列是否为空,deQueue()中在pop后队列变为空队列时将指针设为 -1,这就避免了运行过程中满栈和空栈难分辨的问题。

当然这种思路想着比较具体,但是实现还是需要注意细节,官方还提供了一种思路,不使用tail,而是用count来记录现在队列的长度,这一方法在实现上更加简单:

class MyCircularQueue:

    def __init__(self, k: int):
        """
        Initialize your data structure here. Set the size of the queue to be k.
        """
        self.queue = [0]*k
        self.headIndex = 0
        self.count = 0
        self.capacity = k

    def enQueue(self, value: int) -> bool:
        """
        Insert an element into the circular queue. Return true if the operation is successful.
        """
        if self.count == self.capacity:
            return False
        self.queue[(self.headIndex + self.count) % self.capacity] = value
        self.count += 1
        return True

    def deQueue(self) -> bool:
        """
        Delete an element from the circular queue. Return true if the operation is successful.
        """
        if self.count == 0:
            return False
        self.headIndex = (self.headIndex + 1) % self.capacity
        self.count -= 1
        return True

    def Front(self) -> int:
        """
        Get the front item from the queue.
        """
        if self.count == 0:
            return -1
        return self.queue[self.headIndex]

    def Rear(self) -> int:
        """
        Get the last item from the queue.
        """
        # empty queue
        if self.count == 0:
            return -1
        return self.queue[(self.headIndex + self.count - 1) % self.capacity]

    def isEmpty(self) -> bool:
        """
        Checks whether the circular queue is empty or not.
        """
        return self.count == 0

    def isFull(self) -> bool:
        """
        Checks whether the circular queue is full or not.
        """
        return self.count == self.capacity

队列和广度优先搜索(BFS)

广度优先搜索(BFS)是一种遍历或搜索数据结构(如树或图)的算法,BFS 在树中执行层序遍历
因为在BFS中结点的处理顺序与它们添加到队列的顺序是完全相同的顺序,即先进先出(FIFO)故使用队列实现BFS。

在题目【286. 墙与门】中,要寻找到门最近的距离:在这里插入图片描述
我的思路是利用队列对于每一个门进行循环,将其变为广度优先搜索,先遍历门附近点,在这过程中将遍历点附近点加入队列,然后依次循环。
对于同一点对于不同门之间距离的取舍,自己的解决方法是取最小值,如果当前distance值不是最小时不加入之后点。
这个思路的问题在于对于同时可以到达多个门的点存在多次遍历,官方题解的解决方法是:把所有门都找出来, 对所有门,使用队列,同时都走了1步,先到达的就是距离短的。
代码见下(自己写的):

class Solution:
    def wallsAndGates(self, rooms: List[List[int]]) -> None:
        m,n = len(rooms),len(rooms[0])
        # 设定四个遍历方向
        dist_group = [(1,0),(-1,0),(0,1),(0,-1)]
        # 建立标志已访问的矩阵
        marked = [[False] * n for _ in range(m)]
        dq = collections.deque()
        # 找到所有门,加入队列
        for i in range(m):
            for j in range(n):
                if rooms[i][j] == 0:
                    dq.append((i,j))
                    marked[i][j] = True
                elif rooms[i][j] == -1:
                    marked[i][j] = True
        # length用作判断dist变化
        length = len(dq)
        dist = 0  
        # 遍历队列,不断更新值 
        while dq:
            x,y = dq.popleft()
            rooms[x][y] = dist           
            for a,b in dist_group:
                # 只加入未被访问的点
                if (-1 < x + a < m and -1 < y + b < n) and not marked[x+a][y+b]:
                    dq.append((x+a,y+b))
                    marked[x+a][y+b] = True  
                    # 关于什么时候进行标记也需要注意,
                    # 假如在更改距离时才标记则队列中该点可能出现多次
                    # 又因为自己代码中门也会参与遍历,很难用判断排除第二次访问
            length -= 1
            if not length:
                dist += 1
                length = len(dq)                         

题目【752. 打开转盘锁】假设有一个带有四个圆形拨轮的转盘锁,每次转动一个转盘的一个数字,计算波动到解锁密码需要的最少次数,而且为增加难度题目来设置了一组“死亡数组”即不能出现的数字。
自己对于这道题目完全没有思路,官方题解见下:

class Solution(object):
    def openLock(self, deadends, target):
        def neighbors(node):
            for i in xrange(4):
                x = int(node[i])
                for d in (-1, 1):
                    y = (x + d) % 10
                    yield node[:i] + str(y) + node[i+1:]

        dead = set(deadends)
        queue = collections.deque([('0000', 0)])
        seen = {'0000'}
        while queue:
            node, depth = queue.popleft()
            if node == target: return depth
            if node in dead: continue
            for nei in neighbors(node):
                if nei not in seen:
                    seen.add(nei)
                    queue.append((nei, depth+1))
        return -1

从上面代码可以看出官方的思路便是直观的模仿拨动转盘的过程,设立队列存储之前的结果,然后遍历队列,然后将其拨动的可能结果再添加至队列中。中间要注意的是使用集合防止重复访问、判断是否在“死亡数组”中。

在这里插入图片描述
栈与队列类似,总是在堆栈的末尾添加一个新元素。但是,删除操作,将始终删除队列中相对于它的最后一个元素

栈和深度优先搜索(DFS)

与 BFS 类似,深度优先搜索(DFS)是用于 在树/图中遍历/搜索 的另一种重要算法。也可以在更抽象的场景中使用。
通常,我们使用递归实现 DFS。栈在递归中起着重要的作用。

官方给了深度优先搜索的两个模板,一个是使用系统提供的隐性栈,另一个是防止递归深度太深产生堆栈溢出而使用显性栈。

/*
 * Return true if there is a path from cur to target.
 */
boolean DFS(Node cur, Node target, Set<Node> visited) {
    return true if cur is target;
    for (next : each neighbor of cur) {
        if (next is not in visited) {
            add next to visted;
            return true if DFS(next, target, visited) == true;
        }
    }
    return false;
}
/*
 * Return true if there is a path from cur to target.
 */
boolean DFS(int root, int target) {
    Set<Node> visited;
    Stack<Node> s;
    add root to s;
    while (s is not empty) {
        Node cur = the top element in s;
        return true if cur is target;
        for (Node next : the neighbors of cur) {
            if (next is not in visited) {
                add next to s;
                add next to visited;
            }
        }
        remove cur from s;
    }
    return false;
}

这一部分官方的题目并不算难,主要是递归上要小心些,里面比较难的题目是【494. 目标和】,但是这道题目更多的是考察动态规划,先了解了解就好了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值