题目描述

给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。

附加要求

  • 尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。
  • 你可以使用空间复杂度为 O(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]
输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释:
向右旋转 1 步: [99,-1,-100,3]
向右旋转 2 步: [3,99,-1,-100]

提示

  • 1 <= nums.length <= 2 * 104
  • -231 <= nums[i] <= 231 - 1
  • 0 <= k <= 105

解法1(直接新开数组赋值)

思路

创建一个一模一样大小的数组,按照题目描述的规则进行赋值,最后再把值赋给原本的数组

代码

class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n = nums.size();
int temp[n];
memset(temp,0,sizeof(int)*n);
k %= n;
for(int i = 0;i < k;i++){
temp[i] = nums[n-k+i];
}
for(int i = k;i < n;i++){
temp[i] = nums[i-k];
}
for(int i = 0;i < n;i++)
nums[i] = temp[i];
}
};

复杂度分析

时间复杂度O(n)

空间复杂度O(n)

解法2(环状替换)

思路

对于一个数组,如a[7] = [1, 2, 3, 4, 5, 6, 7],当k = 1时,数组最终会变成 a'[7] = [7, 1, 2, 3, 4, 5, 6],对于每一个数组元素而言,新的位置下标x2与原位置下标x1的关系为x2 = (x1+1) % n,例如数组元素 7 的下标 a[6] -> a[0],(6+1) % 7 = 0。经过观察,可以较为容易的发现每个数组元素的新下标x2 = (x1+k) % n。

接下来要做的就是将每个数组元素的下标都变换一次,如果按照数组元素的顺序开始循环,会导致一部分元素被覆盖,得不到记录,因此这里新定义一种变换方法,即从第一个数组元素开始,依次变换它的新下标的元素的下标,以数组 a2 = [1, 2, 3, 4, 5, 6],k = 2为例,即a[0] -> a[2],使用一个中间变量 temp记录 a[2] 的值,在a[0] 完成变换后紧接着对a[2]进行变换。

算法模拟1:数组 1 2 3 4 5 6 7,k = 3
a[0] -> a[(0+3)%7 = 3],a[3] -> a[(3+3)%7 = 6],a[6] -> [(6+3)%7 = 2],a[2] -> a[(2+3)%5 = 5],a[5] -> a[(5+3)%7 = 1],a[1] -> a[(1+3)%7 = 4],a[4] -> a[(4+3)%7 = 0],a[0] -> ...

从a[0]开始到重新回到a[0]的过程,我将其定义为一个环,在这个例子中,一次环的遍历即可完成所有数组元素下标的变换,接下来看另一个例子。

算法模拟2:数组 1 2 3 4,k = 2
a[0] -> a[(0+2)%4 = 2],a[2] -> a[(2+2)%4 = 0],a[0] -> ...

算法模拟2中,一个环显然不能解决问题,因此当再次回到a[0]的时候,需要从a[0]的下一个元素开始,继续完成下标变换。

  • 问题:如何才能保证将数组中的每个元素都遍历到?

  • 解决方案A:使用一个变量记录当前已经遍历过元素的个数,由于每个元素都需要改变下标,因此可以使用一个中间变量count,每完成一次下标变换,count++,直到count = n,结束循环。

  • 解决方案B:计算出需要循环的次数,首先假设这个数组是往右无线延伸的,那么整个过程如下

数组A 1 2 3 4 5 6 7,k = 3
1 2 3 4 5 6 7 1 2 3 4 5 6 7 1 2 3 4 5 6 7 1 2 3 4 5 6 7 ...
1 --->4---->7---->3---->6---->2---->5---->1 ...
数组B 1 2 3 4,k = 2
1 2 3 4 1 2 3 4
1-->3-->1
2-->4-->2

经过观察可以发现这样一个数学关系式:遍历过的数组数量 a * 数组长度 n = 遍历过的元素个数 b * 移动的距离 k,对于数组A,3*7 = 7*3,对于数组B,1*4 = 2*2(有两个这样的式子)。

