《labuladong的算法小超》A和《代码随想录》B阅读笔记(3)

本文详细介绍了链表的基本概念、删除和添加操作,以及性能分析,特别是与数组的对比。随后转向哈希表,讲解了哈希函数、碰撞解决方法和常见哈希结构的应用,包括数组、集合和映射。重点讨论了不同场景下选择适当哈希结构的方法。
摘要由CSDN通过智能技术生成

第17天-第18天 

开始链表

简单介绍下什么是链表,链表是一种通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)。

链接的入口节点称为链表的头结点也就是head。

如图所示:

链表1

链表的操作

删除节点

删除D节点,如图所示:

链表-删除节点

只要将C节点的next指针 指向E节点就可以了。

那有同学说了,D节点不是依然存留在内存里么?只不过是没有在这个链表里而已。

是这样的,所以在C++里最好是再手动释放这个D节点,释放这块内存。

其他语言例如Java、Python,就有自己的内存回收机制,就不用自己手动释放了。

添加节点

如图所示:

链表-添加节点

可以看出链表的增添和删除都是$O(1)$操作,也不会影响到其他节点。

但是要注意,要是删除第五个节点,需要从头节点查找到第四个节点通过next指针进行删除操作,查找的时间复杂度是$O(n)$。

性能分析

再把链表的特性和数组的特性进行一个对比,如图所示:

链表-链表与数据性能对比

数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。

链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。

203.移除链表元素

移除非头结点,直接改变指针的指向即可

203_链表删除元素1

移除头结点

203_链表删除元素4

203_链表删除元素5

移除头结点和移除其他节点的操作是不一样的,因为链表的其他节点都是通过前一个节点来移除当前节点,而头结点没有前一个节点。

所以头结点如何移除呢,其实只要将头结点向后移动一位就可以,这样就从链表中移除了一个头结点。

那么可不可以 以一种统一的逻辑来移除 链表的节点呢。

其实可以设置一个虚拟头结点,这样原链表的所有节点就都可以按照统一的方式进行移除了。

来看看如何设置一个虚拟头。依然还是在这个链表中,移除元素1。

203_链表删除元素6

这里来给链表添加一个虚拟头结点为新的头结点,此时要移除这个旧头结点元素1。

这样是不是就可以使用和移除链表其他节点的方式统一了呢?

来看一下,如何移除元素1 呢,还是熟悉的方式,然后从内存中删除元素1。

最后呢在题目中,return 头结点的时候,别忘了 return dummyNode->next;, 这才是新的头结点

所以这道题代码是

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeElements(self, head: ListNode, val: int) -> ListNode:
        dy_head = ListNode(0)
        dy_head.next = head
        cur = dy_head
        while(cur.next!=None):
            #print(dy_head)
            if(cur.next.val == val):
                cur.next = cur.next.next
            else:
                cur = cur.next
        return dy_head.next

注意定义的是dy_head链表,而操作的是cur链表

707.设计链表

那么我们就通过这道题好好的了解下链表这种数据结构吧

# 单链表创建 类定义 默认定义为ListNode
class Node:
    def __init__(self, val):
        self.val = val
        self.next = None


class MyLinkedList:
    def __init__(self):
        self._head = Node(0)  # 虚拟头部节点
        self._count = 0  # 添加的节点数

    def get(self, index: int) -> int:
        """
        Get the value of the index-th node in the linked list. If the index is invalid, return -1.
        获取index处的链表值,如果index索引不到值则返回-1
        """
        if 0 <= index < self._count:
            node = self._head
            for _ in range(index + 1):  
                node = node.next 
                # 其实就是一个node的遍历过程,这个过程由node的next指针完成
            return node.val
        else:
            return -1

    def addAtHead(self, val: int) -> None:
        """
        Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list.
        """
        self.addAtIndex(0, val)

    def addAtTail(self, val: int) -> None:
        """
        Append a node of value val to the last element of the linked list.
        """
        self.addAtIndex(self._count, val)

    def addAtIndex(self, index: int, val: int) -> None:
        """
        Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted.
        """
        if index < 0:
            index = 0
        elif index > self._count:
            return

        # 计数累加
        self._count += 1

        add_node = Node(val)
        prev_node, current_node = None, self._head
        for _ in range(index + 1):
            prev_node, current_node = current_node, current_node.next
        else:
            prev_node.next, add_node.next = add_node, current_node

    def deleteAtIndex(self, index: int) -> None:
        """
        Delete the index-th node in the linked list, if the index is valid.
        """
        if 0 <= index < self._count:
            # 计数-1
            self._count -= 1
            prev_node, current_node = None, self._head
            for _ in range(index + 1):
                prev_node, current_node = current_node, current_node.next
            else:
                prev_node.next, current_node.next = current_node.next, None


