</> MAANG.io
coding interview · 301

Advanced

Advanced coding interview preparation covering complex algorithms, system design, and optimization techniques for senior-level positions.

0/95 solved 0% complete

Expression and Simulation

What is this?

Neither of these is a bracket-matching problem, and that is the point. In one, the "stack" is the recursion itself, carrying enough state that a multiplication arriving later can reach back and correct an addition already made. In the other, a genuine stack of call frames answers a question no single frame can: how much time a function spent excluding everything it called.

flowchart TD A["Expression Add Operators"] --> A1["at each gap: nothing, +, -, or *"] A1 --> A2["carry total AND the previous operand"] A2 --> A3["'*' does: total - prev + prev*num"] B["Exclusive Time of Functions"] --> B1["stack of running call ids"] B1 --> B2["on any event, credit elapsed time
to whatever is on top"] B2 --> B3["a child's time is never the parent's"]

💡 Fun fact: the multiplication trick in Expression Add Operators is a small piece of algebra doing the work of a parser. Having built 1 + 2 you hold total = 3 and prev = 2. A * 3 must not give 9 — precedence says the 2 binds to the 3 first. So you undo the previous operand and re-apply it multiplied: total − prev + prev × num = 3 − 2 + 6 = 7. Two extra terms carried through the recursion replace an entire precedence-climbing parser.

🔓 The 2 problems in this chapter are free. Sign in with Google or Microsoft to start solving.


The one-line idea: carry exactly the state a later decision will need — the previous operand so multiplication can correct itself, or the currently running function so elapsed time is credited to the right owner.


1. Carry the previous operand

def dfs(index, expr, total, prev):
    if index == len(num):
        if total == target: results.append(expr)
        return
    for end in range(index, len(num)):
        if end > index and num[index] == '0':
            break                                  # no leading zeros
        s = num[index:end + 1]
        val = int(s)
        if index == 0:
            dfs(end + 1, s, val, val)              # first operand: no operator
        else:
            dfs(end + 1, expr + '+' + s, total + val,  val)
            dfs(end + 1, expr + '-' + s, total - val, -val)
            dfs(end + 1, expr + '*' + s, total - prev + prev * val, prev * val)

Three things are doing real work. prev is signed — after a -, the previous operand is negative, and multiplication must use that sign or the correction is wrong. The leading-zero guard breaks rather than continues, because once a multi-digit number starts with 0 every longer extension is also invalid. And the first operand takes no operator, which is why it is a separate branch rather than a special case inside the loop.

2. Credit time to whoever is on top

res = [0] * n
stack, prev = [], 0
for log in logs:
    fid, typ, ts = parse(log)
    if typ == 'start':
        if stack: res[stack[-1]] += ts - prev      # credit the caller up to now
        stack.append(fid); prev = ts
    else:
        res[stack.pop()] += ts - prev + 1          # inclusive end timestamp
        prev = ts + 1

The + 1 on an end event and the prev = ts + 1 that follows are the whole difficulty: end timestamps are inclusive, so a function that starts and ends at the same timestamp ran for one unit, not zero. Every off-by-one in this problem lives in those two lines.

The credit-on-start line is what makes the time exclusive: the moment a child begins, the parent stops accruing, and it resumes only when the child returns.


3. A 30-second worked example (the multiplication correction)

num = "123", target = 6. Following the branch that builds 1 + 2 * 3:

"1"        total = 1    prev = 1
"+2"       total = 3    prev = 2
"*3"       total = 3 - 2 + (2 x 3) = 7    prev = 6      ✗ not the target

and the branch that builds 1 * 2 * 3:
"1"        total = 1    prev = 1
"*2"       total = 1 - 1 + (1 x 2) = 2    prev = 2
"*3"       total = 2 - 2 + (2 x 3) = 6    prev = 6      ✓ target reached

Note the second line of the second branch: 1 * 2 correctly gives 2, because the correction subtracts the standalone 1 before multiplying it. Naive left-to-right accumulation would give the same answer here but diverges the moment an addition precedes the multiplication — which is exactly what the first branch shows.


4. Where you'll actually meet this

  • Profilers. Self time versus total time per function is computed from a call log exactly as here; every flame graph depends on it.
  • Distributed tracing. Span accounting in Jaeger or OpenTelemetry subtracts child spans from parents the same way.
  • Billing and metering. Attributing elapsed time to the correct owner when work nests.
  • Spreadsheet and rule engines. Evaluating user-supplied expressions with precedence, without writing a full parser.
  • Puzzle and constraint tooling. Inserting operators to reach a target is the shape of countdown-style solvers.

5. Problems in this chapter

▶ Expression Add Operators

Insert +, -, * between digits so the expression equals a target. Backtracking that carries the running total and the signed previous operand so multiplication can correct the last term.
Pattern: backtracking with carried evaluation state. Target: O(4ⁿ) with pruning, O(n) depth.

▶ Exclusive Time of Functions

Compute each function's self time from a log of start and end events. A stack of running call ids, crediting elapsed time to whatever is on top.
Pattern: call-stack accounting. Target: O(n) time, O(depth) space.


6. Common pitfalls 🚫

  • Forgetting prev is signed. After a subtraction it must be negative, or the multiplication correction produces the wrong total.
  • Allowing leading zeros. "05" is not a valid operand, and the guard must break, not continue.
  • Giving the first operand an operator. It has none; handle it as its own branch.
  • Treating end timestamps as exclusive. They are inclusive — hence the + 1 and the prev = ts + 1.
  • Crediting a child's time to its parent. The parent stops accruing the instant the child starts.
  • Assuming single-digit function ids when parsing the log.
  • Integer overflow in other languages: concatenated operands can exceed 32 bits before the target comparison.

7. Key takeaways

  1. Carry the state a later decision needs. prev exists solely so multiplication can undo an addition.
  2. total − prev + prev × num is precedence handled by algebra rather than by a parser.
  3. Sign travels with the operand, not with the operator.
  4. Inclusive timestamps mean + 1 — and that single convention is where every bug in the timing problem lives.
  5. Exclusive time means the parent pauses while a child runs.
  6. Break on leading zeros, because every longer extension is invalid too.
  7. Why interviewers like it: both look like parsing exercises and are really tests of whether you carry precisely the right state — too little and the answer is wrong, too much and the recursion is unmanageable.

Order: Expression Add Operators → Exclusive Time of Functions.

Calculating Exclusive Execution Time for Function Calls

Core idea: A call stack, and one variable holding the timestamp of the last event. On a start, the currently running function (the stack's top) has been executing since that timestamp, so credit it time − prev; then push the new function and set prev = time. On an end, credit the top time − prev + 1 — the + 1 because an end timestamp is inclusive of that unit while a start timestamp is not — then pop and set prev = time + 1. Recursive calls need no special handling: the same function id simply appears on the stack more than once, and each frame is credited independently. O(n) over the logs.

Problem Statement

Given a list of function execution logs in a single-threaded, non-preemptive CPU where functions can call each other recursively, compute the exclusive time for each function. Exclusive time is the time spent by a function excluding time spent in its subfunction calls. Functions are numbered 0 to n-1. Logs are "id:start:end:timestamp", sorted by timestamp, no simultaneous starts/ends.

Key Details

  • Exclusive time = total time - time spent in direct child calls.
  • Single-threaded, no preemption.
  • Handles recursion.

Sample Examples

Example 1:

Input: n=2, logs=["0:start:0","1:start:2","1:end:5","0:end:6"]
Output: [3,4]
  • Function 0 runs from 0-1 (2 units), calls 1 at 2-5 (4 units), runs 6-6 (1 unit), total exclusive 3.
  • Function 1 exclusive 4.

Sign in to continue reading

The rest of this lesson is available with a free account. Signing in with Google or Microsoft is free.

Sign in to read the full lesson

Sign in to MAANG.io

Use your Google or Microsoft account — no password to remember.

Continue with Google Continue with Microsoft

Please accept the terms above to continue.