PAT A1101 Quick Sort (动态规划)

A1101 Quick Sort | B1045 快速排序

作者: CAO Peng, 单位: Google, 时间限制: 200 ms, 内存限制: 64 MB

There is a classical process named partition in the famous quick sort algorithm. In this process we typically choose one element as the pivot. Then the elements less than the pivot are moved to its left and those larger than the pivot to its right. Given N distinct positive integers after a run of partition, could you tell how many elements could be the selected pivot for this partition?

For example, given N = 5 and the numbers 1, 3, 2, 4, and 5. We have:

  • 1 could be the pivot since there is no element to its left and all the elements to its right are larger than it;
  • 3 must not be the pivot since although all the elements to its left are smaller, the number 2 to its right is less than it as well;
  • 2 must not be the pivot since although all the elements to its right are larger, the number 3 to its left is larger than it as well;
  • and for the similar reason, 4 and 5 could also be the pivot.
    Hence in total there are 3 pivot candidates.

Input Specification:
Each input file contains one test case. For each case, the first line gives a positive integer N (≤ 105). Then the next line contains N distinct positive integers no larger than 109. The numbers in a line are separated by spaces.

Output Specification:
For each test case, output in the first line the number of pivot candidates. Then in the next line print these candidates in increasing order. There must be exactly 1 space between two adjacent numbers, and no extra space at the end of each line.

Sample Input:
5
1 3 2 4 5

Sample Output:
3
1 4 5

  • 题意:简单地描述快速排序的过程,即每趟排序均选择一个主元,将序列中所有小于主元的数排在其左侧,反之,将大于该主元的数排在其右侧,再将该主元两侧的序列递归地通过此方式排序,最终可使该序列整体有序。题目要求在给定的序列中找出可能是主元的数,并升序输出。具体描述可参考本题的乙级中文版 1045 快速排序 (25分)
  • 思路:题目中已经给出例子,实际上只需要遍历该序列,并判断当前元素是否比所有其左侧元素都大且比所有其右侧元素均小即可。

哈哈…当然没这么简单啦。

这类题读完的第一反应是暴力解决,直接嵌套两个for循环,把符合条件的结果保存到 vector 中即能得到答案,第二反应是看时间限制,200ms ,题目中 N 的最大规模是 105,暴力法的时间复杂度 O(n2),那么实际上问题规模就变成了 1010,这个执行次数在最坏情况下所用的时间已经远远超过 100s 了,所以暴力法一定会存在超时的测试点。于是我立马想到了可以稍作优化的解决方案:在遍历过程中,如果已经判定当前元素可以作为主元,那么至少我能保证在这个元素之后的所有元素均是大于该数的,因此在对下一个元素进行判定的时候就不需要再将当前元素与所有前面的元素进行大小比较了,而是直接从上一个满足条件的主元开始,于是设置一个 left 变量来记录左侧判断时循环开始的位置。但是这种做法,时间复杂度的数量级还是 O(n2),所以当n很大的时候这种优化并没有多少意义,但是执念还是让我把代码写出来了,哈哈。

提交代码如下,核心代码 9-28

#include <bits/stdc++.h>
using namespace std;
int main() {
    int n;
    cin >> n;
    vector<int> v(n), ans;
    for (int i = 0; i < n; i++) scanf("%d", &v[i]);
    int left = 0;
    for (int i = 0; i < v.size(); i++) {
        bool flag = true;                 // 设置标志位
        for (int j = left; j < i; j++) {  // 从left开始
            if (v[j] > v[i]) {
                flag = false;
                break;  // 及时退出, 下同
            }
        }
        // 如果左侧判断均通过则还需判定右侧, 因此循环条件中先加入flag
        for (int j = i; flag && j < v.size(); j++) {
            if (v[j] < v[i]) {
                flag = false;
                break;
            }
        }
        if (flag) {  // 符合条件, 添加进vector
            ans.push_back(v[i]);
            left = i;
        }
    }
    sort(ans.begin(), ans.end());
    printf("%d\n", ans.size());
    if (ans.size() == 0) printf("\n");
    for (int i = 0; i < ans.size(); i++) {
        if (i != 0) printf(" ");
        printf("%d", ans[i]);
    }
    return 0;
}

