问题说明(含示例)
问题描述 :给定一个 m x n
的矩阵(元素为浮点数),矩阵具有每行从左到右升序、每列从上到下升序的特性。需搜索矩阵中与目标值 target
最接近的数值 ,并返回该数值及其所在的行号 和列号(行号和列号从 0 开始)。
示例:现有矩阵:
[
[1, 5, 9, 13, 17],
[2, 6, 10, 14, 18],
[3, 7, 11, 15, 19],
[4, 8, 12, 16, 20]
]
-
输入:
target = 5.1
输出:[5, 0, 1]
解释:5 与 5.1 的差值为 0.1,是矩阵中最小的差值,其位置为第 0 行第 1 列。 -
输入:
target = 11.7
输出:[12, 3, 2]
解释:12 与 11.7 的差值为 0.3,是最小差值,位置为第 3 行第 2 列。 -
输入:
target = 100
输出:[20, 3, 4]
解释:矩阵中最大元素为 20,与 100 的差值最小,位置为第 3 行第 4 列。
解题关键
利用矩阵 "每行升序、每列升序 " 的特性,采用右上角起点搜索法 ,高效缩小搜索范围,避免暴力遍历(暴力法时间复杂度 O(mn)
,此方法优化至 O(m + n)
)。核心思路:
- 起点选择 :从矩阵右上角(
i=0, j=cols-1
)开始搜索,该位置是当前行的最大值、当前列的最小值,便于快速判断搜索方向。 - 搜索方向 :
- 若当前元素
matrix[i][j] < target
:说明当前行左侧元素均小于target
,需向下移动(i += 1
),寻找更大的元素; - 若当前元素
matrix[i][j] > target
:说明当前列下方元素均大于target
,需向左移动(j -= 1
),寻找更小的元素;
- 若当前元素
- 记录最接近值 :在搜索过程中,实时计算当前元素与
target
的差值,更新 "最小差值" 及对应的元素值、行号、列号。 - 终止条件 :当
i
超出矩阵行数或j
小于 0 时,搜索结束,返回记录的最接近值信息。
对应代码
python
class Solution(object):
def searchMatrix(self, matrix, target):
"""
:type matrix: List[List[float]]
:type target: float
:rtype: List
"""
if not matrix or not matrix[0]: # 处理空矩阵或者第一行为空的矩阵
return []
rows = len(matrix)
cols = len(matrix[0])
# 初始化起点为右上角
i, j = 0, cols - 1
min_diff = float('inf') # 最小差值,初始为无穷大
res_val = None # 最接近的数值
res_row = -1 # 最接近数值的行号
res_col = -1 # 最接近数值的列号
while i < rows and j >= 0:
current = matrix[i][j]
current_diff = abs(current - target) # 当前差值
# 更新最小差值及结果
if current_diff < min_diff:
min_diff = current_diff
res_val = current
res_row = i
res_col = j
# 若差值为0,直接返回(已找到最接近值)
if min_diff == 0:
return [res_val, res_row, res_col]
# 调整搜索方向
if current < target:
i += 1 # 当前元素偏小,向下寻找更大值
else:
j -= 1 # 当前元素偏大,向左寻找更小值
return [res_val, res_row, res_col]
对应的基础知识
实现该算法需掌握以下基础概念与操作:
-
二维列表的访问与遍历
- 矩阵在 Python 中以二维列表表示,
matrix[i][j]
访问第i
行第j
列的元素; - 通过
len(matrix)
获取行数(外层列表长度),len(matrix[0])
获取列数(内层列表长度)。
- 矩阵在 Python 中以二维列表表示,
-
条件判断与方向调整
- 核心逻辑
if current < target: i += 1 else: j -= 1
利用矩阵有序性,通过比较当前元素与目标值,动态调整搜索方向,避免无效遍历。
- 核心逻辑
-
差值计算与最小值更新
- 用
abs(current - target)
计算当前元素与目标值的绝对值差值; - 通过
if current_diff < min_diff
实时更新 "最小差值" 及对应元素信息,确保最终结果是全局最接近值。
- 用
-
循环与边界控制
- 循环条件
i < rows and j >= 0
确保搜索范围在矩阵内(i
不超过行数,j
不小于 0); - 当循环终止时,已遍历所有可能的 "潜在接近值",直接返回记录的结果。
- 循环条件
对应的进阶知识
该问题的解决涉及算法优化与矩阵特性利用的进阶思想:
-
时间复杂度优化
- 暴力法需遍历矩阵所有元素(
O(mn)
),而本算法最多移动m + n
步(从右上角到左下角的最坏路径),时间复杂度降至O(m + n)
,尤其适合大规模矩阵(如m, n = 10^4
时,效率提升显著)。
- 暴力法需遍历矩阵所有元素(
-
起点选择的科学性
- 选择右上角(或左下角)作为起点的核心原因是:该位置是 "行最大值" 和 "列最小值" 的交点,能通过一次比较明确排除一行或一列的元素(例如
current < target
时,当前行所有元素均小于target
,可直接排除)。 - 若选择左上角或右下角作为起点,则无法通过一次比较排除整行或整列,会增加无效搜索步骤。
- 选择右上角(或左下角)作为起点的核心原因是:该位置是 "行最大值" 和 "列最小值" 的交点,能通过一次比较明确排除一行或一列的元素(例如
-
边界情况的隐性处理
- 当
target
小于矩阵所有元素时:搜索会一直向左移动,最终停在左上角元素(最小元素),符合预期; - 当
target
大于矩阵所有元素时:搜索会一直向下移动,最终停在右下角元素(最大元素),符合预期; - 当矩阵中存在与
target
相等的元素时:min_diff
会变为 0,触发提前返回,减少不必要的搜索。
- 当
-
与二分查找的对比
- 对于每行单独二分查找(时间复杂度
O(m log n)
),本算法在m
和n
接近时更优(O(m + n)
优于O(m log n)
); - 核心差异:二分查找依赖 "完全有序"(如每行是前一行的延续),而本算法仅利用 "行内、列内有序" 的弱条件,适用范围更广。
- 对于每行单独二分查找(时间复杂度
编程思维与启示
-
"特性驱动" 的算法设计 :面对有序数据结构(如本题的矩阵),不要急于暴力遍历,而是先观察其有序特性(行 / 列升序),思考如何利用特性减少搜索范围。本题正是通过 "右上角元素是行最大、列最小" 的特性,设计出
O(m + n)
的高效算法。 -
"关键起点" 的选择逻辑:复杂问题的突破口往往在于找到一个 "能简化决策" 的起点。右上角作为起点,每次比较都能明确排除一行或一列,这种 "非此即彼" 的决策逻辑,是减少无效操作的核心。
-
"实时更新" 的贪心思想:在搜索过程中,通过实时比较并更新 "最小差值",确保每一步都保留当前最优解,最终自然得到全局最优解。这种 "贪心" 策略避免了存储所有可能解再比较的额外空间开销。
-
"边界先行" 的健壮性意识 :代码开篇即处理空矩阵的情况(
if not matrix or not matrix[0]
),体现了 "先防御、再逻辑" 的编程习惯。提前处理异常输入,能避免后续代码因索引错误或无效计算崩溃。 -
"空间优化" 的潜意识 :算法仅使用常数个变量(
i, j, min_diff
等)存储中间结果,未依赖额外的数据结构(如列表存储所有元素),体现了对空间复杂度的优化意识,尤其适合大规模数据场景。