【读书笔记】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 };
还可以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 };
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 };
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 };
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 };
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 };
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 };
可以把空间压缩成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 };
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 };
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个数。