LeetCode.189(轮转数组)

对于轮转数组这个题,文章一共提供三种思路,对于每种思路均提供其对应代码的时间、空间复杂度。

目录

1. 创建变量来保存最后一个数,并将其余数组向前挪动一位 :

1.1 原理解析:

1.2 代码实现:

 2.创建一个数组,用于存放需要旋转的元素,并放到相应位置:

2.1 原理解析:

2.2 代码实现:

3. 先左部分右旋,再右部分右旋,最后整体逆序(三段逆序法):

3.1 原理解析:

 3.2 代码实现:


题目要求如图所示:

 

1. 创建变量来保存最后一个数,并将其余数组向前挪动一位 :

(注:第一种思路虽然可以解决问题,但是代码的时间复杂度为O(N^2),空间复杂度为O(1),时间复杂度不符合LeetCode的提交标准,所以,第一种思路仅供参考与扩展。)

1.1 原理解析:

 假设一个数组为:

nums[7] = {1,2,3,4,5,6,7};

为了方便表示,用下图来代表数组及数组中的元素:

第一步:先将数组种最后一位元素,即:7,用一个临时变量保存。

 

第二步:将其他剩余元素全部向右移动一位。

 

第三步: 把临时变量保存的7,放到数组的首元素的位置。

 

 至此,完成一次交换。

1.2 代码实现:

用代码表示上述过程,即:

#include<stdio.h>
#include<assert.h>
void rotata(int* nums,int numsize)
{
	assert(nums);
	int tmp = nums[numsize - 1];//用临时变量保存数组最后一个元素
	int i = 0;
	for (i = 6 ; i > 0; i--)
	{
		nums[i] = nums[i - 1];//将其余元素向右移动一位
	}
	nums[0] = tmp;//将临时变量保存的元素放在数组首个元素的位置
}
int main()
{
	int nums[7] = { 1,2,3,4,5,6,7 };
	int sz = sizeof(nums) / sizeof(nums[0]);
	rotata(nums, sz );
	return 0;
}

打印结果如下:

 

上面的代码,只能实现右移一位。对于移动多个元素,可以将上面移动一个元素的过程用循环来进行。例如,用变量k来代表旋转的次数,代码为:

#include<stdio.h>
#include<assert.h>
void rotata(int* nums,int numsize,int k)
{
    k = k % numsize + 1;//防止k过大,使k的范围在0-7
	assert(nums);
	int j = 0;
	for (j = 0; j < k; j++)
	{
		int tmp = nums[numsize - 1];//用临时变量保存数组最后一个元素
		int i = 0;
		for (i = 6; i > 0; i--)    //0 1 2 3 4 5 6 
			                       //  1 2 3 4 5 6
		{
			nums[i] = nums[i - 1];//将其余元素向右移动一位
		}
		nums[0] = tmp;//将临时变量保存的元素放在数组首个元素的位置
	}
	
	
}
int main()
{
	int nums[7] = { 1,2,3,4,5,6,7 };
	int sz = sizeof(nums) / sizeof(nums[0]);
	int k = 0;
	scanf("%d", &k);
	rotata(nums, sz,k );
	int i = 0;

	return 0;
}

因为数组每旋转7次,数组中的元素就回到不旋转的位置,所以,即使在输入旋转次数为77次时,得到的效果也和旋转一次一样

旋转一次效果:

旋转两次效果:

 

旋转77次:

 

但是,当k的值过大时,因为每右旋7次就是一个循环,所以,为了减少编译器的工作量,用k%numsize+1,让k的取值范围保持在0\rightarrow 7这个区间(闭区间)

 2.创建一个数组,用于存放需要旋转的元素,并放到相应位置:

方法二的时间复杂度为O(N),空间复杂度为O(N)

2.1 原理解析:

依旧使用上面的图来解释思路:

右旋一次:

 右旋两次:

对于上面的两种情况,不难发现,其实所谓的右旋,可理解为将需要进行右旋的元素看成一个整体,让后放到其余元素的前面,例如旋转两次时:

第一步:将元素6,7看作一个整体,其余元素看成一个整体。

 

第二步:作为整体的6,7放到剩余元素的前面进行整合:

 

2.2 代码实现:

有了解决问题的思路后,下面给出具体实现的代码:

void rotate(int* nums, int numsSize, int k){
   int n = numsSize;
   int* tmp = (int*)malloc(sizeof(int)*(n));
  
   k = k%n;

   memcpy(tmp,nums+n-k,sizeof(int)*(k));
   memcpy(tmp+k,nums,sizeof(int)*(n-k));
   memcpy(nums,tmp,sizeof(int)*(n));

   free(tmp);

}

 整体代码的逻辑为: n是数组中元素的个数,k是执行右旋的次数。

1.先用malloc开辟一块空间 

2.用k%n来限制k的取值范围。

3.先利用memcpy函数将nums数组种起始地址n+k,数量为k的元素拷贝到tmp这个临时空间种。

4.再利用memcpy函数将nums剩余的元素拷贝到tmp中。

此时,tmp已经存储了旋转后的数组。

5.将tmp中的元素赋值到nums中。

执行结果如下:

 从结果中可以看到,方法二是用空间来换取速度的方法。

3. 先左部分右旋,再右部分右旋,最后整体逆序(三段逆序法):

最优解法,时间复杂度为O(N),空间复杂度因为只创建了一个额外的临时变量,为O(1)

3.1 原理解析:

对于三段逆序法,同样使用图片来解释:

假设:数组名为nums,数组中的元素个数为n,按照LeetCode中7个元素。需要旋转的位数为k。下面的代码就拿k = 3来解释。

第一步:先将左部分元素整体逆序,即:把下标从0到nums-k-1(即数组中第一个到第四个元素逆序)的元素逆序:

 

第二步:将右半部分元素整体逆序,即:把下标为nums - kn-1(即数组中第五个到第七个元素逆序):

第三步:整体逆序:即:对下标从0到nums-1的元素逆序:

通过LeetCode网站给出的样例发现,整体逆序后结果与样例相同:

 

 3.2 代码实现:

  在上面的解释中,既然每一步都需要逆序,所以,为了方便并且减少代码量,可以提前封装一个交换函数或者直接使用交换函数,这里给出提前封装交换函数的代码:

其中:变量a对应数组nums,变量left对应交换的起始位置,变量right对于交换的结束位置

void reserve( int*a,int left,int right)
{
    while(left < right)
    {
        int tmp = a[left];
        a[left] = a[right];
        a[right] = tmp;
        left++;
        right--;
    }
}

再交换后,根据上面对原理的解释,对逆序函数进行三次调用并传递相应的参数即可:

void rotate(int* nums, int numsSize, int k){

   int n = numsSize;
   k = k%n;
   reserve(nums,0,n-k-1);
   reserve(nums,n-k,n-1);
   reserve(nums,0,n-1);

}

整体函数如下:

void reserve( int*a,int left,int right)
{
    while(left < right)
    {
        int tmp = a[left];
        a[left] = a[right];
        a[right] = tmp;
        left++;
        right--;
    }
}


void rotate(int* nums, int numsSize, int k){

   int n = numsSize;
   k = k%n;
   reserve(nums,0,n-k-1);
   reserve(nums,n-k,n-1);
   reserve(nums,0,n-1);

}

运行结果如下:

 

评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

起床写代码啦!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值