在问 · 算法题解

324. 摆动排序 II

中等贪心排序 + 两半倒序穿插力扣原题 ↗

题目描述

输入一个整数数组 nums。

重新排列它的元素,使相邻元素严格地一小一大交替出现:nums[0] < nums[1],nums[1] > nums[2],nums[2] < nums[3],依此类推。直接在原数组上修改,函数无需返回值。

题目保证一定能排出这样的结果。符合要求的排法可能不止一种,给出任意一种都算正确。

示例 1
输入:nums = [1,5,1,1,6,4]
输出:[1,6,1,5,1,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]

解释:2 < 3 > 1 < 3 > 1 < 2。

数据范围

  • 1 <= nums.length <= 5 * 10^4
  • 0 <= nums[i] <= 5000
  • 输入保证存在符合要求的排列。

进阶:能否在 O(n) 时间内完成,或者只使用 O(1) 额外空间原地完成?

解题思路:排序 + 两半倒序穿插

先排序,把数组分成较小的前半段(长度 (n+1)//2)和较大的后半段。偶数位从前半段的末尾往前取,奇数位从后半段的末尾往前取。倒序取是关键:中位数附近的重复值会被分到两端去,不会相邻。

为什么正确

偶数位取得的值来自小半段,奇数位来自大半段;两个指针 j、k 同步从大到小移动,所以每个奇数位都大于两侧的偶数位。

复杂度分析

时间 O(n log n),空间 O(n)。

易错点

如果正序交替取(小、大、小、大),中间的重复值可能恰好相邻,例如 [4,5,5,6] 会得到 [4,5,5,6] 而不满足条件。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def wiggleSort(self, nums: List[int]) -> None:
        arr = sorted(nums)
        n = len(nums)
        mid = (n + 1) // 2
        j = mid - 1
        k = n - 1
        for i in range(n):
            if i % 2 == 0:
                nums[i] = arr[j]
                j -= 1
            else:
                nums[i] = arr[k]
                k -= 1

载入编辑器,自己跑一遍 →

其他解法

排序把所有元素排好,简单可靠,O(n log n);快速选择只找中位数并做三路划分,保留穿插所需的最少顺序信息,平均 O(n)。两者最后的倒序穿插步骤完全相同。

解法 2:快速选择中位数 + 三路划分 时间 平均 O(n),最坏 O(n²),空间 O(n)

穿插并不需要完全有序,只需知道中位数 m 并按“小于 m / 等于 m / 大于 m”分成三段。先用快速选择找到第 (n-1)/2 小的元素 m,再对副本做荷兰国旗三路划分,得到“小 | 等 | 大”的数组。最后和主解法一样:偶数位倒序取前半部分,奇数位倒序取后半部分。倒序保证两侧等于 m 的元素分别落在数组两端,不会相邻。

平均时间降到 O(n),也说明了本题真正依赖的只是中位数划分。实现比直接排序复杂得多。这里取区间中间元素作枢轴,最坏情况 O(n²),可改为随机枢轴。若要达到 O(1) 空间,还需要用“虚拟下标”原地划分。

class Solution:
    def wiggleSort(self, nums: List[int]) -> None:
        n = len(nums)
        arr = nums[:]
        k = (n - 1) // 2
        lo, hi = 0, n - 1
        while True:
            pivot = arr[(lo + hi) // 2]
            lt, i, gt = lo, lo, hi
            while i <= gt:
                if arr[i] < pivot:
                    arr[lt], arr[i] = arr[i], arr[lt]
                    lt += 1
                    i += 1
                elif arr[i] > pivot:
                    arr[gt], arr[i] = arr[i], arr[gt]
                    gt -= 1
                else:
                    i += 1
            if k < lt:
                hi = lt - 1
            elif k > gt:
                lo = gt + 1
            else:
                m = pivot
                break
        lt, i, gt = 0, 0, n - 1
        while i <= gt:
            if arr[i] < m:
                arr[lt], arr[i] = arr[i], arr[lt]
                lt += 1
                i += 1
            elif arr[i] > m:
                arr[gt], arr[i] = arr[i], arr[gt]
                gt -= 1
            else:
                i += 1
        j, r = (n + 1) // 2 - 1, n - 1
        for i in range(n):
            if i % 2 == 0:
                nums[i] = arr[j]
                j -= 1
            else:
                nums[i] = arr[r]
                r -= 1

贪心 专题的其他题目

查看 贪心 全部题目 →