3 高质量的代码
面试题16 数值的整数次方
题目描述
给定一个 double 类型的浮点数 base 和 int 类型的整数 exponent。求 base 的 exponent 次方。不可使用库函数,不用考虑大数问题。
解题思路
考察代码的完整性,不能觉得它简单,要尽可能想的全面。
- base 是 0,或者 exponent 是0
- exponent 是一个负值
- 快速幂
Code
递归的进行快速幂:
class Solution {
public:
double Power(double base, int exponent) {
if (exponent == 0)
return 1;
if (exponent == 1)
return base;
if (base == 0)
return 0;
if (exponent < 0)
{
base = 1 / base;
exponent = -exponent;
}
if (exponent & 1)
return base * Power(base*base, exponent >> 1);
else
return Power(base*base, exponent >> 1);
}
};
面试题17 打印从 1 到最大的 n 位数
题目描述
输入数字 n,按顺序打印出从 1 最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数即 999。
解题思路
这里需要考虑大数的问题,因为当 n 稍微大一点的时候,比如 十几,用 int 就放不下了。这里可以使用回溯法的思想,利用 string 保存数字,一直去按顺序探索路径,每次探索的时候保留着前缀。
Code
void print(int n, string prefix){
// 打印 n 位数
char a = '0';
if (n == 1){
for (int i = 0; i < 10; i ++){
cout << prefix << char(a + i) << " ";
}
return;
}
for (int i = 1; i < 10; i ++){
// 在前面加一位数
string new_prefix = char(a + i) + prefix;
print(n-1, new_prefix);
}
}
int main(){
int n;
string empty;
while(cin >> n){
for (int i = 1; i <= n; i++){
print(i, empty);
}
}
cout << endl;
return 0;
}
面试题18.1 删除链表的节点
题目描述
在 O(1) 时间内删除链表节点,给定单向链表的头指针和一个节点指针,定义一个函数在 O(1) 时间内删除该节点。
解题思路
删除链表中的节点并不必须知道它的前一个节点,分两种情况考虑
- 要删除的节点不是尾节点,把它后面的节点值复制到当前节点,然后把后面的节点删除
- 如果是尾节点,没办法,只能从头到尾遍历一遍,时间复杂度为 O(n)
算法的平均复杂度是 O(1)
面试题18.2 删除链表重复的节点
题目描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
解题思路
- 为了使得处理统一,建立一个空的头指针
- 遍历整个链表,遍历的同时保留住其上一个节点,当遇到某一个节点的值与其之后节点的值相同时,进入一个 while 循环,不断删除掉与这个值相同的节点
Code
class Solution {
public:
ListNode* deleteDuplication(ListNode* pHead)
{
if (pHead == NULL) return pHead;
ListNode* newHead = new ListNode(0);
newHead -> next = pHead;
ListNode* p = newHead -> next;
ListNode* q = newHead;
while( p != NULL){
if (p -> next != NULL && p -> val == p -> next -> val){
int t = p -> val; // found duplicates
while(p != NULL && p -> val == t){
q -> next = p -> next;
p = q -> next;
}
}
else{
q = p;
p = p -> next;
}
}
return newHead -> next;
}
};
面试题19 正则表达式匹配
题目描述
请实现一个函数用来匹配包括 ‘.’ 和 ‘*’ 的正则表达式。模式中的字符 ‘.’ 表示任意一个字符,而 ‘*’ 表示它前面的字符可以出现任意次(包含 0 次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"a.a" 和 “ab*ac*a” 匹配,但是与 “aa.a” 和 “ab*a” 均不匹配。
解题思路
这题不难,主要是需要考虑的东西太多。
- 递归进行求解。
- 先写停止条件
- 如果 pattern 和 str 都为空,说明配对成功
- 如果只有 pattern 为空,说明配对失败
- (要注意,如果只有 str 为空,也是有可能配对成功的)
- 如果 pattern 的第二个字符为 *
- 如果 pattern 和 str 第一个字符相等,或者 pattern 第一个字符为 ‘.’,并且str 不为空,说明可以配对,这时可以
- 让 pattern 往后走两个,或者
- 让 str 往后走一个
- 如果不等的话,只能让 pattern 往后走两个
- 如果 pattern 和 str 第一个字符相等,或者 pattern 第一个字符为 ‘.’,并且str 不为空,说明可以配对,这时可以
- 如果第二个字符不为 *
- 如果 pattern 和 str 第一个字符相等,或者 pattern 第一个字符为 ‘.’,并且str 不为空,说明可以配对,这时让 str 和 pattern 同时走一步
Code
class Solution {
public:
bool match(char* str, char* pattern)
{
if (*pattern == '\0' && *str == '\0')
return true;
if (*pattern == '\0' && *str != '\0')
return false;
if (*(pattern + 1) != '*'){
if (*str == *pattern || (*pattern == '.' && *str != '\0'))
return match(str + 1, pattern + 1);
else
return false;
}
else{
if (*str == *pattern || (*pattern == '.' && *str != '\0')){
return match(str + 1, pattern) ||
match(str, pattern + 2);
}
else
return match(str, pattern + 2);
}
}
};
面试题20 表示数值的字符串
题目描述
判断一个给定的字符串是不是一个有效的数字,比如,字符串 “+100”,“5e2”,"-123",“3.1416” 和 “-1E-16” 都表示数值。 但是 “12e”,“1a3.14”,“1.2.3”,“±5” 和 “12e+4.3” 都不是。
解题思路
用三个布尔变量,分别表示是否见过符号,小数点,以及 e,有以下的判断条件:
- 不可以存在两个 e
- e 之后不可以是空
- 如果第二次出现符号,那么它之前一定是 e
- 如果是第一次出现符号,但是它并不在字符串的开头,那么它之前也应该是 e
- e 之后不可以接小数点,并且小数点不可以出现两次
- 不可以有不合法的字符
Code
class Solution {
public:
bool isNumeric(char* str) {
// 标记符号、小数点、e是否出现过
bool sign = false, decimal = false, hasE = false;
for (int i = 0; i < strlen(str); i++) {
if (str[i] == 'e' || str[i] == 'E') {
if (i == strlen(str)-1) return false; // e后面一定要接数字
if (hasE) return false; // 不能同时存在两个e
hasE = true;
} else if (str[i] == '+' || str[i] == '-') {
// 第二次出现+-符号,则必须紧接在e之后
if (sign && str[i-1] != 'e' && str[i-1] != 'E') return false;
// 第一次出现+-符号,且不是在字符串开头,则也必须紧接在e之后
if (!sign && i > 0 && str[i-1] != 'e' && str[i-1] != 'E') return false;
sign = true;
} else if (str[i] == '.') {
// e后面不能接小数点,小数点不能出现两次
if (hasE || decimal) return false;
decimal = true;
} else if (str[i] < '0' || str[i] > '9') // 不合法字符
return false;
}
return true;
}
};
面试题21
题目描述
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
解题思路
如果不考虑相对位置的话,可以利用两个指针,当前指针碰到偶数,后指针碰到奇数,交换两者的位置。
这里需要考虑相对的位置,那么只能够考虑别的方法。
- 可以通过插入排序的思想,去寻找待插入元素的位置。按顺序去插入,如果是奇数的话,插入到数组中奇数的最后(这个时候需要后面所有的偶数都往后搬),如果是偶数,插入到数组的最后面(不需要移动元素)。
- 另一种方法是,从前往后扫描元素,如果碰到了一个偶数,那么在它的后面找奇数,如果全部是偶数,说明任务完成。找到奇数以后,让这个奇数与它前面的元素交换位置,这里必须相邻的交换,不然会造成排序不稳定。
- 前两种方法时间复杂度都是 O(n^2),空间复杂度 O(1)。还有一种可以 O(n) 时间完成的方法,空间复杂度也是 O(n)。先遍历一遍数组,统计奇数的个数,然后新建一个数组,保留奇数偶数两个 idnex,奇数初始值为 1,偶数初始值为奇数的个数。然后将数组按照奇偶数对应的复制过去。
Code
class Solution {
public:
void reOrderArray(vector<int> &array) {
for (int i = 0; i < array.size(); i ++){
if (array[i] % 2 == 1){
continue;
}
else{
//cound even number
bool exchange = false;
for(int j = i + 1; j < array.size(); j ++){
if (array[j] % 2 == 1){
// 只可以相邻的进行交换
swap(array[j - 1], array[j]);
exchange = true;
break;
}
}
if (exchange){
i --; // i 不变化,继续让后面的往前面换
}
else{
return;
}
}
}
}
};
面试题23 链表中倒数第 k 个节点
题目描述
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
解题思路
用两个指针,第一个指针先往后走 k 步,然后两个指针一起走,当第一个指针走到头时,第二个指针指向的就是倒数第 k 个节点。需要注意以下的情况:
- 当长度不到 k 的时候需要正确处理
- 当 k = 0 时
- 当链表指针为空时
Code
class Solution {
public:
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
unsigned int p = 0;
if (pListHead == NULL || k == 0)
return NULL;
ListNode* t = pListHead;
while(t != NULL && p < k){
p ++;
t = t -> next;
}
if (p != k) return NULL;
ListNode* q = pListHead;
while(t){
t = t -> next;
q = q -> next;
}
return q;
}
};
面试题23 链表中环的入口节点
题目描述
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
解题思路
设置快慢两个指针,快指针一次走两步,慢指针一次走一步。如果两个指针相遇,说明链表中是有环的。这时,让其中一个指针从头开始走,当两个指针再次相遇,这时就是链表中环的起始节点。
Code
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
ListNode* fast = pHead;
ListNode* slow = pHead;
while(fast != NULL && fast -> next != NULL){
fast = fast -> next -> next;
slow = slow -> next;
if(fast == slow)
break;
}
if (fast == NULL || fast -> next == NULL)
return NULL;
fast = pHead;
while(fast != slow){
fast = fast -> next;
slow = slow -> next;
}
return fast;
}
};
面试题24 反转链表
题目描述
输入一个链表,反转链表后,输出新链表的表头。
解题思路
- 直接使用链表的头插法即可,记得自己 new 的空间,要自己 delete 掉,不然会内存泄漏(被面试的时候教的。。。)
- 第二种思路,递归实现。先反转当前节点的下一个节点。反转完成以后,返回了链表头,当前节点的下一个节点变成了链表的末尾。然后把当前节点续到链表末尾上。
- 第三种思路,直接修改指针。将当前节点的下一个节点的 next 指向当前节点。
Code
第一种思路:
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
ListNode* newHead = new ListNode(0);
while(pHead){
ListNode* t = pHead -> next;
pHead -> next = newHead -> next;
newHead -> next = pHead;
pHead = t;
}
ListNode* t = newHead -> next;
delete newHead;
return t;
}
};
第二种思路:
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
if(pHead == NULL || pHead -> next == NULL) return pHead;
ListNode* newHead = ReverseList(pHead -> next);
pHead -> next -> next = pHead;
pHead -> next = NULL;
return newHead;
}
};
第三种思路:
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
if (pHead == NULL || pHead -> next == NULL){
return pHead;
}
ListNode* p = pHead;
ListNode* q = p -> next;
pHead -> next = NULL;
while(q){
ListNode* r = q -> next;
q -> next = p;
p = q; q = r;
}
return p;
}
};
面试题25 合并两个排序的链表
题目描述
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
解题思路
直接归并排序。迭代比较好实现,递归的思想很巧妙。
Code
第一种解法,迭代:
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
ListNode* newHead = new ListNode(0);
ListNode* p = newHead;
while(pHead2 != NULL && pHead1 != NULL){
ListNode* t;
if(pHead1 -> val <= pHead2 -> val){
t = pHead1;
pHead1 = pHead1 -> next;
}
else{
t = pHead2;
pHead2 = pHead2 -> next;
}
t -> next = NULL;
p -> next = t;
p = p -> next;
}
if(pHead1){
p -> next = pHead1;
}
if(pHead2){
p -> next = pHead2;
}
p = newHead -> next;
delete newHead;
return p;
}
};
第二种解法,递归:
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
if (pHead1 == NULL)
return pHead2;
if (pHead2 == NULL)
return pHead1;
if (pHead1 -> val <= pHead2 -> val){
pHead1 -> next = Merge(pHead1 -> next, pHead2);
return pHead1;
}else{
pHead2 -> next = Merge(pHead1, pHead2 -> next);
return pHead2;
}
}
};
面试题26 树的子结构
题目描述
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)。需要注意子结构的定义,一棵树是另外一棵树的一部分即可,不一定要包含叶节点。
解题思路
递归的进行,在 A 树上的每个节点判断,在这个节点上,B 树是不是 A 树的子树。这个判断也是一个递归的过程。
Code
class Solution {
public:
bool isSubTree(TreeNode* root1, TreeNode* root2){
if (root2 == NULL) return true;
if (root1 == NULL) return false;
if (root1 -> val != root2 -> val)
return false;
return isSubTree(root1 -> left, root2 -> left) && isSubTree(root1 -> right, root2 -> right);
}
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
{
if(pRoot1 == NULL || pRoot2 == NULL)
return false;
if(isSubTree(pRoot1, pRoot2))
return true;
return HasSubtree(pRoot1 -> left, pRoot2) || HasSubtree(pRoot1 -> right, pRoot2);
}
};