第一个错误的版本
参考代码
//使用查到第一个满足某条件的模板
// Forward declaration of isBadVersion API.
bool isBadVersion(int version);
class Solution {
public:
int firstBadVersion(int n) {
int left=1,right=n;
while(left<right){
long long mid=left+(right-left)/2;
if(isBadVersion(mid))
right=mid;
else
left=mid+1;
}
return left;
}
};
两个数组的交集II
参考代码
常规解法(哈希表)
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
vector<int> ret;
if(nums1.size()==0||nums2.size()==0) return ret;
map<int ,int > mmap;
for(int x:nums1){
if(mmap.find(x)==mmap.end()){
mmap.insert(make_pair(x,1));
}
else{
mmap[x]++;
}
}
for(int x:nums2){
if(mmap.find(x)!=mmap.end()){
if(mmap[x]!=0){
ret.push_back(x);
mmap[x]--;
}
}
}
return ret;
}
};
进阶解法
1.两个数组排序
2.设定两个指向这两个数组头部的指针,比较两个指针的元素是否相等
3.如果相等,元素push到返回值里,两个指针同时往前
4.如果不相等,元素小的指针往前(因为大元素可能在小元素数组里存在,但是小元素在大元素所在数组肯定不存在。因为已经排过序了。)
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
vector<int> ret;
if(nums1.size()==0||nums2.size()==0) return ret;
sort(nums1.begin(),nums1.end());
sort(nums2.begin(),nums2.end());
int p=0;
int q=0;
while(p<nums1.size()&&q<nums2.size()){
if(nums1[p]==nums2[q]){
ret.push_back(nums1[p]);
p++;
q++;
}else if(nums1[p]<nums2[q]){
p++;
}else{
q++;
}
}
return ret;
}
};
常规解法的时间和空间复杂度都是O(n)
进阶解法的时间复杂度是O(nlogn),空间复杂度是O(1)
进阶问题:
1.若给定的数组排好序了,则使用进阶解法
2.若nums1的大小比nums小很多,先排序,再查找
3.先将nums1的元素存在map中,再遍历nums2进行查找
扩充知识
判断子序列
参考代码
class Solution {
public:
bool isSubsequence(string s, string t) {
if(s.size()==0) return true;
int a=0;
for(int i=0;i<t.size();i++){
if(s[a]==t[i])
a++;
if(a==s.size()) return true;
}
return false;
}
};
后继挑战
后续挑战,输入量大,小写字母创建25的二维数组,存储t的坐标,这样就可以把s的判断直接转为坐标的判断,
dp[0]代表了存储了a出现在t的所有的位置,逐个字符判断s的字符顺序是否在t内,直接返回结果。
时间复杂度O(t.size()+2000):分别为创建数组需要O(t.size()),
索引是递增的使用二分查找s的单个字符20次之内就可找到需要O(100* 20)。
适用大量的输入判断子序列。
bool isSubsequence(string s, string t) {
vector<vector<int>>dp(26);
int tag=-1;//tag记录s中上一个字符在t中的位置
for(int i=0;i<t.size();i++)
dp[t[i]-'a'].push_back(i);
for(int i=0;i<s.size();i++){
int now=s[i]-'a';
int left=0,right=dp[now].size()-1;
//在t中找字符s[i],这个字符是第一个位置大于tag的
while(left<right){
int mid=(left+right)/2;
if(dp[now][mid]>tag)
right=mid;
else
left=mid+1;
}
//如果right<left说明dp[now].size()==0
//dp[now][left]<tag已经遍历到了dp[now].size()-1那个元素
if(right<left || dp[now][left]<tag)return false;
tag=dp[now][left];
}
return true;
}
供暖器
参考代码
//解题思路:
//遍历所有House,记录当前house距离最近heater的距离,最后所有House的距离中的最大值即为所求
class Solution {
public:
int findRadius(vector<int>& houses, vector<int>& heaters) {
sort(houses.begin(),houses.end());
sort(heaters.begin(),heaters.end());
int maxDis=0;
for(int c:houses){
//找到第一个大于等于c的heater
int left=0,right=heaters.size();
while(left<right){
long long mid=left+(right-left)/2;
if(heaters[mid]>=c)
right=mid;
else
left=mid+1;
}
//说明没有比C位置更大的heater
if(left==heaters.size()) maxDis=max(maxDis,(c-heaters[left-1]));
else if(heaters[left]==c) continue;
else if(left==0) maxDis=max(maxDis,heaters[left]-c);
else maxDis=max(maxDis,min(heaters[left]-c,c-heaters[left-1]));
}
return maxDis;
}
};
山脉数组的峰顶索引
参考代码
class Solution {
public:
int peakIndexInMountainArray(vector<int>& A) {
int left=0,right=A.size()-1;
if(A.size()<=2) return -1;
while(left<=right){
long long mid=left+(right-left)/2;
if(A[mid]>A[mid-1]&&A[mid]>A[mid+1]) return mid;
else if(A[mid]>A[mid-1])
left=mid+1;
else
right=mid-1;
}
return -1;
}
};
搜索旋转排序数组
参考代码
class Solution {
public:
int search(vector<int>& nums, int target) {
int left=0,right=nums.size()-1;
while(left<=right){
long long mid=left+(right-left)/2;
if(nums[mid]==target) return mid;
else if(nums[mid]>=nums[left]){
//如果前半部分有序,注意是大于等于,当等于时mid==left,此时是一个元素,也认为有序,否则会出错
if(nums[left]<=target&&target<nums[mid])
right=mid-1;
else
left=mid+1;
}else{
//后半部分有序
if(nums[mid]<target&&target<=nums[right])
left=mid+1;
else
right=mid-1;
}
}
return -1;
}
};
搜索旋转排序数组 II
参考代码
class Solution {
public:
bool search(vector<int>& nums, int target) {
if(nums.size()==0) return false;
int left=0,right=nums.size()-1;
while(left<=right){
long long mid=left+(right-left)/2;
if(nums[mid]==target) return true;
else if(nums[mid]==nums[left]&&nums[mid]==nums[right]){//中间与两边的值相等时,分不清前面有序还是后面有序
left=left+1;
right=right-1;
}
else if(nums[mid]>=nums[left]){
if(nums[mid]>target&&target>=nums[left])
right=mid-1;
else
left=mid+1;
}else if(nums[mid]<nums[left]){
if(nums[mid]<target&&target<=nums[right])
left=mid+1;
else
right=mid-1;
}
}
return false;
}
};
pow(x,n)
参考代码
class Solution {
public:
double myPow(double x, int n) {
if(n==0) return 1;
long long N=n;
if(n<0){
x=1/x;
N=-N;//不能写成N=-n,因为若n=-x^31,此时会越界
}
double ret=1,base=x;
while(N){
if(N&1){//若N为奇数
ret*=base;
}
base*=base;
N>>=1;//N右移一位
}
return ret;
}
};
搜索二维矩阵
参考代码
//将二维矩阵展开进行二分查找
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
if(matrix.size()==0) return false;
int n=matrix.size();
int m=matrix[0].size();
int left=0,right=n*m-1;
while(left<=right){
long long mid=left+(right-left)/2;
int row=mid/m,col=mid%m;
if(matrix[row][col]==target) return true;
else if(matrix[row][col]>target)
right=mid-1;
else
left=mid+1;
}
return false;
}
};
寻找旋转排序数组中的最小值
参考代码
class Solution {
public:
int findMin(vector<int>& nums) {
if(nums.size()==1) return nums[0];
int left=0,right=nums.size()-1;
if(nums[left]<nums[right]) return nums[left];
while(left<=right){
long long mid=left+(right-left)/2;
if(nums[mid]>nums[mid+1]) //因为mid取得是左中位数,所以mid+1一定不会越界
return nums[mid+1];
if(nums[mid]<nums[mid-1]) //放在第二个判断,一定不能放在第一个判断,因为不能保证mid-1>=0,而当只有两个元素是会出现mid-1=-1的情况,此时第一个判断条件可以直接得到结果,不会进入该判断
return nums[mid];
if(nums[mid]>nums[0])
left=mid+1;
else
right=mid-1;
}
return nums[left];
}
};
寻找峰值(?)
参考代码
//解题关键:只要数组中存在一个元素比相邻元素大,那么沿着它一定可以找到一个峰值
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int left=0,right=nums.size()-1;
while(left<right){
long long mid=left+(right-left)/2;
if(nums[mid]>nums[mid+1])
right=mid;
else
left=mid+1;
}
return left;
}
};
长度最小的子数组
参考代码(双指针)
//定义左右指针p,q
//若[p,q]区间内的元素和大于等于s,则p++
//若[p,q]区间内的元素和小于s,则q++
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
if(nums.size()==0) return 0;
int sum=nums[0];
int len=INT_MAX;
int p=0,q=0;
while(true){
if(sum>=s)
{
len=min(len,(q-p)+1);
sum-=nums[p];
p++;
if(p>=nums.size()) break;
}else{
q++;
if(q>=nums.size()) break;
sum+=nums[q];
}
}
return len==INT_MAX?0:len;
}
};
搜索二维矩阵 II
参考代码
解法一(排除法)
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
if(matrix.size()==0||matrix[0].size()==0)
return false;
int row=0;
int col=matrix[0].size()-1;
while(row<matrix.size()&&col>=0){
if(matrix[row][col]<target)
row++;
else if(matrix[row][col]>target)
col--;
else
return true;
}
return false;
}
};
解法二:递归二分法
//解题思路:我们可以将已排序的二维矩阵划分为四个子矩阵,其中两个可能包含目标,其中两个肯定不包含
//我们沿着索引行的矩阵中间列,则目标值要么在中间列上,要么在左上和右下举矩阵中
H指数II
参考代码
//套用模板查找第一个满足条件的位置,注意要考虑不存在的情况
class Solution {
public:
int hIndex(vector<int>& citations) {
if(citations.size()==0) return 0;
if(citations.size()==1&&citations[0]) return 1;
else if(citations.size()==1&&citations[0]==0) return 0;
int left=0,right=citations.size()-1;
while(left<right){
long long mid=left+(right-left)/2;
int num=citations.size()-mid;
if(citations[mid]>=num)
right=mid;
else
left=mid+1;
}
int ret=citations.size()-left;
return (ret<=citations[left])?ret:0;
}
};
寻找重复数
参考代码
哈希表法
class Solution {
public:
int findDuplicate(vector<int>& nums) {
unordered_map<int,int> mmap;
for(int x:nums){
if(mmap.find(x)==mmap.end()){
mmap.insert(make_pair(x,1));
}else{
mmap[x]++;
if(mmap[x]>1) return x;
}
}
return -1;
}
};
二分法
class Solution {
public:
int findDuplicate(vector<int>& nums) {
//这里的left,right并不是索引下标,而是nums的取值范围[left,right],初始时的取值范围是[1,n]
int left=1,right=nums.size()-1;
while(left<right){
//这里的mid是指中位数,不是索引
long long mid=left+(right-left)/2;
int counter=0;
//统计有多少个小于等于mid的数,若没有重复的数,则小于等于mid的数应当有mid个
for(int num:nums)
if(num<=mid)
counter++;
if(counter>mid)
right=mid;
else
left=mid+1;
}
return left;
}
};
有序矩阵中第k小的元素
参考代码
解题思路:
首先考虑一维数组的情况:
[1,3,2,5,4,6,8,9] k=3
首先计算出中位数(1+9)/2=5,统计小于等于5的个数为5>=3,所以目标值在[1,5]范围内
再计算中位数(1+5)/2=3,同理统计个数有3>=3,所以目标值在[1,3]内
再计算中位数(1+3)/2=2,统计个数有2<3,所以目标值在[3,3],此时只有一个数,所以目标值就是3
二维数组同理,有序二维矩阵的最小值为最左上角的数,最大值为右下角的数
class Solution {
public:
int kthSmallest(vector<vector<int>>& matrix, int k) {
int len=matrix.size();
int left=matrix[0][0],right=matrix[len-1][len-1];
while(left<right){
long long mid=left+(right-left)/2;
//统计小于等于mid的个数
int j=matrix[0].size()-1;
int counter=0;
for(int i=0;i<len;i++){
while(j>=0&&matrix[i][j]>mid) j--;
counter+=(j+1);
}
if(counter>=k)
right=mid;
else
left=mid+1;
}
return left;
}
};
疑问:如何保证每次迭代求得的mid一定在有序矩阵中
总结
1.使用二分模板查找满足某条件的元素时,若该元素不一定存在,一定要对边界情况进行讨论。
2.对于一些找目标值的问题,若给出的数组中数的取值范围确定,则可以考虑对数的值的二分查找,而不是对索引的二分查找,比如寻找重复数问题,有序矩阵中第k小的元素
3.二分法的使用需要数据是有序的,同时在编写的时候一定要避免出现死循环