完美洗牌问题

完美洗牌问题

1.问题引入

给定一个长度为偶数的数组arr,长度记为2*N。前N个为左部分,后N个为右部分。 arr就可以表示为{L1,L2,…,Ln,R1,R2,…,Rn},请将数组调整成{R1,L1,R2,L2,…,Rn,Ln}的样子。要求空间复杂度O(1),时间复杂度尽量低

扩展问题:给定数组arr,将其最后变为[a,b,c,d,e,f],其中满足a<=b,b>=c,c<=d,d>=e…的排序

2.思路

通过小数量级的例子,来观察下标的变化

例1:

index 	 1 2 3 4 //注意这里下标我们默认从1开始
arr =	 a b c d
target=  c a d b       
元素初始位置变换后的位置
a12
b24
c31
d43

例2:

index =  1 2 3 4 5 6
 arr  =  a b c d e f 
target=  d a e b f c 
元素初始位置变换后的位置
a12
b24
c36
d41
e53
f65

通过上面的规律我们不难总结下标变换的规律:

当1<=index<=len/2 index->2*index

当len/2<index<=n index->2*(index-len/2)-1

所以我们只需要依次遍历数组中的每个位置,并且通过上述的计算公式得出其目标位置即可,但是题目要求额外空间复杂度为O(1),所以我们就不能额外创建一个O(n)的空间来存放数组;通过上面的两种情况我们不难发现:

例1,存在一个环1-2-4-3-1。例2,存在两个环1-2-4-1 3-6-5-3;就说明对于一个arr,肯定存在有限的几个环,我们只需要依次处理这几个环,把元素放入正确的位置。

3.归纳

对于一个数组长度M=(3k)-1,他的所有环的出发点{1,3,9,…,3(k-1)};所以对于一个长度为n(偶数)的数组,我们总是可以将其剥分为若干个长度为(3^k)-1,然后依次对这些数组进行位置的调整。

例:

n=12 arr={L1,L2,L3,L4,L5,L6,R1,R2,R3,R4,R5,R6}
我们寻找长度小于等于12,并且满足(3^k)-1<=12的最大的k,
  1.可以算出k=2,即我们先解决index[0...8]这些位置的数字,即这一步我们先将数组变为
	{[L1,L2,L3,L4,R1,R2,R3,R4],L5,L6,R5,R6}->{R1,L1,R2,L2,R3,L3,R4,L4,L5,L6,R5,R6}
	2.接着寻找k解决L5L6R5R6部分,可知k=1...

4.代码

import java.util.Arrays;

public class ShuffleProblem {

    /**
     * 数组下标从1开始
     *
     * @param i   调整前的位置
     * @param len 数组的长度
     * @return
     */
    public static int modifyIndex(int i, int len) {
        if (i <= len / 2) {
            return 2 * i;
        } else {
            return 2 * (i - (len / 2)) - 1;
        }
    }

    /**
     * 对数组arr[L...R]进行逆序
     *
     * @param arr
     * @param L
     * @param R
     */
    public static void reverse(int[] arr, int L, int R) {
        while (L < R) {
            int tmp = arr[L];
            arr[L++] = arr[R];
            arr[R--] = tmp;
        }
    }

    /**
     * [L..M]为左半部分,[M+1...R]为右半部分,左右两部分交换
     *
     * @param arr
     * @param L
     * @param M
     * @param R
     */
    public static void rotate(int[] arr, int L, int M, int R) {
        reverse(arr, L, M);
        reverse(arr, M + 1, R);
        reverse(arr, L, R);
    }

    /**
     * 主函数
     *
     * @param arr
     */
    public static void shuffle(int[] arr) {
        if (arr != null && arr.length != 0 && (arr.length & 1) == 0) {
            shuffle(arr, 0, arr.length - 1);
        }
    }

    /**
     * 在[L...R]做完美洗牌
     *
     * @param arr
     * @param L
     * @param R
     */
    public static void shuffle(int[] arr, int L, int R) {
        while (R - L + 1 > 0) { //切成一块一块的,每一块长度满足(3^k)-1
            int len = R - L + 1;
            int base = 3;
            int k = 1;
            //计算小于等于len,并且是离len最近的,满足(3^k)-1的数
            //也就是找到最大的k,满足3^k<=len+1
            while (base < (len + 1) / 3) {
                base *= 3;
                k++;
            }
            //当前要解决数组[0...base-1]的部分
            int half = (base - 1) / 2;
            //找到[L...R]中点位置
            int mid = (L + R) / 2;
            //此时要旋转的部分为[L+half...mid] [mid+1...mid+half]
            //此时下标是从0开始
            rotate(arr, L + half, mid, mid + half);
            cycles(arr, L, base - 1, k);
            L = L + base - 1;
        }
    }

    /**
     * 从start位置开始,往右len的长度这一段,做下标连续推
     * 出发的位置一次为1,3,9...
     *
     * @param arr
     * @param start
     * @param len
     * @param k
     */
    public static void cycles(int[] arr, int start, int len, int k) {
        //i 一共有多少个环需要循环
        //trigger 环的相对初始位置
        //下标从0开始
        for (int i = 0, trigger = 1; i < k; i++, trigger *= 3) {
            int preValue = arr[start + trigger - 1];
            int cur = modifyIndex(trigger, len);
            while (cur != trigger) {
                int tmp = arr[start + cur - 1];
                arr[start + cur - 1] = preValue;
                preValue = tmp;
                cur = modifyIndex(cur, len);
            }
            arr[cur + start - 1] = preValue;
        }
    }

    public static void wiggleSort(int[] arr) {
        if (arr == null || arr.length == 0) {
            return;
        }
        Arrays.sort(arr);
        if ((arr.length & 1) == 1) {
            shuffle(arr, 1, arr.length - 1);
        } else {
            shuffle(arr, 0, arr.length - 1);
            for (int i = 0; i < arr.length; i += 2) {
                int tmp = arr[i];
                arr[i] = arr[i + 1];
                arr[i + 1] = tmp;
            }
        }
    }


    public static void main(String[] args) {
        int[] arr = new int[]{2, 4, 6, 8, 1, 3, 5, 7};
        shuffle(arr);
        System.out.println(Arrays.toString(arr));
    }

}

扩展问题也是基于洗牌的思想,大家可以自信思考,欢迎评论区讨论!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值