顺序存储结构中除了数组外,栈和队列相关的内容也是常被问到的,栈是先进后出、队列是先进先出。虽然形式不同,但和数组同属于顺序存储的数据结构,有些语言本身是不支持栈和队列的,那做这类题的时候就可以使用数组来模拟。
数据结构
队列和栈的例子:232. 用栈实现队列
题解:创建两个stack,当调用
push
让元素入队时,只要把元素压入stack1
即可。使用peek
或pop
操作队头的元素时,若s2
为空,可以把stack1
的所有元素取出再添加进stack2
,这时候stack2
中元素就是先进先出顺序了
class MyQueue {
var stack1: [Int]
var stack2: [Int]
var front = -1
init() {
self.stack1 = []
self.stack2 = []
}
func push(_ x: Int) {
if stack1.isEmpty {
front = x
}
stack1.append(x)
}
func pop() -> Int {
if stack2.isEmpty {
while !stack1.isEmpty {
stack2.append(stack1.removeLast())
}
}
return stack2.popLast() ?? -1
}
func peek() -> Int {
return stack2.last ?? front
}
func empty() -> Bool {
return stack1.isEmpty && stack2.isEmpty
}
}
其他经典的题有:225. 用队列实现栈、155. 最小栈
单调队列
单调队列其实就是一个“队列”。只是使用了一点巧妙方法,使得队列中的元素都是单调递增(或递减)的。 具体的举例:239. 滑动窗口最大值
题解:使用一个队列充当不断滑动的窗口,每次滑动记录其中的最大值。如何在 O(1) 时间计算最大值,只需要一个特殊的数据结构「单调队列」,push 方法依然在队尾添加元素,但是要把前面比自己小的元素都删掉,直到遇到更大的元素才停止删除。使用单调队列数据结构就能完成本题。
class Solution {
func maxSlidingWindow(_ nums: [Int], _ k: Int) -> [Int] {
if nums.count == 0 || k < 1 {
return []
}
if k == 1 {
return nums
}
// 存放最大值
var max = Array(repeating: 0, count: nums.count-k+1)
// 滑动窗口数组,存放索引
var deque: [Int] = []
// 扫描所有元素
for i in 0..<nums.count {
// 只要删除nums[队尾] <= nums[i],就删除队尾
while !deque.isEmpty && nums[i] >= nums[deque.last!] {
deque.removeLast()
}
// 将i加入到队尾
deque.append(i)
// 检查窗口索引是否合法
let w = i - k + 1
if w < 0 {
continue
}
// 检查队头的合法性
if deque.first! < w {
// 队头不合法
deque.remove(at: 0)
}
// 设置窗口的最大值
max[w] = nums[deque.first!]
}
return max
}
}
单调栈
单调栈和单调队列类似,单调栈其实就是一个“栈”,只是利用了一些巧妙的逻辑,使得每次新元素入栈后,栈内的元素都保持有序(单调递增或单调递减)。 举例:496. 下一个更大元素 I
题解:单调栈模板 实现了一个计算下一个更大元素的函数,这里可以直接复用。因为题目说 nums1 是 nums2 的子集,那么我们先把 nums2 中每个元素的下一个更大元素算出来存到一个映射里,然后再让 nums1 中的元素去查表即可。
class Solution {
// 计算 nums 中每个元素的下一个更大元素
func nextGreaterElement(_ nums: [Int]) -> [Int] {
// 存放答案的数组
var res: [Int] = Array(repeating: -1, count: nums.count)
var stack: [Int] = []
// 倒着往栈里放
for i in (0..<nums.count).reversed() {
// 判定个子高矮
while !stack.isEmpty && stack.last! <= nums[i] {
// 矮个子起开,被挡住了
stack.removeLast()
}
// nums[i] 身后 next great number
res[i] = (stack.isEmpty ? -1 : stack.last)!
stack.append(nums[i])
}
return res
}
func nextGreaterElement(_ nums1: [Int], _ nums2: [Int]) -> [Int] {
// 记录 nums2 中每个元素的下一个更大元素
let greater = nextGreaterElement(nums2)
// 转化成映射: 元素x -> x的下一个最大元素
var greaterMap: [Int: Int] = [:]
for i in 0..<nums2.count {
greaterMap[nums2[i], default: 0] = greater[i]
}
// nums1是nums2的子集,所以根据 greaterMap可以得到结果
var res: [Int] = Array(repeating: 0, count: nums1.count)
for i in 0..<nums1.count {
res[i] = greaterMap[nums1[i], default: 0]
}
return res
}
}
其他经典题有:503. 下一个更大元素 II、739. 每日温度、接雨水
总结
栈和队列也是比较好培养刷题感觉的基础结构类的题,刷好基础类的题会对刷其他类型的题有所帮助。 其他经典题有:20. 有效括号、71. 简化路径、394. 字符串解码