第一题,数字在排序数组中出现的次数
统计一个数字在排序数组中出现的次数. 例如输入排序数组{1,2,3,3,3,3,4,5},由于3在这个数中出现了4次,输出4.
思路
如何利用二分查找找到第一个k? 二分查找算法总是先拿数组中间的数组和k作比较.
- 如果中间数字比k大,那么k只可能出现数组的前半段,下一轮只在数组的前半段查找就可以了.
- 如果中间数字比k小,那么k只可能出现数组的后半段,下一轮只在数组的后半段查找就可以了.
- 如果中间数组和k相等,先判断这个数字是不是第一个k.
- 如果位于中间数字前面的一个数字不是k,那么此时中间的数字刚好就是第一个k.
- 如果中间数字的前面一个数字也是k,也就是说第一个k肯定在数组的前半段,下一轮仍然需要在数组的前半段查找.
基于同样的思路可以在排序数组中找到最后一个k.
- 如果中间数字比k大,那么k只能出现在数组的前半段.
- 如果中间数字比k小,那么k只能出现在数组的后半段.
- 如果中间数子和k相等,需要判断这个数字是不是最后一个k.
- 如果位于中间数字后面一个数字不是k,那么此时中间的数字刚好就是最后一个k.
- 如果中间数字的后面一个数字也是k,也就是说第一个k肯定在数组的后半段,下一轮仍然需要在数组的后半段查找.
#include <iostream> #include <vector> using namespace std; class Solution { public: int get_num_k(const vector<int> &v,int k); int get_first_k(const vector<int> &v,int k,int start,int end); int get_last_k(const vector<int> &v,int k,int start,int end); }; int Solution::get_num_k(const vector<int> &v,int k) { if(v.empty()||v.size()<0) return -1; int first=get_first_k(v,k,0,v.size()-1); int last=get_last_k(v,k,0,v.size()-1); if(first>=0&&last<v.size() ) return last-first+1; else return 0; } int Solution::get_first_k(const vector<int> &v,int k,int start,int end) { if(v.empty()||v.size()<0) return -1; int mid=(start+end)>>1; int mid_value=v[mid]; if(k==mid_value) { if((mid>0&&v[mid-1]!=k)||mid==0) return mid; else end=mid-1; } else if(k>mid_value) start=mid+1; else end=mid-1; return get_first_k(v,k,start,end); } int Solution::get_last_k(const vector<int> &v,int k,int start,int end) { if(v.empty()||v.size()<0) return -1; int mid=(start+end)/2; int mid_value=v[mid]; if(mid_value==k) { if((mid<v.size()-1&&v[mid+1]!=k)||mid==v.size()-1) return mid; else start=mid+1; } else if(mid_value>k) end=mid-1; else start=mid+1; return get_last_k(v,k,start,end); } int main() { vector<int> v{1,2,3,3,3,3,4,5}; int k; cin>>k; Solution s; cout<<s.get_num_k(v,k)<<endl; return 0; }
第二题,0~n-1中缺失的数字
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0 ~ n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
思路
1.先用公式n*(n-1)/2求出0~n-1所有数字之和s1,在求出数组所有数字之和s2,那个不在数组中的数字就是s1-s2的差,这种解法需要O(n)的时间
2.数组一开始的一些数字与他们的下表相同,如果说不在数组中的那个数组记为m,那么所有比m小的数字下标与他们的值都相同。可以基于二分查找
1>如果中间元素的值和它的下标相等,那么下一轮只需要找它的右半边
2>如果中间元素的值和它的下标不等,并且它前一个元素和它的下标相等,意味着这个中间的数字就是第一个值与下标不等的数字。
3>如果中间元素的值和它的下标不等,并且他前面的元素和它的下标相等不等,那么只需查找左半边。
#include <iostream> #include <vector> using namespace std; class Solution { public: int get_miss_num(const vector<int> &v,int len); }; int Solution::get_miss_num(const vector<int> &v,int len) { if(v.empty()||v.size()<=0||len<=0) return -1; int left=0,right=len-1; while(left<=right) { int mid=(left+right)>>1; if(v[mid]!=mid) { if(mid==0||v[mid-1]==mid-1) return mid; else right=mid-1; } else left=mid+1; } if(left==len) return len; } int main() { vector<int> v{1}; Solution s; cout<<s.get_miss_num(v,1)<<endl; return 0; }
第三题,数组中数值和它的下标相等的元素
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0到n-1之内。在范围0到n-1的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
思路
由于数组是单调递增排序的,因此我们可以尝试二分查找算法来进行优化。假设我们某一步抵达数组中的第i个数字。如果我们很幸运,该数字的值刚好也是i,那么我们就找到了一个数字和其下标相等。
当数字的值和下标不相等的时候,假设数字的值为m。先考虑m大于i的情形,即数字的值大于它的下标。由于数组中的所有数字都唯一并且单调递增,那么对于任意大于0的k,位于下标i+k的数字的值大于或等于m+k。另外,因为m>i,所以m+k>i+k。因此,位于下标i+k的数字的值一定大于它的下标。这意味着如果第i个数字的值大于i,那么它的右边的数字都大于对应的下标,我们都可以忽略。下一轮查找只需要从它左边的数字中查找即可。
数字的值m小于它的下标i的情形和上面类似。它左边的所有数字的值都小于对应的下标,也可以忽略。
也可以这样想:
先获取数组的中间数,若中间数的值和下标相等,则找到一个满足条件的数;若中间数的值大于它的下标,则根据数组递增的特性可知:中间元素至少比他的下标大1,而且中间数以后的元素的值至少每个比前面大1,同时它们的下标每次也是增加1,所以右边的这些元素的值都大于它们的下标(至少大1),因此需要继续在左边寻找。同理,若中间数的值小于它的下标,则中间数左边的那些元素的值也都小于它们的下标,因此需要继续在右边寻找。
#include <iostream> #include <vector> using namespace std; class Solution { public: int get_num_same_as_index(const vector<int> &v); }; int Solution::get_num_same_as_index(const vector<int> &v) { if(v.empty()||v.size()<0) return -1; int left=0; int right=v.size()-1; while(left<=right) { int mid=(left+right)>>1; if(v[mid]==mid) return mid; if(v[mid]>mid) right=mid-1; else left=mid+1; } } int main() { vector<int> v{-3,-1,1,3,5}; Solution s; cout<<s.get_num_same_as_index(v)<<endl; return 0; }