【除夕篇】LeetCode 热题 100 之 189.轮转数组

首先祝大家

新的一年旺旺旺!!!

核心思路

向右轮转 k 个位置,等价于将数组的后 k 个元素整体移动到数组头部。需要注意:

  • k >= nums.length,可先对 k 取模(k = k % nums.length),避免无效操作。
  • 进阶要求:实现至少三种解法,并给出空间复杂度为 O(1) 的原地算法。

解法一:使用额外数组 空间复杂度 O (n)

思路:创建一个新数组,将原数组元素按轮转后的位置填充,再复制回原数组。

  • 时间复杂度:O (n)

  • 空间复杂度:O (n)

    public void rotate(int[] nums, int k) {
    int n = nums.length;
    k = k % n;
    int[] temp = new int[n];
    for (int i = 0; i < n; i++) {
    temp[(i + k) % n] = nums[i];
    }
    System.arraycopy(temp, 0, nums, 0, n);
    }

System.arraycopy(temp, 0, nums, 0, n);

  • 作用 :将临时数组 temp 中存储的 "轮转后元素",完整复制到原数组 nums 中,实现原数组的修改。
  • 为什么不用 nums = temp :Java 中数组是引用类型 ,如果直接写 nums = temp,只是让 nums 变量指向 temp 的内存地址,而原数组的内存空间不会被修改 (如果 nums 是方法参数,外部调用者看不到变化);而 System.arraycopy 是把 temp 的内容 "复制" 到 nums 原本的内存空间,真正实现 "原地修改"。
  • 参数解释System.arraycopy(源数组, 源数组起始索引, 目标数组, 目标数组起始索引, 复制元素个数)
    • temp:源数组(轮转后的数组)
    • 0:从 temp 的索引 0 开始复制
    • nums:目标数组(要修改的原数组)
    • 0:复制到 nums 的索引 0 位置
    • n:复制 n 个元素(整个数组)

解法二:暴力轮转 空间复杂度 O (1)

思路 :每次将最后一个元素移动到数组开头,重复 k 次。

  • 时间复杂度:O (n*k)(k 较大时易超时)

  • 空间复杂度:O (1)

    public void rotate(int[] nums, int k) {
    int n = nums.length;
    k = k % n;
    for (int i = 0; i < k; i++) {
    int last = nums[n - 1];
    for (int j = n - 1; j > 0; j--) {
    nums[j] = nums[j - 1];
    }
    nums[0] = last;
    }
    }

解法三:三次反转法 空间复杂度 O (1)

思路:通过三次反转实现原地轮转,是满足进阶要求的最优解法:

  1. 反转整个数组
  2. 反转前 k 个元素
  3. 反转后 n-k 个元素
  • 时间复杂度:O (n)
  • 空间复杂度:O (1)

