AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

Grade 9 AI & Computer Science Practice Questions — Set 3

20 questions from the Grade 9 bank, each with its answer and a full explanation. Set 3 of 11 · 221 questions in this grade.

Reading is revision; testing is practice. Take the same questions as a timed quiz →

Question 41 · Graph algorithms · hard

A software team models an unweighted directed graph with V=8 vertices and E=12 edges using an adjacency list, then runs BFS starting from vertex A to find the shortest path to vertex H. What is the time complexity of this BFS traversal?

  1. BFS runs in O(V+E) = O(8+12) = O(20) for this graph, using a FIFO queue where each vertex is dequeued in O(1) time, because each vertex and edge is visited exactly once during traversal
  2. BFS runs in O(V^2) = O(64) because it uses a nested loop checking all vertex pairs regardless of edge count
  3. The algorithm runs in O(E^2) = O(144) because each edge comparison requires scanning all other edges
  4. Time complexity is O(2^V) = O(256) because BFS explores all possible paths using backtracking

Answer: A. BFS runs in O(V+E) = O(8+12) = O(20) for this graph, using a FIFO queue where each vertex is dequeued in O(1) time, because each vertex and edge is visited exactly once during traversal

ExplanationStep-by-step BFS execution: (1) Initialize: visited set (empty), queue with start vertex A. (2) Process vertices: each vertex is visited exactly once, contributing O(V) = O(8). (3) For each vertex, examine all adjacent edges: total edge examinations = E = 12. (4) Therefore, total complexity = O(V+E) = O(8+12) = O(20). Queue operations (enqueue/dequeue) each cost O(1). Space complexity: O(V) = O(8) for the visited set and queue.

Question 42 · Graph algorithms · hard

Given a unweighted directed graph with V=10 vertices and E=15 edges, represented as an adjacency list: const graph = new Map(); graph.set('S', ['B','C','D']); — implement DFS and analyze its time complexity for finding path from S to G?

  1. DFS runs in O(V+E) = O(10+15) = O(25) for this graph, using a stack (or the recursive call stack) where each vertex is pushed and popped in O(1) time, because each vertex and edge is visited exactly once during traversal
  2. DFS runs in O(V^2) = O(100) because it uses a nested loop checking all vertex pairs regardless of edge count
  3. The algorithm runs in O(E^2) = O(225) because each edge comparison requires scanning all other edges
  4. Time complexity is O(2^V) = O(1024) because DFS explores all possible paths using backtracking

Answer: A. DFS runs in O(V+E) = O(10+15) = O(25) for this graph, using a stack (or the recursive call stack) where each vertex is pushed and popped in O(1) time, because each vertex and edge is visited exactly once during traversal

ExplanationStep-by-step DFS execution: (1) Initialize: visited set (empty), stack (or recursion) starting at vertex S. (2) Process vertices: each vertex is pushed and visited exactly once, contributing O(V) = O(10). (3) For each vertex popped off the stack, examine all adjacent edges to decide which unvisited neighbors to push next: total edge examinations = E = 15. (4) Therefore, total complexity = O(V+E) = O(10+15) = O(25). Stack operations (push/pop), or equivalently each recursive call, each cost O(1). Space complexity: O(V) = O(10) for the visited set plus the stack/recursion depth. Because the graph has 15/10 = 1.5 avg edges per vertex, it is sparse, making adjacency list representation optimal.

Question 43 · Graph algorithms · hard

Given a weighted directed graph with V=6 vertices and E=9 edges, represented as an adjacency list: const graph = new Map(); graph.set('A', [{node:'B',weight:3},{node:'C',weight:7}]); — implement Dijkstra and analyze its time complexity for finding path from A to F?

  1. Dijkstra runs in O((V+E)logV) = O(15 × log 6) ≈ O(15 × 3) = O(45) for this graph, using a min-heap priority queue where each vertex is extracted in O(log V) = O(log 6) ≈ O(3) time, because each of the V vertex extractions and each of the E decrease-key operations costs O(log V)
  2. Dijkstra runs in O(V^2) = O(36) because it uses a nested loop checking all vertex pairs regardless of edge count
  3. The algorithm runs in O(E^2) = O(81) because each edge comparison requires scanning all other edges, as if Dijkstra used a brute-force pairwise edge-relaxation approach instead of a priority queue
  4. Time complexity is O(2^V) = O(64) because Dijkstra explores all possible paths using backtracking

Answer: A. Dijkstra runs in O((V+E)logV) = O(15 × log 6) ≈ O(15 × 3) = O(45) for this graph, using a min-heap priority queue where each vertex is extracted in O(log V) = O(log 6) ≈ O(3) time, because each of the V vertex extractions and each of the E decrease-key operations costs O(log V)

ExplanationStep-by-step Dijkstra execution: (1) Initialize: visited set (empty), distance array with Infinity for all vertices except source = 0. (2) Process vertices: each vertex is visited exactly once, contributing O(V) = O(6). (3) For each vertex, examine all adjacent edges: total edge examinations = E = 9. (4) Therefore, total complexity = O(V+E) = O(6+9) = O(15). With min-heap: extract-min costs O(log V) = O(log 6) = O(3.0), called V times = O(V log V). Decrease-key called E times = O(E log V). Total: O((V+E) log V) = O(15 × 3) = O(45). Space complexity: O(V) = O(6) for visited set + queue/stack. Because the graph has 9/6 = 1.5 avg edges per vertex, it is sparse, making adjacency list representation optimal.

Question 44 · Graph algorithms · hard

