一些C语言中字符串的算法问题解决实例小结

字符串问题是面试中经常出现的问题,这类问题有很多,难以不一。下面是几道字符串的题目,网上都能找到解答,自己实现了一下,供网友参考。感觉算法重要的是要有正确的思路,实现起来不是问题。自己一定要多思考,这样收获可能会更多一点。
       
问题1:找两个字符串的最长公共子串。
        具体描述,如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,则字符串一称之为字符串二的子串。注意,并不要求子串(字符串一)的字符必须连续出现在字符串二中。请编写一个函数,输入两个字符串,求它们的最长公共子串,并打印出最长公共子串。
    思路:算法书上一般都有介绍,就是用动态规划的思想。关键是要找到最优子结构性质,这一点比较难。另外一个经常问到的问题“求子数组的最大和”,用的也是动态规划的思想。找两个字符串最长公共子串的核心就是找最优子结构。
        设序列X = {x1,x2,…xm}和Y = {y1,y2,…yn}的最长公共子串为Z = {z1,z2,…zk},则
        1 若xm= yn且zk=xm=yn, 则Zk-1是Xm-1和Yn-1的最长公共子串
        2 若xm!=yn且zk!=xm,则Z是Xm-1和Y的最长公共子串
        3 若xm!=yn且zk!=yn,则Z是X和Yn-1的最长公共子串
         其中Xm-1= {x1,x2,…,xm-1},Yn-1 = {y1,y2,…,yn-1}Zk-1 = {z1,z2,…zk-1}
      有了上述关系,则很容易得到递推的式子。用一个二维数组 R 记录结果,其中Z[i][j]表示Xi和Yj的最长公共子串长度。
     (1)如果i = 0或j = 0,Z[i][j] = 0
     (2)如果xi和yj相等,Z[i][j] = Z[i-1][j-1] + 1
     (3) 如果xi和yj不相等,Z[i][j] = max{Z[i-1][j],Z[i][j-1]}
        基本上差不多了,但是题目要求打印最长公共子串,只要用一个数组R记录得到最长公共子串的过程,其中R[i][j]表示Z[i][j]的值是由哪个子问题的解得到的。
       参考代码:

//函数功能 : 打印最长公共子串
//函数参数 : X为源字符串1,Y为源字符串2,R为记录的过程,row为R的行坐标,col为R的列坐标
//返回值 :  空
void LCS_Print(const char *X, const char *Y, int **R, int row, int col)
{
  if(R[row][col] == 1)
  {
    LCS_Print(X, Y, R, row-1, col-1);
    cout<<X[row-1];  //由Xi-1和Yj-1,再加上Xi或Yj得到
  }
  else if(R[row][col] == 2)
    LCS_Print(X, Y, R, row-1, col); //由Xi-1和Yj得到
  else if(R[row][col] == 3)
    LCS_Print(X, Y, R, row, col-1); //由Xi和Yj-1得到
  else
    return;
}
//函数功能 : 求两个字符串的最大公共子串
//函数参数 : X为源字符串1,Y为源字符串2
//返回值 :  最大长度
int LCS(const char *X,const char *Y)
{
  if(X == NULL || Y == NULL)
    return 0; 

  int lenX = strlen(X);
  int lenY = strlen(Y);
  if(lenX == 0 || lenY == 0)
    return 0; 

  int i, j;
  int **C = new int *[lenX+1];
  int **R = new int *[lenX+1]; 

  //初始化
  for(i = 0; i <= lenX; i++)
  {
    C[i] = new int [lenY+1];
    R[i] = new int [lenY+1];
    for(j = 0; j <= lenY; j++)
    {
      C[i][j] = 0;
      R[i][j] = 0;
    }
  } 

  //开始计算
  for(i = 1; i <=lenX; i++)
  {
    for(j = 1; j <=lenY; j++)
    {
      if(X[i-1] == Y[j-1]) //字符串的下标从0开始,所以减1,即X1 = X[0] Y1 = Y[0]
      {
        C[i][j] = C[i-1][j-1] + 1;
        R[i][j] = 1;  //由Xi-1和Yj-1,再加上Xi或Yj得到
      }
      else
      {
        if(C[i-1][j] >= C[i][j-1])
        {
          C[i][j] = C[i-1][j];
          R[i][j] = 2;   //由Xi-1和Yj得到
        }
        else
        {
          C[i][j] = C[i][j-1];
          R[i][j] = 3;   //由Xi和Yj-1得到
        }
      }
    }
  }
  //打印最长公共子串
  LCS_Print(X, Y, R, lenX, lenY);
  //记录最大长度
  int lcs = C[lenX][lenY];
  //释放空间
  for(i = 0; i <= lenX; i++)
  {
    delete [] C[i];
    delete [] R[i];
  }
  delete C;
  delete R;
  R = C = NULL;
  return lcs;
}

