二维数组搜索:从暴力地毯到二分神技的奇幻之旅 🔍🚀
面试官:"在有序矩阵里找出 target!"
你:😎 "循环遍历?小菜一碟!"
面试官:"要求时间复杂度低于 O(mn)。"
你:🤯 "稍等...我需要召唤位面之力!"
稳住! 这篇带你通关二维迷宫,从"「人肉扫描仪」进化成「二分法魔神」,全程梗王附体+代码玩梗,包你笑着拿下 offer!👇
核心问题:搜索二维矩阵
题目 :给定一个 m x n
矩阵,满足:
1️⃣ 每行从左到右非严格递增 (允许重复数字)
2️⃣ 每行首个数字 > 上一行最后一个数字
找出目标值 target
是否存在
心路历程:从青铜到王者的进化
1. 暴力双循环:老实人的地毯搜查 ️♂️
刚看到题目嘴角上扬:两重循环直接梭哈!
口号 :"管他有序无序,老子逐个掀桌查!"
只要我循环得够快,Offer 就追不上我!思路:
- 遍历每一行
- 每行再遍历每一列
- 找到目标返回
true
,否则返回false
无语时刻:
- 时间复杂度:O(mn),最坏情况遍历整个矩阵
- 空间复杂度:O(1),只需要常数级额外空间
java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[0].length; j++) {
if (matrix[i][j] == target) return true; // 找到!拍桌狂喜 → 发现面试官冷笑 😶🌫️
}
}
return false; // 没找到,默默流泪 💔
}
}
真相时刻:
优点 | 缺点 |
---|---|
✅ 逻辑简单如喝水 | ❌ 数据量 >1000?电脑风扇秒变轰炸机!✈️ |
✅ 空间 O(1) 省内存 | ❌ 时间 O(mn) → 万级数据直接卡成PPT 💥 |
面试官冷笑:"这算法...是用我 CPU 煎蛋?" 🍳
2. 压缩数组法:空间换时间的土豪 💸
口号:"降维打击!二维压成一维再二分!" 把矩阵拉成一条数组 👇
思路:
- 先计算出总元素个数
m * n
- 创建一个新数组
combined
,长度为m * n
- 遍历二维数组,将每个元素按行优先顺序放入
combined
中- 对
combined
数组进行二分查找- 如果找到目标返回
true
,否则返回false
氪金时刻:
- 时间复杂度:O(mn) ,
- 转化二维数组的时候时间复杂度是
O(mn)
,二分查找的时间复杂度是O(log(mn))
,所以总的时间复杂度是O(mn)
- 空间复杂度:O(mn) ,需要额外的
combined
数组
java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length, n = matrix[0].length;
int[] combined = new int[m * n]; // 土豪行为:开新数组!
// 二维坐标 → 一维索引(行优先公式:k = i*n + j)
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
combined[i * n + j] = matrix[i][j]; // 二维搬家 📦
}
}
// 一维数组直接二分查找!
int index = Arrays.binarySearch(combined, target);
return index >= 0;
}
}
氪金分析:
操作 | 特效 | 代价 |
---|---|---|
开新数组 | 内存爆炸金色传说 💥 | 空间 O(mn) 💸 |
二分查找 | 紫色速度光环 ⚡ | 时间 O(log(mn)) ✅ |
氪金避坑指南 :
1️⃣ 内存爆炸 :当 m*n=10^6
时,直接吃掉 4GB内存 💥
2️⃣ 时间陷阱 :数组拷贝耗时 O(mn) ,实际比暴力还慢 🐢
3️⃣ 嘲讽防御:面试官问"空间复杂度?"时,大声回答:"钱能解决的问题都不是问题!" 💰🙃
面试官扶额:"这内存...是用我钱包买的?" 👛 → 下一秒拒信发邮箱 ✉️💣
3. 二分查找神技:时空双省的位面之子 ✨
顿悟 :既然数组是有序的,那就不用进行压缩,直接利用数学特性抽象的转化成一维数组,二维当一维用!不用真实的转化,利用有序特性直接映射坐标,那就可以用二分查找!
思路:
- 先计算出总元素个数
m * n
- 定义左右指针
left = 0
和right = m * n - 1
- 进入二分循环,直到
left > right
- 计算中间索引
mid = left + (right - left) / 2
- 将
mid
映射回二维坐标(mid/n, mid%n)
- 比较
matrix[mid/n][mid%n]
与目标值target
- 如果相等,返回
true
核心公式:
- 一维索引
mid
→ 二维坐标(mid/n, mid%n)
- 二维坐标
(i, j)
→ 一维索引k = i*n + j
所以已知k
求i
和j
:i = k / n
j = k % n
封神理由:
- ✅ 时间 O(log(mn)) → 比一维二分还快?
- ✅ 空间 O(1) → 只用 3 个变量,内存笑开花 😄
- ✅ 不改原数组 → 面试官狂喜!
java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length, n = matrix[0].length;
int left = 0, right = m * n - 1; // 虚拟一维数组的首尾
while (left <= right) {
int mid = left + (right - left) / 2;
int midVal = matrix[mid / n][mid % n]; // 魔法坐标转换!✨
if (midVal == target) return true; // 欧皇一击命中 🎯
else if (midVal < target) left = mid + 1; // 向右半区突进 →
else right = mid - 1; // 向左半区后撤 ←
}
return false;
}
}
面试官震惊:"这思路...是开挂了吧?" 🤯 → 当场掏出劳动合同 ✍️💰
4. 双指针巡查:优雅的矩阵猎人
口号:"从右上角开始,遇小下移,遇大左移!"
思路:
- 从右上角开始
- 如果当前元素等于目标,返回
true
- 如果当前元素小于目标,下移
- 如果当前元素大于目标,左移
- 如果超出边界,返回
false
代码简洁,优雅
时间复杂度 :O(m + n) ,最坏情况遍历全表,但是成功将时间复杂度降到了
O(m + n)
常数级,无疑是一个重大意义的突破空间复杂度 :O(1),只需要常数级额外空间
java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int row = 0, col = matrix[0].length - 1; // 定位右上角
while (row < matrix.length && col >= 0) {
if (matrix[row][col] == target) return true; // 逮到!
else if (matrix[row][col] < target) row++; // 目标更大→下移 👇
else col--; // 目标更小→左移 👈
}
return false;
}
}
巡查指南:
当前位置 vs Target | 行动 | 方向 |
---|---|---|
等于 | 收工撒花 🎉 | - |
小于 | 下移找更大 ⬇️ | 抛弃当前行 |
大于 | 左移找更小 ️ | 抛弃当前列 |
灵魂拷问:"如果无序怎么办?" 面试官阴险一笑 😏
无序矩阵?这套路照样浪到飞起! 🌊
5. 分行二分法:学院派de精确制导
策略:先二分定位行,再二分定位列
思路:
- 先二分查找行
- 找到目标行
- 在目标行进行二分查找
- 如果找到目标,返回
true
,否则返回false
代码清晰,逻辑严谨!
时间复杂度 :O(log m + log n),因为每次二分都将搜索空间减半,我的妈呀,这直接优化的更快更迅捷了空间复杂度 :O(1),只需要常数级额外空间
java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length, n = matrix[0].length;
// Step 1: 二分锁定行(找最后一个 matrix[i][0] <= target 的行)
int top = 0, bottom = m - 1;
while (top <= bottom) {
int midRow = top + (bottom - top) / 2;
if (matrix[midRow][0] == target) return true;
else if (matrix[midRow][0] < target) top = midRow + 1;
else bottom = midRow - 1;
}
if (bottom < 0) return false; // target 比所有数都小
// Step 2: 在目标行 binarySearch
int left = 0, right = n - 1;
int[] targetRow = matrix[bottom];
while (left <= right) {
int mid = left + (right - left) / 2;
if (targetRow[mid] == target) return true;
else if (targetRow[mid] < target) left = mid + 1;
else right = mid - 1;
}
return false;
}
}
适用场景:
- 需分别获取行列位置时
- 面试官要求"显式分步操作"
面试官:"这思路...你明天能来上班吗?" 😲
你:"薪资 double 的话,现在就能签合同!" ✍️💰
🔥 终极大乱斗:解法性能 PK 台
解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
---|---|---|---|---|
暴力双循环 🔄 | O(mn) | O(1) | 代码直白 | 慢如乌龟 |
压缩数组法 💾 | O(log(mn)) | O(mn) | 思路简单 | 内存爆炸 💥 |
二分神技 ✨ | O(log(mn)) | O(1) | 时空双优 | 坐标转换需理解 |
双指针巡查 | O(m + n) | O(1) | 代码优雅 | 最坏情况遍历全表 |
分行二分法 🎓 | O(log m + log n) | O(1) | 分步清晰 | 代码略长 |
彩蛋:无序矩阵怎么搜?
面试官阴笑 :"如果行和列各自递增,但整体无序呢?" (这在拓展里直接解决) 答案 :
1️⃣ 暴力循环:删库跑路前の挣扎 ♂️💨
2️⃣ 贪吃蛇升级版:照样从右上角开溜! 🐍💨 (双指针)
javawhile (row < matrix.length && col >= 0) { ... } // 代码不改,嚣张依旧 😎
原理:
- 行从左→右递增 → 列可收缩
- 列从上→下递增 → 行可收缩
面试官:"这波操作...我裂开了啊!" 🤯
彩蛋 搜索二维矩阵 II
题目升级 :每行从左到右升序,每列从上到下升序,但 失去"行首 > 上行尾"特性!
依旧是万能的暴力解决法,代码甚至不需要改动,原版直接写
名场面 :当面试官掏出 10^6 级无序矩阵,你的暴力代码直接触发电脑煎蛋模式🍳→机箱飘出烤面包香气🤯
java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length;
int n = matrix[0].length;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (matrix[i][j] == target) return true;
}
}
return false;
}
}
地狱级暴雷三连 :
1️⃣ 时间复杂度 O(mn) :万级数据?等结果时够打完三局王者🍜
2️⃣ 稀疏矩阵噩梦 :99%空值?照样憨憨遍历到地老天荒⏳
3️⃣ 面试官补刀:"还能优化吗?" → 你:"...我选择删库跑路💨"
适用场景:
- 面试想提前结束🙏
- 给电脑煎蛋当借口🍳
双指针贪吃蛇2.0:无序矩阵的救世主
走位原理 :
行从左→右递增 → 列=贪吃蛇通道 "右边永远比左边胖"
列从上→下递增 → 行=滑滑梯 "楼下永远比楼上香" 🛝
java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int row = 0, col = matrix[0].length - 1; // 坚守右上角起点!
while (row < matrix.length && col >= 0) {
if (matrix[row][col] == target) return true;
else if (matrix[row][col] < target) row++; // 目标更大→下移 👇
else col--; // 目标更小→左移 👈
}
return false;
}
}
为何仍有效?
- ✅ 每行从左到右递增 → 列方向可收缩
- ✅ 每列从上到下递增 → 行方向可收缩
🤓 分行二分法:学院派de精确制导
灵魂暴击 :当双指针被问复杂度?秒切分行二分!
操作流 :1️⃣ 行二分 :锁定
matrix[i][0] ≤ target
的最后一行 🔍2️⃣ 列二分:在目标行玩一维数组二分 🎯
java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length;
int n = matrix[0].length;
for (int i = 0; i < m; i++) {
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (matrix[i][mid] == target) {
return true;
} else if (matrix[i][mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return false;
}
}
为什么封神 :
- ✅ 时间 O(log m + log n) → 比降维二分更快?玄学时刻!🌀
- ✅ 显式分步 → 面试官想挑刺都无从下手👓
- ✅ 避免坐标转换 → 数学苦手の救星🎓
致命缺陷 :
失去全局有序性 → 每行必须独立二分!否则直接翻车💥
💣 三大解法的修罗场:卷王争霸赛开战!
根据文档1的硬核对比,240题解法新增骚气评价👇
解法 | 名场面 | 杀伤力 | 梗王评级 |
---|---|---|---|
暴力循环 | 电脑煎蛋声滋滋响 🍳 | 伤敌0自损10086 | 🤦♂️《自爆卡车》 |
二分分行 | if-else套娃写到手抽筋 ✍️ | 精准但龟速 | 🧠《学院派の强迫症》 |
双指针 | 矩阵里走出六亲不认の魔鬼步伐 👾 | 时空双优+代码短 | 🐍《贪吃蛇の速度与激情》 |
无限大の编程哲学:
"从
O(mn)
暴力到O(log(mn))
二分,就像人生------
找准坐标映射,降维打击难题,offer 自会降临!" 🚀