Given an unweighted directed graph with V=7 vertices and E=8 edges, represented as an adjacency list: ```js const graph = new Map(); graph.set('A', ['B', 'C']); graph.set('B', ['D']); graph.set('C', ['D', 'E']); graph.set('D', ['F']); graph.set('E', ['F']); graph.set('F', ['G']); graph.set('G', []); ``` Implement Topological Sort and analyze its time complexity for finding the result?

  1. Topological Sort runs in O(V+E) = O(7+8) = O(15) for this graph, using a FIFO queue where each vertex is dequeued in O(1) time, because each vertex and edge is visited exactly once during traversal
  2. Topological Sort runs in O(V^2) = O(49) because it uses a nested loop checking all vertex pairs regardless of edge count
  3. The algorithm runs in O(E^2) = O(64) because each edge comparison requires scanning all other edges
  4. Time complexity is O(2^V) = O(128) because Topological Sort explores all possible paths using backtracking

Answer: A. Topological Sort runs in O(V+E) = O(7+8) = O(15) for this graph, using a FIFO queue where each vertex is dequeued in O(1) time, because each vertex and edge is visited exactly once during traversal

ExplanationStep-by-step Topological Sort execution using Kahn's algorithm: (1) Compute in-degrees for all vertices — A has in-degree 0, so it starts in the queue. (2) Process vertices: each of the V=7 vertices is dequeued and visited exactly once, contributing O(V) = O(7). (3) For each dequeued vertex, examine all its outgoing edges to decrement neighbors' in-degrees: total edge examinations across the whole run = E = 8. (4) Therefore, total complexity = O(V+E) = O(7+8) = O(15). Queue operations (enqueue/dequeue) each cost O(1). Space complexity: O(V) = O(7) for the in-degree array plus the queue. Because the graph has 8/7 ≈ 1.1 average edges per vertex, it is sparse, making the adjacency-list representation optimal.

Question 45 · Graph algorithms · hard

Given a unweighted directed graph with V=12 vertices and E=20 edges, represented as an adjacency list: const graph = new Map(); graph.set('X', ['B','C','D']); — implement BFS shortest path and analyze its time complexity for finding path from X to Z?

  1. BFS shortest path runs in O(V+E) = O(12+20) = O(32) for this graph, using a FIFO queue where each vertex is dequeued in O(1) time, because each vertex and edge is visited exactly once during traversal
  2. BFS shortest path runs in O(V^2) = O(144) because it uses a nested loop checking all vertex pairs regardless of edge count
  3. The algorithm runs in O(E^2) = O(400) because each edge comparison requires scanning all other edges
  4. Time complexity is O(2^V) = O(4096) because BFS shortest path explores all possible paths using backtracking

Answer: A. BFS shortest path runs in O(V+E) = O(12+20) = O(32) for this graph, using a FIFO queue where each vertex is dequeued in O(1) time, because each vertex and edge is visited exactly once during traversal

ExplanationStep-by-step BFS shortest path execution: (1) Initialize: visited set (empty), queue with start vertex X. (2) Process vertices: each vertex is visited exactly once, contributing O(V) = O(12). (3) For each vertex, examine all adjacent edges: total edge examinations = E = 20. (4) Therefore, total complexity = O(V+E) = O(12+20) = O(32). Queue operations (enqueue/dequeue) each cost O(1). Space complexity: O(V) = O(12) for visited set + queue/stack. Because the graph has 20/12 = 1.7 avg edges per vertex, it is sparse, making adjacency list representation optimal.

Question 46 · Graph algorithms · hard

Given a unweighted directed graph with V=9 vertices and E=14 edges, represented as an adjacency list: const graph = new Map(); graph.set('A', ['B','C','D']); — implement DFS cycle detection and analyze its time complexity for finding the result?

  1. For DFS cycle detection, this graph yields O(V+E) = O(9+14) = O(23), since it uses a stack (recursion stack) where each vertex push/pop takes O(1) time and each vertex and edge is visited exactly once during traversal
  2. This same DFS approach would instead run in O(V^2) = O(81) because it uses a nested loop checking all vertex pairs regardless of edge count
  3. The algorithm runs in O(E^2) = O(196) because each edge comparison requires scanning all other edges
  4. Time complexity is O(2^V) = O(512) because DFS cycle detection explores all possible paths using backtracking

Answer: A. For DFS cycle detection, this graph yields O(V+E) = O(9+14) = O(23), since it uses a stack (recursion stack) where each vertex push/pop takes O(1) time and each vertex and edge is visited exactly once during traversal

ExplanationFor DFS-based cycle detection, here is the step-by-step execution: (1) Initialize an empty visited set and an empty recursion stack, then start the traversal at vertex A. (2) Each vertex is visited exactly once as DFS recurses into its unvisited neighbors, contributing O(V) = O(9). (3) For each vertex, all its adjacent edges are examined once, giving total edge examinations = E = 14; a cycle exists whenever DFS reaches a vertex that is already present in the current recursion stack (a back edge). (4) Therefore, total time complexity = O(V+E) = O(9+14) = O(23). Each stack push/pop operation costs O(1) time. Space complexity is O(V) = O(9) for the visited set plus the recursion stack. Since this graph has 14/9 = 1.6 average edges per vertex, it is sparse, making the adjacency-list representation optimal.

Question 47 · Graph algorithms · hard

A weighted directed graph has V=5 vertices and E=8 edges, some with negative weights (which rules out Dijkstra), represented as an adjacency list: ```javascript const graph = new Map(); graph.set('S', [{node:'B', weight:3}, {node:'C', weight:7}]); // ...6 more edges among B, C, D, T ``` Bellman-Ford is run from source S to compute shortest paths to all other vertices. Which statement correctly explains its time complexity?

  1. Bellman-Ford runs in O(V×E): it relaxes all E edges in each of V−1 passes over the graph, since after k passes the shortest path using at most k edges is guaranteed correct and any simple path has at most V−1 edges — here (5−1)×8 = 32 relaxation operations, which is O(5×8) = O(40)
  2. Using a min-heap priority queue to always relax the vertex with the smallest tentative distance first, Bellman-Ford achieves O((V+E) log V), extracting each vertex once in O(log V) time — here O((5+8) log 5) ≈ O(30)
  3. Like BFS, Bellman-Ford only needs to examine each vertex and edge once, giving O(V+E) = O(13) to compute shortest distances from the source
  4. A brute-force all-pairs comparison of every pair of vertices via a distance matrix is how Bellman-Ford achieves O(V²) = O(25), so the edge count E never enters the calculation

