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 spacepublic 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 使用Listpublic 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 spacepublic 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 spacepublic 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;
}
}