# 双链表
# 相对于单链表, Node新增了prev属性
class Node:
    
    def __init__(self, val):
        self.val = val
        self.prev = None
        self.next = None


class MyLinkedList:

    def __init__(self):
        self._head, self._tail = Node(0), Node(0)  # 虚拟节点
        self._head.next, self._tail.prev = self._tail, self._head
        self._count = 0  # 添加的节点数

    def _get_node(self, index: int) -> Node:
        # 当index小于_count//2时, 使用_head查找更快, 反之_tail更快
        if index >= self._count // 2:
            # 使用prev往前找
            node = self._tail
            for _ in range(self._count - index):
                node = node.prev
        else:
            # 使用next往后找
            node = self._head   
            for _ in range(index + 1):
                node = node.next
        return node

    def get(self, index: int) -> int:
        """
        Get the value of the index-th node in the linked list. If the index is invalid, return -1.
        """
        if 0 <= index < self._count:
            node = self._get_node(index)
            return node.val
        else:
            return -1

    def addAtHead(self, val: int) -> None:
        """
        Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list.
        """
        self._update(self._head, self._head.next, val)

    def addAtTail(self, val: int) -> None:
        """
        Append a node of value val to the last element of the linked list.
        """
        self._update(self._tail.prev, self._tail, val)

    def addAtIndex(self, index: int, val: int) -> None:
        """
        Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted.
        """
        if index < 0:
            index = 0
        elif index > self._count:
            return
        node = self._get_node(index)
        self._update(node.prev, node, val)

    def _update(self, prev: Node, next: Node, val: int) -> None:
        """
            更新节点
            :param prev: 相对于更新的前一个节点
            :param next: 相对于更新的后一个节点
            :param val:  要添加的节点值
        """
        # 计数累加
        self._count += 1
        node = Node(val)
        prev.next, next.prev = node, node
        node.prev, node.next = prev, next

    def deleteAtIndex(self, index: int) -> None:
        """
        Delete the index-th node in the linked list, if the index is valid.
        """
        if 0 <= index < self._count:
            node = self._get_node(index)
            # 计数-1
            self._count -= 1
            node.prev.next, node.next.prev = node.next, node.prev

第19天

我决定先过了二叉树和链表,因为他们不便于调试,现在是过年没太多时间,之后安静的时候再搞,况且面试的时候也不太考这两种数据结构。之后有时间就把链表和二叉树的题过一遍就行了。

哈希表相关

哈希表定义

哈希表是根据关键码的值而直接进行访问的数据结构。

一般哈希表都是用来快速判断一个元素是否出现集合里。

我们只需要初始化把这所学校里学生的名字都存在哈希表里,在查询的时候通过索引直接就可以知道这位同学在不在这所学校里了。

哈希函数

这个映射过程的完成就是由哈希函数进行的

哈希函数如下图所示,通过hashCode把名字转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值,这样就把学生名字映射为哈希表上的索引数字了。

哈希表2

如果hashCode得到的数值大于 哈希表的大小了,也就是大于tableSize了,怎么办呢?

此时为了保证映射出来的索引数值都落在哈希表上,我们会在再次对数值做一个取模的操作,就要我们就保证了学生姓名一定可以映射到哈希表上了。

此时问题又来了,哈希表我们刚刚说过,就是一个数组。

如果学生的数量大于哈希表的大小怎么办,此时就算哈希函数计算的再均匀,也避免不了会有几位学生的名字同时映射到哈希表 同一个索引下标的位置。

接下来哈希碰撞登场

哈希碰撞

