LeetCode每日一题(三)——【轮转数组】

博客探讨了如何使用Python高效地实现数组右旋操作。通过翻转数组的技巧,只需两次翻转即可完成,避免了复杂循环。当k大于数组长度时,取k模数组长度作为实际旋转次数。博客提供了Python、C++和Java的实现代码,并强调了Python中切片操作的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


问题描述:

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

原因分析:【这是大佬总结的,在这里谢谢小甜甜大佬】

  • 此题可以采用头插法,一个一个的移动。但是有种更加简单的选择数组的方式。
  • 我们可以采用翻转的方式,比如12345经过翻转就变成了54321,
  • 这样已经做到了把前面的数字放到后面去,但是还没有完全达到我们的要求,
  • 比如,我们只需要把12放在后面去,目标数组就是34512,与54321对比发现我们就只需要在把分界线前后数组再进行翻转一次就可得到目标数组了。
  • 所以此题只需要采取三次翻转的方式就可以得到目标数组:
  • 首先翻转分界线前后数组,再整体翻转一次即可。
  • 此题面试常考,大家可以记一下此方法。
  • 需要注意的是,本题还有一个小陷阱,题目输入中,如果k大于nums.size了应该怎么办?**

解决方案:

1.如果k大于nums.size了应该怎么办?

举个例子,比较容易想,

例如,1,2,3,4,5,6,7 如果右移动15次的话,是 7 1 2 3 4 5 6 。 

所以其实就是右移 k % nums.size() 次,即:15 % 7 = 1

 官方没有给出python的方法,因为在python中,列表的切片真的是太灵活了。

class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        nums[: ] = (nums[i] for i in range(-(k % len(nums)), len(nums) - k % len(nums)))

这是大佬的代码。【优化后】

至于上述思路中的三次翻转,大佬只用了两次翻转。因为python对列表可以选取,所以不需要整体去翻转,只需要按照k值,截取移动长度,然后在拼接成一个,就可以。

【优化前的代码,理解起来也挺容易的】:

class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        nums[: ] = nums[-k % len(nums): ] + nums[: -k % len(nums)]

至于为什么开头的nums要变为nums[ : ] ,是因为:

不写切片相当于nums修改的地址重新指向右边的临时地址,写切片相当于按着切片下标修改值,前者在线上判定里无法AC,线上判定只判定原地地址的情况,不写切片的nums只在函数内有效

 如果换成其他语言,就可以循规蹈矩,按照之前的分析做即可:

1、python【俺自己写的,比较垃圾】

class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        if(len(nums)<2):
            return
        k = k%len(nums)-1
        nums[ : ] = nums[::-1]
        nums[ : ] = nums[k::-1]+nums[:k:-1]

2、 C++【大佬写的,侵删】

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        // 三次翻转搞定
        k = k % nums.size();
        reverse(nums.begin(), nums.begin() + nums.size() - k);
        reverse(nums.begin() + nums.size() - k, nums.end());
        reverse(nums.begin(), nums.end());
    }
};

3、Java 【大佬写的,侵删】

class Solution {
    private void reverse(int[] nums, int start, int end) {
        for (int i = start, j = end; i < j; i++, j--) {
            int temp = nums[j];
            nums[j] = nums[i];
            nums[i] = temp;
        }
    }
    public void rotate(int[] nums, int k) {
        int n = nums.length;
        k %= n;
        reverse(nums, 0, n - 1);
        reverse(nums, 0, k - 1);
        reverse(nums, k, n - 1);
    }
}

总结:

其实如果单纯用python自带的reverse函数做,其实和其他语言没有区别,

但是如果用python中的切片方法做,那么你得了解足够了解,不然很容易被弄糊涂【比如我】

感谢大佬们在Leecode中的精彩评论,感谢优快云中各位大哥的博客。

这是一篇博客,让我了解和复习了python切片,十分感谢:

精密解析Python切片用法_weixin_44099558的博客-优快云博客_python切片用法众所周知,我们可以通过索引值(或称下标)来查找序列类型(如字符串、列表、元组…)中的单个元素,那么,如果要获取一个索引区间的元素该怎么办呢?切片(slice)就是一种截取索引片段的技术,借助切片技术,我们可以十分灵活地处理序列类型的对象。通常来说,切片的作用就是截取序列对象,然而,对于非序列对象,我们是否有办法做到切片操作呢?在使用切片的过程中,有什么要点值得重视,又有什么底层原理值得关注呢?...https://blog.youkuaiyun.com/weixin_44099558/article/details/85764468

### LeetCode 189 题 Python 种解法 以下是针对 LeetCode 189 题的种不同 Python 实现方式,每种方法都具有其独特的逻辑和时间/空间复杂度特点。 #### 方法一:使用额外数组 这种方法通过创建一个新的临时数组来存储轮转后的结果,并最终将其复制回原始数组。此方法的时间复杂度为 \(O(n)\),空间复杂度也为 \(O(n)\)。 ```python class Solution: def rotate(self, nums: list[int], k: int) -> None: """ Do not return anything, modify nums in-place instead. """ n = len(nums) k %= n # 处理 k >= n 的情况 temp = [0] * n for i in range(n): temp[(i + k) % n] = nums[i] for i in range(n): nums[i] = temp[i] ``` 这种方法的核心在于利用 \((i + k) \% n\) 来计算每个元素的新索引位置[^1]。 --- #### 方法二:反转数组部分区域 该方法基于次反转操作完成数组轮转。具体步骤如下: 1. 反转整个数组; 2. 反转前 \(k\)元素; 3. 反转剩余的部分。 这种方案的空间复杂度为 \(O(1)\),因为它是原地修改;而时间复杂度仍为 \(O(n)\)[^3]。 ```python class Solution: def rotate(self, nums: list[int], k: int) -> None: """ Do not return anything, modify nums in-place instead. """ n = len(nums) k %= n # 处理 k >= n 的情况 self.reverse(nums, 0, n - 1) # 整体反转 self.reverse(nums, 0, k - 1) # 前 k 个元素反转 self.reverse(nums, k, n - 1) # 后面部分反转 def reverse(self, nums: list[int], start: int, end: int) -> None: while start < end: nums[start], nums[end] = nums[end], nums[start] start += 1 end -= 1 ``` 这里的关键是定义了一个辅助函数 `reverse` 来执行局部反转的操作[^4]。 --- #### 方法:环状替换(不借助额外数据结构) 这是一种更复杂的原地算法,它通过遍历并交换元素到它们的目标位置来实现轮转。虽然理论上它的平均性能较好,但在某些特殊情况下可能会退化至线性扫描多次,因此实际表现不如其他两种简单直观的方法稳定。 ```python class Solution: def rotate(self, nums: list[int], k: int) -> None: """ Do not return anything, modify nums in-place instead. """ n = len(nums) k %= n # 处理 k >= n 的情况 count = 0 for start in range(n): current_idx = start prev_val = nums[start] while True: next_idx = (current_idx + k) % n nums[next_idx], prev_val = prev_val, nums[next_idx] current_idx = next_idx count += 1 if start == current_idx or count >= n: break ``` 这个版本的主要挑战是如何处理当步长 \(k\) 和长度 \(n\) 存在公约数时可能出现的周期现象[^5]。 --- ### 总结 以上介绍了种不同的解决策略——分别是采用额外内存保存中间状态、仅靠变换顺序达到目的以及完全依赖于迭代更新的方式。尽管这些技术各有优劣之处,但对于大多数应用场景而言,推荐优先考虑第二种即“反转分区”的做法因为它既高效又易于理解维护。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

peter123123123123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值