一、在前面
最近准备了个面试,算法题考到了二分法。故而刷了leetcode上几个经典的二分法题目。这里记录两个medium难度的二分法题目。
二、leetcode33
(1)题目描述
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。
示例 1:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
示例 2:
输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1
(2)代码
class Solution {
public:
int search(vector<int>& nums, int target) {
int begin=0;
int end=nums.size()-1;
int index;
while(begin<=end)
{
index=(begin+end)/2;
if(nums[index]==target)
return index;
else if(nums[end]>=nums[index])
{
if(target>=nums[index]&&target<=nums[end])
begin=index+1;
else
end=index-1;
}
else
{
if(target>=nums[begin]&&target<=nums[index])
end=index-1;
else
begin=index+1;
}
}
return -1;
}
};
(3)分析
这个题目的重点是要知道,每次二分的时候,二分位置(也就是上面代码中的index)的左右两侧,也就是代码中的[begin,index]和[index,end]这部分的数据中,必定有一个是单调递增的。因为这里的旋转数组的定义,就决定了这个。这样的话,我们每次在while(begin<=end)循环中,就可以通过判断nums[end]和nums[index]这两个数据的大小,来判断这个递增区间是[begin,index]还是[index,end]。如果递增区间是[begin,index],通过判断target是否大于nums[begin],小于nums[index],就可以知道target是否在[begin,index]这个区间。如果在[begin,index]这个区间就相应的将其作为下一个二分的区间,否则就将另外一个[index,end]作为下一个二分的区间。
重点就是要想清楚,每次二分的时候,左右两个区间中,必定有一个递增的。
三、leetcode153
(1)题目描述
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中最小的元素。
你可以假设数组中不存在重复元素。
示例 1:
输入: [3,4,5,1,2]
输出: 1
示例 2:
输入: [4,5,6,7,0,1,2]
输出: 0
(2)代码
我的代码:
class Solution {
public:
int findMin(vector<int>& nums) {
int minimun=10000;
int left=0;
int right=nums.size()-1;
int center;
if(nums.size()==1)
return nums[0];
if(nums[nums.size()-1]>nums[0])
return nums[0];
// if(nums[nums.size()-1]<nums[nums.size()-2])
// return nums[nums.size()-1];
while(left<=right)
{
center=(left+right)/2;
if(center!=0&¢er!=(nums.size()-1))
{
if(nums[center]<nums[center-1]&&nums[center]<nums[center+1])
{
return nums[center];
}
}
if(right>=(center+1)&&nums[right]>=nums[center+1])
{
if((center+1)>0&&(center+1)<(nums.size()-1))
{
if(nums[center+1]<nums[center]&&nums[center+1]<nums[center+2])
{
return nums[++center];
}
}
right=center-1;
}
if((center-1)>=left&&nums[center-1]>=nums[left])
{
if((center-1)>0&&(center-1)<(nums.size()-1))
{
if(nums[center-1]<nums[center-2]&&nums[center-1]<nums[center])
{
return nums[--center];
}
}
left=center+1;
}
}
return -1;
}
};
网上的较为简洁的代码:
class Solution {
public:
int findMin(vector<int>& nums) {
int left=0;
int right=nums.size()-1;
int center;
if(nums.size()==1)
return nums[0];
if(nums[nums.size()-1]>nums[0])
return nums[0];
while(left<right)
{
center=(left+right)/2;
if(nums[center]>nums[left])
left=center;
else
right=center;
}
return min(nums[left],nums[left+1]);
}
};
(3)分析
这个题目,我的代码过于复杂了,主要是我自己写的时候,想的太复杂了,考虑到了很多种情况,所以代码越来越复杂。其实这个题目就跟上面的leetcode33的解题思路是差不多的,也是要利用旋转数组的这种特性,左右两个区间中必定有一个递增的。那每次就可以先找到递增的区间,找到之后,我们的最小元素肯定不在这个递增区间啊,但是这个思路有一个很重要的点就是,要知道每次二分得到的center位置的数据,也有可能是我们要找到的最小元素。所以我们确定了递增区间后,在确定下一次循环的区间时,必须要将center位置考虑进去。也就是此时的操作是left=center或者是right=center,而不是left=center+1或者right=center-1.