leetcode 2D search

Posted by martinco on Sat, 19 Feb 2022 13:26:10 +0100

 

Today is the 43 articles on LeetCode. Let's take a look at the 74 questions in LeetCode, search two-dimensional matrix and search two-dimensional matrix.

The official difficulty of this question is Medium, and the pass rate is 36%. Different from the previous questions, this question has a very high praise ratio, 1604 likes and 154 opposes. It can be seen that the quality of this question is still very high. In fact, it is true. This question is very interesting.

meaning of the title

The meaning of this question is also very simple. Given a two-dimensional array matrix and an integer target, each row and column in this array are incremented, and the first element of each row is greater than the last element of the previous row. We are required to return a bool variable to represent whether the target is in the array.

In other words, this is a typical problem of judgment elements. Let's take a look at two examples:

Input:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 3
Output: true
Input:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 13
Output: false

Problem solution

This question may be a little confused just got it. Of course, we can easily see that it is a dichotomy problem, but the dichotomy we made before is on a one-dimensional array. Now the data is two-dimensional. How can we dichotomy?

After carefully reading the meaning of the question and observing the example, it is easy to find that if a two-dimensional array satisfies that each row and column are orderly, and the first element of each row is larger than the last element of the previous row, then if we reshape the two-dimensional array to one dimension, it is still orderly.

For example, there is a two-dimensional array:

[[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]

When it reshape into one dimension, it will become like this:

[1, 2, 3, 4, 5, 6, 7, 8, 9]

Reshape is an expression in numpy, which can also be simply understood as stringing each line together. So the simplest way to solve this problem is to reduce the dimension of the matrix into a one bit array, and then judge whether the elements exist by dichotomy. If you are lazy, you can use numpy to reshape. If you can't use numpy, you can take a look at my previous tutorial on numpy, or you can use loops to deal with it yourself.

After reshape, there is a simple dichotomy. There is no difficulty at all:

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        import numpy as np
        arr = np.array(matrix)
        # You can directly reshape through numpy
        arr = arr.reshape((-1, ))
        l, r = 0, arr.shape[0]
        if r == 0:
            return False
        # Apply dichotomy
        while l+1 < r:
            m = (l + r) >> 1
            if arr[m] <= target:
                l = m
            else:
                r = m
        return arr[l] == target

Serious practice

The introduction of numpy reshape is just to provide you with a solution, which is obviously not a good practice. What should be the correct method?

We still need to make an in-depth analysis of the problem. Thinking forward seems to have no clue. We can think backward. This is also a common routine for solving problems. Suppose we already know that the number target exists in the matrix, and its row number is i and column number is j. So what conclusions can we draw according to the conditions in the title?

By analyzing the size relationship of elements, we can conclude that all elements with line number less than i are less than it, and all elements with line number greater than i are greater than it. The elements of the same row, the elements with column number less than j are less than it, and the elements with column number greater than j are greater than it.

In other words, line number i is an invisible dividing line, which divides the matrix into two parts. The one above i is less than target and the one below i is greater than target. So can we find this i by two points?

It's easy to think of this. We can find i through the last element of each line. For a two-dimensional array, the last element of each row is a one-dimensional array, which can be easily divided into two.

After we find the line number I, we do the same thing again. We divide the line I in half to find the position of j. After finding it, judge whether matrix[i][j] is equal to target. If it is equal, it indicates that the element is in the matrix.

The whole idea should be well understood, but there is a small problem in the implementation, that is, when we find the row, we find the position of the first row greater than or equal to the target. That is to say, we are looking for the right endpoint, so we maintain a left open and right closed interval during bisection. In terms of boundary processing, it is opposite to the usual writing method of left closing and right opening. If you pay attention to this, you can successfully implement the algorithm:

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        n = len(matrix)
        if n == 0:
            return False
        
        m = len(matrix[0])
        if m == 0:
            return False
        
        # Initialization, left opening and right closing, so it is set to - 1, n-1
        l, r = -1, n-1
        
        while l+1 < r:
            mid = (l + r) >> 1
            # Move the left boundary when it is less than target
            if matrix[mid][m-1] < target:
                l = mid
            else:
                r = mid
                
        row = r
        
        # Normal left close right open dichotomy
        l, r = 0, m
        
        while l+1 < r:
            mid = (l + r) >> 1
            if matrix[row][mid] <= target:
                l = mid
            else:
                r = mid
                
        return matrix[row][l] == target

We used two bisections and found the results. Each bisection is an O(logN) algorithm, so the overall algorithm is also a log level algorithm.

optimization

There is no problem with the above algorithm, but we have two bisections and feel a little troublesome. Can we reduce one and only use one bisection?

If we want to find the answer only by using bisection once, that is to say, we can find a method to segment the whole array, and the segmented array also has a size relationship. This condition is the basis of using dichotomy and must be met.

We can easily find such a segmentation attribute in the array, that is, the position of the element. In the problem of matrix elements, one method we often use is to number the elements in the matrix. For example, if a point is in row i and column j, its number is i * m + j, where m is the number of elements in each row. This number is actually the subscript of the element after compressing the two-dimensional array to one dimension.

We can directly dichotomize the number, and the value range of the number is determined, which is [0, mn). After we have the number, we can restore its row number and column number. Moreover, according to the information in the title, we can determine that the elements in the matrix also have an increasing order according to the number. Therefore, we can boldly use dichotomization:

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        n = len(matrix)
        if n == 0:
            return False
        
        m = len(matrix[0])
        if m == 0:
            return False
        
        l, r = 0, m*n
        
        while l+1 < r:
            mid = (l + r) >> 1
            # Restore row and column numbers
            x, y = mid // m, mid % m
            if matrix[x][y] <= target:
                l = mid
            else:
                r = mid
        return matrix[l // m][l % m] == target

In this way, our code is greatly simplified, and the efficiency of code operation is also improved, which is faster than using the method of twice dichotomy.

summary

This question ends here. It's not difficult. It's not difficult to come up with an answer. However, if you encounter in the interview, it is not easy to think of the optimal solution at the first time. On the one hand, we need to accumulate experience, see the topic, and guess what type of algorithm should be used. On the other hand, we also need to have enough understanding and analysis of the problem, so as to read the hidden information in the topic.

Another variant of this problem is to remove the restriction that the first element of each line is greater than the last element of the previous line. Then the property that the elements in the matrix increase by number does not exist. In this case, how should we use dichotomy? This question is LeetCode's 240 question. If you are interested, you can try to do this question to see how much the solution has changed.

Topics: Interview