数据结构与算法面试考点总结—程序员面试金典

0. 前言

简要总结面试或工作中最常用的数据结构和算法以及一些容易忽略但是重要的细节。内容主要包括:数组与字符串;链表;栈与队列;树与图;位操作;智力题;数学与概率;面向对象设计;递归与动态规划;扩展性与存储型限制;排序与查找;测试;C++重要知识点;Java重要知识点;数据库;线程与锁;后续会针对一些典型题目进行题解说明。

1. 数组与字符串

  • 散列表:一种将键映射为值从而实现快速查找的数据结构。平均查找时间复杂度为O(1),因此在算法题目中可以考虑使用其来优化复杂度。建议仔细看看Java中关于HashMap的源码,了解其内部原理。
  • Arraylist:动态数据,可以自动实现扩容操作。
  • StringBuilder:进行字符串拼接操作,需要进行多次字符串拼接时可以考虑使用它。

2. 链表

  • 创建链表:可以采用头插法和尾插法的方式,一定要注意指针的指向;
  • 删除链表中的结点:单向链表和双向链表的操作,注意指针的变化;注意:空指针;必要时需要更新head和tail指针;
  • 快慢指针使用:可以用来获取中间节点;判断链表是否有环等;

3. 栈与队列

  • 栈:先进后出的数据结构,可以用来解决括号匹配问题;
  • 队列:先进先出的数据结构,有双端队列这种变种,可以在两端进行数据进出;
  • 要能够用两个队列实现栈,用两个栈实现队列;具体实现思路自己可以手动在纸上验算;

4. 树与图

  • 树需要注意的问题:是二叉树还是二叉排序树(一般对于二叉排序树里面的数据不重复,左子树小于根节点,右子树大于根节点);平衡与不平衡(AVL是严格平衡,B树是不严格平衡);是否是完全二叉树(叶子节点只能出现在最底层)或者满二叉树;
  • 二叉树的遍历,要能够分别使用递归和非递归(栈)的方式进行二叉树的前序遍历、中序遍历和后序遍历;
  • 了解平衡二叉树和红黑树以及B树的使用场景、特点和区别;
  • 单词查找树(trie树)用于前缀问题处理;
  • 图的遍历:DFS和BFS,一定要掌握。
// DFS
void dfs(Node root){
	if(root == null){
		return;
	}
	// 处理当前节点
	visit(root);
	// 标记当前节点已经遍历过
	//此处需要注意,如果需要回溯,则处理完当前节点后需要还原为false
	root.visited = true;
	// 对每个相邻的元素都进行查找
	for(Node n : root.adjacent){
		if(n.visited == false){
			dfs(n);
		}
	}
}

// BFS
void bfs(Node root){
	Queue queue = new Queue();
	root.visited = true;
	visit(root);
	//将根节点加入队列
	queue.enqueue(root);
	while(!queue.isEmpty){
		//取队列头元素
		Node r = queue.dequeue();
		for(Node n : r.adjacent){
			if(n.visited == false){
				visit(n);
				n.visited = true;
				// 加入队列
				queue.enqueue(n);
			}
		}
	}
}

5. 位操作

  • 要明白位操作的一些基本原理,与或非分别是如何进行处理的;
  • 一些技巧:去掉最后一个1(n&(n-1));获取最后一个1(n&(-n))
  • 习惯使用掩码来进行解题;

6. 智力题

  • 大声说出你的思路;
  • 总结规律:例如用两条绳子进行15分钟计时问题;
  • 略作变通:九球称重问题,两次就可以找到结果;
  • 触类旁通:综合利用各种方式进行推演;

7. 数学与概率

  • 素性检查和埃拉托斯特尼筛法进行素数序列生成;
  • 简单的概率模型要知道,互斥、独立等;
  • 注意:小心float和double的精度是不同的;不要假设一个值是int型的;不要假设多个事件是独立,除非可以严格证明出来;

8.面向对象设计

  • 现在的程序语言大多都是面向对象进行设计的,这样做可以保证高内聚低耦合,如C++,Java;
  • 如何解答面向对象设计问题:处理不确定的地方(题目中存在模糊的地方要确认);定义核心对象;分析对象之间的关系,有的可能是包含关系,有的可能是继承关系;研究对象的动作;
  • 设计模式:要理解一些典型的设计模式,并说明其在具体项目中的应用,例如:单例模式;工厂模式;

9. 递归和动态规划

  • 递归:自下而上递归和自上而下递归;
  • 动态规划:将递归的结果缓存起来,在实际做题的时候尽量少用递归,可以用迭代的方式进行替换,递归的开销很大;

