-
Notifications
You must be signed in to change notification settings - Fork 956
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
290f708
commit 88135a9
Showing
3 changed files
with
160 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
### 题目描述 | ||
|
||
这是 LeetCode 上的 **[324. 摆动排序 II](https://leetcode.cn/problems/wiggle-sort-ii/solution/by-ac_oier-22bq/)** ,难度为 **中等**。 | ||
|
||
Tag : 「构造」、「排序」、「快速选择」 | ||
|
||
|
||
|
||
给你一个整数数组 `nums`,将它重新排列成 `nums[0] < nums[1] > nums[2] < nums[3]...` 的顺序。 | ||
|
||
你可以假设所有输入数组都可以得到满足题目要求的结果。 | ||
|
||
示例 1: | ||
``` | ||
输入:nums = [1,5,1,1,6,4] | ||
输出:[1,6,1,5,1,4] | ||
解释:[1,4,1,5,1,6] 同样是符合题目要求的结果,可以被判题程序接受。 | ||
``` | ||
示例 2: | ||
``` | ||
输入:nums = [1,3,2,2,3,1] | ||
输出:[2,3,1,3,1,2] | ||
``` | ||
|
||
提示: | ||
* $1 <= nums.length <= 5 \times 10^4$ | ||
* $0 <= nums[i] <= 5000$ | ||
* 题目数据保证,对于给定的输入 `nums`,总能产生满足题目要求的结果 | ||
|
||
|
||
进阶:你能用 $O(n)$ 时间复杂度和 / 或原地 $O(1)$ 额外空间来实现吗? | ||
|
||
--- | ||
|
||
### 构造(快选 + 三数排序) | ||
|
||
这道题即使不考虑空间 $O(1)$ 的进阶要求,只要求做到 $O(n)$ 时间的话,在 LC 上也属于难题了。如果大家是第一次做,并且希望在有限时间(不超过 $20$ 分钟)内做出来,可以说是难上加难。 | ||
|
||
本质上,题目要我们实现一种构造方法,能够将 `nums` 调整为满足「摆动」要求。 | ||
|
||
具体的构造方法: | ||
|
||
1. 找到 `nums` 的中位数,这一步可以通过「快速选择」算法来做,时间复杂度为 $O(n)$,空间复杂度为 $O(\log{n})$,假设找到的中位数为 `x`; | ||
|
||
2. 根据 $nums[i]$ 与 `x` 的大小关系,将 $nums[i]$ 分为三类(小于/等于/大于),划分三类的操作可以采用「三数排序」的做法,复杂度为 $O(n)$。 | ||
|
||
这一步做完之后,我们的数组调整为:$[a_1, a_2, a_3, ... , b_1, b_2, b_3, ... , c_1, c_2, c_3]$ ,即分成「小于 `x` / 等于 `x` / 大于 `x`」三段。 | ||
|
||
3. 构造:先放「奇数」下标,再放「偶数」下标,放置方向都是「从左到右」(即可下标从小到大进行放置),放置的值是则是「从大到小」。 | ||
|
||
到这一步之前,我们使用到的空间上界是 $O(\log{n})$,如果对空间上界没有要求的话,我们可以简单对 `nums` 进行拷贝,然后按照对应逻辑进行放置即可,但这样最终的空间复杂度为 $O(n)$(代码见 $P2$);如果不希望影响到原有的空间上界,我们需要额外通过「找规律/数学」的方式,找到原下标和目标下标的映射关系(函数 `getIdx` 中)。 | ||
|
||
容易证明该构造过程的正确性(即该构造过程必然能顺利进行):由于我们是按照值「从大到小」进行放置,如果构造出来的方案不合法,必然是相邻的两个值为相等(“应当递增实际递减”或者“应当递减实际递增”的情况已被「从大到小」进行放置所否决),而当相邻位置放置了相同的值,即存在某个奇数下标,以及其相邻的偶数下标都放置了相同的值,这等价于该值出现次数超过总个数的一半,这与「题目本身保证数据能够构造出摆动数组」所冲突。 | ||
|
||
代码: | ||
```Java | ||
class Solution { | ||
int[] nums; | ||
int n; | ||
int qselect(int l, int r, int k) { | ||
if (l == r) return nums[l]; | ||
int x = nums[l + r >> 1], i = l - 1, j = r + 1; | ||
while (i < j) { | ||
do i++; while (nums[i] < x); | ||
do j--; while (nums[j] > x); | ||
if (i < j) swap(i, j); | ||
} | ||
int cnt = j - l + 1; | ||
if (k <= cnt) return qselect(l, j, k); | ||
else return qselect(j + 1, r, k - cnt); | ||
} | ||
void swap(int a, int b) { | ||
int c = nums[a]; | ||
nums[a] = nums[b]; | ||
nums[b] = c; | ||
} | ||
int getIdx(int x) { | ||
return (2 * x + 1) % (n | 1); | ||
} | ||
public void wiggleSort(int[] _nums) { | ||
nums = _nums; | ||
n = nums.length; | ||
int x = qselect(0, n - 1, n + 1 >> 1); | ||
int l = 0, r = n - 1, loc = 0; | ||
while (loc <= r) { | ||
if (nums[getIdx(loc)] > x) swap(getIdx(loc++), getIdx(l++)); | ||
else if (nums[getIdx(loc)] < x) swap(getIdx(loc), getIdx(r--)); | ||
else loc++; | ||
} | ||
} | ||
} | ||
``` | ||
|
||
- | ||
|
||
```Java | ||
class Solution { | ||
int[] nums; | ||
int n; | ||
int qselect(int l, int r, int k) { | ||
if (l == r) return nums[l]; | ||
int x = nums[l + r >> 1], i = l - 1, j = r + 1; | ||
while (i < j) { | ||
do i++; while (nums[i] < x); | ||
do j--; while (nums[j] > x); | ||
if (i < j) swap(i, j); | ||
} | ||
int cnt = j - l + 1; | ||
if (k <= cnt) return qselect(l, j, k); | ||
else return qselect(j + 1, r, k - cnt); | ||
} | ||
void swap(int a, int b) { | ||
int c = nums[a]; | ||
nums[a] = nums[b]; | ||
nums[b] = c; | ||
} | ||
public void wiggleSort(int[] _nums) { | ||
nums = _nums; | ||
n = nums.length; | ||
int x = qselect(0, n - 1, n + 1 >> 1); | ||
int l = 0, r = n - 1, loc = 0; | ||
while (loc <= r) { | ||
if (nums[loc] < x) swap(loc++, l++); | ||
else if (nums[loc] > x) swap(loc, r--); | ||
else loc++; | ||
} | ||
int[] clone = nums.clone(); | ||
int idx = 1; loc = n - 1; | ||
while (idx < n) { | ||
nums[idx] = clone[loc--]; | ||
idx += 2; | ||
} | ||
idx = 0; | ||
while (idx < n) { | ||
nums[idx] = clone[loc--]; | ||
idx += 2; | ||
} | ||
} | ||
} | ||
``` | ||
* 时间复杂度:快选的时间复杂度为 $O(n)$;三数排序复杂度为 $O(n)$。整体复杂度为 $O(n)$ | ||
* 空间复杂度:我的习惯是不算递归带来的额外空间消耗的,但如果是题目指定 $O(1)$ 空间的话,显然是不能按照习惯来,快选的空间复杂度为 $O(\log{n})$。整体复杂度为 $O(\log{n})$ | ||
|
||
--- | ||
|
||
### 最后 | ||
|
||
这是我们「刷穿 LeetCode」系列文章的第 `No.324` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 | ||
|
||
在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 | ||
|
||
为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 | ||
|
||
在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 | ||
|