題意:給定一個降序的正數數組,要求按【最小、最大、次小、次大…】的順序重新排序。期望的時間復雜度為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