🌕🌕🌗 324. 摆动排序 II

吞佛童子2022年10月10日
  • algorithm
  • Array
  • 变形快排
大约 3 分钟

🌕🌕🌗 324. 摆动排序 II

难度: 🌕🌕🌗

问题描述

img_3.png


解法 1

class Solution {
    public void wiggleSort(int[] nums) {
        // 思路:
        // 数组排序,针对排序后的数组,进行插入
        // 如何插入保证生成摆动序列?
        // 若数组无重复元素,那么 [0, 1, 2, 3, 4] 可以 [mid] 划分为 [left, mid] & [mid + 1, right] 两部分
        // 左半区间长度 >= 右半区间
        // 按照 {left, mid + 1, left + 1, mid + 2, ..., mid, right} 的顺序双指针依次后移交替插入
        // 但是,现在数组中可能存在重复元素,假设为 [0, 1, 1, 1, 4, 5] 此时若还按照传统顺序添加,则生成的序列为
        // {0, 1, 1, 4, 1, 5} 非摆动序列
        // 此时需要转换思路,若为逆序添加,即 {mid, right, mid - 1, right - 1, ..., left, mid + 1}
        // 生成序列为 {1, 5, 1, 4, 0, 1} 满足要求

        int[] clone = nums.clone(); // 浅拷贝
        Arrays.sort(clone); // 无返回数组,说明在 nums 的基础上直接操作,因此为防止生成新数组然后赋值到 nums,直接操作拷贝数组
        int len = nums.length;
        int mid = (len - 1) / 2; // 保证 奇数时 [0, len/2] ;偶数时,[0, len/2 - 1]

        int left = mid;
        int right = len - 1; // 逆序插入
        int index = 0;
        while(index < len) {
            nums[index] = clone[left];
            left --;
            index ++;
            if(right >= mid + 1) { // if 里面也可以用 index < len 代替
                nums[index] = clone[right];
                right --;
                index ++; // 由于 right 区间长度较短,因此左半区间肯定不会超范,右半区间可能超范
            }
        }
    }
}

输出 1

img_2.png


解法 2 - 变形快排

class Solution {
    public void wiggleSort(int[] nums) {
        // 思路:
        // 我们无需对数组进行完全排序,只需要找到中位数,将其拆分为 [0, mid - 1], [mid, ..., mid], [mid + 1, right] 三部分
        // [mid] 集合中可能存在多个相同的 mid
        // 因此快排后,还需要将 [mid] 尽可能插在 左半区间的右边 & 右半区间的左边
        // 然后双指针逆序插入
        int len = nums.length;
        int mid = (len - 1) / 2; // mid 偏左,但是 mid - left + 1 导致左半区间略大于右半区间
        int[] array = nums.clone();

        // 快排
        mySol(array, 0, len - 1, mid);
        // System.out.println(Arrays.toString(array));
        // 获取中位数的值
        int val = array[mid];
        // 遍历左半区间,将 val 全部放到左半区间的右边
        int i = 0;
        int j = mid - 1;
        while(i < j) {
            if(array[i] == val) {
                // 和最右边元素交换
                swap(array, i, j);
                j --; // 原来的 [j] 已经交换为 val,更新 j
            } else {
                i ++;
            }
        }
        i = mid + 1;
        j = len - 1;
        while(i < j) {
            if(array[j] == val) {
                // 和最左元素交换
                swap(array, i, j);
                i ++;
            } else {
                j --;
            }
        }

        // 双指针逆序拆入,形成 res
        i = mid;
        j = len - 1;
        int index = 0;
        while(index < len) {
            nums[index] = array[i];
            i --;
            index ++;
            if(index < len) {
                nums[index] = array[j];
                j --;
                index ++;
            }
        }
    }

    private void mySol(int[] array, int left, int right, int index) {
        // 递归终止条件
        if(left >= right) {
            return; 
        }
        int monitor = array[left]; // 基准值
        int i = left;
        int j = right;
        while(i < j) {
            while(i < j && array[j] >= monitor) {
                j --; // 需要先进行 j -- 而后 i ++
            }
            while(i < j && array[i] <= monitor) {
                i ++;
            }
            swap(array, i, j);
        }
        // i == j
        swap(array, left, i); // 将基准值交换到中间
        // 与标准快排的区别就在于,每次排序最多只选择排序一边,从而复杂度降低
        if(index == i) {
            return;
        } else if(index > i) {
            mySol(array, i + 1, right, index); // 快排右边
        } else {
            mySol(array, left, i - 1, index);
        }
    }

    private void swap(int[] array, int a, int b) {
        int tmp = array[a];
        array[a] = array[b];
        array[b] = tmp;
    }
}

输出 2

img_4.png

上次编辑于: 2022/10/10 下午8:43:48
贡献者: liuxianzhishou