1045. 快速排序(25)

本文介绍了一种算法,用于确定快速排序过程中可能作为主元的元素。通过对比已排序和未排序序列,找出符合条件的主元,并提供了一个C语言实现示例。

著名的快速排序算法里有一个经典的划分过程:我们通常采用某种方法取一个元素作为主元,通过交换,把比主元小的元素放到它的左边,比主元大的元素放到它的右边。 给定划分后的N个互不相同的正整数的排列,请问有多少个元素可能是划分前选取的主元?

例如给定N = 5, 排列是1、3、2、4、5。则:

1的左边没有元素,右边的元素都比它大,所以它可能是主元;
尽管3的左边元素都比它小,但是它右边的2它小,所以它不能是主元;
尽管2的右边元素都比它大,但其左边的3比它大,所以它不能是主元;
类似原因,4和5都可能是主元。

因此,有3个元素可能是主元。

输入格式:

输入在第1行中给出一个正整数N(<= 105); 第2行是空格分隔的N个不同的正整数,每个数不超过109。

输出格式:

在第1行中输出有可能是主元的元素个数;在第2行中按递增顺序输出这些元素,其间以1个空格分隔,行末不得有多余空格。
输入样例:

5
1 3 2 4 5

输出样例:

3
1 4 5

思路:将排好序的数列与未排序列进行比较,相同位置元素相等且该元素为从左至右的最大元素时,为主元