现在将这个关系式推广到一般情况,其中数组长度 n 是已知,现在已经遍历过 b 个元素,那么接下来需要知道还要经过多少次这样的遍历就可以遍历完所有元素,记为count,且count为正整数。b = an/k,an需要尽可能的小,因为多的话又回到了起点,又开始重复之前的遍历了,a,n,b,k 均为正整数,an 是 n 的倍数,an 是 k 的 b 倍,要让 a 尽可能小,an 的值应该为 n,k的最小公倍数,记为lcm(n,k),那么就有 bk = lcm(n,k),b = lcm(n,k)/k。这样一次遍历就遍历了 b 个元素,那么 n 个元素需要 count = n/b = nk/lcm(n,k) = gcd(n,k),gcd指最大公约数。

至此,循环的次数就确定下来了,接下来只需要写对应的代码就行了。

代码

//解决方案A
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n = nums.size();
k %= n;
int count = 1;
int start = 0;
while(count <= n){
int current = start;
int prev = nums[start];
do{
int i = (current+k)%n;
int temp = nums[i];
nums[i] = prev;
prev = temp;
current = i;
count++;
}while(current != start);
start++;
}
}
};
//解决方案B
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n = nums.size();
k %= n;
if(k == 0)
return;
int count = gcd(n,k);
for(int i = 0;i < count;i++){
int current = i;
int prev = nums[i];
do{
int j = (current+k) % n;
int temp = nums[j];
nums[j] = prev;
prev = temp;
current = j;
}while(current != i);
}
}
int gcd(int n,int k){
int m = k;
while(n%k){
m = n%k;
n = k;
k = m;
}
return m;
}
};

复杂度分析

时间复杂度O(n)

空间复杂度O(1)

解法3(Reverse)

思路

翻转数组,先将整个数组翻转,再翻转前k个数,最后再翻转剩余的部分,翻转数组可以使用位运算,即使用异或运算实现数值交换。原理如下

定义:对于任一位向量a,有a^a=0,a^0=a
现假定有 a,b
a = a^b
b = a^b //此时的 a = a^b,则b = a^b^b = a
a = a^b //此时的 a = a^b,b = a,则 a = a^b^a = b

除此之外,也可以使用相加寄存的方式实现数值交换,原理如下

a = a+b
b = a-b //此时a = a+b,则b = a+b-b = a
a = a-b //此时a = a+b,b = a,则a = a+b-a = b

代码

