热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【每天学一点系列~】字符串左/右旋的本质,你真的认清了嘛?

学透字符串的旋转PartI、前言PartII、左右旋1、定义2、共同特点PartIII、初阶解法解法一:创建新数组解法二:原地算法(直接法)PartIV、进阶解法1、三步反转法2、

学透字符串的旋转

  • Part I、前言
  • Part II、左/右旋
    • 1、定义
    • 2、共同特点
  • Part III、初阶解法
    • 解法一:创建新数组
    • 解法二:原地算法(直接法)
  • Part IV、进阶解法
    • 1、 三步反转法
    • 2、找子串法
  • Part V、写在最后
Part I、前言

每天学一点,进步多一点!
今天的主题:string rotation 字符串的左/右旋
《【每天学一点系列~】字符串左/右旋的本质,你真的认清了嘛?》

Part II、左/右旋

1、定义

其实用文字比较难描述出来,我们用例子能更加简单的理解:
给出字符串:ABCDE
左旋一次:BCDEA
左旋两次:CDEAB
左旋三次:DEABC
左旋四次:EABCD
左旋五次:ABCDE
我们注意到,左旋K次,就是把前K个字符整体挪动到字符串的右边,注意,是整体挪动,这意味着挪动的部分,它的顺序与原先相同。
我们还注意到,经过五次左旋,字符串回到了初始的样子。(这句不是废话哦)

同样是字符串:ABCDE
右旋一次:EABCD
右旋两次:DEABC
右旋三次:CDEAB
右旋四次:BCDEA
右旋五次:ABCDE
与左旋类似,我们将后K个字符整体挪动到字符的左边,顺序不变。
我们同样注意到,经过五次右旋,字符串回到了初始的样子。

2、共同特点

特点一:无论是左旋K次还是右旋K次,无非就是将左边的或者右边的K个连续字符,整体的进行位置的挪动,概括下来就是:左旋就是左边整体右挪,右旋就是右边整体左挪,且顺序不变;
特点二:当旋转的次数K达到某一特定值时,字符串维持原样,相当于没有进行旋转,而且我们不难发现,也不难论证出:这个特定值,就是字符串的长度(将整个字符串整体挪动,自然是维持原样)

Part III、初阶解法

由于左右旋具有许多共同特性,考虑到篇幅问题,我们主要讨论左旋的情况,右旋的话,简单改变一下代码中的某些控制量就OK啦~

解法一:创建新数组

我们根据上面的特点1,将后len-K%len个存到一个数组ret中,然后将前K个存到数组ret的剩余位置。

K%len是什么意思? 【K:旋转次数 len:字符串长度(不包括空字符’\0’)】
根据我们先前的特点二:“当旋转的次数K达到字符串长度len时,字符串维持原样,相当于没有进行旋转”
所以当我们让len÷K,得到的结果的整数部分,意味着我们要对这个字符串整体挪动这整数部分次,这并没有对字符串造成实质性的影响
而余数部分,才是我们需要考虑的真正有用的那几次旋转——也就是K%len次;

理解了做法后,我们代码实现一波:

char* left_rotation(char arr[], int len, int K)
{
char* ret = (char*)malloc((len + 1) * sizeof(char));
if (ret)//malloc成功
{
int i;
for (i = 0; i < len - K % len; i++)//将后len-K%len个存到一个数组ret中
{
ret[i] = arr[i + K % len];
}
for (i = 0; i < K % len; i++)//将前K个存到数组ret的剩余位置
{
ret[i + len - K % len] = arr[i];
}
ret[len] = '\0';
return ret;
}
else//malloc失败,返回空指针
{
return NULL;
}
}

注意:调用该函数后需要将返回的指针free掉,避免内存泄漏!

解法二:原地算法(直接法)

原地算法,顾名思义,就是通过覆盖的方式,在arr的内部直接进行旋转操作,同样,我们拿左旋举例——
我们将arr的第一个字符取出,然后拿后方的数据覆盖前面的数据,最后再把取出的字符接到数组的末尾,重复该操作K%len次。
【注:这里的K%len参考上面的解释哦~】
代码实现:

void left_rotation(char arr[], int len, int K)
{
int k=K%len;//执行次数
while (k--)
{
int i, tmp = arr[0];//取出数组的第一个数
for (i = 0; i < len-1; i++)
{
arr[i] = arr[i + 1];//后方数据覆盖前方数据
}
arr[len - 1] = tmp;//将取出的数放到最后一个
}
}
Part IV、进阶解法

1、 三步反转法

