Linear-time patterns
Recognise the signals: “sorted” or “pair/triplet” suggests two pointers; “contiguous subarray or substring” suggests a sliding window; “maximise count or pick the best each time” suggests greedy — once you have checked it with a counterexample.
- GreedyActivity selection, jump game, gas station.
- Two pointersContainer with most water and 3Sum.
- WindowsFixed and variable-size sliding windows.
These three patterns turn brute-force O(n²) solutions into O(n) or O(n log n). Greedy makes the locally best choice when you can prove it leads to the global best; two pointers walk a sorted array from both ends; sliding windows track a contiguous range as it moves.
1. When does a greedy algorithm work?
When a problem has the greedy-choice property (a locally optimal choice is part of some optimal solution) and optimal substructure. Always sanity-check with a counterexample: greedy coin change works for Indian currency denominations but fails for {1, 3, 4} and amount 6. If greedy fails, think dynamic programming.
2. Activity selection: the maximum number of non-overlapping meetings.
Sort by end time and always pick the meeting that ends earliest among those that start after the last chosen one. Finishing early leaves the most room. O(n log n).
int maxMeetings(int[][] meetings) {
Arrays.sort(meetings, Comparator.comparingInt(m -> m[1])); // by end time
int count = 0, lastEnd = Integer.MIN_VALUE;
for (int[] m : meetings) {
if (m[0] >= lastEnd) { count++; lastEnd = m[1]; }
}
return count;
}3. Jump game: can you reach the last index?
Track the farthest index reachable so far; if the current index is beyond it, you are stuck. One pass, O(n).
boolean canJump(int[] nums) {
int farthest = 0;
for (int i = 0; i < nums.length; i++) {
if (i > farthest) return false;
farthest = Math.max(farthest, i + nums[i]);
}
return true;
}4. Container with most water.
Two pointers at both ends. The area is limited by the shorter line, so moving the taller line can never help — always move the shorter one inward. O(n).
int maxArea(int[] h) {
int i = 0, j = h.length - 1, best = 0;
while (i < j) {
best = Math.max(best, Math.min(h[i], h[j]) * (j - i));
if (h[i] < h[j]) i++; else j--;
}
return best;
}5. 3Sum: all unique triplets that sum to zero.
Sort, fix one number, then run two pointers on the rest; skip duplicates at every level. O(n²), which beats the O(n³) brute force.
List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
for (int i = 0; i < nums.length - 2; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
int lo = i + 1, hi = nums.length - 1;
while (lo < hi) {
int sum = nums[i] + nums[lo] + nums[hi];
if (sum < 0) lo++;
else if (sum > 0) hi--;
else {
res.add(List.of(nums[i], nums[lo], nums[hi]));
while (lo < hi && nums[lo] == nums[lo + 1]) lo++;
while (lo < hi && nums[hi] == nums[hi - 1]) hi--;
lo++; hi--;
}
}
}
return res;
}6. Fixed-size sliding window: maximum sum of k consecutive elements.
Compute the first window, then slide: add the entering element and subtract the leaving one. O(n) instead of O(n·k).
int maxSumWindow(int[] a, int k) {
int sum = 0;
for (int i = 0; i < k; i++) sum += a[i];
int best = sum;
for (int i = k; i < a.length; i++) {
sum += a[i] - a[i - k];
best = Math.max(best, sum);
}
return best;
}7. Variable sliding window: the smallest subarray with sum ≥ target.
Expand the right edge to grow the sum; while the condition holds, record the length and shrink from the left. Each element enters and leaves once — O(n). Works because all numbers are positive.
int minSubArrayLen(int target, int[] nums) {
int left = 0, sum = 0, best = Integer.MAX_VALUE;
for (int right = 0; right < nums.length; right++) {
sum += nums[right];
while (sum >= target) {
best = Math.min(best, right - left + 1);
sum -= nums[left++];
}
}
return best == Integer.MAX_VALUE ? 0 : best;
}8. Gas station: where can you start to complete the circuit?
If the total gas is less than the total cost, no start works. Otherwise walk once keeping a running tank; whenever it goes negative, the next station becomes the candidate start. O(n).
int canCompleteCircuit(int[] gas, int[] cost) {
int total = 0, tank = 0, start = 0;
for (int i = 0; i < gas.length; i++) {
total += gas[i] - cost[i];
tank += gas[i] - cost[i];
if (tank < 0) { start = i + 1; tank = 0; }
}
return total < 0 ? -1 : start;
}Remember
Key points
- Prove or disprove greedy
Try a small counterexample first.
- Sort enables two pointers
Then move the pointer that can improve.
- Windows need monotonic sums
Positive numbers; else use prefix sums.
- Skip duplicates explicitly
For unique-result problems.
Interview drill
Follow-up questions
What the interviewer asks after your first answer. Turn on practice mode, answer each one aloud, then reveal.
-
1 Minimum number of platforms for trains. Intermediate
Sort arrivals and departures separately and sweep with two pointers, counting trains present; the maximum count is the answer.
-
2 Fractional knapsack vs 0/1 knapsack? Intermediate
Fractional allows taking parts of items, so greedy by value/weight ratio is optimal; 0/1 does not, so it needs dynamic programming.
-
3 Longest substring with at most k distinct characters. Intermediate
Variable window with a frequency map; shrink from the left while the map holds more than k keys.
-
4 Trapping rain water in O(n) and O(1) space. Advanced
Two pointers with leftMax and rightMax: move the side with the smaller max, adding
max - heightas trapped water. -
5 Minimum number of coins for an amount in Indian currency. Basic
Greedy (take the largest note or coin that fits) is optimal for canonical systems like 1, 2, 5, 10, 20, 50, 100, 200, 500.
-
6 Subarray with a given sum when numbers can be negative? Advanced
A sliding window fails; use prefix sums with a hash map of seen sums instead.