轮转数组解决方法

轮转数组

问题描述

给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。也就是说,将数组的每个元素向右移动 k 个位置,超过数组长度的部分循环到数组的开头。

示例:

  • 输入:nums = [1,2,3,4,5,6,7], k = 3
  • 输出:[5,6,7,1,2,3,4]

方法一:使用额外的数组

思路

  • 创建一个与原数组大小相同的额外数组。
  • 将原数组的元素按照旋转后的索引位置放入新数组中。
  • 将新数组的值复制回原数组。

图解

假设 nums = [1,2,3,4,5,6,7], k = 3

  • 计算新位置:对于每个索引 i,新位置为 (i + k) % n,其中 n 是数组长度。
原索引 i原元素 nums[i]新索引 (i + k) % n放入位置
01(0 + 3) % 7 = 33
12(1 + 3) % 7 = 44
23(2 + 3) % 7 = 55
34(3 + 3) % 7 = 66
45(4 + 3) % 7 = 00
56(5 + 3) % 7 = 11
67(6 + 3) % 7 = 22
  • 新数组为 [5,6,7,1,2,3,4]

实现代码

function rotateUsingExtraArray(nums: number[], k: number): void {
    const n = nums.length;
    k = k % n; // 如果 k 大于数组长度,取模
    const rotatedArray = new Array(n);

    // 将旋转后的元素放入新数组
    for (let i = 0; i < n; i++) {
        rotatedArray[(i + k) % n] = nums[i];
    }

    // 将新数组的值复制回原数组
    for (let i = 0; i < n; i++) {
        nums[i] = rotatedArray[i];
    }
}

复杂度分析

  • 时间复杂度:O(n),需要遍历数组两次。
  • 空间复杂度:O(n),需要额外的数组存储旋转后的结果。

方法二:使用数组反转

思路

  • 通过反转数组的部分或全部来实现旋转。
  • 具体步骤:
    1. 反转整个数组。
    2. 反转前 k 个元素。
    3. 反转剩余的元素。

图解

nums = [1,2,3,4,5,6,7], k = 3 为例:

  1. 反转整个数组

    [1,2,3,4,5,6,7][7,6,5,4,3,2,1]

  2. 反转前 k 个元素

    [7,6,5,4,3,2,1][5,6,7,4,3,2,1]

  3. 反转剩余的元素

    [5,6,7,4,3,2,1][5,6,7,1,2,3,4]

实现代码

function rotateUsingReverse(nums: number[], k: number): void {
    const n = nums.length;
    k = k % n; // 如果 k 大于数组长度,取模

    // 定义反转函数
    function reverse(start: number, end: number): void {
        while (start < end) {
            [nums[start], nums[end]] = [nums[end], nums[start]]; // 交换元素
            start++;
            end--;
        }
    }

    // 反转整个数组
    reverse(0, n - 1);
    // 反转前 k 个元素
    reverse(0, k - 1);
    // 反转剩余的元素
    reverse(k, n - 1);
}

复杂度分析

  • 时间复杂度:O(n),反转操作需要遍历数组三次,每次都是 O(n)。
  • 空间复杂度:O(1),只使用了常数级别的额外空间。

解释

  • 为什么这种方法有效?

    • 反转整个数组后,原来的末尾 k 个元素移动到了数组的开头,但顺序是反的。
    • 通过再次反转前 k 个元素,将它们的顺序调整过来。
    • 同理,反转剩余的元素,也恢复它们的顺序。

方法三:使用循环替换(环状替换)

思路

  • 直接将每个元素放到它最终的位置,但需要注意可能会覆盖尚未移动的元素。
  • 通过循环替换元素,避免数据被覆盖。

实现步骤

  1. 初始化计数器 count = 0,记录已经移动的元素数量。
  2. 从索引 start = 0 开始,进行循环替换:
    • 保存当前位置的值 prevValue
    • 计算下一个位置的索引 next = (current + k) % n
    • prevValue 放到 next 位置,保存 nums[next] 为新的 prevValue
    • 更新 current = next
    • 增加 count,直到 count 等于数组长度 n,表示所有元素都已移动。
  3. 如果出现循环,需要从下一个位置开始新的循环。

注意

  • 当数组长度和 k 的最大公约数不为 1 时,会出现循环,需要从下一个起始位置继续。

实现代码

