剑指offer学习笔记

# 剑指offer
## 1. 面试的流程
### 1.1 面试官谈面试
### 1.2 面试的3种形式
#### 1.2.1 电话面试
#### 1.2.2 共享桌面远程面试
#### 1.2.3 现场面试
### 1.3 面试的3个环节
#### 1.3.1 行为面试环节
##### 1 项目经验
##### 2 应聘者技能
###### 技能掌握程度
* 了解
* 仅 上过课/看过书, 妹做过实际项目
* 不列出肤浅的了解的技能,除非职位的确需要
* 熟悉
* 实际项目中使用某项技术较长时间,通过查阅文档可以解决大部分问题
* 已经工作过 / 项目开发过程中所用到的技能 ,可用熟悉
* 精通
* 使用得心应手
* 项目开发中, 别人请教这个领域的问题时,有信心有能力解决
* 能轻松地回答这个领域里绝大多数问题

##### 3 为啥跳槽
* 不抱怨, 不流露负面情绪

###### 避免以下
* 老板苛刻
* 同事难相处
* 加班频繁
* 工资低

#### 1.3.2 技术面试环节
* 基础扎实
* 代码质量高
* 分析问题思路清晰
* 优化时空效率
* 学习沟通等能力

##### 1 基础扎实
* 编程语言
* 数据结构
* 链表(常问): 插入 删除
* 二叉树(常问) :遍历(<-循环/递归) * 查找 排序 * 二分查找 * 归并排序/快速排序 * 动规 贪心 ##### 2 高质量代码 * 考虑全面 * 边界条件 * 特殊输入 * 鲁棒性 ##### 3 思路清晰 ###### 方法 * 举简单例子以便自己理解问题 * 数据结构简化题目 * 分治/动态规划 ##### 4 优化效率的能力 * <- 各种数据结构的优缺点 * 熟练掌握常用算法 ##### 5 优秀的综合能力 * 沟通 * 合作 ###### 学习能力检测 * 问最近学啥 * 学习新概念 ###### 知识迁移能力 * 某题和其上一题有关这种 ###### 抽象建模/发散思维 * 抽象建模 -> 用数据结构表示

