游乐游手机版
首页/编程语言/文章详情

整型数组索引与值互换实现置换逆运算方法

时间:2026-07-08 06:46
构建置换的逆运算需创建独立副本,以原数组值作为新数组索引、原数组索引作为新数组值。必须避免原地修改,否则会覆盖原始数据导致错误。该算法时间复杂度O(n),空间复杂度O(n),适用于密码学等确定性场景。

今天要聊聊一个看似基础但极易出错的编程操作:如何将表示置换关系的整型数组“翻转”过来,也就是构建它的逆置换。这在密码学中的轮转器实现(例如恩尼格玛仿真)里非常常见——编码和解码本质上就是一对对称的置换运算。

先理清核心概念。假设有一个数组 arr,其中 arr[i] = j 表示索引 i 映射到值 j。现在我们希望构造一个新数组 inv,使得 inv[j] = i,即交换索引与值的角色。这个操作常被称为“数组索引与值互换”或“置换求逆”,是双射函数求逆的直观体现:已知 f(i) = arr[i],求 f⁻¹(j) = i。

如何交换整型数组的索引与值(实现置换逆运算)

举个具体的例子。假设正向映射为 [4, 0, 3, 1, 2],即输入 0 得到输出 4,输入 1 得到输出 0,依此类推。那么它的逆置换应变成 [1, 3, 4, 2, 0]——意思是,输出 0 来自输入 1,输出 1 来自输入 3,依此类推。这实际上就是数学上的双射函数求逆:已知 f(i) = arr[i],求 f⁻¹(j) = i。

但在动手编码之前,必须先认清一个极易踩坑的核心问题。

最大的风险在于:绝不能原地修改,否则遍历过程中原始数据会被覆盖,导致逻辑彻底崩溃。

常见错误是直接在原数组上赋值,比如写 R1.Pattern[origPatt[i]] = i,却没有提前保存原始状态。问题在于 origPatt = R1.Pattern 只是复制了引用,而非数据本身。后续对 R1.Pattern 的写入会实时覆盖尚未读取到的原始值——等到循环进行到后面时,引用的已是改写过的东西,结果自然全乱套了。

正确的做法其实很简单:

  • 先创建一个独立副本,用 .clone() 或者手动新建数组都可以;
  • 然后用原数组的作为新数组的索引,用原数组的索引作为新数组的值;
  • 最关键的是,确保原数组的内容在整个循环过程中始终不变。

下面是最安全、最清晰的 Java 实现:

int[] original = {4, 0, 3, 1, 2}; // 正向置换:f(0)=4, f(1)=0, f(2)=3, f(3)=1, f(4)=2
int[] inverse = new int[original.length];

// 构造逆置换:令 inverse[original[i]] = i
for (int i = 0; i < original.length; i++) {
    inverse[original[i]] = i;
}

// 输出结果:[1, 3, 4, 2, 0]
System.out.println(Arrays.toString(inverse)); // [1, 3, 4, 2, 0]

需要留意几个关键细节:

  • 数组必须是有效的置换original 必须包含从 0 到 n−1 的每个整数恰好一次,不能有重复或越界,否则 original[i] 作为索引时会抛出 ArrayIndexOutOfBoundsException
  • 禁止原地计算:千万不要在 original 上直接赋值,必须使用独立的目标数组。
  • 如果确实需要就地更新(例如覆盖原来的轮子配置),那就先做一次深拷贝:int[] origCopy = R1.Pattern.clone();,然后基于 origCopy 构建逆置换,最后写回 R1.Pattern
  • 该算法的时间复杂度为 O(n),空间复杂度为 O(n),已是理论最优解,没有更快的方案。

总结起来其实就一句话:交换索引与值的本质,就是构造置换的数学逆元。牢记“以值为新索引、以原索引为新值”,同时严格分离读写内存区域,就能稳稳支撑起密码学仿真这类对确定性映射有严苛要求的场景。

来源:https://www.php.cn/faq/2782116.html
上一篇ThinkPHP排序不生效原因及快速排查思路 下一篇行序不同的Excel文件差异高效比对提取技巧
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
列表遍历时动态判断阈值并返回相应文本
编程语言 · 2026-07-08

列表遍历时动态判断阈值并返回相应文本

遍历数值列表时,先筛选满足阈值的元素,再根据结果输出列表或友好提示。推荐使用列表推导式结合条件判断,注意边界用`>=`,空列表自动为假。也可用`any()`提前终止遍历,提升效率。此法简洁,避免显式循环,特别适合阈值筛选。

Maven项目中如何强制使用本地构建的依赖版本
编程语言 · 2026-07-08

Maven项目中如何强制使用本地构建的依赖版本

多模块开发中强制使用本地依赖的正确做法是使用-SNAPSHOT版本并配合`-U`参数强制更新,或重构为多模块项目统一管理生命周期。避免使用版本范围语法或手动复制JAR,确保构建行为可靠可重复。

Go语言net.Conn并发写安全与原子性保障解析
编程语言 · 2026-07-08

Go语言net.Conn并发写安全与原子性保障解析

Go标准库中net Conn支持并发方法调用,但Write()不保证原子性。多goroutine同时写入时,系统可能拆分数据包,导致内容交错,破坏消息边界。必须使用互斥锁或bufio Writer等显式同步机制确保写操作完整性,不可依赖系统调用本身的原子性。

Python在Windows系统中获取指定卷标U盘驱动器字母的方法
编程语言 · 2026-07-08

Python在Windows系统中获取指定卷标U盘驱动器字母的方法

使用Pythonwmi库通过Win32_Volume接口查询Windows系统中卷标为“TOSHIBA”的U盘盘符。需安装wmi和pywin32,注意大小写区分及多设备过滤。仅适用于Windows。

TP6.0消息已读功能基于Redis Bitmap未读计数方案
编程语言 · 2026-07-08

TP6.0消息已读功能基于Redis Bitmap未读计数方案

TP6 0中使用Redis位图实现消息已读标记,每条消息仅占一个比特,内存效率高。需将消息ID映射为连续偏移量,通过SETBIT和GETBIT操作。需提前维护用户ID到偏移量的映射表,注意键过期与驱动类型(phpredis与predis)问题。此方法内存极省,适合海量消息场景,且需确保偏移量唯一。