环状替换 + 189 旋转数组

额外数组

题目描述

给你一个数组,将数组中的元素向右轮转 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

环状替换

通俗解释

让座位, 有一个环形座位
每个人跑到同一方向 距离自己k位置的座位上坐下,
这个位置上的人 同时 也需要跑到 距离自己k位置的座位坐下。
所有人都得移动动。

思考:

什么是环状?
什么是替换?

举个例子:
12345
下一个又成了123451234512345…

如果移动步数为2
1----3------5----2----4----1
会发现 1 移动 两步 到3, 3移动两步到5 , 5 移动两步到1。
就变成了 1 3 5 这是一个环的意思了

同理 2 4 1 3 5 2 41
2 4 1 3 5 2 4 1就是一个环

不对 231351
135241是同一个环

同理 3 5 2 4 1 3
3 5 2 4 1也是同意一个环

所以说,其实只有1个环就是
135241
一共就有1个环了

对于 123456
123456123456123456…
移动k = 2 步

1 3 5 为一个环
2 4 6 为一个环
一共有两个环

环数 = gcd(N, K)
N为 多少个点
K为 移动多少步
gcd 是求最大公约数

比如 上面
gcd(5,2) = 1
gcd(6,2) = 2_
在这里插入图片描述

在这里插入图片描述

图解

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

思路:
		假设一数组 a[1,2,3,4,5,6,7,8,9] 移动位数k=31开始,要将a[0]右移三位,移到a[3]
		a[3]右移三位,移到a[6]
		a[6]移到a[0]
		回到了a[0],我称这叫一个环路,按下标表示为0360,以元素来表示是1471。暂且抛去其他,我们看怎样实现一个环路:
		
		    如果只简单地令a[3]=a[0],那么数组变为a[1,2,3,1,5,6],此时出现问题,a[3]原本的值被覆盖丢失了。
		    于是我们先交换a[3]和a[0],数组变为a[4,2,3,1,5,6],这样交换完成后,原本a[3]的值被临时存储到a[0]中,
		    	而a[0]已经赋值给了a[3],所以值不会丢失。
		    接下来很简单,依次将剩下的非首元素的环路元素与a[0]交换。
		    具体到上面的0360,可以表述为,a[3]、a[6]依次与a[0]交换位置,整个环路就交换完成。
		    
		 解决了一个环路内的元素如何旋转的问题,我们要知道这个数组到底有几个环路
		 
             用数组b[1,2,3,4,5,6,7,8]来举例
             从元素1开始,很容易看出环路为147258361,
             然后看元素2,但元素2已经在元素1的环路中了,所以我们知道,此数组一共三个环路
             我们将环路遍历过程完整地写下来,为了更容易理解。如果右移超出了数组长度,我们将数组复制一份到右边
             (但这只是为了方便理解所做,真正的数组不变)
             1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8
             1     4     7     2     5     8     3     6
             一共走过了三个数组的长度
             将环路中的每个元素与其后面的两个元素看为一个整体。如123是一个整体、456也是一个整体
             那么
             1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8
             1     4     7     2     5     8     3     6
             |     |     |     |     |     |     |     |
             1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8
             
             很容易看出:
             环路中元素的个数b*移动的位数k = 数组的长度n*遍历走过的数组个数a 即bk=na
             k、n是已知的。我们求出一个环路中元素的个数b,就可以求出环路个数x了。
             关键是a
             我们看,从元素1开始,以两个元素的间隔向后遍历,最终会回到元素1.那如果再向后遍历呢?
             1 2 3 4 5 6 7 8|1 2 3 4 5 6 7 8|1 2 3 4 5 6 7 8|1 2 3 4 5 6 7 8|1 2 3 4 5 6 7 8|1 2 3 4 5 6 7 8
             1     4     7  |  2     5     8|    3      6    |1     4     7  |  2     5     8|    3     6
             再向后遍历一个循环,还是一样会回到元素1,此时a=6。以此类推,当a=3 6 9 12....时,遍历一定还会回到元素1
             但是因为第一个环路已经遍历完了数组内的所有元素,以后的遍历会把已经遍历完的元素再遍历一遍,这是不需要的。
             所以我们取a的最小值。
             在bk=na中
             n是固定的,所以我们要取na的最小值。
             显然,na可以被n、k整除且结果都大于0
             所以,na是n、k的公倍数,又因为na要取最小,所以
             bk=na=nk的最小公倍数
             故 b=nk的最小公倍数/k
             每个环路遍历的元素是一样多的,且总和为数组长度n
             所以环路数x=n/b

难点

如何实现旋转数组