问题2:在字符串中删除特定元素
    具体描述,输入两个字符串,从第一字符串中删除第二个字符串中所有的字符。例如,输入”They are students.”和”aeiou”,则删除之后的第一个字符串变成”Thy r stdnts.”。
    思路:这是字符查找的一个问题,最简单的就是考察第二个字符串的每个字符,然后检查第一个字符串中有没有这个字符,有的话删除。这样的时间复杂度是O(mn)。对于查找,我们容易想到二分查找、散列这些概念。散列的查找是非常快,时间复杂度为O(1)。如果能联想到散列,那么很容易就能给出下面的解法,相信很多人都能想到。需要一个256字节的数组,记录第二个字符串中每个字符的出现情况,0表示未出现,1表示出现。然后遍历第一个字符串,针对每个字符,去查询记录数组,以决定删除与否。
   参考代码:

//函数功能 : 在字符串中删除特定字符
//函数参数 : pSrc为源字符串,pDel为特定字符集
//返回值 :  空
void DeleteSpecialChars(char *pSrc, char *pDel)
{
  if(pSrc == NULL || pDel == NULL)
    return;
  int lenSrc = strlen(pSrc);
  int lenDel = strlen(pDel);
  if(lenSrc == 0 || lenDel == 0)
    return;
  bool hash[256] = { false };
  for(int i = 0; i < lenDel; i++) //建立删除字符的索引
    hash[pDel[i]] = true;
  //开始删除
  char *pCur = pSrc;
  while(*pSrc != '\0')
  {
    if(hash[*pSrc] == false) //不用删除
      *pCur++ = *pSrc;
    pSrc++;
  }
  *pCur = '\0';
}

问题3:左旋转字符串,其实也可以左旋数组
   具体描述,定义字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部。如把字符串abcdef左旋转2位得到字符串cdefab。请实现字符串左旋转的函数。要求时间对长度为n的字符串操作的复杂度为O(n),辅助内存为O(1)。
   思路:用了一个小技巧,如果旋转的字符串为XY,其中Y是要旋转到X前面的。只要分别将子字符串 X 和 Y 翻转,然后再将整个字符串再做一次翻转即可。STL的一个算法rotate就是用了这个。
   参考代码:

//函数功能 : 将字符串翻转
//函数参数 : pBegin指向第一个字符,pEnd指向最后一个字符
//返回值 :  空
void ReverseString(char *pBegin, char *pEnd)
{
  if(pBegin == NULL || pEnd == NULL)
    return; 

  while(pBegin < pEnd)
  {
    //交换字符
    char tmp = *pBegin;
    *pBegin = *pEnd;
    *pEnd = tmp;
    //往中间靠拢
    pBegin++;
    pEnd--;
  }
} 

//函数功能 : 将字符串左旋 n 位
//函数参数 : pSrc为源字符串,n为左旋位数
//返回值 :  空
void LeftRotateString(char *pSrc, unsigned n)
{
  if(pSrc == NULL || n == 0 || n > strlen(pSrc))
    return; 

  int len = strlen(pSrc);
  ReverseString(pSrc, pSrc + n - 1);
  ReverseString(pSrc + n, pSrc + len - 1);
  ReverseString(pSrc, pSrc + len - 1);
}

