全栈开发知识库

92.搜索二维矩阵

题目

给你一个满足下述两条属性的 m x n 整数矩阵:

  • 每行中的整数从左到右按非严格递增顺序排列。
  • 每行的第一个整数大于前一行的最后一个整数。

给你一个整数 target ,如果 target 在矩阵中,返回 true ;否则,返回 false 。

你必须编写一个时间复杂度为 O(log(m * n)) 的解决方案。

示例 1:

Image - 输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3 - 输出:true

示例 2:

Image

  • 输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13
  • 输出:false

提示:

  • m == matrix.length
  • n == matrix[i].length
  • 1 <= m, n <= 100
  • -104 <= matrix[i][j], target <= 104

思路

题目要求 O(log(m·n)),也就是必须二分。

把上面那两条性质连起来看,会发现一件事:

▎ 按行拼接读下来,整个矩阵就是一个完整的升序数组。

  1, 3, 5, 7, 10, 11, 16, 20, 23, 30, 34, 60
  └─第0行──┘ └──第1行───┘ └──第2行────┘

严格升序,一处不乱。第二条性质(行首 > 上行末尾)就是专门为了保证接缝处也递增才给的。

所以这题的本质是:在一个长度为 m·n 的有序数组里二分查找。"二维"只是它的存储方式,不是它的逻辑结构。

注意 O(log(m·n)) 这个复杂度要求本身就在提示你: 它等于 log m + log n,说明目标就是把 m·n 个元素当成一个整体来折半。

既然要当一维数组用,就得能把"第 idx 个元素"翻译成"第几行第几列"。

设列数为 n(每行有 n 个元素),则:

  • row = idx / n 整除:跳过了几个完整的行
  • col = idx % n 取余:在本行内的偏移
  class Solution {

      /**
       * 搜索二维矩阵 —— 把矩阵当成一维有序数组做二分
       *
       * 核心思想:题目保证"每行升序 + 行首大于上行末尾",
       *          意味着按行拼接后整个矩阵就是一个完整的升序数组。
       *          于是在 [0, m*n - 1] 上二分,取值时用 idx/n、idx%n 换算成坐标。
       *
       * 时间复杂度 O(log(m * n)) = O(log m + log n)
       * 空间复杂度 O(1):没有真的把矩阵展开成数组
       */
      fun searchMatrix(matrix: Array<IntArray>, target: Int): Boolean {
          // 题目保证矩阵非空,这里仍做一次防御性判断
          if (matrix.isEmpty() || matrix[0].isEmpty()) return false

          val m = matrix.size
          val n = matrix[0].size            // 列数,下面换算全靠它

          // 闭区间 [left, right],覆盖全部 m*n 个"虚拟下标"
          var left = 0
          var right = m * n - 1

          while (left <= right) {
              // 防溢出写法,别写 (left + right) / 2
              val mid = left + (right - left) / 2

              // ⚠️ 关键:把一维下标翻译成二维坐标
              //    除数和模数都是 n(列数),因为走满 n 个元素才换一行
              val value = matrix[mid / n][mid % n]

              when {
                  value == target -> return true       // 命中
                  value < target  -> left = mid + 1    // 太小,往右半边找
                  else            -> right = mid - 1   // 太大,往左半边找
              }
          }

          return false                                 // 区间耗尽,不存在
      }
  }

复杂度

  • 时间复杂度: O(log(m * n)) = O(log m + log n)
  • 空间复杂度: O(1)没有真的把矩阵展开成数组


  • 邮箱 :charon.chui@gmail.com
  • Good Luck!
本页由 Markdown 自动渲染 · 共 628 篇笔记