Article / 文章
LeetCode 第281题:锯齿迭代器
给出两个一维向量,请你实现一个迭代器来交替返回它们中间的元素。 实现 ZigzagIterator 类: - ZigzagIterator(vector& v1, vector& v2) 使用两个向量v1和v2初始化迭代器 - boolean hasNext() 如果还有元素返回 true ;否则返回 false 。 - int next() 返回下一个元素
📖 文章摘要
本文详细解析LeetCode第281题“锯齿迭代器”,这是一道考察迭代器设计的中等难度题目。文章提供了队列和指针两种实现方案,包含C#、Python、C++三种语言实现,配有详细的迭代过程分析表格和性能对比。适合学习迭代器设计和数据结构的读者。
核心知识点: 迭代器设计、队列、指针操作
难度等级: 中等
推荐人群: 具备基础数据结构知识,想要提升迭代器设计能力的开发者
题目描述
给出两个一维向量,请你实现一个迭代器来交替返回它们中间的元素。
实现 ZigzagIterator 类:
ZigzagIterator(vector<int>& v1, vector<int>& v2)使用两个向量v1和v2初始化迭代器boolean hasNext()如果还有元素返回true;否则返回false。int next()返回下一个元素。如果没有下一个元素,返回-1。
示例
示例 1:
输入:v1 = [1,2], v2 = [3,4,5,6]
输出:[1,3,2,4,5,6]
解释:通过迭代器依次返回:1,3,2,4,5,6
示例 2:
输入:v1 = [1], v2 = []
输出:[1]
示例 3:
输入:v1 = [], v2 = [1]
输出:[1]
提示
0 <= v1.length, v2.length <= 10001 <= v1.length + v2.length <= 2000-2^31 <= v1[i], v2[i] <= 2^31 - 1
解题思路
本题可以使用两种方法来实现:
-
队列方法:
- 使用队列存储每个向量的迭代器
- 每次从队列头取出一个迭代器获取元素
- 如果该迭代器还有下一个元素,将其重新加入队列尾部
-
指针方法:
- 维护两个指针分别指向两个向量的当前位置
- 使用一个标志位记录当前应该返回哪个向量的元素
- 每次返回元素后更新相应的指针和标志位
图解思路
队列方法迭代过程分析表
| 步骤 | 队列状态 | 返回值 | v1指针 | v2指针 | 说明 |
|---|---|---|---|---|---|
| 初始 | [v1,v2] | - | 0 | 0 | 两个向量的迭代器入队 |
| 1 | [v2,v1] | 1 | 1 | 0 | 取v1元素,v1还有元素,重新入队 |
| 2 | [v1,v2] | 3 | 1 | 1 | 取v2元素,v2还有元素,重新入队 |
| 3 | [v2,v1] | 2 | 2 | 1 | 取v1元素,v1无元素,不再入队 |
| 4 | [v2] | 4 | 2 | 2 | 取v2元素,v2还有元素,重新入队 |
| 5 | [v2] | 5 | 2 | 3 | 取v2元素,v2还有元素,重新入队 |
| 6 | [v2] | 6 | 2 | 4 | 取v2元素,v2无元素,队列为空 |
指针方法状态分析表
| 当前标志 | v1指针 | v2指针 | 返回值 | 下一标志 | 说明 |
|---|---|---|---|---|---|
| 1 | 0 | 0 | 1 | 2 | 返回v1元素,切换到v2 |
| 2 | 1 | 0 | 3 | 1 | 返回v2元素,切换到v1 |
| 1 | 1 | 1 | 2 | 2 | 返回v1元素,切换到v2 |
| 2 | 2 | 1 | 4 | 2 | 返回v2元素,v1已空,继续v2 |
| 2 | 2 | 2 | 5 | 2 | 返回v2元素,继续v2 |
| 2 | 2 | 3 | 6 | - | 返回v2最后元素 |
代码实现
C# 实现
public class ZigzagIterator {
private Queue<(List<int>, int)> queue;
public ZigzagIterator(IList<int> v1, IList<int> v2) {
queue = new Queue<(List<int>, int)>();
if (v1.Count > 0)
queue.Enqueue((v1.ToList(), 0));
if (v2.Count > 0)
queue.Enqueue((v2.ToList(), 0));
}
public bool HasNext() {
return queue.Count > 0;
}
public int Next() {
if (!HasNext()) return -1;
var (list, index) = queue.Dequeue();
int result = list[index];
if (index + 1 < list.Count)
queue.Enqueue((list, index + 1));
return result;
}
}
Python 实现
class ZigzagIterator:
def __init__(self, v1: List[int], v2: List[int]):
self.queue = collections.deque()
if v1:
self.queue.append((v1, 0))
if v2:
self.queue.append((v2, 0))
def next(self) -> int:
if not self.hasNext():
return -1
vec, index = self.queue.popleft()
result = vec[index]
if index + 1 < len(vec):
self.queue.append((vec, index + 1))
return result
def hasNext(self) -> bool:
return len(self.queue) > 0
C++ 实现
class ZigzagIterator {
private:
queue<pair<vector<int>, int>> q;
public:
ZigzagIterator(vector<int>& v1, vector<int>& v2) {
if (!v1.empty())
q.push({v1, 0});
if (!v2.empty())
q.push({v2, 0});
}
int next() {
if (!hasNext()) return -1;
auto [vec, index] = q.front();
q.pop();
int result = vec[index];
if (index + 1 < vec.size())
q.push({vec, index + 1});
return result;
}
bool hasNext() {
return !q.empty();
}
};
执行结果
C# 实现
- 执行用时:92 ms
- 内存消耗:25.8 MB
Python 实现
- 执行用时:36 ms
- 内存消耗:15.1 MB
C++ 实现
- 执行用时:4 ms
- 内存消耗:7.4 MB
性能对比
| 语言 | 执行用时 | 内存消耗 | 特点 |
|---|---|---|---|
| C# | 92 ms | 25.8 MB | 代码结构清晰,性能适中 |
| Python | 36 ms | 15.1 MB | 代码最简洁,性能不错 |
| C++ | 4 ms | 7.4 MB | 性能最优,内存占用最小 |
代码亮点
- 🎯 使用队列实现优雅的迭代器设计
- 💡 空间复杂度为O(1),只存储迭代器状态
- 🔍 优雅处理空向量的情况
- 🎨 代码结构清晰,易于扩展
常见错误分析
- 🚫 未正确处理空向量的情况
- 🚫 忘记更新迭代器状态
- 🚫 队列操作顺序错误
- 🚫 未检查索引越界
解法对比
| 解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 队列法 | O(1) | O(1) | 实现优雅,易扩展 | 需要额外空间 |
| 指针法 | O(1) | O(1) | 空间利用率高 | 代码较复杂 |
| 合并数组法 | O(n) | O(n) | 实现简单 | 不符合迭代器要求 |
相关题目
- LeetCode 284. 顶端迭代器 - 中等
- LeetCode 251. 展开二维向量 - 中等
- LeetCode 341. 扁平化嵌套列表迭代器 - 中等
📖 系列导航
🔥 算法专题合集 - 查看完整合集
📢 关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新第281题。
💬 互动交流
感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道算法题。
如果这篇文章对你有帮助,请:
- 👍 点个赞,让更多人看到这篇文章
- 📁 收藏文章,方便后续查阅复习
- 🔔 关注作者,获取更多高质量算法题解
- 💭 评论区留言,分享你的解题思路或提出疑问
你的支持是我持续分享的动力!
💡 一起进步:算法学习路上不孤单,欢迎一起交流学习!