「PAT乙级真题解析」Basic Level 1045 快速排序 (问题分析+完整步骤+伪代码描述+提交通过代码)

题设要求所有给定数列中的满足主元条件的元素。 主元条件: 这个元素大于等于其左边所有元素 且 小于等于其右边所有元素; 所以题目可以描述为: 找出所有「大于等于其左边所有元素且小于等于其右边所有元素」的元素。

#算法#数据结构#需求分析#c语言#pat考试

Table of Contents

乙级的题目训练主要用来熟悉编程语言的语法和形成良好的编码习惯和编码规范。从小白开始逐步掌握用编程解决问题。

PAT乙级BasicLevelPractice 1045

问题分析

题设要求所有给定数列中的满足主元条件的元素。 主元条件: 这个元素大于等于其左边所有元素 且 小于等于其右边所有元素; 所以题目可以描述为: 找出所有「大于等于其左边所有元素且小于等于其右边所有元素」的元素。

如何找?

最符合直觉的方法就是: 既然确定一个元素是不是主元, 只需要遍历其左边元素检查是否小于这个元素, 同时遍历其右边元素检查是否大于这个元素,那么对所有元素做这个操作就可以找出所有主元。 于是会写成两重循环的结构:

for 元素 in 所有元素:
    for 元素 in 左边元素:
        比较大小
    for 元素 in 右边元素:
        比较大小

先不考虑是否会被OJ系统判为超时的问题(事实确实会被判为超时), 我们考虑优化性能上的优化, 看看是不是存在不必要的操作 首先我们遍历左边是为了检查是否大于左边全部元素, 即大于左边元素的最大值 遍历左边是为了检查是否小于右边全部元素, 即小于右边元素的最小值 如果我们采用事先记录最值用于后续比较(呼应【数据的存储是为了方便使用】的原则),是否可以进行更少的比较操作?

事先记录最值

记录最大值时, 每一个索引位置只需要将当前元素与左边已经记录的最大值进行比较, 取二者最大值即可, 比较次数为1. 记录最小值时同理, 只需要取当前元素与右边已记录的最小值二者的最小值即可, 比较次数为1. 所以数组元素个数为n时, 记录最值需要进行2n次比较

检查主元时, 由于每个元素只需要跟记录的最大值、最小值比较, 所以单个元素比较次数为2 所以整个查找过程总比较次数为4n;

由于是lg(n)级别的, 而涉及遍历全部或部分元素的二重循环复杂度是lg(N^2)级别的. 性能确实得到了优化。

完整描述步骤

  1. 获取输入: 正整数排列
  2. 初始化数组的最值记录器: 左边元素最大值记录器left_max; left_max[0] = 0; 右边元素最小值记录器right_min; right_min[length - 1] = 100001;
  3. 开始记录最值: index from 1 to length - 1: left_max[index] = max(numbers[index], left_max[index - 1]) index from length - 2 to 0: right_min[index] = min(numbers[index], right_min[index - 1])
  4. 检查元素是否是主元: 如果left_max[index] < numbers[index] < right_min[index], 则为主元
  5. 输出所有主元

伪代码描述

  1. get input: number_amount, numbers
  2. init recorders: left_max, right_min left_max[0] = 0 right_min[number_amount - 1] = 100001 main_element_amount = 0; main_elements;
  3. for index in range(1, number_amount): left_max[index] = max(numbers[index], left_max[index - 1]) for index in range(number_amount - 2, -1, -1): right_min[index] = min(numbers[index], right_min[index - 1])
  4. for index in range(0, number_amount): if left_max[index] < numbers[index] < right_min[index]: main_element_amount++; main_elements.add(numbers[i])
  5. print(main_element_amount, main_elements)

完整提交代码

