《编程之法》习题集答案整理-第一章

来源:互联网 发布:网络电视怎么设置频道 编辑:程序博客网 时间:2024/05/21 22:25

最近读了某博主整理的一些笔试面试算法题,该书的部分习题是没有直接答案的,为了自己学习方便,同时方便后面学习的同学,立此文整理,感谢原博文http://blog.csdn.net/v_july_v/article/details/6543438

1.第一个只出现一次的字符
问题描述:在一个字符串中找到第一个只出现一次的字符。如输abaccdeff,则输出b。
解析:
方法(1):看到这个题目,最直观的想法就是就是遍历法,也就是从头开始取字符串中的一个字符,将其与其后的所有字符比较,如果有相同的字符,那么就证明它不是只出现一次的字符。当第一次出现遍历完其后字符并且没有重复时,表明这个字符就是“第一个只出现一次的字符”。如果字符串有n个字符,每个字符可能与后面的O(n)个字符相比较,因此这种思路的时间复杂度是O(n2)。
方法(2):题目中要求第一个只出现一次的字符,那么就跟字符出现的次数有关。我们考虑如何统计字符出现的次数,然后找出第一个次数为1的那个字符。这里我们需要一个数据容器来保存字符出现次数,并且能够通过字符找出其相对应的次数。哈希表就是一种常用用的容器。我们可以定义哈希表的键值(Key)是字符的ASCII值,而值(Value)是该字符出现的次数。同时我们需要扫描两次字符串,第一次扫描字符串时,每扫描到一个字符就在哈希表的对应项中把次数加1。接下来第二次扫描的时候,没扫描到一个字符就能在哈希表中得到该字符出现的次数。找出第一个Value为1的那个key就是我们需要找到那个字符。
该题代码实现 很简单,就不写了

2、对称子字符串的最大长度
问题描述:输入一个字符串,输出该字符串中对称的子字符串的最大长度。比如输入字符串“google”,由于该字符 串里最长的对称子字符串是“goog”,因此输出4。
问题分析:
方法一:判断字符串的每一个子串,若是对称的,则求出它的长度即可。这种办法对每一个子串,从两头向中间判断是不是子串。总的时间复杂度为O(n^3),
方法二:与方法一正好相反,字符串中的每一个开始,向两边扩展,此时可分为两种情况:
(1)对称子串长度是奇数时, 以当前字符为对称轴向两边扩展比较
(2)对称子串长度是偶数时,以当前字符和它右边的字符为对称轴向两边扩展
总的时间复杂度为O(n^2)

