Facebook Pixel

1351. Count Negative Numbers in a Sorted Matrix

Problem Description

You are given a m x n matrix grid where all elements are sorted in non-increasing order both row-wise and column-wise. This means:

  • Each row is sorted from left to right in descending order (or non-increasing order)
  • Each column is sorted from top to bottom in descending order (or non-increasing order)

Your task is to count and return the total number of negative numbers present in the entire matrix.

For example, if you have a matrix like:

[[ 4,  3,  2, -1],
 [ 3,  2,  1, -1],
 [ 1,  1, -1, -2],
 [-1, -1, -2, -3]]

Each row decreases from left to right, and each column decreases from top to bottom. The negative numbers in this matrix are: -1, -1, -1, -2, -1, -1, -2, -3, giving us a total count of 8.

The solution uses binary search on each row to find the first negative number. Since each row is sorted in non-increasing order, we can define a feasible function: feasible(mid) = grid[row][mid] < 0. This creates a pattern of [false, false, ..., true, true] where we search for the first true (first negative). Once found at index firstNegIndex, all elements from firstNegIndex to n-1 are negative, giving us n - firstNegIndex negatives for that row.

This approach efficiently counts all negatives in O(m log n) time complexity by performing binary search on each of the m rows.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight comes from observing the sorted property of each row. Since each row is sorted in non-increasing order (descending), the elements follow a pattern: all non-negative numbers come first, followed by all negative numbers.

This creates a boolean pattern when we ask "is this element negative?":

[false, false, false, true, true, true]

This is exactly the pattern that binary search excels at: finding the first true in a sorted boolean array. We define our feasible function as:

feasible(mid) = grid[row][mid] < 0

For each row, we use binary search to find the first negative index (firstTrueIndex). Once we find this boundary:

  • If firstTrueIndex is found (not -1), all elements from index firstTrueIndex to n-1 are negative
  • The count for this row is n - firstTrueIndex
  • If no negative exists in the row (firstTrueIndex == -1), contribute 0

By applying binary search to each of the m rows, we efficiently count all negatives without checking every element.

Learn more about Binary Search patterns.

Solution Implementation

1class Solution:
2    def countNegatives(self, grid: List[List[int]]) -> int:
3        """
4        Count the number of negative numbers in a sorted matrix.
5        Uses binary search on each row to find the first negative.
6
7        Time Complexity: O(m log n) where m is rows and n is columns
8        Space Complexity: O(1)
9        """
10        rows, cols = len(grid), len(grid[0])
11        total_count = 0
12
13        # Apply binary search on each row
14        for row in range(rows):
15            # Binary search template: find first negative (first true)
16            left, right = 0, cols - 1
17            first_true_index = -1
18
19            while left <= right:
20                mid = (left + right) // 2
21                # Feasible condition: is this element negative?
22                if grid[row][mid] < 0:
23                    first_true_index = mid
24                    right = mid - 1  # Search left for earlier negative
25                else:
26                    left = mid + 1  # Search right for negatives
27
28            # If we found a negative, count all elements from that index to end
29            if first_true_index != -1:
30                total_count += cols - first_true_index
31
32        return total_count
33
1class Solution {
2    /**
3     * Count negative numbers in a sorted matrix.
4     * Uses binary search on each row to find the first negative.
5     *
6     * Time Complexity: O(m log n) where m is rows and n is columns
7     * Space Complexity: O(1)
8     */
9    public int countNegatives(int[][] grid) {
10        int rows = grid.length;
11        int cols = grid[0].length;
12        int totalCount = 0;
13
14        // Apply binary search on each row
15        for (int row = 0; row < rows; row++) {
16            // Binary search template: find first negative (first true)
17            int left = 0;
18            int right = cols - 1;
19            int firstTrueIndex = -1;
20
21            while (left <= right) {
22                int mid = left + (right - left) / 2;
23                // Feasible condition: is this element negative?
24                if (grid[row][mid] < 0) {
25                    firstTrueIndex = mid;
26                    right = mid - 1;  // Search left for earlier negative
27                } else {
28                    left = mid + 1;  // Search right for negatives
29                }
30            }
31
32            // If we found a negative, count all elements from that index to end
33            if (firstTrueIndex != -1) {
34                totalCount += cols - firstTrueIndex;
35            }
36        }
37
38        return totalCount;
39    }
40}
41
1class Solution {
2public:
3    /**
4     * Count negative numbers in a sorted matrix.
5     * Uses binary search on each row to find the first negative.
6     *
7     * Time Complexity: O(m log n) where m is rows and n is columns
8     * Space Complexity: O(1)
9     */
10    int countNegatives(vector<vector<int>>& grid) {
11        int rows = grid.size();
12        int cols = grid[0].size();
13        int totalCount = 0;
14
15        // Apply binary search on each row
16        for (int row = 0; row < rows; row++) {
17            // Binary search template: find first negative (first true)
18            int left = 0;
19            int right = cols - 1;
20            int firstTrueIndex = -1;
21
22            while (left <= right) {
23                int mid = left + (right - left) / 2;
24                // Feasible condition: is this element negative?
25                if (grid[row][mid] < 0) {
26                    firstTrueIndex = mid;
27                    right = mid - 1;  // Search left for earlier negative
28                } else {
29                    left = mid + 1;  // Search right for negatives
30                }
31            }
32
33            // If we found a negative, count all elements from that index to end
34            if (firstTrueIndex != -1) {
35                totalCount += cols - firstTrueIndex;
36            }
37        }
38
39        return totalCount;
40    }
41};
42
1/**
2 * Counts the number of negative numbers in a sorted matrix.
3 * Uses binary search on each row to find the first negative.
4 *
5 * Time Complexity: O(m log n) where m is rows and n is columns
6 * Space Complexity: O(1)
7 */
8function countNegatives(grid: number[][]): number {
9    const rows: number = grid.length;
10    const cols: number = grid[0].length;
11    let totalCount: number = 0;
12
13    // Apply binary search on each row
14    for (let row = 0; row < rows; row++) {
15        // Binary search template: find first negative (first true)
16        let left: number = 0;
17        let right: number = cols - 1;
18        let firstTrueIndex: number = -1;
19
20        while (left <= right) {
21            const mid: number = Math.floor((left + right) / 2);
22            // Feasible condition: is this element negative?
23            if (grid[row][mid] < 0) {
24                firstTrueIndex = mid;
25                right = mid - 1;  // Search left for earlier negative
26            } else {
27                left = mid + 1;  // Search right for negatives
28            }
29        }
30
31        // If we found a negative, count all elements from that index to end
32        if (firstTrueIndex !== -1) {
33            totalCount += cols - firstTrueIndex;
34        }
35    }
36
37    return totalCount;
38}
39

