Leetcode数据结构算法题轮转数组(顺序表篇)

Posted 大家好我叫张同学

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Leetcode数据结构算法题轮转数组(顺序表篇)相关的知识,希望对你有一定的参考价值。

题目内容:

给你一个数组,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。


leetcode题目链接(点击即可跳转)


思路分析

看完题目后,我们首先要做的就是理解题目的含义,也就是审题。从不同维度,不同角度的去设问题并主动回答这些问题。题目中出现了一个我们比较陌生的词语“轮转”,可能我们一下子无法理解这个词是啥子玩意儿。这个时候就可以借助题目中的示例来帮助理解。比如说示例1:

用通俗的话来说就是,向右轮转1步,就是将最后一个元素放到第一个位置,其余元素位置向右移动一个位置。类似于所有元素向右滑动一个位置,只不过最后一个元素向右滑动的时候需要回到第一个位置。

理解了“轮转”的含义后,为了更好的理解题目的要求和隐藏含义,我们需要从以下几个角度进行自我设问:
(1)题目中给的数组有告诉我们数组的长度能否为0?
(2)向右轮转k次的k的实际取值如何,能不能为0?能不能超过数组本身的长度?
关于(1)(2)这两个问题,我们可以看题目的提示部分:

根据提示的内容我们可以清楚的知道,数组的长度至少为1,不能为0。这就解决了问题(1)。另外k的取值范围是0到10^5,虽然没有直接表明k与数组长度之间的关系,但是我们可以猜测,k的大小可以超过数组的长度。也就是说k可以等于0,可以超过数组本身的长度(可以一直向右转啊转),这就解决了问题(2)。这两个问题想清楚后,就可以进行相应的算法分析和设计了。


方法一:k次轮转法

这个方法其实可以通过题目给的示例直接想到。
为了简化问题,我们可以让k先为1,这样就是向右轮转一步。
如果k=2,那么先向右轮转一步,再向右轮转一步。
如果k=3…(套娃套娃再套娃)
另外,当k的大小超过数组的长度时,比如数组长度为5,k=6,这时候向右进行k次轮转,相当于前面5次轮转白做了,只进行了最后1次轮转,因为轮转6次和轮转1次的效果是一样的。所以实际的轮转效果应该为 k=k%(数组的长度)。(这里的轮转效果就是相当于轮转多少次…我随便创造的词方便理解…看不懂也没关系…)

算法图解


函数接口实现

void rotate(int* nums, int numsSize, int k)
    //求出实际轮转效果
    k = k % numsSize;
    //k为多少,就向右轮转多少次
    for(int i = 0; i <k; i++)
        //向右轮转一次的过程
        int temp = nums[numsSize-1];//先保存最后一个元素
        //其余元素向右移动一个位置
        //为了防止数据被覆盖,需要从后往前依次移动
        for(int j = numsSize - 1; j > 0; j--)
            nums[j] = nums[j-1];
        
        nums[0] = temp;
    

当我们辛辛苦苦写完代码,通过测试用例,进行提交的时候,突然出现了这个

此时心中应该有一万个问号????飘过。

郁闷的情绪发泄完后,我们还得老老实实的回来分析报错原因,然后去解决它。(生活不也正是这样这样子吗?尽管我们会遇到各种各样的挫折、痛苦,但是我们要想生活变得更好,还是需要去勇敢面对它们,去解决它们。逃避,永远都解决不了问题,只能解决我们自己。)
代码提交未通过的原因是“超出时间限制”,换句话来说就是“程序运行花的时间太长了”,“时间复杂度未达到要求”。通过分析k次轮转法算法的时间复杂度,为O(n^2)。(用了两层循环)为了减少时间复杂度,提高算法的运行速度,一种通常的做法就是“空间换时间”,也就是说通过提高空间复杂度来降低时间复杂度。(学习算法、数据结构的同学对这种思想应该不陌生)把这种思想应用到本题中,就是通过创建一个临时数组,达到降低时间复杂度的效果。
时间复杂度、空间复杂度相关文章:
【数据结构学习笔记】一、数据结构介绍及算法分析(新手入门进阶指南)


方法二:临时数组法

创建临时数组,可以达到“空间换时间”的目的,这个时候我们还需要回到题目中,看看题目是否对空间复杂度有要求,可以本题对空间复杂度无要求。那就说明这种方法是适用的。
假设k不超过数组的长度,向右轮转k次,相当于将后面k个元素移到前面去,将前面的元素向右滑动k个位置。

算法图解:


函数接口实现

void rotate(int* nums, int numsSize, int k)
    //求出实际轮转次数
    k %= numsSize;
    //开辟大小为k的临时数组
    int* arr = (int*)malloc(k*sizeof(int));
    //将nums数组后面k个元素放到临时数组中
    int i = numsSize - k;
    int j = 0;
    while(i < numsSize)
        arr[j++] = nums[i++];
    
    //将nums前面的元素从后往前向后移动k个位置
    for(i = numsSize-k-1; i >=0; i--)
        nums[i+k] = nums[i];
    
    //将临时数组的元素拷贝回nums数组中
    for(j = 0; j < k; j++)
        nums[j] = arr[j];
    


采用临时数组的方式虽然成功的解决了问题,但其空间复杂度为O(n)。假设当我们要处理的数据很多的时候,那么开辟临时数组所需要的空间就会很大,有没有办法解决这个问题呢?也就是说在保证时间复杂度为O(n)的情况下,降低空间复杂度呢?


方法三:三步翻转法

这种方式可能我们大多数人都想不出来(我一开始也没想出来🙂),因为这种方法要通过观察规律,然后提炼总结出来。(其实就是要接触过,做过相关题目,比如说反转字符串,翻转字符串,轮转字符串等等)。
三步翻转法的核心思想是:

1)先翻转数组后k个元素 (也可以先翻转前面的元素)
2)再翻转数组前面的元素
3)将数组整体翻转一下

就能得到最终的结果,是不是很神奇,有没有感觉跟变魔术一样?我只想膜拜第一个想出这种方法的大佬Orz。

算法图解


函数接口实现

void reverse(int* arr,int begin,int end)
    while(begin < end)
        int temp = arr[begin];
        arr[begin] = arr[end];
        arr[end] = temp;
        begin++;
        end--;
    


void rotate(int* nums, int numsSize, int k)
    k %= numsSize;
    reverse(nums,0,numsSize-k-1);//先翻转数组前面的元素
    reverse(nums,numsSize-k,numsSize-1);//再翻转数组后面k个元素
    reverse(nums,0,numsSize-1);//最后整体翻转一下


可能这篇文章您阅读完只花了几分钟,甚至几秒钟,但却花了我数个小时甚至一整天的时间┭┮﹏┭┮
原创不易,各位小伙伴点个赞,评论+关注呗~
(小手点赞,水逆退散,逢考必过,诸事顺利~)

以上是关于Leetcode数据结构算法题轮转数组(顺序表篇)的主要内容,如果未能解决你的问题,请参考以下文章

Leetcode数据结构算法题数组形式的整数加法(顺序表篇)

Leetcode数据结构算法题删除有序数组中的重复项(顺序表篇)

LeetCode数组算法题(Java版)

Leetcode -面试题17.04.消失的数字 -189.轮转数组

LeetCode与《代码随想录》哈希表篇:做题笔记与总结-JavaScript版

Leetcode数据结构算法题原地移除元素(顺序表练习题)