
问题描述新生入学后图书馆将 N 本书摆在一条书架上。书的编号为 1,2,…,N。目前这些书的顺序可能是乱的。管理员希望通过调整书的位置使书架最终变为第 1 个位置放编号 1 的书第 2 个位置放编号 2 的书依次类推。书架前安装了一台轨道机械臂。每次操作时小蓝可以选择三个连续的位置机械臂会交换第一个位置和第三个位置上的书中间位置上的书保持不动。例如当前书架顺序为 1,2,3,4,5。选择第 2 至第 4 个位置后编号为 2 和 4 的书会交换书架变为 1,4,3,2,5。现在给出书架上 N 本书的当前顺序请你计算至少需要进行多少次操作才能将书架恢复为 1,2,…,N 的顺序。如果无论如何都无法完成输出 −1。输入格式第一行包含一个整数 N3≤N≤2000表示书的数量。第二行包含 N 个整数 A1,A2,…,AN表示书架当前从左到右的顺序。保证 A是 1∼N 的排列。输出格式输出一个整数表示恢复书架顺序所需的最少操作次数如果无法完成输出 −1。样例说明小蓝可以按下面的顺序操作选择第 3 到第 5 个位置书架变为 5,2,1,4,3选择第 1 到第 3 个位置书架变为 1,2,5,4,3选择第 3 到第 5 个位置书架变为 1,2,3,4,5。因此至少需要进行 3 次操作。代码展示import java.util.Scanner; // 1:无需package // 2: 类名必须Main, 不可修改 public class Main { public static void main(String[] args) { Scanner scan new Scanner(System.in); //在此输入您的代码... int N scan.nextInt(); int[] arr new int[N1]; for(int i1;iN;i){ int num scan.nextInt(); arr[i] num; } //奇偶数位要分别对应奇偶数 boolean b true; for(int i1;iN;i){ if(i%20){ int o i;//偶数位 if(arr[o]%2!0){ b false; break; } }else{ int j i;//奇数位 if(arr[j]%2!1){ b false; break; } } } if(bfalse){ System.out.println(-1); return; } //从第一位数字开始放回正确的位置 int count 0; for(int i1;iN;i){ while(arr[i]!i){ int p; for(pi;pN;p){ if(arr[p]i){ break; } } int t arr[p]; arr[p] arr[p-2]; arr[p-2] t; count; } } System.out.println(count); scan.close(); } }要点拆解经观察不难得知只有满足奇数位置的编号必须为奇数偶数位置的编号必须为偶数才能恢复顺序否则输出-1。这里要注意分奇偶的写法最开始我写的是//奇偶数位要分别对应奇偶数 boolean b true; for(int i1;iN;i){ int j 2*i-1; int o 2*i; if(arr[j]%2!1||arr[o]%2!0) b false; } if(bfalse){ System.out.println(-1); }但这一定是错的这样写显然没有考虑数组越界的情况。比如当 i 取 n 的时候arr [ j ] 和arr [ o ] 就会越界而报错。于是我改成了下面这个版本//奇偶数位要分别对应奇偶数 boolean b true; for(int i1;iN;i){ int o,j;//声明全局变量 if(i%20){ o i;//偶数位 }else{ j i;//奇数位 } if(arr[o]%2!0||arr[j]%2!1){ b false; } } if(bfalse){ System.out.println(-1); }不难发现这里的逻辑依然是错的第一点每次循环经过if - else 逻辑时只能选择其中一条而这就必然导致总有 o 或者 j 没有值而Java 要求局部全局变量使用前必须被赋值此处埋下一个伏笔。 比如 i 是奇数的时候执行 else给ji但是o完全没赋值后面你写arr[o]就报错。同理i 是偶数的时候j没有赋值。第二点每一轮循环i只是单个位置不要试图同时处理 o、j 两个位置必须分开写因而才有了第三个版本的出现//奇偶数位要分别对应奇偶数 boolean b true; for(int i1;iN;i){ if(i%20){ int o i;//偶数位 if(arr[o]%2!0){ b false; } }else{ int j i;//奇数位 if(arr[j]%2!1){ b false; } } } if(bfalse){ System.out.println(-1); }这个版本终于没有逻辑问题了但仍然有瑕疵聪明的你发现了吗问题在于发现无解打印-1后一定要return终止 main 方法不然后面还会继续输出 count。最终版本终于出来了//奇偶数位要分别对应奇偶数 boolean b true; for(int i1;iN;i){ if(i%20){ int o i;//偶数位 if(arr[o]%2!0){ b false; } }else{ int j i;//奇数位 if(arr[j]%2!1){ b false; } } } if(bfalse){ System.out.println(-1); return; }顺序重排的逻辑梳理从第一个位置开始按顺序依次往后判断编号是否到达自己对应的位置。如果不能对应先找到数字1目前的位置每次向左退两位直到到达第一个位置。后续以此类推...局部变量p的使用还记得前面埋的伏笔吗你可能会问这里的p不也没有初始化吗为什么在这里就能使用p作为局部变量呢其根本原因并不是变量有没有在一开始直接赋值而是在于在读取之前有没有被赋值如果代码路径保证一定能给 p 赋值那就没问题 比如这里int p;声明变量此时 p 无值。紧接着执行for(p i; …)→for 循环的初始化部分pi给 p 赋值了对本题中while内部for循环的理解1p应该从i开始遍历因为前面的1 ~ i - 1个元素已经排列好无需在重复循环2for(pi; pN; p)里面的break会立刻跳出这个 for 循环此时 p 就停在找到的那个下标不会再继续增加了。break的作用终止当前这一层 for 循环保留 p 当前的值直接跳到 for 循环后面的代码。因此不必担心for循环内部没有保存目标的p,break出来后符合条件的p就会自己冻结从而能用于后续的代码。最后强调一下引用类型值传递与基本类型值传递的区别引用类型传递的是该对象地址值的副本两个变量保存的是同一对象的地址指向的是同一个对象修改其中任意一个变量的数据都会改变两个变量的值而基本类型传递的是数据副本在副本修改数据并不会影响原先变量的值。