/**
 * Given an unsorted array nums, reorder it such that nums[0] < nums[1] > nums[2] < nums[3]....
 * <p>
 * Example:
 * (1) Given nums = [1, 5, 1, 1, 6, 4], one possible answer is [1, 4, 1, 5, 1, 6].
 * (2) Given nums = [1, 3, 2, 2, 3, 1], one possible answer is [2, 3, 1, 3, 1, 2].
 * <p>
 * Note:
 * You may assume all input has valid answer.
 * <p>
 * Follow Up:
 * Can you do it in O(n) time and/or in-place with O(1) extra space?
 * <p>
 * Tags: Sort
 * Similar Problems: (M) Sort Colors, (M) Kth Largest Element in an Array, (M) Wiggle Sort
 */
public class WiggleSort2 {

    public void wiggleSort(int[] nums) {

    }

}

package com.freetymekiyan.algorithms.level.medium;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * Given an unsorted array nums, reorder it such that nums[0] < nums[1] > nums[2] < nums[3]....
 * <p>
 * Example:
 * (1) Given nums = [1, 5, 1, 1, 6, 4], one possible answer is [1, 4, 1, 5, 1, 6].
 * (2) Given nums = [1, 3, 2, 2, 3, 1], one possible answer is [2, 3, 1, 3, 1, 2].
 * <p>
 * Note:
 * You may assume all input has valid answer.
 * <p>
 * Follow Up:
 * Can you do it in O(n) time and/or in-place with O(1) extra space?
 * <p>
 * Tags: Sort
 * Similar Problems: (M) Sort Colors, (M) Kth Largest Element in an Array, (M) Wiggle Sort
 */
public class WiggleSort2 {

    private WiggleSort2 w;

    /**
     * Quick select + Three-way partition.
     * Find the median with quick select.
     * Then put smaller ones on even indices.
     * Put larger ones on odd indices.
     * https://discuss.leetcode.com/topic/41464/step-by-step-explanation-of-index-mapping-in-java/11
     */
    public void wiggleSort(int[] nums) {
        int n = nums.length;
        int median = quickSelect((n + 1) / 2, nums);
        int left = 0;
        int i = 0;
        int right = n - 1;
        while (i <= right) {
            if (nums[newIndex(i, n)] > median) {
                swap(nums, newIndex(left, n), newIndex(i, n));
                left++;
                i++;
            } else if (nums[newIndex(i, n)] < median) {
                swap(nums, newIndex(right, n), newIndex(i, n));
                right--;
            } else {
                i++;
            }
        }
    }

    /**
     * Create an index mapping.
     * [0, 1, 2, 3, 4, 5] -> [1, 3, 5, 0, 2, 4]
     * [0, 1, 2, 3, 4, 5, 6] -> [1, 3, 5, 0, 2, 4, 6]
     */
    private int newIndex(int i, int n) {
        return (2 * i + 1) % (n | 1);
    }

    /**
     * Return the ranking position of k in nums.
     */
    private int quickSelect(int k, int[] nums) {
        int l = 0;
        int r = nums.length - 1;
        while (l < r) {
            int pos = l + (r - l) / 2;
            pos = partition(nums, l, r, pos);
            if (pos == k) {
                return nums[pos];
            } else if (pos < k) {
                l = pos + 1;
            } else {
                r = pos - 1;
            }
        }
        return nums[l];
    }

    private int partition(int[] nums, int l, int r, int pivot) {
        int val = nums[pivot];
        swap(nums, pivot, r); // Move pivot to the end
        int storeIndex = l;
        for (int i = l; i < r; i++) {
            if (nums[i] < val) { // Move numbers smaller than pivot to the front
                swap(nums, storeIndex, i);
                storeIndex++;
            }
        }
        swap(nums, storeIndex, r);
        return l;
    }

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

    @Before
    public void setUp() {
        w = new WiggleSort2();
    }

    @Test
    public void testExamples() {
        int[] nums = {1, 2, 3, 4, 5, 6};
        System.out.println(w.quickSelect((nums.length) / 2, nums));
    }

    @After
    public void tearDown() {
        w = null;
    }

}

add Wiggle Sort 2