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 ...
随机推荐
- Python的一个mysql实例
按公司名统计一定时期内入货的总车数,总重量还有总价格.数据表如下: 要用到的库是pymysql,读取excel表格的xlrd,写入excel的xlwt和复制excel模板的xlutils,代码如下: ...
- 庐山真面目之九微服务架构 NetCore 基于 Docker 基础镜像和挂载文件部署
庐山真面目之九微服务架构 NetCore 基于 Docker 基础镜像和挂载文件部署 一.简介 我们在上一篇文章<庐山真面目之八微服务架构 NetCore 基于 Dockerfile ...
- sqlserver varchar和Nvarchar区别
sql server中的varchar和Nvarchar有什么区别? 答:varchar(n)长度为 n 个字节的可变长度且非 Unicode 的字符数据.n 必须是一个介于 1 和 8,000 ...
- $emit的用法
自定义事件$emit.使用$emit建立父子组件之间的通信.子组件到父组件之间的通信. 子组件: 父组件:
- Ubuntu系统的ifconfig命令不能执行
新安装的Ubuntu想要用WinSCP传文件时发现,ifconfig命令用不了 ping www.baidu.com 获得回应,应该是ifconfig未安装 解决这个问题,首先如图(时间较长,获取:[ ...
- 从0开始快速入门学Java----基本篇
由于是0基础入门java,所以花了比较多的时间学习了基本语法知识,阶段性梳理下知识: 1. Java的介绍+JDK安装及环境变量配置+第一个程序HelloWorld的编写 这部分开始遇到的问题比较多, ...
- 解决UE4缓存使C盘膨胀的问题
使用UE4的时候会发现C盘越来越小了,那是因为UE4引擎的缓存文件默认保存在C盘的缘故. 概述 一.出现的问题:UE4的缓存文件会导致C盘膨胀. 二.解决的方式:请严格按照下列步骤来执行.1. 更改U ...
- VC维相关知识
假设空间H(Hypothesis Set) 输入空间D(X1...Xn) 1.增长函数(grown function) 是关于输入空间尺寸n的函数 假设空间对于D中所有实例实现分类(赋予标记)的分类方 ...
- MVC和WebApi路由机制比较
1.MVC使用的路由 在MVC中,默认路由机制是通过解析url路径来匹配Action.比如:/User/GetList,这个url就表示匹配User控制器下的GetList方法,这是MVC路由的默认解 ...
- linux中的dmesg命令以及确定进程是否被系统主动kill
linux中的dmesg命令以及确定进程是否被系统主动kill Feb 21, 2017 | java | 185 Hits 近期发现线上项目的进程莫名其妙的就不见了,也没有崩溃日志,就怀疑是被操作系 ...