《程序员面试金典》习题解答(C/C++)
一.数据结构
1.数组与字符串
1.1 实现一个算法,确定一个字符串的所有字符是否全都不同。假使不允许使用额外的数据结构,又该如何处理?
/* 假设字符集为ASCII字符串,那么字符串至多有256个字符,因为一旦大于256个则必定有两个是相同的 */ #include<iostream> #include<string> using namespace std; bool isUniqueChars(string str){ if (str.size() > 256) return false; bool char_set[256]; for(int i = 0; i < str.size; ++i) { int val = str[i]; //获取字符对应得ASCII码 if (char_set[val]) //这个字符已经在字符串中出现 return false; char_set[val] = 1; } return true; } /* 假如输入的字符只含有小写字母a到z,则我们只需要用一个int型变量 */ bool isUniqueChar2(string str) { int flag = 0; if (str.size() > 26) return false; for (int i = 0; i < 26; ++i) { int val = str[i] - 'a'; if (flag&(1 << val) > 0) return false; flag |= (1 << val); } return true; }
1.2 用C或C++实现void reverse(char* str)函数,即反转一个null结尾的字符串。
/*假设原地反转,不分配额外空间,下面为C语言实现*/ #include<stdio.h> void reverse(char* str) { char* end = str; char* start = str; char tmp; if (str) { while (*end) ++end; } --end; /*回退一个字符,因为最后的字符为null*/ /*从首尾开始交换*/ while (start < end) { tmp = *end; *end = *start; *start = tmp; ++start; --end; } }
1.3 给定两个字符串,请编写程序,确定其中一个字符串的字符重新排列后,能否变成另外一个字符串。
/*给定两个字符串,请编写程序,确定其中一个字符串的字符重新排列后,能否变成另外一个字符串。*/ /*假设字符区分大小写,空白字符也考虑在内,以下是两种解法,使用C++*/ #include<iostream> #include<string> #include <algorithm> using namespace std; /*解法一:排序字符串,将两个字符串进行排序后再进行比较*/ bool permutation1(string s, string t) { if (s.size() != t.size()) return false; string s1 = s; string t1 = t; /*进行排序*/ sort(s1.begin(), s1.end()); sort(t1.begin(), t1.end()); if (s1 == t1) return true; return false; } /*解法二:比较两个字符串各字符数是否相同*/ bool permutation2(string s, string t) { if (s.size() != t.size()) return false; int count[256] = { 0 }; /*计算s中各个字符出现的次数*/ for (string::size_type i = 0; i !=s.size(); ++i) { int val = (int)s[i]; count[val]++; } for (string::size_type j = 0; j != t.size(); ++j) { int c = (int) t[j]; if (--count[c] < 0) return false; } return true; }
1.4 编写一个方法,将字符串中的空格全部替换成“%20”。假定该字符串尾部有足够的空间存放新增字符,并且知道字符串的真实长度。例如,输入:"Mr Jone Smith",输出:“Mr20%Jone20%Smith”。
/*编写一个方法,将字符串中的空格全部替换成“%20”。假定该字符串尾部有足够的空间存放新增字符,并且知道字符串的真实长度。*/ /*例如,输入:"Mr Jone Smith",输出:“Mr20%Jone20%Smith”。*/ #include<iostream> #include<string> using namespace std; void replaceSpaces(char* str,int length) { int spaceCount = 0; for (int i = 0; i < length; ++i) { if (str[i] == ' ') ++spaceCount; } int newLength = length + spaceCount * 2; str[newLength] = '\0'; int p = length - 1; int q = newLength - 1; while (p < q) { if (str[p] != ' ') str[q--] = str[p--]; else { str[q--] = '0'; str[q--] = '2'; str[q--] = '%'; p--; } } }
1.5 利用字符重复出现的次数,编写一个方法,实现基本的字符串压缩功能。比如,字符串"aabcccccaaa"会变成“a2b1c5a3”。若“压缩”后字符串没有变短,则返回原先的字符串。
#include<iostream> #include<string> using namespace std; string compress(string str) { if (str.empty()) return NULL; string str1; char last = str[0]; int count = 1; for (string::size_type i = 1; i < str.size(); ++i) { if (str[i] == last) ++count; else { str1.push_back(last); str1+=to_string(count); last = str[i]; count = 1; } } /*因为最后一组重复字符还未放入压缩字符串中就退出循环,所以还要更新最后一组字符*/ str1.push_back(last); str1+=to_string(count); return str1.size() < str.size() ? str1: str; }
1.6 给定一幅由M*N矩阵表示的图像,其中每个像素的大小为4字节,编写一个方法,将图像旋转90度。不占用额外内存空间能否做到?
/*假设顺时针旋转,*/ #include<iostream> using namespace std; void rotate(int** matrix, int n) { for (int layer = 0; layer < n / 2; ++layer) { int first = layer; int last = n - 1 - layer; for (int i = first; i < last; ++i) { int offset = i - first; //存储上边 int top = matrix[first][i]; //左到上 matrix[first][i] = matrix[last - offset][first]; //下到左 matrix[last - offset][first] = matrix[last][last - offset]; //右到下 matrix[last][last - offset] = matrix[i][last]; //上到右 matrix[i][last = top; } } }
1.7 编写一个算法,若M*N的矩阵中某个元素为0,则将其所在的行与列清零。
void setZeros(vector<vector<int> > matrix) { bool *row= new bool[matrix.size()]; bool *column= new bool[matrix[0].size()]; //为保险起见首先初始化为false for (int i = 0; i < matrix.size(); ++i) { for (int j = 0; j < matrix[0].size(); ++j) { row[i] = false; column[j] = false; } } //记录值为0的元素所在的行的索引和列的索引 for (int i = 0; i < matrix.size(); ++i) { for (int j = 0; j < matrix[0].size(); ++j) { if (matrix[i][j] == 0) { row[i] = true; column[j] = true; } } } //若行i或列j有个元素为0,则matrix[i][j]置为0 for (int i = 0; i < matrix.size(); ++i) { for (int j = 0; j < matrix[0].size(); ++j) { if(row[i]||column[j]) matrix[i][j] = 0; } } }
1.8 假定有一个方法isSubstring,可检查一个单词是否为其他字符串的子串。给定两个字符串s1和s2,请编写代码检查s2是否为s1旋转而成,要求只能调用一次isSubstring。(比如,waterbottle是erbottlewat旋转后的字符串)
/* *假设s2由s1旋转而成,那么我们可以找出旋转点在哪。例如,若以wat旋转waterbottle,就会得到erbottlewat。 *我们把字符串s1切分为两部分x和y,并将它们重新组合成s2. *s1=xy=waterbottle *x=wat *yerbottle *s2=yx=erbottlewat *我们需要确认的是有没有办法将s1切分成xy=s1,yx=s2。不论xy的分割点在何处,xy都是xyxy的子串。也即s2是s1s1的子串 */ #include<iostream> using namespace std; bool isRotation(string s1, string s2) { int len = s1.size(); //检查s1和s2是否等长且不为空 if (len == s1.size() && len > 0) { //凭借s1和s1,放入新字符串中 string s1s1 = s1 + s1; return isSubstring(s1s1, s2); } return false; }
2.链表
2.1 编写代码,移除为排序链表中的重复节点。进阶:如果不得使用临时缓冲区,该怎么解决?
#include<iostream> #include<map> using namespace std; //链表节点定义 struct Node { Node* next = NULL; int data; }; /*解法一:采用散列表来存储节点的值是否出现过,若重复,则删除该节点。*/ void deleteDups1(Node *phead) { map<int, bool> imap; Node* previous = phead; Node* p = phead; while (p != NULL) { if (imap[p->data]) { previous->next =p->next; } else { imap[p->data] = true; previous = p; } p = p->next; } } /*解法二:进阶不使用额外缓冲区,使用两个指针来迭代:current迭代访问整个列表,runner用于检查后续的节点是否重复*/ void deleteDups2(Node* phead) { if (phead == NULL) return; Node* current = phead; while (current != NULL) { Node* runner = current; while (runner->next != NULL) { if (current->data == runner->next->data) { current->next = runner->next->next; } else { runner = runner->next; } current = current->next; } } }