本文涉及的库函数或者数据结构与算法不熟悉的地方,可以在文章末找到相关知识详解链接。
题目描述
OJ链接:LeetCode 189. 轮转数组
给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
解法一 循环移位
直接向右轮转k位,我们很难办到,但是我们可以分治他,一轮只移动每个元素1位,移动k轮,这样确保每个数据不会丢失。
注意:如果k超过数组的大小,就会轮转回原来数组,再轮转,不如使k对numsSize取余,只进行结果意义上的有效轮转。
时间复杂度:O(N) 准确时间复杂度 (K%numsSize)*N 可能会超出时间限制
空间复杂度:O(1)
void rotate(int* nums, int numsSize, int k)
{
assert(nums);
//只进行有效轮转
k%=numsSize;
//轮转k%numsSize趟
for(int i=0;i<k;i++)
{
//对每趟数组元素进行移位
int tmp=nums[numsSize-1];
for(int j =numsSize-1;j>0;j--)
{
nums[j]=nums[j-1];
}
nums[0]=tmp;
}
}//timeO(N) spaceO(1)
解法二 数组映射
假设我们有数组 [1,2,3,4,5,6],我们想要向右轮转2位,轮转结果应为 [5,6,1,2,3,4],如果直接向下标+2的位置进行轮转,数组最右侧数据会发生越界,我们可以对下标取余numsSize大小,即可消除越界。
你可以假设原数组有无限大,尽管向右移位多少距离都行,只是最后因为是有限大,利用取余将其限制在数组相应位置内。
时间复杂度:O(N) 准确时间复杂度 2*N
空间复杂度:O(N)
void rotate(int* nums, int numsSize, int k)
{
assert(nums);
//开辟临时数组,用于储存轮转好的数组
int *tmp=(int*)malloc(numsSize*sizeof(int));
if(tmp ==NULL)
{
printf("malloc fail\n");
exit(-1);
}
for(int i=0;i<numsSize;i++)
{
//注意此思想轮转后,数的位置如何改变,可以举例解决,巧用取余
tmp[(i+k)%numsSize]=nums[i];
}
//拷贝回原数组
for(int i=0;i<numsSize;i++)
{
nums[i]=tmp[i];
}
//及时释放堆内存
free(tmp);
tmp=NULL;
}//timeO(n) spaceO(n)
解法三 巧用反转
反转数组是一个常见的操作,用在这题上十分巧妙,你可以在看后用笔画一画,试一试。
假设我们有数组 [1,2,3,4,5,6],我们想要向右轮转2位,轮转结果应为 [5,6,1,2,3,4]。
- 首先要使 k 对 numsSize 取余,才能确保后续操作正确且简洁。
- 我们先反转数组最右边 k 个数,得 [1,2,3,4,6,5]。
- 再反转数组最左边 numsSize-k个数,得[4,3,2,1,6,5]。
- 再对数组整体进行反转,得结果[5,6,1,2,3,4]
时间复杂度:O(N) 准确时间复杂度 2*N
空间复杂度:O(1)
//要被反转的数组首地址,区间[left,right]反转
void reverse(int *nums,int left,int right)
{
assert(nums);
while(left<right)
{
//需要使用交换两数功能,你可以也把这个常用功能封装成函数,这里就不封装了
int tmp =nums[left];
nums[left]=nums[right];
nums[right]=tmp;
//区间左右两头数交换后,向中间靠拢
left++;
right--;
}
}
void rotate(int* nums, int numsSize, int k){
assert(nums);
//k超过numsSize大小的部分没意义
k%=numsSize;
//我们自己写一个反转数组的函数,注意传递的区间参数
reverse(nums,numsSize-k,numsSize-1);
reverse(nums,0,numsSize-k-1);
reverse(nums,0,numsSize-1);
}
码字不容易,欢迎关注,点赞,收藏,评论,转发。