运行结果不出所料的,有两个样例没通过,拿到 20/25分 (这题目考察点就在算法的时间复杂度上)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m8B4Sh0G-1581662292878)(/img/pat-a1101(1)].png)
所以,这题暴力法行不通(但考试的时候如果能在10分钟内把25分题拿到20分那也不错了,可以先直接换到下一题,哈哈)。于是更换思路,像这种类型的题,还能想到的方法是动态规划。设立 dp 数组,dp[i] 存放 v[0~i] 中最大的值,那么,对于满足条件的元素 dp[i] 必定是等于 v[i] 的,因此根据dp的定义,可以得到这样的式子:

dp[i] = max(dp[i - 1], v[i]);

实际上,在动态规划中,把 dp[i] 称为问题的状态,上面的式子称作状态转移方程。那么对于判断当前元素是否是大于所有左侧元素就变成了判断 v[i] 是否与 dp[i] 相等。

再次提交代码,核心代码 8-22

#include <bits/stdc++.h>
using namespace std;
int main() {
    int n;
    cin >> n;
    vector<int> v(n), dp(n), ans;
    for (int i = 0; i < n; i++) scanf("%d", &v[i]);
    dp[0] = v[0];
    for (int i = 1; i < n; i++) {
        dp[i] = max(dp[i - 1], v[i]);
    }
    for (int i = 0; i < v.size(); i++) {
        bool flag = true;
        if (dp[i] != v[i]) flag = false;
        for (int j = i; flag && j < v.size(); j++) {
            if (v[j] < v[i]) {
                flag = false;
                break;
            }
        }
        if (flag) ans.push_back(v[i]);
    }
    sort(ans.begin(), ans.end());
    printf("%d\n", ans.size());
    if (ans.size() == 0) printf("\n");
    for (int i = 0; i < ans.size(); i++) {
        if (i != 0) printf(" ");
        printf("%d", ans[i]);
    }
    return 0;
}

运行结果较暴力法又多对了一个样例 _!!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LrHg11Cr-1581662292879)(/img/pat-a1101(2)].png)
在上面这个解法中,动态规划的思想只使用于判断左侧元素是否均小于当前元素,实际上时间复杂度还是 O(n2)。所以,对于右侧元素还是同理,再另设dpMin数组,dpMin[i] 存放 v[i~(n-1)] 中最小的值,状态转移方程为:

dpMin[n - i - 1] = min(dpMin[n - i], v[n - i - 1]);

再次提交代码如下,核心代码 8-16

#include <bits/stdc++.h>
using namespace std;
int main() {
    int n;
    scanf("%d", &n);
    vector<int> v(n), dpMax(n), dpMin(n), ans;
    for (int i = 0; i < n; i++) scanf("%d", &v[i]);
    dpMax[0] = v[0];
    dpMin[n - 1] = v[n - 1];
    for (int i = 1; i < n; i++) {
        dpMax[i] = max(dpMax[i - 1], v[i]);
        dpMin[n - i - 1] = min(dpMin[n - i], v[n - i - 1]);
    }
    for (int i = 0; i < v.size(); i++) {
        if (dpMax[i] == v[i] && dpMin[i] == v[i]) ans.push_back(v[i]);
    }
    sort(ans.begin(), ans.end());
    printf("%d\n", ans.size());
    if (ans.size() == 0) printf("\n");
    for (int i = 0; i < ans.size(); i++) {
        if (i != 0) printf(" ");
        printf("%d", ans[i]);
    }
    return 0;
}

运行结果预料之内,本题终于 AC 了,惊呼amazing,哈哈

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SnTXeEip-1581662292879)(/img/pat-a1101(3)].png)
其中最耗时的样例(也许是最大规模的极端数据)只用了 44ms,而这一次的时间复杂度变成了 O(n),在大规模的问题中,时间复杂度从 O(n2)O(n) 是很大的优化。至此本题也结束。

这道题从 暴力法动态规划 直至 AC,写代码的过程并没花多少时间,但是这个思路的快速转变也是自己很大的进步,虽然是最基础的动态规划思想的使用,但是也完全体现出了动态规划的魅力,也使代码变得简短直观。

Ps: 提交完AC的代码,也查看了其他网友的解法,似乎没人用DP,也许是大材小用吧,哈哈。

发布了27 篇原创文章 · 获赞 0 · 访问量 94

猜你喜欢

转载自blog.csdn.net/charjindev/article/details/104312019