20210330 LeetCode 每日一题(搜索二维矩阵)

354 阅读3分钟

题目描述

原题链接:搜索二维矩阵

编写一个高效的算法来判断  m x n  矩阵中,是否存在一个目标值。该矩阵具有如下特性:

每行中的整数从左到右按升序排列。 每行的第一个整数大于前一行的最后一个整数。

示例

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

解答

一般来讲题中一旦看到有序字眼,第一反应应该是想到二分查找。这里贴一个笔者常用的二分查找模板:

function binarySearch(arr, target) {
  let start = 0,
    end = arr.length - 1,
    mid
  while (start <= end) {
    mid = Math.floor(start + (end - start) / 2)
    if (arr[mid] > target) {
      end = mid - 1
    } else if (arr[mid] < target) {
      start = mid + 1
    } else {
      return true
    }
  }
  return false
}

根据具体的题目要求模板中修改对应的条件,问题就迎刃而解。

解法一

回到本题,笔者最先想到的思路根据每一组的第一项用二分查找法找到目标值可能处在的行,即定义一个函数 searchRow,输入一个二维矩阵和目标值,返回目标值可能处在的行。从中间开始比较,当中间一行的第一个数大于目标值,说明后半部分和这一行都不用考虑了,当中间一行的第一个数小于或等于目标值时,则不考虑前半部分:

var searchRow = function (matrix, target) {
  var start = -1,
    end = matrix.length - 1,
    mid
  while (start < end) {
    var mid = Math.floor((end - start + 1) / 2) + start
    if (matrix[mid][0] <= target) {
      start = mid
    } else if (matrix[mid][0] > target) {
      end = mid - 1
    }
  }
  return start
}

找到了目标值可能处在的行后问题就简化成一维顺序矩阵中查找,可以运用标准的二分查找法:

var searchCol = function (arr, target) {
  var start = 0,
    end = arr.length - 1,
    mid
  while (start <= end) {
    mid = Math.floor(start + (end - start) / 2)
    if (arr[mid] === target) {
      return true
    } else if (arr[mid] > target) {
      end = mid - 1
    } else {
      start = mid + 1
    }
  }
  return false
}

完整的代码如下:

var searchMatrix = function (matrix, target) {
  const rowIndex = searchRow(matrix, target)
  if (rowIndex < 0) {
    return false
  }
  return searchCol(matrix[rowIndex], target)
}

var searchRow = function (matrix, target) {
  var start = -1,
    end = matrix.length - 1,
    mid
  while (start < end) {
    var mid = Math.floor((end - start + 1) / 2) + start
    if (matrix[mid][0] <= target) {
      start = mid
    } else if (matrix[mid][0] > target) {
      end = mid - 1
    }
  }
  return start
}

var searchCol = function (arr, target) {
  var start = 0,
    end = arr.length - 1,
    mid
  while (start <= end) {
    mid = Math.floor(start + (end - start) / 2)
    if (arr[mid] === target) {
      return true
    } else if (arr[mid] > target) {
      end = mid - 1
    } else {
      start = mid + 1
    }
  }
  return false
}

复杂度分析:

  • 时间复杂度:O(log(mn)),其中 m、n 分别为矩阵的行数和列数
  • 空间复杂度:O(1),只采用了常数个临时变量

解法二

由于这个矩阵中数有着特殊的排列顺序,将这个二维矩阵拍平后就是一个升序的一维数组,所以利用二维矩阵行号和列号与一维矩阵的下标映射关系,我们完全可以把这题直接当成一个升序一维矩阵用二分法模板来解答。

这里插播一下一维矩阵和二维矩阵的下标映射技巧,假设我们有这样一个二维矩阵:

1  3  5  7
10 11 16 20
20 30 34 50

例如将第 2 行第 3 列的 16 这个数,其矩阵的坐标是(2,1),而映射到一维数组的时候,其对应的下标索引 idx=6

idx=6=i*n+j=1*4+2=6

那如何通过 idx=6 反向得到矩阵的坐标呢?

i=idx/n=6/4 =1
j=idx%n=6%4 =2

得到矩阵的坐标为(i,j) ==>(1,2)

回到本题,我们将二维矩阵视为一维矩阵去解答,首先定义一个下标映射的方法 mapCoord

var mapCoord = function (index, itemLength) {
  const row = Math.floor(index / itemLength)
  const col = index % itemLength
  return { row, col }
}

然后套用模板:

var searchMatrix = function (matrix, target) {
  if (matrix.length === 0 || matrix[0].length === 0) return false
  var start = 0,
    end = matrix.length * matrix[0].length - 1,
    mid,
    midCoord
  while (start <= end) {
    mid = Math.floor(start + (end - start) / 2)
    midCoord = mapCoord(mid, matrix[0].length)
    if (matrix[midCoord.row][midCoord.col] > target) {
      end = mid - 1
    } else if (matrix[midCoord.row][midCoord.col] < target) {
      start = mid + 1
    } else {
      return true
    }
  }
  return false
}

是不是感觉更简单了呢?

复杂度分析:

  • 时间复杂度:O(log(mn)),其中 m、n 分别为矩阵的行数和列数
  • 空间复杂度:O(1),只采用了常数个临时变量

参考链接

二维矩阵的常见转换技巧