问题4:在一个字符串中找到第一个只出现一次的字符。如输入abaccdeff,则输出b。
   思路:这一题不难,因为每个字符只有8位,因此可以用计数法。首先统计字符串中每个字符的出现次数,然后从头遍历字符串,对于当前字符,查询统计表,如果出现的次数为1,则输出该字符即可。
   参考代码:

//函数功能 : 在一个字符串中找到第一个只出现一次的字符
//函数参数 : pStr为源字符串
//返回值 :  目标字符
char FirstAppearOnce(char *pStr)
{
  if(pStr == NULL)
    return '\0'; //未找到返回空字符 

  int count[256] = {0};
  char *pTmp = pStr; 

  while(*pTmp != '\0') //统计字符串中每个字符出现的次数
  {
    count[*pTmp]++;
    pTmp++;
  }
  while(*pStr != '\0') //遍历字符串,找到第一个只出现一次的字符
  {
    if(count[*pStr] == 1)
      break;
    pStr++;
  }
  return *pStr; //找到的字符
}

问题5:写一个函数,它的原形是int continumax(char *outputstr,char *intputstr)。功能:在字符串中找出连续最长的数字串,并把这个串的长度返回,并把这个最长数字串付给其中一个函数参数outputstr所指内存。
        例如:"abcd12345ed125ss123456789"的首地址传给intputstr后,函数将返回9,outputstr所指的值为123456789
    思路:这一题比较简单,扫描一遍字符串即可,遇到数字时将数字个数加1,然后与最长串比较,如果长度大于最长串,更新最长串;遇到非数字时,将数字计数器清零。
    参考代码:函数名和形参名修改了一下,个人习惯。

//函数功能 : 在字符串中找出连续最长的数字串
//函数参数 : pSrc表示源串,pDest记录最长数字串的起始位置
//返回值 :  最长数字串的长度
int ContinueMax(char *pSrc,char * &pDest)
{
  pDest = NULL;
  if(pSrc == NULL)
    return 0; 

  int max = 0, cnt = 0;
  while(*pSrc != '\0')
  {
    if(*pSrc >= '0' && *pSrc <= '9') //数字
    {
      cnt++;
      if(cnt > max) //更新最长的数字串
      {
        pDest = pSrc - cnt + 1;
        max = cnt;
      }
    }
    else
      cnt = 0;
    pSrc++;
  }
  if(cnt > max)
  {
    pDest = pSrc - cnt + 1;
    max = cnt;
  }
  return max;
}

问题6:字符串转换为整数
      问题描述:输入一个表示整数的字符串,把该字符串转换成整数并输出。例如输入字符串"345",则输出整数345。
       思路:转换的过程比较简单,每次读入一个字符,将之前保存的值乘以10,然后再加上这个字符表示的数字。这是正常情况。这个问题主要是考察各种不正常情况的处理。假设函数的声明为 int StrToInt(const char *str);
       (1)输入的字符串指针为空;
       (2)数字前面有正负号的处理;
       (3)字符串表示的数字超过了32位整数能表示的范围,即溢出处理;
       (4)输入了非法字符,即除了数字及正负号的其他字符;
       (5)以字符' 0 '开始的串,' 0 '后面还跟了其他字符,也是非法的。
        如果能很好的处理这些情况,那么程序的健壮性大大增强。其中有两种情况处理起来有点麻烦,第一,如何处理溢出,我们可以使用std::numeric_limits<int>::max(),可以定义一个long long的变量,然后与这个最大值相比,从而判断是否溢出了。第二。由于返回值为一个整型数,那么如果转换失败,返回什么呢?如果是'0 ' ,那么就无法区分正常输入"0"的情况。两种方案,修改函数声明,通过返回值表明转换的成功与否,或者定义一个全局变量,用来保存转换的成功与否。参考代码中使用了第二种方案。
        参考代码:先给出的是std::numeric_limits<int>::max()的用法。

#include <iostream>
#include <limits>  //需包含这个头文件
using namespace std;
int main() {
  cout << "The maximum value for type float is: "
    << numeric_limits<float>::max( )
    << endl;
  cout << "The maximum value for type double is: "
    << numeric_limits<double>::max( )
    << endl;
  cout << "The maximum value for type int is: "
    << numeric_limits<int>::max( )
    << endl;
  cout << "The maximum value for type short int is: "
    << numeric_limits<short int>::max( )
    << endl;
} 

