【读书笔记】Cracking the Code Interview(第五版中文版)

导语

所有的编程练习都在牛客网OJ提交,链接: https://www.nowcoder.com/ta/cracking-the-coding-interview

第八章 面试考题

8.1 数组与字符串

1.1 实现一个算法,确定一个字符串的所有字符是否全都不相同。假设不允许使用额外的数据结构,又该如何处理?

题解:应该先clarify上面的字符串是 ASCII 还是 unicode,假设是 ASCII,我们可以直接用256个字母表来统计。用个vector<int> st(256, 0)即可。time complexity: O(N)

 1 class Different {
 2 public:
 3     bool checkDifferent(string iniString) {
 4         if (iniString.size() > 256) {
 5             return false;
 6         }       
 7         vector<int> st(256, 0);
 8         for (int i = 0; i < iniString.size(); ++i) {
 9             if (st[iniString[i]]) {return false;}
10             st[iniString[i]] = 1;
11         }
12         return true;
13     }
14 };
View Code

还可以sort之后,前后相邻的字符比较。time complexity: O(nlogn)

1.2 用C++实现void reverse(char* str)  函数,即反转一个null结尾的字符串。

题解:直接2 pointers。

 1 class Reverse {
 2 public:
 3     string reverseString(string iniString) {
 4         int n = iniString.size();
 5         int i = 0, j = n-1;
 6         while (i < j) {
 7             swap(iniString[i++], iniString[j--]);
 8         }
 9         return iniString;
10     }
11 };
View Code

1.3 给定两个字符串,请编写程序,确定其中一个字符串的字符重新排列之后,能够变成另外一个字符串。

题解:书上给的方法是用一个vector当作字母表,256个字符,统计频次,然后用第二个字符串去减掉。time complexty O(N+M)

也可以排序,或者用个map。

 1 #include <unordered_map>
 2 class Same {
 3 public:
 4     bool checkSam(string stringA, string stringB) {
 5         const int n1 = stringA.size(), n2 = stringB.size();
 6         if (n1 != n2) {return false;}
 7         unordered_map<char, int> mp1;
 8         for (auto& c : stringA) {
 9             mp1[c]++;
10         }
11         for (auto& c : stringB) {
12             if (mp1.find(c) == mp1.end() || mp1[c] == 0) {
13                 return false;
14             } 
15             mp1[c]--;
16         }
17         return true;
18     }
19 };
View Code

1.4 编写一个方法,将字符串中所有的空格都替换成”%20”。假设该字符串尾部有足够的空间存放新字符,并且知道字符串的真实长度。

题解:先统计空格数量,然后计算出新的总长度,然后从后往前一个一个移动处理。2 pass。

 1 class Replacement {
 2 public:
 3     string replaceSpace(string iniString, int length) {
 4         int cntSpace = 0;
 5         for (auto c : iniString) {
 6             if (c == ' ') {
 7                 cntSpace++;
 8             }
 9         }
10         int newLen = cntSpace * 2 + length;
11         string ret;
12         ret.resize(newLen);
13         int p2 = newLen - 1;
14         for (int i = length-1; i >= 0; --i) {
15             if (iniString[i] == ' ') {
16                 ret[p2--] = '0';
17                 ret[p2--] = '2';
18                 ret[p2--] = '%';
19             } else {
20                 ret[p2--] = iniString[i];
21             }
22         }
23         return ret;
24     }
25 };
View Code

1.5 利用字符重复出现的次数,编写一个方法,实现基本的字符串压缩功能。比如,字符串“aabcccccaaa”会变成“a2b1c5a3”。若“压缩”后的字符串没有变短,则返回原先的字符串。

题解:2 pointers。time complexity: O(N), space complexity: O(N)

 1 class Zipper {
 2 public:
 3     string zipString(string iniString) {
 4         const int n = iniString.size();
 5         string ret;
 6         if (n == 0) {return ret;}
 7         int p1 = 0, p2= 0;
 8         while (p2 < n) {
 9             while (p2 < n && iniString[p2] == iniString[p1]) {
10                 p2++;
11             }
12             int cnt = p2 - p1;
13             ret += string(1, iniString[p1]) + to_string(cnt);
14             p1 = p2;
15         }
16         return ret.size() < iniString.size() ? ret : iniString;
17     }
18 };
View Code