10.扩展性与存储限制

  • 循序渐进法:大胆假设:假设一台计算机就可以装下所有数据;切合实际:如何拆分数据;解决问题;
  • 这里面涉及到大数据处理的一些思想,处理的基本思路是先对大数据按照一定规则进行切分,然后分别处理,最后再进行结果汇总;

11.排序与查找

  • 这部分应该是考察的重点,需要能够根据具体的场景选择合适的算法进行排序。需要考虑到时间复杂度,空间复杂度,稳定性以及是否可以改变原始数组的顺序等;
  • 冒泡排序:比较相邻的元素,如果第一个比第二个大就交换。两轮循环,外层控制趟数内层控制每次比较操作。最好O(N),最差O(N2),平均O(N2),稳定排序
  • 选择排序:每次选择最大或者最小的元素放在排序序列的起始位置,然后从剩余元素中继续寻找最大最小值。任何数据都是O(N2),适合小规模数据。复杂度均为O(N2),不稳定
  • 插入排序:将序列分为有序和无序两部分,每次选择一个元素放入有序组中。最佳数组有序O(N),最坏和平均为O(N^2)
  • 希尔排序:将无序记录分割成若干子序列分别进行直接插入排序:选取增量序列,按照增量序列进行K此排序,最后一个增量为1。任何情况下都是O(NlogN)
  • 归并排序:将序列分成两个序列,分别进行归并排序,然后再合并。稳定排序,任何情况都是O(NlogN)
  • 快速排序:通过一趟排序将待排序记录分隔成两部分,其中一部分记录的关键字比另一部分的关键字小。
    实现:1)双向指针设置两个哨兵,一个从左向右找,一个从右向左找;2)挖坑法,一个哨兵,每次只维护小于基准值的序列;3)前后指针发,pre,cur分别从左至右和从右至左进行搜索,base是基准值。不稳定,最坏O(n^2),其他O(nlogn)
// 快速排序是考察的重点,其难点在于划分函数的书写上,通常有三种写法
// 第一种是挖坑法,是算法书籍中讲解的最基本的方式
// 第二种是左右指针法
// 第三种是两个指针都从前进行搜索,下面展示第二种方式
// 定义快排函数
void quickSort(int[] nums, int left, int right){
	int index = partition(nums, left, right);
	if(left < index - 1){
		quickSort(nums, left, index - 1);
	}
	if(index < right){
		quickSort(nums, index, right);
	}
}

//划分函数
int partition(int[] nums, int left, int right){
	//选择一个基准点,基准点选择也有很多方式例如直接选最后或最前元素
	//或者选择中间元素,或者三数取中,或者采用随机数选择
	int pivot = nums[right];	
	while(left <= right){
		while(nums[left] < pivot) left++;
		while(nums[right] > pivot) right--;
		if(left <= right){
			swap(nums, left, right);
			left ++;
			right --;
		}
	}
	return left;
}
  • 堆排序:构建一个大顶堆,然后每次堆顶元素和最后一个元素交换,调整堆。任何情况都是nlogn,不稳定的。
  • 计数排序:找出待排序数组中的最大和最小值,统计数组中每个值为i的元素出现的个数,存储记录数组中,不断累加反向填充目标数组。任何情况都是O(n+k)
  • 桶排序:将数据分到有限数量的桶里,每个桶再分别进行排序。平均O(n+k)
  • 基数排序:从低位到高位进行排序,按照该位上的数字进行排序。O(n*k)

12.测试

  • 面试考察点:全局观;懂整合;会组织;可操作;
  • 测试现实生活中的事物:使用是哪些人,做什么用?有哪些用例?有哪些使用限制?压力与失效情况下的状态如何?如何执行测试?
  • 测试一套软件:手动测试与自动化测试;黑河测试与白盒测试;
  • 测试一个函数:(1)定义测试用例:正常情况;极端情况;空指针和非法输入;奇怪输入;(2)定义预期结果;(3)编写测试代码;
  • 调试与故障排除:理清状况;分解问题;创建特定可控的测试用例;

13 C++

  • 类和继承;C++中所有数据成员和方法默认为私有;
  • 构造函数和析构函数(删除时执行清理工作);
  • 虚函数
  • 默认值
  • 操作符重载
  • 指针和引用
  • 模板

14. Java

  • 如何处理类的问题
  • 重载与重写
  • 集合框架

15. 数据库

  • SQL语法及各类变体
  • 非规范化和规范化数据库
  • SQL语句
  • 小型数据库设计
  • 大型数据库设计

16. 线程和锁

  • 创建线程的两种方式,一是实现runnable接口,另外一种是继承Thread;
  • 可以使用synchronized和Lock进行同步,后者更细粒度;
  • 死锁的发生与预防:条件:互斥,持有并保持,没有抢占,循环等待; 破坏前面四个条件,进行死锁预防;

你可能感兴趣的:(面试思考)