白板编程常见题目总结
目录
一、二分查找实现 upper_bound、lower_bound
二、排序——快排
三、排序——归并
四、排序——堆排
五、排序——冒泡
六、最大子数组和
七、最大子数组积
七、TopK问题
一、二分查找实现 upper_bound、lower_bound
记住两个函数的含义upper_bound找到大于目标值的第一个位置,lower_bound找到大于等于目标值的第一个位置
int lower_bound(vector<int>&num,int head,int tail,int &val) { int mid=int((head+tail)/2),out=-1; if(head==tail||head+1==tail) { if(num[head]==val) return head; else if(num[tail]==val) return tail; else return -1; } if(num[mid]<val) out=lower_bound(num,mid,tail,val); else out=lower_bound(num,head,mid,val); return out; }
int upper_bound(vector<int>&num,int head,int tail,int &val) { int mid=int((head+tail)/2),out=-1; if(head==tail||head+1==tail) { if(num[head]!=val) return head; else if(num[tail]!=val) return tail; else return -1; } if(num[mid]<=val) out=upper_bound(num,mid,tail,val); else out=upper_bound(num,head,mid,val); return out; }
二、排序——快排
具体思路见 https://www.cnblogs.com/dzzy/p/12241585.html
时间:O(N*logN) ;空间:O(N*logN);不稳定
void quick_sort(vector<int>&num,int start,int end) { int head=start,tail=end,hole=start;//头指针 尾指针 坑 。初始 head=start,tail=end,hole=start int tmp_cmp=num[hole];//取出第一个坑处元素值//坑可能在头处,可能在尾处,初始在头处 while(head<tail)//终止条件 { if(hole==head)//坑在头处,和尾指针比较 { if(tmp_cmp>num[tail])//tail处小,交换坑的位置 { num[hole]=num[tail]; hole=tail; head++; } else//tail处大,移动tail的位置 { tail--; } } else if(hole==tail)//坑在尾处,和头指针比较 { if(tmp_cmp<num[head])//head处大,交换坑的位置 { num[hole]=num[head]; hole=head; tail--; } else//head处小,移动head的位置 { head++; } } } num[head]=tmp_cmp; //此时确认head==tail处正是这一个元素的真实位置,借此二分继续查找 if(start<=head-1) quick_sort(num,start,head-1); if(head+1<=end) quick_sort(num,head+1,end); return; }
三、排序——归并
时间:O(N*logN);空间:O(N);稳定
void merge(vector<int>&num,vector<int>&tmp,int head,int mid,int tail) { int i=head,j=mid+1;//num数组的两个指针 int x=head;//tmp数组指针 while(i<=mid&&j<=tail) { if(num[i]<=num[j]) tmp[x++]=num[i++]; else tmp[x++]=num[j++]; } while(i<=mid) tmp[x++]=num[i++]; while(j<=tail) tmp[x++]=num[j++]; for(i=head;i<=tail;i++) num[i]=tmp[i]; } void Merge_search(vector<int>&num,vector<int>&tmp,int head,int tail) { int mid=int((head+tail)/2); if(head<tail) { Merge_search(num,tmp,head,mid); Merge_search(num,tmp,mid+1,tail); merge(num,tmp,head,mid,tail); } }
四、排序——堆排
时间:O(N*logN);空间O(1);不稳定
const int INF = 0x3f3f3f3f;
void rbulid_heap(vector<int>&num,int start,int end)//重构大根堆 { for(int i=start;i<end;) { int L_n=-INF,R_n=-INF; if(i*2+1<=end) L_n=num[i*2+1]; if(i*2+2<=end) R_n=num[i*2+2]; if(num[i]<L_n&&L_n>=R_n)//根小于左叶子 交换 { int tmp=num[i];//交换 num[i]=num[i*2+1]; num[i*2+1]=tmp; i=i*2+1; } else if(num[i]<R_n&&R_n>L_n)//根小于右叶子 交换 { int tmp=num[i];//交换 num[i]=num[i*2+2]; num[i*2+2]=tmp; i=i*2+2; } else break; } return; } void Heap_sort(vector<int>&num) { int end=num.size()-1; //将指定范围内构建成大根堆 根在0处 注:需要从叶子向上更新才能保证有序 for(int i=end;i>=0;i--) rbulid_heap(num,i,end); for(;end>0;end--)//依次缩小堆的范围 { rbulid_heap(num,0,end); int tmp=num[0];//交换 num[0]=num[end]; num[end]=tmp; } return; }
五、排序——冒泡
时间:O(n^2);空间:O(1);稳定
void papaw(vector<int>&num) { int size=num.size(),tmp; for(int i=0;i<size-1;i++) { int lable=0; for(int j=1;j<size-i;j++) { if(num[j-1]>num[j])//交换 冒泡 { tmp=num[j-1]; num[j-1]=num[j]; num[j]=tmp; lable=1; } } if(lable==0) break; } return; }
六、最大子数组和
主要考虑前面的加和为+对当前有贡献、加和为-对当前有负贡献,因此只需记录最大加和
dp[j]=max{dp[j-1]+num[i],num[i]}
int maxSubArray(vector<int> nums) { int size=nums.size(); if(size==0) return 0; int maxl=nums[0]; for(int i=1;i<=size-1;i++) { nums[i]=max(nums[i-1]+nums[i],nums[i]); maxl=max(maxl,nums[i]); } return maxl; }
七、最大子数组积
不同于加和,当前最大乘积有可能是一个很小的负数×一个负数得到的,要维护一个max一个min获得当前最大成绩
void max_mult(vector<int>&num) { int size=num.size(),MAX_RESULT; vector<int>maxl(size,0); vector<int>minl(size,0); maxl[0]=minl[0]=MAX_RESULT=num[0]; for(int i=1;i<size;i++) { maxl[i]=max(max(maxl[i-1]*num[i],minl[i-1]*num[i]),num[i]); minl[i]=min(min(maxl[i-1]*num[i],minl[i-1]*num[i]),num[i]); MAX_RESULT=max(MAX_RESULT,maxl[i]); } return MAX_RESULT; }
七、TopK问题
TopK问题一般答两种思路,参考
1、一种是基于快排思想的topk二分查找,一轮排序后确定的一个元素位置是真实的位置,这个元素i左面的都比它小、右边的都比它大,如果i==k那么找到了topk的位置;如果i<k那么topk位置一定大于i,用右边部分查找top(k-i)成为一个子问题;如果i>k那么继续在左边寻找topk;
2、最好的思路是堆排序,维护一个小根堆,每次比较当前元素和根的大小,大于根,那么就把根替换成当前元素,重新维护堆的顺序,再次比较。好处,可以处理流式数据,不需要预先把所有数据都存在内存中。
#include<bits/stdc++.h> using namespace std; const int INF = 0x3f3f3f3f; void build_heap(vector<int>&num,int start,int end)//重建堆 维护顺序 { for(int i=start;i<end;) { int L_n=INF,R_n=INF; if(i*2+1<=end) L_n=num[i*2+1]; if(i*2+2<=end) R_n=num[i*2+2]; if(num[i]>L_n&&L_n<R_n)//根大于左叶子 交换 { int tmp=num[i];//交换 num[i]=num[i*2+1]; num[i*2+1]=tmp; i=i*2+1; } else if(num[i]>R_n&&R_n<L_n)//根大于右叶子 交换 { int tmp=num[i];//交换 num[i]=num[i*2+2]; num[i*2+2]=tmp; i=i*2+2; } else break; } } void print_heap(vector<int>&num,int k) { for(int i=0;i<k;i++) cout<<num[i]<<" "; cout<<endl; } int main() { int K,n,tmp; cin>>K>>n;//topK - 数组规模n vector<int>num(n,-INF);//用数组模拟堆 for(int i=0;i<K;i++)//先用k个数据构建堆 cin>>num[i]; for(int i=K-1;i>=0;i--)//初始化堆 build_heap(num,i,K-1); for(int i=K;i<n;i++) { cin>>tmp; if(tmp>num[0]) { num[0]=tmp; build_heap(num,0,K-1); } } print_heap(num,K); return 0; } /* 5 10 8 2 5 9 6 10 12 9 8 13 */