数据研发笔试Leetcode刷题笔记02:二维数组中的查找

文章目录

  • 1 题目描述
  • 2 解题思路
    • 2.1 直接遍历二维数组
    • 2.2 巧妙利用标志数性质
    • 2.3 递归
  • 3 代码实现(Python3)
  • 4 复杂度分析

1 题目描述

来源:力扣(LeetCode)

在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
数据研发笔试Leetcode刷题笔记02:二维数组中的查找_第1张图片

2 解题思路

2.1 直接遍历二维数组

如果不考虑二维数组排好序的特点,则直接遍历整个二维数组的每一个元素,判断目标值是否在二维数组中存在。

依次遍历二维数组的每一行和每一列。如果找到一个元素等于目标值,则返回 true。如果遍历完毕仍未找到等于目标值的元素,则返回 false。

2.2 巧妙利用标志数性质

若使用暴力法遍历矩阵 matrix ,则时间复杂度为 O(N*M)O(N∗M) 。暴力法未利用矩阵 “从上到下递增、从左到右递增” 的特点,显然不是最优解法。本题解利用矩阵特点引入标志数,并通过标志数性质降低算法时间复杂度。1

标志数引入: 此类矩阵中左下角和右上角元素有特殊性,称为标志数。

  • 左下角元素: 为所在列最大元素,所在行最小元素。
  • 右上角元素: 为所在行最大元素,所在列最小元素。

标志数性质: 将 matrix 中的左下角元素(标志数)记作 flag ,则有:

  • 若 flag > target ,则 target 一定在 flag 所在行的上方,即 flag 所在行可被消去。
  • 若 flag < target ,则 target 一定在 flag 所在列的右方,即 flag 所在列可被消去。

本题解以左下角元素为例,同理,右上角元素 也具有行(列)消去的性质。

算法流程: 根据以上性质,设计算法在每轮对比时消去一行(列)元素,以降低时间复杂度。

从矩阵 matrix 左下角元素(索引设为 (i, j) )开始遍历,并与目标值对比:

  • 当 matrix[i][j] > target 时: 行索引向上移动一格(即 i- -),即消去矩阵第 i 行元素;
  • 当 matrix[i][j] < target 时: 列索引向右移动一格(即 j++),即消去矩阵第 j 列元素;
  • 当 matrix[i][j] == target 时: 返回 true。
  • 若行索引或列索引越界,则代表矩阵中无目标值,返回 false。

算法本质: 每轮 i 或 j 移动后,相当于生成了“消去一行(列)的新矩阵”, 索引(i,j) 指向新矩阵的左下角元素(标志数),因此可重复使用以上性质消去行(列)。
数据研发笔试Leetcode刷题笔记02:二维数组中的查找_第2张图片

2.3 递归

在 mid列寻找满足条件 matrix[row - 1][mid]2:
数据研发笔试Leetcode刷题笔记02:二维数组中的查找_第3张图片

  • 由 target>9,可知 target 在 9 的右侧或下侧;
  • 由 target<14,可知target 在14 的上侧或左侧;

因此对左下和右上两个区域进行递归,直到遇到终止条件进行回溯,返回结果。 终止条件为:

  • 区域中没有元素;
  • target大于深色区域右下角的值(最大值)或小于深色区域左上角的值(最小值)

其中,找到黄色点的方法如下:

  • 列索引 mid 采用二分查找;
  • 行索引沿 mid列从上向下移动,并保持该位置元素小于target。

3 代码实现(Python3)

class Solution:
    def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
        # 法1 直接遍历二维数组
        try:
            for i, line in enumerate(matrix):
                if target >= line[0] and target <= line[-1] :
                    for j in matrix[i]:
                        if target == j:
                            return True
        except:
            return False
        return False
        
        # 法2 巧妙利用标志数性质
        i, j = len(matrix) - 1, 0
        while i >= 0 and j < len(matrix[0]):
            if matrix[i][j] > target: i -= 1
            elif matrix[i][j] < target: j += 1
            else: return True
        return False
        
        # 法3 递归
        if not matrix: return False

        def search_backtrack(left, up, right, down):
            if left > right or up > down:
                return False
            elif target < matrix[up][left] or target > matrix[down][right]:
                return False

            mid = left + (right - left) // 2

            row = up
            while row <= down and matrix[row][mid] <= target:
                if matrix[row][mid] == target:
                    return True
                row += 1

            return search_backtrack(left, row, mid - 1, down) or search_backtrack(mid + 1, up, right, row - 1)

        return search_backtrack(0, 0, len(matrix[0]) - 1, len(matrix) - 1)

    

4 复杂度分析

法1:直接遍历二维数组

  • 时间复杂度:O(NM)。二维数组中的每个元素都被遍历,因此时间复杂度为二维数组的大小。
  • 空间复杂度:O(1)。

法2:巧用标志数性质

  • 时间复杂度:O(N+M)。访问到的下标的行最多增加 n 次,列最多减少 m 次,因此循环体最多执行 n + m 次。
  • 空间复杂度:O(1)。

法3:递归

  • 时间复杂度:O(NlogN)
  • 空间复杂度:O(logN)

深入了解复杂度:数据分析学习总结笔记11:空间复杂度和时间复杂度


  1. 解题思路_作者:jyd ↩︎

  2. 解题思路_作者:z1m ↩︎

你可能感兴趣的:(算法,数据结构,leetcode,排序算法,python)