Skip to content

Latest commit

 

History

History
573 lines (446 loc) · 26.1 KB

02_complexity.md

File metadata and controls

573 lines (446 loc) · 26.1 KB

Arey bacho! Jaldi se class mein aa jao aur dhyan seedhe whiteboard par lagao.

Pichle chapter mein humne seekha tha ki JavaScript ke basics aur computational logic kya hote hain. Aaj hum computer science aur software engineering ke sabse important aur foundational topic ko start karne ja rahe hain—Complexity Analysis & Big-O Notation.

Bohot se bache DSA padhte waqt algorithms ko rat lete hain: "Binary Search ki complexity O(log n) hoti hai," ya "Bubble Sort ki complexity O(n^2) hoti hai." Lekin beta, jab product companies (jaise Google, Microsoft, Amazon) ke interview mein naya code saamne aayega, toh ratta kaam nahi aayega.

Mera waada hai: Aaj ki is masterclass ke baad, tum kisi bhi JavaScript code ko dekhkar uski Time aur Space Complexity khud apne dimaag se nikaal paoge. Bilkul zero se shuru karenge aur interview-level analysis tak lekar chalenge!


1. FOUNDATION: WHY COMPLEXITY MATTERS?

Chalo ek asaan real-life story se shuru karte hain.

Manlo humare do dost hain: Amit aur Sumeet. Dono ko ek array mein se duplicate elements dhoondhne ka task diya gaya.

  • Amit ne ek code likha jismein nested loops use ho rahe the (Double loop).
  • Sumeet ne ek single loop likha jismein usne extra memory (Set) ka use kiya.

Dono ne apne laptops par code run kiya aur dono ka output bilkul correct aaya. Amit bolta hai, "Bhaiya, mera code zyada achha hai kyunki maine koi extra memory allocate nahi ki!" Sumeet kehta hai, "Nahi, mera code fast chal raha hai!"

┌─────────────────────────────────────────────────────────────┐
│                 THE SCALABILITY SHIFT                       │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   Input Size (n)   │  Amit's Time (O(n²)) │ Sumeet's Time   │
│   ─────────────────┼──────────────────────┼──────────────   │
│   10               │   0.1 Millisecond    │ 0.1 Millisecond │
│   1,000            │   10 Milliseconds    │ 1 Millisecond   │
│   100,000          │   1.6 Hours ⏰       │ 10 Milliseconds │
│   1,000,000        │   7 Days! 🔥 (Crash) │ 100 Milliseconds│
│                                                             │
└─────────────────────────────────────────────────────────────┘

Dhyan se dekho beta! Jab input size (n) chota tha, dono code lagbhag same time le rahe the. Lekin jaise-jaise n ki value badhi, Amit ka code collapse ho gaya. Real-world applications mein (jaise Google Maps, Instagram Feed, ya database search), humara data lakhon-karodon mein hota hai. Agar wahan slow code likh diya, toh application crash ho jayegi.

Growth Trend ko analyze kyun karte hain, exact seconds ko kyun nahi?

Hum kabhi bhi computational complexity ko milliseconds ya seconds mein nahi naapte. Kyun?

  1. Hardware Dependency: Ek hi code mere high-end Core-i9 developer machine par 1 second lega, par aapke phone par shayad 10 seconds le.
  2. System Load: background execution, processing state, aur RAM utilization ke basis par run-time badal jata hai.

Isiliye humein ek aise mathematical tool ki zaroorat thi jo hardware-independent ho. Aur yahin par entry hoti hai Asymptotic Analysis ki!


2. THE BIG THREE: ASYMPTOTIC NOTATIONS (THE BOUNDS)

Interviewers aksar poochte hain, "Big-O, Big-Theta aur Big-Omega mein kya farq hai?" Aur 90% bache galat answer dete hain ki "Big-O worst case hai, Omega best case hai." Yeh bilkul galat definition hai!

