哈希表
基本概念不做阐述
增删改查的时间复杂度都是常数级别(但是比较大的常数)
- java中,如果哈希表的key是基本数据类型,哈希表内部按真实值传递
- java中,如果哈希表的key不是基本数据类型,哈希表内部按地址值传递(8字节)
有序表
- 有序表在使用层面上可以理解为一种集合结构
- 如果只有key,没有伴随数据value,可以使用TreeSet结构
- 如果既有key,又有伴随数据value,可以使用TreeMap结构
- 有无伴随数据,是TreeSet和TreeMap唯一的区别,底层实现是一回事
- 有序表和哈希表的区别是,有序表把key按照顺序组织起来,而哈希表完全不组织
- 红黑树、AVL树、size-balance-tree和跳表等都属于有序表结构,只是底层具体实现不同
- 放入有序表的东西,如果是基础类型,内部按值传递,内存占用就是这个东西的大小
- 放入有序表的东西,如果不是基础类型,必须提供比较器,且内部按地址值传递,内存占用的就是这个东西的内存地址大小
- 不管底层用什么实现,只要是有序表,都有固定的基本功能和固定的时间复杂度
链表
面试时链表解题方法论
对于笔试,不用太在乎空间复杂度,一切为了时间复杂度
对于面试,时间复杂度依然放在第一位,但是一定要找到空间最省的方法
重要技巧:
额外数据结构记录(哈希表)
快慢指针
题目
反转单向和双向链表
题目:分别实现反转单向链表和反转双向链表的函数
要求:如果链表长度为N,时间复杂度要求为O(N),额外空间复杂度为O(1)
打印两个有序链表的公共部分
题目:给定两个有序链表的头指针head1和head2,打印两个链表的公共部分
要求:如果两个链表的长度之和为N,时间复杂度要求为O(N),额外空间复杂度为O(1)
判断一个链表是否是回文结构
题目:给定一个单链表的头结点head,请判断该链表是否为回文结构
例子:1->2->1,返回true;1->2->2->1,返回true;15->6->15,返回true;1->2->3,返回false;
要求:如果链表长度为N,时间复杂度要求为O(N),额外空间复杂度为O(1)
方法1:笔试利用栈来做,遍历一遍链表,把链表元素入栈;再遍历一遍链表,栈弹出元素并与原链表当前元素对比,不一样就返回false
方法2:利用快慢指针,快指针一次走两步,慢指针一次走一步,当快指针到最后的时候,慢指针刚好到中间,这时候利用栈将链表后半部分的元素入栈,再弹出比较,从而节省了一半空间
方法3:利用快慢指针,快指针到最后的时候,慢指针刚好到中点,然后利用慢指针,将链表后半部分逆序,即1->2变成2->1,再用两个指针,一个从头开始遍历,一个从尾开始遍历,有不同则返回false,否则结束后返回true,最后记得恢复原数组
将单向链表按某值划分成左边小,中间相等,右边大的形式
题目:给定一个单链表的头结点head,节点的值类型是整型,再给定一个整数pivot,实现一个调整链表的函数,将链表调整为左部分都是值小于pivot的节点,中间部分都是值等于pivot的节点,右部分都是值大于pivot的节点
进阶:在实现原问题功能的基础上,增加如下要求
要求:调整后所有小于pivot的节点之间的相对顺序和调整前一样
要求:调整后所有等于pivot的节点之间的相对顺序和调整前一样
要求:调整后所有大于pivot的节点之间的相对顺序和调整前一样
要求:时间复杂度达到O(N),额外空间复杂度为O(1)
方法1:利用node数组,将链表全部节点方法数组中,然后进行partation,但是好像保持不了相对顺序
方法2:利用6个变量,分别指向小于区的头,小于区的尾,等于区的头,等于区的尾,大于区的头,大于区的尾;通过遍历,可以将所有元素对应哪个区域找到,并更新各个变量的值,最后再将小于区的尾和等于区的头连接,等于区的尾和大于区的头连接,返回小于区的头(一定要讨论是否有小于区,等于区,大于区)
复制含有随机指针节点的链表
题目:一种特殊的单链表节点类描述如下:
rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向null。给定一个由node节点类型组成的无环单链表的头结点head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头结点
要求:时间复杂度O(N),额外空间复杂度O(1)
两个单链表相交的一系列问题
题目:给定两个可能有环也可能无环的单链表,头结点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的第一个节点。如果不想交,返回null
要求:如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度为O(1)
步骤一:先判断一个链表是否有环,如果有,返回头结点
方法1:使用hash表遍历
方法2:利用快慢指针
public static Node getLoopNode(Node head) {
if (head == null || head.next == null || head.next.next == null) {
return null;
}
Node n1 = head.next; // n1 -> slow
Node n2 = head.next.next; // n2 -> fast
while (n1 != n2) {
if (n2.next == null || n2.next.next == null) {
return null;//无环
}
n2 = n2.next.next;
n1 = n1.next;
}
n2 = head; // n2 -> walk again from head
while (n1 != n2) {
n1 = n1.next;
n2 = n2.next;
}
return n1;
}
步骤二:讨论head1和head2在不同情况下的处理方法
情况1:loop1 == null && loop2 == null
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) {//当cur1变成最后一个就出来了
n++;//cur的总数
cur1 = cur1.next;
}
while (cur2.next != null) {
n--;//得到差值
cur2 = cur2.next;
}
if (cur1 != cur2) {//如果两个无环链表的最后一个节点不相等,那么不相交
return null;
}
cur1 = n > 0 ? head1 : head2;//谁长谁的head变成cur1
cur2 = cur1 == head1 ? head2 : head1;//谁短谁的head变成cur2
n = Math.abs(n);
while (n != 0) {//先走两个链表的差值
n--;
cur1 = cur1.next;
}
while (cur1 != cur2) {//然后一起走,第一次相交的时候就是那个点
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
情况2:loop1,loop2中有null也有非null,则一定不相交
情况3:loop1,loop2都非空,则分三种情况
public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
Node cur1 = null;
Node cur2 = null;
if (loop1 == loop2) {
cur1 = head1;
cur2 = head2;
int n = 0;
while (cur1 != loop1) {
n++;
cur1 = cur1.next;
}
//情况2),和单链表的相交差不多,原先是遇到 null结尾处终止 ,现在是loop处就终止
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 {//看能不能遇到loop2
cur1 = loop1.next;
while (cur1 != loop1) {//如果都转回自己,还没有遇到loop2,就是情况1)
if (cur1 == loop2) {
return loop1;
}
cur1 = cur1.next;
}
return null;
}
}
主函数调用
public static Node getIntersectNode(Node haed1,Node head2){
if(head1 == null || head2 == null){
return null;
}
Node loop1 = getLoopNode(head1);
Node loop2 = getLoopNode(head2);
if(loop1 == null && loop2 == null){
return noLoop(head1, head2);
}
if(loop1 != null && loop2 != null){
return bothLoop(head1, loop1, head2, loop2);
}
return null;
}