#### 1.3.3 应聘者提问
* <- 面试官想了解应聘者最关心的问题 * -> 要问1~2,不然显得没兴趣
* 视面试官职务
* 不问薪水,此和hr谈
* 不问面试结果
* <- 白问 * <- 显得无自我评估能力 * 与应聘 职位/项目 相关问题 * <- 上网搜索公司大概做啥 * <- 面试官说过的项目 ## 2. 面试需要的基础知识 ### 2.1 面试官谈基础知识 * 陈黎明 * os理解程度 * 一门编程语言的掌握程度。上手新的快,理解深 * 常用的算法和数据结构。不了解的程序员基本只能写写'hello world' ### 2.2 编程语言 ###### 考核方式 * 直接问语法 * 写代码解决问题 ###### 语言类别 * c <- 底层开发(如驱动) * c++ <- linux * c# <- windows * java <- 跨平台 * object c <- ios * perl, python <- 脚本语言 #### 2.2.1 c++ * 都要掌握 ##### 1 概念理解 * c艹关键字理解程度 * sizeof ``` // 实例必须要占用空间 sizeof(struct none{}) = 1 <- visual studio // 构造析构函数,只需知函数地址即可 && 无关于实例 sizeof(stuct none{只有 构造/析构函数}) = 1 sizeof(struct none{有个虚函数}) = 4 <-32bit sizeof(struct none{有个虚函数}) = 8 // 64位机器上指针长8byte ``` ##### 2 脑中运行 * 复制构造函数的参数,只能是 常量引用 * 不引用的话,就会无限调用复制构造函数 ##### 3 实现 类/成员函数 * c艹 * 《effective c++》适合突击 * 列举了c艹常见问题及解决技巧 * 提的问题常考 * 《c++ primer》全面了解 * 《深度探索c艹对象模型》 * 理解c艹难题 * 《The c艹 programming language》 全面深入掌握 ###### eg1 * c艹基础语法 * 内存泄漏的理解 * 异常安全原则 * 申请内存分配失败后,不改变原先的 ``` ; eg1 赋值运算符函数 ``` #### 2.2.2 c sharp * 常考与c艹区别 * struct && class * c艹中struct和class区别 * struct中 成员变量&&成员函数 默认访问权限为public * c#中 * 成员变量&&成员函数 在 struct和class中 默认为private * struct定义值类型,在栈上分配 * class定义引用类型,在堆上分配 * c#中Finalizer方法(<- 运行时垃圾回收调用), Dispose方法释放资源 * c#可有 静态构造函数 * 第一次使用前 调用一次 * c#特有功能 * 反射 * 应用程序域 * 其它 * 书 * 《Professional c#》 * 附录介绍区别 * 《CLR via c#》 * c# * clr * .net * 装箱卸箱 * 垃圾回收 * 反射 * 其它 ###### eg2 实现singleton模式 * c#基础语法 * 单例模式 * 能多线程运行(加锁 ### 2.3 数据结构 * 是面试考察的重点 * 主要考察 * 数组 * 连续内存存数字 * 字符串 * 连续内存存字符 * 链表(最高频率 * 鲁棒性 * 树(最高频率 * 栈 * 递归相关 * 深入理解帮住解决很多算法问题 * 队列 * bfs * 深入理解帮住解决很多算法问题 #### 2.3.1 数组 * 简单 * 内存连续,顺序存储 * -> 时间效率高 -> 实现哈希表 -> 快查
* 预分配 -> 空间效率低
* <- vector * 每次扩容时,新容量 = 2 * 之前的容量 * 旧数组复制到新数组,释放之前的内存 ###### c艹 * sizeof * sizeof(数组) = 数组大小 * sizeof(指针) = 4 <- 32bit * 数组作为参数时,数组退化指针 ###### eg3 数组中重复的数字 * 扫描排序后的 * 哈希表 * 时O(1), 空O(1) ``` ``` #### 2.3.2 字符串 * 频率高 * <- 优化 <- 特殊规定 ###### c/c艹/csharp字符串特性 * c/c艹 * 以 '\0' 结尾 * 注意越界 * 常量字符串 在单独的内存区域 * csharep * System.String * <- 不可改变 * StringBuilder * 可改 #### 2.3.3 链表 * 面试中使用最频繁 * 结构简单 * 操作代码量少 * 哈希表、有向图等复杂数据结构,一个操作代码量大 -> 几十分钟的面试很难完成
* 是动态数据结构,需要指针操作
* 需较好编程功底
* 灵活
* 题可以有挑战性

#### 2.3.4 树
* 涉及大量指针
* 考察指针力
* 大多二叉树
* 遍历最重要

###### 遍历
* 遍历方式
* 前序
* 中序
* 后序
* 上三种都可 递归 / 循环
* 层次
* 用队列

* 前序遍历

“`
// 递归遍历
private void preorder(List l, TreeNode node) {
if (node == null) return;
l.add(node.val);
if (node.left != null)
preorder(l, node.left);
if (node.right != null)
preorder(l, node.right);
}

// 非递归遍历
Stack s = new Stack();
s.push(root);
while (!s.empty()) {
TreeNode n = s.pop();
l.add(n.val);
if (n.right != null) {
s.push(n.right);
}
if (n.left != null) {
s.push(n.left);
}
}
“`

* 中序遍历

“`
// 递归
private void inorder(List l, TreeNode n) {
if (n.left != null)
inorder(l, n.left);
l.add(n.val);
if (n.right != null)
inorder(l, n.right);
}

// 非递归
Stack s = new Stack();
TreeNode p = root;
while (!(p==null && s.empty())) {
while (p != null) { //来到最左边
s.push(p);
p = p.left;
}
if (!s.empty()) {
// 这步运行之后即是到达最左边的叶子节点
p = s.pop(); // p分为是否是叶子。是叶子则程序继续运行再次来到这里遍历父节点(这步之后的程序则是从左子节点到父节点),不是叶子(有右节点)则遍历右子节点树
l.add(p.val);
p = p.right;
}
}
“`

* 后序遍历

“`

// 非递归
Stack s = new Stack();
TreeNode p = root, vis = null;
while (!(p == null && s.empty())) {
while (p != null) {
s.push(p);
p = p.left;
}
if (!s.empty()) {
p = s.peek();
if (p.right != null && vis != p.right) {
p = p.right;
} else {
p = s.pop();
l.add(p.val);
vis = p;
p = null;
}
}
}
“`

* 层次遍历

