Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Two Pointers & Sliding Window

Builds on Converging Pointers, which you haven’t finished yet.

P2MediumBuilt on Converging Pointers~4 min speedrun

Saddleback Search (Staircase Search in a Sorted Matrix)

Built on
Converging PointersThe pointers are a row and a column in a sorted grid, and each comparison discards a whole row or a whole column.
Unlocks
Kth Smallest
The problem

Search a 2D Matrix II (LC 240)(opens leetcode.com)

matrix has m rows and n columns. Every row is sorted in ascending order from left to right, and every column is sorted in ascending order from top to bottom (values may repeat). Return true if target appears anywhere in matrix, otherwise false.

Example 1
Input:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
Output:true
Target 5 is found at (1, 1).
14711152581219369162210131417241821232630

Staircase Search

row- and column-sorted matrix ➔ start at the top-right corner ➔ drop a row or a column per step
Spot it when: Both rows and columns are sorted and you must find a value, or walk the boundary between values that are too small and too big.
Staircase Search
Python
1def search_matrix(matrix: list[list[int]], target: int) -> bool:
2 """Search a 2D Matrix II: walk from the top-right corner, dropping a row or column per step.
3 Time: O(M + N) | Space: O(1)
4 """
5 if not matrix or not matrix[0]:
6 return False
7 
8 # row and col are the two pointers: row only moves down and col only moves left, so together they walk a staircase of cells
9 row, col = 0, len(matrix[0]) - 1 # Top-right corner: the largest in its row, the smallest in its column
10 
11 # 1. Drop One Line Per Step:
12 # Among the cells that are left, the current value is the largest in its row and the smallest in its column, so one comparison with target rules out a whole column or a whole row. A dropped line never comes back.
13 while row < len(matrix) and col >= 0:
14 val = matrix[row][col]
15 if val == target:
16 return True
17 if val > target:
18 col -= 1 # Everything below in this column is even bigger: drop the column
19 else:
20 row += 1 # Everything left in this row is even smaller: drop the row
21 
22 # 2. Report That The Target Is Missing:
23 # The walk left the matrix, so every row and every column that could hold target was dropped.
24 return False
Invariant: If target is in matrix, it is in a row at or below row and a column at or left of col.

The top-right value is the largest in its row and the smallest in its column, so start there: one comparison with target rules out a whole line. If val > target, everything below it in that column is even bigger, so col -= 1. If val < target, everything to its left in that row is even smaller, so row += 1. This is Converging Pointers on a grid: row only moves down and col only moves left, and a dropped line never comes back. The walk ends when it finds target or steps off the matrix, after at most M + N steps. Nothing is halved, so this is an elimination walk, not a binary search. The trap is the starting corner: start only at the top-right or the bottom-left corner. At the top-left corner both moves make the value bigger, so no comparison can rule anything out.

See it run

Search a 2D Matrix II: search_matrix(matrix, 23)

row 0 · col 4 · val 15

row 0 · col 4 · val 1

row 0 · col 4 · val 15

row 1 · col 4 · val 19

row 2 · col 4 · val 22

row 3 · col 4 · val 24

row 3 · col 3 · val 17

row 4 · col 3 · val 26

row 4 · col 2 · val 23

row 4 · col 2 · val 23 · answer True

Each row and each column increases. A dot marks a cell in a row or column that earlier steps already dropped.

  • val the cell compared with target in this step
  • top-left a start corner that cannot rule out a line
  • found the cell equal to target

Start: row = 0 and col = 4, the top-right corner. The target is 23.

Trap: the top-left corner holds 1. Its right neighbour and its lower neighbour are both larger, so 1 < 23 does not say which way to go.

val 15 is smaller than target 23. Everything left of it in row 0 is smaller too, so row += 1.

val 19 is smaller than target 23. Everything left of it in row 1 is smaller too, so row += 1.

val 22 is smaller than target 23. Everything left of it in row 2 is smaller too, so row += 1.

val 24 is larger than target 23. Everything below it in column 4 is larger too, so col -= 1.

val 17 is smaller than target 23. Everything left of it in row 3 is smaller too, so row += 1.

val 26 is larger than target 23. Everything below it in column 3 is larger too, so col -= 1.

