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有很多种解决方案,但是适合自己的才是好的。
浙公网安备 33010602011771号