#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#define N 100000
int comp(const void*a,const void*b);
int main()
{
    int n,*a,*b,max=-1,i;
    int ret[N],k=0;
    scanf("%d",&n);
    a=(int *)malloc(n*sizeof(int));
    b=(int *)malloc(n*sizeof(int));
    for(i=0;i<n;i++)
    {
        scanf("%d",&a[i]);
        b[i]=a[i];
    }
    qsort(b,n,sizeof(int),comp);
    for(i=0;i<n;i++)
    {
        if(max<a[i])max=a[i];
        if(a[i]==b[i]&&max==a[i])ret[k++]=a[i];
    }
    printf("%d\n",k);
    for(i=0;i<k;i++)
    {
        printf("%d",ret[i]);
        if(i<k-1)printf(" ");
    }
    printf("\n");
}
int comp(const void*a,const void*b)
{
    return *(int *)a-*(int *)b;
}
1045 快速排序 分数 25 作者 CAO, Peng 单位 Google 著名的快速排序算法里有一个经典的划分过程:我们通常采用某种方法取一个元素作为主元,通过交换,把比主元小的元素放到它的左边,比主元大的元素放到它的右边。 给定划分后的 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 对于这道题,我的想法是,直接对该数组排序,若对应位置上的元素不变则判断它是题目所需输出的元素,加入vector中,最后对vector进行sort,按格式输出。这个逻辑是否有误 #define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<cstdio> #include<algorithm> #include <vector> using namespace std; #define MAX 100010 int main() { int N; scanf("%d", &N); int arr[MAX], tmp[MAX]; for (int i = 0; i < N; i++) { scanf("%d", &arr[i]); tmp[i] = arr[i]; } vector<int>v1; sort(tmp, tmp + N); int count = 0; for (int i = 0; i < N; i++) { if (tmp[i] == arr[i]) { count++; v1.push_back(tmp[i]); } } printf("%d\n", count); int print_count = 0; sort(v1.begin(), v1.end()); for (auto elem : v1) { if (print_count == 0) printf("%d", elem); else { printf(" %d", elem); } print_count++; } printf("\n"); return 0; }
最新发布
03-08
<think>嗯,我现在得仔细看看这个问题。题目是关于快速排序的主元判断。用户给出的思路是先将数组排序,然后比较排序前后同一位置的元素是否相同,如果是,就认为这个元素可能是主元。然后统计这些元素的数量并按顺序输出。但用户不确定这个逻辑是否正确,需要我来验证。 首先,我得回忆快速排序的主元性质。主元在划分后的位置应该是它在整个数组中的正确位置,也就是说,主元左边的元素都比它小,右边的都比它大。因此,主元的位置在排序后是确定的,不会改变。所以,如果排序后的元素与原数组同一位置的元素相同,那么这个元素可能满足主元的条件。这一步逻辑是对的,但可能存在例外吗? 比如,原数组中的某个元素在排序后的位置和原来一样,但是它的左边可能存在比它大的元素,或者右边存在比它小的元素。这时候,虽然位置相同,但它并不能作为主元。例如,考虑数组3、2、1。排序后是1、2、3。原数组中的2在排序后的位置是中间,但原数组中左边是3,比它大,所以2不可能是主元。这时候用户的算法就会错误地把2算进去。 这说明用户的思路有缺陷。因为虽然排序后的位置是正确的,但原数组中该位置的元素可能不满足左边的所有元素都比它小,或者右边的所有元素都比它大。这时候,用户的算法会错误地计入这些元素。 那正确的做法应该是怎样的呢?正确的条件应该是,该元素是当前为止的最大值,并且该元素的位置等于排序后的位置。因为,如果该元素是当前遍历到i的位置时的最大值,那么说明它左边的所有元素都比它小;而如果它在排序后的位置和当前位置相同,那么说明右边的元素都比它大。这时候,该元素才能作为主元。 例如,原数组1、3、2、4、5。排序后的数组是1、2、3、4、5。当i=0时,原数组的1是当前最大值,且等于排序后的位置,所以是主元。i=1,原数组3,排序后位置是2,但此时当前最大值是3,但因为位置不符,所以不选。i=2,原数组2,排序后的位置是1,但当前最大值还是3,所以不符合。i=3时,原数组4,排序后的位置是3,此时当前最大值是4,满足条件。i=4,原数组5,排序后的位置是4,当前最大值是5,满足条件。所以总共有三个主元,正确。 所以正确的条件应该是:当前元素等于排序后的对应位置元素,并且当前元素是原数组中到当前位置为止的最大值。这样就能保证左边的元素都小于它,右边的元素都大于它,因为排序后的位置已经正确,右边的元素在排序后的数组中都是更大的。 那用户的代码仅仅比较了排序后的位置是否相同,而没有检查该元素是否是原数组中的前i个元素的最大值。比如前面的例子中的3、2、1数组,排序后是1、2、3。原数组中的元素3在位置0,排序后的位置0是1,所以不会匹配。但假设另一个例子,比如原数组是2、1、3。排序后是1、2、3。原数组中的2在位置1,排序后的位置是1。此时,原数组中到位置1的最大值是2,此时是否满足条件?是的,因为左边是1,右边是3。所以这个元素是主元。但用户的算法在这种情况下是正确的。 那为什么前面的反例中的2会被用户的算法错误选中?比如原数组3、2、1,排序后的数组是1、2、3。原数组中元素3的位置是0,排序后的位置0是1,不匹配,所以不会被选中。原数组中的元素2在位置1,排序后的位置是1的元素是2?不,原数组排序后的数组应该是1、2、3。原数组是3、2、1,排序后的数组是1、2、3。所以原数组中的元素在位置0是3,排序后的位置0是1,不匹配。位置1的元素是2,排序后的位置1是2。这时候,原数组的位置1的元素是2,排序后的位置1的元素是2,所以会被用户算法选中。但此时,原数组中的2的左边元素是3,比它大,所以它不可能是主元。这说明用户的算法在这种情况下会错误地计入这个元素。 这说明用户的思路存在漏洞。因此,正确的做法应该是:只有当元素在排序后的位置与原位置相同,并且该元素是原数组中到该位置为止的最大值,同时该最大值等于排序后的该位置元素的最大值。或者说,正确的条件是两个条件都要满足:排序后的位置与原位置相同,并且该元素是原数组中前i个元素的最大值,并且该元素等于排序后的数组中的对应位置的值。 那么,正确的算法应该是怎样的? 正确的条件: 对于某个元素arr[i],满足: 1. arr[i]等于排序后的数组sorted_arr[i]。 2. arr[i]是原数组前i+1个元素中的最大值(因为数组下标从0开始,所以到i的位置共有i+1个元素)。 这样就能保证,该元素左边的所有元素都比它小(因为它在原数组中到当前位置i时是最大的),并且右边的元素都比它大(因为排序后的数组中该位置的元素已经是正确的,所以右边的元素都比它大)。 例如,前面的反例数组3、2、1: 排序后的数组是1、2、3。 对于i=0,原数组元素是3,排序后的元素是1,不满足条件1,排除。 i=1,原数组元素是2,排序后的元素是2。条件1满足。此时原数组前i+1=2个元素的最大值是3,而原数组元素是2,所以条件2不满足。因此,不会被选中。 i=2,原数组元素是1,排序后的元素是3,条件1不满足。 所以,正确的结果应该是没有主元?或者有没有主元的情况?比如,原数组3、2、1,主元只能是3吗?因为当快速排序第一次选取3作为主元,此时正确的位置应该在最后。所以这个数组没有被正确划分过。所以原题中的情况给出的数组是划分后的结果。所以在该数组中,可能的主元是那些满足条件的元素。那么在这个例子中,主元是否存在? 比如,原数组3、2、1。假设这是划分后的数组,那么主元只能是1吗?或者没有主元? 或者,可能主元必须满足左边的元素都小,右边的都大。比如,在这个数组中,3的位置是0,左边没有元素,右边都比它小,所以不可能是主元。元素2的位置1,左边3比它大,所以不能。元素1的位置2,右边没有元素,左边都比它大,所以不能。所以该数组中没有主元。而用户的算法在这种情况下,当原数组是3、2、1时,用户算法中排序后的数组是1、2、3。原数组的每个元素都不等于排序后的对应位置元素,所以结果正确,count为0。 那之前的反例中的数组2、1、3的情况。排序后的数组是1、2、3。原数组中的元素2位于位置1,等于排序后的元素,同时原数组的前两个元素的最大值是2。所以条件满足,会被选中。此时,该元素是否是主元? 原数组是2、1、3。主元是2和3吗? 当主元是2时,左边是2,右边是1、3。但是划分后的数组应该是左边都是小于等于主元,右边都是大于主元。可能原数组已经是划分后的结果。比如,假设主元是2,那么原数组中左边的元素都小于等于主元,右边的大于。原数组中的主元位置可能不是最终的排序位置?或者,这里可能有些混淆。 或者题目中的“划分后的排列”指的是经过主元划分后的结果,此时主元的位置已经确定,左边的元素都比它小,右边的都比它大。因此,主元的位置在排序后的位置是正确的,并且此时主元左边的元素的最大值不超过主元,右边的元素的最小值不低于主元。这可能意味着,主元的位置在排序后的数组中是正确的,且原数组中的该元素等于排序后的该位置元素。同时,主元在原数组中左边的所有元素都小于它,右边的所有元素都大于它。所以,当且仅当某个元素等于排序后的数组对应位置元素,并且在原数组中左边的所有元素都比它小,右边的所有元素都比它大。 那如何高效地判断这两个条件? 因为,左边的所有元素都比它小,等价于该元素是左边所有元素的最大值,并且该最大值等于该元素本身。因为如果左边存在一个比它大的元素,那么该元素不可能满足左边所有元素都比它小。所以,该元素必须是原数组中到当前位置为止的最大值。同时,右边的元素必须都比它大。而排序后的数组中该元素的位置正确,意味着右边的所有元素都比它大。所以,满足这两个条件的元素就是主元可能的候选。 因此,正确的条件是两个: 1. 该元素在排序后的数组中的位置与原数组中的位置相同。 2. 该元素是原数组中从0到i位置的最大值。 这样,当这两个条件满足时,左边的元素都比它小(因为到i的最大值是它自己),右边的元素在排序后的数组中都比它大,所以原数组中右边的元素也必须比它大(否则排序后的位置不会正确)。 所以用户的思路漏掉了第二个条件,即当前元素是否是原数组中到该位置的最大值。例如,当原数组是3、2、1的时候,排序后的数组是1、2、3。原数组中的元素2在排序后的位置是1,原数组中的元素在位置1是2,等于排序后的对应位置元素。此时,条件1满足。但原数组中到位置1的最大值是3,而当前元素是2,所以条件2不满足,不会被计入。 这说明用户的代码存在错误,只有当两个条件都满足时才能计入主元。因此,用户提供的代码的逻辑不正确,导致错误。 因此,正确的做法是:在遍历数组时,对每个元素,检查是否满足两个条件: a. 排序后的位置与原位置相同。 b. 该元素是原数组到当前位置的最大值。 这两个条件都满足时,该元素才是主元候选。 因此,用户的代码需要修改,加入第二个条件的判断。例如,在循环中维护一个当前最大值,如果当前元素等于排序后的数组的对应位置,并且等于当前最大值,那么才计入结果。 例如,修改后的代码如下: 在循环中维护一个max_val变量,初始化为arr[0]。对于每个i从0到N-1: 如果arr[i] > max_val,则更新max_val为arr[i]。 然后,如果arr[i] == sorted_arr[i] 并且 arr[i] == max_val,则计入结果。 这样,就能满足两个条件。 例如,原数组1、3、2、4、5。排序后的数组是1、2、3、4、5。在遍历时: i=0: arr[i]=1,max_val=1。sorted是1。满足条件,计入。 i=1: arr[i]=3,max_val=3。sorted是2,不相等,不满足条件1。不计入。 i=2: arr[i]=2,max_val仍然是3。sorted是3,此时arr[i]是2,不等于sorted的3,所以不满足条件1。 i=3: arr[i]=4,max_val=4。sorted是4,相等,计入。 i=4: arr[i]=5,max_val=5。sorted是5,计入。 总共有3个元素,正确。 另一个例子,原数组3、2、1: 排序后的数组是1、2、3。 i=0: arr[0]=3,max_val=3。sorted[0]=1,不等。不计入。 i=1: arr[1]=2,max_val仍为3。sorted[1]=2。此时arr[i]等于sorted[i],但max_val是3不等于arr[i]的2,所以不满足条件。不计入。 i=2: arr[i]=1,sorted[2]=3。不等,不计入。所以总共有0个主元,正确。 另一个例子,原数组是2、1、3: 排序后的数组是1、2、3. i=0: arr[0]=2,sorted是1。不等,不计入。 i=1: arr[1]=1,sorted是2。不等,不计入。 i=2: arr[2]=3,max_val是3。sorted是3。计入。 所以主元是3。是否正确? 划分后的数组是2、1、3。此时主元是3,因为它的左边都比它小,右边没有元素。所以是正确的。所以用户原来的算法在i=1时,原数组元素是1,排序后的元素是2,所以不会被计入。i=2时,元素3等于排序后的元素,所以会被计入。所以用户原来的算法在这种情况下会得到1个主元,是正确的。 但此时,原数组中的元素3满足条件,是正确的。所以用户原来的算法在这种情况下是正确的。但之前的例子中,原数组是2、1、3,用户原来的算法在i=1的时候,原数组中的元素是1,排序后的元素是2,所以不会被计入。i=2的时候,元素3等于排序后的元素,所以会被计入。此时,max_val在i=2的时候是3,满足条件,所以是正确的。用户原来的算法是否漏掉这个条件? 用户原来的算法是否只检查了排序后的元素是否相等? 是的。用户原来的算法中的条件只有排序后的元素是否相等,所以在i=2的时候,会被计入。但是此时,原数组中的元素3是否是原数组中的前3个元素的最大值?是的。所以用户原来的算法在这种情况下是正确的。 那这说明,在用户原来的算法中,可能并没有错误?或者,是否只有当排序后的元素与原数组元素相等时,该元素必然是原数组中的到该位置的最大值? 例如,假设有一个元素在排序后的位置与原位置相同,并且该元素是原数组中到该位置的最大值。那么用户的算法是否能够正确计入该元素? 是的。因为,当排序后的数组中该元素的位置正确,说明原数组中该元素的值等于排序后的该位置的值,也就是该元素已经处于其最终的正确位置。此时,该元素左边的所有元素都小于它,右边的所有元素都大于它。而原数组中的该元素是否满足左边的所有元素都小于它? 不一定。比如,原数组是5、3、4、7、6。排序后的数组是3、4、5、6、7。原数组中的5在排序后的位置是2。此时,原数组中该位置的元素是5,排序后的位置也是5。所以用户算法会认为这是一个主元候选。但原数组中左边的元素是5、3、4,该位置是2(元素5),那么左边的元素3和4都比5小吗?是的。右边的元素是7、6,比5大吗?是的。所以5是主元候选。此时,该元素在排序后的位置正确,并且是原数组中前三个元素中的最大值(5是原数组中的前三个元素5、3、4的最大值)。所以用户算法会正确计入这个元素。因此,用户原来的算法在这种情况下是正确的。 那为什么前面的反例中的情况,用户的算法会错误? 比如,原数组是3、2、1的情况。排序后的数组是1、2、3。原数组中的元素在i=0的位置是3,排序后的i=0是1,不等,所以不选。i=1的位置是2,排序后的i=1是2。此时,原数组的i=1的元素是2,排序后的元素是2。用户算法会认为这是一个候选。但此时,原数组中前i+1=2个元素的最大值是3,而该元素是2,所以不符合条件,所以用户算法错误地计入了这个元素。这说明用户的算法在这种情况下会得到错误的结果。 这说明,用户的算法中的逻辑存在漏洞,即仅检查排序后的元素是否相等是不够的,必须同时检查该元素是否是到当前位置的最大值。 所以,正确的做法是,在遍历数组时,维护当前最大值,并同时检查排序后的元素是否相等。如果两者都满足,则该元素是主元候选。 因此,用户的代码需要修改,在判断条件中加入最大值判断。 所以,用户的思路存在错误,必须进行修正。 现在,回到用户提供的代码。用户代码中对每个元素i,判断排序后的tmp[i]是否等于arr[i],如果是的话就加入结果。此时,用户没有检查该元素是否是原数组中到当前位置的最大值。因此,在类似3、2、1这样的例子中,用户的代码会错误地计入元素2。 因此,用户的代码逻辑是错误的,必须修正。 现在,总结正确的解决步骤: 1. 将原数组排序,得到sorted_arr。 2. 遍历原数组中的每个元素arr[i],同时维护一个当前的最大值max_so_far: a. 如果i=0,max_so_far初始化为arr[0]。 b. 否则,max_so_far = max(max_so_far, arr[i]). 3. 对于每个元素,如果arr[i]等于sorted_arr[i],并且arr[i]等于max_so_far,则将该元素计入结果。 这样,就能确保该元素在排序后的位置正确,并且是原数组中到当前位置的最大值,从而满足主元的条件。 例如,前面的例子: 原数组3、2、1: sorted_arr是1、2、3. 遍历: i=0: arr[i]=3。max_so_far=3。sorted_arr[i]=1。不等于,不计入。 i=1: arr[i]=2。max_so_far=3。sorted_arr[i]=2。arr[i]等于sorted_arr[i],但max_so_far是3不等于arr[i],所以不计入。 i=2: arr[i]=1。max_so_far=3。sorted_arr[i]=3。不等于,不计入。 总共有0个主元,正确。 另一个例子,原数组是1、3、2、4、5: sorted_arr是1、2、3、4、5. 遍历: i=0: max_so_far=1。等于sorted_arr[0]=1,计入。 i=1: max_so_far=3。sorted_arr[1]=2。不等,不计入。 i=2: max_so_far=3。sorted_arr[2]=3。等于。但arr[i]是2,所以不相等,不计入。 i=3: max_so_far=4。sorted_arr[3]=4,等于,计入。 i=4: max_so_far=5,等于sorted_arr[4]=5,计入. 总共有3个主元,正确。 这样,正确的条件是必须同时满足这两个条件。 用户的代码中没有维护max_so_far,导致错误。所以,用户需要修改代码,增加这个条件。 因此,用户提供的代码逻辑错误,正确的做法是添加对max_so_far的判断。</think>您的思路存在一定问题。虽然排序后位置不变的元素确实可能是主元,但需要同时满足该元素是原数组中到当前位置的最大值。以下是详细分析: ### 错误原因分析 原方法仅判断排序前后位置是否一致,但忽略了一个重要条件:主元必须是其左侧所有元素的最大值。例如测试案例`3 2 1`排序后为`1 2 3`,原数组中元素`2`在位置1时虽然满足位置一致,但其左侧的`3`比它大,因此不能被选为主元。 ### 正确思路 主元必须满足以下两个条件: 1. 该元素在排序后的位置与当前相同(保证右侧元素均大于它) 2. 该元素是原数组中到当前位置的最大值(保证左侧元素均小于它) ### 改进后的算法步骤 1. **排序原数组**,得到有序版本。 2. **遍历原数组**,维护当前最大值。 3. **双重条件判断**:位置一致且是当前最大值。 ### 参考代码 ```cpp #include <iostream> #include <algorithm> #include <vector> using namespace std; int main() { int N; scanf("%d", &N); vector<int> arr(N), sorted(N); for (int i = 0; i < N; ++i) { scanf("%d", &arr[i]); sorted[i] = arr[i]; } sort(sorted.begin(), sorted.end()); vector<int> ans; int max_so_far = 0; for (int i = 0; i < N; ++i) { if (arr[i] > max_so_far) max_so_far = arr[i]; if (arr[i] == sorted[i] && arr[i] == max_so_far) ans.push_back(arr[i]); } printf("%d\n", ans.size()); for (int i = 0; i < ans.size(); ++i) { if (i > 0) printf(" "); printf("%d", ans[i]); } printf("\n"); // 即使空也要换行 return 0; } ``` ### 关键修正点 - **维护最大值**:使用`max_so_far`记录遍历过程中的最大值。 - **双重条件判断**:同时检查位置一致性和最大值条件。 - **边界处理**:正确处理空输出的换行要求。 ### 复杂度分析 - 时间复杂度:$O(N \log N)$,主要来自排序操作。 - 空间复杂度:$O(N)$,存储排序后的数组和结果。 这样修改后,算法能正确识别所有可能的主元元素。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值