原理示例 :原数组:[1,2,3,4,5,6,7]k=3

  1. 反转整个数组 → [7,6,5,4,3,2,1]

  2. 反转前 3 个 → [5,6,7,4,3,2,1]

  3. 反转后 4 个 → [5,6,7,1,2,3,4]

    public void rotate(int[] nums, int k) {
    // 步骤1:获取数组长度,记为n(后续所有操作的基础)
    int n = nums.length;
    // 步骤2:计算有效轮转次数,处理k大于数组长度的情况
    k = k % n;
    // 步骤3:第一次反转:反转整个数组(索引0到n-1)
    reverse(nums, 0, n - 1);
    // 步骤4:第二次反转:反转前k个元素(索引0到k-1)
    reverse(nums, 0, k - 1);
    // 步骤5:第三次反转:反转剩余的n-k个元素(索引k到n-1)
    reverse(nums, k, n - 1);
    }

    private void reverse(int[] nums, int start, int end) {
    // 双指针循环:当start < end时,交换首尾元素并向中间靠拢
    while (start < end) {
    // 临时变量保存start位置的元素(避免交换时被覆盖)
    int temp = nums[start];
    // 把end位置的元素放到start位置
    nums[start] = nums[end];
    // 把原来start的元素放到end位置
    nums[end] = temp;
    // start指针右移一位,向中间靠近
    start++;
    // end指针左移一位,向中间靠近
    end--;
    }

用完整例子验证执行过程

以经典案例 nums = [1,2,3,4,5,6,7]k=3 为例,一步步看执行效果:

步骤 操作 数组状态
初始 - [1,2,3,4,5,6,7]
步骤 1 n=7,k=3%7=3 不变
步骤 2 反转整个数组(0-6) [7,6,5,4,3,2,1]
步骤 3 反转前 3 个元素(0-2) [5,6,7,4,3,2,1]
步骤 4 反转后 4 个元素(3-6) [5,6,7,1,2,3,4]

解法对比

解法 空间复杂度 时间复杂度 优点 缺点
额外数组 O(n) O(n) 简单易懂,效率稳定 需额外空间,不符合进阶要求
暴力轮转 O(1) O(n*k) 空间复杂度低 k 较大时易超时
三次反转 O(1) O(n) 空间复杂度低,效率最优 需要理解反转逻辑

官方题解

环状替换
复制代码
class Solution {
    public void rotate(int[] nums, int k) {
        // 步骤1:获取数组长度
        int n = nums.length;
        // 步骤2:计算有效轮转次数(避免k>n)
        k = k % n;
        // 步骤3:计算k和n的最大公约数(环的数量)
        int count = gcd(k, n);
        // 步骤4:遍历每个环的起始位置(共count个环)
        for (int start = 0; start < count; ++start) {
            // 步骤5:初始化当前位置、前一个元素(环的起点元素)
            int current = start;
            int prev = nums[start];
            // 步骤6:循环处理当前环的所有元素,直到回到起点
            do {
                // 计算当前元素轮转后的下一个位置
                int next = (current + k) % n;
                // 保存下一个位置的原始值(避免被覆盖)
                int temp = nums[next];
                // 把前一个元素放到下一个位置(完成一次替换)
                nums[next] = prev;
                // 更新前一个元素为下一个位置的原始值
                prev = temp;
                // 更新当前位置为下一个位置
                current = next;
            } while (start != current); // 回到环的起点时终止
        }
    }

    // 辅助函数:计算两个数的最大公约数(欧几里得算法)
    public int gcd(int x, int y) {
        return y > 0 ? gcd(y, x % y) : x;
    }
}

链接:https://leetcode.cn/problems/rotate-array/solutions/551039/xuan-zhuan-shu-zu-by-leetcode-solution-nipk/

来源:力扣(LeetCode) 著作权归作者所有。

相关推荐
qingwufeiyang_5301 分钟前
Nacos学习笔记
java·笔记·学习·spring cloud·服务发现
charlie1145141913 分钟前
嵌入式现代C++开发——三路比较运算符
开发语言·c++·学习·算法·嵌入式·编程指南
2401_900151543 分钟前
C++编译期正则表达式
开发语言·c++·算法
天涯明月19933 分钟前
服务网格完全指南:从基础概念到生产实践
java·服务器·数据库·分布式·微服务
倾心琴心3 分钟前
【agent辅助pcb routing coding学习】实践1 kicad pcb 格式讲解
算法·agent·pcb·eda·routing
仙俊红4 分钟前
LeetCode493周赛T3,前后缀分解
数据结构·算法·leetcode
Han.miracle7 分钟前
万字详解 Lombok 构造方法注解:@AllArgsConstructor 非空校验实现与最佳实践
java·前端·数据库
_日拱一卒7 分钟前
LeetCode(力扣):二叉树的前序遍历
java·数据结构·算法·leetcode
倾心琴心7 分钟前
【agent辅助pcb routing coding学习】实践5 kicad类按类别理解
算法·agent·pcb·eda·routing
Frostnova丶11 分钟前
LeetCode 50. Pow(x, n)
算法·leetcode