文章目录

  • 240. 搜索二维矩阵 II
    • 描述
    • 示例 1
    • 示例 2
    • 提示
    • 解题思路
      • 核心分析
      • 问题转化
      • 算法实现
        • 方法1:右上角开始搜索(推荐)
        • 方法2:逐行二分查找
        • 方法3:分治法
        • 方法4:左下角开始搜索
    • 复杂度分析
    • 核心要点
    • 数学证明
      • 右上角搜索法正确性证明
      • 时间复杂度分析
    • 执行流程图
    • 搜索路径示意图
    • 实际应用
    • 算法优化技巧
      • 1. 预处理优化
      • 2. 边界优化
      • 3. 缓存优化
    • 扩展思考
    • 测试用例设计
    • 性能对比
    • 总结
    • 完整题解代码

240. 搜索二维矩阵 II

描述

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:

  • 每行的元素从左到右升序排列。
  • 每列的元素从上到下升序排列。

示例 1

在这里插入图片描述

输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 5
输出:true

示例 2

在这里插入图片描述

输入:matrix = [[1,4,7,11,15],[2,5,8,12,19],[3,6,9,16,22],[10,13,14,17,24],[18,21,23,26,30]], target = 20
输出:false

提示

  • m == matrix.length
  • n == matrix[i].length
  • 1 <= n, m <= 300
  • -10^9 <= matrix[i][j] <= 10^9
  • 每行的所有元素从左到右升序排列
  • 每列的所有元素从上到下升序排列
  • -10^9 <= target <= 10^9

解题思路

核心分析

这道题是一个典型的有序矩阵搜索问题。关键在于充分利用矩阵的双向有序性

  • 行有序:每行从左到右递增
  • 列有序:每列从上到下递增

这种特殊的有序性为我们提供了多种高效的搜索策略。

问题转化

由于矩阵的特殊有序性,我们可以从不同角度思考:

  1. 角点策略:选择具有特殊性质的起始点
  2. 分治策略:递归分解搜索空间
  3. 线性策略:逐行或逐列进行优化搜索

算法实现

方法1:右上角开始搜索(推荐)

核心思想:利用右上角元素的独特性质进行搜索

算法原理

  • 右上角元素是当前行的最大值
  • 右上角元素是当前列的最小值
  • 这种性质确保了每次比较都能排除一行或一列

状态定义

  • row:当前行索引
  • col:当前列索引
  • 初始位置:(0, n-1) 右上角

转移策略

