81.搜索旋转排序数组II
已知存在一个按非降序排列的整数数组 nums
,数组中的值不必互不相同。
在传递给函数之前,nums
在预先未知的某个下标 k
(0 <= k < nums.length
)上进行了 旋转 ,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]
(下标 从 0 开始 计数)。例如, [0,1,2,4,4,4,5,6,6,7]
在下标 5
处经旋转后可能变为 [4,5,6,6,7,0,1,2,4,4]
。
给你 旋转后 的数组 nums
和一个整数 target
,请你编写一个函数来判断给定的目标值是否存在于数组中。如果 nums
中存在这个目标值 target
,则返回 true
,否则返回 false
。
你必须尽可能减少整个操作步骤。
力扣解法:
class Solution {
public:
int search(vector<int>& nums, int target) {
int n = (int)nums.size();
if(!n) return false;
if(n == 1) return nums[0] == target ? true : false;
int left = 0, right = n - 1;
while(left <= right){
int mid = left + (right - left) / 2;
if(nums[mid] == target) return true;
if(nums[left] == nums[mid] && nums[mid] == nums[right]){
++left;
--right;
}
else if(nums[left] <= nums[mid]){
if(nums[left] <= target && target< nums[mid]){
right = mid - 1;
}else{
left = mid + 1;
}
}else{
if(nums[mid] < target && target <= nums[n - 1]){
left = mid + 1;
}else{
right = mid - 1;
}
}
}
return false;
}
};
收获总结:
1.对于这种情况,将当前二分区间的左边界加一,右边界减一,然后在新区间上继续二分查找。
2.注意再需要缩小区间或无法判断,尤其是出现重复元素时,考虑这++--策略.
74.搜索二维数组
给你一个满足下述两条属性的 m x n
整数矩阵:
- 每行中的整数从左到右按非递减顺序排列。
- 每行的第一个整数大于前一行的最后一个整数。
给你一个整数 target
,如果 target
在矩阵中,返回 true
;否则,返回 false
。
力扣解法一:两次二分查找
由于每行的第一个元素大于前一行的最后一个元素,且每行元素是升序的,所以每行的第一个元素大于前一行的第一个元素,因此矩阵第一列的元素是升序的。
我们可以对矩阵的第一列的元素二分查找,找到最后一个不大于目标值的元素,然后在该元素所在行中二分查找目标值是否存在。
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
//upper_bound从matrix迭代器找到第一个比目标值大的数,结合lambda重写比较规则
auto left = upper_bound(matrix.begin(), matrix.end(), target, [](const int b, const vector<int>&a){
return b < a[0];
});
//表明没找到:target比开头的数还小,返回false
if(left == matrix.begin()) return false;
--left;
return binary_search(left->begin(), left->end(), target);
}
};
力扣解法二:一次二分查找
若将矩阵每一行拼接在上一行的末尾,则会得到一个升序数组,我们可以在该数组上二分找到目标元素。代码实现时,可以二分升序数组的下标,将其映射到原矩阵的行和列上。
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int m = matrix.size(), n = matrix[0].size();
int left = 0, right = m * n - 1;
while(left <= right){
int mid = left + (right - left) / 2;
//二维数组定位
int x = matrix[mid / n][mid % n];
if(x < target) left = mid + 1;
else if(x > target) right = mid - 1;
else return true;
}
return false;
}
};
收获总结:
1.两种方法殊途同归,都利用了二分查找,在二维矩阵上寻找目标值。值得注意的是,若二维数组中的一维数组的元素个数不一,方法二将会失效,而方法一则能正确处理。
2.注意lambda表达式、库函数binary_search、upper_bound函数的运用(在知晓原理可省时间)。
3.二维数组的定位 / 和 %。
240.搜索二维数组II
编写一个高效的算法来搜索 m x n
矩阵 matrix
中的一个目标值 target
。该矩阵具有以下特性:
- 每行的元素从左到右升序排列。
- 每列的元素从上到下升序排列。
力扣解法一:直接遍历
我们直接遍历整个矩阵 matrix,判断 target是否出现即可。
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
for (const auto& row: matrix) {
for (int element: row) {
if (element == target) {
return true;
}
}
}
return false;
}
};
力扣解法二:依行二分查找
由于矩阵 matrix中每一行的元素都是升序排列的,因此我们可以对每一行都使用一次二分查找,判断target 是否在该行中,从而判断target 是否出现。
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
for (const auto& row: matrix) {
auto it = lower_bound(row.begin(), row.end(), target);
if (it != row.end() && *it == target) {
return true;
}
}
return false;
}
};
力扣解法三: Z字形查找
我们可以从矩阵 matrix的右上角 (0,n−1)进行搜索。在每一步的搜索过程中,如果我们位于位置 (x,y),那么我们希望在以 matrix 的左下角为左下角、以(x,y) 为右上角的矩阵中进行搜索,即行的范围为 [x,m−1],列的范围为 [0,y]:
如果 matrix[x,y]=target,说明搜索完成;
如果 matrix[x,y]>target,由于每一列的元素都是升序排列的,那么在当前的搜索矩阵中,所有位于第 y 列的元素都是严格大于target 的,因此我们可以将它们全部忽略,即将 y 减少 1;
如果 matrix[x,y]<target,由于每一行的元素都是升序排列的,那么在当前的搜索矩阵中,所有位于第 x行的元素都是严格小于 target 的,因此我们可以将它们全部忽略,即将 x增加 1。
在搜索的过程中,如果我们超出了矩阵的边界,那么说明矩阵中不存在 target。
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int m = matrix.size(), n = matrix[0].size();
int x = 0, y = n - 1;
while (x < m && y >= 0) {
if (matrix[x][y] == target) {
return true;
}
if (matrix[x][y] > target) {
--y;
}
else {
++x;
}
}
return false;
}
};
收获总结:
1.二分查找每次搜索可以排除半行或半列的元素,Z字形查找每次搜索可以排除一行或一列的元素。