如图所示,小李和小王都映射到了索引下标 1 的位置,这一现象叫做哈希碰撞

哈希表3

一般哈希碰撞有两种解决方法, 拉链法和线性探测法。

拉链法

刚刚小李和小王在索引1的位置发生了冲突,发生冲突的元素都被存储在链表中。 这样我们就可以通过索引找到小李和小王了

哈希表4

(数据规模是dataSize, 哈希表的大小为tableSize)

其实拉链法就是要选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。

线性探测法

使用线性探测法,一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。

例如冲突的位置,放了小李,那么就向下找一个空位放置小王的信息。所以要求tableSize一定要大于dataSize ,要不然哈希表上就没有空置的位置来存放 冲突的数据了。如图所示:

哈希表5

常见的三种哈希结构

当我们想使用哈希法来解决问题的时候,我们一般会选择如下三种数据结构。

数组、set (集合)和map(映射)  下面分别找一道题,看下他们的利用场合

242.有效的字母异位词

class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        record = [0] * 26
        for i in range(len(s)):
            # 并不需要记住字符的ASCII,只需要求出一个相对的数值就可以了
            record[ord(s[i])-ord('a')] +=1
        #print(record)
        for i in range(len(t)):
            record[ord(t[i])-ord('a')] -=1
        for i in range(26):
            if record[i] != 0:
                # 如果record数组中有元素不为0,说明s和t数组一定有所不同
                return False
                break
        return True

如果题目限制了数组的大小,那么使用数组完成哈希的题目是比较方便的。没有限制数值的大小,就无法使用数组来做哈希表了。

需要注意如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费

38.赎金信-数组哈希表另外举例

class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        # 首先建立一个基本数组
        arr = [0] * 26
        # 先遍历magazine,建立被查询的依据
        for x in magazine:
            arr[ord(x) - ord('a')] += 1
        # 遍历ransomNote数组
        # 如果查询结果显示不包含此元素,则返回False
        for x in ransomNote:
            if arr[ord(x) - ord('a')] == 0:
                return False
            # 如果查询结果显示包含此元素,则对应索引-1
            else:
                arr[ord(x) - ord('a')] -= 1
        # 知道遍历结束还没有出现False,则返回True
        return True

349.两个数组的交集

class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        return list(set(nums1) & set(nums2))    # 两个数组先变成集合,求交集后还原为数组

求交集部分,就需要注意去除重复元素,这时的set比较合适使用,因为set可以自动去重,并且有逻辑运算。 

那有同学可能问了,遇到哈希问题我直接都用set不就得了,用什么数组啊。

直接使用set 不仅占用空间比数组大,而且速度要比数组慢,set把数值映射到key上都要做hash计算的。

不要小瞧 这个耗时,在数据量大的情况,差距是很明显的。

202.快乐数-集合哈希表另外举例

class Solution:
    def isHappy(self, n: int) -> bool:
        def calculate_happy(num):
            # 此函数是用来计算每一位平方之后的和
            sum_ = 0
            # 从个位开始依次取,平方求和
            while num:
                sum_ += (num % 10) ** 2
                num = num // 10
            return sum_

        # 记录中间结果
        record = set()

        while True:
            n = calculate_happy(n)
            if n == 1:
                return True
            # 如果中间结果重复出现,说明陷入死循环了,该数不是快乐数
            if n in record:
                return False
            else:
                record.add(n)

这道题的关键是:只要某一次的计算结果在之前出现过,就被认定是进入了死循环,也就不是快乐数。 

1.两数之和

242这道题目是用数组作为哈希表来解决哈希问题,349这道题目是通过set作为哈希表来解决哈希问题。

本题呢,则要使用map,那么来看一下使用数组和set来做哈希法的局限:
(1)数组的大小是受限制的,且元素较少而哈希值过大时会造成内存空间的浪费
(2)set是一个集合,里面放的元素只能是一个key,而两数之和这道题不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。

python中使用map其实就是dict

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        records = dict()
        # 用枚举更方便,不需要通过索引再去取当前位置的值
        for idx, val in enumerate(nums):
            #print("idx", idx, " val:", val, " records", records)
            if target - val not in records:
                records[val] = idx
            else:
                return [records[target-val], idx]

