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 ...
随机推荐
- locust的使用
一.简介 Locust是一款使用Python编写的压力测试工具,本篇总结会介绍在实际测试过程中遇到的问题 https://www.locust.io/ 使用Locust的原因是因为可以模拟的用户数量可 ...
- 【升级版】如何使用阿里云云解析API实现动态域名解析,搭建私有服务器【含可执行文件和源码】
原文地址:http://www.yxxrui.cn/article/179.shtml 未经许可请勿转载,如有疑问,请联系作者:yxxrui@163.com 我遇到的问题:公司的网络没有固定的公网IP ...
- Redis 设计与实现:Redis 对象
本文的分析都是基于 Redis 6.0 版本源码 redis 6.0 源码:https://github.com/redis/redis/tree/6.0 在 Redis 中,有五大数据类型,都统一封 ...
- RxHttp 完美适配Android 10/11 上传/下载/进度监听
1.前言 随着Android 11的正式发布,适配Android 10/11 分区存储就更加的迫切了,因为Android 11开始,将强制开启分区存储,我们就无法再以绝对路径的方式去读写非沙盒目录下的 ...
- 为什么MySQL不推荐使用uuid作为主键?
前言 在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一,单机递增),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么 ...
- MM-合作伙伴确定过程
第一步:物料管理---采购---合作伙伴确定---合作伙伴角色---定义合作伙伴角色. 第二步:物料管理---采购---合作伙伴确定---合作伙伴角色---定义每个科目组适合的合作伙伴角色. 第三步: ...
- JavaDailyReports10_16
今天学习安装配置了JavaWeb的资源环境, 明天开始学习HTML!
- 三目运算符(C++)
一.简介 固定格式 ?: 三目运算符:可用于赋值语句 三目运算表达式:<表达式1>?<表达式2>:<表达式3> 注:"?"运算符的含义是: 先求 ...
- 风炫安全WEB安全学习第二十七节课 XSS的防御措施
风炫安全WEB安全学习第二十七节课 XSS的防御措施 XSS防御措施 总的原则 控制好输入/输出 过滤:根据业务需求进行过滤,对email,手机号码这样的输入框进行验证. 转义:所有输出到前端的数据都 ...
- 杭电OJ2007----平方和与立方和(易错题)
Problem Description 给定一段连续的整数,求出他们中所有偶数的平方和以及所有奇数的立方和. Input 输入数据包含多组测试实例,每组测试实例包含一行,由两个整数m和n组成. Out ...