Asymptotic notations mathematics hain jo functions ke growth bounds ko define karte hain:

  Operations
     ▲
     │          / Upper Bound: Big-O (O) - Maximum rate of growth
     │         /
     │        /─── Actual Algorithm Execution Rate
     │       /
     │      /───── Lower Bound: Big-Omega (Ω)
     │     /
     └─────┴─────────────────────────────────────► Input Size (n)
  1. Big-O (O) — Upper Bound (Roof/Ceiling): Yeh batata hai ki aapka code maximum kitne operations le sakta hai. Is bound ko rate of growth kabhi cross nahi karega. (It guarantees: "Isse bura nahi hoga!")
  2. Big-Omega (Ω) — Lower Bound (Floor): Yeh batata hai ki aapka code kam se kam kitne operations lega hi lega (minimum benchmark). (It guarantees: "Isse behtar nahi chalega!")
  3. Big-Theta (Θ) — Tight Bound: Jab upper bound aur lower bound bilkul same ho jayein, toh use tight bound kehte hain. Yeh exact rate of growth ko lock kar deta hai.

Best, Average, aur Worst Case vs. Notations

Bacho, dhyan se suno. Cases input ke behavior par depend karte hain, jabki Notations mathematical boundaries hain.

Chalo ek simple array mein Linear Search ka example lete hain: let arr =; and target dhoondhna hai.

  • Best Case: Target pehle hi element par mil jaye (target = 10). Operations = 1. Complexities: Upper Bound is O(1), Lower Bound is Ω(1), Tight Bound is Θ(1).
  • Worst Case: Target aakhri index par ho ya array mein ho hi na (target = 100). Pure array ko traverse karna padega. Operations = n. Complexities: Upper Bound is O(n), Lower Bound is Ω(n), Tight Bound is Θ(n).
  • Average Case: Target array ke beech mein kahin mile (around n/2 steps). Constant factor hatane par complexity is Θ(n).

Interview Tip: Jab interview mein tumse complexity poochte hain, toh implicitly wo Worst Case ka Upper Bound pooch rahe hote hain, jise hum Big-O (O) se represent karte hain taaki safety margin assure rahe.


3. COMMON GROWTH RATES (THE SPECTRUM)

SDE interview room ke standard weapon spectrum ko dhyan se dekho:

  O(1)  <  O(log n)  <  O(n)  <  O(n log n)  <  O(n²)  <  O(2ⁿ)  <  O(n!)
  ──────   ─────────   ──────   ───────────   ──────   ──────   ──────
  Fastest  Excellent   Fair        Good        Slow   Terrible  Collapse

1. O(1) — Constant Time (Instant)

Input size chahe 10 ho ya 10 karod, operations hamesha fixed rahenge.

function printFirstElement(arr) {
    console.log(arr); // O(1) space and time lookup
}

2. O(log n) — Logarithmic Time (Very Fast)

Har step par aapka problem space directly aadha (half) ho jata hai.

  • Example: Sorted array par Binary Search chalana.
// Binary search cuts the search space in half with every iteration.
  • Math Intuition: Agar n = 16 hai, toh operations honge: 16 → 8 → 4 → 2 → 1 (sirf 4 steps, i.e., log_2 16 = 4).

3. O(n) — Linear Time (Fair)

Operations directly input size n ke proportion mein badhte hain.

function findElement(arr, target) {
    for (let i = 0; i < arr.length; i++) { // Runs 'n' times in worst case
        if (arr[i] === target) return true;
    }
    return false;
}

4. O(n log n) — Linearithmic Time (Good)

Efficient sorting algorithms (jaise Merge Sort aur Quick Sort) is path par chalte hain.

  • Intuition: Problem space ko log n levels mein toda jata hai, aur har level par complete boundary range par O(n) work chalaya jata hai.

5. O(n^2) — Quadratic Time (Slow)