class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n = nums.size();
k %= n;
if(k == 0)
return;
reverse(nums, 0, n - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, n - 1);
}
void reverse(vector<int>&nums,int l,int r){
while (l < r) {
nums[l] = nums[l] ^ nums[r];
nums[r] = nums[l] ^ nums[r];
nums[l] = nums[l] ^ nums[r];
l++;
r--;
}
}
};
Reverse()函数重写,其余部分一样
void reverse(vector<int>&nums,int l,int r){
while (l < r) {
nums[l] = nums[l] + nums[r];
nums[r] = nums[l] - nums[r];
nums[l] = nums[l] - nums[r];
l++;
r--;
}

复杂度分析

时间复杂度O(2n) = O(n)

空间复杂度O(1)

C++旋转数组(三种解法详解)的更多相关文章

  1. [转]hibernate三种状态详解

    本文来自 http://blog.sina.com.cn/u/2924525911 hibernate 三种状态详解 (2013-04-15 21:24:23) 转载▼   分类: hibernate ...

  2. 多表连接的三种方式详解 hash join、merge join、 nested loop

    在多表联合查询的时候,如果我们查看它的执行计划,就会发现里面有多表之间的连接方式.多表之间的连接有三种方式:Nested Loops,Hash Join 和 Sort Merge Join.具体适用哪 ...

  3. 分布式事务 Seata Saga 模式首秀以及三种模式详解 | Meetup#3 回顾

    https://mp.weixin.qq.com/s/67NvEVljnU-0-6rb7MWpGw 分布式事务 Seata Saga 模式首秀以及三种模式详解 | Meetup#3 回顾 原创 蚂蚁金 ...

  4. Android 三种动画详解

    [工匠若水 http://blog.csdn.net/yanbober 转载请注明出处.点我开始Android技术交流] 1 背景 不能只分析源码呀,分析的同时也要整理归纳基础知识,刚好有人微博私信让 ...

  5. leetcode#42 Trapping rain water的五种解法详解

    leetcode#42 Trapping rain water 这道题十分有意思,可以用很多方法做出来,每种方法的思想都值得让人细细体会. 42. Trapping Rain WaterGiven n ...

  6. PHP实现链式操作的三种方法详解

    这篇文章主要介绍了PHP实现链式操作的三种方法,结合实例形式分析了php链式操作的相关实现技巧与使用注意事项,需要的朋友可以参考下 本文实例讲述了PHP实现链式操作的三种方法.分享给大家供大家参考,具 ...

  7. Spring依赖注入三种方式详解

    在讲解Spring依赖注入之前的准备工作: 下载包含Spring的工具jar包的压缩包 解压缩下载下来的Spring压缩包文件 解压缩之后我们会看到libs文件夹下有许多jar包,而我们只需要其中的c ...

  8. Linux如何让进程在后台运行的三种方法详解

    问题分析: 我们知道,当用户注销(logout)或者网络断开时,终端会收到 HUP(hangup)信号从而关闭其所有子进程.因此,我们的解决办法就有两种途径:要么让进程忽略 HUP 信号,要么让进程运 ...

  9. Hibernate 的三种状态详解

    hibernate的对象有3种状态,分别为:瞬时态(Transient).持久态(Persistent).脱管态(Detached). 处于持久态的对象也称为PO(Persistence Object ...

随机推荐

  1. C#中的深度学习(五):在ML.NET中使用预训练模型进行硬币识别

    在本系列的最后,我们将介绍另一种方法,即利用一个预先训练好的CNN来解决我们一直在研究的硬币识别问题. 在这里,我们看一下转移学习,调整预定义的CNN,并使用Model Builder训练我们的硬币识 ...

  2. Spring Boot GraphQL 实战 02_增删改查和自定义标量

    hello,大叫好,我是小黑,又和大家见面啦~ 今天我们来继续学习 Spring Boot GraphQL 实战,我们使用的框架是 https://github.com/graphql-java-ki ...

  3. flowable 任务多实例

    项目地址:https://gitee.com/lwj/flowable.git 分支flowable-base*业务场景:收集每个员工的绩效考核信息:收集一次组织活动的信息:一个合同需要三个经理审批, ...

  4. Amazing 2020

    Amazing 2020 Intro 2020 转眼即逝,2020 是比较艰辛的一年,因为疫情原因,很多人的工作以及生活都多多少少受到了一些影响. 引用网上盛传的一句话--2020 实"鼠& ...

  5. 为什么import React from 'react',React首字母必须大写?

    很奇怪的是,明明没有用到 React,但是我不得不 import React.这是为什么? import React from 'react'; export default function (pr ...

  6. Kubernetes项目简介

    Kubernetes项目简介 Kubernetes 是 Google 团队发起的开源项目,它的目标是管理跨多个主机的容器,提供基本的部署,维护以及运用伸缩,主要实现语言为 Go 语言.Kubernet ...

  7. spark进行相同列的join时,只留下A与B关系,不要B与A

    一.问题需求: 近期需要做一个商品集合的相关性计算,需要将所有商品进行两两组合笛卡尔积,但spark自带的笛卡尔积会造成过多重复,而且增加join量 假如商品集合里面有: aa   aa bb   b ...

  8. JVM--理解介绍

    JVM?JDK?JRE?关系? JDK(Java Development Kit),它是实际上存在的,它包含JRE+编译.运行等开发工具. JRE(Java Runtime Environment), ...

  9. MySQL中Exists和In的使用

    Exists关键字: exists表示存在,是对外表做loop循环,每次loop循环再对内表(子查询)进行查询,那么因为对内表的查询使用的索引(内表效率高,故可用大表),而外表有多大都需要遍历,不可避 ...

  10. linux kernel操作GPIO函数

    一.头文件 #include <asm/gpio.h> 二.注册 GPIO int gpio_request(unsigned gpio, const char *label) 功能:申请 ...