一个更优的字符串左旋算法

使用递归方法实现字符串左旋

字符串的相关算法中,有一个左旋操作,将一个字符串进行按字符左旋k次,例如,“abcdefgh"左旋3次,得到"defghabc":
在这里插入图片描述

实现该操作的算法常见有三种:

  • 将第一个字符缓存,将后面的所有字符向前移动一位,最后将缓存的第一个字符存到最后空出来的位置。这样的的操作执行k次。
  • 将前k个字符缓存,然后将剩余字符一次性向前移动k位,最后将缓存的内容填充到空出来的位置。
  • 将字符串分成两部分,前一部分长度为k,这两部分分别执行一次字符串反转,最后再对整个字符出串执行翻转。

个人对左旋前后结果对比,想到一个可能更好的算法。

分析

以上面的左旋例子为参考,字符串"abcdefgh"左旋3次,前3个字符"abc"会被移动到结尾,这是可以直接操作的,假设先将前3个字符跟后3个字符位置交换,得到:
在这里插入图片描述
此时"abc"已经移动到准确的位置,不需要再改变位置。

"fghde"部分,“de"之前是紧跟在"abc"后面,所以应该移动到最前面,而"fgh"应该在"de"之后,所以需要交换位置:
在这里插入图片描述
这里相邻内容交换位置,实际就是左旋,需要对"fghde"左旋3次,从而得到"defgh”。

进一步左旋

上面的交换位置操作后,“fghde” 需要进行左旋3次。上面的逻辑,左旋可以先进行一次位置交换,但是 “fgh” 比 “de” 长,无法直接交换前3个与后3个位置。

此时反过来考虑,“de” 实际是要移动到最前面的,只交换 “de” 与前2个字符,即可以保证 “de” 的位置准确。交换结果:
在这里插入图片描述
只剩下 “hfg” 的顺序不对。“h” 是一部分,“fg” 是一部分,此时只需要对 “hfg” 进行一次左旋即可。

继续,再进行一次交换,“h” 位置固定,“gf” 需要一次左旋:
在这里插入图片描述
继续,最后交换 “g” 和 “f”:
在这里插入图片描述

所以有两个问题:

  • 如何确定交换的长度
  • 交换后确定下一次左旋的边界和次数

确定算法

首先,左旋次数k,会将字符串分成两部分,前k个和剩余部分。每次交换后,首或尾一部分子串固定。剩余子串中上次交换的和没有交换的,将该子串前后分成两部分a和b,需要对该子串进行左旋,次数为a部分的长度。

所以,假设左旋次数为k,字符串长度为len:

扫描二维码关注公众号,回复: 14462111 查看本文章
  • 如果 k <= len / 2,交换首尾 k 长度的内容,字符串右边界定为len - k,k不变,重复;
  • 如果 k > len / 2,k1 = len - k, 交换首尾 k1 长度的内容,字符串左边界定位k1,k = k - k1;重复;

参考代码:

int offset = 0;
int len = str.length();
k = k % len;
while (len > 1 && k > 0 && k != len) {
    
    
	if (k <= len / 2) {
    
    
		const int end = offset + len - k;
		for (int i = 0; i < k; i++) {
    
    
			std::swap(str[offset + i], str[end + i]);
		}
		len -= k;
	}
	else
	{
    
    
		int tmpn = len - k;
		const int end = offset + len - tmpn;
		for (int i = 0; i < tmpn; i++) {
    
    
			std::swap(str[offset + i], str[end + i]);
		}
		offset += tmpn;
		len -= tmpn;
		k -= tmpn;
	}
}

需要注意的是:

  • 子串长度为1时,不执行左旋
  • 假设k == len/2,经过一次交换后,已经完成了整个操作,此时下一步的子串长度刚好等于k,不需要对子串进行左旋。

上面的代码,也可以进一步优化:

  • 代码可以改为递归方式
  • 每次循环结尾,k = k % len,保持k始终小于len
  • len == 2时,k只能为1,直接交换即可

猜你喜欢

转载自blog.csdn.net/eiilpux17/article/details/124803104