左旋字符串及左旋字符串的进阶

Posted 大家好我叫张同学

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了左旋字符串及左旋字符串的进阶相关的知识,希望对你有一定的参考价值。



左旋字符串

题目内容:
实现一个函数,可以左旋字符串中的k个字符。
例如:
ABCD左旋一个字符得到BCDA
ABCD左旋两个字符得到CDAB


理解题意并画出相应的图解:
在这里插入图片描述

思考:首先对于字符串,我们应该主动的将其完整的内容补全,字符串是以‘\\0’为结束标志,虽然字符串’'ABCD"的字符内容为A,B,C,D,但是其存储的时候,实际上存的是A,B,C,D,\\0。


方法一:创建临时数组法

从我们理解的图解中我们可以观察到,左旋k个字符,实际上就是将前k个字符以类似于平移的方式放到字符的后面位置去,具体过程如下:
左旋一个字符:
在这里插入图片描述
左旋两个字符:
在这里插入图片描述

因此,我们可以尝试去创建一个临时字符数组,
1.如果左旋k个字符,就将字符串的前k个字符先保存在这个临时的字符数组中
2.然后将字符串剩余的字符移动到首地址处,每个字符向前平移k个单位地址
3.然后将临时数组中的前k个字符内容平移回字符串,这样就完成的左旋k个字符的操作

在这里插入图片描述

代码实现:

#include<stdio.h>
#include<string.h>
#include<assert.h>

void levotor1(char* arr1, int k)
{
	assert(arr1 != NULL);//断言,防止传入空指针,刷题时这句可以去掉
	char arr2[5] = { 0 };
	int i = 0;
	int len = strlen(arr1);//计算好字符串的长度
	assert(k <= len);//断言,防止k大于字符串长度,刷题时这句可以去掉
	//1.将前k个字符存放到arr2数组中
	for (i = 0; i < k; i++)
	{
		arr2[i] = arr1[i];
	}
	//2.将字符串第k个字符后面的字符往前平移k个单位
	for (i = k; i < len; i++)
	{
		arr1[i - k] = arr1[i];
	}
	//3.将临时数组中的内容平移回字符串
	for (i = len - k; i < len; i++)
	{
		arr1[i] = arr2[i - (len - k)];
	}
}
int main()
{
	char arr1[] = "ABCD";
	int k = 0;
	scanf("%d", &k);
	levotor1(arr1, k);//左旋字符函数
	printf("%s\\n", arr1);
	return 0;
}

结果展示:
在这里插入图片描述


创建临时数组虽然完成了左旋k个字符的操作,但是这种方式是有局限性的,主要体现在:

1、如果字符串的长度发生变化,那么实现创建的临时数组需要给它多大的空间呢?如果给一个定值,比如代码中的5,又或者10、50、100?临时数组的空间给大了,容易造成空间的浪费,空间给小了又无法实现我们的左旋k个字符操作。(有的同学也许会说:“可以用开辟动态数组的方式,比较数组的大小和字符串长度,如果数组大小不够,就用realloc扩大动态数组的空间”。这种方式虽然行得通,但是比较麻烦)
2、如果需要进行左旋的字符串长度很大,那么为了满足左旋操作的需求,临时空间也需要很大,这种空间需求对程序和栈区空间来说压力也挺大的。

为了解决方法一存在的弊端,我们可以尝试方法二:


方法二:临时变量k次法

将左旋n个字符中的n = 1, n = 2, n = 3,观察其过程:
左旋一个字符:
在这里插入图片描述

再左旋一个字符:
在这里插入图片描述
可以发现:左旋k个字符相当于将左旋一个字符的操作重复了k遍,按照这种思路:

1.实现左旋一个字符的操作
2.利用循环重复


代码实现:

#include<stdio.h>
#include<string.h>
#include<assert.h>

//方法二:临时变量k次法
void levotor2(char* arr1, int k)
{
	assert(arr1 != NULL);//断言,防止传入空指针,刷题时这句可以去掉
	int i = 0;
	int j = 0;
	int len = strlen(arr1);
	assert(k <= len);//断言,防止k大于字符串长度,刷题时这句可以去掉
	//2.重复k遍
	for (i = 0; i < k; i++)
	{
		char tmp = arr1[0];//创建临时变量存放取出来的字符
		//1.实现左旋一次的操作
		for (j = 0; j < len - 1; j++)
		{
			arr1[j] = arr1[j + 1];
		}
		arr1[len - 1] = tmp;
	}
}
int main()
{
	char arr1[] = "ABCD";
	int k = 0;
	scanf("%d", &k);
	//levotor1(arr1,k);//左旋字符函数:方法一
	levotor2(arr1, k);//左旋字符函数:方法二

	printf("%s\\n", arr1);
	return 0;
}

结果展示:
在这里插入图片描述

方法二这种方式相较于方法一而言确实有一定的提升,尤其是再空间利用率这一块(空间复杂度更低),但实际上再运行次数更多(时间复杂度更高),使用两层循环,如果有一个字符串长度为n,左旋n个字符,那么实际运行次数为n ^ 2次,时间复杂度为O(n ^ 2),如果n很大,那么n ^ 2增长后的结果更大,运算时间将变得很长。
所以方法二与方法一相比较,前者是在拿时间换空间,后者是拿空间换时间。

