python 矩阵中寻找就接近的目标值 (矩阵-中等)含源码(八)
问题说明(含示例)
问题描述:给定一个 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 时,搜索结束,返回记录的最接近值信息。
对应代码
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 - 1min_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_diffres_val = currentres_row = ires_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
等)存储中间结果,未依赖额外的数据结构(如列表存储所有元素),体现了对空间复杂度的优化意识,尤其适合大规模数据场景。