二分查找是一种在有序序列中进行检索的算法。
洛谷 P2249 【深基13.例1 】查找
通过比较中间值与要查找的数字的大小关系,确定搜索范围,不断进行二分搜索,
直到找到目标数字或搜索范围为空。
#include <bits/stdc++.h>
using namespace std;
int n,a[100000000],s;
int gg(int h){
int l,r,mid;
l=1,r=n;
while(r>l){
mid=l+(r-l)/2;
if(a[mid]>=h) r=mid;
else l=mid+1;
}
if(a[l]==h) return l;
else return -1;
}
int main(){int h;
cin>>n>>s;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=s;i++){
cin>>h;
cout<<gg(h)<<" ";
}
return 0;
}
这题也可以用 STL
自带的二分函数—— lower_bound
。
函数 返回第一个大于等于 h 的数的地址。因为是地址,在最后要 -a。
#include <bits/stdc++.h>
using namespace std;
int n,a[100000000],s;
int main(){
int h,m;
cin>>n>>s;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=s;i++){
cin>>h;
m=lower_bound(a+1,a+n+1,h)-a;
if(a[m]==h) cout<<m<<" ";
else cout<<"-1 ";
}
return 0;
}
P1102 A-B数对
这用常规的二分方法去做,稍微有些麻烦,使用标准库函数解决实际算法问题,效率很高。
#include <bits/stdc++.h>
using namespace std;
int main(){
int n,h,a[200000];
long long ans=0;
cin>>n>>h;
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+n+1);
for(int i=1;i<=n;i++){
ans+=upper_bound(a+1,a+n+1,a[i]+h)-lower_bound(a+1,a+n+1,a[i]+h);
}
cout<<ans;
return 0;
}
洛谷 P1873砍树
通过给每个数加上一个高度h,然后计算每个数比h高的部分的和cet,找到一个h的值使得cet大于等于
m,即可得到答案。使用二分查找,不断逼近h的值,并根据cet的大小进行更新l和r指针。最终输出middle即为答案。
#include <bits/stdc++.h>
using namespace std;
long long n,m,a[1000005],cet,l,r,mid;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
if(a[i]>r) r=a[i];
}
while(l<=r){
cet=0;
mid=l+(r-l)/2;
for(int i=1;i<=n;i++){
if(a[i]>mid) cet+=a[i]-mid;
}
if(m>cet) r=mid-1;
else l=mid+1;
}
cout<<r;
return 0;
}
[NOIP2001 提高组] 一元三次方程求解
通过二分查找 在-100~100之间找到三个根,并确定它们的取值范围。然后通过画线段和判断交点的方法,找到每个焦点的值,不断靠近精确值。最后输出结果。
#include <bits/stdc++.h>
using namespace std;
double a,b,d,c,mark=0;
double f(double x){
return a*x*x*x+b*x*x+c*x+d;
}
void find(double l,double r){
if(r-l<0.001) {
printf("%.2f ",r);
return;
}
double mid=(l+r)/2;
if(f(mid)==0) {printf("%.2f ",mid);return;
}
if(f(mid)*f(r)<0) find(mid,r);
else find(l,mid);
}
int main(){
cin>>a>>b>>c>>d;
for(double i=-100;i<=100,mark<3;i++){
if(f(i)==0) {
printf("%.2f ",i);
mark++;
continue;
}
if(f(i)*f(i+1)<0) {
find(i,i+1);
mark++;
}
}
return 0;
}
P1678 烦恼的高考志愿
题意是给定一组有序数组,要找到一个位置使得插入该位置后,相邻节点的差值最小。通过二分查找的方式,找到一个位置,然后进行差值比较,最终得到最小差值。
#include <bits/stdc++.h>
using namespace std;
long long n,m,a[1000005],s=0;
int find(int h){
int l=1,r=n;
while(true){
int mid=l-(l-r)/2;
if(a[mid]==h) return 0;
else if(a[mid]<h && a[mid+1]>=h) return min(a[mid+1]-h,h-a[mid]);
else if(a[mid]<h) l=mid+1;
else if(a[mid]>h) r=mid;
}
}
int main(){
long long b[1000005];
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=m;i++){
cin>>b[i];
}
sort(a+1,a+n+1);
for(int i=1;i<=m;i++){
if(a[n]<b[i]) s+=b[i]-a[n];
else if(b[i]<a[1]) s+=a[1]-b[i];
else s+=find(b[i]);
}
cout<<s;
return 0;
}
当然也可以用lower_bound代替find()函数
#include <bits/stdc++.h>
using namespace std;
long long n,m,a[1000005],s=0,k,b[1000005];
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=m;i++){
cin>>b[i];
}
sort(a+1,a+n+1);
for(int i=1;i<=m;i++){
if(a[n]<b[i]) s+=b[i]-a[n];
else if(b[i]<a[1]) s+=a[1]-b[i];
else {
k=lower_bound(a+1,a+n+1,b[i])-a;
s+=min(a[k]-b[i],b[i]-a[k-1]);
}
}
cout<<s;
return 0;
}
双指针
双指针比较灵活,可以大大降低时间复杂度,可用在数组,单链表等数据结构中。
快慢指针:一快一慢,步长一大一小。例如,是否有环问题(看慢指针是否能追上快指针),单链表找中间节点问题(快指针到单链表结尾,慢指针到一半)。
对撞指针:一左一右向中间逼近。
滑动窗口:一般是右端向右扩充,达到停止条件后右端不动,左端向右端逼近,逼近达到停止条件后,左端不动,右端继续扩充。
快慢指针
力扣 27. 移除元素
快指针来寻找新数组的元素 。新数组就是不含有目标元素的数组
慢指针指向更新 新数组下标的位置
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
if (val != nums[fastIndex]) {
nums[slowIndex++] = nums[fastIndex];
}
}
return slowIndex;
}
};
对撞指针
力扣 977.有序数组的平方
两个指针,i指向头,j指向尾,数组m和nums数组一样的大小,让k指向m数组终止位置
如果nums[i]*nums[i] > nums[j]*nums[j] , 则m[k--] = nums[i] * nums[i];
如果nums[i]*nums[i] > nums[j]*nums[j] , 则m[k--] = nums[i] * nums[i];
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector <int> m(nums.size(),0);
int k = nums.size()-1;
for (int i = 0,j = nums.size()-1; i <= j;){
if (nums[i]*nums[i] > nums[j]*nums[j]) {
m[k--] = nums[i] * nums[i];
i++;
}
else {
m[k--] =nums[j] * nums[j];
j--;
}
}
return m;
}
};
力扣 844 比较含退格的字符串
同时从后向前遍历S和T(i初始为S末尾,j初始为T末尾),记录#的数量,模拟消除的操作,如果#用完了,就开始比较Si]和T[j]。
class Solution {
public:
bool backspaceCompare(string s, string t) {
int r = s.size( ) - 1,r2 = t.size( ) - 1, mark = 0, mark2 = 0;
while(1) {
while (r >= 0) {
if (s[r] == '#') {
mark++;
}
else {if (mark > 0){
mark--;
}
else break;
}
r--;
}
while (r2 >= 0) {
if (t[r2] == '#') {
mark2++;
}
else {if (mark2 > 0){
mark2--;
}
else break;
}
r2--;
}
if (r < 0 || r2 < 0) break;if (s[r] != t[r2]) return false;
r--;r2--;
}
if (r == -1 && r2 == -1) return true;
return false;
}
};
滑动窗口
力扣 209.长度最小的子数组
窗口是满足其和 ≥ target的长度最小的连续子数组。如果当前窗口的值大于s了,窗口就要向前移动了。窗口的结束位置就是遍历数组的指针。
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
int minx = INT_MAX, l = 0, sum = 0;
for (int i = 0, j = 0; i < nums.size(); i++){
sum += nums[i];
while (j <= i && sum >= target) {
l = i - j + 1;
minx = min (minx,l);
sum -= nums[j];
j++;
}
}
return minx == INT_MAX ? 0 : minx;
}
};
链表
力扣 203.移除链表元素
设置虚拟头节点,原链表的所有节点就可以按照统一的方式进行移除
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
ListNode *dummy = new ListNode(0);
dummy -> next = head;
ListNode *cur = dummy;
while (cur -> next != NULL) {
if (cur -> next -> val == val) {
ListNode *tmp = cur -> next;
cur -> next = cur -> next -> next;
delete tmp;
}
else {
cur = cur -> next;
}
}
head = dummy -> next;
delete dummy;
return head;
}
};
力扣 707.设计链表
这道题目设计链表的五个接口:获取链表第index个节点的数值 在链表的最前面插入一个节点 在链表的最后面插入一个节点 在链表第index个节点前面插入一个节点 删除链表的第index个节点。覆盖了链表的常见操作
class MyLinkedList {
public:
struct ListNode {
int val;
ListNode *next;
ListNode (int val) : val(val),next(nullptr){}
};
MyLinkedList() {
dummyhead = new ListNode(0);
_size = 0;
}
int get(int index) {
if (index < 0 || index > _size-1) return -1;
ListNode *cur = dummyhead -> next;
while (index--) {
cur = cur -> next;
}
return cur -> val;
}
void addAtHead(int val) {
ListNode *p = new ListNode(val);
p -> next = dummyhead -> next;
dummyhead -> next = p;
_size++;
}
void addAtTail(int val) {
ListNode *p = new ListNode(val);
ListNode *cur = dummyhead;
while (cur -> next != nullptr) {
cur = cur -> next;
}
cur -> next = p;
_size++;
}
void addAtIndex(int index, int val) {
if (index > _size) return;
if (index < 0) index = 0;
ListNode *p = new ListNode(val);
ListNode *cur = dummyhead;
while (index--) {
cur = cur -> next;
}
p -> next = cur -> next ;
cur -> next = p;
_size++;
}
void deleteAtIndex(int index) {
ListNode *cur = dummyhead;
if (index < 0 || index > _size-1) return;
while (index--) {
cur = cur -> next;
}
ListNode *tmp = cur -> next;
cur -> next = cur -> next -> next;
delete tmp;
tmp = nullptr;
_size--;
}
void printLinkedList() {
ListNode *cur = dummyhead;
while (cur -> next != nullptr) {
cout << cur -> next -> val<<" ";
cur = cur -> next;
}
cout << endl;
}
private :
int _size;
ListNode *dummyhead;
};
力扣 206.反转链表
定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。开始反转 cur->next 节点用tmp指针保存。移动pre和cur指针。cur 指针已经指向了null,循环结束,链表也反转完毕。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *p = head;
ListNode *pre = NULL;
ListNode *tmp;
while (p) {
tmp = p -> next;
p -> next = pre;
pre = p;
p = tmp;
}
return pre;
}
};