首页 > 其他分享 >LeetCode面试150——189轮转数组

LeetCode面试150——189轮转数组

时间:2024-07-28 14:26:52浏览次数:21  
标签:150 ListNode nums int next current 数组 189 LeetCode

题目难度:中等

默认优化目标:最小化平均时间复杂度。

Python默认为Python3。

1 题目描述

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

示例 2:

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

提示:

  • 1 <= nums.length <= 105

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

  • 0 <= k <= 105

进阶:

  • 尽可能想出更多的解决方案,至少有 三种 不同的方法可以解决这个问题。

  • 你可以使用空间复杂度为 O(1)原地 算法解决这个问题吗?

2 题目解析

输入是一个整数数组nums,输出是元素向右轮转k个位置后的数组。

暴力求解就是另外开辟一个数组,把nums中的元素按照题目要求一个一个放进去。这显然去面试的人每个人应该都会,所以不推荐,没优势。另外一种就是循环指针,把数组的首尾连接起来,依次向右轮转。

3 算法原理及程序实现

3.1 暴力求解

我们新开辟一个数组temp,然后遍历nums,将nums中的元素轮转k个位置取出放入temp,最后再用temp更新nums

平均时间复杂度O(n),平均空间复杂度O(n)。

C++代码实现

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        int n=nums.size();
        vector<int> temp(n);
​
        for(int i=0;i<n;i++){
            temp[(i+k)%n]=nums[i];//轮转
        }
        nums.assign(temp.begin(),temp.end());//将temp中的元素全部复制到nums
​
    }
};

Python代码实现

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

3.2 循环链表

我们将数组nums首尾相连,用一个指针指向原来尾的位置,向左移动k个位置后断开,就产生了新的头尾。这样新的数组就是所求的数组。但毕竟这是数组不是链表,没这么方便。将数组转换成列表,费时又费空间。

平均时间复杂度O(n),平均空间复杂度O(n)。

C++代码实现

class Solution {
public:
    struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(nullptr) {}
    };
    
    void rotate(std::vector<int>& nums, int k) {
        if (nums.empty() || k == 0) return;
​
        // 将数组转换为链表
        ListNode* head = new ListNode(nums[0]);
        ListNode* current = head;
        for (int i = 1; i < nums.size(); ++i) {
            current->next = new ListNode(nums[i]);
            current = current->next;
        }
​
        // 找到链表的尾部并连接成环
        ListNode* tail = current;
        tail->next = head;
​
        // 找到新的尾部和头部
        int stepsToNewHead = nums.size() - k % nums.size();
        ListNode* newTail = tail;
        for (int i = 0; i < stepsToNewHead; ++i) {
            newTail = newTail->next;
        }
        ListNode* newHead = newTail->next;
​
        // 断开环
        newTail->next = nullptr;
​
        // 将链表转换回数组
        current = newHead;
        for (int i = 0; i < nums.size(); ++i) {
            nums[i] = current->val;
            current = current->next;
        }
​
        // 释放链表内存
        while (newHead) {
            ListNode* temp = newHead;
            newHead = newHead->next;
            delete temp;
        }
    }
};

Python3代码实现

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None
​
class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        if not nums or k == 0:
            return
​
        # 将数组转换为链表
        head = ListNode(nums[0])
        current = head
        for num in nums[1:]:
            current.next = ListNode(num)
            current = current.next
​
        # 找到链表的尾部并连接成环
        tail = current
        tail.next = head
​
        # 找到新的尾部和头部
        steps_to_new_head = len(nums) - k % len(nums)
        new_tail = tail
        for _ in range(steps_to_new_head):
            new_tail = new_tail.next
        new_head = new_tail.next
​
        # 断开环
        new_tail.next = None
​
        # 将链表转换回数组
        current = new_head
        for i in range(len(nums)):
            nums[i] = current.val
            current = current.next

3.3 环状替代

要想空间复杂度为O(1),我们不能使用额外的数组,也就是我们只能用一个零时变量temp来保存要被替代的元素。

替换规则为(i+k)%nnnums的长度。要保证每个元素都被替换,我们就要解决循环终止条件。假设我们走了a圈,遍历了b个元素,则有an=bk。an一定是n、k的公倍数。我们希望a越小越好,故an是n、k的最小公倍数lcm(n,k)。所以b=lcm(n,k)/k。所以我们需要遍历的次数为


\frac{n}{lcm(nk)/k}=\frac{nk}{lcm(n,k)}=gcd(n,k)
 

gcd为最大公约数。

平均时间复杂度为O(n),平均空间复杂度为O(1)。

C++代码实现

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        int n = nums.size();
        k = k % n;//防止k超过n
        int count = gcd(k, n);//确定循环次数
        for (int start = 0; start < count; ++start) {
            int current = start;
            int prev = nums[start];//记录当前元素
            do {//交换操作
                int next = (current + k) % n;
                swap(nums[next], prev);
                current = next;
            } while (start != current);
        }
    }
};

Python代码实现

class Solution:
    def rotate(self, nums: List[int], k: int) -> None:
        n = len(nums)
        k = k % n  # 防止k超过n
        count = gcd(k, n)  # 确定循环次数
        for start in range(count):
            current = start
            prev = nums[start]  # 记录当前元素
            while True:  # 交换操作
                next_idx = (current + k) % n
                nums[next_idx], prev = prev, nums[next_idx]
                current = next_idx
                if start == current:
                    break

