牛客网:JZ58 左旋转字符串
描述:
汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列 S ,请你把其循环左移 K 位后的序列输出。例如,字符序列 S = ”abcXYZdef” , 要求输出循环左移 3 位后的结果,即 “XYZdefabc”。
数据范围:输入的字符串长度满足 0≤len≤100, 0<=n<=100
进阶:空间复杂度O(n),时间复杂度O(n)
示例1
输入: “acXYZdef”,3
返回值:“XYZdefabc”示例2
输入: “aab”,10
返回值:“aba”
需要注意的是,本题那个函数的形参部分是char *str指的是用指针的的形式接收main中所定义str数组首元素的地址。比如我们给大家看一下main函数的代码是怎么进行调用函数LeftRotateString的。
int main() { char str[] = "ABCD"; LeftRotateString(str, 1); printf("%s\n", str); return 0; }
而形参部分的n指的是该字符串需要左移的个数。比方说LeftRotateString函数实参部分我传的是1,那么n的值就为1。
当我们把该字符串已经翻转好所传的函数实参n的次数,那我们直接就返回字符串就行,无需打印。例如:
return str;
当我们明白题目的打印输出方式,那我们就可以开始解题啦!
首先呢,在解题之前,我们先把字符串的长度先求出来。
那我们该怎么求字符串长度呢?我们可以先定义一个len的变量,然后再用strlen函数计算它的长度。
又因为函数形参部分*str是接收了main函数中数组str首元素的地址,那么strlen函数是从数组的首元素地址开始计算,直到遇到‘\0’就结束。
那么这个代码我们可以这么写:int len=strlen(str);
当我们算出len字符串长度后,我们就进入正式进入解题环节。
那我们如果如何知道自己挪动的次数呢?首先呢,我们可以采用1个字符1个字符地左旋,比如把最左边的那个字符左旋转到最后面那个字符,也就是左旋到’\0’前面的那一个字符,然后根据所要旋转的次数,逐一进行左旋,直到我们把所有字符都左旋完毕后,就返回str字符串到main函数那儿。
但我们如何控制所要移动的次数呢?
比方说,我们原字符串为“ABCD”,我们要左移5次,最终它左旋后的结果是什么呢?
我们可以通过动图观看它的移动轨迹~
最终旋转之后的结果是这样的:
从图中,我们可以看到把字符串"ABCD"左旋5次之后的结果跟左旋一次之后的结果是一样的。
并且,虽然它的字符为4个字符,但是我可以挪动的次数可以超过4个字符次。
事实上,我们可以定义一个变量times,然后用形参n%lens,比如我用main函数中传过去的是5,那么n=5,接下来用strlen计算出该字符串长度len=4,然后5%4=1,也就是说只用挪动一次就能得到左旋一次的那个字符串结果出来。
那如果我们是一次一次地挪,我们代码该怎么写呢?
比方说,我们可以先写个外层的for循环,然后在循环体内,我们可以定义变量tmp,并对数组名str进行解引用操作,然后把str所指向对象的值赋给变量tmp。
然后我们定一个变量j,令j=0,然我们在外层的for循环体内再写一个for循环,然后随着j的值增加,然后我们还要加上j。
可能有人会有疑问为什么循环是j
我们可以看一下下面的动图所示:
从该动图中,我们可以看到,在循环体内,数组后面的元素会覆盖掉前面的元素,如果我们写成代码形式的话。
就是这样:
str[j]=str[j+1]
当j的值等于len-1的话,会跳出内层的for循环,然后到那个时候,j的值等于len-1,然后我们再把tmp的值赋给str[j]。
也就是:
str[j]=tmp;
当我们理解它的代码逻辑之后,我们就能进行整个代码实现了。
char* LeftRotateString(char* str, int n) {
int lens = strlen(str);
if (lens == 0)
return "";
int times = n % lens;
for (int i = 0; i < times; i++) {
char tmp = *str;
int j = 0;
for (; j < lens - 1; j++) {
str[j] = str[j + 1];
}
str[j] = tmp;
}
return str;
}
当然有人会有疑问为什么要加上这两行代码?
if (lens == 0)
return "";
虽然这种一个字符一个字符这样移方法比较简单,但是执行次数会比较多,有点繁琐,因此我们接下来会展示其他思路。从图中,我们很明显就看出如果main函数中传入的是空的字符串,那么我们strlen计算lens的长度,它就为0。但是当lens=0作为除数的话,这显然是不符合数学逻辑的,因此当strlen计算出len=0时,应立即返回str这个值。就不能让下面那条语句执行,不然程序就会出错。
而当我们把那两行代码加上去后,它那个评分系统才会全部通过所有用例。
那么我们讲的第二种方法就是拷贝法拼接法,拼接法的实质也就是借助C语言的库函数来完成。
那我们就先介绍拷贝函数strcpy。
从函数官网中,我们可以看出strcpy函数中的第一个参数是填目标的字符串,而第二个参数是填从原字符串的某个位置开始拷贝的。
实际上,strcpy函数用法是从原字符串的某个位置开始拷贝,直到遇到null字符就停止拷贝,也就是遇到’\0’就停止拷贝。
另外,我们再讲一下strncpy函数,strncpy这个库函数和strcpy函数很类似,但其实它们两个函数还是有所区别的,因为它的第三个函数参数要写那个数字个数。
那比如这个原字符串为“ABCD”,我们要移动3次。那这个代码我们用刚刚所讲的那两个库函数该怎么实现呢?
首先呢,还是老样子,我们还是要先用strlen函数计算那个字符串长度,并且还要保证那个所传的str字符串不是空字符,接着再n%len,计算出所要左旋的次数。也就是我们要先加上这四行代码。
int lens = strlen(str); if (lens == 0) return str; int times = n % lens;
接着,我们还要定义一个tmp的数组来存储那个字符。
根据题目要求,输入的字符串长度要满足0≤len≤100。
因此我们最好就这样定义那个tmp数组。char tmp[101】
从动图中,我们也能得知,该函数strcpy可以先将从str+times地址所指向的字符到‘\0’之前的字符拷贝到变量tmp中。
那么代码我们可以这么写:strcpy(tmp,str+times);
接着,我们看动图,它把"BCD"字符串直接拷到srt那儿,那这个代码我们又该怎么写呢?
我们可以用strncpy函数来进行拷贝,然后在strncpy函数内的我们可以怎么写呢。由于刚刚我们已经把字符A已经拷贝到变量tmp,所以tmp首元素的地址所指向的对象就是字符A,但我们不能直接拷到tmp首元素的那个位置那里,因为这样会把字符A给覆盖掉了。那我们那个目标位置要放到字符A的后面,也就是用tmp首元素地址+(字符串长度lens-左旋的次数times),然后起始位置我们就可以写成src了,因为数组名是数组首元素的地址。然后最后一个参数size_t num就直接写成左旋的次数times就OK啦!因此代码可以写成这样子。strncpy(tmp+(times-lens),str,times);
这样我们就成功把左旋后的字符串全部拷贝给tmp数组里面,但是题目要求我们返回的是str字符串,因此我们还要把tmp数组里面的字符串直接拷贝到str数组内,那么这时,我们直接用strcpy这个函数进行拷贝就行了,并且它们的起始位置和目标位置分别为tmp和str数组首元素的位置。因此根据分析,那个代码就可以这么写
strcpy(str,tmp);
在分析完这些代码逻辑后,最终我们代码可以这么写。
char* LeftRotateString(char* str, int n) {
int lens = strlen(str);
if (lens == 0)
return str;
int times = n % lens;
char tmp[101];
strcpy(tmp,str+times);
strncpy(tmp+(lens-times),str,times);
strcpy(str,tmp);
return str;
}
虽然这个代码可以成功运行,但我认为这个程序还是有待完善的。
那我们该怎么完善呢?接下来我来给大家分析一下。
再讲解这个思路分析2的代码实现之前,我们再给大家介绍一个库函数strncat,首先呢,我们先看一下函数的参数有什么呢?
从图中,我们可以看出这个strncat这个函数的函数参数看似跟strncpy这个函数参数一样,并且都是遇到’\0’就终止了。
但是这两个函数本质上还是有区别的。我们来给大家解释一下为什么。比方说,我们还是跟上面一样,先加上这行代码strcpy(tmp,str+times);
然后呢,我们这时候如果是用strncat函数进行拼接的话,它的第二个参数目标位置是可以直接写成tmp数组名的形式。其他两个参数都不变。也就是这种形式。
strncat(tmp,str,times);
有人会有疑问,为什么可以这么写呢?
因为它这个strncat函数是不会把前面已经拷贝进tmp的字符给覆盖掉,而是会在之前strcpy拷贝后的那个字符后面再进行拼接,并且如果这么写,显然是比之前写的那个strncat拼接函数要方便。
而当我们拼接完那个函数后,我们就直接用拷贝函数strcpy操作就行,也就是:strcpy(str,tmp);
当我们分析完这个strncat函数用法后,我们就可以直接写出它的代码出来了。
char* LeftRotateString(char* str, int n) {
int lens = strlen(str);
if (lens == 0)
return str;
int times = n % lens;
char tmp[101];
strcpy(tmp,str+times);
strncat(tmp,str,times);
strcpy(str,tmp);
return str;
}
运行结果也是照样正确:
虽然这个解法代码思路比较简洁易懂,但是呢,仅仅用两个库函数就做出来,未免会有点耍技巧。假如我们到时参加技术笔试、面试,面试官如果说不能用这个库函数的方法解这类题,那我们该如何改进呢?
那么接下来,我们就给大家介绍另外一种解题方法。
首先呢,我们可以采用局部翻转的方法来对字符串进行左旋的操作。
比方说,我们举个例子,我们main函数传的字符串是"ABCDEF",旋转次数为2的话。那左旋后的字符串是不是”CDEFAB"?
可能很多人对局部翻转的概念还是很懵,那我们直接上动图!
从该动图中,我们发现当左旋的次数n=2时,就是先对前两个字符翻转,接下来就对后面那四个字符进行翻转,最后再进行整体进行翻转。最终我们就能把左旋n次的字符串求出来。
那这个代码我们该怎么实现呢?
首先呢,还是老样子,我们依然要把字符串times的次数和lens的长度求出来。也就是要加上这四行代码:
int lens = strlen(str); if (lens == 0) return str; int times = n % lens;
接下来,我们要创建一个Reverse的函数,那里面的参数我们该传什么呢?事实上,由于我们是通过数组下标来进行局部翻转字符的,并且从上面动图分析,就是先将以下标为0~times-1的元素来翻转,然后再将下标time-lens-1的元素进行翻转,最后再将下标为0-lens-1的元素进行整体翻转。那么我们就可以在写出这三行调用函数的代码出来,也就是
Reverse(str,0,times-1); Reverse(str,times,lens-1); Reverse(str,0,lens-1);
那么接下来在Reverse这个函数内部我们应该怎么写呢?
首先呢,在形参部分我们先用指针的形式来接收字符str,又因为在Reverse函数内要对数组局部再进行翻转,因此呢,剩下那两个参数我们可以写成int left,int right的形式,方便到时进行数组翻转操作。
那么形参部分就是写成void Reverse(char*str,int left,int right)
又因为我们本质上是要实现所传进来字符的交换,因此在Reverse函数内部我们可以这么写,==我们可以先定义一个while循环,在循环体内,首先创建一个变量tmp,然后把*(str+left)赋给tmp,相当于str跳过left个元素,接着再对它进行解引用操作,就能访问它所指向的对象出来 ,接着我们再把*(str+right)所指向对象的值赋给*(str+left),最后再将tmp的值赋给*(str+right)。那么我们就能写下这三行代码出来!
char tmp=*(str+left); *(str+left)=*(str+right); *(str+right)=tmp;
而又因为left的值要比right的值要小,因此如果要把所传进来的字符进行全部翻转,我们就要写上这两行代码:
right--; left++;
那么循环条件我们可以通过上面那两行代码推导出来,也就是while(left
left和right下标的值逐渐向中间的元素的值靠拢,直到left=right,不符合while循环条件,便跳出while循环,从而就能实现这些字符地交换。
最终我们代码可以写成这样:
void Reverse(char*str,int left,int right){
while(left<right){
char tmp=*(str+left);
*(str+left)=*(str+right);
*(str+right)=tmp;
left++;
right--;
}
}
char* LeftRotateString(char* str, int n) {
int lens = strlen(str);
if (lens == 0)
return str;
int times = n % lens;
Reverse(str,0,times-1);
Reverse(str,times,lens-1);
Reverse(str,0,lens-1);
return str;
}
这里是显示是通过所有用例的,因此这个代码逻辑是没问题的。
好了,今天题目的分享到这里就结束啦,如果大家对本题有更多的解题思路,或者博主讲得不太好的地方,欢迎大家在评论区交流或者私信博主。
写到最后,希望大家能给博主一个三连支持一下
谢谢!!!!!