Solution Approach

The implementation uses binary search on each row to find the first negative number, following the exact boundary-finding template.

Algorithm Steps:

  1. Initialize variables:

    • rows, cols = len(grid), len(grid[0]) - Get matrix dimensions
    • totalCount = 0 - Counter for negative numbers
  2. For each row, apply binary search:

    • Initialize: left = 0, right = cols - 1, firstTrueIndex = -1
    • Define feasible condition: grid[row][mid] < 0 (is this position negative?)
  3. Binary search loop (while left <= right):

    • Calculate mid = (left + right) // 2
    • If grid[row][mid] < 0: Found a negative (feasible is true)
      • Record this position: firstTrueIndex = mid
      • Search left for earlier negative: right = mid - 1
    • Else: Current element is non-negative
      • Search right for negatives: left = mid + 1
  4. Count negatives for this row:

    • If firstTrueIndex != -1: Add cols - firstTrueIndex to total count
    • If firstTrueIndex == -1: No negatives in this row, add 0
  5. Return the total count after processing all rows

Why this works:

Each row is sorted in non-increasing order, creating the pattern [non-neg, non-neg, ..., neg, neg]. Binary search finds the boundary (first negative) in O(log n) time. Once we know where negatives start, we count all remaining elements in that row.

Time Complexity: O(m log n) - Binary search O(log n) on each of m rows

Space Complexity: O(1) - Only using a few variables for tracking

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's trace through a small example using the binary search template:

grid = [[3,  2, -1],
        [2,  0, -2],
        [1, -1, -3]]

Initial Setup:

  • rows = 3, cols = 3 (3x3 matrix)
  • totalCount = 0

Row 0: [3, 2, -1]

  • Initialize: left = 0, right = 2, firstTrueIndex = -1
  • Iteration 1: mid = 1, grid[0][1] = 2 ≥ 0 (not negative) → left = 2
  • Iteration 2: mid = 2, grid[0][2] = -1 < 0 (negative!) → firstTrueIndex = 2, right = 1
  • Loop ends: left = 2 > right = 1
  • Result: firstTrueIndex = 2, add cols - 2 = 1 negative
  • totalCount = 1

Row 1: [2, 0, -2]

  • Initialize: left = 0, right = 2, firstTrueIndex = -1
  • Iteration 1: mid = 1, grid[1][1] = 0 ≥ 0 (not negative) → left = 2
  • Iteration 2: mid = 2, grid[1][2] = -2 < 0 (negative!) → firstTrueIndex = 2, right = 1
  • Loop ends: left = 2 > right = 1
  • Result: firstTrueIndex = 2, add cols - 2 = 1 negative
  • totalCount = 2

