第四章
哈希表
哈希表的简单介绍
1)哈希表在使用层面上可以理解为一种集合结构
2)如果只有key,没有伴随数据value,可以使用HashSet
结构(C++中叫UnOrderedSet)
3)如果既有key,又有伴随数据value,可以使用HashMap
结构(C++中叫UnOrderedMap)
4)有无伴随数据,是HashMap和HashSet唯一的区别,底层的实际结构是一回事
5)使用哈希表增(put)、删(remove)、改(put)和查(get)的操作,可以认为时间复杂度为O(1),但是常数时间比较大
6)放入哈希表的东西,如果是基础类型,内部按值传递
,内存占用就是这个东西的大小
7)放入哈希表的东西,如果不是基础类型,内部按引用传递
,内存占用是这个东西内存地址的大小有关哈希表的原理,
将在提升班“与哈希函数有关的数据结构”一章中讲叙原理
有序表
1)有序表在使用层面上可以理解为一种集合结构
2)如果只有key,没有伴随数据value,可以使用TreeSet
结构(C++中叫OrderedSet)
3)如果既有key,又有伴随数据value,可以使用TreeMap
结构(C++中叫OrderedMap)
4)有无伴随数据,是TreeSet和TreeMap唯一的区别,底层的实际结构是一回事
5)有序表和哈希表的区别是,有序表把key按照顺序组织起来,而哈希表完全不组织
5)红黑树、AVL树、size-balance-tree和跳表等都属于有序表结构,只是底层具体实现不同
6)放入有序表的东西,如果是基础类型,内部按值传递,内存占用
就是这个东西的大小
7)放入有序表的东西,如果不是基础类型,必须提供比较器
,内部按引用传递,内存占用是这个东西内存地址的大小
8)不管是什么底层具体实现,只要是有序表,都有以下固定的基本功能和固定的时间复杂度
有序表的固定操作
- void put(K key, V value):将一个(key, val ue)记录加入到表中,或者将key的记录更新成 value.
- V get(K key):根据给定的key,查询 value并返回。
- void remove(K key):移除key的记录
- boolean containskey(K key):询问是否有关于key的记录。
- K firstkey ():返回所有键值的排序结果中,最左(最小)的那个。
- K laskey ():返回所有键值的排序结果中,最右(最大)的那个。
- K floorKey(K key):如果表中存入过key,返回key;否则返回所有键值的排序结果中,key的前一个
- K ceilingKey(K key):如果表中存入过key,返回key;否则返回所有键值的排序结果中,
key的后一个。
以上所有操作时间复杂度都是0(logN)
,N为有序表含有的记录数
有关有序表的原理,将在提升班“有序表详解”一章中讲叙原理
注: 有序表和哈希表在,大量刷题过程中,不需要知道原理。需要知道原理的题的都是难题,中等难度都不会涉及到内部原理。
链表
换头返回node,不换头返回void
题目1
反转单链表,双链表。
[分析]:用一个cur节点分析,想办法调整next 或 pre 指向。有点像交换值,记得让cur后移直至末尾。
题目2
Leetcode.234 是否是回文链表。
笔试:
1)栈
2)快慢指针(把握边界, leetcode题解有注释),找中点,只把右边放入栈中注意比较即可。
面试:
1)快慢指针找中心
2)反转右半部分,和左边逐一比较
3)恢复成原链表
题目3
给定key,链表小于在左,等于在中,大于在右。
笔试:
[分析] 用数组储存,partition一下,再复原回去原链表
面试:
[分析] 定义六个指针。注意,如果没有小于部分,大于部分,等于部分时的讨论。
题目4
笔试:
[分析]:用hashmap存储原节点
和其克隆节点
。把克隆节点串起来就行了;
面试:
[分析]:
1)先把克隆节点都连在原节点后面串起来
2)把克隆节点rand指针指向其原节点rand指针的下一个
3)分离链表
题目5 hard
证明单链表有环
1)哈希set
2)快慢指针
1.快2step, 慢1step. 指针相遇后,
2.快指针回到原点,然后每人都1步1步挪,相遇就是第一次入环节点。(有环返回入环loop节点,无环返回空)
由于单链表的结构,两个单链表相交必定兵合一处,"Y"字形状
由于单链表的结构,要么两个都有环,要么都没环,不可能是一个有环一个无环
1)都无环
让长的那一部分,截到短一样长,一起走,相等了就是相遇节点, 返回相遇节点loop
让6的部分和4对称,一起向下走,相等就相交
public static Node noLoop(Node head1, Node head2) {
if (head1 == null || head2 == null) {
return null;
}
Node cur1 = head1;
Node cur2 = head2;
int n = 0; //只用一个变量记录,两链表长度之差
while (cur1.next != null) {
n++;
cur1 = cur1.next;
}
while (cur2.next != null) {
n--;
cur2 = cur2.next;
}
if (cur1 != cur2) {
return null;
}
//n: 链表1长度减去链表2长度的值
cur1 = n > 0 ? head1 : head2; //谁长谁的头变cur1
cur2 = cur1 == head1 ? head2 : head1; //谁短谁的头变cur2
n = Math.abs(n);
while (n != 0) { //先走到一样长
n--;
cur1 = cur1.next;
}
while (cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
2)都有环
1) 不相交,让loop1往下走直到回到loop1(转一圈), 遇到 loop2就是<3>,否则就是<1>。
2) loop1==loop2,让loop1当作无环情况中的end尾节点进行上述操作就可。
3) 返回loop2或者loop1
public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
Node cur1 = null;
Node cur2 = null;
if (loop1 == loop2) { //case2, 参考上述无环代码
cur1 = head1;
cur2 = head2;
int n = 0;
while (cur1 != loop1) {
n++;
cur1 = cur1.next;
}
while (cur2 != loop2) {
n--;
cur2 = cur2.next;
}
cur1 = n > 0 ? head1 : head2;
cur2 = cur1 == head1 ? head2 : head1;
n = Math.abs(n);
while (n != 0) {
n--;
cur1 = cur1.next;
}
while (cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
else {//情况1,3
cur1 = loop1.next; //case3
while (cur1 != loop1) {// 转一圈回来
if (cur1 == loop2) {// 看环里有没有loop2,有就是case3,
return loop1;
}
cur1 = cur1.next;
}
return null;// case1
}
}
第五章
递归序
1) 第一次到
函数体 , -->>先序遍历:
2) 第二次回到
函数体 , -->>中序遍历:
3) 第三次回到
函数体 , ->>后序遍历:
非递归深度遍历(栈)
先序遍历
打印顺序 : 头 -> 左 -> 右
图解 :
先弹栈,先右入后左入,(则弹出为 先左后右==与原本顺序一致)
后序遍历
需要辅助栈,为的是逆序放到辅助栈里去
注: 入栈左右,出栈右左
中序遍历
左边一次性全入,然后处理cur节点,再把右树 重复操作
就是先左再头,然后右树上先左再头
广度优先遍历(队列)
队列,先弹出,放左再放右
题目1
求一个二叉树的最大宽度
- HashMap
最后一个走完,队空,因此退出循环,但是没有进行if else里的max ,curLevel的更新。
while外补上max = Math.max(max, curLevelNodes)
2.不用哈希表
NodeCurEnd, NodeNextEnd, CurLevel,max
class Solution {
public int widthOfBinaryTree(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
TreeNode curLevelEnd = root;//当前层的最后一个
TreeNode nextLevelEnd = null;//下一层的最后一个
int curLevelNodes = 0;
int max = Integer.MIN_VALUE;//返回值
while( !queue.isEmpty() ){
root = queue.poll();//pop
System.out.print("\t" + root.val);
if(root.left!= null){//一直更新下一层的值
queue.add(root.left);
nextLevelEnd = root.left;
}
if(root.right!= null){
queue.add(root.right);
nextLevelEnd = root.right;
}
if(root != curLevelEnd){//不到当前最后一个节点
curLevelNodes++;//当前层的节点数++
}
else{
curLevelEnd = nextLevelEnd;//进入下一层,末尾节点赋值
nextLevelEnd = null;//更新下一层的末尾节点,同时更新max
max = max > curLevelNodes ? max : curLevelNodes+1;
curLevelNodes = 0;//进入下一层后,此层节点数reset
}
}
return max;
}
}
特殊二叉树
搜索二叉树 BST
中序遍历,升序(没看懂)
完全二叉树
遇到第一个带圈的节点后,后面的节点都得是叶子节点。
满二叉树
最大深度d和节点个数n满足 n=2^d-1
平衡二叉树(总结二叉树的套路)
(左右子树高度差不超过1)
套路: 左树要信息(bool isBalanced,int hight),右树要信息,3个条件取交集。
套路
套路(解决一切树形DP,动态规划(二叉树面试最难的))
用于: 问左树要信息,右树要信息把问题解了
求树里的中位数就无法此套路解
面试考核的题,旨在优化,所以这很好用。
1》 BST
(左数 BST
&& 右数 BST
&& 左max < cur.val < 右min)
!!!注意:递归套路,返回的东西应该是一样的,因此左右数都要返回三个信息(bool isBST,int max ,int min)
2》 满二叉树
(int height, int nodes)
nodes==(1<<height)-1 //- 的优先级比<<高
题目1
o1, o2最低公共祖先
1》 HashMap(self, father)
先把所有节点录入HashMap
然后用一个HashSet存储 o1的父节点路径,至到根节点
用o2往祖先索引,直至有和 o1的父节点路径(HashSet) 重合的节点
2》 分析:
1。 o1直接就是o2的祖先,或者o2直接就是o1的祖先。
2。o1和o2,往回有交汇
public static Node lowestAncestor(Node head, Node o1, Node o2) {
if (head == null || head == o1 || head == o2) {
return head;
}
Node left = lowestAncestor(head.left, o1, o2);//左下
Node right = lowestAncestor(head.right, o1, o2);//右下
//左,右都有返回值,即2。返回当前的
if (left != null && right != null) {
return head;
}
return left != null ? left : right;//左右两数不都有返回值,返回不空的
}
题目2 后继节点
比普通二叉树多了一个father属性,head的father是空
中序遍历结果的后一个,最后一个节点后继为null
有右树,=》找到右数最左下的那个
无右数,=》往回,至到成为左孩子,返回其父节点,(最右的那个不会满足此条件,但是head的father刚好为空,不影响)
题目3 序列化和反序列化
遍历树,
//先序遍历的序列化 和 反序列化
//序列化
public static String serialByPre(Node head) {
if (head == null) {//#--空,!--分割,value--值
return "#!";
}
String res = head.value + "!";
res += serialByPre(head.left);
res += serialByPre(head.right);
return res;
}
//反序列化。 分割
public static Node reconByPreString(String preStr) {
String[] values = preStr.split("!");
Queue<String> queue = new LinkedList<String>();
for (int i = 0; i != values.length; i++) {
/*offer,add; poll,remove; peek,element
前者返回空,后者异常
*/
queue.offer(values[i]);
}
return reconPreOrder(queue);
}
//反序列化
public static Node reconPreOrder(Queue<String> queue) {
String value = queue.poll();
if (value.equals("#")) {
return null;
}
Node head = new Node(Integer.valueOf(value));
head.left = reconPreOrder(queue);
head.right = reconPreOrder(queue);
return head;
}
题目4 折纸问题
打印折痕方向 =》 凹 ,凸
中序遍历即可
//space