“`
public List> levelOrder(TreeNode root) {
List> l = new ArrayList<>();
if (root == null) return l;
Queue q = new LinkedList();
q.offer(root);
int level = 0;
while (!q.isEmpty()) {
l.add(new ArrayList());
int levelLength = q.size();
while ((levelLength–) > 0) {
TreeNode n = q.poll();
l.get(level).add(n.val);

if (n.left != null) {
q.offer(n.left);
}
if (n.right != null) {
q.offer(n.right);
}
}
++level;
}

return l;
}
“`

* 深度

“`
// 递归
public int maxDepth(TreeNode root) {
if (root == null) return 0;
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
“`

* 常直接/间接被考察
* 有很多特例
* 二叉搜索树
* 堆
* 最大堆
* 最小堆
* 红黑树
* 节点分 红黑二色
* 根节点to叶节点 的最长路径 《= 2x最短路
* c艹 stl之(multi)set, (multi)map等基于此实现

#### 2.3.5 栈和队列
###### 栈
* 计算机中常见
* 如线程栈存储 函数调用的参数返回地址临时变量等
* 后进先出
* 常不考虑排序
* 一般O(n)寻min/max

###### 队列
* 先进先出

* 栈 队列 相互联系
* 两个栈 => 队列
* 两个队列 => 栈

### 2.4 算法和数据操作
###### 算法
* 常考似数据结构
* 常可 递归/循环
* 递归 更简洁 性能低

* 排序 查找 常考
* 二分查找
* 归并排序 && 快排
* 二维数组搜索路径
* <- 回溯法 * <- 常递归实现 * 或栈实现 ###### 位运算 #### 2.4.1 递归和循环 * fibonacci * 自上而下递归 <- 效率低 * 自下而上循环 <- 实用 * 生僻公式O(logn) <- 不实用 ##### 2.4.2 查找和排序 ###### 查找 * 分类 * 顺序查找 * 二分查找 <- 循环 / 递归 * <- 一定掌握 * 哈希表查找 <- 主要考察数据结构而非算法 * 时O(1)查找元素,效率最高 * 缺点: 需要额外空间 * 二叉排序树查找 <- 主要考察数据结构而非算法 * -> 二叉搜索树
* (部分)排序的 数组中查找 数字/数字出现之次数
* <- 二分查找 ``` public int search(int[] nums, int target) { int start = 0, end = nums.length - 1; int mid = (start + end) >> 1;
while (start <= end) { mid = (start + end) >> 1;
if (target == nums[mid]) return mid;
else if (target < nums[mid]) { end = mid-1; } else { start = mid+1; } } return -1; } ``` ###### 排序 * 分类 * 插入排序 * 冒泡排序 * 归并排序 * 快排 * 常要手写 -> 记
* 其它
* 常比较各类优劣
* 记
* 算法特点
* 额外空间消耗
* 平均时间复杂度
* 最差时间复杂度

###### 面试时
* 问清应用环境
* 问清约束条件(时间空间限制)

* 冒泡排序

“`
for (int i=0; i nums[k+1]) {
int temp = nums[k];
nums[k] = nums[k+1];
nums[k+1] = temp;
}
}
}
“`

* 简单选择排序

“`

for (int i=0; i= 0 && nums[k] > tmp) {
nums[k+1] = nums[k];
–k;
}
++k;
nums[k] = tmp;
}
“`

* 归并排序

“`
private void merge(int[] nums, int left, int mid, int right) {
int i1=left, i2=mid + 1;
int[] tmpArr = new int[right – left + 1];
int c = 0;
while (i1 <= mid && i2 <= right) { if (nums[i1] <= nums[i2]) { tmpArr[c++] = nums[i1++]; } else { tmpArr[c++] = nums[i2++]; } } while (i1 <= mid) { tmpArr[c++] = nums[i1++]; } while (i2 <= right) { tmpArr[c++] = nums[i2++]; } for (int i=left; i<=right; ++i) { nums[i] = tmpArr[i-left]; } } private void mergeSortRe(int[] nums, int left, int right) { if (left == right) return; int midIndex = (left + right) >> 1;
mergeSortRe(nums, 0, midIndex);
mergeSortRe(nums, midIndex + 1, right);
merge(nums, left, midIndex, right);
}
“`

* 时O(n),空O(1)的排序算法

“`
// kong
// nums里的数属于[0, MAX]
int MAX = 100;
// 空O(1) 为常量
int[] count = new int[100];
System.out.println(count.length);
// 时O(n)
for (int i=0; i= right) return;
int pivotIndex = Partion(nums, left, right);
quickSort(nums, left, pivotIndex – 1);
quickSort(nums, pivotIndex + 1, right);
}
“`

