阿里巴巴面试题|完美洗牌问题

题意:给定一个降序的正数数组,要求按【最小、最大、次小、次大…】的顺序重新排序。期望的时间复杂度为O(n),空间复杂度为O(1),即不能申请额外数组。例如:输入【7,6,5,4,3,2,1】输出【1,7,2,6,3,5,4】

分析:

首先,计算每个元素要挪到哪里感觉很简单,目测是这样:
1. 旧位置i < 2/n:新位置j=2 * i +1
2. 旧位置i >= 2/n: 新位置j = 2 * (n - 1 - i)

然后就是沿链轮换了。本例中 a[0]=7应该被移到a[1], a[1]本来的数6应该被移到a[3], a[3]本来的数4应该被移到a[6]... 最后发现,把input数组变成output数组, 需要三组轮换:
1. a[0]->a[1]->a[3]->a[6]->a[0]
2. a[2]->a[5]-a[2]
3. a[4]位置不变
完成每个轮换只需要一个额外的变量,腾出一个数组位置。因此空间复杂度O(1)。每个元素只会被挪动一次,时间复杂度O(n)。
此外还需要标记哪些元素已经被移动过了。通常另开一个数组标记会比较方便。。但是这题不允许,所以可以把每个移动过的元素都都取负作为标记,之后再还原。(也可以用其他的标记方式)

这也对应一个群论中的结论:置换可以分解成若干个不相交的轮换。

代码

#include <stdio.h>

int trans(int len, int i) {
    if(len % 2 == 0) {
        if(i < len / 2) return i * 2 + 1;
        else return (len - i - 1) * 2;
    } else {
        if(i < len / 2) return i * 2 + 1;
        else if(i > len / 2) return (len - i - 1) * 2;
        else return len - 1;
    }
}

void swap(int *a, int *b) {
    *a ^= *b;
    *b ^= *a;
    *a ^= *b;
}

int main(void) {
    int arr[] = {7, 6, 5, 4, 3, 2, 1};

    //start
    int len = sizeof(arr) / sizeof(*arr);
    for(int i = 0; i < len; i++) {
        if(arr[i] > 0) {
            int j = i, tmp = arr[i];
            while(arr[j = trans(len, j)] > 0) {
                swap(&tmp, &arr[j]);    // 每次与前一个交换
                arr[j] = 0 - arr[j];
            }
        }
    }
    for(int i = 0; i < len; i++) {
        arr[i] = 0 - arr[i];
    }

    //print
    for(int i = 0; i < len; i++) {
        printf("%d ", arr[i]);
    }
}

这个问题其实是一个经典问题,

完美洗牌问题:

玩过扑克牌的朋友都知道,在一局完了之后洗牌,洗牌人会习惯性的把整副牌大致分为两半,两手各拿一半对着对着交叉洗牌。

2004年,microsoft 的 Peiyush Jain 在他发表一篇名为:“A Simple In-Place Algorithm for In-Shuffle” 的论文中提出了完美洗牌算法。

什么是完美洗牌问题呢?即给定一个数组

a1,a2,a3, …, an, b1, b2, b3, ..., bn

最终把它置换成

b1, a1, b2, a2, a3, b3,…, bn, an

这个完美洗牌问题本质上与本题完全一致的,可以在O(n)内改成形式相同。



参考链接:

1. https://www.zhihu.com/question/50512830/answer/121386043

2. https://www.zhihu.com/question/50512830/answer/121334031

3. https://www.jianshu.com/p/9c841ad88ded

posted @ 2020-03-23 09:13  Rogn  阅读(859)  评论(0编辑  收藏  举报