读书笔记| 程序员面试金典

github: https://github.com/careercup/CtCI-6th-Edition
my-coding: https://git.coding.net/daydaygo/CTCI.git
date: 2016-03-22 19:45

里面有一张面试流程图, 只能用 吓人专业 来形容了: 1年以上的面试准备 + 扩充人脉 + 自己的项目展示 + 找一个友人模拟面试
请准备纸质简历: 我之前的做法是展示自己的blog, 当然是有 的成分在里面, 不过纸质简历方便面试官写写画画.

十大常见错误

  1. 只在计算机上面coding(我感觉这项没有错, 好的工具更重要)
  2. 不做行为面试题
  3. 不模拟面试
  4. 试图死记硬背答案
  5. 不大声说出自己的解题思路
  6. 过于仓促
  7. 代码不够严谨
  8. 不做测试
  9. 修正错误漫不经心
  10. 轻言放弃

受期待的面试者

  1. 对技术充满热情
  2. 关注扩展性&存储限制(互联网行业)
  3. 谈论产品: 喜欢什么? 有什么值得改进?
  4. 热衷创造
  5. 系统设计(虽然我总是听到 系统架构 不是一天就练成的)

简历

  1. 长度适中, 亮点优先级
  2. 工作经历: 要点 + 亮点
  3. 项目经历
  4. 编程语言 + 工具

行测

项目相关(浓缩成关键字): 1. 最难的部分; 2. 有什么收获; 3. 最有意思的部分; 4. 最难解的bug; 5. 最享受的过程; 6. 与团队成员的冲突
应对: 1. 力求具体, 切忌自大; 2. 省略细枝末节; 3. 条理清晰(主题现行/SAR)

  1. 你有哪些缺点: 正视不足, 重点突出怎么克服的
  2. 项目中最难的问题: 不要泛泛而谈, 这样会显得这个项目没有什么难度
  3. 问面试官的问题: 1. 真实的问题; 2. 有见地的问题; 3. 富有激情

技术面试

需要了解的知识
常见面试问题
关于 2 的问题

注重 数据结构 + 算法

解决面试题:

  1. 大部分时候都不能马上解决问题
  2. 提问, 消除歧义
  3. 设计算法
  4. 伪码
  5. 使用数据结构, 保持结构清晰
  6. 测试: 一般 / 极端情况 / 用户错误

算法题解法:

  1. 举例
  2. 模式匹配(找相似的例子)
  3. 简化推广法(打 单词 当作 单个字符处理, hash 的原型)
  4. 简单构造(递归)
  5. 数据结构头脑风暴(把能想到的数据结构都过一遍试试)

好代码: 正确 / 简洁 / 高效 / 易读 / 可维护

  1. 多用数据结构
  2. 适量重用代码
  3. 模块化
  4. 灵活/健壮
  5. 错误检查: 仔细验证输入 / 边界

其他

薪资考量: 工资 + 奖励 + 年终奖(时薪会是一个比较好的考量)
职业发展: 1. 增加履历; 2. 增长知识; 3. 升迁(技术/管理); 4. 是否处于上升期
幸福指数: 1. 产品; 2. 团队; 3. 工作时长; 4. 企业文化
录用谈判: 1. 理直气壮; 2. 手头还有其他选择; 3. 提出具体要价; 4. 比预期稍高; 5. 不要只盯着薪水(待遇是多方面, 薪水是其中的大头而已); 6. 选择合适的方法(电话 / 邮件)
入职须知: 1. 制定时间表; 2. 人际网络; 3. 寻求经理帮助

面试题

array & string

  1. 散列表 Hashtable / HashMap: 数组 -> 链表 -> 二叉查找树

  2. 动态数组 ArrayList: 存满时动态扩容(一般为2倍)

  3. StringBuffer: 处理字符串拼接, 申请一个可以容纳所有字符串的数组

  4. 判断字符串的所有字符是否相同: 确定编码( ascll 还是 unicode); ascll -> 256 个字符; 26个字母 -> int 变量的每位来存储

  5. c / c++ void reverse(char* str)

  6. 判断2个字符串重新排列后能否相等: 使用辅助数组计数

  7. 替换字符串中所有空格为 %20: 遍历出空格的数量, 算出新字符串的长度, 从字符串尾部开始操作

  8. aabcccccaaa -> a2b1c5a3 实现字符串压缩:

i = 0; j =1;
str2 = [];
while(str[i]){
    while(str[i] == str[i+j]) j++;
    str2[] = str[i];
    str2[] = j;
    j = 1;
}
return strlen(str) > strlen(str2) ? str2 : str;
  1. N x N 的矩阵旋转90度: 顺时针 a[i][j] -> a[j][n-i-1]; 逆时针: a[i][j] -> a[n-j-1][i]
  2. M x N 的矩阵中, 含0的行和列全部清零: 遍历, 使用2个辅助数组, 存储 行/列 中0的位置
  3. 调用一次 subString() 判断 s2 是不是 s1 旋转( s-> xy -> yx )得到: subString(s2, s1s1)

链表(LinkedList)

  1. 注意是单向链表还是双向链表

  2. 创建链表: appendToTail(), 在结尾添加节点

  3. 删除单向链表中的节点: prev.next = n.next 即可, 注意 检查空指针 + 表头/表尾 指针

  4. 快行指针: 同时2个指针遍历链表, 其中一个步速为2倍或固定长度, 如a1->a2..an->b1->b2..bn => a1->b1..an->bn

  5. 递归, 但是至少要 O(n)空间, 所有的递归都可以转为迭代

  6. 移除未排序链表中的重复节点: 使用 Hashtable, 遍历一次; 使用2个指针, 相当于2个for循环

  7. [算法]单向链表中倒数第k个节点: 2个指针, 一个先行k步(k大于链表长度的情况也不用担心了), 之后2个指针一起走

  8. 删除一个节点(你只能访问这个节点): 使用下个节点替换当前节点即可

  9. 定值x将链表分为2部分: 使用2个链表来存储, 然后合并

  10. 链表实现加法, 和数组实现加法类似

  11. 有环链表: slow 步长为1, fast 步长为2; 当 slow 步行 k 到环起点, 环 size 为 m, 此时 fast 在 m-k%m 处; 所以 fast/slow 在 m-k%m 处相遇; 再取一个 指针从 head 开始, 就可以在 环起点 相遇

  12. 检查回文: 反转链表, 只需要比较前半部分; 栈; 递归, 每次 length-1, 终止条件 length<2

栈和队列

栈 Stack
队列 Queue

  1. 一个数组实现3个栈: 固定分配, 只允许每个栈使用固定的数组空间; 不固定分配, 需要考虑数组回绕
  2. 栈中添加一个 min(): 节点增加 min 值; 使用辅助栈存储 min
  3. 栈满时新建栈 + popAt(int index): 使用数组栈来维护栈的数量, pop() / push() / empty() 等方法都要考虑是否需要维护这个 数组栈
  4. 用栈实现汉诺塔: An = 2*An-1 + 1
  5. 2个栈实现一个队列
  6. 升序栈: 一次弹出原栈的元素, 和新栈进行比较, 弹出新栈的元素到原栈, 直到找到元素适合的位置
  7. 维护 queueDog + queueCat

树&图

潜在问题: 二叉树&二叉查找树; 平衡&不平衡(需要衡量算法的 最好/最坏 情况); 完满&完整
二叉树遍历: 前/中/后/层次
树的平衡: 红黑树 / 平衡二叉树
单词查找数(trie): 每个节点存储字符, 每条路径表示一个单词
图的遍历: DFS / BFS

  1. 判断是否为平衡二叉树: 从根节点开始递归判断; 保存每个树的高度

  2. 判断有向图2点之间是否存在路径: dfs / bfs 都可

  3. 将有序数组构建成高度最小的二叉查找数: 依次使用数组中间节点作为根节点, 构建左右子树
    无序数组构建二叉查找数: 依次插入元素到现有二叉树, O(N*logN)

// 构建子树, 返回子树根节点
TreeNode createMinimalBST(int arr[], int start, int end){
    if(end < start) return NULL;

    int mid = (start+end)>>1;
    TreeNode n = new TreeNode(arr[mid]);
    n.left = createMinimalBST(arr, start, mid-1);
    n.right = createMinimalBST(arr, mid+1, end);
    return n;
}
// 使用
createMinimalBST(arr,0,n-1);
  1. 给定二叉树, 创建某一深度上的所有节点的链表
    方法一: 前序遍历, 遍历过程中传递当前层数, 并把节点加入相应链表中
    方法二: 层次遍历

  2. 检查一个树是否为 二叉查找树
    二叉查找树: 根节点 >= 所有左子树节点, <= 所有右子树节点
    最大最小值: 中序遍历结果的时候, 不断更新最大最小值, 如果不符合条件直接停止遍历

  3. 找出指定节点的中序后继
    问题很简单, 但是边界条件很多: null; 右子树为null; 右子树的最左边节点

  4. 找出二叉树中某2个结点的第一个公共祖先
    递归访问当前节点的子树, 看看是否包含 2 个结点

  5. 判断 T2(数据量比较小) 是不是 T1 的子树(数据很大)
    遍历 T1, 当出现节点和 T2 根节点相同时, 进行比较

  6. 树中结点和为固定值的路径