Row 2: [1, -1, -3]

  • Initialize: left = 0, right = 2, firstTrueIndex = -1
  • Iteration 1: mid = 1, grid[2][1] = -1 < 0 (negative!) → firstTrueIndex = 1, right = 0
  • Iteration 2: mid = 0, grid[2][0] = 1 ≥ 0 (not negative) → left = 1
  • Loop ends: left = 1 > right = 0
  • Result: firstTrueIndex = 1, add cols - 1 = 2 negatives
  • totalCount = 4

Final Result: Total negative count = 4

The binary search found the first negative in each row: index 2, index 2, and index 1. By counting all elements from that index to the end of each row, we efficiently found all 4 negative numbers (-1, -2, -1, -3).

Time and Space Complexity

Time Complexity: O(m log n), where m is the number of rows and n is the number of columns in the grid.

The algorithm performs binary search on each of the m rows. Each binary search takes O(log n) time since we're searching through n columns. Therefore, the total time is O(m × log n) = O(m log n).

Space Complexity: O(1)

The algorithm only uses a constant amount of extra space for variables like left, right, firstTrueIndex, and counters. No additional data structures are created that scale with the input dimensions.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Using the Wrong Binary Search Template

Pitfall: Using while left < right with right = mid instead of the correct template with while left <= right and right = mid - 1.

Why it fails: Different binary search variants have different behaviors. Using the wrong template can cause off-by-one errors or infinite loops.

Solution: Always use the boundary-finding template with firstTrueIndex:

# Correct template
left, right = 0, cols - 1
first_true_index = -1

while left <= right:
    mid = (left + right) // 2
    if grid[row][mid] < 0:
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1

2. Returning left Instead of Tracking firstTrueIndex

Pitfall: Some solutions try to return left directly after the loop, which may point to an invalid index if no negative exists.

Why it fails: When there are no negatives in a row, left will equal cols (out of bounds), and using it directly would add incorrect counts.

Solution: Track firstTrueIndex explicitly and check if it's -1:

if first_true_index != -1:
    total_count += cols - first_true_index
# Otherwise, no negatives in this row, contribute 0

3. Incorrect Count Calculation

Pitfall: Counting cols - firstTrueIndex - 1 or cols - firstTrueIndex + 1 instead of cols - firstTrueIndex.

Why it fails:

  • Using cols - firstTrueIndex - 1 would not count the first negative itself
  • Using cols - firstTrueIndex + 1 would count an extra non-existent element

Solution: When firstTrueIndex is the index of the first negative, there are exactly cols - firstTrueIndex elements from that index to the end of the row (inclusive):

# If firstTrueIndex = 2 and cols = 5
# Elements at indices 2, 3, 4 are negative = 5 - 2 = 3 elements
total_count += cols - first_true_index

4. Not Resetting Variables for Each Row

Pitfall: Forgetting to reset left, right, and firstTrueIndex for each row's binary search.

Why it fails: Using stale values from the previous row's search will produce incorrect results.

Solution: Initialize all binary search variables inside the row loop:

for row in range(rows):
    left, right = 0, cols - 1  # Reset for each row
    first_true_index = -1       # Reset for each row
    # ... binary search ...

5. Handling Edge Cases

Pitfall: Not considering edge cases like all positive elements or all negative elements in a row.

Why it fails: The code might return incorrect counts.

Solution: The template handles these naturally:

# All positive row: firstTrueIndex stays -1, contributes 0
# All negative row: firstTrueIndex = 0, contributes cols - 0 = cols
# Single element row: binary search works correctly with left = right = 0
Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What's the output of running the following function using the following tree as input?

1def serialize(root):
2    res = []
3    def dfs(root):
4        if not root:
5            res.append('x')
6            return
7        res.append(root.val)
8        dfs(root.left)
9        dfs(root.right)
10    dfs(root)
11    return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4    StringJoiner res = new StringJoiner(" ");
5    serializeDFS(root, res);
6    return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10    if (root == null) {
11        result.add("x");
12        return;
13    }
14    result.add(Integer.toString(root.val));
15    serializeDFS(root.left, result);
16    serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2    let res = [];
3    serialize_dfs(root, res);
4    return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8    if (!root) {
9        res.push("x");
10        return;
11    }
12    res.push(root.val);
13    serialize_dfs(root.left, res);
14    serialize_dfs(root.right, res);
15}
16

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More