val 23 equals target 23, so return True.

Done: 23 is in the matrix, so the answer is True.

row 0
col 01
col 14
col 27
col 311
col 415val
row 1
2
5
8
12
19
row 2
3
6
9
16
22
row 3
10
13
14
17
24
row 4
18
21
23
26
30
1 / 10
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Starting the walk at the top-left corner

    Mistake
    row, col = 0, 0
    Fix
    row, col = 0, len(matrix[0]) - 1

    What happens: At (0, 0), moving down and moving right both lead to larger values. So when the value is too small, one comparison cannot rule out a whole line. The top-right value is the largest in its row and the smallest in its column. There, each comparison rules out one row or one column. The bottom-left corner works too.

    Example: Search a 2D Matrix II (LC 240), Example 1, target = 5
    Mistake: returns FalseFix: returns True
  2. 2

    Gotcha 2: Stopping the loop at col > 0

    Mistake
    while row < len(matrix) and col > 0:
    Fix
    while row < len(matrix) and col >= 0:

    What happens: Column 0 is a real column of the matrix. With col > 0, the walk stops before it reads column 0. A target that is only in column 0 is reported missing. The walk should end only when col drops below 0 or row passes the last row.

    Example: Search a 2D Matrix II (LC 240), Example 1, target = 18
    Mistake: returns FalseFix: returns True
  3. 3

    Gotcha 3: Moving both row and col after one comparison

    Mistake
    col -= 1
    row += 1
    Fix
    col -= 1

    What happens: val > target rules out only the column below val. Moving row as well drops the rest of the current row without a check. That row may hold target.

    Example: matrix = [[1, 2, 3], [4, 5, 6]], target = 2
    Mistake: returns FalseFix: returns True
Not this one if…
  • Each row starts after the previous row ends, as in Search a 2D Matrix (LC 74): read row by row the grid is one sorted list, so a binary search can drop half of the cells per comparison, not one row or column. See Global Matrix Bisection
  • You must find the k-th smallest value, not whether one given value is present, as in Kth Smallest Number in Multiplication Table (LC 668). See Kth Smallest

Deep Dive

Why the top-right corner rules out a whole line

Converging Pointers already teaches that two pointers can start at two ends and discard one end per comparison. This card adds only the grid version: the two pointers are row and col, and each comparison discards a whole row or a whole column.

In Search a 2D Matrix II (LC 240), every row increases from left to right, and every column increases from top to bottom. The top-right value is the largest in its row, and it is the smallest in its column.

If val > target, the values below val in the same column are larger than val. None of them can equal target, so col -= 1 drops the column. If val < target, the values to the left of val in the same row are smaller than val. None of them can equal target, so row += 1 drops the row.

After a drop, the cells that are left form a smaller rectangle. Its top-right cell has the same property, so the next comparison works in the same way.

Tracing the 5 by 5 matrix with target = 23

The board shows the matrix of Example 1 of Search a 2D Matrix II (LC 240). A dot marks a cell in a row or column that an earlier step dropped. The tag val marks the cell that is compared.

The walk starts at 15. It is smaller than 23, so row += 1. The values 19 and 22 are also smaller, so row becomes 3. Then 24 is larger than 23, so col becomes 3.

The value 17 is smaller, so row becomes 4. The value 26 is larger, so col becomes 2. The value at row 4 and column 2 is 23, which equals target, so the function returns True.

The walk read 7 cells of a 25-cell matrix. If target is 20, which is not in the matrix, the walk reads 9 cells. It ends when row passes the last row. For the same matrix, target = 5 returns True and target = 20 returns False.

Why the corner matters, and why this is not binary search

The start corner must be the top-right or the bottom-left corner. At the top-left corner, moving right and moving down both lead to larger values. In the trace, 1 < 23 does not say whether 23 is to the right or below. A top-left walk that moves right when val < target and down otherwise returns False for target = 5 on the traced matrix, although 5 is in it.

At the top-right corner, the two moves lead to different directions of change: left is smaller and down is larger. This is why each comparison has exactly one safe drop.

