字符串左旋详解(超详细)~

在这里插入图片描述



Hello,大家好呀,今天给大家讲解一下这道字符串左旋题型。

题目链接:

牛客网: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<lens-1的循环条件
可能有人会有疑问为什么循环是j<lens-1,而不是j<len呢?

我们可以看一下下面的动图所示:
在这里插入图片描述

从该动图中,我们可以看到,在循环体内,数组后面的元素会覆盖掉前面的元素,如果我们写成代码形式的话。
就是这样:

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这个值。就不能让下面那条语句执行,不然程序就会出错。

而当我们把那两行代码加上去后,它那个评分系统才会全部通过所有用例。
在这里插入图片描述

虽然这种一个字符一个字符这样移方法比较简单,但是执行次数会比较多,有点繁琐,因此我们接下来会展示其他思路。

解法二

思路分析1:

那么我们讲的第二种方法就是拷贝法拼接法,拼接法的实质也就是借助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);
代码实现1:

在分析完这些代码逻辑后,最终我们代码可以这么写。
在这里插入图片描述

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:

再讲解这个思路分析2的代码实现之前,我们再给大家介绍一个库函数strncat,首先呢,我们先看一下函数的参数有什么呢?
在这里插入图片描述
从图中,我们可以看出这个strncat这个函数的函数参数看似跟strncpy这个函数参数一样,并且都是遇到’\0’就终止了。
但是这两个函数本质上还是有区别的。我们来给大家解释一下为什么。比方说,我们还是跟上面一样,先加上这行代码

strcpy(tmp,str+times);

然后呢,我们这时候如果是用strncat函数进行拼接的话,它的第二个参数目标位置是可以直接写成tmp数组名的形式。其他两个参数都不变。也就是这种形式。

strncat(tmp,str,times);

有人会有疑问,为什么可以这么写呢?

因为它这个strncat函数是不会把前面已经拷贝进tmp的字符给覆盖掉,而是会在之前strcpy拷贝后的那个字符后面再进行拼接,并且如果这么写,显然是比之前写的那个strncat拼接函数要方便。

而当我们拼接完那个函数后,我们就直接用拷贝函数strcpy操作就行,也就是:

strcpy(str,tmp);
代码实现2:

当我们分析完这个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;
}

运行结果也是照样正确
在这里插入图片描述
虽然这个解法代码思路比较简洁易懂,但是呢,仅仅用两个库函数就做出来,未免会有点耍技巧。假如我们到时参加技术笔试、面试,面试官如果说不能用这个库函数的方法解这类题,那我们该如何改进呢?

那么接下来,我们就给大家介绍另外一种解题方法。

解法3

思路分析:

首先呢,我们可以采用局部翻转的方法来对字符串进行左旋的操作。
比方说,我们举个例子,我们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<right)。让数组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;
}

我们不妨看一下运行结果。
在这里插入图片描述

这里是显示是通过所有用例的,因此这个代码逻辑是没问题的。

好了,今天题目的分享到这里就结束啦,如果大家对本题有更多的解题思路,或者博主讲得不太好的地方,欢迎大家在评论区交流或者私信博主。

写到最后,希望大家能给博主一个三连支持一下

在这里插入图片描述


谢谢!!!!!

  • 40
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 34
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 34
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Keven-zhou

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

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

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

打赏作者

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

抵扣说明:

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

余额充值