1.6 给定一幅由N*N矩阵表示的图像,其中每个像素的大小为4字节,编写一个方法,将图像旋转90度。不占用额外的内存空间能不能做到。48. Rotate Image

题解:转圈矩阵的通用解法。

 1 class Transform {
 2 public:
 3     vector<vector<int> > transformImage(vector<vector<int> > mat, int n) {
 4         int tr = 0, dr = n-1, tc = 0, dc = n-1;
 5         while (tr < dr) {
 6             rotate(mat, tr, dr, tc, dc);
 7             tr++, dr--, tc++, dc--;
 8         }
 9         return mat;
10     }
11     void rotate(vector<vector<int>>& mat, int tr, int dr, int tc, int dc) {
12         int group = dr - tr;
13         for (int i = 0; i < group; ++i) {
14             int temp = mat[tr][tc+i];
15             mat[tr][tc+i] = mat[dr-i][tc];
16             mat[dr-i][tc] = mat[dr][dc-i];
17             mat[dr][dc-i] = mat[tr+i][dc];
18             mat[tr+i][dc] = temp;
19         }
20     }
21 };
View Code

1.7 编写一个算法,若 M*N的矩阵中某个元素为0,则将其所在的行和列清零。73. Set Matrix Zeroes

题解:书上给的是两个 1D array 存放一行和一列是否为应该set 为0. row[i] = 0 代表第 i 行为 0, col[j] = 0 代表第 j 列为 0. time complexity: O(N*N), space complexity: O(N)

 1 class Clearer {
 2 public:
 3     vector<vector<int> > clearZero(vector<vector<int> > mat, int n) {
 4         vector<int> dp1(n, 1), dp2(n, 1);
 5         for (int i = 0; i < n; ++i) {
 6             for (int j = 0; j < n; ++j) {
 7                 if (mat[i][j] == 0) {
 8                     dp1[i] = dp2[j] = 0;
 9                 }
10             }
11         }
12         for (int i = 0; i < n; ++i) {
13             for (int j = 0; j < n; ++j) {
14                 if (dp1[i] == 0 || dp2[j] == 0) {
15                     mat[i][j] = 0;
16                 }
17             }
18         }
19         return mat;
20     }
21 };
View Code

可以把空间压缩成O(1)的,使用两个变量标记第0行和第0列是否应该为0,然后用第0行和第0列代替上文中的row和col。

1.8 假定有一个方法 isSubstring, 可检查一个单词是否为其他字符串的子串。给定两个字符串s1和s2,请编写代码检查s2是否为s1旋转而成,要求只能调用一次isSubstring。(比如,waterbottle 是 erbottlewat旋转后的字符串)。

题解:假设 s2 是通过 s1 旋转过来的,那么我们可以找出旋转点在哪里。例如,若以 wat 对 waterbottle 旋转,就会得到 erbottlewat 。在旋转字符串的时候,我们会把 s1 切分为两个部分:x 和 y,并且将他们组合成 s2. 

s1 = xy = waterbottle

x = wat

y = erbottle

s2 = yx = erbottlewat

因此,我们需要确认有没有办法将s1切分为x和y,以满足 xy = s1 和 yx = s2。 不论x和y之间的分割点在何处,我们会发现 yx 肯定是 xyxy 的子串。也就是 s2 总是 s1s1 的子串。

 1 class ReverseEqual {
 2 public:
 3     bool checkReverseEqual(string s1, string s2) {
 4         if (s1.size() != s2.size()) {return false;}
 5         string s = s1 + s1;
 6         return isSubstring(s, s2);
 7     }
 8     bool isSubstring(string s1, string s2) {
 9         return s1.find(s2) != string::npos;
10     }
11 };
View Code

8.2 链表

基础知识:

1. 如何创建一个单向链表。

2. 删除单向链表中的结点

3. 快慢指针的技巧:用两个指针来迭代访问链表,其中一个比另外一个稍微超前一些。“快”指针往前先行几步,或者与慢指针相差固定的步数。举个例子===~~~~?

