33. 搜索旋转排序数组
链接:33
方法:部分有序的二分查找
思路
每次查找时根据mid将数组一分为二,其中有一侧是有序的,另一侧可能是有序,也可能是部分有序。
此时若target位于有序部分,则用二分法查找;若位于无序部分,再一分为二,继续之前的操作。
可以利用nums[l],nums[mid],nums[r]的大小关系判断局部是否有序。
代码
class Solution {
public int search(int[] nums, int target) {
int len=nums.length;
int l=0,r=len-1;
while(l<=r){
int mid=(r-l)/2+l;
if(nums[mid]==target){
return mid;
}
//左侧有序
if(nums[mid]>=nums[l]){
if(target>=nums[l]&&target<nums[mid]){
r=mid-1;
}
else{
l=mid+1;
}
//右侧有序
}else{
if(target>nums[mid]&&target<=nums[r]){
l=mid+1;
}
else{
r=mid-1;
}
}
}
return -1;
}
}
复杂度分析
- 时间复杂度O(logn)
- 空间复杂度O(1)
81. 搜索旋转排序数组 II
链接:81
方法:部分有序的二分查找(含重复元素)
思路
和33题的区别在于,旋转前数组是非降序排列,而非升序排列。这一差异造成的影响就是,33题中判断有序区间的方法不再通用,例如如下示例:
[1,13,1,1,1,1]
13
会出现nums[l],nums[mid],nums[r]皆相等,无法判断有序区间的情况。
因此针对这一特殊情况做一下特判,此时二分查找的左右两边各缩小1;其余情况下的做法与33题相同。
代码
class Solution {
public boolean search(int[] nums, int target) {
int len=nums.length;
int l=0,r=len-1;
while(l<=r){
int mid=(r-l)/2+l;
if(nums[mid]==target){
return true;
}
//无法确定有序部分
if((nums[mid]==nums[l])&&(nums[mid]==nums[r])){
r--;
l++;
}
//右侧有序
else if(nums[mid]<=nums[r]){
if(target>nums[mid]&&target<=nums[r]){
l=mid+1;
}
else{
r=mid-1;
}
}
//左侧有序
else if(nums[mid]>=nums[l]){
if(target>=nums[l]&&target<nums[mid]){
r=mid-1;
}
else{
l=mid+1;
}
}
}
return false;
}
}
复杂度分析
- 时间复杂度O(n)
- 空间复杂度O(1)
153. 寻找旋转排序数组中的最小值
链接:153
方法:部分有序的二分查找
思路
此题中,数组旋转后呈现如上图所示的大小关系,二分法寻找最小值的思路很简单:
- 若mid位于第一段升序数组,则最小值一定出现在mid右侧
- 若mid位于第二段升序数组或数组整体有序,则最小值一定出现在mid左侧(含mid)
需要注意区分两种情况使用的条件,要用nums[mid]和nums[r]的大小关系来判断,nums[mid]和nums[l]的大小关系不能实现区分。
代码
class Solution {
public int findMin(int[] nums) {
int len=nums.length;
int l=0,r=len-1;
while(l<r){
int mid=(r-l)/2+l;
if(nums[mid]>nums[r]){
l=mid+1;
}else{
r=mid;
}
}
return nums[l];
}
}
复杂度分析
- 时间复杂度O(logn)
- 空间复杂度O(1)
154. 寻找旋转排序数组中的最小值 II
链接:154
方法:部分有序的二分查找(含重复元素)
思路
此题和153的区别在于旋转前数组为非降序,而非升序。
大致思路和153相同,需要处理一下多个数组元素相等时的情况。
- nums[mid]>nums[r]时,最小值一定出现在mid右侧
- nums[mid]<nums[r]时,最小值一定出现在mid左侧(含mid)
- nums[mid]==nums[r]时,如下图所示,由于数组包含相同元素,因此不能判断最小值出现在哪一侧,因此只是除去nums[r]
代码
class Solution {
public int findMin(int[] nums) {
int len=nums.length;
int l=0,r=len-1;
while(l<r){
int mid=(r-l)/2+l;
if(nums[mid]<nums[r]){
r=mid;
}else if(nums[mid]>nums[r]){
l=mid+1;
}else{
r--;
}
}
return nums[l];
}
}
复杂度分析
- 时间复杂度O(n)
- 空间复杂度O(1)
240. 搜索二维矩阵 II
链接:240
方法一:逐行二分查找
思路
对每一行进行二分查找。
代码
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
//每行二分
int m=matrix.length,n=matrix[0].length;
for(int i=0;i<m;i++){
int l=0,r=n-1;
while(l<=r){
int mid=(r-l)/2 +l;
if(matrix[i][mid]==target){
return true;
}
if(matrix[i][mid]<target){
l=mid+1;
}
else{
r=mid-1;
}
}
}
return false;
}
}
复杂度分析
- 时间复杂度O(mlogn)
- 空间复杂度O(1)
方法二:z字形查找
思路
以矩阵右上角(或者左下角)为基准,若目标值小于基准,则排除基准所在列;若目标值大于基准,则排除基准所在行;若目标值等于基准,则返回查找成功。
这里选择基准的标准在于:是否每次对比都能缩小范围。左上角和右下角是不满足的。
代码
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
//z字形查找
int m=matrix.length,n=matrix[0].length;
int x=0,y=n-1;
while(x<=m-1&&y>=0){
int s=matrix[x][y];
if(target==s){
return true;
}
if(target>s){
x++;
}
if(target<s){
y--;
}
}
return false;
}
}
复杂度
- 时间复杂度O(m+n)
- 空间复杂度O(1)
总结
- 重点:在部分有序、整体无序的序列中进行二分查找
- 尽量把图画出来以便分析各种情况
- 需要格外注意边界和相等情况的处理
- 一般利用nums[mid],nums[r],nums[l]的大小关系来判断当前查找区间属于哪种情况
- 若数组中存在重复元素,在处理对应情况时一般采用只排除一或两个元素的方法,无法达到log时间复杂度