bool strToIntOK; //全局的变量
int StrToInt(const char *str)
{
  strToIntOK = false;
  if(str == NULL) //空指针
    return 0;  

  if(str[0] == '0' && str[1] != '\0') //以'0'开始但不是"0" 这条其实可以忽略
    return 0;  

  unsigned i = 0;
  bool minus = false;  //负数标记
  if(str[i] == '-' || str[i] == '+') //判断是不是以正负号开始
  {
    minus = (str[i] == '-')? true: false;
    i++;
  }  

  long long result = 0; //转换的结果
  while(str[i] != '\0')
  {
    char c = str[i++];
    if(c >= '0' && c <='9')
    {
      result = result * 10 + (c - '0');
      if(minus) //负溢出
      {
        if(result - 1 > numeric_limits<int>::max())
          return 0;
      }
      else //正溢出
      {
        if(result > numeric_limits<int>::max())
          return 0;
      }
    }
    else
    {
      return 0;
    }
  }
  strToIntOK = true;
  //结果返回 需强制转换一下
  return minus? (int)(-result):(int)result;
}
(0)

相关推荐

  • c语言快速排序算法示例代码分享

    步骤为:1.从数列中挑出一个元素,称为 "基准"(pivot);2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边).在这个分区退出之后,该基准就处于数列的中间位置.这个称为分区(partition)操作.3.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序.递归的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了.虽然一直递归下去,但是这个算法总会退出,因为在每次的迭代(iterat

  • 用C语言举例讲解数据结构中的算法复杂度结与顺序表

    数据结构算法复杂度 1.影响算法效率的主要因素 (1)算法采用的策略和方法: (2)问题的输入规模: (3)编译器所产生的代码: (4)计算机执行速度. 2.时间复杂度 // 时间复杂度:2n + 5 long sum1(int n) { long ret = 0; \\1 int* array = (int*)malloc(n * sizeof(int)); \\1 int i = 0; \\1 for(i=0; i<n; i++) \\n { array[i] = i + 1; } for(

  • C语言实现魔方阵算法(幻方阵 奇魔方 单偶魔方实现)

    例如三阶魔方阵为: 魔方阵有什么的规律呢? 魔方阵分为奇幻方和偶幻方.而偶幻方又分为是4的倍数(如4,8,12--)和不是4的倍数(如6,10,14--)两种.下面分别进行介绍. 2 奇魔方的算法 2.1 奇魔方的规律与算法 奇魔方(阶数n = 2 * m + 1,m =1,2,3--)规律如下: 数字1位于方阵中的第一行中间一列:数字a(1 < a  ≤ n2)所在行数比a-1行数少1,若a-1的行数为1,则a的行数为n:数字a(1 < a  ≤ n2)所在列数比a-1列数大1,若a-1的列

  • 算法学习入门之使用C语言实现各大基本的排序算法

    首先来看一下排序算法的一些相关概念: 1.稳定排序和非稳定排序 简单地说就是所有相等的数经过某种排序方法后,仍能保持它们在排序之前的相对次序,我们就说这种排序方法是稳定的.反之,就是非稳定的. 比如:一组数排序前是a1,a2,a3,a4,a5,其中a2=a4,经过某种排序后为a1,a2,a4,a3,a5,则我们说这种排序是稳定的,因为a2排序前在a4的前面,排序后它还是在a4的前面.假如变成a1,a4,a2,a3,a5就不是稳定的了. 2.内排序和外排序 在排序过程中,所有需要排序的数都在内存,

  • 对C语言中递归算法的深入解析

    许多教科书都把计算机阶乘和菲波那契数列用来说明递归,非常不幸我们可爱的著名的老潭老师的<C语言程序设计>一书中就是从阶乘的计算开始的函数递归.导致读过这本经书的同学们,看到阶乘计算第一个想法就是递归.但是在阶乘的计算里,递归并没有提供任何优越之处.在菲波那契数列中,它的效率更是低的非常恐怖. 这里有一个简单的程序,可用于说明递归.程序的目的是把一个整数从二进制形式转换为可打印的字符形式.例如:给出一个值4267,我们需要依次产生字符'4','2','6',和'7'.就如在printf函数中使用

  • C语言实现的排列组合问题的通用算法、解决方法

    尽管排列组合是生活中经常遇到的问题,可在程序设计时,不深入思考或者经验不足都让人无从下手.由于排列组合问题总是先取组合再排列,并且单纯的排列问题相对简单,所以本文仅对组合问题的实现进行详细讨论.以在n个数中选取m(0<m<=n)个数为例,问题可分解为: 1. 首先从n个数中选取编号最大的数,然后在剩下的n-1个数里面选取m-1个数,直到从n-(m-1)个数中选取1个数为止. 2. 从n个数中选取编号次小的一个数,继续执行1步,直到当前可选编号最大的数为m. 很明显,上述方法是一个递归的过程,也

  • C语言中压缩字符串的简单算法小结

    应用中,经常需要将字符串压缩成一个整数,即字符串散列.比如下面这些问题: (1)搜索引擎会通过日志文件把用户每次检索使用的所有检索串都记录下来,每个查询串的长度为1-255字节.请找出最热门的10个检索串. (2)有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M.返回频数最高的100个词. (3)有10个文件,每个文件1G,每个文件的每一行存放的都是用户的query,每个文件的query都可能重复.要求你按照query的频度排序. (4)给定a.b两个文件

  • 一波C语言二元查找树算法题目解答实例汇总

    按层次遍历二元树 问题描述:输入一颗二元树,从上往下按层打印树的每个结点,同一层中按照从左往右的顺序打印.  例如输入: 8 / / 6 10 / / / / 5 7 9 11 输出 8 6 10 5 7 9 11 定义二元树(其实是二元搜索树,但并不遍历算法)的结点为: struct BSTreeNode { int value; BSTreeNode *left; BSTreeNode *right; }; 思路:利用队列的先进先出,很容易实现.每次取出队列的首元素,然后将其左右子女放入队列

  • 常用排序算法的C语言版实现示例整理

    所谓排序,就是要整理文件中的记录,使之按关键字递增(或递减)次序排列起来.其确切定义如下: 输入:n个记录R1,R2,-,Rn,其相应的关键字分别为K1,K2,-,Kn. 输出:Ril,Ri2,-,Rin,使得Ki1≤Ki2≤-≤Kin.(或Ki1≥Ki2≥-≥Kin).     排序的时间开销可用算法执行中的数据比较次数与数据移动次数来衡量.基本的排序算法有如下几种:交换排序(冒泡排序.快速排序).选择排序(直接选择排序.堆排序).插入排序(直接插入排序.希尔排序).归并排序.分配排序(基数排

  • c语言实现冒泡排序、希尔排序等多种算法示例

    实现以下排序 插入排序O(n^2) 冒泡排序 O(n^2) 选择排序 O(n^2) 快速排序 O(n log n) 堆排序 O(n log n) 归并排序 O(n log n) 希尔排序 O(n^1.25) 1.插入排序 O(n^2) 一般来说,插入排序都采用in-place在数组上实现.具体算法描述如下:⒈ 从第一个元素开始,该元素可以认为已经被排序⒉ 取出下一个元素,在已经排序的元素序列中从后向前扫描⒊ 如果该元素(已排序)大于新元素,将该元素移到下一位置⒋ 重复步骤3,直到找到已排序的元素

  • C语言输出旋转后数组中的最小数元素的算法原理与实例

    问题描述:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转.输入一个排好序的数组的一个旋转,输出旋转数组的最小元素.例如数组{3, 4, 5, 1, 2}为{1, 2, 3, 4, 5}的一个旋转,该数组的最小值为1.      思路:这道题最直观的解法并不难.从头到尾遍历数组一次,就能找出最小的元素,时间复杂度显然是O(n).但这个思路没有利用输入数组的特性.既然有时间复杂度更小的算法,我们容易想到二分查找,因为它的时间复杂度为O(logn).这个问题是否可以运用二分查找呢?答

随机推荐