[C++]LeetCode: 127 Sort Colors (计数排序 & 快速排序)

题目:

Given an array with n objects colored red, white or blue, sort them so that objects of the same color are adjacent, with the colors in the order red, white and blue.

Here, we will use the integers 0, 1, and 2 to represent the color red, white, and blue respectively.

Note:
You are not suppose to use the library's sort function for this problem.

click to show follow up.

Follow up:
A rather straight forward solution is a two-pass algorithm using counting sort.
First, iterate the array counting number of 0's, 1's, and 2's, then overwrite array with total number of 0's, then 1's and followed by 2's.

Could you come up with an one-pass algorithm using only constant space?

Answer 1: 计数排序标准方法

思路:题目要求我们将数组排序,并且知道数组内元素只包含0,1,2. 非常适合用计数排序。计数排序是一种稳定的线性时间排序算法,需要额外使用一个空间数组C存储计数, 其中第i个元素是带排序数组A中值等于i的元素的个数让,然后根据数组C来将A中的元素排到正确的位置。

算法步骤:

  1. 找出待排序的数组中最大和最小的元素
  2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i
  3. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
  4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

我们需要进行三次扫描,最后一次是将排序好的数组赋值回原数组中。当然,如果我们直接利用C数组中存储的元素,以及存储的数值,直接赋值回原数组也是可以的,这样经过两次扫描。下一种解法将介绍一种一次扫描的方法。
复杂度:O(N+k),输入的元素是n个0到k之间的整数时,它的运行时间是Θ(n + k)。计数排序快于任何比较排序算法。计数排序的数组长度取决于带排序数组中数据的范围,使得计数排序对于数据范围很大的数组,需要大量时间和内存。
AC Code:
class Solution {
public:
    void sortColors(int A[], int n) {
        int C[3] = {0};
        vector B(n, 0);
        
        //统计每个数字num出现的次数,存入数组C[num]中
        for(int i = 0; i < n; i++)
        {
            int num = A[i];
            C[num]++;
        }
        
        //求计数和
        for(int i = 1; i < 3; i++)
        {
            C[i] += C[i-1];
        }
        
        //反向填充目标数组
        for(int j = n-1; j >= 0; j--)
        {
            int num = A[j];
            B[C[num]-1] = num;
            C[num]--;
        }
        
        //赋值回原数组
        for(int i = 0; i < n; i++)
        {
            A[i] = B[i];
        }
        return;
    }
};

Answer 2: 快速排序思想

思路:我们对数组中的元素进行排序,数组元素只包含0,1,2。我们可以借鉴快速排序的划分的思想,选一个枢纽,我们选择1为枢纽元对数组进行划分,使得0在数组的左边,2在数组的右边,1在数组的中间。我们维护两个变量,zeroEnd表示放0那部分的尾部索引,碰到0,我们就将这个元素放到zeroEnd+1的地方,twoBegin表示放2的那部分的首部索引,如果碰到2,我们就将这个元素放到twoBegin-1处,碰到1,我们就将指针后移。

Attention:

1. &&语句,如果判断前面不成立,就不会执行后面的语句,所以如果不是1或者2,不会移动zeroEnd和twoBegin索引位置。

if(A[i] == 0 && i != ++zeroEnd)
                swap(A[zeroEnd], A[i]);
            else if(A[i] == 2 && i != --twoBegin)
                swap(A[twoBegin], A[i]);

2. 交换后,我们把1和2放到合适的位置,没有移动i, 继续判断A[i].

3. 当i和twoBegin相遇时,说明我们把所有数字都排好序了。如果写成 (i < n)作为迭代终止条件,会得到错误的结果,会把后面的2都置换到前面,打乱了排序。

while(i < twoBegin)

复杂度:O(N) 只遍历了一遍数组,常数空间复杂度。

AC Code:

class Solution {
public:
    void sortColors(int A[], int n) {
        int i = 0;
        int zeroEnd = -1;    //设置两个索引初值
        int twoBegin = n;
        
        while(i < twoBegin)
        {
            if(A[i] == 0 && i != ++zeroEnd)   //zeroEnd维护了0的最后一个位置,zeroEnd+1是紧接着的下一个位置。   
                swap(A[i], A[zeroEnd]);
            else if(A[i] == 2 && i != --twoBegin)  //twoBegin维护了2的最前面的一个位置,twoBegin-1是紧接着的前一个位置
                swap(A[i], A[twoBegin]);
            else
                i++;
        }
        return;
    }
};





你可能感兴趣的:(LeetCode,Array,Two,Pointers,Sort,leetcode)