leetcode_189_ 轮转数组

给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
 示例 1:

输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]
示例 2:

输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释: 
向右轮转 1 步: [99,-1,-100,3]
向右轮转 2 步: [3,99,-1,-100]
 

提示:

1 <= nums.length <= 105
-231 <= nums[i] <= 231 - 1
0 <= k <= 105
 

进阶:

尽可能想出更多的解决方案,至少有 三种 不同的方法可以解决这个问题。
你可以使用空间复杂度为 O(1) 的 原地 算法解决这个问题吗?
  1 package com.leetcode.mianshi;
  2 
  3 import java.util.Arrays;
  4 import java.util.stream.IntStream;
  5 
  6 /**
  7  * LeetCode 189:轮转数组 —— 公平性能对比测试
  8  * 
  9  * 改进目标:
 10  * 1. 每个方法独立使用原始数组的拷贝,保证初始状态一致。
 11  * 2. 预热 + 多次测试取平均,减少 JVM 优化和 GC 干扰。
 12  * 3. 测试三种不同 k 值(小、中、大),观察不同场景下的性能表现。
 13  * 4. 数组规模适中(1000 万),避免 OOM,同时耗时差异足够明显。
 14  * 
 15  * @author z (优化版)
 16  */
 17 public class Leetcode_189_Solution {
 18 
 19     // ---------- 测试配置参数 ----------
 20     private static final int ARRAY_SIZE = 10_000_000;          // 数组长度:1000 万(约 40 MB)
 21     private static final int WARMUP_ITERATIONS = 3;            // 预热次数
 22     private static final int TEST_ITERATIONS = 5;              // 正式测试次数(取平均)
 23     private static final int[] K_VALUES = {3, ARRAY_SIZE / 2, ARRAY_SIZE - 3}; // 小、中、大三种 k
 24 
 25     // ---------- 主测试入口 ----------
 26     public static void main(String[] args) {
 27         // 生成原始升序数组(所有方法都基于它的拷贝进行操作)
 28         int[] original = getInt(ARRAY_SIZE);
 29         System.out.println("数组大小: " + ARRAY_SIZE);
 30         System.out.println("预热次数: " + WARMUP_ITERATIONS + ",正式测试次数: " + TEST_ITERATIONS);
 31 
 32         // 对每种 k 值分别测试所有方法
 33         for (int k : K_VALUES) {
 34             System.out.println("\n========== k = " + k + " ==========");
 35             // 依次测试 6 种方法,每种方法都独立拷贝原始数组
 36             testMethod("方法1 (手工临时数组, 循环拷贝)", original, k, Lecode_189_Solution::rotate1);
 37             testMethod("方法2 (System.arraycopy临时数组)", original, k, Lecode_189_Solution::rotate2);
 38             testMethod("方法3 (Stream拼接)", original, k, Lecode_189_Solution::rotate3);
 39             testMethod("方法4 (三次反转, 空间O(1))", original, k, Lecode_189_Solution::rotate4);
 40             testMethod("方法5 (取模新数组 + arraycopy回拷)", original, k, Lecode_189_Solution::rotate5);
 41             testMethod("方法6 (取模新数组 + for循环回拷)", original, k, Lecode_189_Solution::rotate6);
 42         }
 43     }
 44 
 45     /**
 46      * 通用测试方法:对指定的轮转函数进行预热和正式测试,输出平均耗时(毫秒)。
 47      * 
 48      * @param name     方法名称(显示用)
 49      * @param original 原始数组(不会被修改,每次测试都会拷贝)
 50      * @param k        轮转位数
 51      * @param func     待测试的轮转函数(函数式接口)
 52      */
 53     private static void testMethod(String name, int[] original, int k, RotateFunction func) {
 54         // ----- 预热阶段 -----
 55         // 目的:让 JVM 将热点代码编译为机器码,避免 JIT 影响正式测试
 56         for (int i = 0; i < WARMUP_ITERATIONS; i++) {
 57             int[] copy = Arrays.copyOf(original, original.length); // 每次拷贝独立数组
 58             func.rotate(copy, k);                                  // 执行轮转
 59         }
 60 
 61         // ----- 正式测试阶段 -----
 62         long totalNanos = 0;
 63         for (int i = 0; i < TEST_ITERATIONS; i++) {
 64             int[] copy = Arrays.copyOf(original, original.length); // 重新拷贝,保证初始状态一致
 65             long start = System.nanoTime();                        // 高精度计时(纳秒)
 66             func.rotate(copy, k);
 67             long end = System.nanoTime();
 68             totalNanos += (end - start);
 69         }
 70         // 计算平均耗时,并转换为毫秒(保留两位小数)
 71         double avgMs = totalNanos / (TEST_ITERATIONS * 1_000_000.0);
 72         System.out.printf("%s 平均耗时: %.2f ms\n", name, avgMs);
 73     }
 74 
 75     /**
 76      * 函数式接口,用于传递轮转方法。
 77      */
 78     @FunctionalInterface
 79     interface RotateFunction {
 80         void rotate(int[] nums, int k);
 81     }
 82 
 83     // ---------- 工具方法:生成测试数组 ----------
 84     /**
 85      * 构造升序测试数组:元素为 1 ~ length。
 86      * 
 87      * @param length 数组长度
 88      * @return 升序数组(若 length <= 0 返回空数组)
 89      */
 90     public static int[] getInt(int length) {
 91         if (length <= 0) {
 92             return new int[0];
 93         }
 94         int[] nums = new int[length];
 95         for (int i = 0; i < length; i++) {
 96             nums[i] = i + 1;
 97         }
 98         return nums;
 99     }
100 
101     // ---------- 6 种轮转实现(严格保持原始逻辑) ----------
102 
103     /**
104      * 方法1:手工临时数组(纯 while 循环)
105      * 思路:保存末尾 k 个元素到临时数组,将前部元素后移,再将临时数组放回头部。
106      * 时间复杂度 O(n),空间复杂度 O(k)
107      */
108     public static void rotate1(int[] nums, int k) {
109         int n = nums.length;
110         int turnRight = k % n;          // 有效轮转步数
111         if (turnRight == 0) {
112             return;
113         }
114 
115         int[] temp = new int[turnRight];
116         // 1. 保存末尾 turnRight 个元素到 temp(逆序保存,但回填时按顺序)
117         int a = turnRight - 1;
118         int b = n - 1;
119         while (a >= 0) {
120             temp[a--] = nums[b--];
121         }
122         // 2. 将剩余元素整体后移 turnRight 位
123         while (b >= 0) {
124             nums[b + turnRight] = nums[b--];
125         }
126         // 3. 将 temp 回填到数组头部(temp 中元素顺序正确)
127         int c = turnRight - 1;
128         while (c >= 0) {
129             nums[c] = temp[c--];
130         }
131     }
132 
133     /**
134      * 方法2:System.arraycopy 临时数组
135      * 思路:与方法1相同,但使用 System.arraycopy 进行批量拷贝,性能更高。
136      * 时间复杂度 O(n),空间复杂度 O(k)
137      */
138     public static void rotate2(int[] nums, int k) {
139         int n = nums.length;
140         int turnRight = k % n;
141         if (turnRight == 0) {
142             return;
143         }
144         int[] temp = new int[turnRight];
145         // 1. 末尾 turnRight 个元素 -> temp
146         System.arraycopy(nums, n - turnRight, temp, 0, turnRight);
147         // 2. 开头元素整体后移 turnRight 位
148         System.arraycopy(nums, 0, nums, turnRight, n - turnRight);
149         // 3. temp 回填到头部
150         System.arraycopy(temp, 0, nums, 0, turnRight);
151     }
152 
153     /**
154      * 方法3:Stream 拼接
155      * 思路:用 IntStream 将“末尾段 + 开头段”拼接成新数组,再拷回原数组。
156      * 时间复杂度 O(n),空间复杂度 O(n)(新数组 + Stream 内部开销)
157      */
158     public static void rotate3(int[] nums, int k) {
159         int n = nums.length;
160         int turnRight = k % n;
161         if (turnRight == 0) {
162             return;
163         }
164         // 拼接:先取末尾 turnRight 个,再取开头剩余部分
165         int[] nums2 = IntStream.concat(
166                 Arrays.stream(nums, n - turnRight, n),
167                 Arrays.stream(nums, 0, n - turnRight)
168         ).toArray();
169         // 将结果拷回原数组
170         System.arraycopy(nums2, 0, nums, 0, n);
171     }
172 
173     /**
174      * 方法4:三次反转(原地操作,空间最优)
175      * 思路:整体反转 → 反转前 turnRight 段 → 反转后 n-turnRight 段。
176      * 时间复杂度 O(n),空间复杂度 O(1)
177      */
178     public static void rotate4(int[] nums, int k) {
179         int n = nums.length;
180         int turnRight = k % n;
181         if (turnRight == 0) {
182             return;
183         }
184         // 1. 整体反转
185         reverse(nums, 0, n - 1);
186         // 2. 反转前 turnRight 个元素
187         reverse(nums, 0, turnRight - 1);
188         // 3. 反转剩余部分
189         reverse(nums, turnRight, n - 1);
190     }
191 
192     /**
193      * 辅助方法:反转数组指定区间 [start, end](闭区间)
194      */
195     private static void reverse(int[] nums, int start, int end) {
196         while (start < end) {
197             int tmp = nums[start];
198             nums[start++] = nums[end];
199             nums[end--] = tmp;
200         }
201     }
202 
203     /**
204      * 方法5:取模定位(新数组)+ System.arraycopy 回拷
205      * 思路:元素 i 轮转后应落在 (i+k)%n 位置,先写入临时数组,再批量拷回。
206      * 时间复杂度 O(n),空间复杂度 O(n)
207      */
208     public static void rotate5(int[] nums, int k) {
209         int n = nums.length;
210         int turnRight = k % n;
211         if (turnRight == 0) {
212             return;
213         }
214         int[] temp = new int[n];
215         // 计算每个元素的目标位置
216         for (int i = 0; i < n; i++) {
217             temp[(i + k) % n] = nums[i];
218         }
219         // 使用 System.arraycopy 批量回拷
220         System.arraycopy(temp, 0, nums, 0, n);
221     }
222 
223     /**
224      * 方法6:取模定位(新数组)+ for 循环回拷
225      * 思路:与方法5相同,但回拷改用 for 循环逐元素赋值,对比两种回拷方式的性能差异。
226      * 时间复杂度 O(n),空间复杂度 O(n)
227      */
228     public static void rotate6(int[] nums, int k) {
229         int n = nums.length;
230         int turnRight = k % n;
231         if (turnRight == 0) {
232             return;
233         }
234         int[] temp = new int[n];
235         // 计算每个元素的目标位置
236         for (int i = 0; i < n; i++) {
237             temp[(i + k) % n] = nums[i];
238         }
239         // 使用 for 循环逐个回拷
240         for (int i = 0; i < n; i++) {
241             nums[i] = temp[i];
242         }
243     }
244 }

方法1到方法6是网上查的,自己写的1,2,总结下来,AI辅助是一方面,把理解是很重要的。AI有很多种解决方案,但是适合自己的才是好的。

posted @ 2026-08-21 16:32  瓜瓜先生  阅读(2)  评论(0)    收藏  举报