Nested loops, jahan outer loop ke har single operation ke liye inner loop poora n times chalta hai.

function printAllPairs(arr) {
    for (let i = 0; i < arr.length; i++) {       // Runs N times
        for (let j = 0; j < arr.length; j++) {   // Runs N times
            console.log(arr[i], arr[j]);          // O(1) operation
        }
    }
}

6. O(2^n) — Exponential Time (Terrible)

Har additional element ke badhte hi computations double ho jati hain.

  • Example: Naive Recursive Fibonacci without memoization.

7. O(n!) — Factorial Time (Complete Collapse)

Sabse dangerous rate. Kisi set ke saare possible permutations generate karna.

  • Scale: n = 10 ke liye computations = 3,628,800. n = 100 ke liye computer crash!

4. CODE ANALYSIS: THE ARITHMETIC RULES

Dimaag ki bati jalao beta! Code ko dekhte hi complexity nikaalne ke do bade rules hain:

Rule 1: The Addition Rule — Sequential Steps

Agar operations ek ke baad ek horizontal line mein chal rahe hain (sequential), toh unki complexities add hoti hain.

\[Total Complexity = O(f(n) + g(n))\]

function sequentialTasks(n) {
    // Task 1: Runs 'n' times
    for (let i = 0; i < n; i++) {
        console.log(i); // O(n)
    }

    // Task 2: Runs 'n' times
    for (let j = 0; j < n; j++) {
        console.log(j); // O(n)
    }
}
  • Step-by-step Derivation:
    • Task 1 operations count = n
    • Task 2 operations count = n
    • Total Operations = n + n = 2n
    • Complexity = O(2n)
    • Constant '2' remove karne par: O(n).

Rule 2: The Multiplication Rule — Nested Steps

Agar ek loop ke andar dusra loop nested hai, toh unki complexities multiply hoti hain.

\[Total Complexity = O(f(n) × g(n))\]

function nestedTasks(n) {
    for (let i = 0; i < n; i++) {         // Outer Loop runs 'n' times
        for (let j = 0; j < n; j++) {     // Inner Loop runs 'n' times
            console.log(i, j);             // O(1) task
        }
    }
}
  • Step-by-step Derivation:
    • Outer loop i=0 par chala → Inner loop runs n times.
    • Outer loop i=1 par chala → Inner loop runs n times.
    • ...
    • Outer loop runs n times total.
    • Total operations = n × n = n^2.
    • Final Complexity = O(n^2).

Handling Independent Inputs

Agar inputs alag-alag arrays se aa rahe hain (jaise arr1 aur arr2), toh kabhi bhi use blindly n ya n^2 mat bolo! Inhe alag variables se denote karo (jaise n and m).

Scenario A: Sequential Independent Loops

function sequentialIndependent(arr1, arr2) {
    for (let i = 0; i < arr1.length; i++) { // O(n) where n = arr1.length
        console.log(arr1[i]);
    }
    for (let j = 0; j < arr2.length; j++) { // O(m) where m = arr2.length
        console.log(arr2[j]);
    }
}
  • Final Complexity: O(n + m) Time | O(1) Space.

Scenario B: Nested Independent Loops

function nestedIndependent(arr1, arr2) {
    for (let i = 0; i < arr1.length; i++) {       // Runs N times
        for (let j = 0; j < arr2.length; j++) {   // Runs M times
            console.log(arr1[i], arr2[j]);
        }
    }
}
  • Final Complexity: O(n × m) Time | O(1) Space.

5. RECONSTRUCTING LOOP STRUCTURES

Chalo whiteboard par complex loop structures ko step-by-step trace karte hain:

Loop Type A: Multi-Step Increment / Decrement (i += k, i -= k)

function jumpLoop(n) {
    let count = 0;
    for (let i = 0; i < n; i += 3) { // Jump is constant 3
        count++;
    }
    return count;
}
  • Operation Count: Loop variable i values lega: 0, 3, 6, 9, ... up to n.
  • Pattern: Total loop steps = ≈ n/3.
  • Simplification: 1/3 × n → Constants drop karne par, total complexity bachti hai: O(n) Time.

