1.非递归算法
观察此二维数组(以下称为“矩阵”),可以发现该矩阵的副对角线上的每一个元素均满足一个规律,那就是其左侧在同一行的元素均比其小,下侧在同一列的元素均比其大。于是,从副对角线入手进行查找就可以通过一次比较操作排除一整行和一整列的元素。从而大幅缩短时间开销。具体操作就是,当前位置元素如果等于目标元素则直接返回true,小于目标元素则将遍历指针下移一个单位,大于目标元素则将遍历指针左移一个单位。如果发生了越界情况,则说明没有找到该元素。
假设输入的是一个n*m的矩阵,则该算法时间复杂度为O(n+m)。
具体代码如下:
class Solution {
public:
bool searchArray(vector<vector<int>> array, int target) {
if(array.empty() || array[0].empty()) return false;
int n = array.size(), m = array[0].size();
int i = 0, j = m - 1;
while(i < n && j > -1){
if(target == array[i][j]) return true;
else if(target < array[i][j]) j--; //当前元素大于目标元素,遍历指针左移一个单位
else i++; //当前元素小于目标元素,遍历指针下移一个单位
}
return false;
}
};
2.递归算法
事先说明,这个算法虽然提交了之后依然可以AC,但是并没有任何实用价值(后面会分析这个问题)。之所以写出来是因为我觉得这是个很好的练习递归算法和分析时间复杂度的例子。
首先,我们从左上角的元素开始进入递归。如果某一个位置的元素正是目标元素则返回true。如果目标元素大于当前位置元素,则要考虑其右侧元素和下侧元素是否符合要求,因此要进入更深一层的递归。如果目标元素小于当前位置元素,则没有必要再进行递归查找(因为数组中的元素从左往右和从上往下均是递增排列的,即使继续查找,得到的元素也只会比目标元素更大),返回false即可。如果在查找的过程中发生越界,说明这一行或者这一列的元素均不是目标元素,返回false即可。
具体代码如下:
class Solution {
public:
bool check(vector<vector<int>> &array, int &target, int i, int j){
if(i >= array.size() || j >= array[0].size()) return false; //越界返回false
if(target == array[i][j]) return true;
else if(target < array[i][j]) return false;
else if(target > array[i][j])
return check(array, target, i + 1, j) || check(array, target, i ,j + 1);
}
bool searchArray(vector<vector<int>> array, int target) {
return check(array, target, 0, 0);
}
};
之所以说这个算法没有实用价值,是因为该算法的时间复杂度在最坏的情况下会达到O(2^n)(假设矩阵为n*n的方阵)。要分析这个算法的时间复杂度,需要掌握两个知识。一个是高中的一个排列组合问题:由n*m个单位正方形组成的n*m的矩形,从左上角的正方形开始到右下角的正方形为止,共有多少种不同的走法?另一个则是“二项式定理”。另外,邓俊辉老师的《数据结构(C++语言版)》中的最长公共子序列(LCS)问题的递归算法的时间复杂度分析过程与以上算法的分析过程基本上是如出一辙的。感兴趣的同学可以自己根据提示推导或者是查看资料再分析比较。