问题描述
给定一个python的list
和一个正整数k
,考虑将数组内的元素向右shift
k个位置(感觉用shift表示比较丝滑一些,其实类似于循环移位操作)。
e.g.
list_to_be_shifted=[1,2,3,4,5,6,7]
bitnum = 3
shift(list)
>>> list
[5, 6, 7, 1, 2, 3, 4]
问题本身的解决方案很多,但是题目要求使用空间复杂度为O(1)
来实现,也就是利用原数组就地正法
,这样一来对题目就多了不少限制。
开干
解法一
最直接的办法当然就是直接开干一波带走,你让我移k
个位置,那我就一个个来呗,看谁先把谁送走了(这玩笑开不得,毕竟它只是一堆破 铜 烂 铁
,Markdown不需要引号)。所以干法如下:
def shift(self, nums: list, k: int) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
while k > 0:
temp = nums[-1]
for i in range(len(nums) - 1):
nums[len(nums) - 1 - i] = nums[len(nums) - 2 - i]
nums[0] = temp
k = k - 1
值得注意的一点是,当数组长度len(nums)<k
时,我们并不需要老老实实移动k
次,将k
直接对len(nums)
取模即可,因此上面可以加上一句
k = k % len(nums)
鲁迅先生说过(好吧其实不是他说的):只要是循环结构我们都可以轻易地把它变成递归。但是在这里使用递归的话其实增加了堆栈开销,并不满足要求的空间复杂度,这里只是给一个思路:
def shift_recursion(self, nums: list, k: int) -> None:
"""
Do not return anything, modify nums in-place instead.
"""
k = k % len(nums)
if k > 0:
temp = nums[-1]
for i in range(len(nums) - 1):
nums[len(nums) - 1 - i] = nums[len(nums) - 2 - i]
nums[0] = temp
self.rotate_recursion(nums, k - 1)
这一做法本质上与循环结构没有大的区别,不再多说。
解法二,Reverse真香!
其实抛开一位位移动的想法,我们很容易就可以想到,为什么不一下子把后面待移动的k
位直接放到前面去?这样子不是既省时又省力吗?好家伙,就这么干。稍加思索我们不难发现,所谓的这种移位操作,可以直接通过三次反转来实现!各位看官们,动一动小脑筋,问题就是这么简单(其实俺也没想到可以这么干,Leetcoder牛逼就完事了,自己画了一张丑图,大家将就着看看)。
可以看到,只需要简单的三次Reverse
即可实现题目所要求的功能,可以说是大道至简,具体的python实现如下:
def reverse(self, array: list, begin: int, end: int) -> None:
while begin < end:
temp = array[end]
array[end] = array[begin]
array[begin] = temp
begin += 1
end -= 1
def shift_reverse(self, nums: list, k: int) -> None:
if k == 0:
return
if k > len(nums):
k = k % len(nums)
nums.reverse()
self.reverse(nums, 0, k - 1)
self.reverse(nums, k, len(nums) - 1)
以上代码可以完美实现所要求的功能,且时间复杂度大大降低(使用上面逐个移位的方法对于大型的输入数组是会timeout的…大家注意了)。
解法三,充分利用Python特性
对于Python的list对象,负索引是一个创造性的发明(反正俺在以前学过的语言里面没见过,暂且认为它是具有创造性的叭,有问题提请大家指正!),这样一来,本身我就可以把数组的一个位置当做数组的头部,对数组进行重排列,这正是我们这一问题的本源!其实所谓的旋转,不过是换了数组的起始位置,旋转两位,其实就是从 -2位开始当做数组头,一直到-3位到达数组尾(当然这里的 -3也就是 +(len(nums) - k)),这是我们重构数组时索引结束的位置,那么使用列表生成式直接构建旋转后的数组的两种方法如下:
# method1
# Actually for python, we can use list generator and negative index
def rotate_generator(self, nums: list, k: int) -> None:
nums[:] = nums[-k: len(nums)] + nums[0: -k]
# method2
def rotate_generator_new(self, nums: list, k: int) -> None:
nums[:] = [nums[i] for i in range(- (k % len(nums)), len(nums) - (k % len(nums)))]
关于上述两种方法是否满足空间复杂度为 **O(1)**的要求,还请大家自行理解(就我个人的实验结果来看,方法一在旋转前后,nums指针的值未发生变化,至少说没有开辟新的堆内存空间,但是我觉得它不是空间复杂度为O(1)的实现方法,如有错误还请指正)。