python算法和数据结构刷题[2]:链表、队列、栈
链表
链表的节点定义:
class Node():def __init__(self,item,next=None):self.item=itemself.next=None
删除节点:
删除节点前的节点的next指针指向删除节点的后一个节点
添加节点:
单链表
class Node():"""单链表的结点"""def __init__(self, item):# item存放数据元素self.item = item# next是下一个节点的标识self.next = None
class SingleLinkList():"""单链表"""def __init__(self):self._head = Nonedef is_empty(self):"""判断链表是否为空"""return self._head is Nonedef length(self):"""链表长度"""# 初始指针指向headcur = self._headcount = 0# 指针指向None 表示到达尾部while cur is not None:count += 1# 指针下移cur = cur.nextreturn countdef items(self):"""遍历链表"""# 获取head指针cur = self._head# 循环遍历while cur is not None:# 返回生成器yield cur.item# 指针下移cur = cur.nextdef add(self, item):"""向链表头部添加元素"""node = Node(item)# 新结点指针指向原头部结点node.next = self._head# 头部结点指针修改为新结点self._head = nodedef append(self, item):"""尾部添加元素"""node = Node(item)# 先判断是否为空链表if self.is_empty():# 空链表,_head 指向新结点self._head = nodeelse:# 不是空链表,则找到尾部,将尾部next结点指向新结点cur = self._headwhile cur.next is not None:cur = cur.nextcur.next = nodedef insert(self, index, item):"""指定位置插入元素"""# 指定位置在第一个元素之前,在头部插入if index <= 0:self.add(item)# 指定位置超过尾部,在尾部插入elif index > (self.length() - 1):self.append(item)else:# 创建元素结点node = Node(item)cur = self._head# 循环到需要插入的位置for i in range(index - 1):cur = cur.nextnode.next = cur.nextcur.next = nodedef remove(self, item):"""删除节点"""cur = self._headpre = Nonewhile cur is not None:# 找到指定元素if cur.item == item:# 如果第一个就是删除的节点if not pre:# 将头指针指向头节点的后一个节点self._head = cur.nextelse:# 将删除位置前一个节点的next指向删除位置的后一个节点pre.next = cur.nextreturn Trueelse:# 继续按链表后移节点pre = curcur = cur.nextdef find(self, item):"""查找元素是否存在"""return item in self.items()
双链表:每一个节点有两个指针域,一个指向下一个节点,一个指向上一个节点。
循环链表:链表首尾相连。
链表中的节点在内存中不是连续分布的 ,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。
class Node(object):"""单链表的结点"""def __init__(self, item):# item存放数据元素self.item = item# next是下一个节点的标识self.next = None
class SingleCycleLinkList(object):def __init__(self):self._head = Nonedef is_empty(self):"""判断链表是否为空"""return self._head is Nonedef length(self):"""链表长度"""# 链表为空if self.is_empty():return 0# 链表不为空count = 1cur = self._headwhile cur.next != self._head:count += 1# 指针下移cur = cur.nextreturn countdef items(self):""" 遍历链表 """# 链表为空if self.is_empty():return# 链表不为空cur = self._headwhile cur.next != self._head:yield cur.itemcur = cur.nextyield cur.itemdef add(self, item):""" 头部添加结点"""node = Node(item)if self.is_empty(): # 为空self._head = nodenode.next = self._headelse:# 添加结点指向headnode.next = self._headcur = self._head# 移动结点,将末尾的结点指向nodewhile cur.next != self._head:cur = cur.nextcur.next = node# 修改 head 指向新结点self._head = nodedef append(self, item):"""尾部添加结点"""node = Node(item)if self.is_empty(): # 为空self._head = nodenode.next = self._headelse:# 寻找尾部cur = self._headwhile cur.next != self._head:cur = cur.next# 尾部指针指向新结点cur.next = node# 新结点指针指向headnode.next = self._headdef insert(self, index, item):""" 指定位置添加结点"""if index <= 0: # 指定位置小于等于0,头部添加self.add(item)# 指定位置大于链表长度,尾部添加elif index > self.length() - 1:self.append(item)else:node = Node(item)cur = self._head# 移动到添加结点位置for i in range(index - 1):cur = cur.next# 新结点指针指向旧结点node.next = cur.next# 旧结点指针 指向 新结点cur.next = nodedef remove(self, item):""" 删除一个结点 """if self.is_empty():returncur = self._headpre = Node# 第一个元素为需要删除的元素if cur.item == item:# 链表不止一个元素if cur.next != self._head:while cur.next != self._head:cur = cur.next# 尾结点指向 头部结点的下一结点cur.next = self._head.next# 调整头部结点self._head = self._head.nextelse:# 只有一个元素self._head = Noneelse:# 不是第一个元素pre = self._headwhile cur.next != self._head:if cur.item == item:# 删除pre.next = cur.nextreturn Trueelse:pre = cur # 记录前一个指针cur = cur.next # 调整指针位置# 当删除元素在末尾if cur.item == item:pre.next = self._headreturn Truedef find(self, item):""" 查找元素是否存在"""return item in self.items()
160. 相交链表 - 力扣(LeetCode)
哈希表(集合)
class Solution:def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:s = set()p, q = headA, headBwhile p:s.add(p)p = p.nextwhile q:if q in s:return qq = q.nextreturn None
时间复杂度:O(n)
空间复杂度:O(n)
双指针
短的链表会先指向另外一个链表的头节点使得步差消除
class ListNode:def __init__(self, x):self.val = xself.next = Noneclass Solution:def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:pA, pB = headA, headBwhile pA != pB:pA = headB if not pA else pA.nextpB = headA if not pB else pB.nextreturn pA
时间复杂度:O(N+M)
空间复杂度:O(1)
206. 反转链表 - 力扣(LeetCode)
反转链表其实是一个反转指针方向的过程
迭代
迭代需要三个指针,pre,cur,nxt,分别按顺序指向三个节点
三个指针的初始化:pre指向空节点,cur指向头结点head,nxt指向head.next
因为head.next可能不存在,nxt在循环中定义,这样如果head为空就不会进入循环
迭代过程
nxt指向cur.next
cur.next指向pre
pre移动到cur位置
cur移动到nxt位置
开始新迭代
当cur为空时,返回pre
def reverseList(self, head: ListNode) -> ListNode:prev, curr = None, headwhile curr is not None:next = curr.nextcurr.next = prevprev = currcurr = nextreturn prev
- 时间复杂度:O(n)
- 空间复杂度:O(1)(三个变量都是必要的,并且在整个函数执行过程中它们的数量是固定的,不会随着输入链表的大小而改变)
递归
每一层递归,该层递归中的head会让下一个节点指向自己,head.next.next = head;然后head自己指向空。以此达到反转的目的。
class Solution:def reverseList(self, head: ListNode) -> ListNode:if not head or not head.next:return headnewHead = self.reverseList(head.next)head.next.next = headhead.next = Nonereturn newHead
- 时间复杂度:O(n)
- 空间复杂度:O(n)
234. 回文链表 - 力扣(LeetCode)
翻转链表,再和原链表各元素按顺序对比;
将链表各节点值加入数组,再利用左右指针向中间同时滑动,判断左右指针位置处的值是否相等;
递归法,后序遍历链表,判断倒序的节点值和正序的节点值是否相同
快慢指针先找到链表的中间位置,将后半段链表翻转,再按顺序对比各元素是否相同,最终恢复原链表(再进行一次翻转)
这几种方法的时间复杂度都是O(N),前三种方法的空间复杂度是O(N),第四种方法的空间复杂度是O(1)
递归法
链表是一种兼顾迭代和递归的数据结构,因此链表可以进行先序遍历和后序遍历(树就是多叉链表)。后序遍历链表,可以使链表隐式的倒序访问节点,访问过程中,维护一个正序的指针即可。
class Solution:def isPalindrome(self, head: ListNode) -> bool:# 初始化左指针,指向链表头部self.left = head# 递归函数,用于检查链表是否为回文def check_palindrome(right):# 如果右指针为空,说明已经到达链表末尾,返回Trueif not right:return True# 递归检查下一个节点,如果发现不是回文,则直接返回Falseres = check_palindrome(right.next)# 比较当前左指针和右指针的值,如果不相等,则不是回文if self.left.val != right.val:return False# 将左指针向右移动一位self.left = self.left.next# 返回子问题的结果return res# 调用递归函数检查整个链表是否为回文return check_palindrome(head)
在第一次回溯的时候 self.left
指向第一个节点,right
指向第五个节点
双指针快慢指针
先利用快慢指针,找到链表的后半段,将链表的后半段翻转,再按顺序对比前半段和后半段的值是否一致,最终恢复原链表(后半段再翻转一次)即可。
注意:链表长度有奇数和偶数两种情况,对于奇数,如1->2->3->2->1,此时快指针fast会停在最后的1处,满指针slow停在中间的3处,这时需要对slow.next的链表进行翻转。
class ListNode:def __init__(self, val=0, next=None):self.val = valself.next = nextclass Solution:def isPalindrome(self, head: ListNode) -> bool:# 辅助函数,用于反转链表的一部分def reverse(node):prev = Nonecurrent = nodewhile current:next_node = current.nextcurrent.next = prevprev = currentcurrent = next_nodereturn prev# 辅助函数,用于找到链表的中间节点def find_middle(node):slow = fast = nodewhile fast and fast.next:slow = slow.nextfast = fast.next.nextreturn slow# 如果链表为空或只有一个节点,则直接返回Trueif not head or not head.next:return True# 使用快慢指针找到链表的中间节点middle = find_middle(head)# 反转链表的后半部分second_half = reverse(middle)# 比较前半部分和反转后的后半部分first_half = headis_palindrome = Truewhile is_palindrome and second_half:if first_half.val != second_half.val:is_palindrome = Falsefirst_half = first_half.nextsecond_half = second_half.next# 可选:将链表恢复到原始状态reverse(second_half)return is_palindrome# 使用示例
# solution = Solution()
# head = ListNode(1, ListNode(2, ListNode(2, ListNode(1))))
# print(solution.isPalindrome(head)) # 输出: True
141. 环形链表 - 力扣(LeetCode)
哈希表
class Solution:def hasCycle(self, head: ListNode) -> bool:seen = set()while head:if head in seen:return Trueseen.add(head)head = head.nextreturn False
时间复杂度:O(N),其中 N 是链表中的节点数。最坏情况下我们需要遍历每个节点一次。
空间复杂度:O(N),其中 N 是链表中的节点数。主要为哈希表的开销,最坏情况下我们需要将每个节点插入到哈希表中一次。
双指针:快慢指针
定义两个指针,一快一慢。慢指针每次只移动一步,而快指针每次移动两步。初始时,慢指针在位置 head,而快指针在位置 head.next。这样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表。
class Solution:def hasCycle(self, head: ListNode) -> bool:if not head or not head.next:return Falseslow = headfast = head.nextwhile slow != fast:if not fast or not fast.next:return Falseslow = slow.nextfast = fast.next.nextreturn True
142. 环形链表 II - 力扣(LeetCode)
class ListNode:def __init__(self, val=0, next=None):self.val = valself.next = nextclass Solution:def detectCycle(self, head: ListNode) -> ListNode:if not head or not head.next:return Noneslow = headfast = head.nextwhile slow != fast:if not fast or not fast.next:return Noneslow = slow.nextfast = fast.next.next# 当slow和fast相遇时,使用一个新指针ptr从头开始,与slow以相同速度移动ptr = headwhile ptr != slow:ptr = ptr.nextslow = slow.next# ptr和slow相遇的节点即为环的起始节点return ptr
21. 合并两个有序链表 - 力扣(LeetCode)
递归
class Solution:def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:if l1 is None:return l2elif l2 is None:return l1elif l1.val < l2.val:l1.next = self.mergeTwoLists(l1.next, l2)return l1else:l2.next = self.mergeTwoLists(l1, l2.next)return l2
迭代
class ListNode:def __init__(self, val=0, next=None):self.val = valself.next = nextclass Solution:def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:# 创建一个哨兵节点,以简化合并操作prehead = ListNode(-1)# 初始化prev指针,它将指向合并后链表的最后一个节点prev = prehead# 当两个链表都不为空时,迭代比较它们while l1 and l2:if l1.val < l2.val:# 如果l1的当前节点值小于l2的当前节点值,将l1的节点链接到prev的后面prev.next = l1l1 = l1.next # 移动l1指针到下一个节点else:# 如果l2的当前节点值小于或等于l1的当前节点值,将l2的节点链接到prev的后面prev.next = l2l2 = l2.next # 移动l2指针到下一个节点prev = prev.next # 移动prev指针到合并链表的最后一个节点# 如果l1链表还有剩余的节点,将它们链接到合并链表的末尾if l1:prev.next = l1# 如果l2链表还有剩余的节点,将它们链接到合并链表的末尾if l2:prev.next = l2# 返回哨兵节点的下一个节点,即合并后链表的头节点return prehead.next
2. 两数相加 - 力扣(LeetCode)
递归
def __init__(self, val=0, next=None):self.val = valself.next = nextclass Solution:def addTwoNumbers(self, l1: ListNode, l2: ListNode, carry: int = 0) -> ListNode:# 计算当前位的和以及进位val = l1.val + l2.val + carrycarry = val // 10# 创建当前位的节点current = ListNode(val % 10)# 如果两个链表的下一位都不为空,递归计算下一位的和if l1.next or l2.next:if not l1.next:l1.next = ListNode(0) # 如果l1短,补0if not l2.next:l2.next = ListNode(0) # 如果l2短,补0current.next = self.addTwoNumbers(l1.next, l2.next, carry)# 如果进位不为0,且两个链表的下一位都为空,则添加一个新节点elif carry:current.next = ListNode(carry)return current
19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)
循环迭代
class Solution:def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:dummy = ListNode(0)dummy.next = head #step1: 获取链表长度cur, length = head, 0 while cur:length += 1cur = cur.next #step2: 找到倒数第N个节点的前面一个节点cur = dummyfor _ in range(length - n):cur = cur.next#step3: 删除节点,并重新连接cur.next = cur.next.nextreturn dummy.next
双指针
class Solution:def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:dummy = ListNode(0)dummy.next = head #step1: 快指针先走n步slow, fast = dummy, dummyfor _ in range(n):fast = fast.next #step2: 快慢指针同时走,直到fast指针到达尾部节点,此时slow到达倒数第N个节点的前一个节点while fast and fast.next:slow, fast = slow.next, fast.next #step3: 删除节点,并重新连接slow.next = slow.next.next return dummy.next
递归迭代
class ListNode:def __init__(self, val=0, next=None):self.val = valself.next = nextclass Solution:def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:if not head:self.count = 0return headhead.next = self.removeNthFromEnd(head.next, n)self.count += 1if self.count == n:return head.nextelse:return head
24. 两两交换链表中的节点 - 力扣(LeetCode)
递归和迭代之后再看
class ListNode:def __init__(self, val=0, next=None):self.val = valself.next = nextclass Solution:def swapPairs(self, head: ListNode) -> ListNode:# 如果链表为空或者只有一个节点,则无法交换,直接返回 headif not head or not head.next:return head# 保存当前 head 的下一个节点,这个节点将成为新的头节点newHead = head.next# 递归地交换剩下的节点,并将返回的节点作为当前 head 的下一个节点# 例如,在第一次调用时,这里将交换 C 和 D,并将 D 作为 A 的下一个节点head.next = self.swapPairs(newHead.next)# 将新的头节点(原 head 的下一个节点)的下一个节点设置为 head# 这一步完成了两个节点的交换newHead.next = head# 返回新的头节点,这是交换后的第二个节点return newHead
node1 = ListNode(1)
node2 = ListNode(2)
node3 = ListNode(3)
node4 = ListNode(4)
node1.next = node2
node2.next = node3
node3.next = node4# 实例化 Solution 并调用 swapPairs
solution = Solution()
new_head = solution.swapPairs(node1)# 打印交换后的链表
current = new_head
while current:print(current.val, end=" -> ")current = current.next
148. 排序链表 - 力扣(LeetCode)
学排序的时候看
138. 随机链表的复制 - 力扣(LeetCode)
146. LRU 缓存 - 力扣(LeetCode)
O(1) 时间复杂度内完成
在 Python
语言中,有一种结合了哈希表与双向链表的数据结构 OrderedDict
,只需要短短的几行代码就可以完成本题。OrderedDict
保持元素插入顺序的特性,允许我们将最近使用的元素移动到字典的末尾
from collections import OrderedDictclass LRUCache(OrderedDict):def __init__(self, capacity: int):super().__init__()self.capacity = capacity # 初始化缓存容量def get(self, key: int) -> int:# 如果键不存在于缓存中,返回-1if key not in self:return -1# 将访问的键移动到字典的末尾,表示最近使用self.move_to_end(key)# 返回键对应的值return self[key]def put(self, key: int, value: int) -> None:# 如果键已存在,则移动到末尾if key in self:self.move_to_end(key)# 更新或添加键值对self[key] = value# 如果当前大小超过容量,则删除最久未使用的元素if len(self) > self.capacity:self.popitem(last=False) # last=False 表示删除第一个元素,即最久未使用的元素# 示例使用
lru = LRUCache(2)
lru.put(1, 1)
lru.put(2, 2)
print(lru.get(1)) # 输出 1
lru.put(3, 3) # 这将使得键 2 被移除
print(lru.get(2)) # 输出 -1 (因为键 2 已经被移除)
lru.put(4, 4) # 这将使得键 1 被移除
print(lru.get(1)) # 输出 -1 (因为键 1 已经被移除)
print(lru.get(3)) # 输出 3
print(lru.get(4)) # 输出 4
哈希表和双向链表
class DLinkedNode:def __init__(self, key=0, value=0):self.key = keyself.value = valueself.prev = Noneself.next = Noneclass LRUCache:def __init__(self, capacity: int):self.cache = dict() # 哈希表,用于存储键和对应双向链表节点的映射# 使用伪头部和伪尾部节点来简化边界条件处理self.head = DLinkedNode()self.tail = DLinkedNode()self.head.next = self.tailself.tail.prev = self.headself.capacity = capacity # 缓存容量self.size = 0 # 当前缓存中的元素数量def get(self, key: int) -> int:# 如果键不在缓存中,返回-1if key not in self.cache:return -1# 定位到键对应的节点,并将其移动到头部node = self.cache[key]self.moveToHead(node)return node.value # 返回节点的值def put(self, key: int, value: int) -> None:# 如果键不在缓存中,创建新节点并添加到头部if key not in self.cache:node = DLinkedNode(key, value)self.cache[key] = nodeself.addToHead(node)self.size += 1# 如果超出容量,移除尾部节点if self.size > self.capacity:removed = self.removeTail()self.cache.pop(removed.key)self.size -= 1else:# 如果键在缓存中,更新值并将其移动到头部node = self.cache[key]node.value = valueself.moveToHead(node)def addToHead(self, node):# 将节点添加到双向链表的头部node.prev = self.headnode.next = self.head.nextself.head.next.prev = nodeself.head.next = nodedef removeNode(self, node):# 从双向链表中移除节点node.prev.next = node.nextnode.next.prev = node.prevdef moveToHead(self, node):# 移除节点后将其添加到头部self.removeNode(node)self.addToHead(node)def removeTail(self):# 移除尾部节点并返回node = self.tail.prevself.removeNode(node)return node
23. 合并 K 个升序链表 - 力扣(LeetCode)
优先队列
25. K 个一组翻转链表 - 力扣(LeetCode)
队列和栈
栈
class Stack( ):# 初始化栈为空列表def __init__(self):self.items = []# 判断栈是否为空,返回布尔值def is_empty(self):return self.items == []# 返回栈顶元素def peek(self):return self.items[len(self.items) - 1]# 返回栈的大小def size(self):return len(self.items)# 把新的元素堆进栈里面(程序员喜欢把这个过程叫做压栈,入栈,进栈……)def push(self, item):self.items.append(item)# 把栈顶元素丢出去(程序员喜欢把这个过程叫做出栈……)def pop(self, item):return self.items.pop()if __name__=="__main__":# 初始化一个栈对象my_stack = Stack()# 把'h'丢进栈里my_stack.push('h')# 把'a'丢进栈里my_stack.push('a')my_stack.push('c')my_stack.push('d')my_stack.push('e')# 看一下栈的大小(有几个元素)print(my_stack.size())# 打印栈顶元素print(my_stack.peek())# 把栈顶元素丢出去,并打印出来#print(my_stack.pop())# 再看一下栈顶元素是谁print(my_stack.peek())# 这个时候栈的大小是多少?print(my_stack.size())# 再丢一个栈顶元素#print(my_stack.pop())# 看一下栈的大小print(my_stack.size)# 栈是不是空了?print(my_stack.is_empty())
队列
from queue import Queue #先进先出队列 #LILO队列
q = Queue() #创建队列对象
q.put(0) #在队列尾部插入元素
q.put(1)
q.put(2)
print('LILO队列',q.queue) #查看队列中的所有元素
print(q.get()) #返回并删除队列头部元素
print(q.queue)from queue import LifoQueue #LIFO队列 后进先出(Last In, First Out, LIFO)的队列
lifoQueue = LifoQueue()
lifoQueue.put(1)
lifoQueue.put(2)
lifoQueue.put(3)
print('LIFO队列',lifoQueue.queue)
lifoQueue.get() #返回并删除队列尾部元素
lifoQueue.get()
print(lifoQueue.queue)from queue import PriorityQueue #优先队列
#素的出队顺序不是按照它们的入队顺序,而是按照它们的优先级来决定的。优先级最高的元素会最先被移除。
priorityQueue = PriorityQueue() #创建优先队列对象
priorityQueue.put(3) #插入元素
priorityQueue.put(78) #插入元素
priorityQueue.put(100) #插入元素
print(priorityQueue.queue) #查看优先级队列中的所有元素
priorityQueue.put(1) #插入元素
priorityQueue.put(2) #插入元素
print('优先级队列:',priorityQueue.queue) #查看优先级队列中的所有元素
priorityQueue.get() #返回并删除优先级最低的元素
print('删除后剩余元素',priorityQueue.queue)
priorityQueue.get() #返回并删除优先级最低的元素
print('删除后剩余元素',priorityQueue.queue) #删除后剩余元素
priorityQueue.get() #返回并删除优先级最低的元素
print('删除后剩余元素',priorityQueue.queue) #删除后剩余元素
priorityQueue.get() #返回并删除优先级最低的元素
print('删除后剩余元素',priorityQueue.queue) #删除后剩余元素
priorityQueue.get() #返回并删除优先级最低的元素
print('全部被删除后:',priorityQueue.queue) #查看优先级队列中的所有元素from collections import deque #双端队列
dequeQueue = deque(['Eric','John','Smith'])
print(dequeQueue)
dequeQueue.append('Tom') #在右侧插入新元素
dequeQueue.appendleft('Terry') #在左侧插入新元素
print(dequeQueue)
dequeQueue.rotate(2) #循环右移2次
print('循环右移2次后的队列',dequeQueue)
dequeQueue.popleft() #返回并删除队列最左端元素
print('删除最左端元素后的队列:',dequeQueue)
dequeQueue.pop() #返回并删除队列最右端元素
print('删除最右端元素后的队列:',dequeQueue)
20. 有效的括号 - 力扣(LeetCode)
栈 哈希表
当遇到匹配的最小括号对时,我们将这对括号从栈中删除(即出栈),如果最后栈为空,那么它是有效的括号,反之不是。
- 时间复杂度:O(N)。遍历了一遍字符串。
- 空间复杂度:O(N)。最坏情况下,假如输入是
(((((((
,栈的大小将是输入字符串的长度。
(]
class Solution:def isValid(self, s: str) -> bool:# 使用字典来映射匹配的括号bracket_map = {')': '(', ']': '[', '}': '{'}# 使用列表作为栈来存储未匹配的左括号stack = []# 遍历字符串中的每个字符for char in s:# 如果字符是右括号if char in bracket_map:# 如果栈不为空且栈顶元素与当前右括号匹配if stack and stack[-1] == bracket_map[char]:stack.pop() # 弹出栈顶元素,表示匹配成功else:return False # 如果不匹配或栈为空,则返回Falseelse:# 如果字符是左括号,将其推入栈中stack.append(char)# 如果栈为空,则所有括号都匹配成功,返回Truereturn not stack
155. 最小栈 - 力扣(LeetCode)
class MinStack(object):def __init__(self):"""initialize your data structure here."""self.stack = []def push(self, x):""":type x: int:rtype: void"""if not self.stack:self.stack.append((x, x))else:self.stack.append((x, min(x, self.stack[-1][1])))def pop(self):""":rtype: void"""self.stack.pop()def top(self):""":rtype: int"""return self.stack[-1][0]def getMin(self):""":rtype: int"""return self.stack[-1][1]作者:负雪明烛
232. 用栈实现队列 - 力扣(LeetCode)
class MyQueue(object):def __init__(self):self.stack1 = []self.stack2 = []def push(self, x):self.stack1.append(x)def pop(self):if not self.stack2:#非空while self.stack1:self.stack2.append(self.stack1.pop())return self.stack2.pop()def peek(self):if not self.stack2:while self.stack1:self.stack2.append(self.stack1.pop())return self.stack2[-1]def empty(self):return not self.stack1 and not self.stack2#非空时返回true作者:负雪明烛
时间复杂度:push() 时间复杂度是 O(1);peek()/pop() 均摊时间复杂度是 O(1)
间复杂度:空间复杂度是 O(N),因为总的占用了 N 个元素的空间。
394. 字符串解码 - 力扣(LeetCode)
class Solution:def decodeString(self, s: str) -> str:stack = [] # (str, int) 记录之前的字符串和括号外的上一个数字num = 0res = "" # 实时记录当前可以提取出来的字符串for c in s:if c.isdigit():#检查字符串中的所有字符是否都是数字num = num * 10 + int(c)elif c == "[":stack.append((res, num))res, num = "", 0elif c == "]":top = stack.pop()res = top[0] + res * top[1]else:res += creturn res作者:Rogers
739. 每日温度 - 力扣(LeetCode)
输入: temperatures
= [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]
单调栈
class Solution:def dailyTemperatures(self, T: List[int]) -> List[int]:# 可以维护一个存储下标的单调栈,从栈底到栈顶的下标对应的温度列表中的温度依次递减。# 如果一个下标在单调栈里,则表示尚未找到下一次温度更高的下标。ans = [0] * len(T)stack = []for i in range(len(T)):while stack and T[stack[-1]] < T[i]: # 栈不为空 && 栈顶温度小于当前温度ans[stack[-1]] = i - stack[-1]stack.pop()stack.append(i)return ans
84. 柱状图中最大的矩形 - 力扣(LeetCode)
暴力枚举 - 左右端点法(TLE)
class Solution:def largestRectangleArea(self, heights: List[int]) -> int:n, ans = len(heights), 0if n != 0:ans = heights[0]for i in range(n):height = heights[i]for j in range(i, n):height = min(height, heights[j])ans = max(ans, (j - i + 1) * height)return ans
单调栈
from typing import Listclass Solution:def largestRectangleArea(self, heights: List[int]) -> int:# 在高度数组的开头和结尾各添加一个高度为0的哨兵,以便处理边界情况n = len(heights)heights = [0] + heights + [0]# 初始化栈和最大面积变量stack = [] # 使用 stack 替代 st 以提高可读性max_area = 0 # 使用 max_area 替代 ans 以提高可读性# 遍历整个高度数组(包括哨兵)for i in range(n + 2):# 当栈不为空且当前高度小于栈顶索引对应的高度时,进行面积计算while stack and heights[stack[-1]] > heights[i]:# 弹出栈顶元素,该元素是当前矩形的高度height = heights[stack.pop()]# 计算宽度,当前索引 i 减去新的栈顶索引再减 1width = i - stack[-1] - 1# 更新最大面积max_area = max(max_area, height * width)# 将当前索引压入栈中stack.append(i)# 返回计算得到的最大矩形面积return max_area
42. 接雨水 - 力扣(LeetCode)
class Solution:def trap(self, height: List[int]) -> int:ans = 0st = []for i, h in enumerate(height):while st and h >= height[st[-1]]:bottom_h = height[st.pop()]if not st: # len(st) == 0breakleft = st[-1]dh = min(height[left], h) - bottom_h # 面积的高ans += dh * (i - left - 1)st.append(i)return ans
相关文章:
python算法和数据结构刷题[2]:链表、队列、栈
链表 链表的节点定义: class Node():def __init__(self,item,nextNone):self.itemitemself.nextNone 删除节点: 删除节点前的节点的next指针指向删除节点的后一个节点 添加节点: 单链表 class Node():"""单链表的结点&quo…...
认知神经科学0-----关于心智的生物学(2011年第三版)
译者序 人类的科学事业所面临的挑战之一-就是认识意识与物质或心灵(智慧)与大脑的关系。从古希腊哲学先贤或更早的时代开始,人类对这一-古 老问题就有了大量的探讨或臆测;但仅仅是在近代和现代,人们才真正在科学的意义上探索心智与大脑的关系。脑…...
想品客老师的第九天:原型和继承
原型与继承前置看这里 原型 原型都了解了,但是不是所有对象都有对象原型 let obj1 {}console.log(obj1)let obj2 Object.create(null, {name: {value: 荷叶饭}})console.log(obj2) obj2为什么没有对象原型?obj2是完全的数据字典对象,没有…...
指针(C语言)从0到1掌握指针,为后续学习c++打下基础
目录 一,指针 二,内存地址和指针 1,什么是内存地址 2,指针在不同系统下所占内存 三,指针的声明和初始化以及类型 1,指针的声明 2,指针 的初始化 1, 初始化方式优点及适用场景 4,指针的声明初始化类型…...
php接口连接数据库
框架:https://www.thinkphp.cn/doc 创建网站 域名自己写 创建文件夹,“test”拉取框架,地址栏输入 composer create-project topthink/think5.1.* tp5 会自动创建一个tp5文件夹 根目录选择刚刚创建拉框架的文件夹 以test为示例 “D:\test\…...
Qt中json的使用
目录 一、json相关类和接口 1.QJsonDocument 2.QJsonObject 3.QJsonArray 4.QJsonValue 二、json写文件 1.写文件基本流程 2.代码示例 三、json读文件 1.读文件基本流程 2.代码示例 json是一种轻量级的数据交换格式,在Qt中使用json数据可以通过Qt提供的Q…...
OpenAI-Edge-TTS:本地化 OpenAI 兼容的文本转语音 API,免费高效!
文本转语音(TTS)技术已经成为人工智能领域的重要一环,无论是语音助手、教育内容生成,还是音频文章创作,TTS 工具都能显著提高效率。今天要为大家介绍的是 OpenAI-Edge-TTS,一款基于 Microsoft Edge 在线文本…...
物业系统改革引领行业智能化管理与提升服务质量的新征程
内容概要 在当今迅速变化的社会中,物业系统改革正在悄然推动行业的智能化管理进程。物业管理作为一个古老而传统的领域,面临着诸多挑战,包括效率低下、业主需求难以满足等。数字化转型为这一现象注入了新活力,帮助物业公司通过先…...
【LLM】Deepseek本地部署学习
文章目录 1. 访问ollama官网安装平台2. 选择配置3. 下载和运行 1. 访问ollama官网安装平台 https://ollama.com/ 2. 选择配置 参考以下配置要求 3. 下载和运行 ollama run deepseek-r1:7b...
Vscode编辑器下 Markdown无法显示图片
1.问题 在vscode 编辑器中无法预览 markdon 文件中的图片 2.解决方案 大部分出现这种情况是因为新版本的vscode会阻拦有风险的资源显示,将安全等级调低即可。 方式一: 1.打开任意 MD 文件,ctrl,调出设置 2. 输入 markdown.ch…...
Java实现.env文件读取敏感数据
文章目录 1.common-env-starter模块1.目录结构2.DotenvEnvironmentPostProcessor.java 在${xxx}解析之前执行,提前读取配置3.EnvProperties.java 这里的path只是为了代码提示4.EnvAutoConfiguration.java Env模块自动配置类5.spring.factories 自动配置和注册Enviro…...
高效学习方法分享
高效学习方法分享 引言 在信息高速发展的今天,学习已经成为每个人不可或缺的一部分。你是否曾感到学习的疲惫,信息的爆炸让你无从下手?今天,我们将探讨几种高效的学习方法,帮助你从中找到适合自己的学习之道。关于学…...
分库分表 相关问题
问题:分库后,就有多个数据源需要,dbproxy 对机器做代理,一般需要lvs/f5 等手段来实现流量的负载均衡,跨机房可能需要dns分发,例如 mycat 阿里的主键。 就这个问题通过一问一答的方式解答 什么是 dbproxy&…...
【Linux系统】进程间通信:实现命名管道通信
认识命名管道通信 命名管道通信的结构图示: 图中的 Server 和 Client 是不同的进程, Server 负责发送数据, Client 则是接收数据,进程之间通过命名管道进行数据通信 准备工作: 创建以下文件 Server.hpp #服务器类的…...
IT服务管理平台(ITSM):构建高效运维体系的基石
IT服务管理平台(ITSM):构建高效运维体系的基石 在数字化转型浪潮的推动下,企业对IT服务的依赖日益加深,如何高效管理和优化IT服务成为企业面临的重要课题。IT服务管理平台(ITSM)应运而生,以其系统化的管理方法和工具,助力企业实现IT服务的规范化、高效化和智能化。本…...
SSM开发(八) MyBatis解决方法重载
目录 一、Mybatis能否支持方法重载? 二、解决 MyBatis 方法重载问题的几种方法 解决方法一: (注解方式) 将重载方法命名为不同的方法名 解决方法二:采用@SelectProvider注解 解决方法三:使用 MyBatis 的 标签和动态 SQL 来构建不同参数的 SQL 查询 三、总结 一、Myb…...
AIGC时代的Vue或React前端开发
在AIGC(人工智能生成内容)时代,Vue开发正经历着深刻的变革。以下是对AIGC时代Vue开发的详细分析: 一、AIGC技术对Vue开发的影响 代码生成与自动化 AIGC技术使得开发者能够借助智能工具快速生成和优化Vue代码。例如,通…...
【实践案例】使用Dify构建文章生成工作流【在线搜索+封面图片生成+内容标题生成】
文章目录 概述开始节点图片封面生成关键词实时搜索主题参考生成文章详情和生成文章标题测试完整工作流运行测试结果 概述 使用Dify构建文章生成工作流,使用工具包括:使用 Tavily 执行的搜索查询,使用Flux生成封面图片,使用Stable…...
使用 Context API 管理临时状态,避免 Redux/Zustand 的持久化陷阱
在开发 React Native 应用时,我们经常需要管理全局状态,比如用户信息、主题设置、网络状态等。而对于某些临时状态,例如 数据同步进行中的状态 (isSyncing),我们应该选择什么方式来管理它? 在项目开发过程中ÿ…...
【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.26 统计圣殿:从描述统计到推断检验
1.26 统计圣殿:从描述统计到推断检验 目录 #mermaid-svg-3nz11PRr47fVfGWZ {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3nz11PRr47fVfGWZ .error-icon{fill:#552222;}#mermaid-svg-3nz11PRr47fVfGWZ…...
C# 添加、替换、提取、或删除Excel中的图片
在Excel中插入与数据相关的图片,能将关键数据或信息以更直观的方式呈现出来,使文档更加美观。此外,对于已有图片,你有事可能需要更新图片以确保信息的准确性,或者将Excel 中的图片单独保存,用于资料归档、备…...
商密测评题库详解:商用密码应用安全性评估从业人员考核题库详细解析(9)
1. 申请商用密码测评机构需提交材料考点 根据《商用密码应用安全性测评机构管理办法(试行)》,申请成为商用密码应用安全性测评机构的单位应当提交的材料不包括( )。 A. 从事与普通密码相关工作情况的说明 B. 开展测评工作所需的软硬件及其他服务保障设施配备情况 C. 管…...
开源项目Umami网站统计MySQL8.0版本Docker+Linux安装部署教程
Umami是什么? Umami是一个开源项目,简单、快速、专注用户隐私的网站统计项目。 下面来介绍如何本地安装部署Umami项目,进行你的网站统计接入。特别对于首次使用docker的萌新有非常好的指导、参考和帮助作用。 Umami的github和docker镜像地…...
模型I/O功能之模型包装器
文章目录 模型包装器分类LLM模型包装器、聊天模型包装器 截至2023年7月,LangChain支持的大语言模型已经超过了50种,这其中包括了来自OpenAI、Meta、Google等顶尖科技公司的大语言模型,以及各类优秀的开源大语言模型。对于这些大语言模型&…...
免杀国内主流杀软的恶意样本分析
目录下存在愤怒的小鸟.exe和fun.dll文件,最新版火绒,windows defender,腾讯电脑管家,360静态扫描都未发现恶意程序 动态执行,杀软也未拦截 上传到virustotal网站分析恶意程序,只有三个引擎检测出来 die分析…...
Cloudreve:Star22.3k,免费开源的网盘,支持多种存储方式,它允许用户快速搭建个人或团队的私有云存储服务。
嗨,大家好,我是小华同学,关注我们获得“最新、最全、最优质”开源项目和高效工作学习方法 Cloudreve是一个基于Web的文件管理和分享系统,它允许用户快速搭建个人或团队的私有云存储服务。该项目以其高度的可定制性和灵活性&#x…...
【高内聚】设计模式是如何让软件更好做到高内聚的?
高内聚(High Cohesion)是指模块内部的元素紧密协作,共同完成一个明确且相对独立的功能。就像高效的小团队,成员们目标一致,相互配合默契。 低耦合(Loose Coupling)是指模块之间的依赖较少&#…...
第一个3D程序!
运行效果 CPP #include <iostream> #include <fstream> #include <string> #include <cmath>#include <GL/glew.h> #include <GLFW/glfw3.h> #include <glm/glm.hpp> #include <glm/gtc/type_ptr.hpp> #include <glm/gtc/…...
基础项目实战——学生管理系统(c++)
目录 前言一、功能菜单界面二、类与结构体的实现三、录入学生信息四、删除学生信息五、更改学生信息六、查找学生信息七、统计学生人数八、保存学生信息九、读取学生信息十、打印所有学生信息十一、退出系统十二、文件拆分结语 前言 这一期我们来一起学习我们在大学做过的课程…...
【PyTorch】6.张量形状操作:在深度学习的 “魔方” 里,玩转张量形状
目录 1. reshape 函数的用法 2. transpose 和 permute 函数的使用 4. squeeze 和 unsqueeze 函数的用法 5. 小节 个人主页:Icomi 专栏地址:PyTorch入门 在深度学习蓬勃发展的当下,PyTorch 是不可或缺的工具。它作为强大的深度学习框架&am…...
OpenEuler学习笔记(十六):搭建postgresql高可用数据库环境
以下是在OpenEuler系统上搭建PostgreSQL高可用数据环境的一般步骤,通常可以使用流复制(Streaming Replication)或基于Patroni等工具来实现高可用,以下以流复制为例: 安装PostgreSQL 配置软件源:可以使用O…...
记录一次Sqoop从MySQL导入数据到Hive问题的排查经过
个人博客地址:记录一次Sqoop从MySQL导入数据到Hive问题的排查经过 | 一张假钞的真实世界 问题描述 MySQL中原始数据有790W+的记录数,在Sqoop抽取作业成功的情况下在Hive中只有500W左右的记录数。 排查过程 数据导入脚本Log 通过Log可以发现以下信息: 该Sqoop任务被分解…...
什么是集成学习
什么是集成学习 集成学习是一种分布式机器学习框架,通过构建多个学习器并将其结合起来完成学习任务。由于在实际应用中单一的学习器往往不能达到理想的学习效果,且有时单一学习器会导致过拟合,因此使用多个学习器进行集成学习往往能够达到更好…...
VSCode+Continue实现AI辅助编程
Continue是一款功能强大的AI辅助编程插件,可连接多种大模型,支持代码设计优化、错误修正、自动补全、注释编写等功能,助力开发人员提高工作效率与代码质量。以下是其安装和使用方法: 一、安装VSCode 参见: vscode安…...
Springboot如何使用面向切面编程AOP?
Springboot如何使用面向切面编程AOP? 在 Spring Boot 中使用面向切面编程(AOP)非常简单,Spring Boot 提供了对 AOP 的自动配置支持。以下是详细的步骤和示例,帮助你快速上手 Spring Boot 中的 AOP。 1. 添加依赖 首先ÿ…...
ThreadLocal源码解析
文章目录 一、概述二、get()方法三、set()方法四、可能导致的内存泄漏问题五、remove六、思考:为什么要将ThreadLocalMap的value设置为强引用? 一、概述 ThreadLocal是线程私有的,独立初始化的变量副本。存放在和线程进行绑定的ThreadLocalMa…...
Maven的单元测试
1. 单元测试的基本概念 单元测试(Unit Testing) 是一种软件测试方法,专注于测试程序中的最小可测试单元——通常是单个类或方法。通过单元测试,可以确保每个模块按预期工作,从而提高代码的质量和可靠性。 2.安装和配…...
深度学习 Pytorch 深层神经网络
在之前已经学习了三种单层神经网络,分别为实现线性方程的回归网络,实现二分类的逻辑回归(二分类网络),以及实现多分类的softmax回归(多分类网络)。从本节开始,我们将从单层神经网络展…...
【python】三帧差法实现运动目标检测
三帧差法是一种常用的运动目标检测方法,它通过比较连续三帧图像之间的差异来检测运动物体。这种方法尤其适用于背景变化较小的场景。 目录 1 方案 2 实践 ① 代码 ② 效果图 1 方案 具体步骤如下: ① 读取视频流:使用cv2.VideoCapture()…...
机器人抓取与操作经典规划算法(深蓝)——2
1 经典规划算法 位姿估计:(1)相机系位姿 (2)机器人系位姿 抓取位姿:(1)抓取位姿计算 (2)抓取评估和优化 路径规划:(1)笛卡…...
WGCLOUD服务器资源监控软件使用笔记 - Token is error是什么错误
[wgcloud-agent]2025/01/30 10:41:30 WgcloudAgent.go:90: 主机监控信息上报server开始 [wgcloud-agent]2025/01/30 10:41:30 WgcloudAgent.go:99: 主机监控信息上报server返回信息: {"result":"Token is error"} 这个错误是因为agent配置的wgToken和serv…...
在排序数组中查找元素的第一个和最后一个位置(力扣)
一.题目介绍 二.题目解析 使用二分进行查找 2.1处理边界情况 如果数组为空,直接返回 [-1, -1],因为无法找到目标值。 int[] ret new int[2]; ret[0] ret[1] -1; if (nums.length 0) return ret; 2.2查找左端点(目标值开始位置&#…...
Kafka的消息协议
引言 在学习MQTT消息协议的时候我常常思考kafka的消息协议是什么,怎么保证消息的可靠性和高性能传输的,接下来我们一同探究一下 Kafka 在不同的使用场景和组件交互中用到了多种协议,以下为你详细介绍: 内部通信协议 Kafka 使用…...
Vue 3 30天精进之旅:Day 09 - 组合式API
在Vue 3中,组合式API(Composition API)是一个引入的新特性,它为开发者提供了一种更灵活的方式来构建和组织组件。与传统的选项API相比,组合式API更注重逻辑的复用和逻辑的组合,让我们更容易处理大型应用中的…...
Day28(补)-【AI思考】-AI会不会考虑自己的需求?
文章目录 AI会不会考虑自己的需求?一、**技术本质:深度≠理解**二、**传播机制:热搜如何制造幻觉**三、**伦理考量:为何必须"撇清"**关键结论 AI会不会考虑自己的需求? 让思想碎片重焕生机的灵魂:…...
JavaScript 进阶(下)
原型 what 首先,构造函数通过原型分配的函数是所有对象所 共享的。 然后,JavaScript 规定,每一个构造函数都有一个 prototype 属性,指向另一个对象,所以我们也称为原型对象 这个对象可以挂载函数,对象实…...
selenium自动化测试框架——面试题整理
目录 1. 什么是 Selenium?它的工作原理是什么? 2. Selenium 主要组件 3. 常见 WebDriver 驱动 4. Selenium 如何驱动浏览器? 5. WebDriver 协议是什么? 6. Page Object 模式与 Page Factory 7. 如何判断元素是否可见&#x…...
CF1098F Ж-function
【题意】 给你一个字符串 s s s,每次询问给你 l , r l, r l,r,让你输出 s s s l , r sss_{l,r} sssl,r中 ∑ i 1 r − l 1 L C P ( s s i , s s 1 ) \sum_{i1}^{r-l1}LCP(ss_i,ss_1) ∑i1r−l1LCP(ssi,ss1)。 【思路】 和前一道题一样&#…...
数据库备份、主从、集群等配置
数据库备份、主从、集群等配置 1 MySQL1.1 docker安装MySQL1.2 主从复制1.2.1 主节点配置1.2.2 从节点配置1.2.3 创建用于主从同步的用户1.2.4 开启主从同步1.2.4 主从同步验证 1.3 主从切换1.3.1 主节点设置只读(在192.168.1.151上操作)1.3.2 检查主从数…...
电脑要使用cuda需要进行什么配置
在电脑上使用CUDA(NVIDIA的并行计算平台和API),需要进行以下配置和准备: 1. 检查NVIDIA显卡支持 确保你的电脑拥有支持CUDA的NVIDIA显卡。 可以在NVIDIA官方CUDA支持显卡列表中查看显卡型号是否支持CUDA。 2. 安装NVIDIA显卡驱动…...