The loop condition is row < len(matrix) and col >= 0. Column 0 is a real column, so col > 0 would end the walk before it reads column 0. A target that is only in column 0, such as 18 in the trace, would be reported missing.

No comparison removes half of the cells, so this is not a binary search. Each comparison removes one line. Search a 2D Matrix (LC 74) is different: each row starts after the previous row ends, so the whole grid reads as one sorted list, and a binary search on that list is faster.

The cost, and where the walk appears

Each step moves row down or col left, and neither moves back. A matrix with M rows and N columns allows at most M + N - 1 reads, so the time is O(M + N). The extra space is O(1).

The same walk follows the border between small and large values in other sorted grids. Count Negative Numbers in a Sorted Matrix (LC 1351) walks the same way on a matrix sorted in decreasing order, and counts a whole column at once when a value is negative. Leftmost Column with at Least a One (LC 1428) walks along the border of the zeros and the ones.

Find Positive Integer Solution for a Given Equation (LC 1237) does not give a matrix. It gives a function that increases in both arguments. The two arguments play the roles of row and col.

60-Second Pitch

"Rows and columns are both sorted, so I start at the top-right corner. That value is the largest in its row and the smallest in its column. If it equals target, I return True. If it is larger, everything below it in its column is larger, so I move col left. If it is smaller, everything left of it in its row is smaller, so I move row down. Each step drops a whole row or a whole column, and neither pointer moves back. When a pointer leaves the matrix, I return False. The time is O(M + N), and the space is O(1). I cannot use the top-left corner, because both moves lead to larger values there."

Common Questions

What is the time and space complexity?

The time is O(M + N) for M rows and N columns, because every step moves row down or col left, and the walk makes at most M + N - 1 reads. The space is O(1).

Can you start at the bottom-left corner?

Yes. The bottom-left value is the largest in its column and the smallest in its row. If val > target, move up. If val < target, move right. The walk is the mirror image of the top-right walk.

Can binary search do better?

A binary search on each row costs O(M log N). The walk costs O(M + N), which is smaller when the matrix is close to square.

How is this different from Search a 2D Matrix (LC 74)?

In Search a 2D Matrix (LC 74), each row starts after the previous row ends. Read row by row, the grid is one sorted list, and a binary search on the flat index takes O(log(M * N)) time. In this problem, a row can start below the end of the row above, so there is no flat order.

How do you count the values below a limit with the same walk?

In Count Negative Numbers in a Sorted Matrix (LC 1351), the rows and columns are sorted in decreasing order. Start at the top-right corner. If the value is negative, this cell and every cell below it in its column are negative, so add the number of those cells and move col left. If the value is not negative, move row down. The walk is still O(M + N).

SWE Reasoning

Spot it

  • The input is a matrix whose rows and columns are both sorted, and the rows do not continue each other.
  • The question is whether a value is present, or how many values are smaller or larger than a limit.
  • A function increases in two arguments, and you must find the border of a condition.
  • Binary search on a flat list does not apply, because the grid is not one sorted list.

The invariant, step by step

  1. row = 0 and col = len(matrix[0]) - 1 put the walk at the top-right corner.
  2. If target is in matrix, it is in a row at or below row and in a column at or left of col.
  3. val = matrix[row][col] is the largest in its row and the smallest in its column, among the cells that are left.
  4. If val == target, return True.
  5. If val > target, the cells below val in its column are larger, so col -= 1. If val < target, the cells to the left in its row are smaller, so row += 1.
  6. Each drop keeps the invariant. When row passes the last row or col drops below 0, no cell is left, and the function returns False.

Traps

  • Starting at the top-left corner. Both moves lead to larger values, so one comparison cannot rule out a line.
  • col > 0 in the loop condition. Column 0 is never read, and a target that is only there is missed.
  • Moving row and col after one comparison. The other line is dropped without a check, and it may hold target.
  • Calling it binary search. Each step drops one line, not half of the cells, and the time is O(M + N).

Practice

Canonical LeetCode:Search a 2D Matrix II (LC 240)Find Positive Integer Solution for a Given Equation (LC 1237)Count Negative Numbers in a Sorted Matrix (LC 1351)Leftmost Column with at Least a One (LC 1428)
Theory Context•Two Pointers & Sliding Window