Answer: A. Bellman-Ford runs in O(V×E): it relaxes all E edges in each of V−1 passes over the graph, since after k passes the shortest path using at most k edges is guaranteed correct and any simple path has at most V−1 edges — here (5−1)×8 = 32 relaxation operations, which is O(5×8) = O(40)

ExplanationBellman-Ford's core invariant: after relaxing every edge k times, all shortest paths using at most k edges are guaranteed correct. Since a simple shortest path in a graph with V vertices has at most V−1 edges, the algorithm must relax all E edges across V−1 separate passes to guarantee convergence. Here that's (V−1)×E = (5−1)×8 = 32 relaxation operations, which is O(V×E) = O(5×8) = O(40), since V−1 passes is the same asymptotic order as V passes. The min-heap claim wrongly imports Dijkstra's mechanism — Bellman-Ford never uses a priority queue, and a greedy min-heap approach actually fails on negative edge weights, which is precisely the case Bellman-Ford exists to handle. The single-pass BFS-like claim (O(V+E)) only suffices for a DAG processed in topological order; on a general graph, one pass can miss the true shortest path, so all V−1 passes are required. The brute-force distance-matrix claim confuses Bellman-Ford with a pairwise all-pairs approach — Bellman-Ford never builds or scans a V×V matrix; it only walks the E edges in each of its V−1 passes, so the edge count E is central to its cost, not irrelevant.

Question 48 · React hooks · hard

Given the React component: function App() { const [count, setCount] = useState(0); return (<div onClick={() => setCount(count + 1)}>{JSON.stringify(count)}</div>); } — analyze the render cycle and predict how many times React re-renders after 5 clicks?

  1. React re-renders 5 times after 5 clicks because each click fires its own separate event handler containing a single setCount call, and React 18's automatic batching only merges multiple state updates scheduled within the same event handler — it does not merge updates coming from separate, sequential click events.
  2. React re-renders 10 times because each click triggers both a state update and a DOM mutation event.
  3. React never re-renders because useState caches the previous value and returns the same reference on every call.
  4. React re-renders once regardless of click count because React batches all state updates into a single commit phase.

Answer: A. React re-renders 5 times after 5 clicks because each click fires its own separate event handler containing a single setCount call, and React 18's automatic batching only merges multiple state updates scheduled within the same event handler — it does not merge updates coming from separate, sequential click events.

ExplanationStep-by-step React render cycle: (1) Initial render: React calls App(), useState initializes count = 0, and React commits the initial VDOM to the real DOM. (2) On each click: setCount(count + 1) schedules a state update inside that click's own event handler invocation. (3) React 18's automatic batching merges multiple state updates only when they occur within the same event handler call; it does not merge updates across separate, sequential click events. (4) Because each click is its own handler invocation, each one independently triggers React to re-run App(), diff the new JSX against the previous VDOM, and commit the change. (5) Across 5 separate clicks, this produces 5 separate re-renders — one per click.

Question 49 · React hooks · hard

