1045 快速排序 (25 分)
题意描述:
著名的快速排序算法里有一个经典的划分过程:我们通常采用某种方法取一个元素作为主元,通过交换,把比主元小的元素放到它的左边,比主元大的元素放到它的右边。 给定划分后的 N 个互不相同的正整数的排列,请问有多少个元素可能是划分前选取的主元?
例如给定 N = 5 N = 5 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
解题思路:
Alice: 你觉得怎么样?
Bob: 还好,题目描述挺明白,而且我又学过快速排序所以读起来很熟悉。
Alice: (⊙o⊙)…我有一个想法,就是直接按照题目中‘主元’的定义去判断,一个元素一个元素的判断。对每个元素就看一下它是不是大于左边的所有元素,再看一下它是不是小于右边的所有元素,如果是的话,那么它就是一个主元,否则就不是。而且如果不满足条件的话还可以直接退出,减少不必要的计算。
Bob: 对对对,我也是这样想的。就是不知道用Python会不会超时啊,输入有10的5次方呢!
Alice: 我去试试吧~٩(๑>◡<๑)۶
Alice: ╭(╯ ^╰)╮ 哼,真 * * 的超时了
Bob: 恩,看来,是时候展示真正的技术了。我有一个O(N)的想法。
Alice: o, Σ┗(@ロ@;)┛,你 * * 的快说吧。
Bob: 是这样的,一个元素是否大于它左边的所有元素和它是否小于它右边的所有元素是两回事,它们根本就不相关。不相关的东西干嘛要放在一起呢,我们可以把它拆开!
Alice: 拆开 ? 拆开有什么用呢 ?
Bob: 当然有用, 我们使用一个循环来找出满足 大于左边所有的元素 的元素的集合,在使用另一个循环找到满足 小于右边所有元素的 的元素的集合。这两个集合的交集就是我们的答案!
Alice: 那怎么就O(N)了呢 ?你能在一个循环就找到所有 大于左边所有的元素 的元素的集合吗(•ิ_•ิ)?
Bob: 嘿嘿嘿,可以,这个是动态规划的做法啦。
Alice: 快说快说。
Bob: 你想啊,现在我们有一个值记录了前K个元素中的最大值,现在我们来到了第K+1个元素,如何判断这个元素是否大于前面的那K个元素呢?
Alice: 我知道了!!,只有第K+1个元素比前K个元素的最大值还要大那就比它们所有值都要大了。啊呀,你是怎么想到记录前K个元素的最大值的呢?
Bob: (⊙o⊙)…,可能是这样的,最初的方法要重复的比大小,怎么减少比大小的次数呢,如果能记录一些可能有用的计算结果呗,记录什么呢,最大最小值可能有用,嗨,结果还真是这样。
Alice: 这样啊,那对于右边的元素我也会做! o(≧ ▽ ≦)ツ, 反着来,从最右边往左边找,记一个最小值。只要第K-1个元素比后面K到N个元素的最小值还要小,那就比右边的都要小。
Bob: 就是这样 ,就是这样。
Alice: 我去试试,٩(๑>◡<๑)۶
Alice:? 又 * * 超时了,虽然这次只有一个点超时。
Bob: 不应该啊,让我用C giagia 试一下。
Alice: 我我写完了,通过了通过了。٩(๑>◡<๑)۶
Bob: 好好厉害,我还没写完ε=(´ο`)))呐。
代码:
虽然我们的算法是O(N)的,但是这份Python实现还是超时了 ?
def main():
N = int(input())
# 接收输入的正整数N
temp = [int(x) for x in input().split()]
# 接收输入的N个不同的正整数,以列表的形式存储。
left_check_ok = []
# left_check_ok用来记录列表中 左边的所有元素都小于其自身的元素。
left_max = 0
# left_max用来记录在向右扫描的过程中,到目前为止所遇到的最大值。
for index, _ in enumerate(temp):
# 从左到右的扫描整个列表
if temp[index] > left_max:
# 如果当前元素满足, 大于左边所有元素中的最大值。
# 也就是说当前元素大于它左边的所有元素,记下这个元素。
left_check_ok.extend([temp[index]])
left_max = temp[index]
# 然后我们需要把最大值更新为当前元素。
right_check_ok = []
# right_check_ok用来记录列表中 右边的所有元素都大于其自身的元素。
right_min = float('inf')
# right_min 记录从右向左扫描的过程中,到目前为止所遇到的最小值。
for index in range(len(temp) - 1, -1, -1):
# 从右向左扫描整个列表
if temp[index] < right_min:
# 如果当前元素满足, 小于右边所有元素中的最小值。
# 也就是说当前元素小于它右边的所有元素,记下这个元素。
right_check_ok.extend([temp[index]])
right_min = temp[index]
# 然后我们需要把最小值更新为当前元素。
answer = []
for x in left_check_ok:
# 对每个满足 大于它左边的所有元素的元素
if x in right_check_ok:
# 如果它也满足 小于它右边的所有元素
answer.extend([x])
# 这就可能是一个主元,把它记录下来。注意extend()的用法。
answer.sort()
# 按序
print(len(answer))
print(" ".join([str(x) for x in answer]))
# 输出答案。
if __name__ == '__main__':
main()
还能怎么办呢?拿起C++再写一遍呗,经常不写,写起来比张飞绣花还费劲
?
#include <stdio.h>
//基本的输入和输出
#include <algorithm>
//需要使用其中的排序函数
#include <vector>
// vector是C++库,我们用它来构造一个动态数组
#define N 100000 + 10
// 宏定义,简化后续定义数组长度的代码。题目中最大输出为100000, 后面加一个小的整
// 数是为了防止数组越界访问。具体加多少可以看个人喜好,也可以不加。
using namespace std;
// C++ 语言的一个“暗号”
int data[N];
// data用来存储输入的N个正整数
int left_flag[N];
// 用来标记数组中的每个元素是否大于它左边的所有元素
int righ_flag[N];
// 用来标记数组中的每个元素是否小于它右边的所有元素
int main(){
int n;
scanf("%d", &n);
// 读入正整数 N
for(int i=0; i<n; ++i){
scanf("%d", &data[i]);
}
// 读入N个不同的正整数
int left_max = 0;
// 由于输入的是N个不同的正整数,所以left_max初值设置为0即可
for(int i=0; i<n; ++i){
// 对于 数组中的每个元素,注意这里是从左到右扫描的。
if (data[i] > left_max){
// 如果当前元素大于左边所有元素的最大值,也就是说当前元素大于它左边的所有元素
left_max = data[i];
// 更新一下最大值
left_flag[i] = 1;
// 标记一下这个元素
}
}
int righ_min = 1000000000 + 1;
// 由于输入的N个整数都不超过10^9,所以righ_min初值设置为10^9 + 1
for(int i=n-1; i>=0; --i){
// 对于数组中的每个元素,注意这里从右向左扫描的
if(data[i] < righ_min){
// 如果当前元素小于右边所有元素的最小值,也就是说当前元素小于它右边的所有元素
righ_min = data[i];
// 更新一下最大值
righ_flag[i] = 1;
// 标记一下这个元素
}
}
vector<int> answer;
// 创建一个整型的动态数组
for(int i=0; i<n; ++i){
// 对于 数组中的每个元素
if(left_flag[i] == 1 && righ_flag[i] == 1){
//如果它既大于左边的所有元素又小于右边的所有元素
answer.push_back(data[i]);
// 它是一个可能的主元,记下来。不过为什么要使用动态数组呢?因为我们
// 不知道具体有多少个“主元”,而后面还需要排序。如果使用定长的数组还
// 需要多写一些额外的代码去把排序好的主元提出来。
}
}
sort(answer.begin(), answer.end());
// 把所有可能的主元排一下序
int count = answer.size();
printf("%d\n", count);
// 输出有多少个主元
if(count == 1){
printf("%d\n", answer[answer.size()-1]);
// 如果只有一个,直接输出并且换行
}else if(count > 1){
// 如果有多于一个
for(int i=0; i<answer.size()-1; ++i){
// 对于前N-1个主元,输出主元和空格
printf("%d ", answer[i]);
}
printf("%d\n", answer[answer.size()-1]);
// 对于最后一个主元,输出主元和换行
}else{
// 本题的一个坑,没有主元的时候需要单独输出一个换行。
printf("\n");
}
return 0;
}
易错点:
- O(N^2)的算法可能会超时。
- 按照递增的顺序输出 可能是主元的 元素并不意味这我们需要以递增的顺序求解出这些元素。
- 可能的主元数量是
0
的时候需要单独输出一个换行。
总结: