算法练习 - 栈
关于栈的使用,难度从简单到难
练习1LeetCode - 496. Next Greater Element I
class Solution(object):
# 输入:两个无重复的数列nums1和nums2,其中nums1是nums2的一个子集。找出所有的nums中元素之后行下一个比其大的元素在nums2中的位置
# 输出:下一个较大位置的数列
# nums1 和 nums2 中的所有数都是独一无二的
# nums1 和 nums2 的大小不超过1000
# 如果暴力的做,就是遍历nums1然后在nums2中去找对应的比它大一位数的位置,时间复杂度O(n2)
# nums1 和 nums2 中的所有数都是独一无二的,那么就说明 (数-位置) 形成了映射关系,这种映射关系可以在线性时间内完成
# 找到每个数的下一个大的数的序列,也就等于找到了位置序列
def nextGreaterElement(self, findNums, nums):
"""
:type findNums: List[int]
:type nums: List[int]
:rtype: List[int]
"""
# 设 d_next 为nums中数对应下一大数的映射关系
d = {}
# 设 s 为一个空的栈
s = []
# 遍历 nums,当前元素为x:
for x in nums:
# 如果 s 不为空 且 栈顶 < x,在d中添加对应的映射关系,并将当前栈顶数出栈:
while len(s) and s[-1] < x:
d[s.pop()] = x
# 否则,继续入栈
s.append(x)
# 设 ans 为返回序列
ans=[]
# 遍历 findnums ,x:
for x in findNums:
# 在d中查找对应的x是否存在映射关系:
# 如果存在:
# ans中添加当前x的对应的下一大
# 不存在:
# ans中添加-1
ans.append(d.get(x,-1))
# 返回ans
return ans
练习2 LeetCode - 173. Binary Search Tree Iterator
class BSTIterator(object):
def __init__(self, root):
"""
:type root: TreeNode
"""
# 用栈来初始化
self.s = []
curNode = root
while curNode:
self.s.append(curNode)
curNode = curNode.left
# 判断是否存在下一个最小值
def hasNext(self):
"""
:rtype: bool
"""
# 如果s不为空,则返回栈顶
if len(self.s):
return True
else:
return False
# next 返回的是下一个最小值,也就是当前结点的左子节点
def next(self):
"""
:rtype: int
"""
curTop = self.s.pop()
# 将下一个大小的入栈
if curTop.right:
if curTop.right.left:
temp = curTop.right
while temp:
self.s.append(temp)
temp = temp.left
else:
self.s.append(curTop.right)
return curTop.val
练习3 LeetCode - 341. Flatten Nested List Iterator
class NestedIterator(object):
# 不能直接遍历的原因,可能嵌套很多层,如果每次都指定一个新的指针来控制list的迭代,而且你不知道到底深度为多少,算法复杂度会很高
def __init__(self, nestedList):
"""
Initialize your data structure here.
:type nestedList: List[NestedInteger]
"""
# 首先逆序入栈
self.list = []
self.ans = []
for i in nestedList:
self.list.append(i)
# 利用栈的特性将数列展开
# 如果说队列为空,那么直接返回
if not len(self.list):
return
# 否则,将当前栈顶展开入栈
while len(self.list):
# 如果栈顶元素存在且不是数字,而是数列
curNested = self.list.pop()
if curNested and (not curNested.isInteger()):
# 那么将数列展开,逆序入栈
for i in curNested.getList():
self.list.append(i)
# 如果栈顶元素是数字,将栈顶元素的值输入一个list
else:
self.ans.append(curNested.getInteger())
def next(self):
"""
:rtype: int
"""
return self.ans.pop()
def hasNext(self):
"""
:rtype: bool
"""
if len(self.ans):
return True
else:
return False
我的方法是先全部展开,参考其他的答案,也每次获取下一个的时候展开,这样可以节约一定的空间,不过时间上的消耗也更多了
class NestedIterator(object):
# 不能直接遍历的原因,可能嵌套很多层,如果每次都指定一个新的指针来控制list的迭代,而且你不知道到底深度为多少,算法复杂度会很高
def __init__(self, nestedList):
"""
Initialize your data structure here.
:type nestedList: List[NestedInteger]
"""
# 首先逆序入栈
self.list = []
for i in reversed(nestedList):
self.list.append(i)
def next(self):
"""
:rtype: int
"""
return self.list.pop()
def hasNext(self):
"""
:rtype: bool
"""
# 当 栈栈元素为一个数字 的时候,才能返回true
while len(self.list):
# cur 为 栈顶元素
cur = self.list[-1]
# 如果栈顶元素为一个数字,返回返回真
if cur.isInteger():
return True
# 否则 则说明当前栈顶为一个数列,展开数列
else:
# pop出当前的cur
cur = self.list.pop()
# 并且逆序的填入栈中
for i in reversed(cur.getList()):
self.list.append(i)
# 然后继续循环
continue
# 遍历到栈空,都没有数字,那就返回空
return False
练习4LeetCode - 331. Verify Preorder Serialization of a Binary Tree
class Solution(object):
# 使用先序遍历来序列化一个二叉树,遇数计数,无数记 #
# 判断给定的一个字符串,判断是否是一个二叉树先序序列化后的
def isValidSerialization(self, preorder):
"""
:type preorder: str
:rtype: bool
"""
# 先序遍历的特点是 (自身 - 左 - 右)
# 设 s 为一个栈
s = []
# 设 i 为 0
i = 0
# 将preorder转化为一个字符的list
preorder = preorder.split(',')
# 将 preorder第i个元素 入栈
s.append(preorder[i])
# 如果i小于preorder的size,则循环:
for i in range(1,len(preorder)):
# 判断不符合的情况,也就是按照出入栈规则无法继续的情况:
# 当栈为空的时候,i 还未到 preorder 的边界
# 这种情况下,返回false
if len(s) == 0:
return False
# 如果 当前栈顶 为空 且 如果 preorder[i] 也是空,那么:
if len(s) == 1 and s[-1] == '#':
return False
if preorder[i] == '#':
# 将当前的栈顶元素pop出,然后将新的栈顶元素也pop出
while len(s) and s[-1] == '#':
s.pop()
s.pop()
# 并且将 空 入栈
s.append('#')
# 否则:
else:
# 将preorder[i]入栈
s.append(preorder[i])
# 如果此时栈不为空,则返回false,否则返回true
if len(s) > 1 or s[-1] != '#':
return False
else:
return True
练习5 LeetCode - 503. Next Greater Element II
class Solution(object):
"""
同样是找下一个大的数字,不过这次是一个循环列表
那么只有数列中最大的数(或者同样大小的几个数)为-1,其他的所有数都肯定是有一个比它大的数
并且最大数max之前的所有数肯定在一次循环之内可以找到下一个最大数,或者说在遍历到达max就可以找到下一个最大数
而max之后的数,第二遍循环到max的时候,也可以全部找到下一个最大数
也就是当最大的数只有一个的时候,遍历最多两遍可以找到所有下一大
当有多个最大值的时候呢?
想想数列被切分成了好几份,每两个最大值之间的数,一定可以找到最大值,而又是循环的,所以最后一个最大值之后的数字,都可以在第二次遍历到
第一个max之前找到
最重要的是如何判断要跳出循环,特别在多个最大值的时候
直观的来收,是在第二次遍历到最大值的时候,所以应该先用O(n)的时间找到最大值
然后记录遍历的次数,当遍历到第二遍,且已经为最大值的时候,跳出循环
应为存在相同的数不同的位置,所以这次要使用 (位置-下一大)的,也就是说需要一个值来保存当前栈顶的位置,然后每次找到直接在数组中表示出来
"""
def nextGreaterElements(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
# 如果数列的大小为0,返回空
if len(nums) == 0:
return []
# 如果数列的大小为1,那么直接返回[-1]
if len(nums) == 1:
return [-1]
# 遍历一次数组,找到最大值 max_nums
max_nums = max(nums)
# 设 c = 1 为循环的次数
c = 1
# 设 s_nums 为一个用于保存数字栈
s_nums = []
# 设 s_index 为一个用于保存对应数字位置的栈
s_index = []
# 设 ans 为最后的输出,一个所有元素都是-1的,长度和nums相同的数列
ans = [-1]*len(nums)
# 设 i = 0
i = 0
# 遍历 nums,指针为i:
while True:
# 如果当前栈为空:
if len(s_nums) == 0:
# s_nums,s_index直接入栈nums[i]
s_nums.append(nums[i])
s_index.append(i)
i+=1
# 否则:
elif s_nums[-1] == nums[i] and s_nums[-1] == max_nums:
i+=1
else:
# top 为栈顶
top = s_nums[-1]
# 如果 top >= nums[i]:
if top >= nums[i]:
# s_nums 入栈 nums[i]
s_nums.append(nums[i])
# s_index 入栈 i
s_index.append(i)
# i += 1
i += 1
# 如果 top < nums[i]:
else:
# s_nums,s_index出栈当前元素 top ,top_index
s_nums.pop()
# ans中将 top_index 设为 nums[i]
ans[s_index.pop()] = nums[i]
# 如果 i = len(nums):
if i == len(nums):
# i = 0,回到起始点
# c = 2
i,c = 0,2
# 如果 c = 2 且 nums[i] == max_nums 且 最终的栈内应该只包含最大值一个数
if c == 2 and nums[i] == max_nums and len(s_nums) == 1:
# 跳出
break
# 返回 ans
return ans
大神的算法瞻仰,逻辑清晰,条例分明,说明还得练啊:
def nextGreaterElements(self, nums):
# 同样是一个栈和一个等长的res列表
stack, res = [], [-1] * len(nums)
# 循环了两遍,同时,简单的循环两遍,节省了很多逻辑
for i in range(len(nums)) * 2:
# 如果 stack 为空,且 栈顶元素 的 大小 小于 nums【i】,则出栈
while stack and (nums[stack[-1]] < nums[i]):
res[stack.pop()] = nums[i]
# 它只用栈保存了位置index,然后通过nums[index]来获取对应的数,很聪明
stack.append(i)
# 最后栈中应该全是最大的那个数
return res