4. 可以递归。

题目:

2.1 编写代码,移除未排序链表中的重复结点。Follow-up: 如果不得使用临时缓冲区,应该怎么解决?

2.2 实现一个算法,找出单向链表中的倒数第 k 个结点。

2.3 实现一个算法,删除单向链表中间的某个结点,假设你只能访问该结点。

eg 输入:单向链表 a->b->c->d->e 中的结点 c。

输出:不返回任何数据,但是该链表变为 a->b->c->d->e

2.4 编写代码,以给定值x 为基准将链表拆分两个部分,所有小于 x 的结点排在大于等于 x 的结点之前。

2.5 给定两个用链表表示的整数,每个结点包含一个数位。这些数位是反向存放的,也就是个位排在链表首部。编写函数对这两个整数求和,并用链表的形式返回结果。

eg: input: (7->1->6) + (5->9->2) output: 2->1->9

Follow-up: 假设这些数是正向存放的,请在做一遍。

eg: input: (6->1->1) + (2->9->5) output: 9->1->2

2.6 给定一个有环的链表,实现一个算法返回环路的头结点。

2.7 编写一个链表,检查链表是否为回文。

 

8.3 栈和队列:

基础知识:

1. 实现一个栈

2. 实现一个队列

题目:

3.1 描述如何用一个数组实现三个栈

3.2 请设计一个栈,除了pop和push 的方法,还支持 min 的方法,可以返回栈中元素的最小值。Push, pop, min这三个方法的时间复杂度必须为O(1).

3.3 设想有一堆盘子,堆太高可能会倒下来。因此,在现实生活中,盘子堆到一定高度的时候,我们就会堆成另外一堆盘子。请实现数据结构 setOfStacks,模拟这种行为。setOfStacks应该由多个栈组成,并且在前一个栈填满时新建一个栈。此外,setOfStacks.push() 和 setOfStacks.pop() 应该和普通栈的操作方法相同(也就是说,pop()返回的值应该只和一个栈时的情况一样)。

Follow-up:实现一个popAt(int index)方法,根据指定的子栈,执行pop操作。

3.4 在经典问题汉诺塔中,有3根柱子和 N 个不同大小的穿孔圆盘,盘子可以滑入任意一根柱子。一开始,所有盘子自底向上从大到小依次套在第一根柱子上(即每一个盘子只能放在更大的盘子上面)。移动圆盘时有以下限制:

(1) 每次只能移动一个盘子

(2) 盘子只能从柱子顶端滑出移到下一根柱子

(3) 盘子只能叠在比它大的盘子上

请运用栈,编写程序将所有的盘子从第一根柱子移动到最后一根柱子。

3.5 实现一个 MyQueue 类,该类用两个栈来实现一个队列。

3.6 编写程序,按照升序对栈进行排序(最大的元素位于栈顶)。最多只能使用一个额外的栈来存放临时数据,但不得将元素复制到别的数据结构中(如数组)。该栈支持如下操作:push,pop,peek,和 isEmpty。

题解:我们假设有两个栈,s2是排序的,s1是未排序的。每次从s1中弹出一个数,插入到s2的合适位置。怎么做呢?我们先从s1中弹出一个数字num,保存在一个变量中,然后从把s2中比num大的元素都弹出来,放入s1中。然后把num放入s2,再把原来s2中的元素放回去。time complexity: O(N^2), space complexity: O(N)

 1 class TwoStacks {
 2 public:
 3     vector<int> twoStacksSort(vector<int> numbers) {
 4         stack<int> stk1, stk2;
 5         for (int i = numbers.size() - 1; i >= 0; --i) {
 6             stk1.push(numbers[i]);
 7         }
 8         while (!stk1.empty()) {
 9             int num = stk1.top(); stk1.pop();
10             while (!stk2.empty() && stk2.top() > num) {
11                 int temp = stk2.top(); stk2.pop();
12                 stk1.push(temp);
13             }
14             stk2.push(num);
15         }
16         vector<int> ret;
17         while (!stk2.empty()) {
18             ret.push_back(stk2.top());
19             stk2.pop();
20         }
21         return ret;
22     }
23 };
View Code

