algorithm-base/animation-simulation/数据结构和算法/荷兰国旗.md
2021-03-20 15:58:25 +08:00

147 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

今天我们一起来看一下可以用快速排序秒杀的经典题,或许这些题目大家已经做过,不过可以再来一起复习一遍,加深印象。
阅读这篇文章之前,大家要先去看一下我之前写过的快速排序,这样才不会对这篇文章一知半解。好啦,我们一起开整吧。
在上篇文章中,我们提到了,快速排序的优化,利用三向切分来优化数组中存在大量重复元素的情况,到啦这里各位应该猜到我想写什么了吧,
我们今天先来说一下那个非常经典的荷兰国旗问题。
> 题目来源https://www.jianshu.com/p/356604b8903f
问题描述:
荷兰国旗是由红白蓝3种颜色的条纹拼接而成如下图所示
![荷兰国旗](https://cdn.jsdelivr.net/gh/tan45du/test@master/photo/微信截图_20210305145819.4jrud8f8xny0.png)
假设这样的条纹有多条,且各种颜色的数量不一,并且随机组成了一个新的图形,新的图形可能如下图所示,但是绝非只有这一种情况:
![荷兰国旗问题](https://cdn.jsdelivr.net/gh/tan45du/test@master/photo/7789414-8baf85cac6228621.62ygbgv09ek0.png)
需求是:把这些条纹按照颜色排好,红色的在上半部分,白色的在中间部分,蓝色的在下半部分,我们把这类问题称作荷兰国旗问题。
我们把荷兰国旗问题用数组的形式表达一下是这样的:
给定一个整数数组给定一个值K这个值在原数组中一定存在要求把数组中小于 K 的元素放到数组的左边大于K的元素放到数组的右边等于K的元素放到数组的中间最终返回一个整数数组其中只有两个值分别是等于K的数组部分的左右两个下标值。
例如,给定数组:[2, 3, 1, 9, 7, 6, 1, 4, 5]给定一个值4那么经过处理原数组可能得一种情况是[2, 3, 1, 1, 4, 9, 7, 6, 5]需要注意的是小于4的部分不需要有序大于4的部分也不需要有序返回等于4部分的左右两个下标即[4, 4]
这不就是我们之前说过的三向切分吗?一模一样!
那么 leetcode 有没有相似问题呢?我们一起来看下面这道题
**leetcode 75 颜色分类**
题目描述:
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
示例 1
> 输入nums = [2,0,2,1,1,0]
> 输出:[0,0,1,1,2,2]
示例 2
> 输入nums = [2,0,1]
> 输出:[0,1,2]
示例 3
> 输入nums = [0]
> 输出:[0]
示例 4
> 输入nums = [1]
> 输出:[1]
**做题思路**
这个题目我们使用 Arrays.sort() 解决,哈哈,但是那样太无聊啦,题目含义就是让我们将所有的 0 放在前面2放在后面1 放在中间,是不是和我们上面说的荷兰国旗问题一样。我们仅仅将 1 做为 pivot 值。
下面我们直接看代码吧,和三向切分基本一致。
```java
class Solution {
public void sortColors(int[] nums) {
int len = nums.length;
int left = 0;
//这里和三向切分不完全一致
int i = left;
int right = len-1;
while (i <= right) {
if (nums[i] == 2) {
swap(nums,i,right--);
} else if (nums[i] == 0) {
swap(nums,i++,left++);
} else {
i++;
}
}
}
public void swap (int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
```
另外我们看这段代码,有什么问题呢?那就是我们即使完全符合时,仍会交换元素,这样会大大降低我们的效率。
例如:[0,0,0,1,1,1,2,2,2]
此时我们完全符合情况不需要交换元素但是按照我们上面的代码0,2 的每个元素会和自己进行交换,所以这里我们可以根据 i 和 left 的值是否相等来决定是否需要交换,大家可以自己写一下。
下面我们看一下另外一种写法
这个题目的关键点就是,当我们 nums[i] 和 nums[right] 交换后,我们的 nums[right] 此时指向的元素是符合要求的,但是我们 nums[i] 指向的元素不一定符合要求,所以我们需要继续判断。
![细节地方](https://cdn.jsdelivr.net/gh/tan45du/test@master/photo/微信截图_20210305153911.28capmzljy80.png)
我们 2 和 0 交换后,此时 i 指向 0 0 应放在头部,所以不符合情况,所以 0 和 1 仍需要交换。下面我们来看一下动画来加深理解吧。
![](https://img-blog.csdnimg.cn/20210318093047325.gif#pic_center)
另一种代码表示
```java
class Solution {
public void sortColors(int[] nums) {
int left = 0;
int len = nums.length;
int right = len - 1;
for (int i = 0; i <= right; ++i) {
if (nums[i] == 0) {
swap(nums,i,left);
left++;
}
if (nums[i] == 2) {
swap(nums,i,right);
right--;
//如果不等于 1 则需要继续判断,所以不移动 i 指针i--
if (nums[i] != 1) {
i--;
}
}
}
}
public void swap (int[] nums,int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
```
好啦,这个问题到这就结束啦,是不是很简单啊,我们明天见!