(原创,步进分析,24ms)PAT乙级1045 快速排序

2019-02-01  本文已影响0人  仰天蓬蒿人

题目

1045 快速排序 (25 分)
著名的快速排序算法里有一个经典的划分过程:我们通常采用某种方法取一个元素作为主元,通过交换,把比主元小的元素放到它的左边,比主元大的元素放到它的右边。 给定划分后的 N 个互不相同的正整数的排列,请问有多少个元素可能是划分前选取的主元?
例如给定 N = 5, 排列是1、3、2、4、5。则:
1 的左边没有元素,右边的元素都比它大,所以它可能是主元;
尽管 3 的左边元素都比它小,但其右边的 2 比它小,所以它不能是主元;
尽管 2 的右边元素都比它大,但其左边的 3 比它大,所以它不能是主元;
类似原因,4 和 5 都可能是主元。
因此,有 3 个元素可能是主元。
输入格式:
输入在第 1 行中给出一个正整数 N(≤10的5次方);
第 2 行是空格分隔的 N 个不同的正整数,每个数不超过 10的9次方
输出格式:
在第 1 行中输出有可能是主元的元素个数;在第 2 行中按递增顺序输出这些元素,其间以 1 个空格分隔,行首尾不得有多余空格。
输入样例:
5
1 3 2 4 5
输出样例:
3
1 4 5

分析

这条题,按照我做题原则,先好好思考,整理逻辑,实在不行再参考别人。
一开始感觉应该挺简单,首先排除暴力分析,用步进分析法+双指针应该可以一次循环搞掂。
可是做着做着,我用一些测试用例通不过,发现这个逻辑有漏洞,这一分析,发现更多种情况要填要弥补,my god
对于我来说,思考太久一直是我弱项,可能没有写草稿的习惯,后来我尝试通过用例来弥补各种情况的逻辑编写

(请大家不吝赐教一些快速思路。)

最后也发现这样写完后也顺利一次通过。
然后再去看看别人写的,除了我们最后输出的思路一样,但在处理上他们基本上用STL封装好的排序sort,或者结合reverse。我才知道我们思考的区别,因为根据快速排序的主元特性,我一开始觉得没必要去排序或者再增加空间再来筛选,它已经就是按照原样进来。

主题思路

辅助空间:nums保存主元的数组,pre指针保存前一个主元,cur指针当前接收的值,minValue遇过的历史最小值,maxValue遇过的历史最大值
过程:(就是逻辑的暴解)
1.循环每一个进来的值
1.1判断 pre是否小于cur
当前值是否大于上一个主元的值,是就进入与历史最大值或最小值比较的判断
1.2如果pre==cur
与历史最大值,历史最小值比较

这就是我写的其中一些用例分析思路


一种情况
1 3 5 1000  //pre=2 cur =3,min =1,max =1000,只需要变化pre = cur,cur++
1 3 5 1000 7 //pre =3,cur =4,min =1 ,max=1000,明显nums[cur]<nums[pre],这两个不能要,MULength-=2,最后pre要退却一位即pre--,cur=pre+1;
1 3 5 1000(作废) 7(作废) 9 //pre =2 ,cur = 3,虽然符合nums[cur]>nums[pre],但历史中出现过最大值1000,你的cur的值还是不能要,继续cur = pre+1,pre不变
1 3 5 1000(作废) 7(作废) 9(作废) 2. //pre =2 ,cur = 3,明显nums[cur]>nums[pre],这两个不能要,MULength-=2,最后pre要退却一位即pre--,cur=pre+1;
最后结果是1 3 5(作废) 1000(作废) 7(作废) 9(作废) 2(作废),但是这个1和3结果明显不能接受,明显3不能要,需要改写,尝试用局部循环判断nums[cur]<nums[pre],直到把pre退却到适合的位置

另一种情况
2 3 5 1000  //pre=2 cur =3,min =2,max =1000,只需要变化pre = cur,cur++
2 3 5 1000 1//pre =3,cur =4,min =2 ,max=1000,明显nums[cur]<nums[pre],再发现nums[cur]比历史最小值还要小,前面所有作废,这里就要pre与cur重置了,cur =pre=0,nums[0]=nums[cur],更新一下MULength-cur
2(作废) 3(作废) 5(作废) 1000(作废) 1(作废)

另一种情况
2 1 //pre =0,cur =1,min=2,max=2;明显nums[cur]<nums[pre],这两个不能要,MULength-=2,最后pre要退却一位即pre--, 若是pre退无可退,也就是负值,设置pre=cur = 0,否则cur=pre+1;

代码

