非递归
方法一
思路:
将字符串的第一个字符和最后一个字符交换,接着将第二个字符和倒数第二个字符交换,以此类推,直到所有的对应字符交换完,具体操作如下:
1、采用两个下标left和right分别指向字符串的第一个字符和最后一个字符
2、交换left和right所指元素,接着left向后走一步指向第二个字符,right向前走一步指向倒数第二个字符
3、重复第2步,直到所有对应字符交换完
核心代码:
void reverseStr(char* str) //逆序字符串的函数
{
int left = 0; //初始化,指向字符串第一个字符
int right = strlen(str) - 1; //初始化,指向字符串最后一个字符
while (left < right) //只要left小于right,一直交换
{
//交换两个对应字符
char tmp = str[left];
str[left] = str[right];
str[right] = tmp;
left++; //更新left,left向后走
right--; //更新right,right向前走
}
}
方法二
思路:
方法二的思路和方法一相同,只不过是具体操作不一样罢了,具体操作如下:
1、假设字符串的长度为len,用i作为下标,依次遍历数组
2、每次遍历到一个字符的时候,交换下标i和len-1-i对应的字符
3、重复第2步,只要i<len/2就一直交换
核心代码:
void reverseStr(char* str) //逆序字符串的函数
{
int len = strlen(str); //求字符串的长度
int i = 0;
for (i = 0; i < len / 2; i++) //以中间字符为中点,依次交换前后对应字符
{
//交换对应字符
char tmp = str[i];
str[i] = str[len - i - 1];
str[len - i - 1] = tmp;
}
}
总结:非递归逆序字符串的核心就是:以中间元素为中点,交换前后对应元素,非递归逆序字符串不是很难理解,时间复杂度也不是很高,相当于遍历了一半字符串
递归
注:当你在用递归解决问题的时候,你要知道递归是:将一个大的规模问题转换成一个具有相同性质的小的规模问题,所以你在用递归方式写一个函数的时候,你只需要想清楚如何解决这个小问题,梳理清楚解决步骤,写出一个函数,剩下的深层次就交给计算机来完成就ok了,不要将这个问题一层层的想的太深,否则你就会陷入到问题的循环当中。
心平气和的去想,一丝不苟的去做,多看几遍,最重要的是自己动手画图推导一下,要相信自己可以破局!
方法一
思路:
假如reverseStr( )这个函数可以逆序字符串,先不要着急想这个函数内部如何具体实现,我们一步一步推导内部实现,具体推导reverseStr( )函数内部实现如下:
1、如果字符串不为空,先逆序第一个字符后面的字符串(此时需要逆序后面的字符串,我们就可以将它交给reverseStr( )函数来实现,这一步其实就是在递归),如果字符串为空或者仅剩一个字符的时候,则什么事都不用做,就等于终止递归了
2、后面字符串逆序结束之后再将第一个字符放到这个逆序字符串的后面(完成这一过程具体操作:先将逆序字符串整体往前移动一位,再将第一个字符放到后面就ok了)
3、第1步中需要逆序的字符串又可以采用第1步和2步来完成,这样一直递归直到不满足递归条件
注:第1步和2步就是reverseStr( )函数的内部具体实现,我们发现我们在第1步中还调用了reverseStr( )函数,这就相当于我们在函数内部调用了自身,这就是递归。
递归函数表达式和伪代码:
图解:
核心代码:
void reverseStr(char* str) //逆序字符串的函数
{
if (*str && *(str + 1)) //字符串至少还有两个元素
{
reverseStr(str + 1); //逆序当前字符串中第一个字符后面的字符串
//将第一个字符放在上一步逆序的字符串后面(具体操作就是:将上一步逆序的字符串整体往前移动一位,再将第一个字符放到最后面)
char tmp = *str; //先保存第一个字符,以免被覆盖
char* p = str + 1; //操作指针
while (*p) //整体前移字符串,遇到'\0'停止
{
*(p - 1) = *p;
p++;
}
*(p - 1) = tmp; //第一个元素放到最后面
}
}
方法二
思路:
先将第一个字符和最后一个字符中间的字符串逆序,然后再将第一个字符和最后一个字符交换,而要逆序中间的字符串继续采用相同的方式逆序,这样一直递归下去,直到中间的字符串为空或者只剩一个字符的时候终止递归。
问题来了:对于上述思路,当我需要逆序中间字符串的时候,我每次要怎么确定中间字符串的区间呢(每次往下递归的时候,区间都是不一样的)?下面介绍了两种写法:
写法一:
采用两个下标left和right分别指向字符串两端,每次逆序中间字符串的时候只需要将left加1,right减1即可,直到left大于或者等于right的时候停止
核心代码:
void reverseStr(char* str, int left, int right) //逆序left和right中间区域的字符串(不包括left和right所指字符)
{
if (left < right) //left小于right一直递归
{
reverseStr(str, left + 1, right - 1); //递归逆序第一个字符和最后一个字符中间的字符串
//交换第一个和最后一个字符
char tmp = str[left];
str[left] = str[right];
str[right] = tmp;
}
}
写法二:
1、保存当前字符串第一个字符,将当前字符串最后一个字符赋值给第一个字符,再将最后一个字符赋值为’\0’
2、逆序第一个字符后面的字符串
3、将保存的第一个字符赋值给第1步所说的最后一个字符
4、对于第2步中逆序字符串又可以采用第1、2和3步同样的方式处理,这样一直递归下去,直到字符串剩余一个字符或者为空
图解:
核心代码:
void reverseStr(char* str) //逆序字符串的函数
{
int len = strlen(str);
if (len >= 2) //字符串至少应有两个字符才能递归
{
char tmp = str[0]; //保存第一个字符
str[0] = str[len - 1]; //将当前字符串中最后一个字符赋值给第一个字符
str[len - 1] = '\0'; //将最后一个字符赋值为'\0'
reverseStr(str + 1); //逆序中间的字符串
str[len - 1] = tmp; //将保存的第一个字符赋值给最后一个字符
}
}
总结:
1、采用非递归的方式逆序字符串效率比递归方式高
2、递归方式中,方法一相对于方法二要慢一些,主要是因为要移动大量的元素,
3、我个人在考虑递归问题的时候,我会先心里定义一个函数,我不知道这个函数的实现,我会把这个函数当作一个别人已经写好的函数并且具有解决这个问题的功能,然后带着这个函数从问题出发梳理清楚逻辑,把这个函数用到解决这个问题的代码中,最后所写的代码就是最终的这个递归函数