那么有没有一种方法既可以保证空间的有效利用,也能保证运行的时间效率也很高呢?
答案当然是有!
这也就是我将要介绍的方法三!


方法三:三步翻转法

三步翻转法,顾名思义,就是经过翻转三次即可得到左旋k次的结果,为了更好的理解三步翻转法,我们来看下面的具体过程:
以左旋2个字符为例子:
在这里插入图片描述

代码实现:

#include<stdio.h>
#include<string.h>
#include<assert.h>
//方法三:三步翻转法
//翻转函数
void reverse(char* start, char* end)//将start和end之间的字符进行翻转
{
	while (start < end)
	{
		char tmp = *start;
		*start = *end;
		*end = tmp;
		start++;
		end--;
	}
}
void levotor3(char* arr1, int k)
{
	assert(arr1 != NULL);//断言,防止传入空指针,刷题时这句可以去掉
	int len = strlen(arr1);
	assert(k <= len);//断言,防止k大于字符串长度,刷题时这句可以去掉
	reverse(arr1, arr1 + k - 1);//1.将前k个字符进行翻转
	reverse(arr1 + k, arr1 + len - 1);//2.将第k+1到第len个字符翻转
	reverse(arr1, arr1 + len - 1);//3.整体翻转
}
int main()
{
	char arr1[] = "ABCD";
	int k = 0;
	scanf("%d", &k);
	//levotor1(arr1,k);//左旋字符函数:方法一
	//levotor2(arr1,k);//左旋字符函数:方法二
	levotor3(arr1, k);//左旋字符函数:方法三
	printf("%s\\n", arr1);
	return 0;
}

结果展示:
在这里插入图片描述


左旋字符串进阶

题目内容:
写一个函数,判断一个字符串是否为另外一个字符串旋转之后的字符串。
例如:
给定s1 = AABCD和s2 = BCDAA,返回1
给定s1 = abcd和s2 = ACBD,返回0。

AABCD左旋一个字符得到ABCDA
AABCD左旋两个字符得到BCDAA
AABCD右旋一个字符得到DAABC

思路:要想判断一个字符串是否为另外一个字符串旋转之后的字符串,可以先将另外的字符串旋转的结果都列出来,如果其中有一个字符串和当前的字符串相同,则是旋转之后的结果,否则就不是旋转之后的结果!

注意:判断两个字符串是否相等不能直接用 == ,而是要用strcmp函数,如果strcmp的返回结果为0,则相等,否则就不相等。


方法一:左旋k次比较法

在这里插入图片描述

前提:两个字符串长度相等 如果两个字符串长度不相等,那么必定不是旋转之后的结果
1.将字符左旋1个字符的得到的结果跟另一个字符相比较
2.将步骤1重复len次

代码实现:

#include<stdio.h>
#include<string.h>
#include<assert.h>

//方法三:三步翻转法
//翻转函数
void reverse(char* start, char* end)//将start和end之间的字符进行翻转
{
	while (start < end)
	{
		char tmp = *start;
		*start = *end;
		*end = tmp;
		start++;
		end--;
	}
}
void levotor3(char* arr1, int k)
{
	assert(arr1 != NULL);//断言,防止传入空指针,刷题时这句可以去掉
	int len = strlen(arr1);
	assert(k <= len);//断言,防止k大于字符串长度,刷题时这句可以去掉
	reverse(arr1, arr1 + k - 1);//1.将前k个字符进行翻转
	reverse(arr1 + k, arr1 + len - 1);//2.将第k+1到第len个字符翻转
	reverse(arr1, arr1 + len - 1);//3.整体翻转
}



//方法一:字符串比较法
int Is_levotor1(char* s1, char* s2)
{
	assert(s1 && s2);//断言,防止传入空指针,刷题时这句可以去掉
	int len1 = strlen(s1);
	int len2 = strlen(s2);
	if (len1 != len2)
	{
		return 0;
	}
	else
	{
		int i = 0;
		for (i = 0; i < len1; i++)//左旋0个字符和左旋len1个字符的结果是一样的
		{
			levotor3(s1, 1);
			int ret = strcmp(s1, s2);
			if (ret == 0)
			{
				return 1;
			}
		}
	}
	return 0;
}

int main()
{
	char s1[] = "AABCD";
	char s2[] = "BCDAA";
	int ret = Is_levotor1(s1, s2);//方法一:字符串比较法
	if (ret == 1)
	{
		printf("s2 is a levotorsion-string of s1.\\n ");
	}
	else
	{
		printf("s2 is not a levotorsion-string of s1.\\n ");
	}
	return 0;
}

在这里插入图片描述

方法一实现了判断是否为旋转之后的结果,但是这种方式有两个明显的缺点
一是改变了原字符串的内容,虽然我们可以通过创建临时字符数组的方式来避免这种情况的发生,但是临时数组的大小给多少呢?
二是旋转len次,将每次的结果进行比较看起来有点不太聪明的样子。

