【PAT甲级】1067 Sort with Swap(0, i)(贪心)

Given any permutation of the numbers {0, 1, 2,..., N−1}, it is easy to sort them in increasing order. But what if Swap(0, *) is the ONLY operation that is allowed to use? For example, to sort {4, 0, 2, 1, 3} we may apply the swap operations in the following way:

Swap(0, 1) => {4, 1, 2, 0, 3}
Swap(0, 3) => {4, 1, 2, 3, 0}
Swap(0, 4) => {0, 1, 2, 3, 4}

Now you are asked to find the minimum number of swaps need to sort the given permutation of the first N nonnegative integers.

Input Specification:

Each input file contains one test case, which gives a positive N (≤10​5​​) followed by a permutation sequence of {0, 1, ..., N−1}. All the numbers in a line are separated by a space.

Output Specification:

For each case, simply print in a line the minimum number of swaps need to sort the given permutation.

Sample Input:

10
3 5 7 2 6 4 9 0 8 1

Sample Output:

9

题目大意

这题给出0到n-1的一个数字序列,要求只能用0和其他数字进行交换,然后排序成从小到大的序列,求出最小的交换次数。

个人思路

这题是贪心算法的应用,贪心在每次选择交换的都是当前位置编号的那个数字,即每次都尽量让一个数字到达正确位置。

这题我建立了一个映射mapp,存储数字和数字位置的编号,如mapp[num] = i,表示数字num在第i个位置,当i == num的时候表示数字位置正确,否则数字位置错误。

同时还设立了两个set容器,存储位置正确的数字集合和数字错误的数字集合。

程序主要过程在一个循环中,正确数字集合的元素个数等于n时退出循环。

0在和其他数字的交换过程中有两种情况:

第一种是排序尚未完成,但是0已经在位置0处了,此时需要找到一个错误位置的数字把0从正确位置换出来。

第二种情况是0在非正确位置,那么就把0和位置编号的这个数字zero_pos进行交换,即把zero_pos换到了正确位置。

在循环中反复对0进行交换并计数,直到退出循环。

代码实现

#include <cstdio>
#include <set>
#include <cmath>
#include <iostream>
using namespace std;
const int maxn = 100005;
int mapp[maxn]; // 记录数字编号到数字位置的映射

int main() {
    // correct记录位置正确的数字编号 wrong记录位置错误的数字编号
    set <int> correct, wrong;
    // 输入并记录初始正确/错误状态
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i ++) {
        int num;
        scanf("%d",&num);
        mapp[num] = i;
        if (num == i) correct.insert(num);
        else wrong.insert(num);
    }
    // 当所有的数字编号位置都正确时,结束循环
    int cnt = 0;
    while (correct.size() < n) {
        // 如果数字0在0位置处,需要把0换出来
        if (mapp[0] == 0) {
            int wrong_num = *(wrong.begin());
            swap(mapp[0], mapp[wrong_num]);
            correct.erase(0);
            wrong.insert(0);
            cnt ++;
        }
        // 将0和0所占的位置编号数进行交换,那么原来的0所占的位置编号数在交换后就到达正确位置了
        int zero_pos = mapp[0];
        swap(mapp[0], mapp[zero_pos]);
        correct.insert(zero_pos);
        wrong.erase(zero_pos);
        if (mapp[0] == 0) {
            correct.insert(0);
            wrong.erase(0);
        }
        cnt ++;
    }
    printf("%d", cnt);
    return 0;
}

总结

学习不息,继续加油

这题说实话修改了好几次。不断优化最终才AC,一开始把0从正确位置换出来的时候寻找错误数字是通过遍历数字序列实现的,后来我干脆直接不存数字序列而直接存数字和下标的映射关系。然后还是有一个样例会超时,于是我把对数字序列的操作给删除了,因为存数字和下标的映射已经够了,就不用存数字序列了。改到这一步其实已经AC了,但是我发现在超时的边缘,有一个样例的用时在195ms+,然后我发现其实不用map存映射关系,用普通的数组就可以了【因为map是红黑树实现,复杂度有O(logn),而数组是随机访问O(1)】,改完之后时间就比较好了。

然后我又去看了看柳神的代码,才发现自己又天真了。其实我发现改到最后我的思路和柳神已经很像了,但是对于一些条件的判断我写得很麻烦【虽然思路看上去比较直观一点】,柳神通过巧妙的思路设计完全没有用STL,二十行代码就解决问题了。这里贴出柳神链接膜一下。https://www.liuchuo.net/archives/2301

#include <iostream>
using namespace std;
int main() {
    int n, t, cnt = 0, a[100010];
    cin >> n;
    for(int i = 0; i < n; i++){
    	cin >> t;
    	a[t] = i;
    }
    for(int i = 1; i < n; i++) {
        if(i != a[i]) {
            while(a[0] != 0) {
                swap(a[0],a[a[0]]);
                cnt++;
            }
            if(i != a[i]) {
                swap(a[0],a[i]);
                cnt++;
            }
        }
    }
    cout << cnt;
    return 0;
}

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值