当前位置: 首页 > java >正文

位运算题目:循环码排列

文章目录

  • 题目
    • 标题和出处
    • 难度
    • 题目描述
      • 要求
      • 示例
      • 数据范围
  • 前言
  • 解法一
    • 思路和算法
    • 代码
    • 复杂度分析
  • 解法二
    • 思路和算法
    • 证明
    • 代码
    • 复杂度分析

题目

标题和出处

标题:循环码排列

出处: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,,2n1) 的任意排列 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[2n1] 的二进制表示形式也只有一位不同。

示例

示例 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} 1n16
  • 0 ≤ start < 2 n \texttt{0} \le \texttt{start} < \texttt{2}^\texttt{n} 0start<2n

前言

这道题要求对于给定的整数 n n n 生成包含 0 0 0 2 n − 1 2^n - 1 2n1 的排列,要求排列中的任意两个相邻元素之间以及首个整数和最后一个整数之间都满足二进制表示只有一位不同,该性质与格雷码序列的性质相同。因此,这道题的本质是计算 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 n0 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 k0,假设 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 0i<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} 2ki<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+11i]+2k=Gk+1[2k+11i]+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 0start<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 0i<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)istart

使用该方法即可一步生成有效的排列。

证明

考虑 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[2n1] 的二进制表示恰好有一位不同且对于任意 0 ≤ i < 2 n − 1 0 \le i < 2^n - 1 0i<2n1 都有 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 0t<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)。注意返回值不计入空间复杂度。

http://www.xdnf.cn/news/904.html

相关文章:

  • Lesson 7 DNS域名解析服务器
  • Java秒杀功能-案例
  • jvm-获取方法签名的方法
  • 【uniapp-兼容性处理】安卓uView组件中u-input后置插槽不展示
  • 03-HTML常见元素
  • win10设置软件开机自启
  • 从0开始配置spark-local模式
  • 聊透多线程编程-线程互斥与同步-12. C# Monitor类实现线程互斥
  • Prompt 攻击与防范:大语言模型安全的新挑战
  • Google Store 如何利用 glTF 3D 模型改变产品教育
  • L1-1、Prompt 是什么?为什么它能“控制 AI”?
  • C++入门语法
  • 在线查看【免费】 mp3,wav,mp4,flv 等音视频格式文件文件格式网站
  • Spark,IDEA编写Maven项目
  • c++算法-(1)
  • Precision Machine Dynamics/Mechatronics Design - 5
  • 算法工程师面试题与参考答案资料(2025年版)
  • Python实例题:Pvthon3实现简单的FTP认证服
  • Pycharm(九)函数的闭包、装饰器
  • 【TeamFlow】4.1 Git使用指南
  • 高级java每日一道面试题-2025年4月19日-微服务篇[Nacos篇]-Nacos未来的发展方向和规划有哪些?
  • mac 本地 docker 部署 nacos
  • 本地搭建一个简易版本的 Web3 服务
  • 【Easylive】AdminFilter 详细解析
  • Sentinel源码—7.参数限流和注解的实现一
  • 经典算法 输出在环上的点
  • 【阿里云大模型高级工程师ACP学习笔记】2.1 用大模型构建新人答疑机器人
  • 绿色体育直播赛事扁平自适应M25直播模板源码
  • Qt项目——汽车仪表盘
  • git详解