(leetcode) 力扣100 15轮转数组(环状替代)

题目

给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

数据范围

1 <= nums.length <= 105

-231 <= nums[i] <= 231 - 1

0 <= k <= 105

测试用例

示例一

java 复制代码
输入: 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]

示例二

java 复制代码
输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释: 
向右轮转 1 步: [99,-1,-100,3]
向右轮转 2 步: [3,99,-1,-100]

解法一(额外数组,时空复杂度O(n))

java 复制代码
public static void rotate(int[] nums, int k) {
        int temp[]=nums.clone();
        int len=nums.length;
        for(int i=0;i<len;i++){
            nums[(i+k)%len]=temp[i];
        }
    }

解法二(环状代替)

java 复制代码
public static void rotate(int[] nums, int k) {
        int len=nums.length;
        k=k%len;
        int count = gcd(len,k);
        for(int start=0;start<count;start++){
            int i=start;
            int current=nums[i];
            int t=(i+k)%len;
            int next=nums[t];
            do{
                nums[t]=current;
                current=next;
                i=t;
                t=(i+k)%len;
                next=nums[t];
            }while(i!=start);
        }

    }

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

思路

这道题思路解答很简单,不解决进阶问题的话,额外复制一个数组就可以轻松解决,同样也没有意义。

所以我们目标肯定是要想着进阶的,对于进阶解法,说是算法,更像是一个数学知识。我们可以把数组想象一个圆圈,如果我们每次固定步数前进,肯定会在有限圈数回到出发点,这个过程中,有可能会遍历完数组所有数,有可能遍历有限数。

我们利用这个过程,首先计算需要多少个从起点走回起点的圈数(count,为数组长度与步数的最大公约数,具体推推导后面会给),然后再遍历每一圈,通过创建一个临时变量(current,next,类似于指针的作用),来存储需要移动的变量将其移动到下一个位置,而下一个位置变为信的需移动变量,直到走完当前圈数。因为当前圈数能走的步数有限,所以需要更换起始位置(start++),即可最终解决问题。

关于具体逻辑要这样做,你可这么理解,当以固定步数绕圈圈时,数组被撕裂成了多少个互不相通的"平行宇宙",例如如果你跨出的步子 k 和跑道总长 n 都是某个数(比如 2)的倍数,那么你永远只能踩到这个数(2)的倍数的格子上。你就像被下了诅咒,永远踩不到奇数格。

gcd就是计算有多少个这样"平行宇宙",从而确定我们需要走的圈数。

但圈数为什么是gcd,证明如下:

第一步:找出"单圈长度"假设数组长度为 nnn,步长为 kkk。我们从起点 000 开始跳,每一步跳 kkk 个单位。我们要问:跳多少步之后,会第一次回到起点?跳的总距离:假设跳了 SSS 步,那么总距离是 S×kS \times kS×k。回到起点的条件:要回到起点(下标 0),意味着跳过的总距离必须是跑道长度 nnn 的整数倍。也就是说,总距离也是 R×nR \times nR×n(RRR 是跑的圈数)。结论:总距离必须既是 kkk 的倍数,又是 nnn 的倍数。第一次回到起点:意味着这是 最小公倍数 (LCM)。所以,单次遍历的总距离 = lcm(n,k)\text{lcm}(n, k)lcm(n,k)。那么,单次遍历走了多少步(也就是踩了多少个格子)?步数=总距离步长=lcm(n,k)k\text{步数} = \frac{\text{总距离}}{\text{步长}} = \frac{\text{lcm}(n, k)}{k}步数=步长总距离=klcm(n,k)第二步:利用 GCD 和 LCM 的关系数学中有一个非常著名的公式,描述了最大公约数 (GCD) 和最小公倍数 (LCM) 的关系:n×k=gcd(n,k)×lcm(n,k)n \times k = \text{gcd}(n, k) \times \text{lcm}(n, k)n×k=gcd(n,k)×lcm(n,k)我们可以把这个公式变形一下,求出 lcm(n,k)\text{lcm}(n, k)lcm(n,k):lcm(n,k)=n×kgcd(n,k)\text{lcm}(n, k) = \frac{n \times k}{\text{gcd}(n, k)}lcm(n,k)=gcd(n,k)n×k第三步:推导"单圈步数"现在我们将第二步的公式,代回到第一步的"步数"公式里:单圈步数=lcm(n,k)k=n×kgcd(n,k)k\text{单圈步数} = \frac{\text{lcm}(n, k)}{k} = \frac{\frac{n \times k}{\text{gcd}(n, k)}}{k}单圈步数=klcm(n,k)=kgcd(n,k)n×k此时,分子和分母里的 kkk 互相抵消了:单圈步数=ngcd(n,k)\text{单圈步数} = \frac{n}{\text{gcd}(n, k)}单圈步数=gcd(n,k)n这句话的意思是:在这个游戏中,你启动一次任务,只能覆盖到 ngcd(n,k)\frac{n}{\text{gcd}(n, k)}gcd(n,k)n 这么多个人。第四步:计算"需要的圈数"我们要覆盖数组里所有的 nnn 个元素。既然转一圈只能搞定 ngcd(n,k)\frac{n}{\text{gcd}(n, k)}gcd(n,k)n 个元素,那我们需要转几圈?圈数=总人数单圈能搞定的人数\text{圈数} = \frac{\text{总人数}}{\text{单圈能搞定的人数}}圈数=单圈能搞定的人数总人数圈数=nngcd(n,k)\text{圈数} = \frac{n}{\frac{n}{\text{gcd}(n, k)}}圈数=gcd(n,k)nn数学计算一下,分母的分母翻上去:圈数=gcd(n,k)\text{圈数} = \text{gcd}(n, k)圈数=gcd(n,k)证明完毕。

相关推荐
杰克尼2 小时前
蓝桥云课-5. 花灯调整【算法赛】
java·开发语言·算法
.小墨迹2 小时前
C++学习之std::move 的用法与优缺点分析
linux·开发语言·c++·学习·算法·ubuntu
wanghowie2 小时前
01.02 Java基础篇|核心数据结构速查
java·开发语言·数据结构
努力学算法的蒟蒻2 小时前
day38(12.19)——leetcode面试经典150
算法·leetcode·面试
搬砖魁首2 小时前
ZK-ALU-在有限域上实现乘法和除法
算法·zk·alu·域运算·算术逻辑单元·模乘·蒙哥马利模约简
iAkuya2 小时前
(leetcode)力扣100 17缺失的第一个正数(哈希)
算法·leetcode·哈希算法
断剑zou天涯2 小时前
【算法笔记】树状数组IndexTree
java·笔记·算法
sonadorje2 小时前
ECC公钥生成过程
算法·安全
声声codeGrandMaster2 小时前
线性回归实战下与深度学习概念
深度学习·算法·线性回归