目录
有时数据排序后很方便二分,所以这也是为啥官方题解名字会加个/排序吧,= ̄ω ̄=
一、二分查找


注意while循环条件含有等于,因为我们还需要判断元素的相等,当left==right我们还需要对这个位置的元素做一步判断,所以得是等于。
如果是要找相对位置不需要进一步判断元素相等的情况,则不需要加上等于,因为当left==right时,该位置就是我们需要的位置。
int search(vector<int>& nums, int target) {
//特殊处理
int n=nums.size();
if(n==0)return -1;
int left=0,right=n-1;
//朴素二分等于时仍然需要判断
while(left<=right)
{
int mid=(left+right)>>1;
//不等时跳过mid
if(nums[mid]<target)left=mid+1;
else if(nums[mid]>target)right=mid-1;
else return mid;
}
return -1;
}
二、二维数组中的查找


解法一:二维遍历+每一层二分查找
二维遍历嵌套朴素二分模板即可,当tartget小于遍历到的一维数组第一个元素,直接return false
bool Find(int target, vector<vector<int> >& array) {
int m=array.size(),n=array[0].size();
if(m==0||n==0)return false;
for(int i=0;i<m;++i)
{
if(target<array[i][0])break;
int left=0,right=n-1;
while(left<=right)
{
int mid=(left+right)>>1;
if(array[i][mid]<target)left=mid+1;
else if(array[i][mid]>target)right=mid-1;
else return true;
}
}
return false;
}
解法二:线性查找
摘自牛客第一篇题解,不得不说真是妙妙妙妙

bool Find(int target, vector<vector<int> >& array) {
int m=array.size(),n=array[0].size();
if(m==0||n==0)return false;
//每次只会往上或者往右
int i=m-1,j=0;
while(i>=0&&j<=n-1)
{
if(array[i][j]>target)--i;
else if(array[i][j]<target)++j;
else return true;
}
return false;
}
三*、寻找峰值



与该题的第三次会面
都说二分查找本质是利用数据的二段性,将数据划分为两个区间,然后舍弃一部分区间,高效地找到目标点。这里的二段性,就是数组元素的递增性和递减性


以下有四种正确策略,你可以按照你想写的那种,参考一下鄙人拙见。(*^-^*)
写到这里,思考是用nums[mid+1]还是nums[mid-1]参与判断
如果是mid+1:mid和mid+1之间,
要么写[mid]<[mid+1]:
mid+1是我要的,判断达成后写left=mid+1(为啥呢,这保证left为起点的左边保证都是有递增的,left起点右边仍然值得探索,如果right来得到赋值,那么右边的区域就无法探索了)
else right=mid;
因为这里是right赋值mid,所以mid应该采用选左边所以是left+right,这符合,按这个思路继续写
能过
int findPeakElement(vector<int>& nums) {
int n=nums.size();
int left=0,right=n-1;
while(left<right)
{
int mid=(left+right)>>1;
if(nums[mid]<nums[mid+1])
{
left=mid+1;
}
else right=mid;
}
return left;
}
要么写[mid]>[mid+1]:
mid是我要的,判断达成后left=mid,有误,应该是right=mid这里递减了,right为起点保证右边存在递减区间,左边的区间仍然值得探索
else mid+1就是我left要的,left=mid+1
因为mid赋值给了right,所以mid的策略选择采用左边,left+right嗯正确的,按照这个思路继续写
通过
int findPeakElement(vector<int>& nums) {
int n=nums.size();
int left=0,right=n-1;
while(left<right)
{
int mid=(left+right)>>1;
if(nums[mid]>nums[mid+1])
{
right=mid;
}
else left=mid+1;
}
return left;
}
如果是mid-1,[mid]和[mid-1]之间,
要么写[mid]<[mid-1]:
这样有点不符合顺序性,看着别扭,改一下
要么写[mid-1]<[mid]:
mid是我们要的,判断达成后,left赋值mid
else right=mid-1
因为left赋值mid,所以mid得选择右边的,mid修改成(left+right+1)>>,按照思路继续写代码
通过
int findPeakElement(vector<int>& nums) {
int n=nums.size();
int left=0,right=n-1;
while(left<right)
{
int mid=(left+right+1)>>1;
if(nums[mid-1]<nums[mid])
{
left=mid;
}
else right=mid-1;
}
return left;
}
要么写[mid-1]>[mid]:
思考一下这里显示出递减性,那么当然是mid-1赋值给right
else left=mid
left赋值mid,mid选择右边,left+right+1,嗯是正确的,继续写
int findPeakElement(vector<int>& nums) {
int n=nums.size();
int left=0,right=n-1;
while(left<right)
{
int mid=(left+right+1)>>1;
if(nums[mid-1]>nums[mid])
{
right=mid-1;
}
else left=mid;
}
return left;
}
四、数组中的逆序对

