C++旋转数组(三种解法详解)
题目描述
给定一个数组,将数组中的元素向右移动 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 - 10 <= 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++旋转数组(三种解法详解)的更多相关文章
- [转]hibernate三种状态详解
本文来自 http://blog.sina.com.cn/u/2924525911 hibernate 三种状态详解 (2013-04-15 21:24:23) 转载▼ 分类: hibernate ...
- 多表连接的三种方式详解 hash join、merge join、 nested loop
在多表联合查询的时候,如果我们查看它的执行计划,就会发现里面有多表之间的连接方式.多表之间的连接有三种方式:Nested Loops,Hash Join 和 Sort Merge Join.具体适用哪 ...
- 分布式事务 Seata Saga 模式首秀以及三种模式详解 | Meetup#3 回顾
https://mp.weixin.qq.com/s/67NvEVljnU-0-6rb7MWpGw 分布式事务 Seata Saga 模式首秀以及三种模式详解 | Meetup#3 回顾 原创 蚂蚁金 ...
- Android 三种动画详解
[工匠若水 http://blog.csdn.net/yanbober 转载请注明出处.点我开始Android技术交流] 1 背景 不能只分析源码呀,分析的同时也要整理归纳基础知识,刚好有人微博私信让 ...
- leetcode#42 Trapping rain water的五种解法详解
leetcode#42 Trapping rain water 这道题十分有意思,可以用很多方法做出来,每种方法的思想都值得让人细细体会. 42. Trapping Rain WaterGiven n ...
- PHP实现链式操作的三种方法详解
这篇文章主要介绍了PHP实现链式操作的三种方法,结合实例形式分析了php链式操作的相关实现技巧与使用注意事项,需要的朋友可以参考下 本文实例讲述了PHP实现链式操作的三种方法.分享给大家供大家参考,具 ...
- Spring依赖注入三种方式详解
在讲解Spring依赖注入之前的准备工作: 下载包含Spring的工具jar包的压缩包 解压缩下载下来的Spring压缩包文件 解压缩之后我们会看到libs文件夹下有许多jar包,而我们只需要其中的c ...
- Linux如何让进程在后台运行的三种方法详解
问题分析: 我们知道,当用户注销(logout)或者网络断开时,终端会收到 HUP(hangup)信号从而关闭其所有子进程.因此,我们的解决办法就有两种途径:要么让进程忽略 HUP 信号,要么让进程运 ...
- Hibernate 的三种状态详解
hibernate的对象有3种状态,分别为:瞬时态(Transient).持久态(Persistent).脱管态(Detached). 处于持久态的对象也称为PO(Persistence Object ...
随机推荐
- [leetcode299] 299. Bulls and Cows
public String getHint(String secret, String guess) { /* 判断bull 是通过比较两个字符串的每一位,每次相同就删除该字符出现的次数,因为后边的 ...
- Docker 的 2020,实 "鼠" 不易!
元旦你们出去嗨,栈长在家撸文章,惨惨惨- 没错, Docker 的 2020 年也过的不是很顺利,可以说是流年不利.命运多舛,一年发生两件大事,太折腾! 相信大家也已经看到很多相关的报道了,但同时也有 ...
- Spring Boot的自动配置
Spring Boot的自动配置 --摘自https://www.hollischuang.com/archives/1791 随着Ruby.Groovy等动态语言的流行,相比较之下Java的开发显得 ...
- git 工作区与版本库
git 工作区.版本库 在我们使用git的时候,我们脑海中一定要有一个关于git的框架,如下图: 我们先对git的工作区.暂存区.本地仓库做一个基本的解释 工作区: 就是我们电脑中代码的下载目录 版本 ...
- C#实现 Server-sent Events
基于http协议交互的推送方法大概方法如下: 轮询(ajax),比较耗费服务器资源.COMET方式(COMET 技术并不是 HTML 5 ) websocket 双向数据推送,灵活,功能强大 Serv ...
- Mybatis-plus的使用步骤
Mybatis-plus的简单使用步骤 花开堪折直需折,莫待无花空折枝 导入依赖 <dependency> <groupId>org.projectlombok</gro ...
- RMI之由浅入深(一)
0x01.什么是RMI RMI(Remote Method Invocation)即Java远程方法调用,RMI用于构建分布式应用程序,RMI实现了Java程序之间跨JVM的远程通信.顾名思义,远程方 ...
- 初识 D3.js :打造专属可视化
一.前言 随着现在自定义可视化的需求日益增长,Highcharts.echarts等高度封装的可视化框架已经无法满足用户各种强定制性的可视化需求了,这个时候D3的无限定制的能力就脱颖而出. 如果想要通 ...
- 【Redis3.0.x】配置文件
Redis3.0.x 配置文件 概述 Redis 的配置文件位于Redis安装目录下,文件名为 redis.conf. 可以通过 CONFIG 命令查看或设置配置项. Redis 命令不区分大小写. ...
- RabbitMQ常用的几种消息模型
第一种模型(HelloWorld) 上图来自官方文档 P代表生产者用来生产消息,发送给消费者C,中间的共色部分代表消息队列,用来缓存消息. 首先导入依赖 <dependency> < ...