/*
# 问题分析
题设要求所有给定数列中的满足主元条件的元素。
主元条件: 这个元素大于等于其左边所有元素 且 小于等于其右边所有元素;
所以题目可以描述为:
找出所有「大于等于其左边所有元素且小于等于其右边所有元素」的元素。
 
## 如何找?
最符合直觉的方法就是: 
既然确定一个元素是不是主元, 只需要遍历其左边元素检查是否小于这个元素,
同时遍历其右边元素检查是否大于这个元素,那么对所有元素做这个操作就可以找出所有主元。
于是会写成两重循环的结构:
for 元素 in 所有元素:
    for 元素 in 左边元素:
        比较大小
    for 元素 in 右边元素:
        比较大小
 
先不考虑是否会被OJ系统判为超时的问题(事实确实会被判为超时), 我们考虑优化性能上的优化, 看看是不是存在不必要的操作
首先我们遍历左边是为了检查是否大于左边全部元素, 即大于左边元素的最大值
遍历左边是为了检查是否小于右边全部元素, 即小于右边元素的最小值
如果我们采用事先记录最值用于后续比较(呼应【数据的存储是为了方便使用】的原则),是否可以进行更少的比较操作?
 
## 事先记录最值
记录最大值时, 每一个索引位置只需要将当前元素与左边已经记录的最大值进行比较, 取二者最大值即可, 比较次数为1.
记录最小值时同理, 只需要取当前元素与右边已记录的最小值二者的最小值即可, 比较次数为1.
所以数组元素个数为n时, 记录最值需要进行2n次比较
 
检查主元时, 由于每个元素只需要跟记录的最大值、最小值比较, 所以单个元素比较次数为2
所以整个查找过程总比较次数为4n;
 
由于是lg(n)级别的, 而涉及遍历全部或部分元素的二重循环复杂度是lg(N^2)级别的. 性能确实得到了优化。
 
# 完整描述步骤
1. 获取输入: 正整数排列
2. 初始化数组的最值记录器:
    左边元素最大值记录器left_max; left_max[0] = 0;
    右边元素最小值记录器right_min; right_min[length - 1] = 100001;
3. 开始记录最值:
    index from 1 to length - 1: left_max[index] = max(numbers[index], left_max[index - 1])
    index from length - 2 to 0: right_min[index] = min(numbers[index], right_min[index - 1])
4. 检查元素是否是主元:
    如果left_max[index] < numbers[index] < right_min[index], 则为主元
5. 输出所有主元
 
# 伪代码描述
1. get input: number_amount, numbers
2. init recorders: left_max, right_min
    left_max[0] = 0
    right_min[number_amount - 1] = 100001
    main_element_amount = 0;
    main_elements;
3. for index in range(1, number_amount):
    left_max[index] = max(numbers[index], left_max[index - 1])
   for index in range(number_amount - 2, -1, -1):
    right_min[index] = min(numbers[index], right_min[index - 1])
4. for index in range(0, number_amount):
    if left_max[index] < numbers[index] < right_min[index]:
        main_element_amount++;
        main_elements.add(numbers[i])
5. print(main_element_amount, main_elements)
 
 
*/
 
 
#include <stdio.h>
#define MAX_LENGTH 100001
#define MAX_VALUE 1E9
 
int main() {
  int number_amount;
  scanf("%d", &number_amount);
 
  int numbers[number_amount];
  for (int i = 0; i < number_amount; i++) {
    scanf("%d", &numbers[i]);
  }
 
  int left_max[number_amount];
  left_max[0] = 0;
  int right_min[number_amount];
  right_min[number_amount - 1] = MAX_VALUE;
  for (int i = 1; i < number_amount; i++) {
    left_max[i] = (numbers[i] > left_max[i - 1]) ? numbers[i] : left_max[i - 1];
  }
  for (int i = number_amount - 2; i >= 0; i--)
    right_min[i] =
        (numbers[i] < right_min[i + 1]) ? numbers[i] : right_min[i + 1];
 
  int main_element_amount = 0;
  int pos = 0;
  int main_elements[number_amount];
  for (int i = 0; i < number_amount; i++) {
    if (numbers[i] >= left_max[i] && numbers[i] <= right_min[i]) {
      main_element_amount++;
      main_elements[pos] = numbers[i];
      pos++;
    }
  }
 
  printf("%d\n", main_element_amount);
  for (int i = 0; i < main_element_amount; i++) {
    printf("%d", main_elements[i]);
    if (i + 1 != main_element_amount) {
      printf(" ");
    }
  }
  printf("\n");
  return 0;
}
「PAT乙级真题解析」Basic Level 1045 快速排序 (问题分析+完整步骤+伪代码描述+提交通过代码) | 生活糖果