实际上每次旋转的结果可以用字符串追加后的字串来表示,例如:
字符串AABCD旋转之后的结果为字符串AABCDAABCD的字串,利用这个特点,我们就不用去旋转len次,而是去追加字符串,判断字串即可,这也是方法二的思路。


方法二:查找字串法

判断一个字符串是否为另外一个字符串的字串,使用函数strstr,不是字串返回一个空指针NULL,否则就返回第一个字串的地址。
字符串追加函数是strcat
在这里插入图片描述


代码实现:

#include<stdio.h>
#include<string.h>
#include<assert.h>

//方法二:字符串追加判断字串法
int Is_levotor2(char* s1, char* s2)
{
	assert(s1 && s2);//断言,防止传入空指针,刷题时这句可以去掉
	int len1 = strlen(s1);
	int len2 = strlen(s2);
	if (len1 != len2)
	{
		return 0;
	}
	char* s[20] = { 0 };
	strcat(s, s1);
	strcat(s, s1);//s追加两次,相当于s1自己追加自己
	if (strstr(s, s2) == NULL)
	{
		return 0;
	}
	return 1;
}

int main()
{
	char s1[] = "AABCD";
	char s2[] = "BCDAA";
	//int ret = Is_levotor1(s1, s2);//方法一:字符串比较法
	int ret = Is_levotor2(s1, s2);//方法二:字符串追加判断字串法

if (ret == 1)
	{
		printf("s2 is a levotorsion-string of s1.\\n ");
	}
	else
	{
		printf("s2 is not a levotorsion-string of s1.\\n ");
	}
	return 0;
}

在这里插入图片描述

(求点赞,收藏+评论,如果你能关注我,就是对我最大的鼓励!)


完整代码一:

#include<stdio.h>
#include<string.h>
#include<assert.h>

//方法一:临时数组法
void levotor1(char* arr1,int k)
{
	assert(arr1 != NULL);//断言,防止传入空指针,刷题时这句可以去掉
	char arr2[5] = { 0 };
	int i = 0;
	int len = strlen(arr1);//计算好字符串的长度
	assert(k <= len);//断言,防止k大于字符串长度,刷题时这句可以去掉
	//1.将前k个字符存放到arr2数组中
	for (i = 0; i < k; i++)
	{
		arr2[i] = arr1[i];
	}
	//2.将字符串第k个字符后面的字符往前平移k个单位
	for (i = k; i < len; i++)
	{
		arr1[i-k] = arr1[i];
	}
	//3.将临时数组中的内容平移回字符串
	for (i = len - k; i < len; i++)
	{
		arr1[i] = arr2[i-(len-k)];
	}
}
//方法二:临时变量k次法
void levotor2(char* arr1, int k)
{
	assert(arr1 != NULL);//断言,防止传入空指针,刷题时这句可以去掉
	int i = 0;
	int j = 0;
	int len = strlen(arr1);
	assert(k <= len);//断言,防止k大于字符串长度,刷题时这句可以去掉
	//2.重复k遍
	for (i = 0; i < k; i++)
	{
		char tmp = arr1[0];//创建临时变量存放取出来的字符
		//1.实现左旋一次的操作
		for (j = 0; j < len - 1; j++)
		{
			arr1[j] = arr1[j + 1];
		}
		arr1[len - 1] = tmp;
	}
}

//方法三:三步翻转法
//翻转函数
void reverse(char* start, char* end)//将start和end之间的字符进行翻转
{
	while (start < end)
	{
		char tmp = *start;
		*start = *end;
		*end = tmp;
		start++;
		end--;
	}
}
void levotor3(char* arr1, int k)
{
	assert(arr1 != NULL);//断言,防止传入空指针,刷题时这句可以去掉
	int len = strlen(arr1);
	assert(k <= len);//断言,防止k大于字符串长度,刷题时这句可以去掉
	reverse(arr1, arr1 + k - 1);//1.将前k个字符进行翻转
	reverse(arr1 + k, arr1 + len - 1);//2.将第k+1到第len个字符翻转
	reverse(arr1, arr1 + len - 1);//3.整体翻转
}
int main()
{
	char arr1[] = "ABCD";
	int k = 0;
	scanf("%d", &k);
	//levotor1(arr1,k);//左旋字符函数:方法一
	//levotor2(arr1,k);//左旋字符函数:方法二
	levotor3(arr1, k);//左旋字符函数:方法三
	printf("%s\\n", arr1);
	return 0;
}

完整代码二:

#include<stdio.h>
#include<string.h>
#include<assert.h>

//方法三:三步翻转法
//翻转函数
void reverse(char* start, char* end)//将start和end之间的字符进行翻转
{
	while (start < end)
	{
		char tmp = *start;
		*start = *end;
		*end = tmp;
		start++;
		end--;
	}
}
void levoto

以上是关于左旋字符串及左旋字符串的进阶的主要内容,如果未能解决你的问题,请参考以下文章

C语言进阶之旅(每日一题)字符串左旋

最强解析面试题:左旋转字符串

最强解析面试题:左旋转字符串

C语言编程 字符串的旋转(左旋右旋及判断)

字符串左旋问题

字符串左旋