字符串旋转问题

有一道关于字符串旋转的题比较有意思,拿来和大家分享一下,题目是这样的:

实现一个函数,可以左旋字符串中的k个字符。
例如:AABCD左旋一个字符得到ABCDA,AABCD左旋两个字符得到BCDAA

现在就来讲讲这个三步旋转法是怎么实现左旋的,先假设 k 的值为 1 :

1、把原字符串整体逆序----------------------->>>DCBAA;

2、把左半部分的字符串(DCBA)逆序--->>>ABCDA;

3、把右半部分的字符串(A)逆序---------->>>ABCDA

当k为其它值的时候也是使用的哦!

按照我以前的套路,先把整个程序框架建起来:

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

int main()
{
	char arr[] = "aabcd";

	printf("%s\n",  left_move(arr, 2));

	return 0;
}
头文件我们也先摆在这里,一会要用到,接下来是字符串左旋函数的具体实现:

char * left_move(char * src, int k)
{
	int i = 0;
	int len = strlen(src);
	char tmp = 0;
	assert(src);							//保证src有效性

	for (i = 0; i < len / 2; i++)			//整体逆序
	{
		tmp = *(src + i);
		*(src + i) = *(src + len - 1 - i);
		*(src + len - 1 - i) = tmp;
	}

	for (i = 0; i < (len - k) / 2; i++)		//左串逆序
	{
		tmp = *(src + i);
		*(src + i) = *(src + len - k - 1 - i);
		*(src + len - k - 1 - i) = tmp;
	}

	for (i = 0; i < k / 2; i++)				//右串逆序
	{
		tmp = *(src + len - k + i);
		*(src + len - k + i) = *(src + len - 1 - i);
		*(src + len - 1 - i) = tmp;
	}

	return src;
}

也就是连续三次字符串的逆序,是不是觉得很简单呢!

这个程序还可以优化一下,比如把其中多次用到的逆序操作封装成一个函数,这样,整个程序看起来可以更加的简洁! 这里我偷个懒就不再改了。

这道题还有一个II版本,也让我们来分析一下:

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

由于我们已将有了左旋的函数了,所以这道题也就不难了。想一想,其实只要我们每次旋转一个字符,然后比较一下,这样一圈下来,结果不就出来了吗?

但要注意的是,字符串的 旋转可不仅有左旋,还有右旋!那么对于右旋的情况怎么办呢?要再实现一个右旋函数么?

其实仔细想想,完全没有这个必要:我们只需先依次左旋s1再与s2进行比较,发现不相等,就接着左旋s2然后与s1进行比较就好了,脑筋转不过来弯不妨画个图看看吧微笑

现在就来改一改上面的程序:

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

/*
* 实现一个函数,可以左旋字符串中的k个字符。
* AABCD左旋一个字符得到ABCDA
* AABCD左旋两个字符得到BCDAA
*/
char * left_move(char * src, int k)
{
	int i = 0;
	int len = strlen(src);
	char tmp = 0;
	assert(src);							//保证src有效性

	for (i = 0; i < len / 2; i++)			//整体逆序
	{
		tmp = *(src + i);
		*(src + i) = *(src + len - 1 - i);
		*(src + len - 1 - i) = tmp;
	}

	for (i = 0; i < (len - k) / 2; i++)		//左串逆序
	{
		tmp = *(src + i);
		*(src + i) = *(src + len - k - 1 - i);
		*(src + len - k - 1 - i) = tmp;
	}

	for (i = 0; i < k / 2; i++)				//右串逆序
	{
		tmp = *(src + len - k + i);
		*(src + len - k + i) = *(src + len - 1 - i);
		*(src + len - 1 - i) = tmp;
	}

	return src;
}

/* 
 * 判断一个字符串是否为另外一个字符串旋转之后的字符串。
 * 例如:给定s1 = AABCD和s2 = BCDAA,返回1,给定s1=abcd和s2=ACBD,返回0.
 */
int is_string_move(char * left, char * right)
{
	int len1 = strlen(left);
	int len2 = strlen(right);
	int i = 0;
	assert(left);
	assert(right);
	for (i = 0; i < len1; i++)		//依次左移left串与right比较,循环len次后,刚好回到初始位置
	{
		if (0 == strcmp(left_move(left, 1), right))
			return 1;
	}
	for (i = 0; i < len1; i++)		//依次左移right串与left比较,相当于右移left串与right比较
	{
		if (0 == strcmp(left_move(left, 1), right))
			return 1;
	}
	return 0;
}
int main()
{
	char arr[] = "aabcd";
	char arr2[] = "abcde";

	if (is_string_move(arr2, arr))
		printf("yes\n");
	else
		printf("no\n");

	return 0;
}

这样肯定是可以的,但是这样的效率是不怎么高的!所以我就又想了想,其实还可以这样做:

s1 = AABCD和s2 = BCDAA,我们把s1变为AABCDAABCD,这样,我们只需在其中查找是否包含s2这个子串就好了!因为修改后的s1包含了所有旋转后的可能的情况。

新实现的代码如下:

int is_string_move2(char * left, char * right)
{
	int len_left = strlen(left);
	int len_right = strlen(right);

	assert(left);
	assert(right);
	
	if (len_left != len_right)		//长度不等,必不存在
		return 0;

	strcat(left, left);		
	if (strstr(left, right) != NULL)
		return 1;
	else
		return 0;
}
仔细看看上面这段代码,看起来似乎可以,然而我们编译运行一下,发现崩溃了!为什么?

原来是strcat()这个函数出了问题,因为strcat是把str拷贝到str的末尾,所以会出现永远拷不完的情况,开始时str要移到str的末尾

字符串旋转问题_第1张图片

然后把str字符挨个拷到str末尾,变成这样:

字符串旋转问题_第2张图片

发现str永远拷不完了!

所以我们改用strncat()函数,是一个相对来说较安全的一个函数,每次调用可以指定拷多少个字符:

int is_string_move2(char * left, char * right)
{
	int len_left = strlen(left);
	int len_right = strlen(right);

	assert(left);
	assert(right);
	
	if (len_left != len_right)		//长度不等,必不存在
		return 0;

	strncat(left, left, len_left, len_left);		
	if (strstr(left, right) != NULL)
		return 1;
	else
		return 0;
}

这样程序就没有问题了!


你可能感兴趣的:(字符串,旋转,源代码,C语言)