281. Zigzag Iterator
On LeetCode ->Problem¶
Implement an iterator over two integer lists: alternate elements starting with the first list, skipping exhausted lists. Provide next() and non-consuming hasNext().
For \(k\) lists, visit nonempty lists cyclically.
class ZigzagIterator:
def __init__(self, v1: list[int], v2: list[int]):
pass
def hasNext(self):
pass
def next(self):
pass
Key trick¶
Keep a queue of nonempty lists and their positions; after consuming an element, move that list to the back only if it has elements remaining.
Trap¶
Do not stop when one list ends, enqueue empty lists, or let repeated hasNext() calls consume elements.
Why is it interesting?¶
It combines iterator state with round-robin scheduling, and the queue approach extends directly to \(k\) lists.
Python solution¶
from collections import deque
class ZigzagIterator:
def __init__(self, v1: list[int], v2: list[int]):
# Only lists with remaining elements participate in the rotation.
self.q = deque((vec, 0) for vec in (v1, v2) if vec)
def hasNext(self) -> bool:
return bool(self.q)
def next(self) -> int:
if not self.q:
raise StopIteration
vec, i = self.q.popleft()
val = vec[i]
i += 1
if i < len(vec):
self.q.append((vec, i))
return val
Complexity: Initialization, hasNext(), and next() take \(O(1)\) time; auxiliary space is \(O(1)\) for two lists. For \(k\) lists, initialization takes \(O(k)\) time and auxiliary space is \(O(k)\); each operation remains \(O(1)\).
Comment on my solution¶
Your solution is correct, with \(O(1)\) time per operation and \(O(1)\) auxiliary space. Although hasNext() changes cur_v, repeated checks do not affect the output; the queue approach makes hasNext() read-only and avoids scanning exhausted lists when generalized to \(k\) vectors.
class ZigzagIterator:
def __init__(self, v1: list[int], v2: list[int]):
self.vec = [v1, v2]
self.cur_v = 0
self.pos = [0, 0]
def hasNext(self):
if self.pos[self.cur_v] < len(self.vec[self.cur_v]):
return True
self.cur_v = (self.cur_v + 1) % 2
if self.pos[self.cur_v] < len(self.vec[self.cur_v]):
return True
return False
def next(self):
if not self.hasNext():
raise StopIteration
v = self.cur_v
pos = self.pos[v]
val = self.vec[v][pos]
self.pos[v] += 1
self.cur_v = (self.cur_v + 1) % 2
return val
Extra¶
Problem description¶
Given two vectors of integers v1 and v2, implement an iterator to
return their elements alternately.
Implement the ZigzagIterator class:
ZigzagIterator(List<int> v1, List<int> v2)initializes the object with the two vectorsv1andv2.boolean hasNext()returnstrueif the iterator still has elements, andfalseotherwise.int next()returns the current element of the iterator and moves the iterator to the next element.
Example 1:
Input: v1 = [1,2], v2 = [3,4,5,6]
Output: [1,3,2,4,5,6]
Explanation: By calling next repeatedly until hasNext returns false, the order of elements returned by next should be: [1,3,2,4,5,6].
Example 2:
Example 3:
Constraints:
0 <= v1.length, v2.length <= 10001 <= v1.length + v2.length <= 2000-2^31 <= v1[i], v2[i] <= 2^31 - 1
Follow up: What if you are given k vectors? How well can your code
be extended to such cases?
Clarification for the follow-up question:
The "Zigzag" order is not clearly defined and is ambiguous for k > 2
cases. If "Zigzag" does not look right to you, replace "Zigzag" with
"Cyclic".
Follow-up Example:
Input: v1 = [1,2,3], v2 = [4,5,6,7], v3 = [8,9]
Output: [1,4,8,2,5,9,3,6,7]
What is round-robin scheduling?¶
What is round-robin scheduling?
Round-robin scheduling means giving each active participant one turn, in a fixed repeating order. After the last participant, return to the first.
For your iterator, each list is a participant, and a turn means returning one element:
Once a list is exhausted, it stops participating.
A queue naturally represents whose turn comes next:
Queue before turn Action Queue afterward
[A, B, C] Take from A; A has more [B, C, A]
[B, C, A] Take from B; B is exhausted [C, A]
[C, A] Take from C; C has more [A, C]
The important distinction is one item per turn, not consuming an entire list before moving on. This gives every active list regular opportunities to contribute.
In operating systems, the same idea schedules programs: each runnable program gets a short CPU time slice, then moves to the back of the queue if it still needs to run.