Consider the following React function component: '''javascript function Counter() { const [count, setCount] = useState(0); useEffect(() => { console.log('Effect ran, count =', count); }, [count]); return ( <button onClick={() => setCount(count + 1)}> Increment </button> ); } ''' The button is clicked 4 times in a row, with no other renders happening in between. Counting the initial mount, how many times does the useEffect callback execute in total, and why?

  1. 4 times — useEffect only fires in response to state updates, not on the component's initial render, so the mount doesn't count
  2. 9 times — each click causes two effect executions (one triggered by the setCount render and one from the effect's own commit), plus one for the initial mount, giving 4 × 2 + 1 = 9
  3. 5 times — the effect runs once on the initial mount, then once again after each of the 4 clicks, because count changes value on every click and is listed in the dependency array, so React re-runs the effect whenever a listed dependency's value changes
  4. 1 time — since useEffect is given a dependency array at all (rather than no array), React treats it exactly like an effect mounted with an empty array and only ever runs it once

Answer: C. 5 times — the effect runs once on the initial mount, then once again after each of the 4 clicks, because count changes value on every click and is listed in the dependency array, so React re-runs the effect whenever a listed dependency's value changes

ExplanationReact always runs an effect once after the component's very first commit, regardless of what's in the dependency array — that's run #1, logging count = 0. After every later render, React compares each value in the dependency array to its value from the previous render, and re-runs the effect only if something changed. Click 1 calls setCount(count + 1), changing count from 0 to 1; since 1 differs from 0, the effect re-runs (run #2, count = 1). Click 2 changes count to 2 (run #3), click 3 changes it to 3 (run #4), and click 4 changes it to 4 (run #5). That's 1 mount run plus 4 update runs = 5 total executions. Option A wrongly assumes effects skip the initial mount, but React always fires an effect after the first render commit no matter what dependencies it lists. Option B invents a second execution per click — there is no setState call inside this effect, so it has no way to trigger itself a second time per click. Option D confuses a non-empty dependency array with an empty one: passing [] makes an effect run exactly once (mount only), but passing [count] makes it re-run on mount AND on every subsequent render where count's value differs from before.

Question 50 · CSS Grid layout · hard

A CSS Grid container is defined as: .container { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; width: 650px; } and holds 7 child elements. How many columns does the auto-fill algorithm actually create, what is the true rendered width of each column after the fr units grow to fill leftover space, and how many rows do the 7 items occupy?

  1. The auto-fill algorithm fits 4 columns, because 4 tracks at the 150px minimum need 4×150 + 3×10 = 630px (fits inside 650px) while a 5th track would need 5×150 + 4×10 = 790px (exceeds 650px); with 4 columns confirmed, the four 1fr units then split the remaining 650 − 3×10 = 620px equally, giving each column an actual rendered width of 155px, and the 7 items wrap into 2 rows (4 items in the first row, 3 in the second).
  2. Using that same 630px-vs-790px comparison, the algorithm still settles on 4 columns, but minmax(150px, 1fr) locks each track at its 150px lower bound regardless of leftover space, so the columns stay 150px wide, 20px of the container is left unused, and the 7 items still wrap into 2 rows.
  3. Dividing the 650px container by the 160px combined slot size (150px track + 10px gap) and rounding up gives 5 tracks, so the algorithm fits 5 columns; each column then narrows to 122px, and the 7 items wrap into 2 rows with 5 items in the first row and 2 in the second.
  4. Whenever a gap is specified, the algorithm ignores the minmax(150px, 1fr) lower bound entirely and instead creates exactly one column per grid item, so all 7 items sit in a single row of 7 columns, each rendered at 650px ÷ 7 ≈ 93px wide.

Answer: A. The auto-fill algorithm fits 4 columns, because 4 tracks at the 150px minimum need 4×150 + 3×10 = 630px (fits inside 650px) while a 5th track would need 5×150 + 4×10 = 790px (exceeds 650px); with 4 columns confirmed, the four 1fr units then split the remaining 650 − 3×10 = 620px equally, giving each column an actual rendered width of 155px, and the 7 items wrap into 2 rows (4 items in the first row, 3 in the second).

ExplanationAuto-fill first decides the column COUNT using each track's minimum size (150px) plus gaps (10px), not the final size. Test n=4: 4×150 + 3×10 = 600 + 30 = 630px, which fits within the 650px container. Test n=5: 5×150 + 4×10 = 750 + 40 = 790px, which exceeds 650px. So the algorithm settles on 4 columns — it never creates 5 or 7. Once the count is fixed at 4, the 1fr in minmax(150px, 1fr) is free to grow: subtract the 3 gaps between 4 columns (650 − 30 = 620px) and divide by 4 columns to get 155px per column (155px ≥ 150px, so the minimum is not violated — minmax() only caps growth from below, it does not freeze the track at its floor). With 4 columns and 7 items, the items wrap after every 4th item: row 1 holds items 1–4, row 2 holds items 5–7, giving 2 rows total. Final answer: 4 columns, 155px each, 2 rows.

Question 51 · Redis memory management · hard

A Redis cache is configured with maxmemory set to 128 MiB (134,217,728 bytes) and maxmemory-policy set to allkeys-lru. Starting from an empty cache, a client runs the following operation continuously: ``` SET(unique_key, counter_value) ``` New keys are written at a steady rate of 1024 unique keys per second, no key is ever re-read after being written, and no TTL is set on any key. Each stored key-value pair, including Redis's internal per-entry bookkeeping overhead, occupies exactly 128 bytes of memory. Assuming memory grows linearly with each write, after how many seconds of continuous writing does Redis's LRU eviction first begin removing the least-recently-used key to admit a new one?

  1. Eviction begins at precisely the 1024-second mark, since 128 MiB (134,217,728 bytes) holds exactly 1,048,576 entries of 128 bytes each, and writing 1024 new unique keys every second fills that capacity in exactly 1,048,576 ÷ 1024 = 1024 seconds before allkeys-lru starts evicting the least-recently-used key to admit each further write.
  2. Assuming decimal-byte MB instead of binary MiB, eviction begins around 976.6 seconds, because treating the limit as 128,000,000 bytes yields only 1,000,000 storable 128-byte entries, which the 1024-key-per-second write rate exhausts in about 976.6 seconds.
  3. Dividing raw bytes directly by the write rate suggests eviction begins near 131,072 seconds, because 134,217,728 bytes divided by 1024 keys per second gives that figure without first converting the byte limit into a count of 128-byte entries.
  4. LRU eviction actually begins immediately at the first SET call, because the allkeys-lru policy always evicts one existing key before admitting any new write, regardless of how much of the 128 MiB limit is currently occupied.

Answer: A. Eviction begins at precisely the 1024-second mark, since 128 MiB (134,217,728 bytes) holds exactly 1,048,576 entries of 128 bytes each, and writing 1024 new unique keys every second fills that capacity in exactly 1,048,576 ÷ 1024 = 1024 seconds before allkeys-lru starts evicting the least-recently-used key to admit each further write.

ExplanationConvert the memory limit into a byte count first: 128 MiB = 128 × 1,048,576 bytes = 134,217,728 bytes. Since every key-value pair, including overhead, takes exactly 128 bytes, the cache can hold 134,217,728 ÷ 128 = 1,048,576 entries before it is full. Writes arrive at 1024 new unique keys per second, so the cache fills after 1,048,576 ÷ 1024 = 1024 seconds. Only once that limit is reached does allkeys-lru start evicting the least-recently-used key for each further SET — eviction does not start at the very first write, because there is free capacity to fill first. Using decimal megabytes (128,000,000 bytes) instead of the true binary 128 MiB understates capacity to 1,000,000 entries, which would exhaust in about 976.6 seconds — too early, since it uses the wrong byte count for the limit. Dividing the raw byte limit (134,217,728) directly by the write rate without first converting to an entry count gives 131,072 seconds — too late, since it skips accounting for the 128-byte size of each entry. The correct figure accounts for both the byte-to-entry conversion and the binary (not decimal) definition of MiB, giving exactly 1024 seconds.

Question 52 · AVL tree rotation · hard

Consider the following AVL tree insert implementation, which performs a single right rotation, a single left rotation, or a double rotation depending on the balance factor computed after each insertion: ```js class AVL { insert(val) { this.root = this._insert(this.root, val); } _insert(node, val) { if (!node) return new Node(val); if (val < node.val) node.left = this._insert(node.left, val); else node.right = this._insert(node.right, val); node.height = 1 + Math.max(this.h(node.left), this.h(node.right)); const bf = this.bf(node); if (bf > 1 && val < node.left.val) return this.rotateRight(node); if (bf < -1 && val > node.right.val) return this.rotateLeft(node); if (bf > 1 && val > node.left.val) { node.left = this.rotateLeft(node.left); return this.rotateRight(node); } if (bf < -1 && val < node.right.val) { node.right = this.rotateRight(node.right); return this.rotateLeft(node); } return node; } } ``` If n = 100,000 elements are inserted into this tree, in any order including strictly ascending order, what is the time complexity of a single insert operation, and why?

  1. Insertion degrades to O(n) time per call in the worst case, because when every new value is larger than all existing values, the tree grows as a straight right-leaning chain that is never rotated back into balance
  2. Insertion runs in O(log n) time, because the balance-factor check after each insert triggers rotateRight for a left-heavy case, rotateLeft for a right-heavy case, or a double rotation for the left-right/right-left cases, so height stays close to log2(n); for n=100,000 that is about log2(100000) ≈ 17
  3. The insert method runs in O(1) amortized time, because V8's JIT compiler inlines rotateRight and rotateLeft into constant-time machine code regardless of tree height
  4. The insert method takes O(n!) time in the worst case, because the balance-factor check must consider every possible ordering of previously inserted values before selecting a rotation

Answer: B. Insertion runs in O(log n) time, because the balance-factor check after each insert triggers rotateRight for a left-heavy case, rotateLeft for a right-heavy case, or a double rotation for the left-right/right-left cases, so height stays close to log2(n); for n=100,000 that is about log2(100000) ≈ 17

ExplanationThis _insert method handles all four AVL imbalance cases, not just one: bf > 1 with val < node.left.val is the left-left case, corrected by a single rotateRight; bf < -1 with val > node.right.val is the right-right case, corrected by a single rotateLeft; bf > 1 with val > node.left.val is the left-right case, corrected by rotating node.left left, then rotating node right; and bf < -1 with val < node.right.val is the right-left case, corrected by rotating node.right right, then rotating node left. Because every possible imbalance is caught and fixed immediately after each insertion, the tree's height is always kept within a constant factor of log2(n) — this holds even for strictly ascending input, where each new value would otherwise build a right-leaning chain but is instead caught by the right-right case and rotated back to balance. For n = 100,000, log2(100000) ≈ 16.6, so about 17 levels are visited on a root-to-leaf walk during each insert, giving O(log n) time per insertion.

Question 53 · red-black tree insertion · hard

A red-black tree enforces two structural rules on every insertion: no red node may have a red child, and every root-to-leaf path must pass through the same number of black nodes (equal black-height), though red nodes may still appear along a path without affecting that count. For a red-black tree holding n = 500,000 elements, where the height of a minimally balanced binary tree of that size would be about log2(500,001) ≈ 19, what does enforcing only these two rules imply about the tree's worst-case height?

  1. A red-black tree's structural rules only bound black-height, so its worst-case height can run roughly twice the height of a minimally balanced tree, about 2 x log2(500,001) ≈ 38 compared to the ≈19-level minimum, since red nodes can pad a path without changing the black-node count.
  2. Because the two rules together force strict node-for-node balance, the tree's height stays fixed at exactly log2(500,001) ≈ 19, matching a perfectly balanced binary tree in every case.
  3. Repeatedly inserting the 500,000 elements in already-sorted order defeats these rules, so the tree degrades toward a single unbalanced chain with height growing linearly in n.
  4. These two rules give the tree the same ±1 balance-factor guarantee as an AVL tree, so its worst-case height stays within roughly 1.44 x log2(500,001) of the minimum.

Answer: A. A red-black tree's structural rules only bound black-height, so its worst-case height can run roughly twice the height of a minimally balanced tree, about 2 x log2(500,001) ≈ 38 compared to the ≈19-level minimum, since red nodes can pad a path without changing the black-node count.

ExplanationThe black-height rule only counts black nodes along a root-to-leaf path; red nodes are permitted to sit on that same path as long as no red node has a red child. That means the shortest possible path (all black) reflects the true minimum height, about log2(500,001) ≈ 19, while the longest possible path can alternate red and black nodes and run to roughly twice that length, about 2 x log2(500,001) ≈ 38. This 2x factor is the standard worst-case height bound for red-black trees, and it is what keeps insert, search, and delete at O(log n) even though the tree is not as tightly balanced as an AVL tree. Believing the height stays fixed at exactly 19 mistakes the black-height guarantee for a guarantee on total height, when only the black-node count is equalized across paths, not the total node count. Believing sorted-order insertion collapses the tree into a linear chain describes what happens to a plain, unbalanced binary search tree with no rebalancing; red-black insertion specifically performs rotations and color flips (rotateLeft, rotateRight, flipColors) to fix right-leaning red links and split temporary 4-nodes, which prevents that collapse regardless of insertion order. Believing the tree gets a +-1 AVL-style balance factor confuses two different balancing strategies: AVL trees bound height using a strict per-node balance factor, giving a tighter bound near 1.44 x log2 n, while red-black trees use the looser color-based black-height rule instead, which is exactly why their worst-case bound is 2x rather than 1.44x.

Question 54 · A* pathfinding · hard

In the A* search algorithm, every node n on the open list is scored using f(n) = g(n) + h(n), where g(n) is the exact cost of the path from the start to n, and h(n) is a heuristic estimate of the remaining cost from n to the goal. A* always expands the open-list node with the lowest f(n). A warehouse robot's open list currently holds four nodes: Node A: g(n) = 2, h(n) = 9 Node B: g(n) = 8, h(n) = 1 Node C: g(n) = 5, h(n) = 3 Node D: g(n) = 9, h(n) = 10 Which node will A* expand next?

  1. Node C will be expanded next, because A* always selects the open-list node with the lowest f(n) = g(n) + h(n), and C's f-value of 5+3=8 is lower than every other node's f-value.
  2. Node A will be expanded next, because A* always selects the open-list node with the lowest g(n) — the exact cost already spent reaching it — and A's g-value of 2 is the smallest on the list.
  3. Node B will be expanded next, because A* always selects the open-list node with the lowest h(n) — the estimated distance remaining to the goal — and B's h-value of 1 is the smallest on the list.
  4. Node D will be expanded next, because A* always selects the open-list node with the highest combined g(n) + h(n), and D's total of 9+10=19 is the largest on the list.

Answer: A. Node C will be expanded next, because A* always selects the open-list node with the lowest f(n) = g(n) + h(n), and C's f-value of 5+3=8 is lower than every other node's f-value.

ExplanationA* scores every node on the open list with f(n) = g(n) + h(n) and always expands the node with the smallest f-value, since that node offers the best current estimate of total path cost. Computing f for each node: A = 2+9 = 11, B = 8+1 = 9, C = 5+3 = 8, D = 9+10 = 19. Node C has the smallest f-value, 8, so A* expands it next. Picking the node with the lowest g(n) alone (node A, g=2) describes Dijkstra's algorithm, which ignores the heuristic entirely and can waste effort on paths that are cheap so far but far from the goal. Picking the node with the lowest h(n) alone (node B, h=1) describes Greedy Best-First Search, which chases whatever looks closest to the goal while ignoring how expensive it was to reach — this can miss the actual shortest path. Picking the node with the highest combined g(n)+h(n) (node D, 19) has the logic backwards: a higher f-value means a worse estimated total cost, so A* would never prioritize it over a lower one.

Question 55 · B-tree operations · hard

Given the implementation: class BTree { constructor(t=3) { this.t = t; this.root = new BNode(t); } search(k) { return this._search(this.root, k); } _search(node, k) { let i = 0; while(i < node.n && k > node.keys[i]) i++; if(i < node.n && k === node.keys[i]) return node; if(node.leaf) return null; return this._search(node.children[i], k); } } — with n=1,000,000 elements and minimum degree t=3, analyze this B-tree's time complexity and predict its worst-case search behavior?

  1. The algorithm degrades to O(n^2) for all inputs because the B-tree cannot maintain its structural invariant when elements are inserted in sorted order
  2. The B-tree achieves O(log_t n) by maintaining balanced structure invariants; with n=1,000,000 elements and t=3, it performs log3(1000000) which rounds up to 13 levels of comparisons, because each level narrows the search to one of t children, shrinking the remaining keys by a factor of t as height grows logarithmically
  3. Complexity is O(1) amortized because JavaScript engines JIT-compile the B-tree traversal into constant-time machine instructions
  4. The implementation requires O(n!) = O(overflow) because the B-tree search explores all permutations of the input

Answer: B. The B-tree achieves O(log_t n) by maintaining balanced structure invariants; with n=1,000,000 elements and t=3, it performs log3(1000000) which rounds up to 13 levels of comparisons, because each level narrows the search to one of t children, shrinking the remaining keys by a factor of t as height grows logarithmically

ExplanationFor a B-tree of minimum degree t=3, the height h satisfies h = O(log_t n): each internal node holds up to 2t-1 keys and 2t children, so descending one level narrows the search to a subtree containing roughly 1/t of the remaining keys. With n=1,000,000 elements, log_3(1,000,000) is approximately 12.6, which rounds up to a height of 13 levels — the same figure the O(log_t n) bound predicts, with no contradiction between the formula and the count. In the worst case, search costs exactly this height, O(log_t n), because every lookup follows a single root-to-leaf path, and the tree's split/merge rebalancing on insertion and deletion keeps that path length logarithmic no matter the insertion order — unlike an unbalanced binary search tree, which can degrade to O(n) on sorted input.

Question 56 · Prim MST algorithm · hard

Consider this binary-heap-based implementation of Prim's algorithm: function prim(graph, V) { const key = new Array(V).fill(Infinity); const inMST = new Array(V).fill(false); const pq = new MinHeap(); key[0] = 0; pq.push(0, 0); while (pq.size > 0) { const u = pq.pop(); if (inMST[u]) continue; inMST[u] = true; for (const [v, w] of graph.get(u)) { if (!inMST[v] && w < key[v]) { key[v] = w; pq.push(v, w); } } } } Each edge can trigger at most one pq.push() call, and a binary-heap insertion costs O(log V) because the new entry sifts up through at most the height of the heap. For a graph with V = 1000 vertices and E = 5000 edges, using log₂(1000) ≈ 10, what is the approximate total number of heap-insertion operations across the entire run, and why does this beat the O(V²) array-based version of Prim's algorithm on this graph?

  1. About 10,000 operations (V x log2V = 1000 x 10), since the number of vertices sets the heap cost, not the number of edges -- so on this graph the array-based version does no better, because it also only depends on V.
  2. About 50,000 operations (E x log2V = 5000 x 10); this beats O(V^2) because the heap version only pays O(log V) for each edge that actually gets relaxed, while the array-based version scans all V vertices to find the minimum key on every one of its V extractions, costing O(V^2) = 1,000,000 regardless of how sparse the graph is.
  3. About 5,000 operations (E x O(1) per edge), since pushing a new (vertex, weight) pair onto a heap just appends it to the end of the underlying array in constant time.
  4. About 5,000,000 operations (E x V = 5000 x 1000), because maintaining the min-heap property after each insertion requires comparing the new element against every other element currently stored in the heap.

Answer: B. About 50,000 operations (E x log2V = 5000 x 10); this beats O(V^2) because the heap version only pays O(log V) for each edge that actually gets relaxed, while the array-based version scans all V vertices to find the minimum key on every one of its V extractions, costing O(V^2) = 1,000,000 regardless of how sparse the graph is.

ExplanationEach edge in the graph can cause at most one pq.push() (the earlier, larger key value for that vertex simply becomes a stale heap entry, later skipped by the `if (inMST[u]) continue;` check). A binary heap holding up to V elements has height O(log V), and inserting a new element only requires sifting it up along one root-to-leaf path -- so each push costs O(log V), not O(1) and not O(V). With E = 5000 edges and log2(1000) approx 10, total heap-insertion work is E x log2V = 5000 x 10 = 50,000 operations. Compare this to the array-based version of Prim's algorithm, which does not use a heap at all: on every one of its V iterations it linearly scans all V vertices to find the current minimum key, costing O(V) per extraction and O(V^2) = 1000 x 1000 = 1,000,000 operations overall. Because this graph is sparse (E = 5000 is far smaller than V^2 = 1,000,000), the heap-based bound of O(E log V) = 50,000 is about 20 times faster than the array-based O(V^2) = 1,000,000. This is exactly why textbooks recommend a binary-heap priority queue for Prim's algorithm on sparse graphs: the true cost driver is E (how many edges actually get relaxed), each paying only O(log V) for its heap insertion -- not a full O(V) rescan of every vertex.

Question 57 · Floyd-Warshall APSP · hard

Floyd-Warshall computes all-pairs shortest paths with three nested loops over V vertices: ```javascript function floydWarshall(dist, V) { for (let k = 0; k < V; k++) for (let i = 0; i < V; i++) for (let j = 0; j < V; j++) if (dist[i][k] + dist[k][j] < dist[i][j]) dist[i][j] = dist[i][k] + dist[k][j]; return dist; } ``` This runs in O(V^3) time. An alternative way to get all-pairs shortest paths is to run the array-based (no-heap) version of Dijkstra's algorithm, which costs O(V^2) per source vertex, once from each of the V vertices. For a graph with V = 200 vertices, how do the total operation counts of these two approaches compare?

  1. Repeated Dijkstra performs only about 40,000 operations in total (200^2), roughly 200 times fewer than Floyd-Warshall's 8,000,000, because each call's O(V^2) cost need not be multiplied by the number of source vertices.
  2. Both methods land at the same order of magnitude, about 8,000,000 operations, since Floyd-Warshall's triple loop gives V^3 directly and running the O(V^2) array-based Dijkstra from all V=200 sources gives V times V^2 = V^3 as well.
  3. Only about 40,000 operations (200^2) are actually needed by Floyd-Warshall, since the outer loop over intermediate vertex k merely selects an index and contributes no independent factor of V to the total work.
  4. An array-based, heap-free Dijkstra can still extract each minimum-distance vertex in O(log V) time, so repeating it across all 200 sources costs roughly 306,000 operations (V^2 log V), beating Floyd-Warshall.

Answer: B. Both methods land at the same order of magnitude, about 8,000,000 operations, since Floyd-Warshall's triple loop gives V^3 directly and running the O(V^2) array-based Dijkstra from all V=200 sources gives V times V^2 = V^3 as well.

ExplanationFloyd-Warshall's three nested loops each run the full range 0 to V-1 independently of one another, so the total work is V times V times V = V^3; for V=200 that is 200^3 = 8,000,000 relaxation checks. The array-based (no-heap) version of Dijkstra costs O(V^2) for a single source, because finding the nearest unvisited vertex means scanning all V vertices at each of V steps — without a heap there is no O(log V) extraction. Running that O(V^2) procedure once from every one of the V=200 vertices multiplies the per-call cost by the number of calls: V times V^2 = V^3 = 200 times 40,000 = 8,000,000. So both approaches land on the same order, 8,000,000 operations, for this graph. The claim that repeated Dijkstra stays at O(V^2) ignores that it must be run V separate times, one per source; the claim that Floyd-Warshall is O(V^2) misreads its k-loop as a free index lookup when it is a full V-iteration loop just like the other two; and O(log V) minimum extraction requires a heap-based priority queue, not the array-based scan this question specifies.

Question 58 · segment tree range query · hard

Given the implementation: class SegTree { build(arr) { this.n = arr.length; this.tree = new Array(4*this.n).fill(0); this._build(arr,1,0,this.n-1); } query(l,r) { return this._query(1,0,this.n-1,l,r); } _query(node,start,end,l,r) { if(r<start||end<l) return 0; if(l<=start&&end<=r) return this.tree[node]; const mid=(start+end)>>1; return this._query(2*node,start,mid,l,r)+this._query(2*node+1,mid+1,end,l,r); } } — with n=100,000 elements, analyze the segment tree range query algorithm with O(log n) complexity, calculate that it performs 17 per query, 100000 queries = 1700000 operations, and predict the worst-case behavior?

  1. The algorithm degrades to O(n^2) for all inputs because segment tree range query cannot maintain its structural invariant when elements are inserted in sorted order
  2. The segment tree range query algorithm achieves O(log n) by maintaining balanced structure invariants; with n=100,000 elements it performs 17 per query, 100000 queries = 1700000 operations, because each step halves the search space via balanced partitioning
  3. Complexity is O(1) amortized because JavaScript engines JIT-compile segment tree range query operations into constant-time machine instructions
  4. The implementation requires O(n!) = O(overflow) because segment tree range query explores all permutations of the input

Answer: B. The segment tree range query algorithm achieves O(log n) by maintaining balanced structure invariants; with n=100,000 elements it performs 17 per query, 100000 queries = 1700000 operations, because each step halves the search space via balanced partitioning

ExplanationThe tree is a complete binary tree built over the n=100,000 leaves, so its height is ceil(log2(n)) = ceil(log2(100000)) = 17. In _query, each recursive call either returns immediately (node fully outside or fully inside [l,r]) or splits into two calls covering the left and right halves of the current range, so the recursion depth along any explored path is bounded by the tree height, giving O(log n) = O(17) work per query. For 100,000 queries, total operations = 17 per query x 100,000 queries = 1,700,000 operations. Worst case occurs when the query range [l,r] straddles the midpoint at every level (e.g., a range like [1, n-2]), forcing the recursion to branch into both children at each of the 17 levels instead of returning early, but the branching is still bounded by O(log n) per level so the total stays at O(log n) per query rather than degrading further.

Question 59 · union-find disjoint set · hard

Given the implementation: class UnionFind { constructor(n) { this.parent = Array.from({length:n},(_, i)=>i); this.rank = new Array(n).fill(0); } find(x) { if(this.parent[x]!==x) this.parent[x]=this.find(this.parent[x]); return this.parent[x]; } union(x,y) { const px=this.find(x), py=this.find(y); if(px===py) return; if(this.rank[px]<this.rank[py]) this.parent[px]=py; else if(this.rank[px]>this.rank[py]) this.parent[py]=px; else { this.parent[py]=px; this.rank[px]++; } } } — with n=100,000 elements, analyze the union-find disjoint set algorithm with O(alpha(n)) complexity, calculate that it performs near-constant inverse Ackermann operations, and predict the worst-case behavior?

  1. Because the union() method here always attaches the second tree's root under the first without checking this.rank, an adversarial sequence of unions can build a chain of height O(n), so a single find can cost O(n) time before path compression flattens it
  2. The find() method here performs path halving, pointing each node to its grandparent rather than the root, so the amortized bound is O(log n) per operation rather than O(alpha(n))
  3. The union-find achieves O(alpha(n)) amortized time per operation because find() uses full path compression, repointing every visited node directly to the root, and union() uses union by rank, always attaching the shorter tree under the taller one, so with n=100,000 elements even the worst-case sequence of operations costs only a near-constant inverse Ackermann factor per operation
  4. Because the rank array only increases when two trees of equal rank merge, after enough unions on n=100,000 elements the tree height can still grow to O(n) in the worst case, giving this implementation O(n) find operations despite path compression

Answer: C. The union-find achieves O(alpha(n)) amortized time per operation because find() uses full path compression, repointing every visited node directly to the root, and union() uses union by rank, always attaching the shorter tree under the taller one, so with n=100,000 elements even the worst-case sequence of operations costs only a near-constant inverse Ackermann factor per operation

ExplanationUnion-find reaches its O(alpha(n)) amortized bound only when both optimizations shown in the code are present together. In find(), the line this.parent[x] = this.find(this.parent[x]) performs full path compression: every node visited on the way to the root is repointed directly to that root, not just halfway. In union(), the three-way comparison on this.rank always attaches the shorter tree beneath the taller one and only increments rank on a tie, which is union by rank; this guarantees no tree can grow taller than needed for its size. Together these two mechanisms are what force the amortized cost per operation down to O(alpha(n)), where alpha is the inverse Ackermann function, an extraordinarily slow-growing function that stays at most 4 for any n up to far beyond 100,000. That gives the worst-case prediction directly: even a sequence of operations deliberately chosen to maximize tree height cannot push a single find or union past this bound once amortized, so for n=100,000 elements a sequence of m operations completes in O(m * alpha(100,000)) = O(4m) total steps rather than O(m log n) or O(mn). This is the precise reason the algorithm is described as "near-constant per operation" while still not being a strict O(1) worst case for every single call.

Question 60 · trie autocomplete · hard

Given the implementation: class Trie { constructor() { this.children = new Map(); this.isEnd = false; } insert(word) { let node = this; for(const ch of word) { if(!node.children.has(ch)) node.children.set(ch, new Trie()); node = node.children.get(ch); } node.isEnd = true; } autocomplete(prefix) { let node = this; for(const ch of prefix) { if(!node.children.has(ch)) return []; node = node.children.get(ch); } return this._collect(node, prefix); } } — with n=50,000 elements, analyze the trie autocomplete algorithm with O(L+k) complexity, calculate that it performs avg prefix=5 + 20 results = 25 operations, and predict the worst-case behavior?

  1. The implementation requires O(n) time because it scans every one of the 50,000 stored words to check each against the prefix, ignoring the trie's branching structure that lets mismatched branches be skipped entirely.
  2. Complexity degrades to O(n^2) because when many stored words share a common prefix, the algorithm re-traverses the same shared nodes separately for each matching word instead of visiting them once.
  3. Complexity is O(1) regardless of match count because trie autocomplete only follows the fixed-depth prefix path from the root and never traverses into the matching subtree to collect results.
  4. The trie autocomplete algorithm achieves O(L+k) by walking one child pointer per prefix character to reach the target node, then traversing only the matching subtree to collect results; with n=50,000 elements it performs avg prefix=5 + 20 results = 25 operations, since L and k depend only on the prefix length and result size, not on n.

Answer: D. The trie autocomplete algorithm achieves O(L+k) by walking one child pointer per prefix character to reach the target node, then traversing only the matching subtree to collect results; with n=50,000 elements it performs avg prefix=5 + 20 results = 25 operations, since L and k depend only on the prefix length and result size, not on n.

ExplanationTrie autocomplete reaches its target node by walking one child pointer per prefix character (cost L), then performs a depth-first traversal of only the subtree rooted there to collect matches (cost k, the total characters across matched words) — a total of O(L+k) work that is independent of n, the total number of words stored. For the given inputs, prefix length L=5 and k=20 characters of results gives the average-case figure of 25 operations. The worst case arises not from n directly but from k: if the prefix matches a large fraction of the stored vocabulary — for instance if most of the n=50,000 words share the same 5-character prefix — the matching subtree can contain close to all n words, pushing k up to roughly n. In that scenario, total work approaches O(L+n) = O(5+50,000) ≈ 50,005 operations, roughly 2,000 times the 25-operation average case, even though the O(L+k) form of the bound never changes.
← Set 2Set 4 →