Builds on Converging Pointers, which you haven’t finished yet.

P2MediumBuilt on Converging Pointers~4 min speedrun

Saddleback Search (Staircase Search in a Sorted Matrix)

Built on
Converging PointersThe pointers are a row and a column in a sorted grid, and each comparison discards a whole row or a whole column.
Unlocks
Kth Smallest
The problem

Search a 2D Matrix II (LC 240)(opens leetcode.com)

matrix has m rows and n columns. Every row is sorted in ascending order from left to right, and every column is sorted in ascending order from top to bottom (values may repeat). Return true if target appears anywhere in matrix, otherwise false.

Example 1
Input:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
Output:true
Target 5 is found at (1, 1).
14711152581219369162210131417241821232630

Staircase Search

row- and column-sorted matrix ➔ start at the top-right corner ➔ drop a row or a column per step
Spot it when: Both rows and columns are sorted and you must find a value, or walk the boundary between values that are too small and too big.
Staircase Search
Python
1def search_matrix(matrix: list[list[int]], target: int) -> bool:
2 """Search a 2D Matrix II: walk from the top-right corner, dropping a row or column per step.
3 Time: O(M + N) | Space: O(1)
4 """
5 if not matrix or not matrix[0]:
6 return False
7 
8 # row and col are the two pointers: row only moves down and col only moves left, so together they walk a staircase of cells
9 row, col = 0, len(matrix[0]) - 1 # Top-right corner: the largest in its row, the smallest in its column
10 
11 # 1. Drop One Line Per Step:
12 # Among the cells that are left, the current value is the largest in its row and the smallest in its column, so one comparison with target rules out a whole column or a whole row. A dropped line never comes back.
13 while row < len(matrix) and col >= 0:
14 val = matrix[row][col]
15 if val == target:
16 return True
17 if val > target:
18 col -= 1 # Everything below in this column is even bigger: drop the column
19 else:
20 row += 1 # Everything left in this row is even smaller: drop the row
21 
22 # 2. Report That The Target Is Missing:
23 # The walk left the matrix, so every row and every column that could hold target was dropped.
24 return False
Invariant: If target is in matrix, it is in a row at or below row and a column at or left of col.

The top-right value is the largest in its row and the smallest in its column, so start there: one comparison with target rules out a whole line. If val > target, everything below it in that column is even bigger, so col -= 1. If val < target, everything to its left in that row is even smaller, so row += 1. This is Converging Pointers on a grid: row only moves down and col only moves left, and a dropped line never comes back. The walk ends when it finds target or steps off the matrix, after at most M + N steps. Nothing is halved, so this is an elimination walk, not a binary search. The trap is the starting corner: start only at the top-right or the bottom-left corner. At the top-left corner both moves make the value bigger, so no comparison can rule anything out.

See it run

Search a 2D Matrix II: search_matrix(matrix, 23)

row 0 · col 4 · val 15

row 0 · col 4 · val 1

row 0 · col 4 · val 15

row 1 · col 4 · val 19

row 2 · col 4 · val 22

row 3 · col 4 · val 24

row 3 · col 3 · val 17

row 4 · col 3 · val 26

row 4 · col 2 · val 23

row 4 · col 2 · val 23 · answer True

Each row and each column increases. A dot marks a cell in a row or column that earlier steps already dropped.

  • val the cell compared with target in this step
  • top-left a start corner that cannot rule out a line
  • found the cell equal to target

Start: row = 0 and col = 4, the top-right corner. The target is 23.

Trap: the top-left corner holds 1. Its right neighbour and its lower neighbour are both larger, so 1 < 23 does not say which way to go.

val 15 is smaller than target 23. Everything left of it in row 0 is smaller too, so row += 1.

val 19 is smaller than target 23. Everything left of it in row 1 is smaller too, so row += 1.

val 22 is smaller than target 23. Everything left of it in row 2 is smaller too, so row += 1.

val 24 is larger than target 23. Everything below it in column 4 is larger too, so col -= 1.

val 17 is smaller than target 23. Everything left of it in row 3 is smaller too, so row += 1.

val 26 is larger than target 23. Everything below it in column 3 is larger too, so col -= 1.

