一、题目完整解读
大家先不用怕"二维矩阵""算法"这些听起来高深的词,我们用最通俗的话把题目讲明白,保证完全没接触过编程的人也能懂。
题目给我们一个"表格"(就是二维矩阵),这个表格有两个固定规律,一定要记牢:
-
规律1:每一行的数字,从左到右是逐渐变大的(比如第一行 [1,4,7,11,15],1<4<7<11<15);
-
规律2:每一列的数字,从上到下是逐渐变大的(比如第一列 [1,2,3,10,18],1<2<3<10<18)。
我们的任务很简单:给一个目标数字(target),写一段代码,判断这个数字是不是在这个"表格"里。如果在,就返回"真"(True/true);如果不在,就返回"假"(False/false)。
举两个具体例子,帮大家理解:
示例1:表格是 [[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]],目标数字是5。我们能在表格第二行第二列找到5,所以返回True。
示例2:还是同一个表格,目标数字是20。整个表格里没有20,所以返回False。
题目额外说明(不用死记,了解即可):表格的行数(m)和列数(n)都是1到300之间,数字的范围很大(-10⁹到10⁹),但我们不用关心这个,代码会自动处理。
二、前置知识
在看代码之前,必须先掌握3个核心基础知识点,每一个都讲得明明白白,不会跳过任何一个细节,放心看!
知识点1:什么是二维矩阵(表格)?如何访问表格里的数字?
我们生活中常见的Excel表格、课堂上的座位表,都是"二维矩阵"。在编程里,它就是"列表套列表"(Python)或"向量套向量"(C++),可以理解成"一行一行的列表,拼在一起组成一个大列表"。
举个例子(和题目示例一致):
Python里的二维矩阵:matrix = [[1,4,7,11,15], [2,5,8,12,19], [3,6,9,16,22]]
C++里的二维矩阵:vector<vector<int>> matrix = {{1,4,7,11,15}, {2,5,8,12,19}, {3,6,9,16,22}};
怎么访问表格里的某个数字?------ 用"行号+列号"定位,记为 matrix[行号][列号](重点!)
注意:编程里的"行号""列号"都是从0开始算的(不是从1开始!),比如:
-
matrix[0][0]:第0行(第一行)、第0列(第一列),数字是1;
-
matrix[1][1]:第1行(第二行)、第1列(第二列),数字是5;
-
matrix[2][3]:第2行(第三行)、第3列(第四列),数字是16。
补充:如何获取表格的行数和列数?
-
Python:行数 = len(matrix)(大列表的长度,就是有多少个小列表);列数 = len(matrix[0])(第一个小列表的长度,就是一行有多少个数字);
-
C++:行数 = matrix.size()(向量的size()方法,获取大向量里有多少个小向量);列数 = matrix[0].size()(第一个小向量的size(),获取一行有多少个数字)。
知识点2:循环遍历(如何"挨个查看"表格里的所有数字)
我们要判断目标数字在不在表格里,最直接的方式就是"挨个看",这就需要用到"循环"------ 重复做一件事(查看数字),直到所有数字都看完。
因为是二维表格(行+列),所以需要"嵌套循环":先遍历每一行,再遍历当前行的每一个数字(每一列)。
举个通俗例子:像老师查座位表,先查第一行(从左到右查每个学生),再查第二行,直到所有行查完。
两种语言的循环写法(先有个印象,后面代码里会详细讲):
-
Python:用for循环,先遍历每一行(for row in matrix),再遍历当前行的每一个数字(for num in row);
-
C++:用for循环,先定义行号i(从0到行数-1),再定义列号j(从0到列数-1),通过matrix[i][j]访问每个数字。
知识点3:条件判断(如何判断"当前数字是不是目标值")
遍历到一个数字后,我们需要判断它是不是我们要找的目标值(target),这就需要用到"条件判断"------ if-else语句。
核心逻辑(非常简单):
-
如果 当前数字 == 目标值 → 找到目标,直接返回"真"(True/true),不用再继续查看;
-
如果 当前数字 != 目标值 → 继续查看下一个数字;
-
如果所有数字都查完了,还是没有找到 → 返回"假"(False/false)。
补充:边界判断(避免出错的关键)
如果表格是空的(比如没有任何行,或者只有一行但没有任何数字),直接返回False,因为里面不可能有目标值------ 这一步很重要,能避免代码报错。
知识点4:补充(Python/C++ 基础语法,必备)
为了让大家看懂代码,再补充几个基础语法,不用死记,跟着代码对照看即可:
-
Python:
-
from typing import List:导入列表类型,刷题时必须写,用来声明函数参数的类型(告诉代码,matrix是"列表套列表",target是整数);
-
class Solution: 定义一个"解决方案类",刷题时固定写法,所有解题函数都放在这个类里面;
-
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool: 定义解题函数,self是类的固定参数,后面两个是输入(matrix和target),-> bool表示函数返回值是"真/假"。
-
-
C++:
-
#include <vector>:导入向量(vector)库,因为二维矩阵用vector实现,必须写;
-
using namespace std;:简化代码写法,不用每次都写"std::vector",直接写"vector"即可;
-
class Solution: 定义解决方案类,固定写法;
-
bool searchMatrix(vector<vector<int>>& matrix, int target):定义解题函数,返回值是bool(真/假),参数matrix是"vector套vector",&表示"引用传递"(不用复制整个矩阵,更高效),target是目标值。
-
三、解法一:暴力遍历法
1. 核心思路(能直接理解)
完全不利用题目中"每行、每列升序"的规律,就是"挨个查看"表格里的每一个数字,直到找到目标值,或者查完所有数字。
步骤拆解:
-
先判断表格是不是空的(边界判断),如果是空的,直接返回False;
-
遍历表格的每一行;
-
遍历当前行的每一个数字;
-
如果当前数字 == 目标值 → 返回True;
-
所有数字都查完,没找到 → 返回False。
优点:代码最简单,不用思考任何规律,零基础能快速写出来;
缺点:效率低,最坏情况下要查完所有数字(比如目标值在最后一个位置,或者不在表格里),时间复杂度是O(m×n)(m是行数,n是列数)。
2. Python 暴力解法(逐句注释,每句都讲清含义)
python
# 导入列表类型声明(刷题必备,告诉代码"List"是什么,避免报错)
from typing import List
# 定义解决方案类(刷题固定写法,所有解题函数都放在这个类里)
class Solution:
# 定义解题函数:searchMatrix是函数名,self是类的固定参数
# matrix: List[List[int]] → 声明输入的matrix是"列表套列表"(二维矩阵),里面的元素是整数
# target: int → 声明输入的target是整数
# -> bool → 声明函数的返回值是"真/假"(True/False)
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
# 边界判断1:如果矩阵是空的(没有任何行),直接返回False(里面不可能有目标值)
if not matrix:
return False
# 边界判断2:如果矩阵有行,但第一行是空的(没有任何列),也返回False
if not matrix[0]:
return False
# 遍历矩阵的每一行:row代表当前行(比如第一次循环,row是[1,4,7,11,15])
for row in matrix:
# 遍历当前行的每一个数字:num代表当前行的某个数字(比如第一次循环,num是1,然后是4、7...)
for num in row:
# 条件判断:如果当前数字等于目标值,说明找到了,直接返回True
if num == target:
return True
# 循环结束:所有行、所有数字都遍历完了,还是没找到目标值,返回False
return False
# 主函数(测试用,可以直接运行,查看结果)
# 主函数的作用:定义测试数据,调用上面的解题函数,输出结果
if __name__ == "__main__":
# 实例化Solution类(创建一个"解题工具",才能调用里面的searchMatrix函数)
sol = Solution()
# 测试用例1(题目给定的示例1)
matrix1 = [
[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 = 5
# 调用解题函数,传入测试数据,获取结果
result1 = sol.searchMatrix(matrix1, target1)
# 输出结果:打印"测试用例1"和对应的结果,让我们能看到是否正确
print(f"测试用例1:matrix={matrix1}, target={target1}")
print(f"测试结果1:{result1}(正确结果应为True)\n")
# 测试用例2(题目给定的示例2)
matrix2 = [
[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]
]
target2 = 20
result2 = sol.searchMatrix(matrix2, target2)
print(f"测试用例2:matrix={matrix2}, target={target2}")
print(f"测试结果2:{result2}(正确结果应为False)\n")
# 自定义测试用例3(空矩阵,测试边界)
matrix3 = [] # 没有任何行的空矩阵
target3 = 10
result3 = sol.searchMatrix(matrix3, target3)
print(f"自定义测试用例3:matrix={matrix3}, target={target3}")
print(f"测试结果3:{result3}(正确结果应为False)\n")
# 自定义测试用例4(只有一行一列的矩阵)
matrix4 = [[7]] # 只有一行、一列,数字是7
target4 = 7
result4 = sol.searchMatrix(matrix4, target4)
print(f"自定义测试用例4:matrix={matrix4}, target={target4}")
print(f"测试结果4:{result4}(正确结果应为True)\n")
# 自定义测试用例5(目标值比所有数字都小)
matrix5 = [[2,4,6],[3,5,7]]
target5 = 1
result5 = sol.searchMatrix(matrix5, target5)
print(f"自定义测试用例5:matrix={matrix5}, target={target5}")
print(f"测试结果5:{result5}(正确结果应为False)\n")
# 自定义测试用例6(目标值比所有数字都大)
matrix6 = [[2,4,6],[3,5,7]]
target6 = 8
result6 = sol.searchMatrix(matrix6, target6)
print(f"自定义测试用例6:matrix={matrix6}, target={target6}")
print(f"测试结果6:{result6}(正确结果应为False)")
3. Python 代码调用流程 & 运行过程(逐步看懂)
(1)调用流程(怎么用这个代码)
-
先导入List类型(from typing import List),确保代码能识别"二维列表";
-
定义Solution类,里面包含解题函数searchMatrix;
-
在主函数(if name == "main":)里,创建Solution的实例sol(相当于"打开解题工具");
-
定义测试用例(matrix和target);
-
调用sol.searchMatrix(matrix, target),传入测试数据,获取返回结果;
-
打印结果,查看是否正确。
(2)运行过程(以测试用例1为例,逐步看代码怎么执行)
测试用例1:matrix1是5行5列的矩阵,target1=5
-
执行边界判断:matrix1不是空的,matrix1[0](第一行)也不是空的,所以不返回False;
-
进入第一个for循环,遍历第一行row = [1,4,7,11,15];
-
进入第二个for循环,遍历第一行的数字:
-
num=1 → 1 !=5 → 继续;
-
num=4 → 4 !=5 → 继续;
-
num=7 →7 !=5 → 继续;
-
num=11 →11 !=5 → 继续;
-
num=15 →15 !=5 → 第一行遍历结束,进入下一行。
-
-
遍历第二行row = [2,5,8,12,19];
-
进入第二个for循环,遍历第二行的数字:
-
num=2 →2 !=5 → 继续;
-
num=5 →5 ==5 → 满足条件,直接返回True;
-
-
函数返回True,主函数接收结果,打印"测试结果1:True",整个运行结束。
补充:如果测试用例2(target=20),代码会遍历完所有5行5列的数字(共25个),都没找到20,最后返回False。
4. C++ 暴力解法(逐句注释,每句都讲清含义)
cpp
#include <vector> // 导入向量库,因为二维矩阵用vector(向量)实现,必须写
using namespace std; // 简化语法,不用每次写"std::vector",直接写"vector"
// 定义解决方案类(刷题固定写法)
class Solution {
public:
// 定义解题函数:返回值是bool(真/假)
// vector<vector<int>>& matrix → 输入的二维矩阵,&表示"引用传递"(高效,不复制整个矩阵)
// int target → 输入的目标值(整数)
bool searchMatrix(vector<vector<int>>& matrix, int target) {
// 边界判断1:如果矩阵是空的(没有任何行),返回False
if (matrix.empty()) {
return false;
}
// 边界判断2:如果矩阵有行,但第一行是空的(没有任何列),返回False
if (matrix[0].empty()) {
return false;
}
// 获取矩阵的行数m:matrix.size() → 大向量的长度(有多少个小向量,就是多少行)
int m = matrix.size();
// 获取矩阵的列数n:matrix[0].size() → 第一个小向量的长度(一行有多少个数字,就是多少列)
int n = matrix[0].size();
// 外层for循环:遍历每一行,i是行号(从0到m-1,因为行号从0开始)
for (int i = 0; i < m; i++) {
// 内层for循环:遍历当前行的每一列,j是列号(从0到n-1)
for (int j = 0; j < n; j++) {
// 访问当前行、当前列的数字:matrix[i][j]
// 条件判断:如果当前数字等于目标值,返回true
if (matrix[i][j] == target) {
return true;
}
}
}
// 所有数字都遍历完,没找到目标值,返回false
return false;
}
};
// 主函数(测试用,可以直接复制运行,查看结果)
// 主函数是程序的入口,代码从这里开始执行
int main() {
// 实例化Solution类,创建解题对象sol(相当于"打开解题工具")
Solution sol;
// 测试用例1(题目给定的示例1)
vector<vector<int>> matrix1 = {
{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}
};
int target1 = 5;
// 调用解题函数,传入测试数据,获取结果(result1是bool类型,true/false)
bool result1 = sol.searchMatrix(matrix1, target1);
// 输出结果:cout是C++的打印语句,endl是换行
cout << "测试用例1:" << endl;
cout << "matrix:" << endl;
// 打印矩阵(方便查看测试数据)
for (auto row : matrix1) {
for (auto num : row) {
cout << num << " ";
}
cout << endl;
}
cout << "target:" << target1 << endl;
cout << "测试结果1:" << (result1 ? "true" : "false") << "(正确结果应为true)" << endl << endl;
// 测试用例2(题目给定的示例2)
vector<vector<int>> matrix2 = {
{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}
};
int target2 = 20;
bool result2 = sol.searchMatrix(matrix2, target2);
cout << "测试用例2:" << endl;
cout << "matrix:" << endl;
for (auto row : matrix2) {
for (auto num : row) {
cout << num << " ";
}
cout << endl;
}
cout << "target:" << target2 << endl;
cout << "测试结果2:" << (result2 ? "true" : "false") << "(正确结果应为false)" << endl << endl;
// 自定义测试用例3(空矩阵)
vector<vector<int>> matrix3; // 空矩阵(没有任何行)
int target3 = 10;
bool result3 = sol.searchMatrix(matrix3, target3);
cout << "自定义测试用例3:" << endl;
cout << "matrix:空矩阵" << endl;
cout << "target:" << target3 << endl;
cout << "测试结果3:" << (result3 ? "true" : "false") << "(正确结果应为false)" << endl << endl;
// 自定义测试用例4(只有一行一列的矩阵)
vector<vector<int>> matrix4 = {{7}};
int target4 = 7;
bool result4 = sol.searchMatrix(matrix4, target4);
cout << "自定义测试用例4:" << endl;
cout << "matrix:" << endl;
for (auto row : matrix4) {
for (auto num : row) {
cout << num << " ";
}
cout << endl;
}
cout << "target:" << target4 << endl;
cout << "测试结果4:" << (result4 ? "true" : "false") << "(正确结果应为true)" << endl << endl;
// 自定义测试用例5(目标值比所有数字都小)
vector<vector<int>> matrix5 = {{2,4,6},{3,5,7}};
int target5 = 1;
bool result5 = sol.searchMatrix(matrix5, target5);
cout << "自定义测试用例5:" << endl;
cout << "matrix:" << endl;
for (auto row : matrix5) {
for (auto num : row) {
cout << num << " ";
}
cout << endl;
}
cout << "target:" << target5 << endl;
cout << "测试结果5:" << (result5 ? "true" : "false") << "(正确结果应为false)" << endl << endl;
// 自定义测试用例6(目标值比所有数字都大)
vector<vector<int>> matrix6 = {{2,4,6},{3,5,7}};
int target6 = 8;
bool result6 = sol.searchMatrix(matrix6, target6);
cout << "自定义测试用例6:" << endl;
cout << "matrix:" << endl;
for (auto row : matrix6) {
for (auto num : row) {
cout << num << " ";
}
cout << endl;
}
cout << "target:" << target6 << endl;
cout << "测试结果6:" << (result6 ? "true" : "false") << "(正确结果应为false)" << endl;
return 0; // 主函数结束,返回0表示程序正常运行
}
5. C++ 代码调用流程 & 运行过程(逐步看懂)
(1)调用流程(怎么用这个代码)
-
导入vector库(#include <vector>),否则无法使用二维矩阵;
-
写using namespace std;,简化代码写法;
-
定义Solution类,里面包含解题函数searchMatrix;
-
主函数main()是程序的入口,代码从这里开始执行;
-
在main()里,创建Solution的实例sol(打开解题工具);
-
定义测试用例(vector<vector<int>>类型的matrix和int类型的target);
-
调用sol.searchMatrix(matrix, target),传入测试数据,获取返回结果(bool类型);
-
用cout打印测试数据和结果,查看是否正确;
-
主函数返回0,程序正常结束。
(2)运行过程(以测试用例1为例,逐步看代码怎么执行)
测试用例1:matrix1是5行5列的矩阵,target1=5
-
执行边界判断:matrix1不是空的(matrix.empty()为false),matrix1[0]也不是空的(matrix[0].empty()为false),所以不返回false;
-
获取行数m=5(matrix1.size()=5),列数n=5(matrix1[0].size()=5);
-
外层for循环:i从0开始(i=0,第一行);
-
内层for循环:j从0开始(j=0,第一列),访问matrix1[0][0]=1 → 1 !=5 → j=1,matrix1[0][1]=4 →4 !=5 → j=2,matrix1[0][2]=7 →7 !=5 → j=3,matrix1[0][3]=11 →11 !=5 → j=4,matrix1[0][4]=15 →15 !=5 → 内层循环结束,i=1(第二行);
-
内层for循环:j=0,matrix1[1][0]=2 →2 !=5 → j=1,matrix1[1][1]=5 →5 ==5 → 满足条件,返回true;
-
主函数接收result1=true,打印"测试结果1:true",程序继续执行其他测试用例,最后返回0,结束运行。
四、解法二:高效查找法
1. 核心思路(利用矩阵规律,大幅提速)
这是这道题的最优解法,核心是"利用题目中'每行升序、每列升序'的规律,每次排除一行或一列,不用遍历所有数字",效率大幅提升。
关键技巧:选择「矩阵右上角的数字」作为查找的起点(为什么选这里?因为这个数字有个天然优势):
👉 右上角的数字 = 当前行的最大值(因为行是升序,最右边最大) + 当前列的最小值(因为列是升序,最上边最小)
基于这个优势,我们可以每次排除一行或一列,快速缩小查找范围,步骤拆解(必看,结合例子理解):
-
边界判断:如果矩阵是空的,直接返回False;
-
初始化起点:行号row=0(第一行),列号col=矩阵列数-1(最后一列)→ 也就是右上角的数字;
-
循环条件:行号row不超过总行数(没越界),列号col不小于0(没越界);
-
获取当前起点的数字current = matrix[row][col];
-
三种情况:
-
情况1:current == target → 找到目标值,返回True;
-
情况2:current > target → 目标值一定在当前数字的左边(因为当前列是升序,当前数字是列的最小值,左边的数字都比它小),所以列号col减1(向左移一列,排除当前列);
-
情况3:current < target → 目标值一定在当前数字的下边(因为当前行是升序,当前数字是行的最大值,下边的数字都比它大),所以行号row加1(向下移一行,排除当前行);
-
-
如果循环结束(行或列越界),还没找到目标值 → 返回False。
举个通俗例子(测试用例1,target=5):
矩阵右上角是matrix[0][4] =15 → 15>5 → 列号减1(col=3),排除第4列;
当前数字是matrix[0][3]=11 →11>5 → 列号减1(col=2),排除第3列;
当前数字是matrix[0][2]=7 →7>5 → 列号减1(col=1),排除第2列;
当前数字是matrix[0][1]=4 →4<5 → 行号加1(row=1),排除第0行;
当前数字是matrix[1][1]=5 →5==5 → 返回True。
优点:效率极高,时间复杂度是O(m+n)(最多走m+n步,比如从右上角走到左下角),是面试时的首选解法;
缺点:需要理解"右上角起点"的逻辑,比暴力解法稍难一点,但只要记住"大左移、小下移",就能轻松写出来。
2. Python 最优解法(逐句注释,每句都讲清含义)
python
# 导入列表类型声明(刷题必备)
from typing import List
# 定义解决方案类(固定写法)
class Solution:
def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
# 边界判断1:如果矩阵是空的(没有任何行),返回False
if not matrix:
return False
# 边界判断2:如果矩阵有行,但第一行是空的(没有任何列),返回False
if not matrix[0]:
return False
# 初始化起点:右上角(第0行,最后一列)
row = 0 # 行号,从第一行(0行)开始
col = len(matrix[0]) - 1 # 列号,从最后一列开始(len(matrix[0])是列数,减1是因为列号从0开始)
rows = len(matrix) # 总行数,用来判断行号是否越界
# 循环条件:行号row < 总行数(没越界),列号col >=0(没越界)
while row < rows and col >= 0:
# 获取当前起点的数字(右上角开始,每次更新)
current = matrix[row][col]
# 情况1:当前数字等于目标值,找到目标,返回True
if current == target:
return True
# 情况2:当前数字大于目标值 → 目标值在左边,列号减1(向左移一列)
elif current > target:
col -= 1
# 情况3:当前数字小于目标值 → 目标值在下边,行号加1(向下移一行)
else:
row += 1
# 循环结束:行或列越界,没找到目标值,返回False
return False
# 主函数(测试用,和暴力解法的主函数结构一致,测试所有案例)
if __name__ == "__main__":
sol = Solution()
# 测试用例1(题目示例1)
matrix1 = [
[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 = 5
result1 = sol.searchMatrix(matrix1, target1)
print(f"测试用例1:matrix={matrix1}, target={target1}")
print(f"测试结果1:{result1}(正确结果应为True)\n")
# 测试用例2(题目示例2)
matrix2 = [
[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]
]
target2 = 20
result2 = sol.searchMatrix(matrix2, target2)
print(f"测试用例2:matrix={matrix2}, target={target2}")
print(f"测试结果2:{result2}(正确结果应为False)\n")
# 自定义测试用例3(空矩阵)
matrix3 = []
target3 = 10
result3 = sol.searchMatrix(matrix3, target3)
print(f"自定义测试用例3:matrix={matrix3}, target={target3}")
print(f"测试结果3:{result3}(正确结果应为False)\n")
# 自定义测试用例4(只有一行一列)
matrix4 = [[7]]
target4 = 7
result4 = sol.searchMatrix(matrix4, target4)
print(f"自定义测试用例4:matrix={matrix4}, target={target4}")
print(f"测试结果4:{result4}(正确结果应为True)\n")
# 自定义测试用例5(目标值在左上角)
matrix5 = [[1,2,3],[4,5,6],[7,8,9]]
target5 = 1
result5 = sol.searchMatrix(matrix5, target5)
print(f"自定义测试用例5:matrix={matrix5}, target={target5}")
print(f"测试结果5:{result5}(正确结果应为True)\n")
# 自定义测试用例6(目标值在右下角)
matrix6 = [[1,2,3],[4,5,6],[7,8,9]]
target6 = 9
result6 = sol.searchMatrix(matrix6, target6)
print(f"自定义测试用例6:matrix={matrix6}, target={target6}")
print(f"测试结果6:{result6}(正确结果应为True)")
3. Python 最优解法 调用流程 & 运行过程(逐步看懂)
(1)调用流程
和暴力解法的调用流程完全一致:导入List → 定义Solution类 → 主函数创建实例 → 定义测试用例 → 调用函数 → 打印结果。
(2)运行过程(以测试用例1为例,逐步拆解)
测试用例1:matrix1=5行5列,target1=5
-
边界判断:matrix1非空,matrix1[0]非空,不返回False;
-
初始化:row=0(第一行),col=5-1=4(最后一列),rows=5(总行数);
-
进入while循环(row=0 <5,col=4 ≥0,满足条件):
- current = matrix[0][4] =15 → 15>5 → 执行col -=1 → col=3;
-
循环继续(row=0 <5,col=3 ≥0):
- current = matrix[0][3] =11 →11>5 → col -=1 → col=2;
-
循环继续(row=0 <5,col=2 ≥0):
- current = matrix[0][2] =7 →7>5 → col -=1 → col=1;
-
循环继续(row=0 <5,col=1 ≥0):
- current = matrix[0][1] =4 →4<5 → row +=1 → row=1;
-
循环继续(row=1 <5,col=1 ≥0):
- current = matrix[1][1] =5 →5==5 → 返回True;
-
函数返回True,主函数打印结果,运行结束。
补充(测试用例2,target=20):
从右上角15开始,逐步左移、下移,最后row=5(超过总行数4),循环结束,返回False,全程只走了8步(比暴力解法的25步少很多)。
4. C++ 最优解法(逐句注释,每句都讲清含义)
cpp
#include <vector>
using namespace std;
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
// 边界判断1:矩阵为空(没有任何行),返回false
if (matrix.empty()) {
return false;
}
// 边界判断2:第一行为空(没有任何列),返回false
if (matrix[0].empty()) {
return false;
}
// 初始化起点:右上角(第0行,最后一列)
int row = 0; // 行号,从第0行开始
int col = matrix[0].size() - 1; // 列号,从最后一列开始(size()-1是因为列号从0开始)
int rows = matrix.size(); // 总行数,用于判断行号是否越界
// 循环条件:行号不越界(row < rows),列号不越界(col >= 0)
while (row < rows && col >= 0) {
// 获取当前起点的数字
int current = matrix[row][col];
// 情况1:找到目标值,返回true
if (current == target) {
return true;
}
// 情况2:当前数字大于目标值 → 向左移一列(col减1),排除当前列
else if (current > target) {
col--;
}
// 情况3:当前数字小于目标值 → 向下移一行(row加1),排除当前行
else {
row++;
}
}
// 循环结束,没找到目标值,返回false
return false;
}
};
// 主函数(测试用,和暴力解法主函数结构一致,可直接复制运行)
int main() {
// 实例化Solution类,创建解题对象sol(打开解题工具)
Solution sol;
// 测试用例1(题目给定的示例1)
vector<vector<int>> matrix1 = {
{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}
};
int target1 = 5;
// 调用最优解法的解题函数,传入测试数据,获取结果
bool result1 = sol.searchMatrix(matrix1, target1);
// 打印测试数据和结果,方便查看
cout << "测试用例1:" << endl;
cout << "matrix:" << endl;
for (auto row : matrix1) {
for (auto num : row) {
cout << num << " ";
}
cout << endl;
}
cout << "target:" << target1 << endl;
cout << "测试结果1:" << (result1 ? "true" : "false") << "(正确结果应为true)" << endl << endl;
// 测试用例2(题目给定的示例2)
vector<vector<int>> matrix2 = {
{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}
};
int target2 = 20;
bool result2 = sol.searchMatrix(matrix2, target2);
cout << "测试用例2:" << endl;
cout << "matrix:" << endl;
for (auto row : matrix2) {
for (auto num : row) {
cout << num << " ";
}
cout << endl;
}
cout << "target:" << target2 << endl;
cout << "测试结果2:" << (result2 ? "true" : "false") << "(正确结果应为false)" << endl << endl;
// 自定义测试用例3(空矩阵,测试边界)
vector<vector<int>> matrix3;
int target3 = 10;
bool result3 = sol.searchMatrix(matrix3, target3);
cout << "自定义测试用例3:" << endl;
cout << "matrix:空矩阵" << endl;
cout << "target:" << target3 << endl;
cout << "测试结果3:" << (result3 ? "true" : "false") << "(正确结果应为false)" << endl << endl;
// 自定义测试用例4(只有一行一列的矩阵)
vector<vector<int>> matrix4 = {{7}};
int target4 = 7;
bool result4 = sol.searchMatrix(matrix4, target4);
cout << "自定义测试用例4:" << endl;
cout << "matrix:" << endl;
for (auto row : matrix4) {
for (auto num : row) {
cout << num << " ";
}
cout << endl;
}
cout << "target:" << target4 << endl;
cout << "测试结果4:" << (result4 ? "true" : "false") << "(正确结果应为true)" << endl << endl;
// 自定义测试用例5(目标值在左上角)
vector<vector<int>> matrix5 = {{1,2,3},{4,5,6},{7,8,9}};
int target5 = 1;
bool result5 = sol.searchMatrix(matrix5, target5);
cout << "自定义测试用例5:" << endl;
cout << "matrix:" << endl;
for (auto row : matrix5) {
for (auto num : row) {
cout << num << " ";
}
cout << endl;
}
cout << "target:" << target5 << endl;
cout << "测试结果5:" << (result5 ? "true" : "false") << "(正确结果应为true)" << endl << endl;
// 自定义测试用例6(目标值在右下角)
vector<vector<int>> matrix6 = {{1,2,3},{4,5,6},{7,8,9}};
int target6 = 9;
bool result6 = sol.searchMatrix(matrix6, target6);
cout << "自定义测试用例6:" << endl;
cout << "matrix:" << endl;
for (auto row : matrix6) {
for (auto num : row) {
cout << num << " ";
}
cout << endl;
}
cout << "target:" << target6 << endl;
cout << "测试结果6:" << (result6 ? "true" : "false") << "(正确结果应为true)" << endl;
return 0; // 主函数结束,返回0表示程序正常运行
}
5. C++ 最优解法 调用流程 & 运行过程(逐步看懂)
(1)调用流程
和C++暴力解法的调用流程完全一致,步骤如下,对照代码看就能懂:
-
导入vector库(#include <vector>),确保能使用二维矩阵(vector套vector);
-
写using namespace std;,简化代码,不用每次都写"std::"前缀;
-
定义Solution类,里面包含最优解法的解题函数searchMatrix;
-
主函数main()是程序入口,代码从这里开始执行;
-
在main()里,创建Solution的实例sol(相当于打开解题工具,才能调用里面的函数);
-
定义6个测试用例(和暴力解法完全一致,覆盖正常情况、边界情况);
-
调用sol.searchMatrix(matrix, target),传入测试数据,获取返回结果(bool类型,true/false);
-
用cout打印测试数据(矩阵、目标值)和测试结果,方便查看是否正确;
-
主函数返回0,程序正常结束。
(2)运行过程(以测试用例1为例,逐步拆解,能跟着走)
测试用例1:matrix1是5行5列矩阵,target1=5,核心是"大左移、小下移",逐步缩小范围
-
边界判断:matrix1非空(matrix.empty()为false),matrix1[0]非空(matrix[0].empty()为false),不返回false;
-
初始化起点:row=0(第一行),col=matrix1[0].size()-1=5-1=4(最后一列),rows=matrix1.size()=5(总行数);
-
进入while循环(row=0 <5,col=4 ≥0,满足循环条件): current = matrix1[0][4] = 15 → 15 > 5(当前数字大于目标值);
-
执行col-- → col=3(向左移一列,排除第4列,因为第4列所有数字都比15大,不可能有5);
-
循环继续(row=0 <5,col=3 ≥0): current = matrix1[0][3] = 11 → 11 > 5;
-
执行col-- → col=2(向左移一列,排除第3列);
-
循环继续(row=0 <5,col=2 ≥0): current = matrix1[0][2] = 7 → 7 > 5;
-
执行col-- → col=1(向左移一列,排除第2列);
-
循环继续(row=0 <5,col=1 ≥0): current = matrix1[0][1] = 4 → 4 < 5(当前数字小于目标值);
-
执行row++ → row=1(向下移一行,排除第0行,因为第0行所有数字都比4小,不可能有5);
-
循环继续(row=1 <5,col=1 ≥0): current = matrix1[1][1] = 5 → 5 == 5(找到目标值);
-
直接返回true,跳出循环和函数;
-
主函数接收result1=true,打印"测试结果1:true",然后继续执行其他测试用例,全部执行完后返回0,程序结束。
补充(测试用例2,target=20):
运行过程拆解(帮理解"排除行/列"的优势):
-
初始化row=0,col=4,current=15 → 15 <20 → row++(row=1);
-
current=matrix1[1][4]=19 →19 <20 → row++(row=2);
-
current=matrix1[2][4]=22 →22 >20 → col--(col=3);
-
current=matrix1[2][3]=16 →16 <20 → row++(row=3);
-
current=matrix1[3][3]=17 →17 <20 → row++(row=4);
-
current=matrix1[4][3]=26 →26 >20 → col--(col=2);
-
current=matrix1[4][2]=23 →23 >20 → col--(col=1);
-
current=matrix1[4][1]=21 →21 >20 → col--(col=0);
-
current=matrix1[4][0]=18 →18 <20 → row++(row=5);
-
此时row=5 ≥ rows=5,循环结束,返回false(全程仅8步,远少于暴力解法的25步)。
五、两种解法对比(必看,清晰区分)
为了让快速分清两种解法的区别,整理了清晰的对比,不用死记,理解即可:
| 对比维度 | 解法一:暴力遍历法 | 解法二:高效查找法(最优解) |
|---|---|---|
| 核心逻辑 | 不利用矩阵规律,挨个遍历所有数字,逐个判断 | 利用"每行/每列升序"规律,从右上角开始,每次排除一行或一列 |
| 难度 | 极低,零基础能直接写,不用思考规律 | 中等,记住"大左移、小下移"即可,理解后很简单 |
| 效率(时间复杂度) | 低,O(m×n),最坏情况遍历所有数字(m行n列) | 高,O(m+n),最多走m+n步(从右上角走到左下角) |
| 适用场景 | 新手入门、矩阵规模很小(比如1-2行/列) | 面试、矩阵规模大(题目中300行300列,优先选这个) |
| 关键技巧 | 嵌套循环,逐一遍历 | 右上角为起点,"大左移、小下移",排除法缩小范围 |
六、常见问题解答
整理了学习过程中最容易问的4个问题,每个问题都用通俗的话解答,避免踩坑:
疑问1:为什么行号、列号要从0开始,而不是从1开始?
这是编程的通用约定,不是这道题特有的!就像我们排队,从第0个位置开始数,和生活中"第1个"对应。比如matrix[0][0],生活中是"第一行第一列",编程里写"0行0列",记住这个对应关系就不会错。
疑问2:高效解法为什么选右上角当起点?选左上角、右下角不行吗?
核心原因:只有右上角(或左下角)的数字,能同时实现"排除一行或一列",其他位置不行,举个例子说明:
-
选左上角:数字是当前行最小、当前列最小,比如matrix[0][0]=1,比目标值5小,只能向下移,但无法排除一行/一列,还是要遍历很多数字;
-
选右下角:数字是当前行最大、当前列最大,比如matrix[4][4]=30,比目标值5大,只能向左移,也无法排除一行/一列;
-
选右上角:数字是当前行最大、当前列最小,能直接判断"大了左移、小了下移",每次必排除一行或一列,效率最高。
疑问3:代码里的"边界判断"为什么必须写?不写会怎么样?
必须写!不写会导致代码报错,比如:
-
如果矩阵是空的(matrix=[]),执行len(matrix[0])(Python)或matrix[0].size()(C++)时,会报错"访问了不存在的元素";
-
边界判断能提前"拦截"空矩阵的情况,直接返回false,避免代码崩溃,这是写算法题的好习惯,一定要记住。
疑问4:Python和C++的代码逻辑一样,为什么语法不一样?
因为Python和C++是两种不同的编程语言,就像"中文和英文",表达同一个意思(解题逻辑),但用词、句式不一样。比如:
-
Python用len(matrix)获取行数,C++用matrix.size();
-
Python用for row in matrix遍历行,C++用for循环+行号i遍历;
-
但核心逻辑(边界判断、遍历/排除思路、条件判断)完全一致,学会一种,另一种只要记语法差异就能快速掌握。
七、总结(必看,快速梳理重点)
这道题的核心是"利用矩阵的升序规律,优化查找效率",不用害怕,按以下步骤学习,就能完全掌握:
-
先掌握前置知识:理解二维矩阵、循环遍历、条件判断,这是基础,没掌握就先看第二部分,不要急着看代码;
-
先写暴力解法:不用想规律,先实现"挨个遍历",确保能正确运行,培养编程手感;
-
再学最优解法:记住"右上角起点+大左移、小下移",理解"每次排除一行/一列"的逻辑,这是面试重点;
-
多运行测试用例:把代码复制到编译器里,修改目标值、矩阵,观察运行结果,加深理解;
-
记住对比差异:两种解法的效率、适用场景,面试时能快速说出"暴力解法和最优解法的区别",加分项。