acwing.113特殊排序

特殊排序

题目链接

题目描述

有 N个元素,编号 1,2…N,每一对元素之间的大小关系是确定的,关系具有反对称性,但不具有传递性。

注意:不存在两个元素大小相等的情况。

也就是说,元素的大小关系是 N个点与 N×(N−1)2 条有向边构成的任意有向图。

然而,这是一道交互式试题,这些关系不能一次性得知,你必须通过不超过 10000 次提问来获取信息,每次提问只能了解某两个元素之间的关系。

现在请你把这 N 个元素排成一行,使得每个元素都小于右边与它相邻的元素。

你可以通过我们预设的 bool 函数 compare 来获得两个元素之间的大小关系。

例如,编号为 a 和b 的两个元素,如果元素 a 小于元素b,则 compare(a,b) 返回 true,否则返回 false。

将 N 个元素排好序后,把他们的编号以数组的形式输出,如果答案不唯一,则输出任意一个均可。

数据范围

1≤N≤1000

输入样例

[[0, 1, 0], [0, 0, 0], [1, 1, 0]]

输出样例

[3, 1, 2]

题意解读:

题目的意思是有1n号元素需要排序,每两个元素之间有大小关系,但这个关系并没有传递性,类似于拓扑关系,例如如果a<b,b<c,而a不一定小于c。可以通过调用compare(a,b)函数来判断a是否小于b,但最多允许调用10000次。要求调用10000次以内的compare(a,b)函数将1n号元素排好序,保证每两个相邻元素一定满足左边小于右边

无需处理输入输出,只需要补全一个排序函数,排序函数含有一个参数n,返回值是一个vector,表示排好序后的序列。

    // Forward declaration of compare API.
    // bool compare(int a, int b);
    // return bool means whether a is less than b.
    
    class Solution
    {
    public:
        vector<int> specialSort(int N) 
        {
            
        }
    };

分析:

题目的核心在于这些元素大小关系不具有传递性,要求调用少于10000次比较函数来拍好序列。考虑使用插入排序,首先将1号元素插入一个空vectora中,接下来依次将2~n号元素插入a中,每次插入维护a中元素有序。为了快速找到元素应该插入的位置,可以使用二分查找应该插入的位置。

这里为什么可以使用二分呢?a中元素已经排好序,要将i插入a中,假如x是a中元素,进行一次compare(i,x)比较如果有i>x,那么i一定可以插入到x的后方。

不妨进行模拟证明这个结论,假设x是元素a[idx]。首先i尝试插入到x的后一个位置,就要比较i和a[idx+1],如果i<a[idx+1],那么显然i满足了a[idx]<i<a[idx+1],i可以直接插入a[idx]和a[idx+1]之间。否则有a[idx+1]<i,用i比较下一个元素a[idx+2],如果i<a[idx+2],那么显然i满足了a[idx+1]<i<a[idx+2],i可以直接插入a[idx+1]和a[idx+2]之间。如果任然不满足i<a[idx+2]则继续以此类推用i比较下一个元素直到找到一个大于i的元素。如果找到末尾元素任没有大于i的元素,即a[end]<i那么显然i可以插入最后。所以一旦有i>x,那么i一定可以插入到x的后方。i<x,那么i一定可以插入到x的前方。通过这个性质可以每次二分出一个下标mid,判断i可以插入a[mid]的后方还是前方,不断缩小范围最后确定i可以插入的位置。

代码如下:

    // Forward declaration of compare API.
    // bool compare(int a, int b);
    // return bool means whether a is less than b.
    
    class Solution 
    {
    public:
        vector<int> specialSort(int N) 
        {
            vector<int>a={1};//起初把1号元素加入
            for(int i=2;i<=N;i++)//依次把2~N号元素插入a中
            {
                //二分查找i号元素应该插入的位置
                int l=0,r=a.size()-1;
                while(l<r)
                {
                    int mid=(l+r+1)/2;
                    if(compare(a[mid],i)) l=mid;//i应该插入mid之后
                    else r=mid-1;//i应该插入mid之前
                }
                //二分结束后,i应该插入到r的后面。有一种特殊情况是i应该插入到最前面
                a.push_back(i);//先将i插入a的末尾
                //从后往前依次两两交互,把i换到r的后面
                for(int j=a.size()-2;j>r;j--) swap(a[j],a[j+1]);
                //处理特殊情况,i应该插入最前面的情况,此时r一定为0
                if(compare(a[r+1],a[r])) swap(a[r],a[r+1]);
            }
            return a;
        }
    };
  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值