Loop Type B: Logarithmic / Multiplication-Based Update (i *= 2, i /= 2)

function logarithmicLoop(n) {
    for (let i = 1; i < n; i *= 2) { // Variable multiplied by 2
        console.log(i);
    }
}
  • Dry Run values of i:
    • Step 1: i = 1
    • Step 2: i = 2
    • Step 3: i = 4
    • Step 4: i = 8
    • Step k: i = 2^{k-1}
  • Derivation: Loop tab tak chalega jab tak i < n hai. Yaani 2^{k-1} < n. Dono side standard base-2 log lagane par: k-1 ≈ log_2 n => k ≈ log_2 n.
  • Final Time Complexity: O(log n).

Loop Type C: Dependent / Triangular Nested Loops (AP Series)

Yeh interviews ka sabse bade traps mein se ek hai!

function printTriangle(arr) {
    let n = arr.length;
    for (let i = 0; i < n; i++) {         // Outer runs N times
        for (let j = 0; j <= i; j++) {    // Inner depends on outer loop value 'i'
            console.log(arr[i], arr[j]);
        }
    }
}
  • Dry Run Trace Table:

    • i = 0 → Inner loop runs 1 time.
    • i = 1 → Inner loop runs 2 times.
    • i = 2 → Inner loop runs 3 times.
    • ...
    • i = n-1 → Inner loop runs n times.
  • Math Summation: Total steps = 1 + 2 + 3 + ... + n.

    This is a standard Arithmetic Progression (AP) series:

    \[Total Sum = n(n+1)/2 = n^2 + n/2\]

  • Simplification: Drop lower-order term (n) and ignore constant division factor (1/2):

    \[n^2 + n/2 → n^2/2 → O(n^2)\]

  • Final Time Complexity: O(n^2) Time | O(1) Space.


6. THE SIMPLIFICATION CODE OF CONDUCT

Big-O likhte waqt humesha is standard protocol ko follow karo:

  1. Rule of Dominance (Drop Lower-Order Terms): Pure equation mein jo component sabse tez grow karega, sirf use hi select karenge.

    • O(n^2 + n) → n^2 is dominant → O(n^2)
    • O(2^n + n^5) → 2^n scales far faster → O(2^n)
    • O(n log n + n + 100) → n log n dominates → O(n log n)
  2. Rule of Constants: Constant factors ko ignore karo kyunki extreme high values par constants insignificant ho jate hain.

    • O(2n) → O(n)
    • O(100n^2 + 500) → O(n^2)

7. SPACE COMPLEXITY (THE PHYSICAL FOOTPRINT)

Dhyan se suno bacho! Space Complexity ka matlab hai: "Jaise-jaise input size n badhega, waise-waise aapka program computer ke RAM memory mein kitni extra space consume karega?"

\[Total Space Complexity = Input Space + Auxiliary Space\]

  • Input Space: original input (jaise size-n array) ko memory mein store karne ke liye required space.
  • Auxiliary Space: Wo extra space/memory jo program ne problem ko solve karne ke liye khud dynamically allocate ki (e.g., helper arrays, dynamic hash maps, recursion stack frame pointers). Interviews mein hamesha focus Auxiliary Space par hota hai!

In-Place Algorithms

Aise algorithms jo bina kisi extra/dynamic helper arrays ke original input array ko directly memory coordinates par update/overwrite karte hain, unka Auxiliary Space hamesha O(1) constant hota hai.

function multiplyArrayInPlace(array, multiplier) {
    for (let i = 0; i < array.length; i += 1) {
        array[i] *= multiplier; // Direct modification, no extra variables
    }
    return array; // Auxiliary space: O(1)
}