int maxSymSubstring(char str[])  {      char *fromStart2End, *left, *right;      int length = 1, newlength;      for (fromStart2End = str; *fromStart2End; fromStart2End++)      {          newlength = 1;            //对称子串可能为奇数时          left = fromStart2End - 1;          right = fromStart2End + 1;          for (; left >= str && right <= str + strlen(str) - 1; --left, ++right)              if (*left == *right)    newlength += 2;              else    break;          if (newlength > length)              length = newlength;          newlength = 0;           //对称子串可能为偶数时          left = fromStart2End;          right = fromStart2End + 1;          for (; left >= str && right <= str + strlen(str) - 1; --left, ++right)              if (*left == *right)    newlength += 2;              else    break;          if (newlength > length)              length = newlength;      }// for      return length;  }   

黑凤梨,I am PKU_IS917,持续更新中、、、、、

3.编程判断俩个链表是否相交

问题描述:给出俩个单向链表的头指针,比如h1,h2,判断这俩个链表是否相交。为了简化问题,我们假设俩个链表 均不带环。
解析:
方法(1):先遍历第一个链表到他的尾部,然后将尾部的next指针指向第二个链表(尾部指针的next本来指向的是null)。这样两个链表就合成了一个链表,判断原来的两个链表是否相交也就转变成了判断新的链表是否有环的问题了:即判断单链表是否有环?
方法(2):仔细研究两个链表,如果他们相交的话,那么他们最后的一个节点一定是相同的,否则是不相交的。因此判断两个链表是否相交就很简单了,分别遍历到两个链表的尾部,然后判断他们是否相同,如果相同,则相交;否则不相交。
方法(3):假设第一个链表长度为len1,第二个问len2,然后找出长度较长的,让长度较长的链表指针向后移动|len1 - len2| (len1-len2的绝对值),然后在开始遍历两个链表,判断节点是否相同即可。

typedef struct node_t{  int data;//data  struct node_t *next; //next}node;node* find_node(node *head1, node *head2){  if(NULL == head1 || NULL == head2)  {    return NULL;//如果有为空的链表,肯定是不相交的  }  node *p1, *p2;  p1 = head1;  p2 = head2;  int len1 = 0;  int len2 =0;  int diff = 0;  while(NULL != p1->next)  {  p1 = p1->next;  len1++;  }  while(NULL != p2->next)  {    p2 = p2->next;    len2++;  }  if(p1 != p2) //如果最后一个节点不相同,返回NULL  {    return NULL;  }  diff = abs(len1 - len2);  if(len1 > len2){     p1 = head1;     p2 = head2;  }else{     p1 = head2;     p2 = head1;  }for(int i=0; i<diff; i++){  p1 = p1->next;}while(p1 != p2){  p1 = p1->next;  p2 = p2->next;}  return p1;}

4、逆序输出链表
问题:输入一个链表的头结点,从尾到头反过来输出每个结点的值。
解析:
method1:使用栈
method2:先逆序(头插法),再顺序打印
method3:递归(超级赞)
这里我们只给出递归的代码,因为它最赞嘛,其他方法本部分就给不出了

void recursion(node* head){    if(NULL==head)    {       return;    }    if(head->next!=NULL)    {      recursion(head->next);    }      //如果把这句放在第二个if前面,那就是从头到尾输出链表,曾经的你或许是用while或者用for循环输出链表,现在你又多了一种方式    cout<<head->data<<"\t";}

5、在O(1)时间内删除单链表结点
问题:给定单链表的一个结点的指针,同时该结点不是尾结点,此外没有指向其它任何结点的指针,请在O(1)时 间内删除该结点。
解析:直接用p->next的值赋值给p,把p->next删除掉(好处:不用遍历找到p的前一个指针pre,O(1)时间内搞定),但是此时有两个特例需要注意:(1)删除的是尾指针,需要遍历找到前一个指针;(2)整个链表就一个结点

bool deleteNode(ListNode *&head, ListNode *p){    if(!p || !head)        return false;    if(p->m_pNext != NULL)   //不是尾指针    {        ListNode *del = p->m_pNext;        p->m_nValue = del->m_nValue;        p->m_pNext = del->m_pNext;        delete del;        del = NULL;    }    else if(head == p)       //是尾指针,同时只有一个结点    {        delete p;        head = NULL;    }    else                     //是尾指针,同时有多个结点    {        ListNode *tmp = NULL, *pre = head;        while(pre->m_pNext != p)        {            pre = pre->m_pNext;        }        delete p;        p = NULL;        pre->m_pNext = NULL;    }    return true;}

6、找出链表的第一个公共结点
题目:两个单向链表,找出它们的第一个公共结点。
解析:太简单了,不说了。

7、在字符串中删除特定的字符
题目:输入两个字符串,从第一字符串中删除第二个字符串中所有的字符。例如,输入”They are students.”和”aeiou”,则删除之后的第一个字符串变成”Thy r stdnts.”
解析:由 于字符的总数是有限的。对于八位的char型字符而言,总共只有256个字符。我们可以新建一个大小为256的数组,把所有元素都初始化为0。然后 对于要删除的字典字符串中每一个字符,把它的ASCII码映射成索引,把数组中该索引对应的元素设为1。这个时候,要查找一个字符就变得很快了:根据这个字符的 ASCII码,在数组中对应的下标找到该元素,如果为0,表示字符串中没有该字符,否则字符串中包含该字符。此时,查找一个字符的时间复杂度是O(1)。 其实,这个数组就是一个hash表。
定义两个指针fast和slow指向目标字符串,如果当前fast指向的字符在hash表中存在,则表明需要删除,*slow=*fast;++slow;

void deleteChars2(char* pStrSource, const char* pStrDelete)  {        if(NULL == pStrSource || NULL == pStrDelete)              return;        const unsigned int nTableSize = 256;        int hashTable[nTableSize];        memset(hashTable, 0, sizeof(hashTable));        const char* pTemp = pStrDelete;        while ('\0' != *pTemp)        {              hashTable[*pTemp] = 1;              ++ pTemp;        }        char* pSlow = pStrSource;        char* pFast = pStrSource;        while ('\0' != *pFast)        {              if(1 != hashTable[*pFast])              {                    *pSlow = *pFast;                    ++ pSlow;              }              ++pFast;        }        *pSlow = '\0';  }

黑凤梨,I am PKU_IS917,持续更新中、、、、、

8、字符串的匹配
题目:在一篇英文文章中查找指定的人名,人名使用二十六个英文字母(可以是大写或小写)、空格以及两个通 配符组成(、?),通配符“”表示零个或多个任意字母,通配符“?”表示一个任意字母。如:“J* Smi??” 可以匹配“John Smith
解析:KMP算法
9、字符个数的统计
题目:char *str = “AbcABca”; 写出一个函数,查找出每个字符的个数,区分大小写,要求时间复杂度是n(提示 用ASCII码)
解析:老方法,定义256数组,一次遍历就可以解决统计

10、最小子串
题目:给一篇文章,里面是由一个个单词组成,单词中间空格隔开,再给一个字符串指针数组,比如 char *str[]= {“hello”,”world”,”good”};
求文章中包含这个字符串指针数组的最小子串。注意,只要包含即可,没有顺序要求。 提示:文章也可以理解为一个大的字符串数组,单词之前只有空格,没有标点符号。
解析:该问题容易让人联想到在长字符串中查找短字符串的问题,例如这种形式。
但是现在这个问题,要复杂一些,需要使用并查集。

11、字符串的集合
题目:给定一个字符串的集合,格式如:{aaa bbb ccc}, {bbb ddd},{eee fff},{ggg},{ddd hhh}要求将其中 交集不为空的集合合并,要求合并完成后的集合之间无交集,例如上例应输出{aaa bbb ccc ddd hhh}, {eee fff}, {ggg}。
解析:

12、五笔编码
题目:五笔的编码范围是a ~ y的25个字母,从1位到4位的编码,如果我们把五笔的编码按字典序排序,形成一 个数组如下: a, aa, aaa, aaaa, aaab, aaac, … …, b, ba, baa, baaa, baab, baac … …, yyyw, yyyx, yyyy 其中a的Index为0,aa的Index为1,aaa的Index为2,以此类推。
(1)编写一个函数,输入是任意一个编码,比如baca,输出这个编码对应的Index;
(2)编写一个函数,输入是任意一个Index,比如12345,输出这个Index对应的编码。

解析:1)计算出相邻2个同位数编码之间的距离,保存于base[4]中:

base[4] = 1, 即aaaa与aaab之间相隔;base[3] = base[4]*25+1,即aaa与aab之间相隔;base[2] = 25*base[3] + 1,即aa与ab之间相隔;base[1] = 25*base[2]+1,即a与b之间相隔

2)编码:给定一个字符编码,从高位向低位扫描。对第i位找出相同位数的,且前面i-1位相同,且第i位是a的编码之间的距离

例如baca:

第一步:找出b的位置,即与a之间的距离d1=(‘b’-‘a’)*base[1] + 1

第二步:找出ba的位置,即与ba之间的距离d2 = (‘a’-‘a’)*base[2] + 1

第三步:找出bac的位置,即与baa之间的距离d3 = (‘c’-‘a’)*base[3] + 1

第四步:找出baca的位置,即与baca之间的距离d4 = (‘a’-‘a’)*base[4] + 1

即baca的Index = d1+d2+d3+d4-1

3)解码:是编码的逆过程,给定一个索引值,依次去除base[i],得到与相同位数的,且前面i-1位相同,且第i位是a的编码之间的距离

//参数说明:source给定的字符编码,len为长度      int encode(char *source, int len)      {          //计算base          int base[4];          base[3] = 1;          for(int i=2;i>=0;i--)          {              base[i] = base[i+1]*25 + 1;          }          //计算index          int code = 0;          for(int j=0;j<len;j++)          {              code += ((source[j]-'a')*base[j] + 1);          }          return code-1;      } 
//参数说明:index为索引值      string decode(int index)      {          //计算base          int base[4];          base[3] = 1;          for(int i=2;i>=0;i--)          {              base[i] = base[i+1]*25 + 1;          }          //计算字符          string target;          int j = 0;          while(index>=0)          {              target[j] = (char)('a' + index/base[j]);              index = index%base[j] - 1;              j++;          }          return target;      } 

黑凤梨,I am PKU_IS917,持续更新中、、、、、

13、最长重复子串
题目:一个长度为10000的字符串,写一个算法,找出最长的重复子串,如abczzacbca,结果是bc。 提示:此题是后缀树/数组的典型应用,即是求后缀数组的height[]的最大值。
解析:步骤:1、对待处理的字符串产生后缀数组;2、对后缀数组排序;3、依次检测相邻两个后缀的公共长度;4、取出最大公共长度的前缀。
使用上面的输入例子,可产生后缀数组:
a[0]=abczzacbca
a[1]=bczzacbca
a[2]=czzacbca
a[3]=zzacbca
a[4]=zacbca
a[5]=acbca
a[6]=cbca
a[7]=bca
a[8]=ca
a[9]=a
对后缀数组进行排序,以将后缀相近的(变位词)子串集中在一起:
a[0]=a
a[1]=abczzacbca
a[2]=acbca
a[3]=bca
a[4]=bczzacbca
a[5]=ca
a[6]=cbca
a[7]=czzacbca
a[8]=zacbca
a[9]=zzacbca
依次检测相邻的后缀,可得到最长的重复字符串。

int comlen( char *p, char *q )  {      int i = 0;      while( *p && (*p++ == *q++) )          ++i;      return i;  }for(i = 0 ; i < n-1 ; ++i )  {          temp=comlen( a[i], a[i+1] );          if( temp>maxlen )      {                maxlen=temp;                maxi=i;          }  }

14、字符串的压缩
题目:一个字符串,压缩其中的连续空格为1个后,对其中的每个字串逆序打印出来。比如”abc efg hij”打印 为”cba gfe jih”。
解析:没什么好说的

15、最大重复出现子串
题目:输入一个字符串,如何求最大重复出现的字符串呢?比如输入ttabcftrgabcd,输出结果为abc, canffcancd, 输出结果为can。
给定一个字符串,求出其最长的重复子串。
解析:和T13重复,就不在赘述了。

16、字符串的删除
题目:删除模式串中出现的字符,如“welcome to asted”,模式串为“aeiou”那么得到的字符串为“wlcm t std”,要求性能最优。
解析:将模式串生成字典,遍历目标字符串,时间复杂度O(n);

17、字符串的移动
题目:字符串为?号和26个字母的任意组合,把 ?号都移动到最左侧,把字母移到最右侧并保持相对顺序不变,要求 时间和空间复杂度最小。

18、字符串的包含
输入:
L:“hello”“july”
S:“hellomehellojuly”
输出:S中包含的L一个单词,要求这个单词只出现一次,如果有多个出现一次的,输出第一个这样的单 词。
没搞明白题意什么意思~跳过

19、倒数第n个元素
链表倒数第n个元素。
解析:设置一前一后两个指针,一个指针步长为1,另一个指针步长为n,当一个指针走到链表尾端时,另一指针指向的元素即为链表倒数第n个元素。

20、回文字符串
将一个很长的字符串,分割成一段一段的子字符串,子字符串都是回文字符串。有回文字符串就输出最长 的,没有回文就输出一个一个的字符。
例如:
habbafgh
输出h,abba,f,g,h。

21、最长连续字符
用递归算法写一个函数,求字符串最长连续字符的长度,比如aaaabbcc的长度为4,aabb的长度为2,ab 的长度为1。

int findContinuousCharacter(char *pStr){     if(*pStr == '\0') return 0;     if(*(pStr + 1) == '\0') return 1;     if(*pStr == *(pStr + 1))          return 1 + find(pStr + 1);     return find(pStr + 1);}

22、字符串压缩
题目:通过键盘输入一串小写字母(a~z)组成的字符串。请编写一个字符串压缩程序,将字符串中连续出席的重复 字母进行压缩,并输出压缩后的字符串。 压缩规则:
仅压缩连续重复出现的字符。比如字符串”abcbc”由于无连续重复字符,压缩后的字符串还 是”abcbc”。 压缩字段的格式为”字符重复的次数+字符”。例如:字符串”xxxyyyyyyz”压缩后就成为”3x6yz”。
要求实现函数: void stringZip(const char pInputStr, long lInputLen, char pOutputStr);
输入pInputStr: 输入字符串lInputLen: 输入字符串长度
输出 pOutputStr: 输出字符串,空间已经开辟好,与输入字符串等长;
注意:只需要完成该函数功能算法,中间不需要有任何IO的输入输出 示例
输入:“cccddecc” 输出:“3c2de2c” 输入:“adef” 输出:“adef” 输入:“pppppppp” 输出:“8p”
解析:当个数不是1位数时,需要特殊处理一下。

void stringZip(const char *pInputStr, long lInputLen, char *pOutputStr){       const char *tmp=pInputStr;     const char *tmp2=pInputStr+1;        char *output=pOutputStr;       while(*tmp!='\0'){           if(*tmp!=*tmp2)           {               int dif= tmp2-tmp;                 //这一段是为了处理如果有100位a,超过1位数的压缩字符串。                 if(dif>1){  //大于1是因为,如果1的话,就不打印1                int num=dif;               int count=0;               while(num){                     count++;                     num=num/10;                }               output+=count-1;               while(dif){                   int mod =dif%10;                   *output= '0'+mod;                   output--;                   dif= dif/10;                    }               output+=count+1;               }               *output++ =*tmp;               tmp = tmp2++;           } else {                  tmp2++;                                }                }      *output='\0';  } 

23、集合的差集
题目:已知集合A和B的元素分别用不含头结点的单链表存储,请求集合A与B的差集,并将结果保存在集合A的单 链表中。例如,若集合A={5,10,20,15,25,30},集合B={5,15,35,25},完成计算后A={10,20,30}。

24、最长公共子串
给定字符串A和B,输出A和B中的第一个最长公共子串,比如A=“wepiabc B=“pabcni”,则输 出“abc”。
解析:使用动态规划方法,动态转移方程为: 如果xi == yj, 则 c[i][j] = c[i-1][j-1]+1;如果xi ! = yj, 那么c[i][j] = 0

int longest_common_substring(char *str1, char *str2)  {      int i,j,k,len1,len2,max,x,y;      len1 = strlen(str1);      len2 = strlen(str2);      int **c = new int*[len1+1];      for(i = 0; i < len1+1; i++)          c[i] = new int[len2+1];      for(i = 0; i < len1+1; i++)          c[i][0]=0;        //第0列都初始化为0      for(j = 0; j < len2+1; j++)          c[0][j]=0;        //第0行都初始化为0       max = -1;      for(i = 1 ; i < len1+1 ; i++)      {          for(j = 1; j < len2+1; j++)          {              if(str1[i-1]==str2[j-1])     //只需要跟左上方的c[i-1][j-1]比较就可以了                  c[i][j]=c[i-1][j-1]+1;              else                         //不连续的时候还要跟左边的c[i][j-1]、上边的c[i-1][j]值比较,这里不需要                  c[i][j]=0;              if(c[i][j]>max)              {                  max=c[i][j];                  x=i;                  y=j;              }          }      }      //输出公共子串      char s[1000];      k=max;      i=x-1,j=y-1;      s[k--]='\0';      while(i>=0 && j>=0)      {          if(str1[i]==str2[j])          {              s[k--]=str1[i];              i--;              j--;          }          else       //只要有一个不相等,就说明相等的公共字符断了,不连续了              break;      }      printf("最长公共子串为:");      puts(s);      for(i = 0; i < len1+1; i++)         //释放动态申请的二维数组          delete[] c[i];      delete[] c;      return max;  } 

25、均分01
给定一个字符串,长度不超过100,其中只包含字符0和1,并且字符0和1出现得次数都是偶数。你可以把字 符串任意切分,把切分后得字符串任意分给两个人,让两个人得到的0的总个数相等,得到的1的总个数也 相等。
例如,输入串是010111,我们可以把串切位01, 011,和1,把第1段和第3段放在一起分给一个人,第二段分 给另外一个人,这样每个人都得到了1个0和两个1。我们要做的是让切分的次数尽可能少。
考虑到最差情况,则是把字符串切分(n - 1)次形成n个长度为1的串。
解析:只需要统计‘1’的个数,获取串的一半长度中包含‘1’的个数也是一半就可以了

int divide(const char* s)  {      int count = 0;  //记载1的个数      int i;      int sum = 0;  //1的总数      int length = strlen(s);      for(i = 0; i < length; i++)          sum += s[i] - '0';      for(i = 0; i < length / 2; i++)  //中间划分          count += s[i] - '0';      if(count * 2 == sum)          return 1;  //1刀      else          return 2;  } 

本章结束

1 0
原创粉丝点击