1. 几种排序的时间、空间复杂度及稳定性
- 选择排序:时间复杂度O(N*N);空间复杂度O(1);稳定性:无;
- 冒泡排序:时间复杂度O(N*N);空间复杂度O(1);稳定性:有;
- 插入排序:时间复杂度O(N*N);空间复杂度O(1);稳定性:有;
- 归并排序:时间复杂度O(N*logN);空间复杂度O(N);稳定性:有;
- 快排(3.0版本):时间复杂度O(N*logN);空间复杂度O(logN);稳定性:无;
- 堆:时间复杂度O(N*logN);空间复杂度O(1);稳定性:无;
坑:有一道题目,是奇数放在数组左边,偶数放在数组右边,还要求原始的相对次序不变,碰到这个问题,让面试官解。
原因:可以参考快排,快排能做到排序,那么奇偶问题也就可以。但是稳定性是无,这个题的稳定性也是无。
2. 哈希表的简单介绍
- 哈希表在使用层面可以理解为一种集合结构
- 如果只有key,没有伴随数据value,可以使用HashSet结构
- 如果既有key,又有伴随数据value,可以使用HashMap结构
- 有无伴随数据,是HashSet和HashMap唯一区别,底层的实际结构是一回事
- 使用哈希表增(put)、删(remove)、改(put)和查(get)等操作,可以认为时间复杂度为O(1),但是常数时间比较大
- 放入哈希表的东西,如果是基本类型,内部按值传递,内存占用就是这个东西的大小
- 放入哈希表的东西,如果不是基本类型,内部按引用传递,内存占用就是这个东西内存地址的大小
3. 有序表的简单介绍
- 有序表在使用层面可以理解为一种集合结构
- 如果只有key,没有伴随数据value,可以使用HashSet结构
- 如果既有key,又有伴随数据value,可以使用HashMap结构
- 有无伴随数据,是HashSet和HashMap唯一区别,底层的实际结构是一回事
- 有序表和哈希表的区别是,有序表把key按照顺序组织起来,而哈希表完全不组织
- 红黑树、AVL树、size-balance-tree和跳表等都属于有序表结构,只是底层的具体实现不同。
- 放入有序表的东西,如果是基本类型,内部按值传递,内存占用就是这个东西的大小
- 放入有序表的东西,如果不是基本类型,必须提供比较器,内部按引用传递,内存占用就是这个东西内存地址的大小
- 不管底层具体实现,只要是有序表,都有以下固定的基本功能和固定的时间复杂度
4. 有序表的固定操作
5. 判断一个链表是否是回文结构
笔试思路:(1)整条链表都放在栈中,栈是先进后出,栈出的数据与链表头位置开始比对,都符合链表是回文。(2)取链表中间位置以后的元素,放到栈中,出栈与链表比对,都符合链表是回文。
笔试code
public static boolean isPalindrome1(Node head) {
Stack<Node> stack = new Stack<Node>(); // 准备一个栈
Node cur = head;
while(cur != null){ // 所以的东西都压到栈中
stack.push(cur);
cur = cur.next;
}
while(head != null){
if(head.value != stack.pop().value){ // 栈弹出一个,只要不相等,返回false
return false;
}
head = head.next;
}
return true;
}
n/2的方法
public static boolean isPalindrom2(Node head) {
if(head == null || head.next == null){
return true;
}
Node right = head.next;
Node cur = head;
while(cur.next != null && cur.next.next != null){
right = right.next;
cur =cur.next.next;
}
Stack<Node> stack = new Stack<Node>();
while(right != null){
stack.push(right);
right = right.next;
}
while(!stack.isEmpty()){
if(head.value != stack.pop().value){
return false;
}
head = head.next;
}
return true;
}
面试思路: 快指针一次走两步,慢指针一次走一步,希望快指针走完的时候,慢指针指向中点的位置,中点往下的位置逆序,链表的中间位置设为null,头尾用两个引用计,分别为A、B,两个引用计往中间走,开始比对。引用计有一个走到null,停。返回true和fasle之前再把右边的部分恢复成原来的样子。
面试code
public static boolean isPalindrome3(Node head){
if(head == null || head.next == null){
return true;
}
Node n1 = head;
Node n2 = head;
while(n2.next != null && n2.next.next != null){
n1 = n1.next; // 慢指针一次走一步 n1--->mid
n2 = n2.next.next; // 快指针一次走两步,n2走完,n1会走到中点的位置 n2--->end
}
n2 = n1.next; // 右侧部分的第一个
n1.next = null; // 中间指向null
Node n3 = null;
while(n2 != null){ // 右半部分开始逆序
n3 = n2.next; // 储存下一个节点
n2.next = n1; // 右侧的下一个节点转换
n1 = n2; // n1 移动
n2 = n3; // n2 移动
}
n3 = n1; // n3-->存储最后一个节点
n2 = head; // n2--->左侧第一个节点
boolean res = true;
while(n1 != null && n2 != null){
if(n1.value != n2.value){
res = false;
break;
}
n1 = n1.next; // 左侧往中间走
n2 = n2.next; // 右侧往中间走
}
n1 = n3.next;
n3.next = null;
while(n1 != null){ // 右侧逆序回来
n2 = n1.next;
n1.next = n3;
n3 = n1;
n1 = n2;
}
return res;
}
6. 链表分成左边小、中间相等、右边大的形式
code:
public static Node listPartition2(Node head,int pivot){
Node sH = null; //small head
Node sT = null; //small tail
Node eH = null; //equal head
Node eT = null; //equal tail
Node mH = null; //big head
Node mT = null; //big tail
Node next = null; //save next node
while(head != null){
next = head.next;
head.next = null;
if(head.value < pivot){
if(sH == null){
sH = head;
sT = head;
} esle {
sT.next = head; // 老的尾巴下个节点指向当前节点
sT = head; // 当前节点变成小于区域的新的尾巴
}
} else if(head.value == piovt){
if(eH == null){
eH = head;
eT = head;
} esle {
eT.next = head;
eT = head;
}
} else if(head.value > piovt){
if(mH == null){
mH = head;
mT = head;
} esle {
mT.next = head;
mT = head;
}
}
head = next;
}
// small and equal reconnect
if(sT != null){ // 如果有小于区域
sT.next = eH;
eT = eT == null ? sT : eT; // 下一步,谁去连大于区域的头,谁就变成eT
}
// all reconnect
if(eT != null){ // 如果小于区域和等于区域,不是都没有
eT.next = mH;
}
return sH != null ? sH : (eH != null ? eH : mH);
}
7. 复制含有随机指针节点的链表
分析图哈希表的方式:
public static Node copyListWithrand1(Node head){
HashMap<Node, Node> map = new HashMap<Node, Node>(); // 创建hash表
Node cur = head;
// 作出对应关系,克隆节点也都声成了
while(cur != null){
map.put(cur, new Node(cur.value)); // 来到当前的cur,作出他的克隆节点
cur = cur.next; // 每个节点都循环
}
cur = head;
while(cur != null){
// cur 老
// map.get(cur) 新
map.get(cur).next = map.get(cur.next); // cur.next是老的指向
map.get(cur).rand = map.get(cur.rand);
cur = cur.next;
}
return map.get(head);
}
8. 两个无环单链表相交
入环节点:
c是入环节点
找到第一个入环节点:环外5个节点环内四个节点
快慢指针:快指针走两步,慢指针走一步。快慢指针会在环上相遇。当快慢指针相遇时,快指针回到头节点,快慢指针之后都以一步的形式走,最后会在入环节点相遇。
code:
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; // 返回第一个入环节点
}
两个无环单链表相交
单链表指针只会有一个next的
分析图:
code:
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;
}
cur1 = n > 0 ? head1 : head2; // 记录长链表,谁长,谁的头变成cur1
cur2 = cur1 == head1 ? head2 : head1; // 如果长链表是head1,那么短链表就是head2; 谁短, 谁的头变成cur2
n = Math.abs(n); // n 其实就是长链表和短链表的差值,再取绝对值
// 让长链表走n步
while(n != 0){
n--;
cur1 = cur1.next;
}
// 链表的节点不相等时,长链表也走完了n步,接着长链表和短链表一起走,就会相遇
while(cur1 != cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
9. 两个有环单链表相交
两个链表都有环的三种情况
两个有环链表,返回第一个相交节点,如果不相交返回null
public static Node bothLoop (Node head1, Node loop1, Node head2, Node loop2){
Node cur1 = null;
Node cur2 = null;
// 入环节点相同 上图中的情况2
// 跟无环链表的处理逻辑一样(跟有环无环没有关系)
if(loop1 == loop2){
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);
// 让长链表走n步
while(n != 0){
n--;
cur1 = cur1.next;
}
while(cur1 != cur2){
cur1 = cu1.next;
cur2 = cur2.next;
}
return cur1;
} else {
// cur1等于入环节点的下一个,上图中的1 入环转圈的过程
cur1 = loop1.next;
// cur1在转回入环节点之前
while(cur1 != loop1){
// cur1遇到了cur2的入环节点
if(cur1 == loop2){
// 返回loop1或者loop2都行
return loop1;
}
cur1 = cur1.next;
}
// 如果cur1都转回到自己了,还没有遇到,说明不相交
return null;
}
}
10. 有环无环链表的总体实现
public static Node getIntersectNode(Node head1, Node head2){
if(head1 == null || head2 == null){
return null;
}
Node loop1 = getLoopNode(head1); // head1的入环节点
Node loop2 = getLoopNode(head2); // head2的入环节点
if(loop1 == null && loop2 == null){
return noLoop(head1, head2);
}
if(loop1 != null && loop2 != null){
return bothLoop(head1, loop1, head2, loop2);
}
return null
}