8. RECURSION COMPLEXITY & THE CALL STACK

Bachon ko sabse zyada darr recursion se lagta hai. Lekin darrne ki koi baat nahi.

Jab bhi ek recursive function run hota hai, computer us function call ko incomplete maankar ek frame structure banata hai, aur use memory ke Call Stack par hold karke save rakhta hai jab tak base case hit na ho jaye.

Example A: Factorial Recursion (Depth N)

export function factorial(number) {
    if (number === 0) return 1; // Base case
    return factorial(number - 1) * number; // Recursive call
}

Step-by-Step Recursion Tree:

           factorial(4)   ──►  Holds in call stack
                │
           factorial(3)   ──►  Holds in call stack
                │
           factorial(2)   ──►  Holds in call stack
                │
           factorial(1)   ──►  Holds in call stack
                │
           factorial(0)   ──►  Returns 1 (Base case reached!)
  • Time Analysis: Total call stack frames N linear chain mein execute hote hain, aur har frame ke andar sirf basic constant math calculations hote hain → Time Complexity: O(n).
  • Space Analysis: Jab tak base case execution resolve nahi hota, computer ko pure sequence call stacks chain variables ko memory par save rakhna padta hai → Space Complexity: O(n) Auxiliary.

9. AMORTIZED COMPLEXITY INTUITION

Manlo tum stadium mein match dekhne gaye aur stadium mein seats limit complete ho gayi. Ab stadium ko double capacity ka banana hai, toh poore spectators ko nayi double sized building mein shift karna padega. Is specific migration step mein bohot cost lagegi.

JavaScript Arrays internally dynamic size arrays use karte hain.

Step 1: Size is 2. Elements: [ 10, 20 ]
Step 2: Want to insert 30. No space!
Step 3: Array creates a NEW block of size 2 * 2 = 4 in background.
Step 4: Copies old elements: [ 10, 20, 30, _ ] (Expensive operation!)

Amortized Intuition:

  • Jab size limit complete hoti hai, tab insertion costly high execution step leta hai (size scaling up O(n)).
  • Lekin yeh resizing step hamesha nahi hota! Yeh tabii hota hai jab space complete saturate ho (e.g. at size 2, 4, 8, 16).
  • Baaki bache saare standard insertion steps hamesha O(1) constant fast hote hain.
  • Agar hum in expensive shiftings aur standard inserts ko distribute karke average out karein, toh average cost hamesha O(1) constant hi aati hai. Is average overall consistency cost ko Amortized Complexity kehte hain.

10. JAVASCRIPT BUILD-IN ENGINE COMPLEXITY CHART

Modern engines (jaise Chrome ka V8 engine) dynamic memory management ko optimized low-level machine execution se handle karte hain. Unki under-the-hood complexities ko bilkul dhyan se dimaag mein betha lo:

JavaScript Operations Time Complexity Auxiliary Space Architectural Mechanism / Reason
Array Access (arr[i]) O(1) O(1) Direct pointer arithmetic; packed contiguous block offset jumping.
Array End Insert (push()) O(1) Amortized O(1) Direct append at tail address, dynamic scaling resized occasionally.
Array End Remove (pop()) O(1) O(1) Pointer updates index boundaries instantly.
Array Start Insert (unshift()) O(n) O(1) Pure array elements ko right shift karke index 0 free kiya jata hai.
Array Start Remove (shift()) O(n) O(1) Pure suffix elements left shift hote hain index spaces ko sync karne.
Array Segment Copy (slice(s, e)) O(k) (k = e-s) O(k) Sub-segment memory block copies banakar dynamic result allocate kiya jata hai.
Array In-place Delete (splice()) O(n) O(1) Suffix index lines shifting and rebuilding overheads inside V8.
Searching (includes() / indexOf()) O(n) O(1) Sequential linear scanning loops check sequentially.
Map.get() / Map.has() O(1) Average O(1) Internal dynamic hash-buckets indexing maps directly.
Set.add() / Set.has() O(1) Average O(1) Collision balanced dynamic hash buckets lookup maps.
Sorting (Array.sort()) O(n log n) O(log n) or O(n) Implementation dependent; usually Timsort or hybrid Quick-Merge Sort optimizations.

