描述
给定一个包含N个整数的数组A。你的任务是将A重新排列,使得任意两个相等的整数在数组中都不相邻。
如果存在多个重排后的数组满足条件,输出字典序最小的数组。
这里字典序最小指:首先尽量使第一个整数最小,其次使第二个整数最小,以此类推。
输入
第一行包含一个整数N,表示数组的长度。(1 <= N <= 100000)
第二行包含N个整数,依次是 A1, A2, … AN。(1 <= Ai <= 1000000000)
输出
输出字典序最小的重排数组。如果这样的数组不存在,输出-1。
样例输入
4
2 1 3 3
样例输出
1 3 2 3
思路
本题是《分隔相同字符》的加强版。
《分隔相同字符》的题目分析可以看这里。
在《分隔相同字符》中,数组中的每个元素只能是小写字母(‘a’-‘z’)。换句话说,值域范围是[1, 26]。
而在本题中,数组中的整数值域是[1, 1000000000],照搬《分隔相同字符》的做法会超时。
不过《分隔相同字符》中介绍的结论还是成立的:无解的充要条件是某个数字出现次数超过(N+1)/2。
所以贪心算法仍然适用:
1) 预处理A1~AN,生成二元组集合S={(a1, c1), (a2, c2), … (am, cm)}。表示a1有c1个,a2有c2个……,am有cm个。
2) 找出c值最大的二元组(ai, ci)。如果ci过多,即ci+ci-1 > n,则无解。
3) 依次找出重排后的第一个数、第二个数……第N个数:
3.1) 找出c值最大的二元组(ai, ci),如果ci恰好满足:ci+ci-1==n。那么当前数字只能选择ai。
3.2) 如果ci+ci-1 < n,找出a值最小的二元组(aj, cj)。如果前一个数字选的不是aj,那么选择aj。
3.3) 如果前一个数字选的就是aj,再找出a值次小的二元组(ak, ck)。选择ak。
不论上述哪种情况,假设最后选择的是(at, ct),都要ct–。特别的如果ct=0,则要将(at, ct)从S中删除。
所以我们需要设计一种数据结构,对于二元组集合S={(a1, c1), (a2, c2), … (am, cm)},支持以下操作(时间复杂度要低):
1) 找到c值最大的二元组
2) 找到a值最大的二元组
3) 找多a值次大的二元组
4) 对一个二元组(at, ct)进行ct–的操作
5) 删除一个二元组(at, ct)
能支持以上操作的数据结构很多,比如堆、平衡树,上面的操作都能实现O(logN)的复杂度。考虑到C++ STL里的set/map(Java中的TreeSet/TreeMap)内部实现就是平衡树,我们介绍一种利用set/map实现以上操作的方法,非常简单。
可以参考doubleh同学的代码
Tips: 由于set不能修改容器中的元素,所以ct–操作是先删除(at, ct)再添加(at, ct-1)来实现的。
1 | printf("%d%c", x, " \n"[i == n]); |
1 | #include <bits/stdc++.h> |
1 |
|