位运算题目:循环码排列
文章目录
- 题目
- 标题和出处
- 难度
- 题目描述
- 要求
- 示例
- 数据范围
- 前言
- 解法一
- 思路和算法
- 代码
- 复杂度分析
- 解法二
- 思路和算法
- 证明
- 代码
- 复杂度分析
题目
标题和出处
标题:循环码排列
出处:1238. 循环码排列
难度
5 级
题目描述
要求
给定两个整数 n \texttt{n} n 和 start \texttt{start} start,返回 (0,1,2, … ,2 n − 1) \texttt{(0,1,2,} \ldots \texttt{,2}^\texttt{n} - \texttt{1)} (0,1,2,…,2n−1) 的任意排列 p \texttt{p} p,满足以下条件:
- p[0] = start \texttt{p[0]} = \texttt{start} p[0]=start
- p[i] \texttt{p[i]} p[i] 和 p[i + 1] \texttt{p[i} + \texttt{1]} p[i+1] 的二进制表示形式只有一位不同。
- p[0] \texttt{p[0]} p[0] 和 p[2 n − 1] \texttt{p[2}^\texttt{n} - \texttt{1]} p[2n−1] 的二进制表示形式也只有一位不同。
示例
示例 1:
输入: n = 2, start = 3 \texttt{n = 2, start = 3} n = 2, start = 3
输出: [3,2,0,1] \texttt{[3,2,0,1]} [3,2,0,1]
解释:这个排列的二进制表示是 (11,10,00,01) \texttt{(11,10,00,01)} (11,10,00,01)。所有的相邻元素都有一位是不同的,另一个有效的排列是 [3,1,0,2] \texttt{[3,1,0,2]} [3,1,0,2]。
示例 2:
输出: n = 3, start = 2 \texttt{n = 3, start = 2} n = 3, start = 2
输出: [2,6,7,5,4,0,1,3] \texttt{[2,6,7,5,4,0,1,3]} [2,6,7,5,4,0,1,3]
解释:这个排列的二进制表示是 (010,110,111,101,100,000,001,011) \texttt{(010,110,111,101,100,000,001,011)} (010,110,111,101,100,000,001,011)。
数据范围
- 1 ≤ n ≤ 16 \texttt{1} \le \texttt{n} \le \texttt{16} 1≤n≤16
- 0 ≤ start < 2 n \texttt{0} \le \texttt{start} < \texttt{2}^\texttt{n} 0≤start<2n
前言
这道题要求对于给定的整数 n n n 生成包含 0 0 0 到 2 n − 1 2^n - 1 2n−1 的排列,要求排列中的任意两个相邻元素之间以及首个整数和最后一个整数之间都满足二进制表示只有一位不同,该性质与格雷码序列的性质相同。因此,这道题的本质是计算 n n n 位格雷码序列。
由于这道题还指定了排列的首个整数 start \textit{start} start,因此需要从 start \textit{start} start 开始按格雷码序列的顺序遍历每个整数,得到有效的排列。
可以使用「格雷编码」的做法得到 n n n 位格雷码序列,然后根据排列的首个整数 start \textit{start} start 得到有效的排列。也可以利用格雷码序列的性质,一步生成有效的排列。
解法一
思路和算法
用 G n G_n Gn 表示 n n n 位格雷码序列,其中 n ≥ 0 n \ge 0 n≥0, G n G_n Gn 由 2 n 2^n 2n 个数组成,规定 G 0 = [ 0 ] G_0 = [0] G0=[0]。为了使格雷码序列的结果唯一,当 n > 0 n > 0 n>0 时规定 G n [ 1 ] = 1 G_n[1] = 1 Gn[1]=1。
对于 k ≥ 0 k \ge 0 k≥0,假设 k k k 位格雷码序列 G k G_k Gk 已知,其长度是 2 k 2^k 2k。根据 G k G_k Gk 可以生成 k + 1 k + 1 k+1 位格雷码序列 G k + 1 G_{k + 1} Gk+1,做法如下。
-
对于 0 ≤ i < 2 k 0 \le i < 2^k 0≤i<2k, G k + 1 [ i ] = G k [ i ] G_{k + 1}[i] = G_k[i] Gk+1[i]=Gk[i]。
-
对于 2 k ≤ i < 2 k + 1 2^k \le i < 2^{k + 1} 2k≤i<2k+1, G k + 1 [ i ] = G k [ 2 k + 1 − 1 − i ] + 2 k = G k + 1 [ 2 k + 1 − 1 − i ] + 2 k G_{k + 1}[i] = G_k[2^{k + 1} - 1 - i] + 2^k = G_{k + 1}[2^{k + 1} - 1 - i] + 2^k Gk+1[i]=Gk[2k+1−1−i]+2k=Gk+1[2k+1−1−i]+2k。
根据上述做法,从 G 0 G_0 G0 开始按照位数递增的顺序依次生成格雷码序列,直到得到 n n n 位格雷码序列 G n G_n Gn。
由于排列的首个整数 start \textit{start} start 满足 0 ≤ start < 2 n 0 \le \textit{start} < 2^n 0≤start<2n,因此在 G n G_n Gn 中一定包含 start \textit{start} start。在生成 G n G_n Gn 的过程中得到 start \textit{start} start 的下标 startIndex \textit{startIndex} startIndex,当得到完整的 G n G_n Gn 之后,从下标 startIndex \textit{startIndex} startIndex 开始遍历每个整数,当遍历 2 n 2^n 2n 个整数之后即可得到有效的排列。
代码
class Solution {public List<Integer> circularPermutation(int n, int start) {List<Integer> gray = new ArrayList<Integer>();gray.add(0);int startIndex = 0;int index = 1;int count = 1;for (int i = 1; i <= n; i++, count *= 2) {for (int j = count - 1; j >= 0; j--, index++) {int curr = gray.get(j) | count;gray.add(gray.get(j) | count);if (curr == start) {startIndex = index;}}}List<Integer> permutation = new ArrayList<Integer>();for (int i = 0, j = startIndex; i < count; i++, j = (j + 1) % count) {permutation.add(gray.get(j));}return permutation;}
}
复杂度分析
-
时间复杂度: O ( 2 n ) O(2^n) O(2n),其中 n n n 是给定的整数。当格雷码序列的位数是 n n n 时共有 2 n 2^n 2n 个数,生成每个数的时间都是 O ( 1 ) O(1) O(1),生成格雷码序列之后需要从 start \textit{start} start 开始遍历每个数得到排列,因此时间复杂度是 O ( 2 n ) O(2^n) O(2n)。
-
空间复杂度: O ( 2 n ) O(2^n) O(2n),其中 n n n 是给定的整数。格雷码序列需要 O ( 2 n ) O(2^n) O(2n) 的空间。
解法二
思路和算法
解法一需要首先生成格雷码序列,然后遍历生成的格雷码序列得到有效的排列。有一种更快的做法,不需要首先生成格雷码序列,而是可以直接生成有效的排列。
对于给定的整数 n n n,当 0 ≤ i < 2 n 0 \le i < 2^n 0≤i<2n 时, i i i 的格雷码是 G n [ i ] = ( i > > 1 ) ⊕ i G_n[i] = (i >> 1) \oplus i Gn[i]=(i>>1)⊕i,格雷码序列的首个整数是 0 0 0。为了得到首个整数是 start \textit{start} start 的排列,只要将格雷码序列中的每个整数与 start \textit{start} start 做按位异或运算即可,即 p [ i ] = ( i > > 1 ) ⊕ i ⊕ start p[i] = (i >> 1) \oplus i \oplus \textit{start} p[i]=(i>>1)⊕i⊕start。
使用该方法即可一步生成有效的排列。
证明
考虑 n n n 位格雷码序列 G n G_n Gn,满足 G n [ 0 ] G_n[0] Gn[0] 和 G n [ 2 n − 1 ] G_n[2^n - 1] Gn[2n−1] 的二进制表示恰好有一位不同且对于任意 0 ≤ i < 2 n − 1 0 \le i < 2^n - 1 0≤i<2n−1 都有 G n [ i ] G_n[i] Gn[i] 和 G n [ i + 1 ] G_n[i + 1] Gn[i+1] 的二进制表示恰好有一位不同。
考虑 G n G_n Gn 中的两个相邻整数或者首尾整数 G n [ j ] G_n[j] Gn[j] 和 G n [ k ] G_n[k] Gn[k],满足 G n [ j ] G_n[j] Gn[j] 和 G n [ k ] G_n[k] Gn[k] 的二进制表示恰好有一位不同。对于排列 p p p,有 p [ j ] = G n [ j ] ⊕ start p[j] = G_n[j] \oplus \textit{start} p[j]=Gn[j]⊕start 和 p [ k ] = G n [ k ] ⊕ start p[k] = G_n[k] \oplus \textit{start} p[k]=Gn[k]⊕start。当 0 ≤ t < n 0 \le t < n 0≤t<n 时,用 b ( t ) b(t) b(t) 表示整数 b b b 的二进制表示的从低到高的第 t t t 位,则 p [ j ] ( t ) = G n [ j ] ( t ) ⊕ start ( t ) p[j](t) = G_n[j](t) \oplus \textit{start}(t) p[j](t)=Gn[j](t)⊕start(t), p [ k ] ( t ) = G n [ k ] ( t ) ⊕ start ( t ) p[k](t) = G_n[k](t) \oplus \textit{start}(t) p[k](t)=Gn[k](t)⊕start(t),因此 G n [ j ] ( t ) = G n [ k ] ( t ) G_n[j](t) = G_n[k](t) Gn[j](t)=Gn[k](t) 等价于 p [ j ] ( t ) = p [ k ] ( t ) p[j](t) = p[k](t) p[j](t)=p[k](t), G n [ j ] ( t ) ≠ G n [ k ] ( t ) G_n[j](t) \ne G_n[k](t) Gn[j](t)=Gn[k](t) 等价于 p [ j ] ( t ) ≠ p [ k ] ( t ) p[j](t) \ne p[k](t) p[j](t)=p[k](t)。根据 G n [ j ] G_n[j] Gn[j] 和 G n [ k ] G_n[k] Gn[k] 的二进制表示恰好有一位不同可知, p [ j ] p[j] p[j] 和 p [ k ] p[k] p[k] 的二进制表示也恰好有一位不同。
因此, p p p 是有效的排列。
代码
class Solution {public List<Integer> circularPermutation(int n, int start) {List<Integer> permutation = new ArrayList<Integer>();int total = 1 << n;for (int i = 0; i < total; i++) {permutation.add((i >> 1) ^ i ^ start);}return permutation;}
}
复杂度分析
-
时间复杂度: O ( 2 n ) O(2^n) O(2n),其中 n n n 是给定的整数。排列共有 2 n 2^n 2n 个数,生成每个数的时间都是 O ( 1 ) O(1) O(1)。
-
空间复杂度: O ( 1 ) O(1) O(1)。注意返回值不计入空间复杂度。