11. STRATEGY BLOCK: "CODE DEHKAR COMPLEXITY KAISE NIKAALEIN?"

Interview room mein baithe ho, aur interviewer ne naya code diya. In teen steps ka checklist follow karo:

              COMPLEXITY DIAGNOSIS CHECKLIST
                            │
         ┌──────────────────┴──────────────────┐
  Step 1: Variable Growth               Step 2: Loop Nesting
  Check update triggers inside          Are loops sequential (add)
  the block: i++, i*=2 etc.             or nested (multiply)?
                            │
                            ▼
                     Step 3: Dominance
                     Discard constants and drop lower-order terms.
  1. Step 1: Check the loop updater first:
    • i++ runs n times → O(n).
    • i += 2 runs n/2 times → O(n).
    • i *= 2 or i /= 2 reduces the problem dynamically → O(log n).
  2. Step 2: Identify Loop Interdependency (Sequential vs. Nested):
    • Do independent loops hain sequential order mein? Sum them up: O(n + m).
    • Nested loops with nested execution? Multiply bounds: O(n × m).
  3. Step 3: Clean & Simplify:
    • Constant offsets, initial assignments (O(1) blocks) ko drops karo aur maximum growth class term ko answer lock karo.

12. PRACTICE CORNER (EASY → MEDIUM → HARD)

🚀 Whiteboard bilkul ready hai bacho! Saare examples ko dhyan se dekho. Pehle solution par haath rakh lo aur khud complexity trace karo, phir correct explanations padho!


Practice Problem 1 (Easy): Alternating Jump Sequence

Analyze the code, count operations, and derive time/space complexity:

function alternateJumpSum(arr) {
    let sum = 0;                          // O(1)
    let n = arr.length;                   // O(1)
    for (let i = 0; i < n; i += 2) {      // Jumping updater
        sum += arr[i];                    // O(1) mathematical operation
    }
    return sum;                           // O(1)
}

🧠 Step-by-Step Diagnostic Check:

  1. Operation Count: Loop variable i increments step sequence bounds: 0, 2, 4, 6, ... up to n. Total operations in loop = n/2 steps.
  2. Derivation: Mathematical equation for total steps: Steps = n/2 = 0.5n.
  3. Simplify: Drop constant factor 0.5 → O(n) Time.
  4. Space Check: Simple primitives used, no dynamic arrays allocations → O(1) space.
  5. Final Complexities: Time Complexity: O(n) | Space Complexity: O(1) Auxiliary.

Practice Problem 2 (Medium): Nested Half-Step Grid

Analyze the code, count operations, and derive complexities:

function nestedHalfStepSum(n) {
    let result = 0;                       // O(1)
    for (let i = 0; i < n; i++) {         // Outer Loop: Runs N times
        for (let j = n; j > 0; j = Math.floor(j / 2)) { // Inner Loop
            result += i + j;
        }
    }
    return result;
}

🧠 Step-by-Step Diagnostic Check:

  1. Outer Loop Pattern: Runs exactly n times linearly.

  2. Inner Loop Pattern: Starts at j = n, updates via division: j = ⌊ j / 2 ⌋. This means the inner loop cuts problem size in half each step → Runs log_2 n times.

  3. Operations Summation: Kyunki dono loops nested hain, hum nested multiplication rules follow karenge:

    \[Total Operations = n × log_2 n = n log n\]

  4. Final Complexities: Time Complexity: O(n log n) | Space Complexity: O(1) Auxiliary.


Practice Problem 3 (Hard): Fibonacci Tree Decomposition

Analyze the code and trace complexities step-by-step:

