旋转数组的解法——牛客算法入门题
题目来源:旋转数组_牛客题霸_牛客网 (nowcoder.com)
题目描述
一个数组A中存有N(N>0)个整数,在不允许使用另外数组的前提下,将每个整数循环向右移M(M>=0)个位置,即将A中的数据由(A0 A1 ……AN-1 )变换为(AN-M …… AN-1 A0 A1 ……AN-M-1 )(最后M个数循环移至最前面的M个位置)。如果需要考虑程序移动数据的次数尽量少,要如何设计移动的方法?
示例1
输入:
6,2,[1,2,3,4,5,6]
返回值:
[5,6,1,2,3,4]
备注:
(1<=N<=100,M>=0)
解法1:
对数组中元素进行遍历,遍历过程中将每个元素直接移动至应在的位置。(自己动手画一画就知道了)
class Solution { public: /** * 旋转数组 * @param n int整型 数组长度 * @param m int整型 右移距离 * @param a int整型vector 给定数组 * @return int整型vector */ vector<int> solve(int n, int m, vector<int>& a) { // write code here if(m%n == 0) return a; //若所要移动的位置是元素总数的整数倍,则相当于没移动。 else m %= n; //否则将右移距离除以元素总数取余。此举是为了减少计算移动后下标的计算量。 int temp1,temp2; //临时存储中间变量 int new_pos,idx = 0,i =0,cnt =0;//new_pos为当前元素移动后的下标,cnt指目前已移动了多少个元素,idx为移动的轮数,同时也代表每轮初始移动元素的下标,i指代当前元素移动前的下标。 for(idx;idx<=m;idx++) { if(cnt >= n) //所有元素均移动完毕 break; temp1=a[idx]; //暂存当前元素 i = idx; //初始化当前元素下标 while((i+m)%n!= idx) //只要当前元素移动后的下标不是最初的下标便继续循环。亦即移动这一轮的最后一个元素之前结束循环。 { new_pos = (i+m)%n; //新的下标=旧下标+移动位数 与元素总数n取余。 temp2 = a[new_pos]; //暂存当前新下标中的元素 a[new_pos] = temp1; //移动到新下标中 i = new_pos; //下一个处理的是之前存放在新下标中的老元素 temp1 = temp2; //temp1为即将进行移动的元素值 cnt++; //已经完成了一次移动 } a[idx] = temp1; //移动这一轮最后一个元素到新下标。由于每轮最后一次移动的元素的新下标为这一轮移动的第一个元素的老下标,形成了一个闭环,所以不再继续。 cnt++; //完成最后一次移动 } return a; //循环结束即代表已完成了所有的移动任务。返回。 } };
同种思想的另一种写法:
class Solution {
public:
/**
* 旋转数组
* @param n int整型 数组长度
* @param m int整型 右移距离
* @param a int整型vector 给定数组
* @return int整型vector
*/
vector<int> solve(int n, int m, vector<int>& a) {
// write code here
int start = 0;
int pos = start;
int count = 0;
int tmp1 = a[start];
int tmp2 = tmp1;
m = m % n;
while(true)
{
tmp1 = a[(pos + m) % n];
a[(pos + m) % n] = tmp2;
tmp2= tmp1;
pos = (pos + m) % n;
count ++;
if(count == n)
break;
if(pos == start)
{
start = start + 1;
pos = start;
tmp1 = a[start];
tmp2 = a[start];
}
}
return a;
}
};
解法2:
这个解法应该是这道题名字的由来,这也是网上最普遍的解法,即三次旋转解决问题。
该方法基于如下的事实:当我们将数组的元素向右移动 k 次后,尾部 k mod n 个元素会移动至数组头部,其余元素向后移动 k mod n个位置。
该方法为数组的翻转:我们可以先将所有元素翻转,这样尾部的 k mod n个元素就被移至数组头部,然后我们再翻转 [0, k mod n-1]区间的元素和 [k mod n, n-1]区间的元素即能得到最后的答案。
这里自己写了个reverse函数用于旋转数组。
class Solution { public: /** * 旋转数组 * @param n int整型 数组长度 * @param m int整型 右移距离 * @param a int整型vector 给定数组 * @return int整型vector */ void reverse(vector<int>& a,int start,int end){ while(start<end){ swap(a[start],a[end]); start++; end--; } } vector<int> solve(int n, int m, vector<int>& a) { m=m%n; reverse(a,0,n-1); reverse(a,0,m-1); reverse(a,m,n-1); return a; } };
这里充分运用了vector数组对象的方法begin()和end()以及STL中algorithm库的算法reverse,简化了步骤。
class Solution { public: /** * 旋转数组 * @param n int整型 数组长度 * @param m int整型 右移距离 * @param a int整型vector 给定数组 * @return int整型vector */ vector<int> solve(int n, int m, vector<int> &a) { // write code here if (!n) return a; if (!m) return a; m %= n; reverse(a.begin(), a.end()); reverse(a.begin(), a.begin() + m); reverse(a.begin() + m , a.end()); return a; } };
解法3:
此法抓住了题目的漏洞,既然题目不允许声明另外的数组,那我直接在你给的vector数组后面追加一模一样的元素。实质是使用辅助数组来直接移动。
该法同样活用了vector类的成员函数push_back(int),pop_back(),分别代表在数组尾部追加与删除元素。
class Solution { public: /** * 旋转数组 * @param n int整型 数组长度 * @param m int整型 右移距离 * @param a int整型vector 给定数组 * @return int整型vector */ vector<int> solve(int n, int m, vector<int>& a) { // write code here int M = m%n; for(int i = 0; i < n; i++) { a.push_back(a[i]); } for(int i = 0; i < n; i++) { a[i] = a[i+n-M]; } for(int i = 0;i<n;i++) { a.pop_back(); } return a; //return } };
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了