引言:
方法论
- 对于笔试:一切为了时间复杂度,不用在乎空间复杂度
- 对于面试:时间复杂度放在首位,空间复杂度尽量最小
重要技巧
链表回文
方式1 利用快慢指针走到中点,将链表后一半放入栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| public boolean isPalindrome(Node head) { if (head == null || head.next == null) { return true; }
Stack<Node> stack = new Stack<Node>();
Node right = head.next;
Node cur = head;
while (cur.next != null || cur.next.next != null) { right = right.next; cur = cur.next.next; }
while (right != null) { stack.push(right); right = right.next; }
while (!stack.isEmpty()) { if (head.val != stack.pop().val) { return false; } head = head.next; }
return true; }
|
方式2 利用快慢指针走到中点,将链表后一部分反转,双指针遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| public boolean isPalindrome2(Node head) { if (head == null || head.next == null) { return true; }
Node slowNode = head; Node fastNode = head;
while (fastNode.next != null && fastNode.next.next != null) { slowNode = slowNode.next; fastNode = fastNode.next.next; }
fastNode = slowNode.next; slowNode.next = null; Node tempNode = null;
while (fastNode != null) { tempNode = fastNode.next; fastNode.next = slowNode; slowNode = fastNode; fastNode = tempNode; } tempNode = slowNode; fastNode = head; boolean res = true;
while (slowNode != null && fastNode != null) { if (slowNode.val != fastNode.val) { res = false; break; } slowNode = slowNode.next; fastNode = fastNode.next; }
slowNode = tempNode.next; tempNode.next = null;
while (slowNode != null) { fastNode = slowNode.next; slowNode.next = tempNode; tempNode = slowNode; slowNode = fastNode; } return res; }
|
链表划分
问题
给定一个单链表的头节点head
,节点的值类型是整型,再给定一个整数pivot
。实现一个调整链表的函数,将链表调整为左部分都是值小于pivot
的节点,中间部分都是值等于pivot
的节点,右部分都是值大于pivot
的节点。
进阶
算法图解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| public Node listPartition(Node head, int pivot) { Node sH = null; Node sT = null; Node eH = null; Node eT = null; Node bH = null; Node bT = null; Node next = null;
while (head != null) { next = head.next; head.next = null;
if (head.val < pivot) { if (sH == null) { sH = head; sT = head; } else { sT.next = head; sT = head; } } else if (head.val == pivot) { if (eH == null) { eH = head; eT = head; } else { eT.next = head; eT = head; }
} else { if (bH == null) { bH = head; bT = head; } else { bT.next = head; bT = head; } }
head = next; }
if (sT != null) { sT.next = eH; eT = eT == null ? sT : eT; }
if (eT != null){ eT.next = bH; }
return sH != null ? sH : eH != null ? eH : bH; }
|
复制含有随机指针节点的链表
问题
定义一种特殊单链表结点类,描述如下:
1 2 3 4 5 6 7 8
| class Node { int value; Node next; Node rand; Node(int val) { value = val; } }
|
rand
指针是单链表结点结构中新增的指针,rand
可能指向链表中的任意一个结点,也可能指向null。给定一个由Node
结点类型组成的无环单链表的头结点head
,请实现一个函数完成这个链表的复制,并返回复制的新链表的头结点。
[要求] 时间复杂度$O(N)$,额外空间复杂度 $O(1)$
算法思想
准备哈希表,键值对为原始节点,克隆的新节点
,克隆操作是,来到原始链表节点1,拷贝next
与random
,直到遍历完所有原始链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public Node copyListWithRandom(Node head) { HashMap<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); }
|
链表相交问题
问题
给定两个可能有环也可能无环的单链表,头结点head1
和head2
。请实现一个函数,如果两个链表相交,请返回相交的第一个结点。如果不相交,返回null
。
要求
如果两个链表长度和为 $N$,时间复杂度达到 $O(N)$,额外空间复杂度达到 $O(1)$
单链表是否有环
问题
如何判断一个链表有环,如果有,返回第一个入环口,没有返回null
算法思想
设置快慢指针,快指针走两步,慢指针走一步
如果没有环快指针会先到达终点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public Node getLoopNode(Node head) { if (head == null || head.next == null) { return null; }
Node slow = head.next; Node fast = head.next.next;
while (fast != slow) { if (fast.next == null || fast.next.next == null) { return null; } fast = fast.next.next; slow = slow.next; }
fast = head;
while (fast != slow) { fast = fast.next; slow = slow.next; }
return slow; }
|
备注:如果一个链表有环,另一个链表无环,那么这个链表是无论也不可能相交的。能相交的就是两个情况:
两个链表都无环
两个链表都有环
两个无环链表相交
问题
如何判断两个无环链表是否相交,相交则返回第一个相交节点,不相交返回null
算法思想
指针1从头结点开始,走到最后一个结点(不是结束),统计链表1的长度len1
,记录链表1的最后一个结点为end1
链表2从头结点开始,走到最后一个结点(不是结束),统计链表2的长度len2
,记录链表2的最后给一个结点end2
如果end1!=end2
,说明两个链表不相交,返回null即可;如果end1==end2
,说明两个链表相交,进入下一步. 来寻找第一个相交的结点
如果链表1比较长,链表1就先走len1-len2
步;如果链表2比较长,链表2就先走len2-len1
步。然后两个链表一起走,两个链表第一次走到一起的结点就是第一个相交的结点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| public 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; }
|
两个有环链表相交
问题
如何判断两个有环链表相交,相交则返回第一个相交节点,不相交返回null
[loop1 == loop2 两个链表相交图示]
[loop1 != loop2 两个链表不相交图示]
[loop1 != loop2两个链表相交图示]
算法思想
让链表1从入环扣1(loop1
)出发(由于loop1
和之后的所有结点都在环上,将来一定能返回loop1
)
如果回到loop1
之前没有遇到loop2
,说明两个链表结构如图[loop1 != loop2 两个链表不相交图示]
,即不相交,返回null
如果回到loop1
之前遇到loop2
,说明两个链表结构如图[loop1 != loop2两个链表相交图示]
,也就是相交
由于loop1
和loop2
都在两条链表上,只不过loop1
是离链表1较近的结点,loop2是离链表2较近的结点,返回两者中任意一个即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| public Node bothLoop(Node head1, Node head2, Node loop1, 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 = cur1.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; } }
|
Total
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public Node getIntersectNode(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; }
|