function fibonacci(n) {
    if (n <= 1) return n; // Base case
    return fibonacci(n - 1) + fibonacci(n - 2); // Double recursion branching
}

🧠 Step-by-Step Diagnostic Check:

  1. Recursion Tree Analysis: Har ek non-base case execution call 2 further child sub-calls triggers karti hai.
                          fib(4)
                         /      \
                     fib(3)      fib(2)
                    /     \     /     \
                 fib(2)  fib(1)fib(1) fib(0)
    
  2. Call Levels/Tree Depth: Depth reaches N.
  3. Total Operations: At each level d, the number of branches doubles (2^0 → 2^1 → 2^2 ... up to level n). Total sum of geometric progression operations of tree vertices yields: O(2^n).
  4. Space (Stack Height): At any given moment, the deepest active execution branch on the call stack is N. Call stack resolves sequentially from left branches first → Maximum simultaneous frames on call stack is N → O(n) Auxiliary Space.
  5. Final Complexities: Time Complexity: O(2^n) | Space Complexity: O(n) Auxiliary.

12. SDE TRAPS & COMMON MISTAKES

Dhyan se dekho beta! Interviewers in traps par bacho ko trap karte hain:

  1. Galti 1: Blindly assuming every nested loop is O(n^2)!
    • Trap: for (let i = 0; i < n; i++) nested with for (let j = 1; j < n; j *= 2).
    • Reality: Is loop ki total complexity O(n log n) hai, quadratic nahi! Loop updaters ko dhyan se check karo.
  2. Galti 2: Confusing sequential loops addition with nested multiplication!
    • Trap: for (let i = 0; i < n; i++) followed sequentially by another for (let j = 0; j < n; j++).
    • Reality: Unki complexities add hongi: O(n) + O(n) = O(2n) → O(n), multiply hokar quadratic nahi ho jayegi.
  3. Galti 3: Forgetting Recursion Call Stack Frame space footprint!
    • Trap: "Sir, factorial function runs in O(N) time and O(1) space because no arrays are used!"
    • Reality: Recursive call stack memory stack blocks hold memory locations on call stack proportional to recursion depth, making space complexity O(n) Auxiliary.

13. COMPLEXITY ANALYSIS CHEAT SHEET

Yeh standard metrics table bilkul dimaag mein chipka lo:

Notation Class Performance Category Worst Computations for n=100 Common Algorithm Match
O(1) Instant / Constant 1 computation Direct index check, push/pop.
O(log n) Excellent / Logarithmic 6 computations Sorted Array Binary Search.
O(n) Fair / Linear 100 computations Linear Search, Array traversal.
O(n log n) Good / Linearithmic 600 computations Merge Sort, Quick Sort (average).
O(n^2) Slow / Quadratic 10,000 computations Nested loops, Bubble / Selection Sort.
O(2^n) Terrible / Exponential 1.26 × 10^{29} computations Naive Recursive Fibonacci algorithm.
O(n!) Collapse / Factorial 9.3 × 10^{157} computations Set partitions, String permutations generation.

CHAPTER END SUMMARY

Completed Concepts:

  • Time vs Space Complexity & Scalability trends.
  • Mathematical bounds definition of Big-O, Big-Omega, and Big-Theta.
  • Drop constants and non-dominating loops protocol.
  • V8 engine specifications of built-in Array, Map and Set functions.
  • Recursion Time & Call Stack space analysis.
  • Amortized scaling analysis of dynamic arrays.

Mastered Patterns:

  • The Arithmetic Rules: Sequential additions vs Nested loop multiplications.
  • Logarithmic identification: Multipliers & Divisors loops scale logarithmic bounds.
  • Dependent triangles: Arithmetic summation summation models simplification.

⚠️ Common Mistakes: Forgetting call stack variables allocations during recursion depth tracing, and blindly naming independent variables as O(n^2) instead of O(n × m).