two pointers
快速排序
问题:将一个无序数组排序,要求时间复杂度优于
O
(
n
2
)
O(n^2)
O(n2)
思路:先定位主元位置,主元左侧元素全部小于主元,右侧元素全部大于主元
//实质上是递归,只不过要先找到主元,根据它把数组分成两部分
int Partition(int A[], int left, int right){
int temp=A[left];
while(left<right)//left,right两步走
{
while(left<right && A[right]>temp)--right;//注意每次还是要判断left<right,因为内循环里left,right会改变
A[left]=A[right];
while(left<right && A[left]<=temp)++left;
A[right]=A[left];
}
A[left]=temp;
return left;
}
void quickSort(int A[], int left, int right){
if(left<right){
int pos=Partition(A,left,right);
quickSort(A,left,pos-1);//注意是pos-1,pos已经固定好位置了
quickSort(A,pos+1,right);
}
}
随机选择算法
问题一:从一个无序数组中求出第K大的数,要求对任何输入都达到时间复杂度O(n)
思路:因为时间复杂度O(n)<O(nlogn),所以先排序再取出第K的元素不可行。理解快速排序后,随机选择算法原理类似,也需要先定位主元的位置p;这时主元已经是它左侧元素中的第M =(p-left+1)大,如果我们要找的是第K大,而K<M,则只需要往左递归,找A[left,…,(p-1)]中的第K大;如果K>M,则只需要往右递归,找A[(p+1),…,right]中的第(K-M)大
int randSelect(int A[], int left, int right, int K){
int(left==right)return A[left];
int p=Partition(A,left,right);//这一步如果使用随机数而不是A[left]作为初始temp,可以加速,因此可以采用下面说的randPartition()
int M=p-left+1;
if(K<M) return randSelect(A,left,p-1,K);
else if(K=M)return A[P];
else return randSelect(A,p+1,right,K-M);
}
问题二:给定一个由整数组成的集合,元素各不相同,把它分成两个子集合,要求两个子集合的并为原交集,交为空集,同时两个集合的元素个数
∣
n
1
−
n
2
∣
|n_1-n_2|
∣n1−n2∣尽可能小的前提下,使它们各自的元素之和
∣
S
1
−
S
2
∣
|S_1-S_2|
∣S1−S2∣尽可能大,求这个
∣
S
1
−
S
2
∣
|S_1-S_2|
∣S1−S2∣
思路:如果时间复杂度没有特别要求,一个较简便的方法是先对数组排序,取前、后半部分分别为两个子集,这样
∣
S
1
−
S
2
∣
|S_1-S_2|
∣S1−S2∣即最大。更优的做法是使用随机选择算法,这个问题实际上是求原集合中元素的第
n
2
\frac{n}{2}
2n大,其中一个子集元素全部小于该元素,另一个子集元素全部大于该元素,而两个分得集合内部顺序不作要求
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <algorithm>
using namespace std;
const int maxn=100010;
int A[maxn],n;
int randPartition(int A[], int left, int right){
//int p=(round(1.0*rand()/RAND_MAX*(right-left)+left));
int p=rand()%(right-left+1)+left;//[left,right]
swap(A[p],A[left]);//algorithm头文件
//以下是原Partition()
int temp=A[left];
while(left<right)//left,right两步走
{
while(left<right && A[right]>temp)--right;//注意每次还是要判断left<right,因为内循环里left,right会改变
A[left]=A[right];
while(left<right && A[left]<=temp)++left;
A[right]=A[left];
}
A[left]=temp;
return left;
}
//现在的randSelect()不需要返回值,因为最终是计算$|S_1-S_2|$
void randSelect(int A[], int left, int right, int K){
int(left==right)return A[left];
int p=randPartition(A,left,right);
int M=p-left+1;
if(K<M) randSelect(A,left,p-1,K);
else if(K=M)return;
else randSelect(A,p+1,right,K-M);
}
int main(){
srand((unsigned)time(NULL));//很重要,初始化随机数种子
int sum=0,sum1=0;
scanf("%d",&n);
for(int i=0;i<n;++i){
scanf("%d",&A[i]);
sum+=A[i];//为了后半部分直接减
}
randSelect(A,0,n-1,n/2);
for(int i=0;i<n/2;++i){
sum1+=A[i];
}
printf("%d\n",(sum-sum1)-sum1);
return 0;
}
以前踩过的低级坑:scanf每次读入一个%d变量,用while控制读入多个;或者用几个连在一起写的%d一次性读入多个变量,键盘输入的时候都是用空格隔开就好(如果变量类型不是%c就不会接受空格,会自动跳过);重新写scanf读入的变量,从键盘输入的时候用回车键分开。
其他的随机数的范围通式
产生一定范围随机数的通用表示公式是:
要取得[0,n) 就是rand()%n 表示 从0到n-1的数
要取得[a,b)的随机整数,使用(rand() % (b-a))+ a;
要取得[a,b]的随机整数,使用(rand() %(b-a+1))+ a;
要取得(a,b]的随机整数,使用(rand() % (b-a))+ a + 1;
通用公式:a +rand() % n;其中的a是起始值,n是整数的范围
要取得a到b之间的随机整数,另一种表示:a + (int)b * rand() / (RAND_MAX + 1)
要取得0~1之间的浮点数,可以使用rand() / double(RAND_MAX)
参考
c++随机数产生
算法笔记-胡凡