法一:暴力
暴力解法,从左往右遍历设置两个指针,一个指针用来固定左边的数,然后另一个指针从该位置右边第一个位置开始左往右遍历,判断有几个比这个小的,然后让count加等于这个数。这样。一轮下来就能解决了。复杂度为N方。不过当然会超时的啦。
法二:归并排序
题目提示时间复杂度含有logn,要么是二分要么就是归并了
依照暴力的思路我们可以选择归并排序的思路,思路就是总体分为三步。将数组分为两块,然后。在左边那块先找出逆序对的数量,然后再找出右边的逆序对的数量,然后再将左右排序之后根据一左一右来找到逆序对。
首先我们不断分分分,在每一次归并的处理中,数据左边那一整块相对右边一整块的顺序和原数组是吻合的,我们可以先根据这个判断,同时在局部的时候排序,方便合之后,合之后,左边一整块和右边一整块的判断,比如看下面的两种策略
此外别忘了题目要求的取模,我的做法就是每次加法就取模
策略1:升序排序,合的时候,遇到右边数更小的情况,左边靠到mid的数据都可以,直接ret+=mid-cur1+1;
#include <vector>
class Solution {
vector<int> tmp;
const int MOD=1e9+7;
public:
int InversePairs(vector<int>& nums) {
tmp.resize(nums.size());
return MergeSort(nums,0,nums.size()-1);
}
int MergeSort(vector<int>& nums,int left,int right)
{
if(left>=right)return 0;
int mid=(left+right)>>1,ret=0;
//分!
ret=(ret+MergeSort(nums,left,mid))%MOD;
ret=(ret+MergeSort(nums, mid+1, right))%MOD;
//治!
int cur1=left,cur2=mid+1,i=0;
while(cur1<=mid&&cur2<=right)
{
//大于单独判断
if(nums[cur1]<=nums[cur2])
{
tmp[i++]=nums[cur1++];
}
else
{
ret=(ret+mid-cur1+1)%MOD;
tmp[i++]=nums[cur2++];
}
}
while(cur1<=mid)tmp[i++]=nums[cur1++];
while(cur2<=right)tmp[i++]=nums[cur2++];
//合!
for(int i=left;i<=right;++i)
{
nums[i]=tmp[i-left];
}
return ret;
}
};
策略2:降序,合的时候,找到左边数更大的情况,cur2靠到right都可以,直接ret+=right-cur2+1;
#include <vector>
class Solution {
vector<int> tmp;
const int MOD=1e9+7;
public:
int InversePairs(vector<int>& nums) {
tmp.resize(nums.size());
return MergeSort(nums,0,nums.size()-1);
}
int MergeSort(vector<int>& nums,int left,int right)
{
if(left>=right)return 0;
int mid=(left+right)>>1,ret=0;
//分!
ret=(ret+MergeSort(nums,left,mid))%MOD;
ret=(ret+MergeSort(nums, mid+1, right))%MOD;
//治!
int cur1=left,cur2=mid+1,i=0;
while(cur1<=mid&&cur2<=right)
{
//大于单独判断
if(nums[cur1]<=nums[cur2])
{
tmp[i++]=nums[cur2++];
}
else
{
ret=(ret+right-cur2+1)%MOD;
tmp[i++]=nums[cur1++];
}
}
while(cur1<=mid)tmp[i++]=nums[cur1++];
while(cur2<=right)tmp[i++]=nums[cur2++];
//合!
for(int i=left;i<=right;++i)
{
nums[i]=tmp[i-left];
}
return ret;
}
};
五**、旋转数组的最小数字

题目要求logn那就二分了呗
该题怎么二段性?
数组现在旋转后变成了两段,两段彼此独立的单调递增,那么二段性可以根据数组最后一个元素判断,大于该元素的分为第一段,小于等于该元素的分到第二段(错误,后面纠正)

二段性搞清楚,我们的判断条件就明确了,可以直接写代码了
因为我们这里是找位置,所以while整体的判断条件不用写等于
最先开始是这么写的,没有考虑存在重复元素,如果有重复元素比如1,0,1,1,1,[mid]==nums[n-1]会赋值right=mid,这样右边的就全被跳过了
所以我们得严格要求,当[mid]<[n-1]的时候才更新right=mid,

所以真实情况是这样的
等于时我们需要特殊处理,因为答案是位于right为起点往左的一段区间的,我们只需让right--即可,让right逼近目标点
修改后还存在问题,我们应到舍弃right右边部分,将[right]替换[n-1]的位置,这才是最终正确代码
int minNumberInRotateArray(vector<int>& nums) {
int n=nums.size();
int left=0,right=n-1;
while(left<right)
{
int mid=(left+right)>>1;
if(nums[mid]>nums[right])
{
left=mid+1;
}
else if(nums[mid]<nums[right])
{
right=mid;
}
else --right;
}
return nums[left];
}
六、比较版本号



解法:双指针遍历
这题为啥放在二分,搞不懂
int compare(string version1, string version2) {
int n1=version1.size(),n2=version2.size();
int cur1=0,cur2=0;
while(cur1<n1||cur2<n2)
{
long num1=0;
while(cur1<n1&&version1[cur1]!='.')
{
num1=num1*10+version1[cur1]-'0';
++cur1;
}
//跳过.
++cur1;
long num2=0;
while(cur2<n2&&version2[cur2]!='.')
{
num2=num2*10+version2[cur2]-'0';
++cur2;
}
++cur2;
if(num1>num2)return 1;
if(num1<num2)return -1;
}
return 0;
}

815

被折叠的 条评论
为什么被折叠?



