链表面试题常用数据结构和技巧
1)使用容器(哈希表、数组等)
2)快慢指针
快慢指针
1)输入链表头节点,奇数长度返回中点,偶数长度返回上中点
2)输入链表头节点,奇数长度返回中点,偶数长度返回下中点
3)输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个
4)输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个
- 代码如下
/**
* 整体流程都是快指针一次走两步,慢指针一次走一步,当快指针走到链表尾部是,
* 慢指针正好走到整个链表中部位置(位置需要根据要求作出些许调整)
*/
public class FastLowPoint01 {
//输入链表头节点,奇数长度返回中点,偶数长度返回上中点
public static Node getMidNode1(Node head){
if(head == null || head.next == null || head.next.next == null){
// 为空时返回空,一个节点奇数返回一个,两个节点偶数返回上中点
return head;
}
// 至少有两个指针
// o -> o -> o -> o -> o --null
Node low = head;
Node fast = head.next;
while (fast != null && fast.next != null){
low = low.next;
fast = fast.next.next;
}
return low;
}
// 输入链表头节点,奇数长度返回中点,偶数长度返回下中点
public static Node getMidNode2(Node head){
if(head == null || head.next == null){
return head;
}
// o -> o -> o -> o -> o --null
Node low = head;
Node fast = head;
while (fast != null && fast.next != null){
low = low.next;
fast = fast.next.next;
}
return low;
}
// 输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个
public static Node getMidNode3(Node head){
if (head == null || head.next == null || head.next.next == null){
return null;
}
// o -> o -> o -> o -> o --null
Node low = head;
Node fast = head.next.next;
while (fast != null && fast.next != null && fast.next.next != null){
low = low.next;
fast = fast.next.next;
}
return low;
}
// 输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个
public static Node getMidNode4(Node head){
if(head == null || head.next == null){
// o
return null;
}
Node low = head;
Node fast = head.next;
// o -> o -> o -> o -> o --null
while (fast != null && head.next != null && fast.next.next != null){
low = low.next;
fast = fast.next.next;
}
return low;
}
}
常见面试题
- 给定一个单链表的头节点head,请判断该链表是否为回文结构。
- 方法一,利用栈。
1)先遍历一遍链表把节点加入栈中。
2)弹出一个节点,从头遍历链表,比较。
public class PalindromeLinked {
// 栈
public static boolean isPalindrome(Node head){
if(head == null){
return true;
}
Stack stack = new Stack<>();
Node cur = head;
while (cur != null){
stack.push(cur);
cur = cur.next;
}
cur = head;
while (!stack.isEmpty()){
Node pop = stack.pop();
if(pop.value != cur.value){
return false;
}
cur = cur.next;
}
return true;
}
// 双指针
public static boolean isPalindrome2(Node head){
if(head == null || head.next == null){
return true;
}
Node low = head;
Node fast = head.next;
// o -> o -> o -> o -> o
while (fast != null && fast.next != null){
low = low.next;
fast = fast.next.next;
}
// 此时low指向链表中点,从low 开始反转链表
Node pre = null;
Node cur = low;
while (cur != null){
Node next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
Node tail = pre;
cur = head;
while (cur != null && pre != null){
if(cur.value != pre.value){
return false;
}
cur = cur.next;
pre = pre.next;
}
pre = null;
cur = tail;
while (cur != null){
Node next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return true;
}
}
- 将单向链表按某值划分成左边小、中间相等、右边大的形式
1)把链表放入数组里,在数组上做partition(笔试用)。
2)分成小、中、大三部分,再把各个部分之间串起来(面试用)。 - 克隆链表
一种特殊的单链表节点类描述如下
class Node {
int value;
Node next;
Node rand;
Node(int val) { value = val; }
}
rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向null。
给定一个由Node节点类型组成的无环单链表的头节点 head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。
【要求】
时间复杂度O(N),额外空间复杂度O(1)
public class CopyLinked {
public static Node copyLinked(Node oldHead){
if(oldHead == null){
return null;
}
// 在原链表中间增加复制节点
Node cur = oldHead;
while (cur != null){
Node next = cur.next;
cur.next = new Node(cur.value);
cur.next.next = next;
cur = next;
}
// 重新遍历链表,把克隆节点的random连好
cur = oldHead;
while (cur != null){
// 获取原节点的random节点
Node randomOld = cur.random;
// 克隆节点的random节点
cur.next.random = randomOld == null ? null : randomOld.next;
cur = cur.next.next;
}
// 分离新老链表
cur = oldHead;
Node res = oldHead.next;
while (cur != null){
Node oldNext = cur.next.next;
Node next = cur.next;
cur.next = next.next;
next.next = cur.next == null ? null : cur.next.next;
cur = oldNext;
}
return res;
}
}
- 给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的 第一个节点。如果不相交,返回null 。
【要求】
如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度 请达到O(1)。
public class TwoLinked {
public Node getFirstNode(Node node1,Node node2){
if(node1 == null || node2== null){
return null;
}
// 如果量表都没有环
if(isLoop(node1) == null && isLoop(node2) == null){
// 两个链表都没有环
return notLoop(node1,node2);
}else if (isLoop(node1)!=null && isLoop(node2)!=null){
// 两个链表都有环
return bothLoop(node1,node2);
}
// 一个链表有环,一个链表没有环是不存在的
return null;
}
private Node notLoop(Node node1,Node node2){
// 两个无环链表,先看为指针是否相等
int n1 = 0;
int n2 = 0;
Node cur1 = node1;
Node cur2 = node2;
while (cur1.next != null){
n1++;
cur1 = cur1.next;
}
while (cur2.next != null){
n2++;
cur2 = cur2.next;
}
if(cur1 != cur2){
return null;
}
// 长链表
cur1 = n1 > n2 ? node1 : node2;
// 短链表
cur2 = n1 > n2 ? node2 : node1;
int n = Math.abs(n1-n2);
// 长链表先走n步
while (n > 0){
cur1 = cur1.next;
n--;
}
// 一起走
while (cur1 != cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
private Node bothLoop(Node node1,Node node2){
// 求两个节点的入环节点
Node loop1 = isLoop(node1);
Node loop2 = isLoop(node2);
if(loop1 == loop2){
// 变换成两个无环链表求第一个相交节点问题
int n1 = 0;
int n2 = 0;
Node cur1 = node1;
Node cur2 = node2;
while (cur1 != loop1){
n1++;
cur1 = cur1.next;
}
while (cur2 != loop1){
n2++;
cur2 = cur2.next;
}
cur1 = n1 > n2 ? node1:node2 ;
cur2 = n1 > n2 ? node2:node1 ;
int n = Math.abs(n1-n2);
while (n > 0){
cur1 = cur1.next;
n--;
}
while (cur1 != cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}else{
Node cur = loop1.next;
while (cur != loop1){
if(cur == loop2){
return loop1;
}
cur = cur.next;
}
return null;
}
}
// 判断链表是否有环,返回第一个入环节点
private Node isLoop(Node node){
if(node == null || node.next == null || node.next.next==null){
// 一个节点,或者两个节点都没有环
return null;
}
// 快慢指针
Node fast = node.next.next;
Node low = node.next;
while (fast != low){
if(fast.next == null || low.next == null){
return null;
}
fast = fast.next.next;
low = low.next;
}
// 此时fast 回到头结点
fast = node;
while (fast != low){
// 再次相遇则为入环节点
if(fast == low){
break;
}
fast = fast.next;
low = low.next;
}
return fast;
}
}
- 能不能不给单链表的头节点,只给想要删除的节点,就能做到在链表上把这个点删掉?