map形势下要擅长使用enumerate函数生成索引,以及在for / while循环中查询索引

454.map哈希表另外举例

class Solution:
    def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
        # use a dict to store the elements in nums1 and nums2 and their sum
        hashmap = dict()
        for n1 in nums1:
            for n2 in nums2:
                if n1 + n2 in hashmap:
                    hashmap[n1+n2] += 1
                else:
                    hashmap[n1+n2] = 1
        
        # if the -(a+b) exists in nums3 and nums4, we shall add the count
        count = 0
        for n3 in nums3:
            for n4 in nums4:
                key = - n3 - n4
                if key in hashmap:
                    count += hashmap[key]
        return count

这道题啊,其实和两数之和极其类似,解法倒是不难。

主要突出map的索引搜索的方便。 

第20天

哈希表总结

一般来说哈希表都是用来快速判断一个元素是否会出现在集合里

对于哈希表,要知道哈希函数和哈希碰撞在哈希表中的作用:哈希函数是吧传入的key映射到符号表的索引上。哈希碰撞处理由多个key映射到相同的索引上时的情景,处理碰撞的普遍方式是拉链法和线性探测法。

接下来是三种常见的哈希结构:数组、集合、映射,需要在不同场合中区分使用。

数组作为哈希表

在242.有效的字母异位词中,提到了数组就是简单的哈希表,但数组的大小是受限制的。

这道题只包含小写字母,所以使用数组来做哈希做合适不过了

在383.赎金信中同样只要求有小写字母,同样的道理,也使用数组来完成。

使用数组来完成的都可以使用map来完成哈希表的构建,但使用map的空间消耗要大些,因为map要维护红黑树或者符号表,而且还要做哈希函数的运算,所以使用数组更加的简单直接有效。

set作为哈希表

在349.两个数组的交集这道题中就不能使用数组了,需要使用到set。因为这道题没有限制数值的大小。202.快乐数这道题中使用是因为需要查询很多次哈希表,而且要储存很多的结果值,这种大量的储存用set比较好

set的使用主要考虑以下三点:
数组大小不受限制的情况;数组空间够大,但哈希值较少,特别分散、跨度大,使用数组就造成空间的极大浪费;有交并集运算情况。

map作为哈希表

来说一说:使用数组和set来做哈希法的局限。
数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。

map是一种<key, value>的结构,本题可以用key保存数值,用value在保存数值所在的下标。所以使用map最为合适。

第21天

字符串着重掌握:定义、双指针法、翻转系列和KMP的实现

344.反转字符串

反转字符串和反转链表的不同根本上是因储存连续性的不同导致的(字符串是连续的、链表是不连续的)

对于字符串的反转,一般定义两个指针,一个从字符串的前面,一个从字符串的后面,两个指针同时向中间移动,并交换元素。

class Solution:
    def reverseString(self, s: List[str]) -> None:
        """
        Do not return anything, modify s in-place instead.
        """
        left, right = 0, len(s) - 1
        while left < right:
            s[left], s[right] = s[right], s[left]
            left += 1
            right -= 1

541.反转字符串II

当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章。

class Solution:
    def reverseStr(self, s: str, k: int) -> str:
        """
        1. 使用range(start, end, step)来确定需要调换的初始位置
        2. 对于字符串s = 'abc',如果使用s[0:999] ===> 'abc'。字符串末尾如果超过最大长度,则会返回至字符串最后一个值,这个特性可以避免一些边界条件的处理。
        3. 用切片整体替换,而不是一个个替换.
        """
        def reverse_substring(text):
            left, right = 0, len(text) - 1
            while left < right:
                text[left], text[right] = text[right], text[left]
                left += 1
                right -= 1
            return text
        
        res = list(s)

        for cur in range(0, len(s), 2 * k):
            res[cur: cur + k] = reverse_substring(res[cur: cur + k])
        
        return ''.join(res)

OF05.替换空格

为什么要从后向前填充,从前向后填充不行么?从前向后填充就是$O(n^2)$的算法了,因为每次添加元素都要将添加元素之后的所有元素向后移动。

其实很多数组填充类的问题,都可以先预先给数组扩容带填充后的大小,然后在从后向前进行操作。

