前言
记录LeetCode刷题中遇到的链表相关题目,第二篇
142. 环形链表 II(每日一题)
我们使用快慢双指针来完成这道题。假设链表有环,如上图所示,绿色部分即为环,从链表头head到环入口的距离为a。fast跟slow指针从head出发,fast每次向后移动两个节点,slow每次向后移动一个节点。那么fast跟slow肯定会相遇,假设相遇的节点离环的入口节点距离为b,到环的最后一个节点距离为c。即环的长度为b + c 那么相遇的时候,fast走过的路程为a + n(b + c) + b,n表示fast走过了多少个完整的环,至于n为多大,跟a以及环的长度b + c为多少有关,这不影响我们解题;而slow走过的路程就为a + b,即slow一个完整的环都没走完时,就会与fast相遇。当然,让slow跟fast无休止地走下去的话,它们会相遇很多次,但我们只需要它们第一次相遇的数据就够了, 那么根据fast的“速度”是slow的两倍,就有a + n(b + c) + b = 2(a + b),化简可得 a = (n - 1)(b + c) + c 等式左边就表示从head到环入口的路程,等式右边可以看成slow把当前走的环剩下的路程c走完后,再走n - 1个环的路程,可以看出来走到最后slow就会在环入口的位置。所以让一个新的指针指向head,然后在head跟slow未相遇时,两个指针都每次向前走一步,直到两个指针相等,所在的节点就是环入口节点
public ListNode detectCycle(ListNode head) {
if(head == null || head.next == null || head.next.next == null) return null;
ListNode fast = head.next.next,slow = head.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 fast;
}
234. 回文链表
可以借助反转链表的操作,使用快慢指针找到链表中点,然后将前半部分或后半部分反转,如果把前半部分反转,就比较反转后的前半部分以及原来的后半部分是否相同即可 下面给出的是递归的代码,先找到后半部分的起始节点,然后递归遍历后半部分,递归的效果是后半部分的节点会被逆序访问,而没有直接地去反转链表
class Solution {
ListNode prv;
boolean rec(ListNode n){
if(n.next != null){
if(!rec(n.next)) return false;
}
if(n.val == prv.val){
prv = prv.next;
return true;
}
return false;
}
public boolean isPalindrome(ListNode head) {
if(head == null || head.next == null) return true;
ListNode fast = head,slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
}
if(fast != null) slow = slow.next;
prv = head;
return rec(slow);
}
}
86. 分割链表
快慢双指针:保证慢指针往前的 (包括慢指针本身) 都是小于 x 的节点,慢指针的下一个就是大于等于 x 的节点;由快指针去找到小于 x 的节点,然后将其插入到慢指针的后面。直到快指针指向 null,算法结束 注意点:
- 为了避免插入到头节点之前的情况,设置一个 dummy 节点作为临时头节点
- 由于要把快指针的节点插到慢指针之后,那么就要把插入前快指针的前驱节点跟其后继节点相连,由于是单向链表,所以要维护快指针的前驱节点 fastPrv
public ListNode partition(ListNode head, int x) {
ListNode dummy = new ListNode(-1,head);
ListNode slow = dummy;
while(slow.next != null && slow.next.val < x) slow = slow.next;
ListNode fast = head,fastPrv = dummy;
while(fast != null && fast.val < x){
fast = fast.next;
fastPrv = fastPrv.next;
}
while(true){
while(fast != null && fast.val >= x){
fast = fast.next;
fastPrv = fastPrv.next;
}
if(fast == null) break;
fastPrv.next = fast.next;
fast.next = slow.next;
slow.next = fast;
slow = slow.next;
fast = fastPrv.next;
}
return dummy.next;
}
61.旋转链表
将链表尾接到链表头上形成循环链表,然后再根据 k 的大小,找到旋转后最后一个节点,将其与其下一个节点的链接断开,断开前先记录最后一个节点的下一个节点,也就是旋转后链表的头节点,断开后返回这个新头节点即可
public ListNode rotateRight(ListNode head, int k) {
if(head == null || head.next == null || k == 0) return head;
ListNode tmp = head;
int len = 1;
while(tmp.next != null){
tmp = tmp.next;
len++;
}
int m = k % len;
if(m == 0) return head;
tmp.next = head;
m = len - m;
while(m-- > 0){
tmp = tmp.next;
}
ListNode res = tmp.next;
tmp.next = null;
return res;
}
2. 两数相加
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode p1 = l1;
ListNode p2 = l2;
ListNode dummy = new ListNode();
ListNode tmp = dummy;
int extra = 0;
while(p1 != null && p2 != null){
tmp.next = new ListNode();
tmp = tmp.next;
int val = p1.val + p2.val + extra;
extra = val / 10;
tmp.val = val % 10;
p1 = p1.next;
p2 = p2.next;
}
while(p1 != null){
tmp.next = new ListNode();
tmp = tmp.next;
int val = p1.val + extra;
extra = val / 10;
tmp.val = val % 10;
p1 = p1.next;
}
while(p2 != null){
tmp.next = new ListNode();
tmp = tmp.next;
int val = p2.val + extra;
extra = val / 10;
tmp.val = val % 10;
p2 = p2.next;
}
if(extra != 0){
tmp.next = new ListNode(extra);
tmp.next.next = null;
}else {
tmp.next = null;
}
return dummy.next;
}
445. 两数相加 II
跟上面的 链表求和 不一样的地方在于这里的链表从头到尾是从高位到低位,所以利用栈将两个链表中的值反转过来就可以跟 链表求和 一样从低位到高位直接相加了 那怎么做到还是从头到尾只遍历一遍完就得到结果:
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
int count = 0;
ListNode head, last;
for(head = l1; head != null; head = head.next) count++;
for(head = l2; head != null; head = head.next) count--;
if(count < 0) {
ListNode t = l1;
l1 = l2;
l2 = t;
}
last = head = new ListNode(0);
head.next = l1;
for(int i = Math.abs(count); i != 0; i--){
if(l1.val != 9) last = l1;
l1 = l1.next;
}
int tmp;
while(l1 != null){
tmp = l1.val + l2.val;
if(tmp > 9){
tmp -= 10;
last.val += 1;
last = last.next;
while(last != l1){
last.val = 0;
last = last.next;
}
}
else if(tmp != 9) last = l1;
l1.val = tmp;
l1 = l1.next;
l2 = l2.next;
}
return head.val == 1 ? head : head.next;
}
上面的做法应该算是时间上最优解了
剑指 Offer II 023. 两个链表的第一个重合节点
假设有链表A以及链表B,它们重合部分长度为 l,链表A中除了重合部分外前面的部分长度为 l1,链表B中除了重合部分外前面的部分长度为 l2。 让两个指针p1,p2同时开始遍历A和B,且遍历完再去遍历另外一个链表。即对于p1来说,它先去遍历A,遍历完再去遍历B,当遍历B遍历到第一个重合节点时,其走过的路程为 l1 + l + l2;同理对于p2,当它遍历完B再去遍历A且遍历到第一个重合节点时,它走过的路程为 l2 + l + l1,可以发现两个指针走过的路程都是相等的,也就是说它们会在第一个重合节点相遇,所以相遇时直接返回它们所在节点即可
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA == null || headB == null) return null;
ListNode p1 = headA;
ListNode p2 = headB;
while(p1 != p2){
p1 = p1 == null ? headB : p1.next;
p2 = p2 == null ? headA : p2.next;
}
return p1;
}
剑指 Offer 22. 链表中倒数第k个节点(每日一题)
首先想到的做法应该是先遍历一遍链表求出链表的长度 len,然后再从头开始找到第 len - k + 1 个节点就是倒数第 k 个节点 这种做法需要两次遍历 O(n + n)。做单链表的题总要想想能不能只用一次遍历就解决问题,本题的做法就是:既然要找倒数第 k 个节点,如果有两个指针 p1 和 p2,p1 在前 p2 在后,它们距离一直为 k,那么当 p2 指向链表最后面的 null 的时候,p1 指向的不就是倒数第 k 个节点吗。所以先让 p2 走到第 k + 1 个节点的位置,然后再让 p1 从头开始,两个指针每次都一起向后走一步,当 p2 走到 null 的时候,p1 的位置就是倒数第 k 个节点
public ListNode getKthFromEnd(ListNode head, int k) {
if(head == null) return null;
ListNode p2 = head;
int endP2 = k + 1;
int count = 1;
while(p2 != null && count < endP2){
p2 = p2.next;
count++;
}
if(p2 == null){
if(count == endP2) return head;
return null;
}
ListNode p1 = head;
while(p2 != null){
p1 = p1.next;
p2 = p2.next;
}
return p1;
}
|