3.4 数组翻转

该方法的原理:数组中的元素全部向右移动k位置,尾部k%n个元素会移到数组头部,其余元素会向后移动k%n个位置。

该方法步骤:①先将所有元素翻转②翻转[0,k%(n-1)]区间内的元素③翻转[k%n,n-1]区间内的元素。

第一步让尾部元素到头部,第二部让新的头部元素顺序正确,第三步让新的尾部元素顺序正确。

示例如下,n=7,k=3。

操作结果
nums[1,2,3,4,5,6,7]
步骤①[7,6,5,4,3,2,1]
步骤②[5,6,7,4,3,2,1]
步骤③[5,6,7,1,2,3,4]

平均时间复杂度为O(n),平均空间复杂度为O(1)。

C++代码实现

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        int n=nums.size();
​
        k%=n;
        reverse(nums.begin(),nums.end());
        reverse(nums.begin(),nums.begin()+k);
        reverse(nums.begin()+k%n,nums.end());        
​
    }
};

Python代码实现

class Solution:
    def rotate(self, nums, k):
        n = len(nums)
        k %= n
        nums.reverse()
        nums[:k] = reversed(nums[:k])
        nums[k:] = reversed(nums[k:])

4 题目难度

这道题难在用O(1)的空间复杂度完成。相比于环状替代,数组翻转方法既好理解效果又好。

参考文献

力扣面试经典150题

力扣官方题解

标签:150,ListNode,nums,int,next,current,数组,189,LeetCode
From: https://blog.csdn.net/Junmo12345/article/details/140750314

相关文章

  • (BS ISO 11898-1:2015)CAN_FD 总线协议详解5- MAC子层描述3
    目录 创作不易,请帮忙点赞+评论+转载,非常感谢5.4.3MACRF(远程帧)规范5.4.3.1描述5.4.3.2MACDF和MACRF相同的字段5.4.3.3仲裁字段5.4.3.4控制字段5.4.4错误帧(EF)的规范5.4.4.1描述5.4.4.2错误标志5.4.4.3错误分隔符5.4.5过载帧(OF)的规定5.......
  • luogu P1896 [SCOI2005] 互不侵犯 题解
    luoguP1896[SCOI2005]互不侵犯题解题目传送门思路状态压缩dp。状态压缩dp对于每一行,用一个\(n\)位二进制数表示每行的状态,则对于上下两行之间,设上行的数字为\(a\),下行的数字为\(b\),状态不合法有三种情况:\(a\operatorname{and}b\neq0\),即存在上行与下行同......
  • leetcode-7
    题目:给你一个32位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。如果反转后整数超过32位的有符号整数的范围 [−231, 231 −1] ,就返回0。推导:代码:classSolution{public:intreverse(intx){intres=0;while(x!=0){......
  • LeetCode222.完全二叉树的节点个数
    题目链接:https://leetcode.cn/problems/count-complete-tree-nodes/description/题目叙述给你一棵完全二叉树的根节点root,求出该树的节点个数。完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该......
  • leetcode-6
    题目:将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z字形排列。推导:代码:classSolution{public:stringconvert(strings,intnumRows){if(numRows<2){returns;}vector<string>rows(n......
  • leetcode热题100.最长有效括号(动态规划完结篇)
    今天给大家带来一道leetcode经典题,最长有效括号,本文将介绍动态规划解法解法,希望对你有所帮助。这是动态规划系列的最后一题了,想要看之前动态规划的题解的小伙伴可以去热题100专栏获取......
  • luogu P1896 [SCOI2005] 互不侵犯 题解
    luoguP1896[SCOI2005]互不侵犯题解题目传送门思路状态压缩dp。状态压缩dp对于每一行,用一个\(n\)位二进制数表示每行的状态,则对于上下两行之间,设上行的数字为\(a\),下行的数字为\(b\),状态不合法有三种情况:\(a\operatorname{and}b\neq0\),即存在上行与下行同......
  • 代码随想录算法训练营第22天-leetcode-回溯算法part01:
    #回溯算法理论基础能解决的问题:组合问题:N个数里面按一定规则找出k个数的集合切割问题:一个字符串按一定规则有几种切割方式子集问题:一个N个数的集合里有多少符合条件的子集排列问题:N个数按一定规则全排列,有几种排列方式棋盘问题:N皇后,解数独等等第77题.组合力扣题目链接(op......
  • (leetcode学习)236. 二叉树的最近公共祖先
    给定一个二叉树,找到该树中两个指定节点的最近公共祖先。百度百科中最近公共祖先的定义为:“对于有根树T的两个节点p、q,最近公共祖先表示为一个节点x,满足x是p、q的祖先且x的深度尽可能大(一个节点也可以是它自己的祖先)。”示例1:输入:root=[3,5,1,6,2,0,8,nul......
  • leetcode105. 从前序与中序遍历序列构造二叉树,步骤详解附代码
    leetcode105.从前序与中序遍历序列构造二叉树给定两个整数数组preorder和inorder,其中preorder是二叉树的先序遍历,inorder是同一棵树的中序遍历,请构造二叉树并返回其根节点。示例1:输入:preorder=[3,9,20,15,7],inorder=[9,3,15,20,7]输出:[3,9,20,nul......