我们刚刚说过,左旋的本质实际上就是:将前面的K%len个整体挪动到后方,将后面的挪到前方,也就是交换前后两部分的位置,并且要求它的顺序与原先一致;
借鉴大神的一种解释方式:
我们原先的数组是这样的:
《【每天学一点系列~】字符串左/右旋的本质,你真的认清了嘛?》
我们将其分为两个部分:
《【每天学一点系列~】字符串左/右旋的本质,你真的认清了嘛?》
我们想要的结果是这样:
《【每天学一点系列~】字符串左/右旋的本质,你真的认清了嘛?》
而想要做到这样,我们只需要反转S1和S2:
《【每天学一点系列~】字符串左/右旋的本质,你真的认清了嘛?》
然后整体反转:
《【每天学一点系列~】字符串左/右旋的本质,你真的认清了嘛?》
surprise!目的达成。
这样的做法有一定的数学依据(经评论区老铁建议后修改)——
学过线性代数的知道:当我们假定数组为AB,A代表S1,B代表S2,想要得到BA
只需要:(AT·BT)T
也就是说,对A和B分别求转置矩阵后,在整体求转置矩阵!与这里三次反转如出一辙!
代码实现

void Reverse(char arr[], int left, int right)
{
while (left < right)
{
char tmp = arr[left];
arr[left] = arr[right];
arr[right] = tmp;
left++;
right--;
}
}
void left_rotation(char arr[], int len, int K)
{
Reverse(arr, 0, K % len-1);
Reverse(arr, K % len, len-1);
Reverse(arr, 0, len-1);
}

2、找子串法

这个方法同上面的方法一样,很难想到,但是也很好理解!
本解法灵感来自这道题的一道改编题:给定一个字符串,判断其是否能通过另一个字符串的旋转得到。
比如:CDEFAB可以通过字符串ABCDEF左旋两个字符得到。
对于这道题的做法,我们采取接长字符串ABCDEF的方法,也就是说,我们在ABCDEF的后面再接上一个ABCDEF,让其变成:ABCDEFABCDEF,此时,我们会惊奇的发现,由于字符串旋转的性质一,ABCDEF这个字符串所有可能的旋转结果都是这个接长后的字符串的子串!
如果将这种做法放到这道题上,我们不难看出:
假设字符串长度为len,左旋次数为K,那么这个接长字符串中下标为K%len的就是我们所需要的那个旋转后的字符串的首地址!
比如:ABCDEF左旋两次,就是ABCDEFABCDEF中下标为2的,就是我们需要返回的字符串的首地址。
代码实现:

char* left_rotation(char arr[], int len, int K)
{
strncat(arr,arr,len);//strncat函数可以在数组后追加自己
arr[K%len + len] = '\0';
return arr + K%len;//返回下标为K的那个地址
}

这个函数的使用需要确保arr有足够的空间来追加自己!

Part V、写在最后

一道简单的C语言题目,从第一反应的初阶解法再到进阶解法,真的不得不感叹:想出这种解法的人有多聪明!反正,笔者是真的相当佩服!

不知道这篇文章对你是否有所帮助呢?你又是否对字符串的旋转有什么新的见解?欢迎评论区赐教!
这是《每天学一点系列~》的第一篇,后续还会有更新,喜欢的话,欢迎给个三连哦!
by白龙码
《【每天学一点系列~】字符串左/右旋的本质,你真的认清了嘛?》


推荐阅读
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • C语言注释工具及快捷键,删除C语言注释工具的实现思路
    本文介绍了C语言中注释的两种方式以及注释的作用,提供了删除C语言注释的工具实现思路,并分享了C语言中注释的快捷键操作方法。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • C语言判断正整数能否被整除的程序
    本文介绍了使用C语言编写的判断正整数能否被整除的程序,包括输入一个三位正整数,判断是否能被3整除且至少包含数字3的方法。同时还介绍了使用qsort函数进行快速排序的算法。 ... [详细]
  • C语言常量与变量的深入理解及其影响
    本文深入讲解了C语言中常量与变量的概念及其深入实质,强调了对常量和变量的理解对于学习指针等后续内容的重要性。详细介绍了常量的分类和特点,以及变量的定义和分类。同时指出了常量和变量在程序中的作用及其对内存空间的影响,类似于const关键字的只读属性。此外,还提及了常量和变量在实际应用中可能出现的问题,如段错误和野指针。 ... [详细]
  • 电话号码的字母组合解题思路和代码示例
    本文介绍了力扣题目《电话号码的字母组合》的解题思路和代码示例。通过使用哈希表和递归求解的方法,可以将给定的电话号码转换为对应的字母组合。详细的解题思路和代码示例可以帮助读者更好地理解和实现该题目。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • c语言\n不换行,c语言printf不换行
    本文目录一览:1、C语言不换行输入2、c语言的 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
author-avatar
longyuyuyu
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有