3.7 有家动物收容所只收容狗和猫,并且按照严格“先进先出”的原则。在收养该收容所的动物时,收养人能够收养所有动物中“最老”(根据进入收容所的时间长短)的动物,或者,可以挑选猫和狗(同时必须收养此类动物中最老的)。换言之,收养人不能自由挑选想收养的对象。请创建适用于这个系统的数据结构,实现各种操作方法,比如enqueue, dequeueAny, dequeueDog, dequeueCat等。允许使用内置的linkedlist数据结构。

 牛客网:有家动物收容所只收留猫和狗,但有特殊的收养规则,收养人有两种收养方式,第一种为直接收养所有动物中最早进入收容所的,第二种为选择收养的动物类型(猫或狗),并收养该种动物中最早进入收容所的。

给定一个操作序列int[][2] ope(C++中为vector<vector<int>>)代表所有事件。若第一个元素为1,则代表有动物进入收容所,第二个元素为动物的编号,正数代表狗,负数代表猫;若第一个元素为2,则代表有人收养动物,第二个元素若为0,则采取第一种收养方式,若为1,则指定收养狗,若为-1则指定收养猫。请按顺序返回收养的序列。若出现不合法的操作,即没有可以符合领养要求的动物,则将这次领养操作忽略。

树和图的基本概念

4.1 实现一个函数,检查二叉树是否平衡。在这个问题中,平衡树的定义如下:任意一个结点,其两颗子树的高度差不超过1.

4.2 给定有向图,设计一个算法,找出两个结点之间是否存在一条路径。

4.3 给定一个有序的整数数组,元素各不相同并且按照升序排列,编写一个算法,创建一棵高度最小的BST。

4.4 给定一棵二叉树,设计一个算法,创建含有某一深度上所有结点的链表。(比如,若一棵树的深度为D,则会创建出D个链表)

4.5 设计一个函数,检查一个二叉树是否为BST。

4.6 设计一个算法,找出BST中指定结点的下一个结点(中序后继)。可以假设每个结点都有指向父结点的链接。

4.7 设计并实现一个算法,找出二叉树中某两个结点的第一个公共祖先。不得将额外的结点存储在另外的数据结构中。注意:这不一定是BST。

4.8 你有两颗非常大的二叉树:T1,有几百万个结点;T2,有几百个结点。设计一个算法,判断T2是否为T1的subtree。如果说T1有这么一个结点n,其子树和T2一模一样,则T2为T1的子树。也就是说,从结点n处把树砍断,得到的树与T2完全相同。

4.9 给定一棵二叉树,其中每个结点都含有一个数值。设计一个算法,打印结点数值总和等于某个给定值的所有路径。注意,路径不一定非得从二叉树的根结点或者叶结点开始或结束。

 

 

数学和概率

7.1 有一个篮球框,下面两种玩法可以任选一种:

玩法1: 一次出手机会,投篮命中得分

玩法2: 三次出手机会,必须投中两次。

如果p是某次投篮命中的概率,则p的值为多少时,才会选择玩法1或者玩法2?

7.2 三角形的三个顶点上各有一只蚂蚁。如果蚂蚁开始沿着三角形的边爬行,两只或者三只蚂蚁撞在一起的概率多大?假设每只蚂蚁会随机选一个方向,每个方向被选到的几率相等,并且三只蚂蚁的爬行速度相同。类似问题:在 n 个顶点的多边形上有 n 只蚂蚁,求这些蚂蚁发生碰撞的概率。

7.3 给定直角坐标系上的两条线,判断这两条线会不会相交。

7.4 编写方法,实现整数的乘法,减法和除法运算。只允许使用加号。

7.5 在二维平面上,有两个正方形,请找出一条直线,能够将这两个正方形对半分。假定正方形的上下两条边与x轴平行。

7.6 在二维平面上,有一些点,请找出经过点数最多的那条线。

7.7 有些数的素因子只有3,5,7,请设计一个算法,找出其中第k个数。

原文地址:https://www.cnblogs.com/zhangwanying/p/10371495.html