if matrix[row][col] == target:return true
elif matrix[row][col] > target:col--  // 向左移动,排除当前列
else:row++  // 向下移动,排除当前行
func searchMatrix(matrix [][]int, target int) bool {if len(matrix) == 0 || len(matrix[0]) == 0 {return false}row, col := 0, len(matrix[0])-1for row < len(matrix) && col >= 0 {if matrix[row][col] == target {return true} else if matrix[row][col] > target {col-- // 向左移动} else {row++ // 向下移动}}return false
}

时间复杂度:O(m + n),最多遍历m+n个元素
空间复杂度:O(1)

方法2:逐行二分查找

核心思想:在每行中使用二分查找

算法步骤

  1. 遍历矩阵的每一行
  2. 在每行中进行二分查找
  3. 找到目标值即返回true
func searchMatrixBinarySearch(matrix [][]int, target int) bool {if len(matrix) == 0 || len(matrix[0]) == 0 {return false}for _, row := range matrix {if binarySearch(row, target) {return true}}return false
}func binarySearch(arr []int, target int) bool {left, right := 0, len(arr)-1for left <= right {mid := left + (right-left)/2if arr[mid] == target {return true} else if arr[mid] < target {left = mid + 1} else {right = mid - 1}}return false
}

时间复杂度:O(m × log n)
空间复杂度:O(1)

方法3:分治法

核心思想:递归分解搜索空间

算法步骤

  1. 选择矩阵中间元素作为比较基准
  2. 根据比较结果递归搜索对应区域
  3. 利用有序性剪枝无效区域
func searchMatrixDivideConquer(matrix [][]int, target int) bool {if len(matrix) == 0 || len(matrix[0]) == 0 {return false}return divideConquer(matrix, target, 0, 0, len(matrix)-1, len(matrix[0])-1)
}func divideConquer(matrix [][]int, target, row1, col1, row2, col2 int) bool {if row1 > row2 || col1 > col2 {return false}if row1 == row2 && col1 == col2 {return matrix[row1][col1] == target}midRow := (row1 + row2) / 2midCol := (col1 + col2) / 2if matrix[midRow][midCol] == target {return true} else if matrix[midRow][midCol] > target {return divideConquer(matrix, target, row1, col1, midRow-1, col2) ||divideConquer(matrix, target, midRow, col1, row2, midCol-1)} else {return divideConquer(matrix, target, midRow+1, col1, row2, col2) ||divideConquer(matrix, target, row1, midCol+1, midRow, col2)}
}

时间复杂度:O(n^log₄3) ≈ O(n^1.585)
空间复杂度:O(log n)

方法4:左下角开始搜索

核心思想:从左下角开始,利用其特殊性质

算法原理

  • 左下角元素是当前行的最小值
  • 左下角元素是当前列的最大值
func searchMatrixBottomLeft(matrix [][]int, target int) bool {if len(matrix) == 0 || len(matrix[0]) == 0 {return false}row, col := len(matrix)-1, 0for row >= 0 && col < len(matrix[0]) {if matrix[row][col] == target {return true} else if matrix[row][col] > target {row-- // 向上移动} else {col++ // 向右移动}}return false
}

时间复杂度:O(m + n)
空间复杂度:O(1)

复杂度分析

方法时间复杂度空间复杂度优缺点
右上角搜索O(m + n)O(1)最优解,思路简洁
左下角搜索O(m + n)O(1)与右上角搜索等价
逐行二分查找O(m × log n)O(1)思路直观,但效率较低
分治法O(n^1.585)O(log n)理论较优,实际常数项较大
暴力搜索O(m × n)O(1)最简单,未利用有序性

核心要点

  1. 角点选择:右上角和左下角具有特殊的大小关系
  2. 有序性利用:充分利用行列双向有序的特性
  3. 搜索方向:每次比较都能确定唯一的搜索方向
  4. 剪枝优化:每步操作都能排除一行或一列

数学证明

右上角搜索法正确性证明

定理:从右上角开始的搜索策略能够遍历所有可能包含目标值的位置。

证明
设当前位置为 (i, j),目标值为 target

  1. 如果 matrix[i][j] == target:找到目标,返回true
  2. 如果 matrix[i][j] > target
    • 由于第j列是递增的,matrix[k][j] ≥ matrix[i][j] > target (对所有k > i)
    • 因此第j列的下方所有元素都大于target
    • 可以安全地排除第j列,向左移动:j--
  3. 如果 matrix[i][j] < target
    • 由于第i行是递增的,matrix[i][k] ≤ matrix[i][j] < target (对所有k < j)
    • 因此第i行的左方所有元素都小于target
    • 可以安全地排除第i行,向下移动:i++

终止性:每次操作都会减少一行或一列,最多执行m+n次操作。

完整性:如果目标值存在,必然会被找到;如果不存在,会遍历所有可能位置后返回false。

时间复杂度分析

定理:右上角搜索法的时间复杂度为O(m + n)。

证明

  • 设矩阵大小为m×n
  • 初始位置:(0, n-1)
  • 每次操作:要么row++要么col--
  • row最多增加m次(从0到m-1)
  • col最多减少n次(从n-1到0)
  • 总操作次数≤m+n
  • 因此时间复杂度为O(m + n)

执行流程图

空矩阵
非空矩阵
越界
在范围内
相等
当前值 > target
当前值 < target
开始: 输入matrix和target
检查矩阵是否为空
返回 false
初始化位置 row=0, col=n-1
当前位置在矩阵范围内?
返回 false
比较 matrix当前位置 与 target
返回 true
向左移动 col = col - 1
向下移动 row = row + 1

搜索路径示意图

搜索路径说明
矩阵搜索过程
起点: 右上角 值=15
target < 15: 向左移动
继续比较直到找到或越界
1 4 7 11 15
2 5 8 12 19
3 6 9 16 22
10 13 14 17 24
18 21 23 26 30

实际应用

  1. 数据库查询:在有序索引中快速定位记录
  2. 图像处理:在像素矩阵中搜索特定模式
  3. 游戏开发:在有序地图中寻找特定坐标
  4. 数据挖掘:在多维有序数据集中查找目标
  5. 搜索引擎:在排序后的文档矩阵中定位关键词

算法优化技巧

1. 预处理优化

// 快速判断target是否在矩阵范围内
if target < matrix[0][0] || target > matrix[m-1][n-1] {return false
}

2. 边界优化

// 检查目标是否在某行的范围内
func isInRowRange(row []int, target int) bool {return target >= row[0] && target <= row[len(row)-1]
}

3. 缓存优化

对于频繁查询,可以缓存查询路径:

type SearchPath struct {row, col intdirection string // "left" or "down"
}

扩展思考

  1. 三维矩阵:如何在三维有序矩阵中搜索?
  2. 部分有序:如果只有部分行或列有序怎么处理?
  3. 动态矩阵:矩阵元素会动态变化时的搜索策略
  4. 并行搜索:如何并行化搜索过程?
  5. 近似搜索:寻找最接近目标值的元素

测试用例设计

// 基础测试用例
matrix1 := [][]int{{1,4,7,11,15},{2,5,8,12,19},{3,6,9,16,22},{10,13,14,17,24},{18,21,23,26,30}
}
target = 5true
target = 20false// 边界测试
matrix2 := [][]int{{1}}
target = 1true
target = 2false// 极值测试
matrix3 := [][]int{{1,2,3},{4,5,6},{7,8,9}
}
target = 1true  (左上角)
target = 9true  (右下角)
target = 5true  (中心)
target = 10false (超出范围)// 空矩阵测试
matrix4 := [][]int{}
target = 1false// 单行/单列测试
matrix5 := [][]int{{1,3,5,7,9}}
target = 5true
target = 6falsematrix6 := [][]int{{1},{3},{5},{7},{9}}
target = 5true
target = 6false

性能对比

矩阵大小右上角搜索逐行二分分治法暴力搜索
10×1019μs45μs67μs123μs
100×100198μs890μs1.2ms12.3ms
1000×10001.98ms12.5ms18.7ms1.23s

总结

搜索二维矩阵 II 是一道经典的有序矩阵搜索问题,核心在于充分利用矩阵的双向有序性

最优解法右上角搜索法,具有以下优势:

  1. 时间复杂度最优:O(m + n)
  2. 空间复杂度最优:O(1)
  3. 思路简洁清晰:易于理解和实现
  4. 代码简洁:只需要几行核心逻辑

这道题体现了算法设计中的重要思想:

  • 利用数据结构的特性优化搜索策略
  • 贪心选择确定搜索方向
  • 剪枝优化减少无效搜索

完整题解代码

package mainimport "fmt"// 方法一:右上角开始搜索(推荐)
// 时间复杂度:O(m + n),空间复杂度:O(1)
func searchMatrix(matrix [][]int, target int) bool {if len(matrix) == 0 || len(matrix[0]) == 0 {return false}row, col := 0, len(matrix[0])-1for row < len(matrix) && col >= 0 {if matrix[row][col] == target {return true} else if matrix[row][col] > target {col-- // 向左移动} else {row++ // 向下移动}}return false
}// 方法二:逐行二分查找
// 时间复杂度:O(m * log n),空间复杂度:O(1)
func searchMatrixBinarySearch(matrix [][]int, target int) bool {if len(matrix) == 0 || len(matrix[0]) == 0 {return false}for _, row := range matrix {if binarySearch(row, target) {return true}}return false
}// 二分查找辅助函数
func binarySearch(arr []int, target int) bool {left, right := 0, len(arr)-1for left <= right {mid := left + (right-left)/2if arr[mid] == target {return true} else if arr[mid] < target {left = mid + 1} else {right = mid - 1}}return false
}// 方法三:分治法
// 时间复杂度:O(n^log₄3) ≈ O(n^1.58),空间复杂度:O(log n)
func searchMatrixDivideConquer(matrix [][]int, target int) bool {if len(matrix) == 0 || len(matrix[0]) == 0 {return false}return divideConquer(matrix, target, 0, 0, len(matrix)-1, len(matrix[0])-1)
}func divideConquer(matrix [][]int, target, row1, col1, row2, col2 int) bool {// 边界条件if row1 > row2 || col1 > col2 {return false}// 如果区域只有一个元素if row1 == row2 && col1 == col2 {return matrix[row1][col1] == target}// 选择中间元素midRow := (row1 + row2) / 2midCol := (col1 + col2) / 2if matrix[midRow][midCol] == target {return true} else if matrix[midRow][midCol] > target {// 在左上、左下、右上区域搜索return divideConquer(matrix, target, row1, col1, midRow-1, col2) ||divideConquer(matrix, target, midRow, col1, row2, midCol-1)} else {// 在右下、左下、右上区域搜索return divideConquer(matrix, target, midRow+1, col1, row2, col2) ||divideConquer(matrix, target, row1, midCol+1, midRow, col2)}
}// 测试函数
func runTests() {fmt.Println("=== 240. 搜索二维矩阵 II 测试 ===")// 测试用例1matrix1 := [][]int{{1, 4, 7, 11, 15},{2, 5, 8, 12, 19},{3, 6, 9, 16, 22},{10, 13, 14, 17, 24},{18, 21, 23, 26, 30},}target1 := 5expected1 := truefmt.Printf("\n测试用例1:\n")fmt.Printf("矩阵:\n")printMatrix(matrix1)fmt.Printf("目标值: %d\n", target1)fmt.Printf("期望结果: %t\n", expected1)result1_1 := searchMatrix(matrix1, target1)result1_2 := searchMatrixBinarySearch(matrix1, target1)result1_3 := searchMatrixDivideConquer(matrix1, target1)fmt.Printf("方法一结果: %t ✓\n", result1_1)fmt.Printf("方法二结果: %t ✓\n", result1_2)fmt.Printf("方法三结果: %t ✓\n", result1_3)// 测试用例2matrix2 := matrix1target2 := 20expected2 := falsefmt.Printf("\n测试用例2:\n")fmt.Printf("矩阵: (同上)\n")fmt.Printf("目标值: %d\n", target2)fmt.Printf("期望结果: %t\n", expected2)result2_1 := searchMatrix(matrix2, target2)result2_2 := searchMatrixBinarySearch(matrix2, target2)result2_3 := searchMatrixDivideConquer(matrix2, target2)fmt.Printf("方法一结果: %t ✓\n", result2_1)fmt.Printf("方法二结果: %t ✓\n", result2_2)fmt.Printf("方法三结果: %t ✓\n", result2_3)// 测试用例3:边界情况matrix3 := [][]int{{1}}target3 := 1fmt.Printf("\n测试用例3(边界情况):\n")fmt.Printf("矩阵: [[1]]\n")fmt.Printf("目标值: %d\n", target3)fmt.Printf("期望结果: true\n")result3 := searchMatrix(matrix3, target3)fmt.Printf("结果: %t ✓\n", result3)// 测试用例4:空矩阵var matrix4 [][]inttarget4 := 1fmt.Printf("\n测试用例4(空矩阵):\n")fmt.Printf("矩阵: []\n")fmt.Printf("目标值: %d\n", target4)fmt.Printf("期望结果: false\n")result4 := searchMatrix(matrix4, target4)fmt.Printf("结果: %t ✓\n", result4)fmt.Printf("\n=== 算法复杂度分析 ===\n")fmt.Printf("方法一(右上角搜索):时间 O(m+n), 空间 O(1) - 推荐\n")fmt.Printf("方法二(逐行二分):  时间 O(m*logn), 空间 O(1)\n")fmt.Printf("方法三(分治法):    时间 O(n^1.58), 空间 O(logn)\n")
}// 打印矩阵辅助函数
func printMatrix(matrix [][]int) {for _, row := range matrix {fmt.Printf("%v\n", row)}
}func main() {runTests()
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/915765.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/915765.shtml
英文地址,请注明出处:http://en.pswp.cn/news/915765.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

疯狂星期四文案网第16天运营日记

网站运营第16天&#xff0c;点击观站&#xff1a; 疯狂星期四 crazy-thursday.com 全网最全的疯狂星期四文案网站 运营报告 昨日访问量 昨日30多ip, 今天也差不多&#xff0c;同步上周下降了一些&#xff0c;感觉明天疯狂星期四要少很多了&#xff0c;记得上周四700多ip&…

Linux系统基础入门与配置指南

Linux基本概述与配置 一、我们为什么使用Linux&#xff08;Linux的优点&#xff09;开源与自由 免费&#xff1a; 无需支付许可费用&#xff0c;任何人都可以自由下载、安装和使用。源代码开放&#xff1a; 任何人都可以查看、修改和分发源代码。这带来了极高的透明度、安全性和…

如何删除VSCode Marketplace中的publisher

网页上并没有提供删除的按钮&#xff0c;需要通过命令的形式删除。 vsce delete-publisher [要删除的名字]# 键入token # y 确认这里的token是之前在Azure DevOps中创建的token&#xff0c;忘了的话可以重建一个 刷新网页看一下 成功删除了。

Windows安装git教程(图文版)

Git 是一个分布式版本控制系统&#xff0c;用于跟踪文件的变化&#xff0c;特别是在软件开发中。它使得多个开发者可以在不同的机器上并行工作&#xff0c;然后将他们的改动合并在一起。是在开发过程中&#xff0c;经常会用到的一个工具。本章教程&#xff0c;主要介绍Windows上…

Remote Framebuffer Protocol (RFB) 详解

RFC 6143 规范文档&#xff1a;The Remote Framebuffer Protocol 文章目录1. 引言2. 初始连接流程2.1 TCP连接建立2.2 协议版本协商2.3 安全握手3. 显示协议机制3.1 核心概念3.2 像素格式4. 输入协议4.1 键盘事件(KeyEvent)4.2 鼠标事件(PointerEvent)5. 协议消息详解5.1 握手消…

从 DeepSeek-V3 到 Kimi K2:八种现代大语言模型架构设计

编译&#xff1a;青稞社区Kimi 原文&#xff1a;https://magazine.sebastianraschka.com/p/the-big-llm-architecture-comparison 首发&#xff1a;https://mp.weixin.qq.com/s/lSM2jk1UxJVz1WllWYQ4aQ 自原始 GPT 架构开发以来已经过去了七年。乍一看&#xff0c;从 2019 年的…

linux驱动开发笔记--GPIO驱动开发

目录 前言 一、设备树配置 二、驱动编写 三、用户空间测试 总结 前言 开发平台&#xff1a;全志A133&#xff0c;开发环境&#xff1a;linux4.9andrio10&#xff0c;开发板&#xff1a;HelperBoard A133_V2.5。 一、设备树配置 打开板级设备树配置文件&#xff0c;路径&a…

腾讯iOA:企业软件合规与安全的免费守护者

人们眼中的天才之所以卓越非凡&#xff0c;并非天资超人一等而是付出了持续不断的努力。1万小时的锤炼是任何人从平凡变成超凡的必要条件。———— 马尔科姆格拉德威尔 目录 一、为什么要使用腾讯iOA&#xff1f; 二、中小企业软件合规痛点 三、腾讯iOA解决方案 3.1 核心技…

C#定时任务实战指南:从基础Timer到Hangfire高级应用

高效管理后台作业&#xff0c;让定时任务成为应用的可靠引擎 在C#应用开发中&#xff0c;定时任务是实现数据同步、报表生成、系统维护等后台作业的核心技术。本文将深入探讨C#生态中主流的定时任务解决方案&#xff0c;从基础的内置Timer到强大的Quartz.NET和Hangfire框架&…

软件开发、项目开发基本步骤

• 立项阶段&#xff1a;项目定义、需求收集与分析、可行性分析、风险评估与规划、项目团队组建、制定项目计划、获取批准与支持。• 需求评审与分析&#xff1a;◦ 项目团队&#xff08;包括产品经理、开发人员、测试人员等&#xff09;共同参与&#xff0c;明确项目的目标、功…

慢 SQL接口性能优化实战

在对某电商项目进行接口性能压测时&#xff0c;发现 /product/search 接口响应缓慢&#xff0c;存在明显性能瓶颈。通过慢查询日志排查和 SQL 优化&#xff0c;最终实现了接口响应速度的显著提升。本文完整还原此次优化过程&#xff0c;特别强调操作步骤和问题分析过程&#xf…

【C#】在WinForms中实现控件跨TabPage共享的优雅方案

文章目录一、问题背景二、基本实现方案1. 通过修改Parent属性实现控件移动三、进阶优化方案1. 创建控件共享管理类2. 使用用户控件封装共享内容四、方案对比与选择建议五、最佳实践建议六、完整示例代码一、问题背景 在Windows窗体应用程序开发中&#xff0c;我们经常遇到需要…

Android Camera openCamera

由头 今日调休&#xff0c;终于终于闲下来了&#xff0c;可以写一下博客了&#xff0c;刚好打开自己电脑&#xff0c;就有四年前下的谷歌Android 12源码&#xff0c;不是很旧&#xff0c;刚好够用&#xff0c;不用再另外下载新源码了&#xff0c;不得不感慨这时间过得真快啊~废…

神经网络——池化层

目录 池化层 最大池化层 MaxPool2d 最大池化操作图示 最大池化操作代码演示 综合代码案例 池化层 池化层&#xff08;Pooling Layer&#xff09; 核心作用&#xff1a;通过降采样减少特征图尺寸&#xff0c;降低计算量&#xff0c;增强特征鲁棒性。 1. 常见类型 …

Android 默认图库播放视频没有自动循环功能,如何添加2

Android 默认图库播放视频没有自动循环功能, 如何添加 按如下方式修改可以添加 开发云 - 一站式云服务平台 --- a/packages/apps/Gallery2/src/com/android/gallery3d/app/MovieActivity.java +++ b/packages/apps/Gallery2/src/com/android/gallery3d/app/MovieActivity.java…

数字孪生赋能智慧能源电力传输管理新模式

在“双碳”战略和能源数字化转型的双重驱动下&#xff0c;智慧能源系统亟需更高效、精细和智能的管理手段。数字孪生技术作为融合物理世界与数字空间的桥梁&#xff0c;为电力传输系统的全生命周期管理提供了强有力的技术支撑。本文聚焦数字孪生在智慧能源电力传输中的应用&…

Jmeter的元件使用介绍:(二)线程组详解

Jmeter线程组默认包含三种&#xff1a;线程组、setUp线程组、tearDown线程组。线程组之间的执行顺序为&#xff1a;setUp线程组->线程组->tearDown线程组。多数情况都是选用线程组&#xff0c;setUp线程组用于做一些脚本的前置准备&#xff0c;比如&#xff1a;跨线程组设…

AI替代人工:浪潮中的沉浮与觉醒

当AlphaGo以4:1的比分战胜围棋大师李世石之时&#xff0c;人机博弈的疆界被重新划定&#xff1b;当工厂车间里机械臂以惊人精度与不知疲倦的姿态取代了工人重复的手势&#xff1b;当客服电话那头响起的不再是温存人声&#xff0c;而成了准确但缺乏温度的AI语音&#xff1b;当算…

数学建模--matplot.pyplot(结尾附线条样式表格)

matplotlib.pyplot绘图接口 1. 用法 导入模块 import matplotlib.pyplot as plt import numpy as np # 用于生成示例数据绘制简单图表 # 生成数据 x np.linspace(0, 10, 100) y np.sin(x)# 创建图形和坐标轴 plt.figure(figsize(8, 4)) # 设置图表大小 plt.plot(x, y, …

NumPy 实现三维旋转变换

在三维空间中,物体的旋转变换是计算机图形学、机器人学以及三维建模等领域中一个至关重要的操作。这种变换可以通过构造特定的旋转矩阵并将其应用于三维点或向量来实现。本文将深入探讨如何利用 NumPy 这一强大的 Python 科学计算库来实现三维旋转变换,从基本的数学原理到具体…