* 堆排序

“`
// 大顶堆
public void bigHeapify(int[] nums, int index, int length) {
int leftI = 2 * index + 1, rightI = 2 * index + 2;
if (leftI < length && nums[leftI] > nums[index]) {
int tmp = nums[index];
nums[index] = nums[leftI];
nums[leftI] = tmp;
bigHeapify(nums, leftI, length);
}
if (rightI < length && nums[rightI] > nums[index]) {
int tmp = nums[index];
nums[index] = nums[rightI];
nums[rightI] = tmp;
bigHeapify(nums, rightI, length);
}
}
public void buildBigHeap(int[] nums) {
for (int i= (nums.length / 2 – 1 ); i >= 0; –i) {
bigHeapify(nums, i, nums.length);
}
}
public void bigHeapSort(int[] nums) {
buildBigHeap(nums);
int length = nums.length – 1;
while (length >= 1) {
int tmp = nums[0];
nums[0] = nums[length];
nums[length] = tmp;
bigHeapify(nums, 0, length);
–length;
}
}
“`

* 比较

| 排序方法 | 平均情况 | 最坏情况 | 辅助空间 | 稳定性 | 适用 |
| — | — | — | — | — | — |
| 冒泡排序 | O(n^2) | O(n^2) | O(1) | 稳定 |
| 简单选择排序 | O(n^2) | O(n^2) | O(1) | 不稳定(把最小元素放到前面是来时,前面的被放到了后面) |
| 直接插入排序 | O(n^2) | O(n^2) | O(1) | 稳定 | 少量元素排序 |
| 归并排序 | O(nlogn) | O(nlogn) | O(n) | 稳定(两部分判断时,应判断 前一部分的<=后一部分的,=不能少) | | 快速排序 | O(nlogn) | O(n^2) [主要是递归造成的栈空间使用] | O(logn)~O(n) | 不稳定(遍历完后,基准和tail交换时,前面的放到了后面来) | | 堆排序 | O(nlogn) | O(nlogn) | O(1) | 不稳定(heapSort里将顶与最后一个元素交换时) ##### 2.4.3 回溯法 * 本质上其实是dfs... ##### 2.4.4 动态规划与贪婪算法 * dp特点 * 求最优点 * 最优解依赖于各子问题最优解 * 问题分为多个子问题后,子问题间还有更小的子问题 * 自上而下的递归思路分析 && 自下而上的循环 * 子问题最优解用数组保存(1/2维) 或 从下往上计算 * 由子问题解计算大问题解 -> 避免重复计算
* 动态规划
* 有多个选择则都尝试接着找出最优

* 动规中,分解子问题时特殊选择一定得到最优解
* -> 可贪婪算法

##### 2.4.5 位运算
* n = (n-1)&n -> n中最右边的1变成0
* ( x1 ^ x2 )中1的个数 = x1变成x2需要改变的bit位数

## 3 高质量的代码
### 3.1 面试官谈代码质量
* 容错处理能力 <- alipay * 特别输入如何处理 * 代码不能只是针对 假想的正常值进行处理 * 要考虑异常情况 * 要考虑资源回收等问题 * 不能该掌握的妹掌握,提醒了还不知道 <-autodesk * 浮点数因为精度问题,不能用==去判断是否相等 * 印象大打折扣 <- intel * x 变量 函数命名妹章法 x * x 解决具体问题找不到最合适的数据结构 x * -> 程序写得太少 不够熟悉
* 程序正确性&&鲁棒性 <- 微软 sdeII * 方面 * 输入参数检查 * 处理错误 异常方式 * 命名方式 * 要求 * 学生:正确性之外的基本能容忍,提示后希望能很快解决 * 有工作经验:不能容忍考虑不周,明显鲁棒性错误 ### 3.2 代码的规范性 * 规范性方面 * 清晰的书写 * 面试大都是手写代码 * 代码量大都<=50行 -> 不用担忧妹时间写
* 清晰的布局
* 手写注意 缩进 对齐之类的
* 合理的命名
* 推荐用 完整的英文单词组合 命名 变量/函数
* BinaryTreeNode * pRoot
* 能看出 变量/函数 的用途