#include <stdio.h>
#define MAX_NUM 100000
#define MAX_INT 0x7fffffff
unsigned int GetInputToInt();
int main()
{
    //nums用来放入数据,N是输入个数, MULength是主元个数长度 
    int nums[MAX_NUM]={0},i=0,N,MULength =0;
    //处理输入
    //例子:2 4 3 1 5 ,只有5才是主元 
    //例子:4 5 6 2 1 8 7 9 3 10   只有10才是主元   用于这个测试点,发现我第一个版本错了,漏了判决 
    MULength = N = GetInputToInt();
    
    //处理与控制:采用双指针控制
    int prePos=0,curPos=0,curMinValue=MAX_INT,curMaxValue=0;
    for(;i<N;i++)
    {
        nums[curPos] = GetInputToInt();
        //(尝试步进分析,一旦遇到前者大于后者,马上重置prePos,剔除该两个,因为都不可能是主元了,
        //1还保留一直遇到过的最小值curMinValue,和遇到过的最大值curMaxValue) 
        if(prePos<curPos)
        {
            //1.1若当前值大不过上一个值 
            if(nums[prePos]>nums[curPos])
            {
                //(作废情况)
                //1.3看看是否遇上了比当前最小值还要小的当前值 
                if(curMinValue>nums[curPos])
                {
                    //1.3.1 要是发现历史最小值都比不过当前值小,OK,前面全部作废 
                    curMinValue = nums[curPos];
                    //更新MULength,这里分开写,让可读性好点 
                    MULength = MULength - curPos;//前面作废,有多少个,curPos个(curPos是下标,curPos-1就是个数) 
                    MULength--; //再减去当前第curPos那一个 
                    prePos = curPos = 0; 
                } 
                else
                {
                    MULength-=2;//当前这两个肯定不要了(pre与cur)
                    --prePos;
                    //1.3.2 要是发现前面最小值依然坚挺能接受考验。 需要把prePos退却到刚好nums[curPos]>nums[pre]的位置上
                    while(prePos>=0&&nums[curPos]<nums[prePos])
                    {
                        prePos--;
                        MULength--;
                    }
                    if(prePos<0)
                    //什么情况下会来到这里,应该没有,因为有1.3.1过滤了,最小值是来不了  例如,6(作废) 1(作废) 5(作废) 3 ,但pre=0.cur=0,明显,这个例子来不了 ,希望能找到例外 
                        prePos = curPos = 0; 
                    else
                    //来到这里,就是已经找到刚好 nums[curPos]>nums[pre]的位置上,重置curPos 
                        curPos = prePos+1; 
                }   
            }
            //1.2要是比得过,接下来看情况看下是否更新一下最大值 
            else
            {
                //(正常情况) 看看是否遇上了比历史最大值还要大的当前值
                if(curMaxValue<nums[curPos])
                {
                    curMaxValue = nums[curPos];//保留 
                    prePos = curPos;
                    curPos++; 
                }
                //若然当前值比不上历史最大值 
                else
                {//(作废情况)
                //这种情况就好像1,1000,2,3,4,此时cur=4,pre=3,虽然符合pre<pos,但是前面出现过巨无霸1000,在巨无霸后面到当前值都得作废,除非出现过比巨无霸还要猛的。
                    MULength = MULength -1;//减一就是当前cur的值不要了 
                    curPos = prePos+1; //更新下一次应该要填的位置 
                    //prePos = curPos = 0; 
                }    
            }
            
        }
        //2. 一般进入这里的情况都是,初始化,或者是前面都失效的情况下。
        else if(prePos==curPos)//
        {
            //看情况更新最大值与最小值 
            //2.1 
            if(curMaxValue<nums[curPos])
                curMaxValue = nums[curPos];
            else
            {//若是连历史最大值都大不上,也就是当前值作废  如 20(作废) 10(作废) 15 ,历史最小值是10 
                MULength--;
                prePos = curPos = 0;
                continue;
            }
            
            //2.2
            if(curMinValue>nums[curPos])
                //遇上了比历史最小值还小的值,更新 。如 2(作废) 3(作废) 1 ,历史最小值是2 
                curMinValue = nums[curPos]; 
            else
            {//若是连历史最小值都小不过. 如 20(作废) 10(作废) 21 ,历史最小值是10;如果是  20(作废) 10(作废) 15 ,这个有2.1过滤,不会来到这里 
                //不用操作 , 
            }
            
            prePos = curPos++;
        }
    }
    //2. 输出
    printf("%d\n",MULength);
    for(i=0;i<MULength-1;i++)
        printf("%d ",nums[i]);
    if(i==MULength-1)
        printf("%d",nums[i]);
    putchar('\n');
    return 0; 
}

unsigned int GetInputToInt()
{
    char c;
    unsigned int sum =0;
    while((c=getchar())!=EOF&&c!=' '&&c!='\n')
        sum = sum*10+c-'0';
    return sum;
}
上一篇下一篇

猜你喜欢

热点阅读