这么做有两个好处:1.不用申请新数组。2.从后向前填充元素,避免了从前先后填充元素要来的 每次添加元素都要将添加元素之后的所有元素向后移动。

class Solution:
    def replaceSpace(self, s: str) -> str:
        counter = s.count(' ')
        res = list(s)
        # 每次遇到一个空格就多扩展两个格子,1+2=3个位置存储'%20'
        res.extend([' '] * counter *2)
        # 两个指针分别指向原始字符串的末尾和扩展后的末尾
        left, right = len(s)-1, len(res)-1

        while left >=0:
            if res[left] != ' ':
                res[right] = res[left]
                right -= 1
            else:
                # [right-2, right),左闭右开
                res[right-2:right+1] = '%20'
                right -=3
            left -= 1
        return ''.join(res)

第22天

回忆一下字符串的三个重点,双指针法、反转字符串和KMP

其中的双指针法在之后我们会专门有一个章节仔细讨论。反转字符串这部分多是发现或者学着记录一些字符串遍历的规律,之后将这个规律以代码的形式模拟出来即可。而KMP部分则较为难理解一些,今天主要理解一下这部分。

KMP算法的实现

KMP算法:就是为了解决字符串的匹配问题而诞生的。也可以说是在文本串中寻找模式串的过程。

举例,如果文本串是aabaabaaf,模式串是aabaaf,那么暴力的解法是两层for循环,而KMP则可以从文本串中的第一个b开始下一次的匹配过程。这是由前缀表完成的。

前缀:包含首字母而不包含尾字母的所有子串
后缀:包含尾字母而不包含首字母的所有子串

最长相等前后缀,比如a的最长相等前后缀长度是0、aa为1、aab为0、aaba为1、aabaa为2、aabaaf为0。

前缀表:将上面的计数拿出来:010120就是所谓的前缀表

匹配过程:写过KMP的同学,一定都写过next数组,那么这个next数组究竟是个啥呢?next数组就是一个前缀表(prefix table)。前缀表有什么作用呢?

前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。

为了清楚的了解前缀表的来历,我们来举一个例子:要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。

要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。

如动画所示:

动画里,特意把子串aa 标记上了,这是有原因的,大家先注意一下,后面还会说道。

可以看出,文本串中第六个字符b 和 模式串的第六个字符f,不匹配了。如果暴力匹配,会发现不匹配,此时就要从头匹配了。

但如果使用前缀表,就不会从头匹配,而是从上次已经匹配的内容开始匹配,找到了模式串中第三个字符b继续开始匹配。

此时就要问了前缀表是如何记录的呢?

首先要知道前缀表的任务是当前位置匹配失败,找到之前已经匹配上的位置,再重新匹配,此也意味着在某个字符失配时,前缀表会告诉你下一步匹配中,模式串应该跳到哪个位置。

在哪里?就在前缀表中最长的前后缀长度那里重新匹配。这个最长长度意味着什么?在这个最长相等前后缀后面开始不匹配了,我们就要从与之相等的前缀的后面开始进行匹配。

需要注意:索引是从0开始的

KMP算法中经常会涉及到next数组,next数组是在我们遇到冲突的时候,用于告诉我们需要回退到哪里重新开始搜索。在使用的时候经常会对next数组做一个右移的操作(或者称为减1的操作)

匹配细节代码随想录

具体代码

初始化:双指针   i指针指向后缀末尾位置,j指向前缀末尾位置。j初始化为0,i和j要做对比,所以i初始化为1。

处理前后缀不相同的情况:s[i] != s[j],j应该向前一位所对应的数组中的值回退

处理前后缀相同的情况:s[i] == s[j],j++

更新next数组的值

第23-24天

232.用栈实现队列

栈和队列基础知识。栈:先进后出。 队列:先进先出

这道题是道模拟题,需要模拟出题目中的整个过程,而不涉及到算法。考研的是对栈和队列的理解程度。