function rotateUsingCyclicReplacement(nums: number[], k: number): void {
    const n = nums.length;
    k = k % n; // 如果 k 大于数组长度,取模
    let count = 0; // 记录已经移动的元素个数

    for (let start = 0; count < n; start++) {
        let current = start;
        let prevValue = nums[start];

        do {
            const next = (current + k) % n;
            const temp = nums[next];
            nums[next] = prevValue;
            prevValue = temp;
            current = next;
            count++;
        } while (start !== current);
    }
}

复杂度分析

  • 时间复杂度:O(n),每个元素只移动一次。
  • 空间复杂度:O(1),只使用了常数级别的额外空间。

解释

  • 为什么需要 count?

    • 因为可能会出现环状替换,一个循环无法覆盖所有元素,所以需要计数。
  • 示例演示:

    • nums = [1,2,3,4,5,6], k = 2 为例。

    • 第一轮循环:

      • start = 0current = 0prevValue = nums[0] = 1
      • next = (0 + 2) % 6 = 2
      • nums[2] 被替换为 1prevValue 更新为 nums[2] 的原值 3
      • current 更新为 2count = 1
      • 重复上述步骤,直到 current 回到 start
    • 第二轮循环:

      • start = 1,重复类似的过程。

总结

  • 方法一适用于不考虑空间复杂度的情况,理解起来较为直观。
  • 方法二通过数组反转,实现原地旋转,空间复杂度低,性能优良。
  • 方法三在空间复杂度为 O(1) 的情况下,时间效率较高,但理解起来稍微复杂。

测试代码

你可以使用以下测试代码来验证上述方法的正确性:

function testRotateFunctions() {
    const testCases = [
        { nums: [1, 2, 3, 4, 5, 6, 7], k: 3, expected: [5, 6, 7, 1, 2, 3, 4] },
        { nums: [-1, -100, 3, 99], k: 2, expected: [3, 99, -1, -100] },
    ];

    for (const { nums, k, expected } of testCases) {
        const nums1 = nums.slice();
        rotateUsingExtraArray(nums1, k);
        console.assert(nums1.toString() === expected.toString(), `rotateUsingExtraArray failed for input ${nums}, k=${k}`);

        const nums2 = nums.slice();
        rotateUsingReverse(nums2, k);
        console.assert(nums2.toString() === expected.toString(), `rotateUsingReverse failed for input ${nums}, k=${k}`);

        const nums3 = nums.slice();
        rotateUsingCyclicReplacement(nums3, k);
        console.assert(nums3.toString() === expected.toString(), `rotateUsingCyclicReplacement failed for input ${nums}, k=${k}`);
    }

    console.log("All test cases passed!");
}

testRotateFunctions();

结论

  • 方法一(使用额外的数组):理解简单,但需要额外的空间,空间复杂度为 O(n)。
  • 方法二(使用数组反转):不需要额外空间,空间复杂度为 O(1),是实际应用中较为推荐的方法。
  • 方法三(使用循环替换):也不需要额外空间,空间复杂度为 O(1),但理解和实现相对复杂。
### In-Place Array Rotation Algorithm Implementation To implement an in-place array rotation algorithm without using extra space, one effective method is the **Juggling Algorithm**, which has been described by Jon Bentley in his work on Block Exchange algorithms[^1]. The Juggling Algorithm divides the array into different sets where the number of elements in every set is equal to the greatest common divisor (GCD) of `n` and `d`, where `n` is the size of the array and `d` is the number of positions by which elements should be rotated. For example, consider rotating an array `[a b c d e f g h i j k l m n o p]` leftwards by 4 positions: #### Steps Involved: Elements are grouped based on GCD(n,d). For this case, let's assume we want to rotate the array leftward by 4 places (`d=4`) while having 16 total elements (`n=16`). Since gcd(16,4)=4, there will be 4 groups or cycles. Each cycle involves moving elements within their respective group until all elements have reached their new correct position as per required rotation. ```python def left_rotate(arr, d): n = len(arr) gcd_val = gcd(d, n) for i in range(gcd_val): temp = arr[i] j = i while True: k = (j + d) % n if k == i: break arr[j] = arr[k] j = k arr[j] = temp def gcd(a,b): if b==0: return a else: return gcd(b,a%b) ``` This approach ensures that only constant additional memory is used during execution since no auxiliary data structures like temporary arrays need allocation beyond storing single variables such as indices or values temporarily involved in swaps. When dealing with matrices specifically mentioned in another reference about handling rotations involving width (`w`) and height (`h`)[^2], similar principles apply but involve more complex indexing due to multi-dimensional nature; however, core concept remains same focusing on minimizing external storage requirements through clever manipulation techniques directly over original dataset structure itself rather than copying contents elsewhere before applying transformations.
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小李学软件

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值