力扣之轮转数组(玩转取余操作)

文章目录

  • 环转替代法
  • 数组翻转法

这几天可被一道题愁死了,我废寝忘食但怎么都做不出来,好不容易想出来了一种方法吧,结果超时了,又想出一种办法吧,结果哦答案还对不上,给我气得,记录一下这坑爹题
力扣之轮转数组(玩转取余操作)_第1张图片

  • 我的超时方法,理解起来非常简单,就是一次只往右挪一位,循环k次就好
  • 我还想了一个方法和答案第二种环转方法类似,但是咋都不对,下面重点讲一下这个,这个方法比较难,答案还用到了高代里的一些结论,但是作为数学组选手,俺就喜欢挑战这种方法

环转替代法

  • 好家伙,那答案写的,我看了俩小时都没懂,结果看到一个圆圈,突然就理解了
  • 这道题的解题关键就是得知道遍历次数,啥叫遍历次数呢?简单来说就是圈圈个数,这里的圈必须首尾一致,比如下面这个例子,圈圈个数是2(1351和2462)
  • 力扣之轮转数组(玩转取余操作)_第2张图片
  • 答案里得到遍历次数的公式太牛啦太牛啦,这不看答案怎么知道啊?
  • 关键公式 a n = b k an=bk an=bk
    • a a a是圈数,即完成一次遍历要绕数组几圈。 n n n是整个数组的长度, b b b是每次遍历的元素个数,比如上个例子里的 b = 3 b=3 b=3 k k k就是往右行走的步数(题目给的)
    • 这个公式咋来的呢,力扣的官方解释一开始我没看懂,但结合圆圈来理解就非常清晰了
    • 力扣之轮转数组(玩转取余操作)_第3张图片
      • 这里a=1,n=6,k=2,b=3
    • 再来个例子力扣之轮转数组(玩转取余操作)_第4张图片
      • 这里a = 2,n=3,k=2,b=3
    • 所以 a n = b k an=bk an=bk的解释就是遍历一次走过的距离
      • a n an an是根据转圈数得到,因为首尾一致,肯定是圈,每个圈又有n个元素
      • b k bk bk是根据遍历的元素×迈开步子的长度
  • 以上证明了 a n = b k an=bk an=bk成立的合法性,接着要求 b b b的大小了,因为只要求得b的大小,就能求遍历次数 = n b =\frac{n}{b} =bn,这个公式应该好理解,只要认同每个遍历都包含相同的元素个数即可
  • 现在已知得到是 n , k n,k n,k,怎样能不通过 a a a来求 b b b呢?高级的知识点来了: a n an an肯定是 n n n的倍数毋庸置疑, a n an an也是 k k k的倍数这也毋庸置疑,然后肯定要 a a a取最小,是可以无限转圈,但俺们要求用最小的圈数回到原点,所以可以得到 a n = l c m ( n , k ) an=lcm(n,k) an=lcm(n,k),即 a n an an n , k n,k n,k的最小公倍数
  • 因此就有 b = a n k = l c m ( n , k ) k b=\frac{an}{k}=\frac{lcm(n,k)}{k} b=kan=klcm(n,k)
  • 于是数组一共要遍历的次数为 n b = n k l c m ( n , k ) = g c d ( n , k ) \frac{n}{b}=\frac{nk}{lcm(n,k)}=gcd(n,k) bn=lcm(n,k)nk=gcd(n,k)
    • g c d gcd gcd是最大公约数
    • 解释一下这个式子,为什么 n k nk nk除以最大公倍数就等于最小公约数了呢,高代里有证明啊,可以去看,直观可以这么理解
    • (nk/nk的最大公约数gcd)保证了这个数被拆解成质数的乘积里,肯定没有重复的数,这不就是最大公倍数嘛!
  • 然后在每个遍历里,每个位置的元素都有自己想要去的地方,同时每个位置又都被其他位置的元素惦记
  • 所以指定一个temp/prev,表示某个位置要出去的那个值,比如0位置的元素要出去找下家了,temp=nums[0]=1,那哪位下家能取这个值呢,是 x = ( 0 + k ) m o d      n x=(0+k)\mod\ n x=(0+k)mod n呐,于是 n u m s [ x ] = t e m p nums[x] = temp nums[x]=temp
  • 然后就是要得到遍历停止的条件了,这里的遍历停止指的是一个圈结束哈,标记开始的位置 s t a r t start start,记录现在的位置 c u r r e n t current current,当 c u r r e n t = s t a r t current=start current=start时,一次遍历结束
  • 直接看代码吧
class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        n = len(nums)
        count = gcd(n,k)
        start = 0        
        for start in range(0,count):
            #这里容易出错,没有取余运算的话可能会出现current>n-1的情况
            current = (start+k)%n
            prev = nums[start]
            while current!=start:
            	#python里的swap就是这么简单粗暴
                nums[current], prev = prev,nums[current]              
                #这一步是最妙的,一个式子实现完美转圈圈,我竟然还分了两种情况0~k-1和k~n-1
                current = (current+k) % n
            nums[start] = prev 

力扣之轮转数组(玩转取余操作)_第5张图片

数组翻转法

  • 最简单,没有什么深度理解的地方,就是翻转3次
  • 力扣之轮转数组(玩转取余操作)_第6张图片
  • 关于翻转的代码可以看一下,指定两个指针,start和end,从左右往中间夹击
    C++代码
class Solution {
public:
    void reverse(vector<int>& nums, int start, int end) {
        while (start < end) {
            swap(nums[start], nums[end]);
            start += 1;
            end -= 1;
        }
    }

    void rotate(vector<int>& nums, int k) {
        k %= nums.size();
        reverse(nums, 0, nums.size() - 1);
        reverse(nums, 0, k - 1);
        reverse(nums, k, nums.size() - 1);
    }
};

你可能感兴趣的:(leetcode,windows,vscode,编辑器)