「LeetCode」75.颜色分类

359 阅读3分钟

「这是我参与2022首次更文挑战的第28天,活动详情查看:2022首次更文挑战」。

题目描述🌍

给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

我们使用整数 012 分别表示红色、白色和蓝色。

必须在不使用库的 sort 函数的情况下解决这个问题。

示例 1

输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]

示例 2

输入:nums = [2,0,1]
输出:[0,1,2]

提示

  • n == nums.length
  • 1 <= n <= 300
  • nums[i]012

进阶

三路快排🍖

解题思路

题目本质就是让我们对只包含 012 三个数字的数组进行排序,且不能调代码库的排序函数!

显然我们可以手写各种排序方式,然后调用,但这并不是我们想要的,我们直接来完成进阶中的要求吧。

借用「三路快排」的思想,对数组进行一次遍历,如果是 0,则交换到左边;如果是 2,则交换到右边;一旦排完 02,那么 1 自然也就排好了。

⭐细节:

  • 如果 2 交换到右边,此时从右边交换过来的数可能是 012,所以还需要再对当前位置继续判断;
  • 如果是 0 交换到左边,从左边交换回来的数只能是 01(因为是从左向右遍历的,一遇到 2 就交换到右边了),无需再判断当前位置的元素,(尽管交换回来的数是 1)在随后的排序中会被自动排序好。

以下是排序过程中的示意图

其他解法:还有一种也是类似「三路快排」的解法,只不过 left 用于填充 0,而 right 用于填充 2,大同小异,细节上有所差异罢了,感兴趣的可以自己去写写看。

代码

Java

class Solution {
    // QuickSort3Ways
    public void sortColors(int[] nums) {
        // 左指针: 填充0
        int left = 0;
        // 右指针: 填充2
        int right = nums.length - 1;
        // 遍历指针
        int traverse = 0;
        while (traverse <= right) {
            if (nums[traverse] == 0) {
                // 交换1, left右移, traverse右移
                nums[traverse++] = nums[left];
                nums[left++] = 0;
            } else if (nums[traverse] == 2) {
                // 交换2, right左移, traverse不动
                int tmp = nums[right];
                nums[right--] = nums[traverse];
                nums[traverse] = tmp;
            } else {
                // 遇到1, 不做任何操作
                traverse++;
            }
        }
    }
}

C++

class Solution {
public:
    void sortColors(vector<int> &nums) {
        int left = 0;
        int right = nums.size() - 1;
        int i = 0;
        while (i <= right) {
            if (nums[i] == 0) {
                nums[i++] = nums[left];
                nums[left++] = 0;
            } else if (nums[i] == 2) {
                int temp = nums[right];
                nums[right--] = nums[i];
                nums[i] = temp;
            } else {
                i++;
            }
        }
    }
};

时间复杂度:O(n)O(n)

空间复杂度:O(1)O(1)

知识点🌓

复习一下「快速排序」!

快排(一)

快速排序 (一) 的思想:

  • 从数组中挑出一个基准元素
  • ⭐在范围内,把 (从左到右) 第一个比基准值大的元素与 (从右到左) 第一个比基准值小的元素交换,重复进行;排完后将基准元素与数组中间位置的元素交换,实现基准元素 base 左边都小与 base,右边都大于 base
  • 递归地把左右分区再进行如上排序
// 快排(一)
public static void quickSort(int[] data) {
    subSort(data, 0, data.length - 1);
}

private static void subSort(int[] data, int start, int end) {
    if (start < end) {
        int base = data[start];
        int low = start;
        int high = end + 1;
        while (true) {
            while (low < end && data[++low] - base <= 0)
                ;
            while (high > start && data[--high] - base >= 0)
                ;
            if (low < high) {
                swap(data, low, high);
            } else {
                break;
            }
        }
        swap(data, start, high);

        subSort(data, start, high - 1);
        subSort(data, high + 1, end);
    }
}

private static void swap(int[] data, int i, int j) {
    int temp = data[i];
    data[i] = data[j];
    data[j] = temp;
}

快排(二)

快速排序 (二) 的思想:

  • 从数组中挑出一个基准元素
  • ⭐在指定范围内,维护一个有效数组存放比基准元素小的元素,遍历完指定范围数组后,再将基准元素替换到中间位置(此时右边也都比基准元素大)
  • 递归地把左右分区再进行如上排序
// 快排(二)
public void quickSort2(int[] nums, int leftBound, int rightBound) {
    if (leftBound < rightBound) {
        int partitionIndex = partition(nums, leftBound, rightBound);
        quickSort2(nums, leftBound, partitionIndex - 1);
        quickSort2(nums, partitionIndex + 1, rightBound);
    }
}

// 排序后返回基准值
private int partition(int[] nums, int left, int right) {
    int pivot = left;
    int index = pivot + 1;
    for (int i = index; i <= right; i++) {
        if (nums[i] < nums[pivot]) {
            swap(nums, i, index);
            // 有效数组的末尾指针后移
            index++;
        }
    }
    swap(nums, pivot, index - 1);
    // 返回基准值
    return index - 1;
}

private void swap(int[] nums, int i, int j) {
    int temp = nums[i];
    nums[i] = nums[j];
    nums[j] = temp;
}

最后🌅

该篇文章为 「LeetCode」 系列的 No.26 篇,在这个系列文章中:

  • 尽量给出多种解题思路
  • 提供题解的多语言代码实现
  • 记录该题涉及的知识点

👨‍💻争取做到 LeetCode 每日 1 题,所有的题解/代码均收录于 「LeetCode」 仓库,欢迎随时加入我们的刷题小分队!