LeetCode 面试题 03.01. 三合一
题目描述

题意分析
用一个数组实现编号为
0、1、2的三个独立栈,每个栈容量固定为stackSize。每次只操作指定栈:未满时允许入栈,满栈入栈忽略;空栈出栈或查看栈顶返回-1,并支持判空。
解法:定长数组分段保存三个栈
核心思路
[!blue]
将数组前
3 * cap个位置平均分成三段,每段保存一个栈。编号k的数据起点是cap * k,再用数组末尾下标3 * cap + k保存这个栈当前的元素个数。数据和三个计数都存放在同一个数组中,因此总长度为3 * cap + 3。设某栈当前计数为
size,它的有效元素占据本段前size格,下一次入栈位置就是起点 + size。只有size < cap时才写入并增加计数,既保证栈内顺序,也不会越过自己的数据段或覆盖计数槽位。非空栈的栈顶位于
起点 + size - 1。pop先将计数减一,再用新计数定位原栈顶并返回;peek读取同一位置但不改变计数。由此后压入的元素总是先弹出,符合栈的后进先出规则。弹出后无需把旧槽位清零,因为逻辑栈只由计数限定,下一次入栈会覆盖该位置。三个计数彼此独立,操作一个栈只修改它的数据段和计数,不能借用其他栈的空余容量。
cap = 0时所有计数都为零,入栈条件始终不成立,其他接口也能按空栈正常处理。
解题步骤
- 构造长度 3cap+3 的数组,计数初始化为零。
- push 检查未满,在起点+计数写入,再增加计数。
- pop 检查非空,先减少计数再读取原栈顶。
- peek 读取起点+计数-1,不改变计数;isEmpty 判断计数为零。
代码实现
class TripleInOne {
private int cap;
private int[] stk;
public TripleInOne(int stackSize) {
cap = stackSize;
// 前 cap * 3 格存数据,末尾 3 格存三个栈的元素个数。
stk = new int[cap * 3 + 3];
}
public void push(int stackNum, int value) {
if (stk[cap * 3 + stackNum] < cap) {
stk[cap * stackNum + stk[cap * 3 + stackNum]] = value;
++stk[cap * 3 + stackNum];
}
}
public int pop(int stackNum) {
if (isEmpty(stackNum)) {
return -1;
}
// 先减后取:减完之后计数正好指向原栈顶。
--stk[cap * 3 + stackNum];
return stk[cap * stackNum + stk[cap * 3 + stackNum]];
}
public int peek(int stackNum) {
return isEmpty(stackNum) ? -1 : stk[cap * stackNum + stk[cap * 3 + stackNum] - 1];
}
public boolean isEmpty(int stackNum) {
return stk[cap * 3 + stackNum] == 0;
}
}
type TripleInOne struct {
cap int
stk []int
}
func Constructor(stackSize int) TripleInOne {
// 前 stackSize * 3 格存数据,末尾 3 格存三个栈的元素个数。
return TripleInOne{stackSize, make([]int, stackSize*3+3)}
}
func (this *TripleInOne) Push(stackNum int, value int) {
if this.stk[this.cap*3+stackNum] < this.cap {
this.stk[this.cap*stackNum+this.stk[this.cap*3+stackNum]] = value
this.stk[this.cap*3+stackNum]++
}
}
func (this *TripleInOne) Pop(stackNum int) int {
if this.IsEmpty(stackNum) {
return -1
}
// 先减后取:减完之后计数正好指向原栈顶。
this.stk[this.cap*3+stackNum]--
return this.stk[this.cap*stackNum+this.stk[this.cap*3+stackNum]]
}
func (this *TripleInOne) Peek(stackNum int) int {
if this.IsEmpty(stackNum) {
return -1
}
return this.stk[this.cap*stackNum+this.stk[this.cap*3+stackNum]-1]
}
func (this *TripleInOne) IsEmpty(stackNum int) bool {
return this.stk[this.cap*3+stackNum] == 0
}
复杂度分析
- 时间复杂度:构造时分配并初始化数组需要 $O(cap+1)$;
push、pop、peek、isEmpty都只做固定次数的下标访问,为 $O(1)$。- 空间复杂度:$O(cap+1)$,一个长度为
3 * cap + 3的数组。
关键点总结
[!green]
三个计数同时表示元素个数和下一次写入的段内偏移;数据与计数槽位的下标约定必须一致。
易错点总结
[!yellow]
- cap=0 时所有 push 忽略,不能用 <= 判断未满。
- peek 不得改变计数。
- 三个栈分别检查自己的计数,不能借用其他栈空余容量。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 补充题 121. 用数组实现定长栈 | 中等 | 单个定长栈提供 push/pop 的计数模板,本题额外用分段偏移隔离三个栈。 |
| 622. 设计循环队列 | 中等 | 同样通过固定数组下标管理容量,队列循环复用首尾槽位,栈只维护顶部计数。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!