1:快慢指针
1)输入链表头节点,奇数长度返回中点,偶数长度返回上中点
2)输入链表头节点,奇数长度返回中点,偶数长度返回下中点
3)输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个
4)输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个
static class Node {
int value;
Node next;
public Node(int value) {
this.value = value;
}
}
//1)输入链表头节点,奇数长度返回中点,偶数长度返回上中点
public static Node midOrUpMidNode(Node head) {
if (head == null || head.next == null || head.next.next == null) {
return head;
}
// 链表至少有3个节点
Node sPoint = head.next;
Node qPoint = head.next.next;
while (qPoint.next != null && qPoint.next.next != null) {
qPoint = qPoint.next.next;
sPoint = sPoint.next;
}
return sPoint;
}
public static Node test1(Node head) {
if (head == null) {
return head;
}
Node cur = head;
List<Node> list = new ArrayList<>();
while (cur != null) {
list.add(cur);
cur = cur.next;
}
return list.get((list.size() - 1) >> 1);
}
//2)输入链表头节点,奇数长度返回中点,偶数长度返回下中点
public static Node midOrDownMidNode(Node head) {
if (head == null || head.next == null |) {
return head;
}
//链表至少有2个节点
Node sPoint = head.next;
Node qPoint = head.next;
while (qPoint.next != null && qPoint.next.next != null) {
qPoint = qPoint.next.next;
sPoint = sPoint.next;
}
return sPoint.next;
}
public static Node test2(Node head){
if (head == null) {
return head;
}
Node cur = head;
List<Node> list = new ArrayList<>();
while (cur != null) {
list.add(cur);
cur = cur.next;
}
return list.get(list.size() >> 1);
}
//3)输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个
public static Node midOrUpMidPreNode(Node head) {
if (head == null || head.next == null || head.next.next == null) {
return null;
}
// 至少3个节点
//链表至少有2个节点
Node sPoint = head;
Node qPoint = head.next.next;
while (qPoint.next != null && qPoint.next.next != null) {
qPoint = qPoint.next.next;
sPoint = sPoint.next;
}
return sPoint;
}
public static Node test3(Node head){
if (head == null||head.next==null||head.next.next==null) {
return null;
}
Node cur = head;
List<Node> list = new ArrayList<>();
while (cur != null) {
list.add(cur);
cur = cur.next;
}
return list.get((list.size()-3)>>1);
}
//4)输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个
public static Node midOrDownMidPreNode(Node head) {
if (head == null || head.next == null) {
return null;
}
if (head.next.next == null) {
return head;
}
// 至少3个节点
Node sPoint = head;
Node qPoint = head.next;
while (qPoint.next != null && qPoint.next.next != null) {
qPoint = qPoint.next.next;
sPoint = sPoint.next;
}
return sPoint;
}
public static Node test4(Node head){
if (head == null||head.next==null||head.next.next==null) {
return null;
}
Node cur = head;
List<Node> list = new ArrayList<>();
while (cur != null) {
list.add(cur);
cur = cur.next;
}
return list.get((list.size()-2)>>1);
}
2 常见面试题
题目1 给定一个单链表的头节点head,请判断该链表是否为回文结构。
思路:
1)哈希表方法特别简单(笔试用)
2)改原链表的方法就需要注意边界了(面试用)
public static class Node {
public int value;
public Node next;
public Node(int data) {
this.value = data;
}
}
// need n extra space
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) {
return false;
}
head = head.next;
}
return true;
}
// need n extra space 使用List
public static boolean isPalindromeList2(Node head) {
if (head == null | head.next == null) {
return true;
}
List<Node> list = new ArrayList<>();
Node cur = head;
while (cur != null) {
list.add(cur);
cur = cur.next;
}
cur = head;
for (int i = list.size() - 1; i >= 0; i--) {
if (list.get(i).value != cur.value) {
return false;
}
cur = cur.next;
}
return true;
}
// need n/2 extra space
public static boolean isPalindrome2(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;
}
// need O(1) extra space
public static boolean isPalindromeList(Node head) {
if (head == null || head.next == null) {
return true;
}
// 至少2个节点
Node n1 = head;
Node n2 = head;
// n1 ,链表长度为奇数时,为中点;链表长度为偶数时,为上中点
while (n2.next != null && n2.next.next != null) {
n2 = n2.next.next;
n1 = n1.next;
}
n2 = n1.next;
n1.next = null;
Node n3 = null;
while (n2 != null) {
n3 = n2.next;
n2.next = n1;
n1 = n2;
n2 = n3;
}
// n1 是最后一个节点,n3 是最后一个节点
n3 = n1;
n2 = head;
boolean ans = true;
while (n1 != null && n2 != null) {
if (n1.value != n2.value) {
ans = false;
break;
}
n1 = n1.next;
n2 = n2.next;
}
// n1 为倒数第2个节点
n1 = n3.next;
n3.next = null;
while (n1 != null) {
n2 = n1.next;
n1.next = n3;
n3 = n1;
n1 = n2;
}
return ans;
}
题目2:将单向链表按某值划分成左边小、中间相等、右边大的形式
思路:
1)把链表放入数组里,在数组上做partition(笔试用)
2)分成小、中、大三部分,再把各个部分之间串起来(面试用)
static class Node {
int value;
Node next;
public Node(int value) {
this.value = value;
}
}
// 法1 分成小、中、大三部分,再把各个部分之间串起来(面试用)
public static Node smallEqualBig(Node head, int k) {
// 只有0、1个节点情况下直接返回
if (head == null || head.next == null) {
return head;
}
//至少2个节点的情况
Node smallHead = null;
Node smallTail = null;
Node equalHead = null;
Node equalTail = null;
Node bigHead = null;
Node bigTail = null;
Node next = null;
while (head != null) {
next = head.next;
head.next = null;
if (head.value < k) {
if (smallHead == null) {
smallHead = head;
} else {
smallTail.next = head;
}
smallTail = head;
} else if (head.value == k) {
if (equalHead == null) {
equalHead = head;
} else {
equalTail.next = head;
}
equalTail = head;
} else {
if (bigHead == null) {
bigHead = head;
} else {
bigTail.next = head;
}
bigTail = head;
}
head = next;
}
if (smallTail != null) {
smallTail.next = equalHead;
equalTail = equalTail == null ? smallTail : equalTail;
}
if (equalTail != null) {
equalTail.next = bigHead;
}
return smallHead != null ? smallHead : (equalHead != null ? equalHead : bigHead);
}
// 法2 把链表放入数组里,在数组上做partition(笔试用)
public static Node smallEqualBig2(Node head, int k) {
if (head == null || head.next == null) {
return head;
}
ArrayList<Node> list = new ArrayList<>();
while (head != null) {
list.add(head);
head = head.next;
}
partition(list, k);
for (int i = 0; i < list.size(); i++) {
head = list.get(i);
if (i == list.size() - 1) {
head.next = null;
} else {
head.next = list.get(i + 1);
}
}
return list.get(0);
}
private static void partition(ArrayList<Node> list, int k) {
int smallR = -1;
int bigL = list.size() - 1;
for (int i = 0; i <= bigL; ) {
if (list.get(i).value < k) {
swap(list, ++smallR, i++);
} else if (list.get(i).value == k) {
i++;
} else {
swap(list, bigL--, i);
}
}
}
private static void swap(ArrayList<Node> list, int i, int j) {
Node tmp = list.set(i, list.get(j));
list.set(j, tmp);
}
题目3:链表的复制,并返回复制的新链表的头节点。
leetcode : https://leetcode.com/problems/copy-list-with-random-pointer/
一种特殊的单链表节点类描述如下
class Node {
int value;
Node next;
Node rand;
Node(intval) { value = val; }
}
rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向null。给定一个由Node节点类型组成的无环单链表的头节点 head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。
【要求】
时间复杂度O(N),额外空间复杂度O(1)
static class Node {
int value;
Node next;
Node random;
public Node(int value) {
this.value = value;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Node node = (Node) o;
return value == node.value && Objects.equals(next, node.next) && Objects.equals(random, node.random);
}
@Override
public int hashCode() {
return Objects.hash(value, next, random);
}
}
public static Node copyListWithRandom(Node head) {
// 0、1个节点 直接返回
if (head == null) {
return head;
}
Node cur = head;
Node next = null;
while (cur != null) {
Node newNode = new Node(cur.value);
next = cur.next;
cur.next = newNode;
newNode.next = next;
cur = next;
}
cur = head;
Node random = null;
while (cur != null) {
random = cur.random;
cur.next.random = random != null ? random.next : null;
cur = cur.next.next;
}
Node ans = head.next;
Node ansCur = ans;
cur = head;
while (cur != null) {
next = cur.next.next;
cur.next = next;
ansCur.next = next != null ? next.next : null;
cur = next;
ansCur = ansCur.next;
}
return ans;
}
// 法2 使用容器
public static Node copyListWithRandom2(Node head) {
// key 老节点
// value 新节点
HashMap<Node, Node> map = new HashMap<>(16);
Node cur = head;
while (cur != null) {
map.put(cur, new Node(cur.value));
cur = cur.next;
}
cur = head;
while (cur != null) {
// cur 老
// map.get(cur) 新
// 新.next -> cur.next克隆节点找到
map.get(cur).next = map.get(cur.next);
map.get(cur).random = map.get(cur.random);
cur = cur.next;
}
return map.get(head);
}
题目4 返回2个可能有环的链表相交的第一个节点
给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的 第一个节点。如果不相交,返回null
【要求】如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度 请达到O(1)。
public static Node findFirstIntersectNode(Node head1, Node head2) {
// 两个链表都为空
if (head1 == null || head2 == null) {
return null;
}
// 两个链表都不为空
Node loop1 = isLoop(head1);
Node loop2 = isLoop(head2);
if (loop1 == null && loop2 == null) {
return noLoopIntersect(head1, head2);
} else if (loop1 == null ^ loop2 == null) {
return oneLoopIntersect(head1, head2);
} else {
return twoLoopIntersect(head1, head2);
}
}
// 判断链表是否有环,有环则返还进入环的节点,无环则返回null
private static Node isLoop(Node head) {
if (head == null || head.next == null || head.next.next == null) {
return null;
}
Node slow = head.next;
Node fast = head.next.next;
// 从循环里出来时,slow==fast
while (slow != fast) {
if (fast.next == null || fast.next.next == null) {
return null;
}
fast = fast.next.next;
slow = slow.next;
}
fast = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
/*
* 两个可能有环的链表相交,有以下几种情况:
* 1 两个无环链表不相交
* 2 两个无环链表相交
* 3 一个有环、一个无环 不可能相交
* 4 两个有环链表不相交
* 5 两个有环链表相交
* i 相交节点在第一个入环节点之前
* ii 相交节点环上
* */
// 返回两个无环链表相交节点,不相交返回null(情况1、2)
private static Node noLoopIntersect(@NotNull Node head1, @NotNull Node head2) {
Node end1 = head1;
Node end2 = head2;
//至少有一个节点
int size1 = 1;
int size2 = 1;
while (end1.next != null) {
size1++;
end1 = end1.next;
}
while (end2.next != null) {
size2++;
end2 = end2.next;
}
// 两个无环链表相交,那么最后一个节点一定相同,不同则表示两个链表不相交
if (end1 != end2) {
return null;
}
// end1节点指向长的那个链表,end2指向短的那个链表
end1 = size1 > size2 ? head1 : head2;
end2 = end1 == head1 ? head2 : head1;
for (int i = 0; i < Math.abs(size1 - size2); i++) {
end1 = end1.next;
}
while (end1 != end2) {
end1 = end1.next;
end2 = end2.next;
}
return end1;
}
// 一个有环,一个无环,不可能相交(情况3)
private static Node oneLoopIntersect(Node head1, Node head2) {
return null;
}
// 返回两个有环链表相交第一个节点,不相交返回null(情况4、5)
private static Node twoLoopIntersect(Node head1, Node head2) {
Node loopNode1 = isLoop(head1);
Node loopNode2 = isLoop(head2);
// 相交节点在第一个入环节点之前
if (loopNode1 == loopNode2) {
// 将第一个入环节点当成链表最后一个节点,类似情况2
Node end1 = head1;
Node end2 = head2;
int n = 0;
while (end1 != loopNode1) {
n++;
end1 = end1.next;
}
while (end2 != loopNode2) {
n--;
end2 = end2.next;
}
// end1 指向长的链表
end1 = n > 0 ? head1 : head2;
end2 = end1 == head1 ? head2 : head1;
n = Math.abs(n);
while (n != 0) {
end1 = end1.next;
n--;
}
while (end1 != end2) {
end1 = end1.next;
end2 = end2.next;
}
return end1;
} else {
Node loopNodeCur = loopNode1.next;
while (loopNodeCur != loopNode1) {
// 相交节点在环上
if (loopNodeCur == loopNode2) {
return loopNode1;
}
loopNodeCur = loopNodeCur.next;
}
// 不相交
return null;
}
}