这几天可被一道题愁死了,我废寝忘食但怎么都做不出来,好不容易想出来了一种方法吧,结果超时了,又想出一种办法吧,结果哦答案还对不上,给我气得,记录一下这坑爹题
- 我的超时方法,理解起来非常简单,就是一次只往右挪一位,循环k次就好
- 我还想了一个方法和答案第二种环转方法类似,但是咋都不对,下面重点讲一下这个,这个方法比较难,答案还用到了高代里的一些结论,但是作为数学组选手,俺就喜欢挑战这种方法
环转替代法
- 好家伙,那答案写的,我看了俩小时都没懂,结果看到一个圆圈,突然就理解了
- 这道题的解题关键就是得知道遍历次数,啥叫遍历次数呢?简单来说就是圈圈个数,这里的圈必须首尾一致,比如下面这个例子,圈圈个数是2(1351和2462)
- 答案里得到遍历次数的公式太牛啦太牛啦,这不看答案怎么知道啊?
- 关键公式
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就是往右行走的步数(题目给的)
- 这个公式咋来的呢,力扣的官方解释一开始我没看懂,但结合圆圈来理解就非常清晰了
-
- 这里a=1,n=6,k=2,b=3
- 再来个例子
- 这里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
数组翻转法
- 最简单,没有什么深度理解的地方,就是翻转3次
- 关于翻转的代码可以看一下,指定两个指针,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);
}
};