The constraint list is not fine print. It is the interviewer telling you which complexity they expect, and often which pattern to use. Read it first.
Candidates skip straight to the examples. The constraints are more useful. “n ≤ 20” means exponential is fine and they want backtracking. “n ≤ 10⁵” means anything quadratic will fail. Reading the bound turns a guess into a decision.
So the working rule is: estimate the operation count as a function of n, plug in the largest n in the constraints, and check it lands under about 10⁸. That is the whole method.
for loop over an array is the fast path.n = 10⁵ and your solution is O(n log n), that is about 1.7 million operations, comfortably fine. If it is O(n²), that is 10¹⁰, which takes minutes even in C#. You can tell the interviewer your approach will not work before writing it. That is exactly the judgement they want to see.This is the list to memorise. Find the largest n in the constraints and read across.
long.n ≤ 20 almost always means subsets or bitmask, because 2²⁰ is about a million and 2³⁰ is a billion. And n = 10⁵ almost always means sort it or use a hash map, because n² is 10¹⁰ and n log n is under 2 million. Spot these two at a glance and you save five minutes.Constraints tell you more than the complexity. Several phrasings map almost one to one onto a pattern.
Dictionary, no copy. Think two pointers, in-place swaps, or Floyd.Array.Sort and in-place tricks. Often points at cycle detection or binary search on the value range.int, but sums and products along the way may not. Use long for them and say why.int.MaxValue, which is about 2.1 × 10⁹.PriorityQueue, or a SortedSet.int[26] works and the space is O(1).n gets, ask. Each of those changes the answer. Interviewers score the question as a plus, not as ignorance.Three rules cover almost every case.
O(n) + O(n) = O(n). Not O(n²).O(n) × O(n) = O(n²).O(n² + n log n + n) = O(n²). Drop constants and lower terms.public static class NotQuadratic
{
/// <summary>Two pointers in nested loops, but only O(n) work in total.</summary>
/// <param name="nums">The values.</param>
/// <returns>How many times the inner loop moved left.</returns>
/// <example>NotQuadratic.Moves([1, 2, 3, 4]) returns 3.</example>
public static int Moves(int[] nums)
{
int left = 0; // 0 is the first index. left only ever moves forward.
int total = 0; // 0 moves so far
// right is the outer index. Invariant: left <= right, and left never goes back.
for (int right = 0; right < nums.Length; right++)
{
while (left < right && nums[left] < nums[right])
{
left++; // one step forward. At most n steps across the WHOLE loop.
total++;
}
}
return total;
}
}
Reading the figure. Green cells are behind left and will never be looked at again. Blue cells sit between the two pointers. Amber is where right is now. Grey cells are still ahead. The arrow below shows the only direction either pointer moves.
left starts at 0, only increases, and never passes n. So the inner while runs at most n times across the whole outer loop, not n times per pass. Total: O(n). This amortised argument is the same one behind the sliding window, monotonic stacks and cyclic sort. Interviewers ask about it directly, so have the sentence ready.public static class DedupSlow
{
/// <summary>Keeps first sightings, newest
/// first. O(n^2).</summary>
/// <param name="items">The values.</param>
/// <returns>Distinct values, reversed.</returns>
/// <example>DedupSlow.Run([1, 2, 1, 3])
/// returns [3, 2, 1].</example>
public static List<int> Run(int[] items)
{
var output = new List<int>();
foreach (int item in items)
{
if (output.Contains(item)) // O(n)
{
continue;
}
output.Insert(0, item); // O(n)
}
return output;
}
}
public static class DedupFast
{
/// <summary>Same result. O(n).</summary>
/// <param name="items">The values.</param>
/// <returns>Distinct values, reversed.</returns>
/// <example>DedupFast.Run([1, 2, 1, 3])
/// returns [3, 2, 1].</example>
public static List<int> Run(int[] items)
{
var seen = new HashSet<int>();
var output = new List<int>();
foreach (int item in items)
{
if (seen.Add(item)) // O(1) avg
{
output.Add(item); // O(1)
}
}
output.Reverse(); // O(n), once
return output;
}
}
No nesting in sight, and the left one is O(n²). The lesson: look inside the loop body, not just at the loop headers. List.Contains, Insert(0, ...), RemoveAt(0), ranges such as s[i..], LINQ Min, Max, Sum, Count(), and string += are all linear.
The gap between the second and fourth lines is why quickselect beats a full sort for finding one element. It recurses into one half instead of both.
new long[n + 1, m + 1] is 8 bytes per cell.Concrete numbers, so the symbols have weight. Each figure is an operation count.
Three readings worth keeping.
log n is tiny. A billion items is 30 steps. Never worry about a log factor.n log n is close to n. At n = 10⁵ the factor is only 17. Do not twist a solution to remove a sort.n² falls off a cliff. Fine at 5,000, hopeless at 10⁵. That is the real line the list draws.int[] of 10⁶ items is about 4 MB. Every class instance costs a header of about 16 to 24 bytes on top of its fields. A million small objects is where memory goes.Rough sizes for 10⁶ elements on a 64-bit runtime:
int[]: 4 bytes each, about 4 MB. long[] is 8 MB.bool[]: 1 byte each, about 1 MB. BitArray packs 8 per byte.List<int>: 4 bytes each, plus up to double that while it grows.HashSet<int>: about 16 to 20 bytes each, so around 20 MB.Dictionary<int, int>: about 20 to 24 bytes each, so around 24 MB.List<(int, int)>: 8 bytes each, because value tuples are inline.List<int[]> of pairs: about 40 bytes each, because every int[2] is its own object.The lesson: prefer arrays of value types and value tuples to lists of small arrays or small classes. It is faster and several times smaller.
Conventions for what counts as extra space:
n answers is still “O(1) extra space”.stackalloc memory counts too, and it comes out of that same small stack. Keep it to a few kilobytes.Say four things, in this order. It takes fifteen seconds and closes the question cleanly.
k is. Problems with two inputs need both: string matching is O(n + m), not O(n). Sloppiness here is one of the easiest ways to look less careful than you are.List.Add. Average case O(1) means it usually holds, as with a Dictionary lookup, and an adversary could break it. Using the right word is a small, visible signal.n ≤ 20 means subsets or bitmask. n = 10⁵ means n log n at worst. Two lines, huge payoff.long.