一:面试时链表解题的方法论
1、对于笔试,不用太在乎空间复杂度,一切为了时间复杂度
2、对于面试,时间复杂度依然放在第一位,但是一定要找到空间最省的方法
二:链表面试题常用数据结构和技巧
1、使用容器(哈希表、数组等)
2、快慢指针
使用快慢指针的面试题:
- 1、输入链表头结点,奇数长度返回中点,偶数长度返回上中点
*
public static <T> Node<T> midOrUpMidNode(Node<T> head) {
if(head == null || head.next == null || head.next.next == null) {
return head;
}
Node<T> slow = head.next;
Node<T> fast = head.next.next;
while(fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
- 2、输入链表头结点,奇数长度返回中点,偶数长度返回下中点
public static <T> Node<T> midOrDownMidNode(Node<T> head) {
if(head == null || head.next == null) {
return head;
}
Node<T> slow = head.next;
Node<T> fast = head.next;
while(fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
- 3、输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个
public static <T> Node<T> midOrUpMidPreNode(Node<T> head) {
if(head == null || head.next == null || head.next.next == null) {
return null;
}
Node<T> slow = head;
Node<T> fast = head.next.next;
while(fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
- 4、输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个
public static <T> Node<T> midOrDownMidPreNode(Node<T> head){
if(head == null || head.next == null) {
return null;
}
if(head.next.next == null) {
return head;
}
Node slow = head;
Node fast = head.next;
while(fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
面试题三:反转单链表
思路?建立两个结点分别用于存head 和 head.next结点顺序
public Node<T> reverseLinkList(Node<T> head){
Node<T> pre = null;
Node<T> next = null;
while(head != null) {
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
面试题四:反转双向链表
public Node<T> reverseDoubleLinked(Node<T> head){
head = head.next;
Node<T> pre = null;
Node<T> next = null;
while(head != null) {
next = head.next;
head.next = pre;
head.previous = next;
pre = head;
head = next;
}
return pre;
}
面试题五:从单链表中删除指定元素
public Node<T> deleteTargetNode(Node<T> head,T target){
while(head.getData() != null) {
if(head.getData() == target) {
head = head.next;
}else {
break;
}
}
Node<T> pre = head;
Node<T> cur = head;
while(cur != null) {
if(cur.getData() == target) {
pre.next = cur.next;
}else {
pre = cur;
}
cur = cur.next;
}
return head;
}
面试题六:从双向链表中删除指定元素
public Node<T> deleteTargetNode(Node<T> head,T target){
while(head!=null) {
if(head.getData()!=target) {
break;
}
head = head.next;
}
head.previous = null;
Node<T> pre = head;
Node<T> cur = head;
while(cur!=null) {
if(cur.getData().equals(target)) {
pre.next = cur.next;
if(cur.next!=null) {
cur.next.previous = pre;
}
}else {
pre = cur;
}
cur = cur.next;
}
return head;
}
面试题七:判断一个链表是否为回文结构
例子: 1 → 2 → 1 返回true 1 → 2 → 2 → 1 返回true 1 → 2 → 3 返回false
如果链表长度为N,时间复杂度为O(N),额外空间复杂度O(1)
笔试用栈,面试用改链表方法(利用有限几个变量 空间O(1))
版本一:利用栈 空间O(N)
public static <T> boolean checkByStack(Node<T> head) {
if(head == null || head.next == null) {
return true;
}
Stack<Node<T>> stack = new Stack<>();
Node<T> p = head;
Node<T> q = head;
while(p!=null) {
stack.add(p);
p = p.next;
}
while(!stack.isEmpty()) {
if(!stack.pop().getData().equals(q.getData())) {
return false;
}
q = q.next;
}
return true;
}
版本二:利用栈 空间O(N/2)
public static <T> boolean checkByStackPlus(Node<T> head) {
if(head == null || head.next == null) {
return true;
}
Node<T> fast = head;
Node<T> slow = head;
Node<T> q = head;
while(fast.next!=null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
Stack<Node<T>> stack = new Stack<>();
while(slow.next != null) {
stack.add(slow.next);
slow = slow.next;
}
while(!stack.isEmpty()) {
if(!stack.pop().getData().equals(q.getData())) {
return false;
}
q = q.next;
}
return true;
}
版本三:利用有限几个变量 空间O(1)
public static <T> boolean checkByVar(Node<T> head) {
if(head == null || head.next == null) {
return true;
}
Node<T> fast = head;
Node<T> slow = head;
while(fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
fast = slow.next;
slow.next = null;
Node<T> rightPartFirstNode = null;
while(fast != null) {
rightPartFirstNode = fast.next;
fast.next = slow;
slow = fast;
fast = rightPartFirstNode;
}
rightPartFirstNode = slow;
fast = head;
boolean res = true;
while(slow != null && fast != null) {
if(slow.getData() != fast.getData()) {
res = false;
break;
}
slow = slow.next;
fast = fast.next;
}
slow = rightPartFirstNode.next;
rightPartFirstNode.next = null;
while(slow != null) {
fast = slow.next;
slow.next = rightPartFirstNode;
rightPartFirstNode = slow;
slow = fast;
}
return res;
}
面试题八:把单向链表按某值划分成左边小、中间相等、右边大的形式
1、把链表放入数组里面,在数组上做partition(笔试用)时间复杂度O(N)
public static <T> Node<T> listPartition(Node<T> head, int target){
if(head == null) {
return head;
}
Node<T> cur = head;
int i = 0;
while(cur != null) {
i++;
cur = cur.next;
}
Node<T>[] nodeArr = new Node[i];
i = 0;
cur = head;
for(i = 0; i != nodeArr.length; i++) {
nodeArr[i] = cur;
cur = cur.next;
}
arrPartition(nodeArr,target);
for(i = 1; i != nodeArr.length; i++) {
nodeArr[i -1].next = nodeArr[i];
}
nodeArr[i -1].next = null;
return nodeArr[0];
}
public static <T> void arrPartition(Node<T>[] nodeArr,int target) {
int small = -1;
int big = nodeArr.length;
int index = 0 ;
while(index != big) {
if((int)nodeArr[index].data < target) {
swap(nodeArr,++small,index++);
}else if((int)nodeArr[index].data == target) {
index++;
}else {
swap(nodeArr,--big,index);
}
}
}
public static <T> void swap(Node<T>[] arr,int x,int y) {
Node<T> node = arr[x];
arr[x] = arr[y];
arr[y] = node;
}
2、分成小、中、大 三部分,再把各个部分之间串起来(面试用)
时间复杂度O(N)空间复杂度O(1)
public static <T> Node<T> linkPartition(Node<T> head, int target){
Node<T> sH = null;
Node<T> sT = null;
Node<T> eH = null;
Node<T> eT = null;
Node<T> mH = null;
Node<T> mT = null;
Node<T> next = null;
while(head != null) {
next = head.next;
if((int)head.data < target) {
if(sH == null) {
sH = head;
sT = head;
}else {
sT.next = head;
sT = head;
}
}else if((int)head.data == target) {
if(eH == null) {
eH = head;
eT = head;
}else {
eT.next = head;
eT = head;
}
}else {
if(mH == null) {
mH = head;
mT = head;
}else {
mT.next = head;
mT = head;
}
}
head = next;
}
if(sT != null) {
sT.next = eH;
eT = eT == null ? sT : eT;
}
if(eT != null) {
eT.next = mH;
}
return sH != null ? sH : (eH != null ? eH : mH);
}
面试题九:给定一个由Node结点类型组成的无环单链表的头结点head,请实现一个函数完成这个链表的复制
一种特殊的单链表结点类描述如下
public static class Node{
int value;
Node next;
Node rand;
public Node(int val){
value = val;
}
}
rand指针是单链表结点结构中新增的指针,rand 可能指向链表中的任意一个结点,也可能指向null 给定一个由Node结点类型组成的无环单链表的头结点head
请实现一个函数完成这个链表的复制,并返回复制的新链表的头结点 要求:空间复杂度O(N)、额外空间复杂度O(1)
笔试:用HashMap解决
public static Node copyListWithRandByHashMap(Node head) {
Map<Node,Node> map = new HashMap<>();
Node cur = head;
while(cur != null) {
map.put(cur, new Node(cur.value));
cur = cur.next;
}
cur = head;
while(cur != null) {
map.get(cur).next = map.get(cur.next);
map.get(cur).rand = map.get(cur.rand);
cur = cur.next;
}
return map.get(head);
}
面试:直接在每个结点后面新增该结点 用位置来解决指针指向问题
public static Node copyListWithRand(Node head) {
if(head == null) {
return null;
}
Node cur = head;
Node next = null;
while(cur != null) {
next = cur.next;
cur.next = new Node(cur.value);
cur.next.next = next;
cur = next;
}
cur = head;
Node curCopy = null;
while(cur != null) {
next = cur.next.next;
curCopy = cur.next;
curCopy.rand = cur.rand != null? cur.rand.next : null;
cur = next;
}
Node resHead = head.next;
cur = head;
while(cur != null) {
next = cur.next.next;
curCopy = cur.next;
cur.next = next;
curCopy.next = next!=null? next.next : null;
cur = next;
}
return resHead;
}
面试题十:两个单链表相交的一系列问题
给定两个可能有环也可能无环的单链表,头结点head1 和 head2。
请实现一个函数,如果两个链表相交,请返回相交的 第一个结点。如果不相交,返回null
要求:如果两个链表长度之和为N,时间复杂度语法到O(N),额外空间复杂度,请达到O(1)
分成三种情况
判断单链表是否有环?
找到链表的第一个入环结点,如果无环,返回null
public static Node getLoopNode(Node head) {
if(head == null || head.next == null || head.next.next == null) {
return null;
}
Node slow = head.next;
Node fast = head.next.next;
while(slow != fast) {
if(fast.next == null || fast.next.next == null) {
return null;
}
slow = slow.next;
fast = fast.next.next;
}
fast = head;
while(slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
目前就三种情况:
1、两个链表都无环
2、两个链表 一个有环 一个无环 这种情况 两个链表是不可能相交的
3、两个链表都有环
- 1、两个链表 各自独立 无相交结点
- 2、他俩的入环结点是一个,转变为无环链表的相交问题 base改变
- 3、让loop1循环 如果过程中没遇到loo2,那么就是情况3-1(两个链表 各自独立),反之就是情况3-3
public static Node getInterNode(Node head1,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;
}
public static Node getLoopNode(Node head) {
if(head == null || head.next == null || head.next.next == null) {
return null;
}
Node slow = head.next;
Node fast = head.next.next;
while(slow != fast) {
if(fast.next == null || fast.next.next == null) {
return null;
}
slow = slow.next;
fast = fast.next.next;
}
fast = head;
while(slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
public static Node noLoop(Node head1,Node head2) {
if(head1 == null || head2 == null) {
return null;
}
Node cur1 = head1;
Node cur2 = head2;
int len = 0;
while( cur1.next != null) {
len ++;
cur1 = cur1.next;
}
while(cur2.next != null) {
len --;
cur2 = cur2.next;
}
if(cur1 != cur2) {
return null;
}
cur1 = len > 0 ? head1 : head2;
cur2 = (cur1 == head1) ? head2 : head1;
len = Math.abs(len);
while(len != 0) {
len --;
cur1 = cur1.next;
}
while(cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
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 len = 0;
while(cur1 != loop1) {
len ++;
cur1 = cur1.next;
}
while(cur2 != loop2) {
len --;
cur2 = cur2.next;
}
cur1 = len > 0 ? head1 : head2;
cur2 = (cur1 == head1) ? head2 : head1;
len = Math.abs(len);
while(len != 0) {
len --;
cur1 = cur1.next;
}
while(cur1 != cur2) {
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}else {
cur1 = loop1.next;
while(cur1 != loop1) {
if(cur1 == loop2) {
return loop1;
}
cur1 = cur1.next;
}
return null;
}
}
面试题十一:能不能 不给单链表的头结点,只给想要删除的结点,就能做到在链表上把这个点删掉?
不能,如果没有头结点,无法准确的连接好指针,有一些抖机灵的做法但是有弊端(对内存的理解)
|