### 3.3 代码的完整性
###### 三方面确保完整性
* 功能测试
* 边界测试
* 输入范围
* 循环/递归 终止
* 负面测试
* 错误输入

###### 三种错误处理的方法
* 返回值
* 很多windows api如此, 0->成功, 否则失败
* 错误发生时设置全区变量
* windows可通过GetLastError
* 调用者忘检查
* 抛出异常
* c#推荐 try catch,c无异常
* 程序执行顺序被打乱,影响性能。
* 和面试官讨论具体如何处理
* 采用返回值处理错误,则说返回值含义 -> 表示考虑到了情况

###### 题
* 实现特定库函数(尤其是处理数值和字符串的函数)的功能是一类常见面试题
* 平时能熟悉库函数&&理解库函数的实现原理
* double除了平时判断时要注意(double = 0 <=> 绝对值很小), double作为返回值时也要注意。
* 除以2都用>>1
* 判断是否为奇数用 (x&0x1 == 1)
* char数组初始化后默认为0而不是’0′
* 考虑代码的可扩展性,如类似sort那样抽离出判断的函数

###### 面经
* 实习很看重基础,可以直接按照面经准备,写到简历上的一定要保证面试官问不住泥。 <- by ywt, 2020-02-13 * 面试官问啥答啥,有不明白就提出疑问,要营造一种技术交流的氛围。 ###### java方法参数里的对象也可能是null, 要判断 ### 3.3 代码的鲁棒性 * 面试时最简单实用的是 检查输入 * 面试官检查代码的方法也是 用测试用例进行测试 * 没有特别要求的话,二叉树的遍历用递归去做,因为代码简洁 ###### 题 * 用一个指针遍历链表不能解决问题时,可尝试用两个,让其中一个指针遍历快一些,或让先走若干步。 ###### 题 * 20没做 * 23没找到题 ## 4 解决面试题的思路 ### 4.1 面试官谈面试思路 * 编码前讲自己的思路是一个考察指标。一开始就编码的除非后面很优秀,否则很容易通不过 --支付宝的 * 想让应聘者讲思路并证明 --百度的 * 想让应聘者讲思路。如果应聘者没想清楚就动手,本身就不是太好。应聘者可举例、画图等方式解释清楚。 --sap的 * 一般最好在开始写之前讲清思路和设计 --淘宝的 * 想让应聘者讲思路,讲错了就让ta证明以便ta自己发现 --微软的 ### 4.2 画图让抽象问题形象化 * 应聘者需要向面试官解释自己的思路,说不清的画图,看图讲解,这显示良好的沟通交流能力 ### 4.3 举例让抽象问题具体化 * 若要求处理二叉树的遍历序列,则可以先找到二叉树的根节点,接着拆分成左右子树接着处理 ### 4.4 分解让复杂问题简单化 * 若求符合一定条件的排列组合,则求出所有再筛选 * 若求多个元素的排列组合,则把这些元素们分成 一个+另外,接着递归 ### 4之总结 * 做算法题时,举例分析写出大部分代码后,再完整带入一个例子进行代码完善而不是立即跑起程序进行测验(显得不好) * 分治、动态规划都是分解问题的思路 ## 5 优化时空效率 ### 5.1 面试官谈效率 * 面试时对时空有要求 --baidu * 内存不是特别大的话,时更重要因改进时间复杂度对算法要求更高 --英伟达 * 普通应用空间换时间,因(用户更关心速度、空间一般够); 嵌入式设备则空间换时间,因空间少 -- 微软 ### 5.2 时间效率 * 首先想到的直观的解法不妨说出 * 提示我们还有更好的办法时,不能轻言放弃,而要积极思考、不同角度思考题,而非遇难题退缩 * 许多公司常问海量输入数据相关问题。这种问题中,内存不能装入所有输入数据,只能渐渐的读入一些,因此类似Partion()之类的操作不适用 * 解法多种、各有优缺点时,要问清 * 数据量多大、能否一次性载入内存 * 是否可修改引用类型所指向的(即输入数组之类的) * 若频繁查找、替换替换 min/max, 应用二叉树(堆、红黑树等实现) * 考虑用map时,若键的范围很小,如键只是字母,那就可以用数组而不是map ### 5.3 时空平衡 ### 5.4 本章小结 ## 6 面试中的各种能力 ### 6.5 发散思维能力 * 不用新变量交换两个变量 ``` a = a ^ b; b = a ^ b; a = a ^ b; ```