Programming puzzles and brain teasers are a fun way to sharpen your coding and problem-solving skills. Youâll often see them in technical interviews, where theyâre used to test how you think, analyze problems, and come up with efficient solutions. To do well, it helps to practice and build solid strategies for tackling these kinds of challenges.
A useful solution explains both the answer and the reasoning. Restate the problem, identify constraints, choose an approach, test its assumptions, and then improve it. The sections below connect common problem shapes with the data structures and algorithms that support them.
When tackling programming puzzles, consider the following strategies:
These strategies work best when you use them in the right order. A common trap is trying to be clever too early, jumping straight into optimization, fancy data structures, or tricky math. Build a correct baseline, then improve it deliberately. Do not optimize a solution you donât fully understand yet.
A practical mindset that ties all of these together: always be able to answer two questions at any moment, âWhat do I know is true?â and âWhat am I trying next?â That keeps you moving forward, even when the puzzle feels unfamiliar.
A solid grasp of data structures is helpful for effective programming. Below are some practical strategies and tips to help you use them more confidently and efficiently.
Data structures are where puzzles become manageable. Most brain teasers arenât about memorizing rare tricks, theyâre about recognizing a pattern and picking a container that makes the pattern easy. A good âdoâ is to ask: What operations am I doing most, lookup, insert, delete, min/max, traversal? The right structure makes those operations feel natural.
Arrays are basic data structures that store elements in a contiguous block of memory, making it easy to access any element quickly.
Arrays are the default starting point because theyâre simple and fast. The âwhyâ behind many array techniques is that arrays give you indexes, and indexes give you powerful structure: order, boundaries, and the ability to use two pointers, binary search, and prefix computations. The main âdonâtâ is forgetting that many operations look cheap but hide expensive shifting or copying.
sys.getsizeof()) can help you identify areas for improvement.bisect module helps maintain sorted order in a list by finding the appropriate index for inserting an element or by performing binary searches.bisect.insort() to insert elements into a sorted list while keeping it ordered.bisect.bisect_left() or bisect.bisect_right() to find the index where an element should be inserted.bisect_left() are O(log n), but insort() can be O(n) due to shifting elements.A small but high-impact âdoâ: always sanity-check whether sorting is allowed. Sorting often unlocks elegant solutions, but it changes order. If the problem cares about original positions, keep track of indices or consider a hash-based approach instead.
Strings, as sequences of characters, often require special handling due to their immutable nature in some languages and the variety of operations performed on them.
String problems often look like array problems with extra rules, immutability, encoding, and more expensive slicing. The âwhyâ here is performance surprises: what looks like a tiny operation (like concatenation or slicing) can secretly allocate lots of memory. A good âdoâ is to think in terms of streams and indexes when strings get large.
+ can be very slow, $O(n^2)$ time. Instead, use tools like StringBuilder in Java or ''.join(list_of_strings) in Python to bring it down to $O(n)$..lower() in Python, .toLowerCase() in Java), but remember locale-specific rules (e.g., Turkish dotted/dotless "i").String.format) instead of manual concatenation for cleaner, safer, and often more efficient code.One useful âdonâtâ with strings: donât assume âcharactersâ are always one byte or one visible symbol. If Unicode matters, be explicit about whether you mean bytes, code points, or grapheme clusters, many bugs come from mixing those levels.
Linked lists are dynamic data structures consisting of nodes that contain data and references to the next (and possibly previous) nodes.
Linked lists show up in puzzles because they force pointer thinking: you canât jump around by index, so you learn to solve problems with structure instead of random access. Rely on pointer patterns (fast/slow, dummy head, two-list merge). Do not treat a list like an array, if you need frequent random access, you probably chose the wrong structure.
next pointer to its predecessor. This takes $O(n)$ time and requires only $O(1)$ extra space, making it a very efficient operation.A small âdoâ that prevents a lot of bugs: when manipulating linked lists, use a dummy head for operations near the front. It simplifies edge cases like deleting the first node or building a new list.
Heaps tend to appear when the problem sounds like: ârepeatedly get the best next thing.â If your loop is âpick minimum/maximum, update, repeat,â a heap often turns something expensive into something smooth. The âdonâtâ is using a heap when you actually need fast membership checks or deletion by value, heaps arenât designed for that without extra indexing.
0..C, a conventional bucket implementation has an $O(E+VC)$ bound; the value of $C$ matters.Trees show up everywhere because they model hierarchy, and many puzzles quietly hide a tree even if they donât call it one (ranges, prefixes, decisions, ancestors). Lean on traversal patterns and invariants (BST order, heap property, balance). The âdonâtâ is assuming every tree is balanced, shape matters, and it changes complexity.
Trees are hierarchical data structures with a root node and child nodes. Binary trees are a specific type where each node has at most two children.
Graphs consist of vertices (nodes) and edges connecting them, used to represent complex relationships.
Graph puzzles feel intimidating until you realize most of them are built from a small set of moves: represent the graph well, traverse it correctly, and keep the right bookkeeping (visited sets, distances, parents). Translate the story into edges and nodes early. Do not hand-wave graph direction or weights, those details decide the algorithm.
I. Graph Representations:
II. Graph Traversal Algorithms:
III. Cycle Detection:
IV. Shortest Path Algorithms:
V. Minimum Spanning Trees (MST):
VI. Network Flow Algorithms:
VII. Other Important Concepts:
Hash tables store key-value pairs for efficient lookup, insertion, and deletion.
Hash tables are the go-to âmake it fastâ tool because they turn searching into (usually) constant time. In puzzles, they often appear when you need to remember what youâve seen: duplicates, frequencies, complements, visited states. Leverage them for counting and membership. The âdonâtâ is using mutable keys or assuming worst-case canât happen.
Mastering algorithms is helpful for solving programming problems more efficiently by understanding patterns and techniques that reduce time and space usage.
Algorithms are the âmovesâ you apply once youâve chosen your data structures. A simple way to build skill is to recognize the story the puzzle is telling: pair finding (two pointers), explore choices (backtracking), best so far (greedy), reuse work (DP), split and combine (divide and conquer). Name the pattern out loud, once you name it, you can reach for proven templates.
Use two pointers when:
Typical uses: 2-Sum (sorted), 3-Sum (outer loop + two pointers), merging intervals, palindrome checks, removing duplicates, sliding-window constraints.
Two pointers are powerful because they replace nested loops with a single controlled scan. The âwhyâ is geometry: when the array is sorted, moving left or right has a predictable effect, so you can steer toward the answer instead of brute forcing combinations.
How it works:
left to index 0 and right to index nâ1, which is useful because without this setup you may miss edge combinations or scan redundantly (e.g., starting at the ends of prices [1,2,9,11] for budget 13 quickly reveals 2+11).left < right so that you stop before indices cross, whereas omitting this check can reuse the same element or cause an out-of-bounds access (e.g., halting when left == right prevents pairing one ticket price with itself).left rightward and if too large move right leftward, and skipping these directional moves would prolong the search or miss valid pairs (e.g., item prices [1,3,4,7,10] with budget 11 yield 1+10 then 4+7).For pair enumeration, decide whether the output means distinct value pairs or every pair of indices. Skipping equal values gives distinct value pairs. Listing every index pair can require quadratic output when many values repeat.
Walkthrough example: distinct value pairs summing to 10
Array (sorted): [1, 2, 3, 4, 5, 6, 7, 8, 9]
Start
left â 1 ⊠right â 9 â 1 + 9 = 10 â record (1,9) â move both inward
Next
left â 2, right â 8 â 2 + 8 = 10 â record (2,8) â move both
Next
left â 3, right â 7 â 3 + 7 = 10 â record (3,7) â move both
Next
left â 4, right â 6 â 4 + 6 = 10 â record (4,6) â move both
Stop when left >= right.
Two quick counter-examples for moves:
1 + 8 = 9 < 10 â move left rightward.2 + 9 = 11 > 10 â move right leftward.Variants youâll use often
i+1âŠnâ1 with two pointers while skipping duplicates of i and inner pointers, which is useful because omitting sorting or duplicate skipping causes missed or repeated triplets (e.g., from [-4,-1,-1,0,1,2] you report [-1,-1,2] and [-1,0,1] once).10 in [1,3,7,7,9] you may stop at 3+7 or keep scanning to list it once despite the second 7).right and contracting left to restore the constraint. Sum-based shrinking requires non-negative elements; negative elements can invalidate the reasoning, whereas moving pointers only in one direction can overshoot and miss beneficial ranges (e.g., counting subarrays with sum †8 in [2,3,1,2,4,3] or tracking at most 2 distinct items in a rolling shopping cart).I. Recursion works by breaking a problem into smaller instances of itself, with each call reducing the size of the problem.
II. Recursion process includes:
III. Consider stack overflow when recursion depth is too great. You can either switch to an iterative approach or use tail recursion optimization (if supported by the language). Memoization is another technique to improve efficiency by caching results of recursive calls to avoid redundant computations.
Recursion is less about âcalling yourselfâ and more about writing a solution that matches the shape of the problem. Define what a smaller instance looks like and make sure each call gets you closer to it. Do not hide progress, if the input doesnât clearly shrink toward a base case, the recursion will either loop forever or crash.
factorial(4)
|
|---> 4 * factorial(3)
|
|---> 3 * factorial(2)
|
|---> 2 * factorial(1)
|
|---> 1 * factorial(0)
|
|---> Base case: 1
I. Backtracking is well-suited for problems that require exploring all potential configurations, such as puzzles like Sudoku, the N-Queens problem, or combinatorial tasks.
II. Implementation tips for backtracking include:
III. A classic example is solving the N-Queens problem, where queens are placed row by row and invalid placements are rejected immediately. The following walkthrough uses one-based row and column numbers.
Backtracking is structured trial-and-error. The âwhyâ it works is that you donât blindly try everything, you quickly reject choices that violate constraints, which can cut the search space down dramatically. Make constraint checks cheap and early. Do not forget to undo changes, state leaks are the most common backtracking bug.
Step 1: Start with an empty board.
+---+---+---+---+
| Q | | | |
+---+---+---+---+
| | | | |
+---+---+---+---+
| | | | |
+---+---+---+---+
| | | | |
+---+---+---+---+
Step 2: Move to Row 2.
+---+---+---+---+
| Q | | | |
+---+---+---+---+
| | | Q | |
+---+---+---+---+
| | | | |
+---+---+---+---+
| | | | |
+---+---+---+---+
Step 3: Check Row 3.
No queen is placed in Row 3 because every column is invalid.
Step 4: Backtrack before entering Row 4.
There is no valid partial solution to extend, so Row 4 is never reached on this branch. Rejecting the branch here is the purpose of checking constraints after each choice.
Step 5: Undo the last placement.
Remove the queen from Row 2, Column 3. Keep the queen in Row 1 while trying the next candidate in Row 2. The board is now:
+---+---+---+---+
| Q | | | |
+---+---+---+---+
| | | | |
+---+---+---+---+
| | | | |
+---+---+---+---+
| | | | |
+---+---+---+---+
Step 6: Try other possibilities in Row 2.
+---+---+---+---+
| Q | | | |
+---+---+---+---+
| | | | Q |
+---+---+---+---+
| | | | |
+---+---+---+---+
| | | | |
+---+---+---+---+
Step 7: Move to Row 3.
+---+---+---+---+
| Q | | | |
+---+---+---+---+
| | | | Q |
+---+---+---+---+
| | Q | | |
+---+---+---+---+
| | | | |
+---+---+---+---+
Row 4 has no valid column for this partial placement either, so backtrack again. Eventually the queen in Row 1 must move. The two solutions, written as one-based column choices for Rows 1 through 4, are [2, 4, 1, 3] and [3, 1, 4, 2].
I. Dynamic programming stores reusable subproblem results. Optimization problems rely on optimal substructure; counting and feasibility problems instead combine counts or Boolean results. In each case, a state must capture all information needed to solve its subproblem.
II. There are two primary approaches to dynamic programming:
III. Some steps that might be taken in dynamic programming:
IV. Space optimization is often possible by realizing that only a few recent states are needed, reducing space complexity.
V. Classic examples include problems like the Fibonacci sequence, the Knapsack problem, or calculating the minimum edit distance between two strings.
Dynamic programming is what you reach for when brute force repeats itself. The âwhyâ is efficiency: if the same subproblem appears again and again, you should only solve it once. Define a state you can memoize and a recurrence you trust. Do not build a giant table without a clear meaning for what each cell represents.
0/1 Knapsack: items (weight, value) = (1,1), (3,4), (4,5), (4,7)
Each item is available once; capacity is 7.
Rows = first i available items, columns = capacity.
Capacity -> 0 1 2 3 4 5 6 7
Item 0 [ 0 0 0 0 0 0 0 0 ] (Base case: No items)
Item 1 [ 0 1 1 1 1 1 1 1 ] (Include Item 1)
Item 2 [ 0 1 1 4 5 5 5 5 ] (Include Item 2)
Item 3 [ 0 1 1 4 5 6 6 9 ] (Include Item 3)
Item 4 [ 0 1 1 4 7 8 8 11 ] (First 4 items available)
How the Table Is Filled
For each cell $DP[i][w]$ if the weight of the item $i$ is less than or equal to the current capacity $w$, choose the maximum of:
Visualization of Choices
I. For Item 1 (Weight = 1, Value = 1):
Capacity = 0 â Can't include Item 1 â DP[1][0] = 0
Capacity = 1 â Include Item 1 â DP[1][1] = 1
Capacity = 2 â Include Item 1 â DP[1][2] = 1
...
Capacity = 7 â Include Item 1 â DP[1][7] = 1
II. For Item 2 (Weight = 3, Value = 4):
Capacity = 0 â Can't include Item 2 â DP[2][0] = 0
Capacity = 3 â Include Item 2 â DP[2][3] = 4
Capacity = 4 â Include Item 2 â DP[2][4] = 5
...
Capacity = 7 â Include Item 2 â DP[2][7] = 5
III. Continue for all items, progressively updating the table.
Extract the Solution
To find the maximum value look at the last cell: $DP[4][7] = 11$.
To find the items included trace back from $DP[4][7]$, checking where values changed:
Final Knapsack Contents:
I. Greedy algorithms are used when making a locally optimal choice at each step leads to a globally optimal solution.
II. The two characteristics of greedy algorithms are:
III. Common implementation tips for greedy algorithms include:
IV. Examples of greedy algorithms include the activity selection problem, Huffman coding, and algorithms for finding minimum spanning trees (Prim's and Kruskal's).
Greedy algorithms are tempting because they feel simple: pick the best-looking option and move on. Sometimes that works beautifully, and sometimes it fails spectacularly. Either know the problem is greedy-friendly (via proof or known pattern) or actively search for a counterexample. The âdonâtâ is assuming that âbest right nowâ must lead to âbest overall.â
Example Huffman coding Input:
Characters: $[A, B, C, D, E, F]$
Frequencies: $[5, 9, 12, 13, 16, 45]$
Build a Min-Heap
Create a priority queue (min-heap) with the characters and their frequencies.
Initial Min-Heap:
5(A) 9(B) 12(C) 13(D) 16(E) 45(F)
Build the Huffman Tree
Combine the two smallest frequency nodes into a new node. Repeat until there is one tree.
I. Combine 5(A) and 9(B):
(14)
/ \
5(A) 9(B)
Updated Heap: $[12(C), 13(D), 14(AB), 16(E), 45(F)]$
II. Combine 12(C) and 13(D):
(25)
/ \
12(C) 13(D)
Updated Heap: $[14(AB), 16(E), 25(CD), 45(F)]$
III. Combine 14(AB) and 16(E):
(30)
/ \
14(AB) 16(E)
Updated Heap: $[25(CD), 30(ABE), 45(F)]$
IV. Combine 25(CD) and 30(ABE):
(55)
/ \
25(CD) 30(ABE)
Updated Heap: $[45(F), 55(CDABE)]$
V. Combine 45(F) and 55(CDABE):
(100)
/ \
F:45 (55)
/ \
(25) (30)
/ \ / \
C:12 D:13 (14) E:16
/ \
A:5 B:9
Assign Binary Codes
Traverse the tree to assign codes:
01Codes:
A = 1100
B = 1101
C = 100
D = 101
E = 111
F = 0
Final Huffman Tree Diagram:
(100)
0 / \ 1
F (55)
0 / \ 1
(25) (30)
0 / \1 0/ \1
C D (14) E
0/ \1
A B
The weighted length is 5Ă4 + 9Ă4 + 12Ă3 + 13Ă3 + 16Ă3 + 45Ă1 = 224 bits for 100 symbols, or 2.24 bits per symbol. It also equals the sum of merge weights: 14 + 25 + 30 + 55 + 100.
I. The divide and conquer strategy solves problems by dividing them into smaller subproblems, solving those independently, and then combining their solutions.
II. Implementation tips for divide and conquer:
III. Examples of divide and conquer algorithms include Merge Sort, Quick Sort, and Binary Search.
Divide and conquer is your âzoom lens.â Instead of trying to solve the whole problem at once, you solve smaller independent versions and merge results. Keep subproblems truly independent and make the combine step efficient. The âdonâtâ is accidentally recomputing the same work across branches, if that happens, you may be drifting into dynamic programming territory.
Sorting algorithms are fundamental to computer science and programming. They are used to rearrange elements in a list or array so that they follow a specific order (ascending or descending). Efficient sorting is a first step for optimizing other algorithms (like search and merge algorithms) that require input data to be in sorted lists. Understanding the different sorting algorithms, their time and space complexities, stability, and suitable use cases is essential for problem-solving and technical interviews.
The table uses auxiliary space, including recursive stack storage. Here $n$ is the number of items, $k$ is a key range or bucket count, and radix sort uses $d$ digit positions with radix $b$. Average bounds depend on input assumptions; stability can depend on implementation.
| Algorithm | Average Time Complexity | Worst-Case Time Complexity | Space Complexity | Stability | Best Use Case | Notes |
|---|---|---|---|---|---|---|
| Bubble Sort | $O(n^2)$ | $O(n^2)$ | $O(1)$ | Stable | Educational purposes, small datasets | Simple but inefficient for large datasets |
| Insertion Sort | $O(n^2)$ | $O(n^2)$ | $O(1)$ | Stable | Nearly sorted or small datasets | Efficient for small or nearly sorted datasets |
| Selection Sort | $O(n^2)$ | $O(n^2)$ | $O(1)$ | Unstable | Small datasets, when memory is limited | Inefficient for large datasets |
| Merge Sort | $O(n \log n)$ | $O(n \log n)$ | $O(n)$ | Stable | Large datasets, linked lists | Requires additional memory for merging |
| Quick Sort | $O(n \log n)$ | $O(n^2)$ | $O(n)$ worst; $O(\log n)$ with smaller-side recursion | Unstable | Large datasets, general-purpose sorting | Pivot selection strategy affects performance |
| Heap Sort | $O(n \log n)$ | $O(n \log n)$ | $O(1)$ | Unstable | Large datasets, in-place sorting | Efficient with minimal memory usage |
| LSD Radix Sort | $O(d(n+b))$ | $O(d(n+b))$ | $O(n+b)$ | Stable with stable digit passes | Large datasets with integer keys | Non-comparative sorting algorithm |
| Counting Sort | $O(n + k)$ | $O(n + k)$ | $O(n + k)$ | Stable | Small range of integer keys | Efficient when range $k$ is small |
| Tim Sort | $O(n \log n)$ | $O(n \log n)$ | $O(n)$ | Stable | Real-world data, hybrid sorting | Default sorting algorithm in Python |
| Bucket Sort | $O(n+k)$ under suitable distribution | $O(n^2)$ with insertion-sorted buckets | $O(n+k)$ | Implementation dependent | Uniformly distributed data | Divides elements into buckets |
| Shell Sort | Depends on gap sequence | Depends on gap sequence; $O(n^2)$ for halving gaps | $O(1)$ | Unstable | Medium-sized datasets | Improves upon Insertion Sort |
Bit manipulation involves algorithms that operate directly on bits, the basic units of data in computing. By leveraging bit-level operations, you can achieve performance optimizations, reduce memory usage, and solve certain problems more elegantly. Bit manipulation is particularly useful in systems programming, cryptography, graphics, and competitive programming.
&) operator produces 1 only when both corresponding bits are 1, making it useful for masking bits. The OR (|) operator sets a bit to 1 if at least one of the corresponding bits is 1, often used for setting bits.^) operator produces 1 when the bits are different, useful for toggling bits or swapping values. The NOT (~) operator flips all bits, performing a bitwise negation.<<) operation shifts bits to the left, filling with zeros from the right, effectively multiplying the number by powers of two. Conversely, right shift (>>) operations shift bits to the right, with two variations: logical shifts, which fill with zeros from the left (used for unsigned integers), and arithmetic shifts, which preserve the sign bit, used for signed integers.number |= (1 << n) can be used. This works by left-shifting 1 by $n$ positions to create a mask with only the $n$-th bit set, and then applying bitwise OR to modify the original number.number &= ~(1 << n). Here, 1 is left-shifted by $n$ and negated to form a mask where only the $n$-th bit is 0, and applying bitwise AND clears that bit.number ^= (1 << n) is used. By left-shifting 1 by $n$ and applying XOR, the target bit at $n$ is flipped.(number & (1 << n)) != 0 is employed. It works by left-shifting 1 by $n$ and applying bitwise AND; a non-zero result indicates the bit is set.number &= (number - 1) is effective. Subtracting 1 flips all bits from the LSB onward, and applying AND clears the lowest set bit.isolated_bit = number & (-number) is used. In twoâs complement, -number is the bitwise complement plus one, so ANDing it with number isolates the LSB.int count = 0;
while (number) {
number &= (number - 1);
count++;
}
This repeatedly clears the lowest set bit and counts how many such bits were present. The displayed C loop assumes an unsigned integer.
(number > 0) && ((number & (number - 1)) == 0) is used. This works because powers of two have only one set bit, and subtracting 1 flips all bits after that set bit, resulting in zero when ANDed.a ^= b;
b ^= a;
a ^= b;
This sequence swaps the values only when the two names refer to distinct storage locations. If they alias, the first operation zeroes the value. A normal swap is clearer and does not require this special-case reasoning.
mask = (1 << 0) | (1 << 3) will set only those bits to 1.<<) is a useful technique for multiplying by powers of two. For instance, number << 3 multiplies number by $2^3 = 8$, provided the value is representable and the language's shift rules permit it. Compilers already optimize multiplication by constants; shifting is not automatically faster.>>) can be used for dividing by powers of two. For example, number >> 2 divides number by $2^2 = 4$, making it an efficient way to handle division for unsigned integers or logical shifts.(number >> p) & ((1 << n) - 1) can be applied. This shifts the target bits to the right and uses a mask to isolate only those bits.>>) preserve the sign bit in signed integers, which is useful when working with negative numbers.>>>, which shift zeros into the high-order bits regardless of the sign of the number.Given a string of parentheses, determine the minimum number of parentheses that need to be removed to make the string valid. This can be solved using a stack data structure to track the open parentheses as we iterate through the string. If we encounter an open parenthesis, we add it to the stack. If we encounter a closing parenthesis, we check if there is a matching open parenthesis on the top of the stack. If there is, we pop the open parenthesis from the stack. If there is not, we add the closing parenthesis to a list of characters to remove. After we have processed the entire string, we remove the remaining open parentheses from the stack.
Determine whether a string becomes a palindrome after deleting at most one character. Move two pointers inward while the characters match. At the first mismatch, test the two remaining possibilities: skip the left character or skip the right character, then require the remaining range to be a palindrome without further deletions. Deleting both would exceed the limit. The two checks still give $O(n)$ time and $O(1)$ auxiliary space when implemented by indices.
Find the K points closest to the origin. Maintain a max-heap of at most K points, ordered by squared distance: its root is the farthest retained point. Replace that root when a closer point arrives. This takes $O(n\log K)$ time and $O(K)$ space for $1\le K\le n$; handle $K=0$ separately. Alternatively, build a min-heap of all points and extract K times in $O(n+K\log n)$ time with $O(n)$ storage. Squared distance preserves ordering without computing square roots.
To find a contiguous subarray summing to K when negative numbers are allowed, maintain prefix sums and a map of earlier sums. At prefix sum current, an earlier prefix equal to current - K identifies a matching range. Seed the map with the empty prefix sum zero. Store indices to recover one range, or frequencies to count every matching range, in expected $O(n)$ time and $O(n)$ space. A sum-based sliding window is appropriate only when the input and objective support monotonic shrinking, such as non-negative values.
The linked exercise is a related range-sum task with supplied endpoints rather than target-sum search. Its Python description uses an exclusive end index; specify endpoint conventions before translating a formula or comparing examples.
Add two numbers represented as strings. This can be solved by treating the strings as arrays of digits and using a carry variable to keep track of the carryover from one place value to the next.
Compute the dot product of two sparse vectors, where a vector is represented as a list of (index, value) pairs. This can be solved by iterating through both vectors and adding up the products of the values at the same index.
Compute the sum of the values of all the nodes in a binary search tree within a given range. This can be solved using a recursive in-order traversal of the tree, where we add the value of each node to the sum if it is within the range.
Compute an array where each element is the product of all the other elements in the input array. This can be solved using two pass approach, where we first compute the product of all the elements before each index and then compute the product of all the elements after each index.
Convert a binary search tree to a sorted doubly linked list. This can be solved using a recursive in-order traversal of the tree, where we build the linked list by adding each node to the end of the list as we visit it.
Find the lowest common ancestor (LCA) of two nodes in a binary tree. The LCA is the node in the tree that is the ancestor of both nodes and is the deepest node in the tree. To solve this problem, you can use a variety of techniques such as traversing the tree in a depth-first or breadth-first manner, or using a recursive approach to traverse the tree and find the LCA. You can also use a divide and conquer approach, where you split the tree into left and right subtrees and find the LCA in each subtree. Another approach is to use a hash table or a map to store the ancestors of each node and then use this information to find the LCA.
Designing a cache data structure that stores a limited number of items and removes the least recently used items when the capacity is reached. This can be solved using a doubly linked list and a hash table. The doubly linked list is used to store the items in the cache in the order in which they were accessed, with the most recently accessed item at the front of the list and the least recently accessed item at the back. The hash table maps keys directly to linked-list nodes, allowing lookup, movement to the front, and eviction from the back in expected $O(1)$ time. Storing only values would still require a list search to update recency.
Shuffle the elements of an array randomly. This can be solved using a random number generator and a Fisher-Yates shuffle algorithm. The Fisher-Yates shuffle algorithm works by starting at the end of the array and swapping the item at index i with an item chosen uniformly from indices 0..i, including itself. This results in a randomly shuffled array.
Given a binary tree, return an array containing the values of the nodes on the right side of the tree, when viewed from the right. Use breadth-first traversal and record the last node of each level when visiting children left to right. Alternatively, traverse right-first and record the first node encountered at each depth. The visit order determines which side is visible.
Implement a browser history system that supports the following operations: visit a URL, go back to the previous URL, and go forward to the next URL. One potential approach to this problem could involve using a doubly-linked list to store the URLs visited, with a pointer to the current URL. Going back or forward moves the pointer to the previous or next entry, stopping at the ends. Visiting a new URL after moving back discards the forward history before appending the new entry.
Each row of the binary matrix represents a binary number, with the most significant bit on the left. The score is the sum of those row values, not the total number of ones. First flip any row whose leading bit is zero: that bit is worth more than all later bits in the row combined. Then flip each remaining column if it contains more zeros than ones. Once leading bits are fixed, each column can be optimized independently. This greedy approach takes $O(RC)$ time and $O(1)$ auxiliary space if flips are performed in place.