写在前面
在leetcode上面刷到几道很有意思的排序题,和传统的排序题不一样,很有意思。
75. 颜色分类
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
注意:
不能使用代码库中的排序函数来解决这道题。
示例:
输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]
直接排序的方法我们就先不考虑了,因为最快也只能O(n*log(n))。
两遍扫描
最容易想到的解法,就是一次遍历统计出0,1,2的个数,然后再顺序放进数组。
这样需要遍历两次,思路很清晰,代码这里就略去了。
一遍扫描(荷兰国旗问题)
因为只有012三种数字,所以最后的结果肯定是这样的:
00000……00000011111……11111111222222……2222222
我们其实只要找到两个交界点就行了。
所以我们假设出两个指针,p1,p2,
初始化:p1 = 0, p2 = nums.size()-1。
然后再维护一个curr指针,这个指针从头扫到尾。
算法:
当curr<=p2:
- nums[curr]==0: nums[curr]和nums[p1]交换,然后p1和curr都往后移。
- nums[curr]==1: curr+1
- nums[curr]==2: nums[curr]和nums[p2]交换,p2-1.
代码:
class Solution {
public:
void sortColors(vector<int>& nums) {
if(nums.empty()) return;
int n = nums.size();
int p1(0),p2(n-1),curr(0);
while(curr<=p2){
if(nums[curr]==0){
swap(nums[p1],nums[curr]);
++p1;
++curr;
}
else if(nums[curr]==2){
swap(nums[p2],nums[curr]);
--p2;
}
else{
++curr;
}
}
return;
}
};
280. 摆动排序
给你一个无序的数组 nums, 将该数字 原地 重排后使得 nums[0] <= nums[1] >= nums[2] <= nums[3]…。
示例:
输入: nums = [3,5,2,1,6,4]
输出: 一个可能的解答是 [3,5,1,6,2,4]
排序+交换
最容易想到的就是先排序,然后从第二个开始,每两个交换一下,就可以满足要求了。
代码:
class Solution {
public:
void wiggleSort(vector<int>& nums) {
if(nums.empty()) return;
sort(nums.begin(),nums.end());
int p=1;
while(p<nums.size()&&p+1<nums.size()){
swap(nums[p],nums[p+1]);
p+=2;
}
return;
}
};
一次扫描
排序然后交换的方法,复杂度O(nlog(n))。
有没有更快的方法呢,当然有,我们直接判断相邻两个数字的大小关系,不满足要求就换就完事了。
代码:
class Solution {
public:
void wiggleSort(vector<int>& nums) {
bool up = true;
for(int i=1; i<nums.size(); ++i){
if(up){
if(nums[i]<nums[i-1])
swap(nums[i],nums[i-1]);
}
else{
if(nums[i]>nums[i-1])
swap(nums[i],nums[i-1]);
}
up = !up;
}
return;
}
};
简化一下代码,去掉布尔标志,利用下标奇偶性。
代码:
class Solution {
public:
void wiggleSort(vector<int>& nums) {
for(int i=1; i<nums.size(); ++i){
if((i%2==1&&nums[i]<nums[i-1]) || (i%2==0&&nums[i]>nums[i-1]))
swap(nums[i],nums[i-1]);
}
return;
}
};
有些读者可能就觉得简化代码没有意义,实际上这样简化一下,一方面可以养成比较好的代码习惯,减少不必要的代码,增加可读性,另一方面,简化后的代码性能得到提升,可以少量提速。
上面三个代码的时间,可以发现越来越快。
再放一段更简洁的代码,感兴趣的朋友可以思考一下原理,欢迎评论留言讨论。
class Solution {
public:
void wiggleSort(vector<int>& nums) {
for(int i=1; i<nums.size(); ++i){
if((i%2==1)==(nums[i]<nums[i-1]))
swap(nums[i],nums[i-1]);
}
return;
}
};
324. 摆动排序 II
给定一个无序的数组 nums,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]… 的顺序。
示例 1:
输入: nums = [1, 5, 1, 1, 6, 4]
输出: 一个可能的答案是 [1, 4, 1, 5, 1, 6]
排序+穿插
还是可以先排个序,然后因为这题要求是严格摇摆,不能有等于,所以分成两块之后要逆序穿插。
代码:
class Solution {
public:
void wiggleSort(vector<int>& nums) {
vector<int> a(nums);
int n = nums.size();
sort(a.begin(),a.end());
int l,r;
r = n-1;
l = (n-1)/2;
int p = 0;
while(l>=0 && r>(n-1)/2){
nums[p] = a[l];
nums[p+1] = a[r];
p += 2;
--l;
--r;
}
if(n%2==1){
nums[n-1] = a[0];
}
return;
}
};
快速选择 + 3way-partition
思路和第一个解法是一样的,但是不需要排序,用快速选择可以更快的打到目的,这里po一个我参考的链接,解释的很好,我就不赘述了(我也讲不清-逃)
376. 摆动序列
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。
例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。
示例 1:
输入: [1,7,4,9,2,5]
输出: 6
解释: 整个序列均为摆动序列。
dp
暴力就不写了,没提升。这题很容易想到dp,维护两个数组up和down记录到当前位置升序或降序的最长长度。
转移方程:
up[i] = max(up[i],down[j]+1) j<i
down[i] = max(down[i],up[j]+1) j<i
代码:
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
int l = nums.size();
if(l<2) return l;
vector<int> up(l+1,0);
vector<int> down(l+1,0);
for(int i=0; i<l; ++i)
for(int j=0; j<i; ++j){
if(nums[i]>nums[j])
up[i] = max(up[i],down[j]+1);
else if(nums[i]<nums[j])
down[i] = max(down[i],up[j]+1);
}
return max(down[l-1],up[l-1])+1;
}
};
线性dp
n^2的解法总是有些不尽人意,这里思考一下能不能线性解决呢,我们观察原数组,不难发现相邻两个数只有三种状态:
- 上升的位置,意味着 nums[i] > nums[i - 1]nums[i]>nums[i−1]
- 下降的位置,意味着 nums[i] < nums[i - 1]nums[i]<nums[i−1]
- 相同的位置,意味着 nums[i] == nums[i - 1]nums[i]==nums[i−1]
然后我们还是维护两个dp数组up和down:
-
如果 nums[i] > nums[i-1]nums[i]>nums[i−1] ,意味着这里在摆动上升,前一个数字肯定处于下降的位置。所以 up[i] = down[i-1] + 1up[i]=down[i−1]+1 , down[i]down[i] 与 down[i-1]down[i−1] 保持相同。
-
如果 nums[i] < nums[i-1]nums[i]<nums[i−1] ,意味着这里在摆动下降,前一个数字肯定处于下降的位置。所以 down[i] = up[i-1] + 1down[i]=up[i−1]+1 , up[i]up[i] 与 up[i-1]up[i−1] 保持不变。
-
如果 nums[i] == nums[i-1]nums[i]==nums[i−1] ,意味着这个元素不会改变任何东西因为它没有摆动。所以 down[i]down[i] 与 up[i]up[i] 与 down[i-1]down[i−1] 和 up[i-1]up[i−1] 都分别保持不变。
代码:
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
int l = nums.size();
if(l<2) return l;
vector<int> up(l+1,0);
vector<int> down(l+1,0);
up[0] = down[0] = 1;
for(int i=1; i<l; ++i)
if(nums[i]>nums[i-1]){
down[i] = down[i-1];
up[i] = down[i-1]+1;
}
else if(nums[i]<nums[i-1]){
up[i] = up[i-1];
down[i] = up[i-1]+1;
}
else{
up[i] = up[i-1];
down[i] = down[i-1];
}
return max(down[l-1],up[l-1]);
}
};