class MyQueue:
    def __init__(self):
        """
        in主要负责push,out主要负责pop
        """
        self.stack_in = []
        self.stack_out = []
        
    def push(self, x: int) -> None:
        """
        有新元素进来,就往in里面push
        """
        self.stack_in.append(x)


    def pop(self) -> int:
        """
        Removes the element from in front of queue and returns that element.
        """
        if self.empty():
            return None
        
        if self.stack_out:
            return self.stack_out.pop()
        else:
            for i in range(len(self.stack_in)):
                self.stack_out.append(self.stack_in.pop())
            return self.stack_out.pop()


    def peek(self) -> int:
        """
        Get the front element.
        """
        ans = self.pop()
        self.stack_out.append(ans)
        return ans


    def empty(self) -> bool:
        """
        只要in或者out有元素,说明队列不为空
        """
        return not (self.stack_in or self.stack_out)


# Your MyQueue object will be instantiated and called as such:
# obj = MyQueue()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.peek()
# param_4 = obj.empty()

252.用队列实现栈

class MyStack:

    def __init__(self):
        """
        Python普通的Queue或SimpleQueue没有类似于peek的功能,也无法用索引访问,在实现top的时候较为困难。
        用list可以,但是在使用pop(0)的时候时间复杂度为O(n)。因此这里使用双向队列,我们保证只执行popleft()和append(),因为deque可以用索引访问,可以实现和peek相似的功能
        in - 存所有数据
        out - 仅在pop的时候会用到
        """
        self.queue_in  = deque()
        self.queue_out = deque()

    def push(self, x: int) -> None:
        '''
        直接append即可
        '''
        self.queue_in.append(x)

    def pop(self) -> int:
        """
        1. 首先确认不空
        2. 因为队列的特殊性,FIFO,所以我们只有在pop()的时候才会使用queue_out
        3. 先把queue_in中的所有元素(除了最后一个),依次出列放进queue_out
        4. 交换in和out,此时out里只有一个元素
        5. 把out中的pop出来,即是原队列的最后一个
        
        tip:这不能像栈实现队列一样,因为另一个queue也是FIFO,如果执行pop()它不能像
        stack一样从另一个pop(),所以干脆in只用来存数据,pop()的时候两个进行交换
        """
        if self.empty():
            return None

        for i in range(len(self.queue_in) - 1):
            self.queue_out.append(self.queue_in.popleft())
        
        self.queue_in, self.queue_out = self.queue_out, self.queue_in    # 交换in和out,这也是为啥in只用来存
        return self.queue_out.popleft()

    def top(self) -> int:
        """
        1. 首先确认不空
        2. 我们仅有in会存放数据,所以返回第一个即可
        """
        if self.empty():
            return None
        
        return self.queue_in[-1]


    def empty(self) -> bool:
        """
        因为只有in存了数据,只要判断in是不是有数即可
        """
        return len(self.queue_in) == 0


# Your MyStack object will be instantiated and called as such:
# obj = MyStack()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.top()
# param_4 = obj.empty()

20.有效的括号

括号匹配是使用栈解决的经典问题

写题之前认真思考:不匹配的情况应有三种:左方向的括号多余、右方向的口号多余、括号没有多余,但括号的类型没有匹配上。

第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false

第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false

第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false

那么什么时候说明左括号和右括号全都匹配了呢,就是字符串遍历完之后,栈是空的,就说明全都匹配了。

# 方法一,仅使用栈,更省空间
class Solution:
    def isValid(self, s: str) -> bool:
        stack = []
        
        for item in s:
            if item == '(':
                stack.append(')')
            elif item == '[':
                stack.append(']')
            elif item == '{':
                stack.append('}')
            elif not stack or stack[-1] != item:
                return False
            else:
                stack.pop()
        
        return True if not stack else False
# 方法二,使用字典
class Solution:
    def isValid(self, s: str) -> bool:
        stack = []
        mapping = {
            '(': ')',
            '[': ']',
            '{': '}'
        }
        for item in s:
            if item in mapping.keys():
                stack.append(mapping[item])
            elif not stack or stack[-1] != item: 
                return False
            else: 
                stack.pop()
        return True if not stack else False

大体思路就是:将左括号记录,用栈和字典都可以,然后遇到右括号时,判断如果不匹配(当前遇到的右括号与当前栈的最后一个左括号不匹配)就直接返回False,如果匹配则最后一个与元素出栈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白 AI 日记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值