val 23 equals target 23, so return True.

Done: 23 is in the matrix, so the answer is True.

row 0
col 01
col 14
col 27
col 311
col 415val
row 1
2
5
8
12
19
row 2
3
6
9
16
22
row 3
10
13
14
17
24
row 4
18
21
23
26
30
1 / 10
Gotchas1 of 3

Gotcha 1 of 3

  1. 1
    The trap

    Gotcha 1: Starting the walk at the top-left corner

    Mistake
    row, col = 0, 0
    Fix
    row, col = 0, len(matrix[0]) - 1

    What happens: At (0, 0), moving down and moving right both lead to larger values. So when the value is too small, one comparison cannot rule out a whole line. The top-right value is the largest in its row and the smallest in its column. There, each comparison rules out one row or one column. The bottom-left corner works too.

    Example: Search a 2D Matrix II (LC 240), Example 1, target = 5
    Mistake: returns FalseFix: returns True
  2. 2

    Gotcha 2: Stopping the loop at col > 0

    Mistake
    while row < len(matrix) and col > 0:
    Fix
    while row < len(matrix) and col >= 0:

    What happens: Column 0 is a real column of the matrix. With col > 0, the walk stops before it reads column 0. A target that is only in column 0 is reported missing. The walk should end only when col drops below 0 or row passes the last row.

    Example: Search a 2D Matrix II (LC 240), Example 1, target = 18
    Mistake: returns FalseFix: returns True
  3. 3

    Gotcha 3: Moving both row and col after one comparison

    Mistake
    col -= 1
    row += 1
    Fix
    col -= 1

    What happens: val > target rules out only the column below val. Moving row as well drops the rest of the current row without a check. That row may hold target.

    Example: matrix = [[1, 2, 3], [4, 5, 6]], target = 2
    Mistake: returns FalseFix: returns True
Not this one if…
  • Each row starts after the previous row ends, as in Search a 2D Matrix (LC 74): read row by row the grid is one sorted list, so a binary search can drop half of the cells per comparison, not one row or column. See Global Matrix Bisection
  • You must find the k-th smallest value, not whether one given value is present, as in Kth Smallest Number in Multiplication Table (LC 668). See Kth Smallest

Deep Dive

Why the top-right corner rules out a whole line

Converging Pointers already teaches that two pointers can start at two ends and discard one end per comparison. This card adds only the grid version: the two pointers are row and col, and each comparison discards a whole row or a whole column.

In Search a 2D Matrix II (LC 240), every row increases from left to right, and every column increases from top to bottom. The top-right value is the largest in its row, and it is the smallest in its column.

If val > target, the values below val in the same column are larger than val. None of them can equal target, so col -= 1 drops the column. If val < target, the values to the left of val in the same row are smaller than val. None of them can equal target, so row += 1 drops the row.

After a drop, the cells that are left form a smaller rectangle. Its top-right cell has the same property, so the next comparison works in the same way.

Tracing the 5 by 5 matrix with target = 23

The board shows the matrix of Example 1 of Search a 2D Matrix II (LC 240). A dot marks a cell in a row or column that an earlier step dropped. The tag val marks the cell that is compared.

The walk starts at 15. It is smaller than 23, so row += 1. The values 19 and 22 are also smaller, so row becomes 3. Then 24 is larger than 23, so col becomes 3.

The value 17 is smaller, so row becomes 4. The value 26 is larger, so col becomes 2. The value at row 4 and column 2 is 23, which equals target, so the function returns True.

The walk read 7 cells of a 25-cell matrix. If target is 20, which is not in the matrix, the walk reads 9 cells. It ends when row passes the last row. For the same matrix, target = 5 returns True and target = 20 returns False.

Why the corner matters, and why this is not binary search

The start corner must be the top-right or the bottom-left corner. At the top-left corner, moving right and moving down both lead to larger values. In the trace, 1 < 23 does not say whether 23 is to the right or below. A top-left walk that moves right when val < target and down otherwise returns False for target = 5 on the traced matrix, although 5 is in it.

At the top-right corner, the two moves lead to different directions of change: left is smaller and down is larger. This is why each comparison has exactly one safe drop.

