刷题笔记(3)

快排

模板如下

void quick_sort(int q[],int l,int r){
	if(l>=r)	return;
	int x=q[(l+r)/2],i=l-1,j=r+1;
	while(i<=j){
		do i++;while(q[i]<x);
		do j--;while(x<q[j]);
		if(i<j) swap(q[i],q[j]);
	}
	quick_sort(q,l,j);
	quick_sort(q,j+1,r);
}

值得注意的是边界问题。为什么**(l+r)/2** 可不可以是 (l+r+1)/2
答案是不可以。例如数组为{1 , 2} 则会陷入死循环。
或者说为了避免

quick_sort(q,l,j) 陷入死循环。

复杂度是nlog(n),其实可以看做树,每一次调用都会确定一个元素的具体位置。一般情形树高为log(n),每一层复杂度为n(遍历一遍数组)。极端情形退化为链表,高度为n。(采用随机化取pivot避免)

第K大

快排的衍生物。

将模板的:
	quick_sort(q,l,j);
	quick_sort(q,j+1,r);
改成:
	if(k-1<=j)	quick_sort(q,l,j);//第k大的坐标是k-1
	else 	quick_sort(q,j+1,r);
即可。
顺便得修改一下返回值,返回第k个数值

归并排序

void merge(int q[],int l,int r){
    if(r<=l)    return;
    int mid=(l+r)/2;
    merge(q,l,mid);
    merge(q,mid+1,r);
    int i=l,j=mid+1,k=l;
    while(i<=mid&&j<=r)
        if(q[i]<q[j])   tmp[k++]=q[i++];
        else tmp[k++]=q[j++];
    while(i<=mid)   tmp[k++]=q[i++];
    while(j<=r)     tmp[k++]=q[j++];
    for(int i=l;i<=r;i++,l++)   q[i]=tmp[l];
    //如果最后一行用 while() q[l]=tmp[l++]会错的很离谱,要写成
    //q[l++]=tmp[l]
}

主要就是分治,加双指针排序。显然要额外的空间。

逆序对数

把模板的
	while(i<=mid&&j<=r)
        if(q[i]<q[j])   tmp[k++]=q[i++];
        else tmp[k++]=q[j++];
改成:
	while(i<=mid&&j<=r)
        if(q[i]<=q[j])   tmp[k++]=q[i++];
        else tmp[k++]=q[j++],ret+=(mid-i+1);
顺便把返回值改成long long

这里的逆序对数怎么理解?
如果 q[i]<=q[j] , 显然是顺序的。
如果 q[i]>q[j] , 是逆序的,那么有多少对呢?
显然从当前的 q[i]q[mid] 为止的所有数都和 q[j] 构成逆序对。
原因:(之前的递归调用,保证了两个数组都是有序的)

想起来高代滕**老师讲的一句话:数逆序对,要么数后面,要么数前面,要不然很容易乱,确实如此。这里我们数的是后面数组,即对每一个q[j],计算其逆序对数。
顺便说一句,滕老师对学生真的好。

二分

二分模板:

bool check(){}//这里check的是一个性质,该性质是在一个区间内。
下面的整数二分,其实有两种模板,关键还是向上取整还是向下取整

while(l<r){
	int mid = (l+r)/2;
	if(check()) r=mid;
	else l=mid+1;
}
OR
while(l<r){
	int mid = (l+r+1)/2;
	if(check()) l=mid;
	else r=mid-1;
}
浮点数二分与整数二分类似,多一个esp,且不用考虑向上或向下取整。

数的范围

给定一个按照升序排列的长度为 n 的整数数组,以及 q 个查询。
对于每个查询,返回一个元素 k 的起始位置和终止位置(位置从 0 开始计数)。
如果数组中不存在该元素,则返回 -1 -1

关键点:两个位置所满足的性质是什么?第一个满足大于等于的数,和最后一个满足小于等于的数。

		scanf("%d",&k);
        int l=0,r=n-1,mid;
        while(l<r){
            mid=(l+r)/2;
            if(arr[mid]>=k) r=mid;//这里的是第一个大于等于
            else    l=mid+1;
        }
        if(arr[l]==k)   printf("%d ",l);//可能不存在,下面同理。
        else    printf("%d ",-1);
------------------------------------------找到起始位置
        l=0,r=n-1;
        while(l<r){
            mid=(l+r+1)/2;
            if(arr[mid]<=k) l=mid;//最后一个小于等于
            else    r=mid-1;
        }
        if(arr[l]==k)   printf("%d\n",l);
        else     printf("%d\n",-1);
-----------------------------------------找到结束位置

为什么 l=mid 就需要向上取整呢?
因为每一次二分都是缩小一半范围
最后的只剩两个的边界条件
如:arr[2] = {1,2} mid=0,l=0,r=1会无限循环。

三次方根

需要判断正负号,以及是否在[-1,1]之内(影响到r的初始取值)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值