You are given the same kind of stack samples as in Part 1, plus an integer N. A stack frame should produce events only if that exact frame stays visible in the same depth position for at least N consecutive samples. Use the same frame identity rule as Part 1: a frame continues only while it remains inside the longest common prefix with the previous sample, so recursive calls with the same function name at different depths are still distinct frames. In this version, use the timestamp of the first sample in the qualifying streak as the start time. That means a frame may be confirmed only later, but its emitted start event should be backdated to when it was first seen. If a frame disappears before reaching N consecutive samples, emit nothing for it. If a started frame later disappears, emit one end event at the disappearance timestamp. For multiple ends at the same timestamp, emit inner frames before outer frames. For multiple starts at the same timestamp, emit outer frames before inner frames. Frames still on the stack in the last sample are unfinished, so they do not get end events.
Examples
Input: ([(1, ['A']), (2, ['A']), (3, ['A', 'B']), (4, ['A', 'B']), (5, ['A']), (6, [])], 2)
Expected Output: [('start', 1, 'A'), ('start', 3, 'B'), ('end', 5, 'B'), ('end', 6, 'A')]
Explanation: A is present in samples 1 and 2, so it qualifies and gets start time 1. B is first seen at 3 and confirmed at 4, so its start time is backdated to 3.
Input: ([(1, ['A']), (2, ['A', 'B']), (3, ['A']), (4, [])], 2)
Expected Output: [('start', 1, 'A'), ('end', 4, 'A')]
Explanation: B appears in only one sample, so it never reaches the threshold and produces no events.
Input: ([(1, ['A']), (2, ['A', 'A']), (3, ['A', 'A']), (4, ['A']), (5, ['A']), (6, [])], 2)
Expected Output: [('start', 1, 'A'), ('start', 2, 'A'), ('end', 4, 'A'), ('end', 6, 'A')]
Explanation: The outer A qualifies starting at 1. The recursive inner A is a different frame at a deeper depth; it qualifies starting at 2 and ends earlier.
Input: ([(10, []), (20, ['main']), (30, ['main', 'x']), (40, ['main'])], 1)
Expected Output: [('start', 20, 'main'), ('start', 30, 'x'), ('end', 40, 'x')]
Explanation: With n = 1, every observed frame is emitted immediately, so this behaves like Part 1.
Input: ([], 3)
Expected Output: []
Explanation: No samples means no events.
Solution
def solution(samples, n):
events = []
active = [] # [name, first_seen_timestamp, streak_length, started]
for timestamp, stack in samples:
stack = list(stack)
lcp = 0
limit = min(len(active), len(stack))
while lcp < limit and active[lcp][0] == stack[lcp]:
lcp += 1
for depth in range(len(active) - 1, lcp - 1, -1):
name, first_ts, streak, started = active[depth]
if started:
events.append((timestamp, 0, -depth, 'end', name))
active = active[:lcp]
for depth in range(lcp):
active[depth][2] += 1
if not active[depth][3] and active[depth][2] >= n:
active[depth][3] = True
events.append((active[depth][1], 1, depth, 'start', active[depth][0]))
for depth in range(lcp, len(stack)):
started = (n == 1)
frame = [stack[depth], timestamp, 1, started]
active.append(frame)
if started:
events.append((timestamp, 1, depth, 'start', stack[depth]))
events.sort(key=lambda e: (e[0], e[1], e[2]))
return [(kind, timestamp, name) for timestamp, _, _, kind, name in events]Time complexity: O(S + E log E), where S is the total number of stack entries across all samples and E is the number of emitted events. Space complexity: O(D + E), where D is the maximum stack depth.