The loop condition is row < len(matrix) and col >= 0. Column 0 is a real column, so col > 0 would end the walk before it reads column 0. A target that is only in column 0, such as 18 in the trace, would be reported missing.

No comparison removes half of the cells, so this is not a binary search. Each comparison removes one line. Search a 2D Matrix (LC 74) is different: each row starts after the previous row ends, so the whole grid reads as one sorted list, and a binary search on that list is faster.

The cost, and where the walk appears

Each step moves row down or col left, and neither moves back. A matrix with M rows and N columns allows at most M + N - 1 reads, so the time is O(M + N). The extra space is O(1).

The same walk follows the border between small and large values in other sorted grids. Count Negative Numbers in a Sorted Matrix (LC 1351) walks the same way on a matrix sorted in decreasing order, and counts a whole column at once when a value is negative. Leftmost Column with at Least a One (LC 1428) walks along the border of the zeros and the ones.

Find Positive Integer Solution for a Given Equation (LC 1237) does not give a matrix. It gives a function that increases in both arguments. The two arguments play the roles of row and col.

60-Second Pitch

"Rows and columns are both sorted, so I start at the top-right corner. That value is the largest in its row and the smallest in its column. If it equals target, I return True. If it is larger, everything below it in its column is larger, so I move col left. If it is smaller, everything left of it in its row is smaller, so I move row down. Each step drops a whole row or a whole column, and neither pointer moves back. When a pointer leaves the matrix, I return False. The time is O(M + N), and the space is O(1). I cannot use the top-left corner, because both moves lead to larger values there."

Common Questions

What is the time and space complexity?

The time is O(M + N) for M rows and N columns, because every step moves row down or col left, and the walk makes at most M + N - 1 reads. The space is O(1).

Can you start at the bottom-left corner?

Yes. The bottom-left value is the largest in its column and the smallest in its row. If val > target, move up. If val < target, move right. The walk is the mirror image of the top-right walk.

Can binary search do better?

A binary search on each row costs O(M log N). The walk costs O(M + N), which is smaller when the matrix is close to square.

How is this different from Search a 2D Matrix (LC 74)?

In Search a 2D Matrix (LC 74), each row starts after the previous row ends. Read row by row, the grid is one sorted list, and a binary search on the flat index takes O(log(M * N)) time. In this problem, a row can start below the end of the row above, so there is no flat order.

How do you count the values below a limit with the same walk?

In Count Negative Numbers in a Sorted Matrix (LC 1351), the rows and columns are sorted in decreasing order. Start at the top-right corner. If the value is negative, this cell and every cell below it in its column are negative, so add the number of those cells and move col left. If the value is not negative, move row down. The walk is still O(M + N).

SWE Reasoning

Spot it

  • The input is a matrix whose rows and columns are both sorted, and the rows do not continue each other.
  • The question is whether a value is present, or how many values are smaller or larger than a limit.
  • A function increases in two arguments, and you must find the border of a condition.
  • Binary search on a flat list does not apply, because the grid is not one sorted list.

The invariant, step by step

  1. row = 0 and col = len(matrix[0]) - 1 put the walk at the top-right corner.
  2. If target is in matrix, it is in a row at or below row and in a column at or left of col.
  3. val = matrix[row][col] is the largest in its row and the smallest in its column, among the cells that are left.
  4. If val == target, return True.
  5. If val > target, the cells below val in its column are larger, so col -= 1. If val < target, the cells to the left in its row are smaller, so row += 1.
  6. Each drop keeps the invariant. When row passes the last row or col drops below 0, no cell is left, and the function returns False.

Traps

  • Starting at the top-left corner. Both moves lead to larger values, so one comparison cannot rule out a line.
  • col > 0 in the loop condition. Column 0 is never read, and a target that is only there is missed.
  • Moving row and col after one comparison. The other line is dropped without a check, and it may hold target.
  • Calling it binary search. Each step drops one line, not half of the cells, and the time is O(M + N).

Practice

Canonical LeetCode:Search a 2D Matrix II (LC 240)Find Positive Integer Solution for a Given Equation (LC 1237)Count Negative Numbers in a Sorted Matrix (LC 1351)Leftmost Column with at Least a One (LC 1428)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•