1 题目
题目:颜色分类II(Sort Colors II)
描述:给定一个有n个对象(包括k种不同的颜色,并按照1到k进行编号)的数组,将对象进行分类使相同颜色的对象相邻,并按照1,2,…k的顺序进行排序。
- 不能使用代码库中的排序函数来解决这个问题
- k <= n
lintcode题号——143,难度——medium
样例1:
输入:
[3,2,2,1,4]
4
输出: [1,2,2,3,4]
样例2:
输入:
[2,1,1,2,2]
2
输出: [1,1,2,2,2]
2 解决方案
2.1 思路
将两组元素进行排序需要用双指针,将三组元素进行排序需要用三指针,该题需要排序的颜色种类不确定,无法直接套用相应的指针算法,一种笨办法是先排序出[颜色1,(颜色2,颜色3,...)]
,再排序出[颜色1,颜色2,(颜色3,...)]
,这样可以把该题当成每次都对两组元素进行排序的情况来做,时间复杂度O(n^k)。
考虑进行优化,该题可以用一种更优的算法解决,参考快速排序的思想,将颜色进行分治,而不是一个个颜色去排,将区间按照颜色向下细分,每次用O(n)的耗时将规模为T(k)的问题变为2*T(k/2)的规模,将时间复杂度降为O(n * log k)。
其实该题可以直接通过快速排序解决,不过时间复杂度为O(n * log n)。彩虹排序可以看作对快速排序在特定情况下的优化,将时间复杂度降为O(n * log k),其中n为数组元素个数,k为颜色种类数量。
2.2 图解
假设颜色顺序为(赤,橙,黄,绿,青,蓝,紫):
2.3 时间复杂度
每次使用O(n)的耗时将规模为T(k)的问题变为2*T(k/2)的规模,时间复杂度的计算如下:
T(k) = 2 * T(k/2) + O(n)............................(n=k)
= 2 * (2*T(k/4) + O(k/2)) + O(n).............(n=k/2)
= 4 * T(k/4) + 2*O(k/2) + O(n)
= 4 * (2*T(k/8) + O(k/4)) + 2*O(k/2) + O(n)..(n=k/4)
= 8 * T(k/8) + 4*O(k/4) + 2*O(k/2) + O(n)
= k * T(k/k) + O(k) + O(k) + …… + O(n)
= k*T(1) + (logk - 1)*O(k) + O(n)
= O(k) + (logk - 1)*O(k) + O(n)
= O(nlogk) + O(n)
= O(nlogk)
所以总的时间复杂度为O(n * log k)。
2.4 空间复杂度
空间复杂度为O(1)。
3 源码
细节:
- 彩虹排序算法,递归的定义需要包含索引的起止位置(start,end)以及颜色的起止位置(colorStart,colorEnd)。
- 将区间按照颜色向下细分,每次用O(n)的耗时将规模为T(k)的问题变为2*T(k/2)的规模,时间复杂度O(n * log k)。(n为数组长度,k为颜色种类)
- 索引范围的操作类似快速排序的left和right。
- 排序范围的操作类似合并排序的区间(left,mid)和(mid+1,right)。
C++版本:
/**
* @param colors: A list of integer
* @param k: An integer
* @return: nothing
*/
void sortColors2(vector<int> &colors, int k) {
// write your code here
if (colors.empty())
{
return;
}
rainbowSort(colors, 0, colors.size() - 1, 1, k);
}
// 对数组中从start到end的位置进行排序,排序区间内的颜色分布从colorStart到colorEnd
void rainbowSort(vector<int> & colors, int start, int end, int colorStart, int colorEnd)
{
if (colorStart == colorEnd) // 只有一种颜色,不需要继续排序
{
return;
}
if (start == end) // 只有一个元素,不需要继续排序
{
return;
}
int left = start;
int right = end;
int colorMid = colorStart + (colorEnd - colorStart) / 2;
while (left <= right)
{
if (colors.at(left) <= colorMid) // 按照颜色划分左右区间
{
left++;
continue;
}
if (colors.at(right) > colorMid) // 按照颜色划分左右区间
{
right--;
continue;
}
swap(colors.at(left++), colors.at(right--));
}
rainbowSort(colors, start, right, colorStart, colorMid);
rainbowSort(colors, left, end, colorMid + 1, colorEnd);
}