以 数组 123456 k=2 举例:
1 要 去 到 3
同时设置 一个临时变量 将 3存起来 prev

在这里插入图片描述

结果如下

在这里插入图片描述

然后呢, 3本身 应该是要 前往下一个 地方,也就是5的位置
所以 再把 5取出来,放到3, 同时把3放到5里面, 这里 应该是 交换 3 和 5
在这里插入图片描述

交换后结果如下
在这里插入图片描述

然后是帮 5 找寻它 需要的位置,应该是 1所在的位置
在这里插入图片描述

交换后结果入下
在这里插入图片描述
小结一下
1 — 3 ---- 5 ---- 1
在这里插入图片描述
然后 是 2 开始往后移动
2 — 4 — 6— 2
在这里插入图片描述

coding设计思路

需要 记住

环的起始位置
当前处理的元素
下一个元素的 位置
在这里插入图片描述

初始化
start 和 curr 都指向第一个元素
next指针根据 (curr+k) % n 计算出 next的位置
k为步数 curr为当前指针位置

next指针所指的值 教给prev存储
curr 的值 覆盖 next指针 指向的值

在这里插入图片描述

重新计算next的位置 根据 cur的位置

在这里插入图片描述

next和prev进行交换
在这里插入图片描述

cur 后移 同时 重新计算next位置

在这里插入图片描述
当next位置 再次来到 start
curr == start 的时候,替换结束
在这里插入图片描述

开始第二次环状替换
在这里插入图片描述

代码

public class circleReplace {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4,5,6,7,8};
        System.out.println(Arrays.toString(arr));
        rotate(arr,3);
//        rotate2(arr,3);
        System.out.println(Arrays.toString(arr));
    }
    public static void rotate(int[] nums, int k){

        int n = nums.length;

        //special case
        if (k == 0 || k % n == 0){
            return;
        }

        // get valid k
        k = k % n;

        /** rotate
         * [1,2,3,4,5,6,7]  k = 3
         * 1-->4 4-->7 7-->3 3-->6 6-->2 2-->5 5-->1
         * N = 7
         * k = 3
         * gcd(N,k) = 1 只有1个环
         * 也可以通过 移动次数 == N 来进行判定循环的终止
         * 当移动次数为N时,说明每个数都移动了。而且移动到它变化为K步之后的那个位置
         *
         */
        // outer loop
        int cur = 0;
        int count = 0; // 设定变量 count作为移动次数来统计,n个数要移动,移动次数必为n次
        while (cur < n && count < n){
            // inner loop
            //initialization
            int preV = nums[cur]; // 当前指针cur所指的值, 复制给preV,方面其移动到next位置
            int next = (cur + k) % n;// 根据cur计算出next的值

            while (next != cur && count < n){ // 第一个环是否结束的边界,当next移动到cur 说明环移动完了
                // update value
                /**
                 * //用于交换 next所指的值 和 preV的值
                 * 直白说,就是 上一个值preV 放在 next位置,next位置的值 放在 preV上面,即 交换preV和next位置的值
                 */
                int tmp = nums[next];
                nums[next] = preV;
                preV = tmp;

                // update count 处理完一个items之后,计数+1,方便当count到达n的时候终止
                count++;

                // update point 更新 next指针,next往后移动
                next = (next + k) % n;

            }
            nums[next] = preV; // 将该环最后一个值移动到前面
            count++; //处理1个,count计数++1

            //update start 进入第二个环
            cur++; //这里能执行的话,就是进入第二个环,但是上面会有边界判断,第二个环存不存在
        }
    }

官方精简版本

    public static void rotate2(int[] nums, int k) {
        int n = nums.length;
        k = k % n;
        int count = gcd(k, n);
        for (int start = 0; start < count; start++){
            int cur = start;
            int preV = nums[cur];
            
            do {
                int next = (cur + k) % n; // calculate the next index
                // exchange the nextValue and preValue
                int temp = nums[next];
                nums[next] = preV;
                preV = temp;
                cur = next; // move cur
            } while (start != cur); // 直到环结束才跳出来
        }
    }

    public static int gcd(int x, int y) {
        return y > 0 ? gcd(y, x % y) : x;    
    }

关于gcd递归的理解
在这里插入图片描述

在这里插入图片描述

References

https://zhuanlan.zhihu.com/p/412049933
https://leetcode-cn.com/problems/rotate-array/submissions/
https://daimajiaoliu.com/daima/60f936979515000
https://www.songbingjia.com/nginx/show-106404.html
https://leetcode-cn.com/problems/rotate-array/solution/189-xuan-zhuan-shu-zu-fu-2chong-jie-fa-b-rkrd/

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值