Advertisement

Print in Order - LeetCode 1114 Solution

Print in Order - Complete Solution Guide

Print in Order is LeetCode problem 1114, a Easy level challenge. This complete guide provides step-by-step explanations, multiple solution approaches, and optimized code in python3, java, cpp, c.

Problem Statement

Suppose we have a class: public class Foo { public void first() { print("first"); } public void second() { print("second"); } public void third() { print("third"); } } The same instance of Foo will be passed to three different threads. Thread A will call first() , thread B will call second() , and thread C will call third() . Design a mechanism and modify the program to ensure that second() is executed after first() , and third() is executed after second() . Note: We do not know how the threads

Detailed Explanation

This problem throws a curveball often seen in concurrent programming: enforcing a strict order on operations that might otherwise run in arbitrary sequences due to thread scheduling. We're given three methods, `first()`, `second()`, and `third()`, each intended to print a specific word. The catch is that three distinct threads will call these methods concurrently, and we have absolutely no control over when the operating system decides to run Thread A's `first()`, Thread B's `second()`, or Thread C's `third()`. Our task is to modify the `Foo` class so that despite this concurrent invocation, the output *always* reads "first", then "second", then "third".

Solution Approach

The provided solution brilliantly leverages Python's `threading.Event` objects (or similar condition variables/semaphores in other languages) to establish a clear, sequential dependency chain. We initialize two `Event` objects: `first_done` and `second_done`. Think of these as two distinct flags. When `first()` completes its `printFirst()` call, it immediately "raises" the `first_done` flag by calling `set()`. Now, consider `second()`. Before it proceeds to call `printSecond()`, it first attempts to `wait()` on the `first_done` flag. If `first_done` hasn't been `set()` yet (meaning `first()` is still running or hasn't started), `second()`'s thread will simply block and pause execution, patiently waiting. Once `first_done` is `set()`, `second()` unblocks, prints, and then, crucially, `set()`s its own `second_done` flag. This exact pattern repeats for `third()`, which waits for `second_done` before printing. This approach works because it directly translates the "A before B" rule into a "B waits for A's signal" mechanism, ensuring strict ordering without complex locking schemes.

Step-by-Step Algorithm

  1. 1. **Initialization:** The constructor of the `Foo` class initializes the synchronization primitives (events, semaphores, or mutex/condition variables).
  2. 2. **`first()` execution:** The `first()` method executes the `printFirst` function and signals that it's finished (sets the event, releases the semaphore, or notifies the condition variable).
  3. 3. **`second()` execution:** The `second()` method waits for the signal from `first()` (waits for the event to be set, acquires the semaphore, or waits on the condition variable). After the wait, it executes `printSecond` and signals its completion.
  4. 4. **`third()` execution:** The `third()` method waits for the signal from `second()`, executes `printThird`, and completes.

Key Insights

  • **Direct Dependency Signaling with `Event` Objects:** The most potent insight here is using `threading.Event` as a straightforward, one-time signal for task completion. It's perfectly suited for "task A must finish before task B can start" scenarios where you just need a binary state (done/not done) without complex resource sharing or mutual exclusion.
  • **Chaining Completion Flags for Sequential Pipelines:** The solution constructs a simple pipeline by chaining these `Event` objects. `first` signals `second`, and `second` in turn signals `third`. This elegant cascading mechanism ensures that each step only proceeds once its direct predecessor has fully completed its critical action, perfectly satisfying the strict "print in order" requirement.
  • **Efficiency of Blocking `wait()` Operations:** The `wait()` method of `Event` is highly efficient because it causes the calling thread to enter a dormant state, consuming minimal CPU resources until the event is `set()`. This avoids wasteful "busy-waiting" where a thread repeatedly checks a condition in a loop, making the synchronization mechanism both correct and performant.

Complexity Analysis

Time Complexity: O(1)

Space Complexity: O(1)

Topics

This problem involves: Concurrency.

Companies

Asked at: Nvidia.