今天的题开始考研算法了,题目变得有意思了许多,其中第二题的数组反转是很经典的一类例题,值得细细琢磨。
剑指 Offer 04. 二维数组中的查找
在一个n
* m
的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
示例:
现有矩阵 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
。
给定 target = 20
,返回 false
。
限制:
0 <= n <= 1000
0 <= m <= 1000
解法一(暴力查找)
class Solution {
public:
bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
if(matrix.size() == 0)return false;
int n = matrix.size(), m = matrix[0].size();
for(int i = 0; i < n; ++ i){
for(int j = 0; j < m; ++ j){
if(matrix[i][j] == target){
return true;
}
if(matrix[i][j] > target){
break;
}
}
}
return false;
}
};
算法思路:
在普通矩阵查找的基础上,逐行逐列遍历,因为行和列都有单调性,所以每一行在遍历时如果发现该元素已经大于目标值时则不必继续在这一行查询,由于单调性可以跳过这一行直接到下一行查询。这虽然用到了行递增条件,但没用到列递增这个条件,这种解法能过,但还能继续优化。
解法二(优化成线性查找)
class Solution {
public:
bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
if(matrix.size() == 0)return false;
int n = matrix.size(), m = matrix[0].size();
if(m == 0)return false;
int x = 0, y = m - 1;
while(x < n && y >= 0){
if(matrix[x][y] == target)return true;
else if(matrix[x][y] > target)y --;
else x ++;
}
return false;
}
};
算法思路:
这次不从左上角出发,改成从右上角出发,遇到一个元素如果相同则返回true
,大于目标值则左移,小于目标值则下移,当x
、y
坐标有一越界时则返回false
。下面证明一下正确性:
-
左移(反证):假设存在目前遍历的位置在
(x, y)
,假设此时目标值位于现在位置的右下方(x1, y1)
,因为遍历过程只有左移和下移,此时目标值被忽略。因为
(x1, y1)
在(x, y)
右下方,因此存在(x1, y)
与(x, y)
同行,(x1, y1)
同列,由题目条件可得matrix[x1][y]
>matrix[x][y]
且matrix[x1][y]
<matrix[x1][y1]
,当便利店到达(x1, y)
时由条件得此时应该下移而不是左移,因此假设不成立。 -
下移:与左移同理,用反证法就可以证明。
剑指 Offer 11. 旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2]
为 [1,2,3,4,5]
的一个旋转,该数组的最小值为1。
示例 1:
输入:[3,4,5,1,2]
输出:1
示例 2:
输入:[2,2,2,0,1]
输出:0
class Solution {
public:
int minArray(vector<int>& numbers) {
int n = numbers.size(), ans = min(numbers[0], numbers[n - 1]);
int l = 0, r = n - 1;
while(l < r){
int mid = (l + r) / 2;
if(numbers[mid] < numbers[r]){
r = mid;
}else if(numbers[mid] > numbers[r]){
l = mid + 1;
}else r --;
}
return numbers[l];
}
};
算法思路:
将一个有序序列反转变成两个序列,两个序列依旧有序,我们称两个序列为A
、B
,前为A
,后为B
,A
可以为空,B
不能为空,这道题的主要思路就是将B
数组用二分法收缩,收缩到个数为1
,得到结果。定义头尾指针l
, r
, 每次二分都会得到一个mid
,当numbers[mid]
< numbers[r]
时,此时mid
在B
中r
前移到mid
;当numbers[mid]
>numbers[r]
时,此时mid
在A
中,l
后移到mid+1
;当numbers[mid]
==numbers[r]
时,两种情况都有可能,解决的方法是r
前移一位,理由是:
- 假设此时
mid
在A
:r
前移一位,假设B
此时长度只有1
,r
前移完长度变为0
,此时变成一个有序序列,二分查找依旧可以解决问题;如果B长度大于1
,r
前移对求B
中最小值无影响。 - 假设此时
mid
在B
:r
前移一位,假设B
此时长度只有1
,此种情况只有整个序列都相等,此时r
前移对结果也没影响;如果B长度大于1
,r
前移对求B
中最小值无影响。
因此通过二分的方法很快就能求出结果。
剑指 Offer 50. 第一个只出现一次的字符
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
示例 1:
输入:s = "abaccdeff"
输出:'b'
示例 2:
输入:s = ""
输出:' '
限制:
0 <= s 的长度 <= 50000
class Solution {
public:
char firstUniqChar(string s) {
// unordered_map<char, int> mp;
int mp[26] = {0};
for(auto w : s){
mp[w - 'a'] ++;
}
for(auto w : s){
if(mp[w - 'a'] == 1)return w;
}
return ' ';
}
};
算法思路:
这一题就比较简单了,直接hash就可以解决,因为只有小写字母,所以开一个26大小的数组就可以存储。
【剑指Offer】系列:
【剑指Offer】栈
【剑指Offer】链表
【剑指Offer】字符串
【剑指Offer】查找算法