• 编程面试_字符串


    数据结构

    字符串

    1 旋转字符串

      给定一个字符串,要求把字符串前面的若干个字符移动到字符串的尾部,
      如把字符串“abcdef”前面的2个字符'a'和'b'移动到字符串的尾部,使得原字符串变成字符串“cdefab”。
      请写一个函数完成此功能,要求对长度为n的字符串操作的时间复杂度为 O(n),空间复杂度为 O(1)。
    
    • 1
    • 2
    • 3
    解法1.1 暴力移位法         将字符一个一个的移动到尾部 或头部
      // 左边的第一个字符移动到最右边
      void LeftShiftOne(char* s, int n)
      {
        char tep = s[0];    // 保存左边的第一个字符 tep
        for (int i = 1; i < n; ++i)
         {
            s[i - 1] = s[i];// 从前向后 靠右的字符 依次向左移动1位
         }
        s[n - 1] = tep;     // tep 放入最右边的位置
      }
      // 右边的第一个字符移动到 最左边
      void RightShiftOne(char* s, int n)
      {
         char tep = s[n - 1];// 保存右边的最后一个字符 tep
         for(int  i = n-1; i > 0; --i)
         {
            s[i] = s[i - 1]; // 从后向前 靠左的字符 依次向右 移动1位
         }
         s[0] = tep;         // tep 放入最左边的位置
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    因此,若要把字符串开头的m个字符移动到字符串的尾部,则可以如下操作:

    void LeftRotateSring(char *s, int n, int m)
    {
        while(m--)
        {
             LeftShiftOne(s, n);// 每次移动一个字符 一共 移动 m次
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    而,若要把字符串尾部的m个字符移动到字符串的头部,则可以如下操作:

    void ReftRotateSring(char *s, int n, int m)
    {
        while(m--)
        {
             ReftShiftOne(s, n);// 每次移动一个字符 一共 移动 m次
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    }

    针对长度为n的字符串来说,假设需要移动m个字符到字符串的尾部,对每个字符需要进行移动n次,那么总共需要 mn 次操作,同时设立一个变量保存第一个字符,如此,时间复杂度为O(m n),空间复杂度为O(1),空间复杂度符合题目要求,但时间复杂度不符合,所以,我们得需要寻找其他更好的办法来降低时间复杂度。

    解法1.2:三步反转法
      对于这个问题,换一个角度思考一下。
      将一个字符串XY分成X(m个字符)和Y(n-m个字符)两个部分,在每部分字符串上定义反转操作,
      如X^T,即把X的所有字符反转(如,X="abc",那么X^T="cba"),那么就得到下面的结论:
      (X^TY^T)^T=YX,显然就解决了字符串的反转问题。
      例如,字符串 abcdef ,若要让def翻转到abc的前头,只要按照下述3个步骤操作即可:
      1.首先将原字符串分为两个部分,即X:abc,Y:def;
      2.将X反转,X->X^T,即得:abc->cba;将Y反转,Y->Y^T,即得:def->fed。
      3.反转上述步骤得到的结果字符串X^TY^T,即反转字符串cbafed的两部分(cba和fed)给予反转,cbafed得到defabc,形式化表示为(X^TY^T)^T=YX,这就实现了整个反转。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    三个步骤中后两个步骤都用到了 反转一个字符串

      void ReverseString(char *s, int form, int to)
      {
            while(from < to)// 从首尾开始交换首位位置的值,直到相遇
            {
                  char tep  = s[from];// 保存左边
                  s[from++] = s[to];  //右边 交换给左边 同时左边向右移动一个位置
                  s[to--]   = tep;    // 前右边保存的值 交换给左边 同时 右边向左移动一个位置
            }
      }
    
      // 前 m 后 放在尾部
      // 反转[0..m - 1] 反转[m..n - 1] 反转[0..n - 1]
      void LeftRotateString(char* s, int n, int m)
      {
      m  %= n;// 避免左移数量大于n的情况
      ReverseString(s, 0, m - 1);// 反转[0..m - 1],套用到上面举的例子中,就是X->X^T,即 abc->cba
      ReverseString(s, m, n - 1);// 反转[m..n - 1],例如Y->Y^T,即 def->fed
      ReverseString(s, 0, n - 1);// 反转[0..n - 1],即如整个反转,(X^TY^T)^T=YX,即 cbafed->defabc。
      }
      // 后m个 放在 头部 
      // 反转[0..n - m - 1] 反转[n - m..n - 1] 反转[0..n - 1]
      void LeftRotateString(char* s, int n, int m)
      {
      m  %= n;// 避免左移数量大于n的情况
      ReverseString(s, 0, n - m - 1);// 反转[0..m - 1],套用到上面举的例子中,就是X->X^T,即 abc->cba
      ReverseString(s, n - m, n - 1);// 反转[n - m..n - 1],例如Y->Y^T,即 def->fed
      ReverseString(s, 0, n - 1);// 反转[0..n - 1],即如整个反转,(X^TY^T)^T=YX,即 cbafed->defabc。
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    1.3 序列反转 举一反三
      1、链表翻转。给出一个链表和一个数k,比如,链表为1→2→3→4→5→6,
      k=2,则翻转后2→1→6→5→4→3,
      若k=3,翻转后3→2→1→6→5→4,
      若k=4,翻转后4→3→2→1→6→5,用程序实现。
      注解:原序列 XY 则反转后的序列为 X^T Y^T
    
      2、单词翻转。输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变,句子中单词以空格符隔开。
      为简单起见,标点符号和普通字母一样处理。例如,输入“I am a student.”,则输出“student. a am I”。
      注解:先将整个字符串反转,在将其中每个单词反转回来
    
      void Reverse_word(char *str)      
      {      
          if(str == NULL)// 空指针 返回 
              return;      
          int len = strlen(str);// 字符串长度
          // 反转整个字符串
          ReverseString(str, 0, len - 1);    
          int s = 0;//每个单词的 起始 index
          int e = 0;// 每个单词的 后 index
          for(int i = 0; i < len; i++) // 遍历字符串 查找每一个单词
          {       
              e = i;      
              if(str[e] == ' ')// 此处 遇到空格 
              {      
                  ReverseString(str, s, e-1); // 反转这个单词 
                  s = e+1;// 更新 单词的起始指针 
              }      
          }    
          ReverseString(str, s, len - 1);  // 反转最后一个单词 因为最后一个单词后面无空格
      }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    2 字符串包含

      给定两个分别由字母组成的字符串A和字符串B,字符串B的长度比字符串A短。请问,如何最快地判断字符串B中所有字母是否都在字符串A里?
      为了简单起见,我们规定输入的字符串只包含大写英文字母,请实现函数bool StringContains(string &A, string &B)
      比如,如果是下面两个字符串:
      String 1:ABCD
      String 2:BAD
    
    • 1
    • 2
    • 3
    • 4
    • 5

    答案是true,即String2里的字母在String1里也都有,或者说String2是String1的真子集,当 String 2:BAE 时答案为false

    解法2.1 暴力匹配法
      判断string2中的字符是否在string1中?最直观也是最简单的思路是,
    
    • 1

    针对string2中每一个字符,逐个与string1中每个字符比较,看它是否在String1中。

    代码可如下编写:

      bool StringContain(string &a, string &b)
      {
            for(int i = 0; i < b.size(); ++i)
            {
                  int j;
                  //b中的每一个字符,从a的开头一直向后找,直到出现相同字符/已经到达末尾
                  for(j = 0; (j <  a.size()) && (a[j] != b[i]); ++j) ;
                  if(j >= a.size()) return false;//如果到达末尾 b中的这个字符 都还没有 在a中找到一样的字符 ,则返回错误
            }
            return true;//b中每个字符都在 a中找到了 返回 true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    }

    假设n是字符串String1的长度,m是字符串String2的长度,那么此算法,需要O(n*m)次操作。显然,时间开销太大,应该找到一种更好的办法。

    解法2.2 排序后匹配法 如果允许排序的话,我们可以考虑下排序
      比如可先对这两个字符串的字母进行排序,然后再同时对两个字串依次轮询。
      两个字串的排序需要(常规情况)O(m log m) + O(n log n)次操作,之后的线性扫描需要O(m+n)次操作。
    
    • 1
    • 2

    代码可如下编写:

      //注意A B中可能包含重复字符,所以注意A下标不要轻易移动。这种方法改变了字符串。如不想改变请自己复制
      bool StringContain(string &a,string &b)
      {
          sort(a.begin(),a.end());// 采用最常用的快速排序
          sort(b.begin(),b.end());
          for (int pa = 0, pb = 0; pb < b.length();)// b中的每一个字符
          {
              while ((pa < a.length()) && (a[pa] < b[pb]))// 在a中找 
              {
                  ++pa;
              }
              if ((pa >= a.length()) || (a[pa] > b[pb]))//找到最后 或 a中的字符已经大于b对应的字符了 就已经无相等的了
              {
                  return false;
              }
              //到这里就是 有相同的字符 a[pa] == b[pb]
              ++pb;//遍历 b中 下一个字符
          }
          return true;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    解法2.3 素数乘积取余法
      按照从小到大的顺序,用26个素数分别与字符'A'到'Z'一一对应。
    
    • 1

    遍历长字符串,求得每个字符对应素数的乘积f。
         遍历短字符串,判断乘积f 能否被短字符串中的字符对应的素数整除,如果整除则包含该字符,如果不能整除则这个字符不包含。
    输出结果。

    代码可如下编写:

      //此方法只有理论意义,因为整数乘积很大,有溢出风险
      // 此种素数相乘的方法看似完美,但缺点是素数相乘的结果容易导致整数溢出。
      // 算法的时间复杂度为O(m+n)的最好的情况为O(n)
      //(遍历短的字符串的第一个数,与长字符串素数的乘积相除,即出现余数,便退出,返回false)n为长字串的长度,空间复杂度为O(1)
      bool StringContain(string &a,string &b)
      {
          const int p[26] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,61, 67, 71, 73, 79, 83, 89, 97, 101};
          int f = 1;
          for (int i = 0; i < a.length(); ++i)
          {
              int x = p[a[i] - 'A'];// 字符对应的素数
              if (f % x)// 对于长字符串中出现相同的字符 可不用再次 相乘
              {
                  f *= x;// a中所有不相同字符 对应的素数 的乘积
             }
          }
          for (int i = 0; i < b.length(); ++i)
          {
              int x = p[b[i] - 'A'];// 字符对应的素数
              if (f % x)// 能够被整除 则说明出现相同的字符
              {
                  return false;// 否者出现 了一个不相同的字符 则退出
              }
          }
          return true;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    解法2.4 Hash 签名查找
      事实上,可以先把长字符串a中的所有字符都放入一个Hashtable里,
      然后轮询短字符串b,看短字符串b的每个字符是否都在Hashtable里,
      如果都存在,说明长字符串a包含短字符串b,否则,说明不包含。
      再进一步,我们可以对字符串A,用位运算(26bit整数表示)计算出一个“签名”,再用B中的字符到A里面进行查找。
    
    • 1
    • 2
    • 3
    • 4

    代码可如下编写:

      // “最好的方法”,时间复杂度O(n + m),空间复杂度O(1)
      // 这个方法的实质是用一个整数代替了hashtable,空间复杂度为O(1),时间复杂度还是O(n + m)。
      bool StringContain(string &a,string &b)
      {
          int hash = 0;
          for (int i = 0; i < a.length(); ++i)
          {
              hash |= (1 << (a[i] - 'A'));// (a[i] - 'A')为0~25 右移动一位 位或上签名hash 得到 长串a的最后签名
              // 相当于把 a的每一个不同的 字符 放入 每一个而进行位上
          }
          for (int i = 0; i < b.length(); ++i)
          {
              if ((hash & (1 << (b[i] - 'A'))) == 0)// 位与 上(1 << (b[i] - 'A')) 的0的话就说明对应的位上(字符)与a中午匹配
              {
                  return false;// 对应位上 a中未出现相应的字符  则查找失败
              }
          }
          return true;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    2.5 字符串包含 举一反三 变位词
      如果两个字符串的字符一样,但是顺序不一样,被认为是兄弟字符串。
      比如bad和adb即为兄弟字符串,现提供一个字符串,如何在字典中迅速找到它的兄弟字符串,请描述数据结构和查询过程。
    
    • 1
    • 2

    解答: a包含b b也包含a
         数据结构为:
    设置一个64bit的变量long long hash1,将字符串A中的字符通过移位在hash1中表示;设置另外一个64bit的变量long long hash2, 将字符串B中的字符通过移位在hash2中表示。
    查询过程:
    比较hash1与hash2的大小,若相等,则为兄弟字符串;若不等,则不为兄弟字符串。

    代码可如下编写:

      bool anagramJudge(string &a, string &b){
          long long hash1=0;
          long long hash2=0;
          for(int i=0; i
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    3字符串转换成整数

      输入一个由数字组成的字符串,把它转换成整数并输出。
      例如:输入字符串"123",输出整数123。
      给定函数原型int StrToInt(const char *str) ,
      实现字符串转换成整数的功能,不能使用库函数atoi。
    
    • 1
    • 2
    • 3
    • 4
    3.1不考虑溢出 正负号
      以"123"作为例子:
         当我们扫描到字符串的第一个字符'1'时,由于我们知道这是第一位,所以得到数字1。
         当扫描到第二个数字'2'时,而之前我们知道前面有一个1,
      所以便在后面加上一个数字2,那前面的1相当于10,因此得到数字:1*10+2=12。
         继续扫描到字符'3','3'的前面已经有了12,由于前面的12相当于120,
      加上后面扫描到的3,最终得到的数是:12*10+3=123。
      因此,此题的基本思路便是:
      从左至右扫描字符串,把之前得到的数字乘以10,再加上当前字符表示的数字。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    代码可如下编写:

      int StrToInt(const char *str)
      {
          if(str == NULL)   // 空指针返回
             return -1;
          int n = 0;
          int isNeg = 0;    // 负数标志
          if(str[0] == '-') isNeg = 1;
          while (*str != 0) // 遍历到字符串末尾
          {
              if(*str > '9' || *str < '0') {continue; ++str;}// 跳过非数字字符 或者 直接 退出 return -1;
              int c = *str - '0';
              n = n * 10 + c;// 累加和
              ++str;         // 移动指针
          }
          if(isNeg) return -n;
          else      return n;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    3.2 完整的考虑溢出等情况
      int StrToInt(const char* str)
      {
          static const int MAX_INT = (int)((unsigned)~0 >> 1);
          static const int MIN_INT = -(int)((unsigned)~0 >> 1) - 1;
          unsigned int n = 0;
    
          //判断是否输入为空
          if (str == 0)
          {
              return 0;
          }
    
          //处理空格 跳过第一个空格
          while (isspace(*str))
              ++str;
    
          //处理正负
          int sign = 1;
          if (*str == '+' || *str == '-')
          {
              if (*str == '-')
                  sign = -1;
              ++str;// 如果有正负号 确定标志后 从符号后一位开始处理
          }
    
          //确定是数字后才执行循环
          while ( *str > '0' && *str < '9' )
          {
              //处理溢出
              int c = *str - '0';// 字符转换成 数字
              if (sign > 0 && (n > MAX_INT / 10 || (n == MAX_INT / 10 && c > MAX_INT % 10)))
              {
                  n = MAX_INT;
                  break;
              }
              else if (sign < 0 && (n >(unsigned)MIN_INT / 10 || (n == (unsigned)MIN_INT / 10 && c > (unsigned)MIN_INT % 10)))
              {
                  n = MIN_INT;
                  break;
              }
    
              //把之前得到的数字乘以10,再加上当前字符表示的数字。
              n = n * 10 + c;
              ++str;
          }
          return sign > 0 ? n : -n;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    3.3 举一反三
      实现string到double的转换
    
    • 1

    代码可如下编写:

      double StrToDou(const char *str)
      {
          if(str == NULL)   // 空指针返回
             return -1;
          double result = 0.0;// 最后的值
          double dec = 10.0;// 小数位比值
          int isNeg = 0;   // 负数标志
          int isDec = 0;
          if(str[0] == '-') isNeg = 1;
          while (*str != 0) // 遍历到字符串末尾
          {
              if(*str == '.') {isDec = 1; ++str;}//是小数
              else if(*str > '9' || *str < '0') {continue; ++str;}// 跳过非数字字符 或者 直接 退出 return -1;
              if(!isDec) // 整数部分
                  result = result * 10 + *str - '0';// 整数部分 累加和
              else //识别小数点之后进入 这个分支
                  { 
                   result = result + (*str - '0')/dec;// 小数部分 算法
                   dec *= 10;// 小数位比值递增
                  }
              ++str;         // 移动指针
          }
          if(isNeg) return -result;
          else      return result;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    4 字符串 回文 判断

    4.1 首尾并进判断法
      同时从字符串头尾开始向中间扫描字串,如果所有相对的字符都一样,那么这个字串就是一个回文。
      采用这种方法的话,我们只需要维护头部和尾部两个扫描指针即可。 Palindrome  英 ['pælɪndrəʊm]  pai lin drow mu
    
    • 1
    • 2

    代码可如下编写:

      // 输入字符指针 s 和 字符串大小 num
      bool IsPalindrome(const char* s, int num)
      {
            if(s == NULL || num < 2)  return false;//指针为空 / 字符串长度小于2
            // 定义并初始化 首尾指针
            const char* low, *high;// 首尾指针
            low = s;// 首指针
            high = s + num - 1;// 尾指针
            while(low < high){
                  if(*low ! *high) return false;// 出现一次相对应位置的字符不相同 就返回错误
                  ++low;
                  --high;
            }
            return true; // 到这里的话 就证明 相对应位置上的 字符相同 为 回文
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这是一个直白且效率不错的实现,时间复杂度:O(n),空间复杂度:O(1)。
    上述解法一从两头向中间扫描,那么是否还有其它办法呢?我们可以先从中间开始、然后向两边扩展查看字符是否相等。

    判断一条单向链表是不是“回文
      对于单链表结构,可以用两个指针从两端或者中间遍历并判断对应字符是否相等。
      但这里的关键就是如何朝两个方向遍历。由于单链表是单向的,所以要向两个方向遍历的话,可以采取经典的快慢指针的方法,
      即先位到链表的中间位置,再将链表的后半逆置,最后用两个指针同时从链表头部和中间开始同时遍历并比较即可。
    
    • 1
    • 2
    • 3
    判断一个栈是不是“回文”
      对于栈的话,
      只需要将字符串全部压入栈,然后依次将各字符出栈,
      这样得到的就是原字符串的逆置串,
    
    • 1
    • 2
    • 3

    将逆置串和原字符串各个字符比较,就可以判断了。

    5 最长回文子串

    如果一段字符串是回文,那么以某个字符为中心的前缀和后缀都是相同的,
    例如以一段回文串“aba”为例,以b为中心,它的前缀和后缀都是相同的,都是a。
         那么,我们是否可以可以枚举中心位置,然后再在该位置上用扩展法,记录并更新得到的最长的回文长度呢?

    5.1 遍历子串中心位置,找最大回文子串(分为子串长度为 奇数和偶数的情况)

    参考代码如下:

      int LongestPalindrom(const char* s, int num){
      if(s == NULL || num < 2)  return false;//指针为空 / 字符串长度小于2
      int i,j,max,cnt;// ij 为循环变量 cnt 记录每一个回文子串的长度
      max = 0;//最大字回文字符串长度 初始化为0
    
      // 遍历每一个子串 的 中心点
      for(i = 0; i < n; ++i){ 
         // 对于子串长度为奇数的情况
           for(j = 0; (i - j >= 0)&&(i + j < n); ++j){// j为以i字符点为中心 向两边扩展的间隔 区别首尾区间 [0, n-1]
                  if(s[i - j] != s[i + j]) break; // 间隔相同的位置 字符不同 结束回文
                  cnt = j*2 + 1;// 已此中心点i 的回文子串长度为 间隔j *2 + 1(中间一个字符)
            }
            if(cnt > max) max = cnt;// 保存最长的 回文子串的 长度
    
        // 对于子串 长度为 偶数的情况
            for(j = 0; (i - j >= 0)&&(i + j + 1 < n); ++j){
                  if(s[i - j] != s[i + j +1]) break; // 间隔相同的位置 字符不同 结束回文
                  cnt = j*2 + 2;
            }
            if(cnt > max) max = cnt;// 保存最长的 回文子串的 长度   
       }
      return max;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    6 字符串的全排列

      输入一个字符串,打印出该字符串中字符的所有排列。
      例如输入字符串abc,则输出由字符a、b、c 所能排列出来的所有字符串
      abc、acb、bac、bca、cab 和 cba。
    
    • 1
    • 2
    • 3
    6.1 递归实现
      从集合中依次选出每一个元素,作为排列的第一个元素,然后对剩余的元素进行全排列,
      如此递归处理,从而得到所有元素的全排列。     Permutation  英 [pɜːmjʊ'teɪʃ(ə)n]  Peer mu tai xing
      以对字符串abc进行全排列为例,我们可以这么做:以abc为例 
    
    • 1
    • 2
    • 3

    abc a和a交换后 abc 对a后的 bc进行全排列
         abc a和b交换后 bac 对b后的 ac进行全排列
         abc a和c交换后 cba 对c后的 ba进行全排列

    参考代码如下:

      void CalcAllPermutation(char* s, int from, int to)
      {
      if (s == NULL || to < 2)  return;
    
      // 打印排列后的字符串
      if (from == to)/
      {
        for (int i = 0; i <= to; i++)
            cout << perm[i];
        cout << endl;
      }
      else
      {
        for (int j = from; j <= to; j++)// 从当前位置from开始 依次向后和j位置交换元素后 对from后的元素进行全排列
        {
            swap(perm[j], perm[from]);// 从当前位置from开始 依次向后和j位置交换元素
            CalcAllPermutation(perm, from + 1, to);// 对from后的元素 [from + 1, to]进行全排列
            swap(perm[j], perm[from]);// 再还原字符串
        }
      }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    习题集

    1 第一个只出现一次的字符

      在一个字符串中找到第一个只出现一次的字符。如输入abaccdeff,则输出b。
    
    • 1

    遍历法

      //O(n^2)的时间复杂度
      char FirstNotRepeatingChar(char *pS, int num)
      {
          //如果是空指针,返回\0
          if(pS == NULL) return '\0';
          int i,j,flag=0;
          bool* bap;
          bap = (bool*)maoolc(sizeof(bool)*num);
          for(i =0; i < n; ++i) *bap[i] = false;
          for(i=0; i
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    哈希表法 强烈推荐

      //O(n)的时间复杂度
      char FirstNotRepeatingChar(char *pS)
      {
          //如果是空指针,返回\0
          if(pS == NULL)  return '\0';
          //字符(char)是一个长度为8bit的数据类型,因此总共最多能够表示256种字符。 
          //定义hash表长度256,并创建哈希表
          const int len=256;
          int hashtable[len];
          for(int i=0;i
  • 相关阅读:
    小啊呜产品读书笔记001:《邱岳的产品手记-09》第17讲 产品经理如何获得非权力性的影响力 & 第18讲 产品案例分析:WWFTogether的情怀设计
    怎么查找和自己专业有关的英文文献?
    【Go系列】 array、slice 和 map
    Winform窗体开发:限制只能启动一个exe程序,禁止多开客户端。
    【Redis】基于Spring Cache + Redis + Jackson的注解式自动缓存方案保姆式教程(2022最新)
    marven项目打包第三方jar包
    如何实现Laravel 5.1 分页功能及自定义分页样式
    2024.08.07校招 实习 内推 面经
    spring boot课程评价系统 毕业设计源码211004
    【Apache Spark 】第 11 章使用 Apache Spark 管理、部署和扩展机器学习管道
  • 原文地址:https://blog.csdn.net/RuanJian_GC/article/details/133364093