hot100 轮转数组(189)
本题采用三步翻转法数组逆序排列算法解决“旋转数组”问题。其核心本质是利用空间对称翻转的数学特性通过三次局部置换实现元素区间的整体平移从而将传统辅助数组方案的 O(n) 空间复杂度彻底优化至 O(1) 的原地操作极限。当前解法逻辑纯粹、结构优雅最终走向是通过确定的三次双指针对调直接锁定全局目标位置。一、 问题本质与数据模型对于长度为 n 的整数数组 nums将其向右轮转 k 个位置意味着数组的后 k 个元素将会平移到数组的最前端而前 n - k 个元素则会整体向后顺延。在进入核心算法前必须首先明确两个前置的边界数学逻辑取模优化如果轮转步数 k 大于或等于数组长度 n轮转 n 次将使数组完全恢复原状。因此真正产生位置偏移的有效步数应当对数组长度取模即k k % n。区间划分模型取模后整段数组在逻辑上被切分为两个独立的核心数据块前半部分数据块 A区间为[0, n - k - 1]长度为n - k。后半部分数据块 B区间为[n - k, n - 1]长度为k。原始数组的物理拓扑结构可以表示为[A, B]而轮转完毕后的最终目标拓扑结构应当为[B, A]三步翻转法的精妙之处就在于不需要借助任何临时数组去暂存数据块 B而是直接在原数组的内存空间上通过三次颠倒顺序的操作巧妙地完成了[A, B]到[B, A]的空间拓扑转换。二、 算法演进对比在解决数组整体平移置换问题时三步翻转法在时间与空间的综合资源配置上表现极佳解法名称时间复杂度空间复杂度核心原理物理缺陷 / 瓶颈暴力循环轮转O(n * k)O(1)每次向右轮转 1 位外层循环控制执行 k 次存在大量的元素重复移动面对大数级输入时必然引发时间超时额外数组辅助O(n)O(n)申请一个同等大小的数组利用新下标(i k) % n直接映射赋值引入了与原数据等长的物理内存开销非原地算法环状正向替换O(n)O(1)从一个位置出发利用单变量暂存依次跳跃替换到目标位置逻辑判断分支极其复杂需要利用最大公约数GCD计算循环圈数三步翻转法当前解法O(n)O(1)通过全局翻转与局部翻转的线性组合原地对调区间拓扑无为当前时空复杂度的工程理论极限三、 三步翻转法的数学逻辑证明设符号X^T代表将序列 X 进行完全逆序首尾对调操作。已知逆序操作具有如下数学性质对一个序列连续执行两次逆序序列恢复原样即(X^T)^T X。我们的目标是将初始序列[A, B]转换为[B, A]。算法的三步操作推导过程如下第一步翻转整个数组[0, n - 1]将整个数组完全颠倒原处于后方的块 B 移到了前面前方的块 A 移到了后面。但此时两个块内部的元素顺序也被顺带颠倒了。变换公式[A, B]^T [B^T, A^T]第二步翻转前 k 个元素[0, k - 1]此时前 k 个元素对应的正是已经被颠倒的块B^T。对其再次执行逆序根据性质(B^T)^T B块 B 内部的元素顺序成功恢复正序。变换公式[B^T, A^T]变为[B, A^T]第三步翻转剩余的 n - k 个元素[k, n - 1]此时后半部分的元素对应的是被颠倒的块A^T。对其执行逆序同理(A^T)^T A块 A 内部的元素顺序也恢复正序。变换公式[B, A^T]变为[B, A]经过上述三步纯粹的对称转换最终完美得到了目标拓扑结构[B, A]。四、 算法执行状态机步进示例以输入数据nums [1, 2, 3, 4, 5, 6, 7]k 3为例此时数组长度n 7有效步数k 3 % 7 3算法内部各阶段的物理状态变化如下表所示步骤操作区间与说明作用的局部数据块数组内部实时元素状态物理指针位置说明初始化原始状态-[1, 2, 3, 4, 5, 6, 7]划分点前 4 个为 A后 3 个为 B第 1 步翻转全数组[0, 6]作用于全局[A, B][7, 6, 5, 4, 3, 2, 1]此时前半部分为B^T后半部分为A^T第 2 步翻转前 k 个[0, 2]仅作用于前半部分B^T[5, 6, 7, 4, 3, 2, 1]前 3 个元素成功恢复正序变为 B第 3 步翻转剩余个[3, 6]仅作用于后半部分A^T[5, 6, 7, 1, 2, 3, 4]后 4 个元素成功恢复正序变为 A结束输出结果-[5, 6, 7, 1, 2, 3, 4]成功组装为目标拓扑[B, A]五、源码实现class Solution { public void rotate(int[] nums, int k) { // 边界安全检查若数组为空或长度小于等于1无需进行任何轮转 if (nums null || nums.length 1) { return; } // 核心步骤 1对 k 进行取模优化防止因 k 大于数组长度而执行无意义的循环翻转 k % nums.length; if (k 0) { return; // 若取模后有效偏移为 0直接返回原数组 } // 核心步骤 2第一步原地翻转整个数组打破原有区间分布 reverse(nums, 0, nums.length - 1); // 核心步骤 3第二步原地翻转前 k 个元素让后半部分平移上来的数据恢复正序 reverse(nums, 0, k - 1); // 核心步骤 4第三步原地翻转剩余的元素让被挤到后面的前半部分数据恢复正序 reverse(nums, k, nums.length - 1); } /** * 基于双指针的原地数组逆序辅助方法 * param nums 目标操作数组 * param start 逆序区间的左物理起点 * param end 逆序区间的右物理终点 */ private void reverse(int[] nums, int start, int end) { // 利用相向双指针向中间靠拢在常数空间内完成元素对调 while (start end) { int temp nums[start]; nums[start] nums[end]; nums[end] temp; // 指针步进控制 start; end--; } } }六、 复杂度极限分析1. 时间复杂度O(n)精确计数在reverse辅助函数中双指针通过相向移动进行元素互换。每次互换涉及 1 次变量暂存和 2 次赋值。第一步全局翻转遍历整个数组执行了n / 2次置换。第二步前半部分翻转执行了k / 2次置换。第三步后半部分翻转执行了(n - k) / 2次置换。结论三步操作的总置换次数恰好为(n / 2) (k / 2) ((n - k) / 2) n次。基础运算开销与数组总长度 n 呈现严格的线性正比关系因此时间复杂度稳定在 O(n)。2. 空间复杂度O(1)分析整个算法的控制流全部在传入的nums原数组内存块上展开。执行期间仅在reverse方法内声明了一个临时的整型变量temp用于辅助指针互换以及常数个基础类型的索引变量start,end,k。结论没有任何动态内存申请如没有 new 出任何辅助数组其开销是完全恒定的常数阶空间复杂度为纯正的 O(1) 原地解法。