nc86.矩阵元素查找
1. 思路一: (笨办法, 暴力搜索, 不合要求)
直接遍历二维数组即可.
class Solution { public: vector<int> findElement(vector<vector<int> > mat, int n, int m, int x) { vector<int> res(2); for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (mat[i][j] == x) { res[0] = i, res[1] = j; return res; } } } return res; } };
- 时间复杂度: O(nm). 因为遍历了一遍整个矩阵
- 空间复杂度: O(1). 没有使用数组作为额外空间.
2. 思路二: 从左下往右上找(贪心)
思路一是最笨的暴力办法, 我们没有使用一个条件, 就是该矩阵的每行、每列都是从小到大递增的. 根据这个条件, 我们可以从左下找, 然后这样判断:
- 如果当前位置的数大于目标值: 那么根据该矩阵的特性, 其右边的数只可能更大, 我们往上走一步.
- 如果当前位置的数小于目标值: 那么上边的数只可能更小, 我们往右走.
例如,我们从左下角5开始,寻找目标为7. 算法流程如下:
- 5<7, 说明5上边的一定不是解(比5更小),去掉最左边一列,向右走
- 9>7,说明9右边的一定比9更大,向上走
- 8>7, 8右边的一定比9更大,向上走
- 5<7, 5上边的一定比5更小, 向右走
- 7==7, 找到答案
根据贪心性质, 我们是从左下开始寻找的, 搜索路径只会单调的向右上, 不会往左、往下走, 所以肯定能找到答案.
class Solution { public: vector<int> findElement(vector<vector<int> > mat, int n, int m, int x) { vector<int> res(2); int i = n-1, j = 0; //左下角 // 不走到外面 while (i >= 0 && j <= m-1) { if (mat[i][j] > x) i--; //往上走 else if (mat[i][j] < x) j++; // 往右走 else { // 找到了 res[0] = i, res[1] = j; return res; } } // 走到边上也没找到, 无解 return res; } };
- 时间复杂度: O(n+m). 因为遍历过程中, 只是往上和右走, 最坏的情况走n+m步走到边上, 所以最慢的复杂度是n+m.
- 空间复杂度: O(1). 没有使用数组作为额外空间.
3. 思路三: 二分搜索
因为每一行都是递增的, 且我们不知道答案在哪一行, 所以可以暴力枚举每一行, 再逐行对列二分搜索x.
class Solution { public: vector<int> findElement(vector<vector<int> > mat, int n, int m, int x) { vector<int> res(2); // 枚举i行 for (int i = 0; i < n; i++) { // 如果第i行最左边都比x大,或者最右边都比x小, 那么答案肯定不在这一行 if (mat[i][0] > x || mat[i][m-1] < x) continue; int l = 0, r = m-1; // 二分查找左右端点 while (l <= r) { int mid = (l+r) >> 1; // 如果中点元素大于x, 答案在左边 if (mat[i][mid] > x) r = mid-1; // 小于x, 答案在右边 else if (mat[i][mid] < x) l = mid+1; // 找到了 else { res[0] = i, res[1] = mid; return res; } } } return res; } };
- 时间复杂度: O(nlogm). 遍历过程中, 每一行都对长度为m的数组二分搜索了一次.
- 空间复杂度: O(1). 没有使用数组作为额外空间.