位操作

掩码清零
小数的十进制和二进制: 依次乘 2 然后和 1 对比; 依次减去 1/2, 1/4, 1/8
n&(n-1): 清除 n 中最右边的 1
n&(n-1)==0: n 是 2^k
a^b

  1. 一个正整数二进制表示中 1 的个数相同且大小最接近的 2 个数
  2. 交换奇数和偶数位: 掩码 -> 光 奇数位/偶数位 -> 移位 + 相加
  3. 通过二进制找出 0-n 中缺少的一个数

智力题

  1. 找出20瓶药中份量不同的一瓶: 一瓶有很多粒药
  2. 88 的棋盘切去2个对角, 是否还能使用 12 的方块覆盖(否, 使用黑白相间证明)
  3. 5升水壶+3升水壶 -> 4升水? 数学定理: 2个数字互质, 则可以获得这 1-2个数和之间的任意数
  4. 每个人只能看到其他人的状态, 状态a的人必须离开, 如果有 n 个人状态 a, 第几天离开(n 个人 -> n 天, 使用数学推导)
  5. 2个鸡蛋, 100层楼, 找出鸡蛋扔下不会碎掉的层数, 并使最差情况下的次数最少(围绕 最差情况, 平衡2个鸡蛋扔的负载)
  6. 100个关上的柜子, 第i次改变第n*i柜子的状态, 还有几个柜子开着(数 -> 因子 -> 平方和)

数学&概率

  1. 比较1投命中 & 3投>=2中 的概率
  2. n边行n个蚂蚁相撞的概率(相撞 = 1 - 不相撞)
  3. 2条线是否会相交(线 = 线段 + 射线 + 直线; 二元一次方程)
  4. 加法 实现 减法/乘法/除法
  5. 一条直线评分2个正方形(过正方形中心的直线就可以平分正方形)
  6. 找出平面经过点数最多的直线(使用 斜率+截距 来表示一条直线, 遍历所有点, 返回出现次数最多的直线)
  7. 找出只有 3,5,7 因子的数中第k个数
    维护3个队列, 每次判断新插入数, 然后放入相应的队列

对象对象设计

  1. 标准52张扑克牌

  2. 呼叫中心: call + 三级处理 员工/主管/经理

  3. 音乐点唱机: 点唱机(是否收费) + CD(可能是不同媒介) + 歌曲 + 艺术家 + 播放列表 + 显示屏

  4. 停车场: 不同车 + 不同停车位(多层/多排/可停不同车)

  5. 在线图书阅读系统: one time one user one book + 会员资格和书籍延期 + 图书搜索 + 书籍阅读

  6. 拼图程序: 拼图 = 边 + 3种状态 + 位置(绝对/相对); 判断相邻拼图是否可以拼在一起

  7. 聊天服务器: 在线离线状态 + 请求(发送/接受/拒绝) + 更新状态 + 私聊/群聊 + 发送消息
    确定用户在线: 定时询问; 冲突信息; 负载(多台机器); DOS 攻击

  8. 黑白棋设计

  9. 内存文件系统

  10. 散列表, 使用 链表处理冲突

递归&动态规划

位操作: 使用开发中有遇到, 使用 php 做 socket client, 和 c++ server 通信, 包括 编码 和 数据类型
测试: 测试转开发, 温水煮青蛙; 准备核心测试问题
PM: 处理模糊问题; 以客户为中心(态度 + 技术); 多层次交流; 热爱技术; 团队/领能力
技术主管/项目经理: 团队/领能力; 把握轻重缓急; 沟通; '把事情做好'的能力
创业公司: 个性契合; 技术能力(快速上手); 以往经验
经验积累: 多承担编程工作; 善用闲暇时光
人脉:广度 + 深度

  1. 面试非易事, 姿态很重要.
    之前感觉自己能力很足, 去面试的时候把面试官给呛了, 结果可想而知. 能力只是是否能够胜任的前提条件, 最重要的还是与人相处与人合作与人沟通的能力.

  2. 完美的解决问题并不是关键, 关键是要比其他面试者回答得更好.

你可能感兴趣的:(读书笔记| 程序员面试金典)