24. 两两交换链表中的节点
解析:代码随想录
这道题做过两遍了看到题目就知道怎么做,用的递归,递归操作起来感觉相对清晰明了,非递归的方法等等再想。
class Solution {
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null) return head;
ListNode node = head.next;
head.next = swapPairs(node.next);
node.next = head;
return node;
}
}
19.删除链表的倒数第N个节点
解析:代码随想录
这道题直接也做过,但是想不起来了。这次做用的递归。思路就是设置一个counter来记录,并且每次递归return当前node。当走到最后一个node之后开始counter++。在加完了之后,如果counter == 等于n代表走到要删除的node。直接return上一个node,当++counter == n + 1的时候表明走到要删除的node的前一个node,设置当前node的next等于上次返回的node也就是上上个node。最后一层层return,结束递归。
class Solution {
int count = 0;
public ListNode removeNthFromEnd(ListNode head, int n) {
if (head == null) return null;
ListNode tmp = removeNthFromEnd(head.next, n);
count++;
if (count == n) return tmp;
if (count == n + 1) head.next = tmp;
return head;
}
}
看了解析之后,本质上我的递归写法还是循环了两次,而通过设置快慢指针的解法可以只循环一次。又写了一遍,如下。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dum = new ListNode(0, head);
ListNode slow = dum, fast = dum;
while (n-- > 0) {
fast = fast.next;
}
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dum.next;
}
}
1. 快指针先走n次。
2. 快慢指针同时开始走,当快指针的next == null表明慢指针走到需要删除的节点的前一个节点。完成删除操作。
注意:设置一个next指向hea的虚拟指针来应对删除head的情况。
160.链表相交
解析:代码随想录
这道题一开始想的hashtable,但是空间不是O(1)。题目提示有空间O(1)的算法。最后想出来了。我把case1的在纸上画下来,发现重叠的部分的长度不可能超过min(l1.length, l2.length),所以将m-n得到ahead,再将较长的list的指针移动等于ahead的次数,最后开始一个个比较就行了。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode ta = headA, tb = headB;
int ca = 0, cb = 0;
while (ta != null) {
ca++;
ta = ta.next;
}
while (tb != null) {
cb++;
tb = tb.next;
}
ta = headA;
tb = headB;
if (ca > cb) {
int run = ca - cb;
while (run-- > 0) {
ta = ta.next;
}
} else {
int run = cb - ca;
while (run-- > 0) {
tb = tb.next;
}
}
while (ta != null) {
if (ta == tb) return ta;
ta = ta.next;
tb = tb.next;
}
return null;
}
}
然后看解析,是一个思路。但是我在看官方题解的时候发现了一个np的做法,如下。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode pA = headA;
ListNode pB = headB;
while (pA != pB) {
pA = pA == null ? headB : pA.next;
pB = pB == null ? headA : pB.next;
}
return pA;
// Note: In the case lists do not intersect, the pointers for A and B
// will still line up in the 2nd iteration, just that here won't be
// a common node down the list and both will reach their respective ends
// at the same time. So pA will be NULL in that case.
}
}
这个解法有两次循环:
1.第一次循环找到差值,先扫到了null的链表代表是较短链表,将其pointer重新设置为较长链表的第一个节点,这样在较长链表完成第一次循环的时候刚好刚刚的pointer走完他们直接的差值。这样保证了两个链表的left nodes数量相等,而交叉节点只存在于left nodes里面(原因已在第一个方法中分析)
2.第二次循环就是开始对比,如果相等,直接return。return的值只可能等于交叉点或者第二次循环最后的null值。
time O(n + m) spaceO(1)
142.环形链表II
解析:
自己做的时候用了hashset来记录,当第一次contains(head.next)等于true的时候head.next就是循环的开始节点。但是这个方法很慢而且需要空间消耗。
public class Solution {
public ListNode detectCycle(ListNode head) {
HashSet<ListNode> set = new HashSet();
ListNode snap = null;
while (head != null) {
set.add(head);
if (set.contains(head.next) == true) {
snap = head.next;
break;
}
head = head.next;
}
return snap;
}
}
看了解析之后,大为震撼,原来数学真的有用:
主要思路不再重复了,简单来说就是找出关系公式,在n = 1的时候通过消项可以得出,在有循环的情况下,从(head到循环开始节点的长度)等于(slow, fast节点相遇的节点再到循环开始的节点的长度)(如解析,x = z)通过这个关系式可写出如下代码。
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
while (head != null) {
if (head == slow) return slow;
head = head.next;
slow = slow.next;
}
}
}
return null;
}
}
总结
设置虚拟指针来应对潜在对于head的操作(比如删除),一般情况下对于链表,递归相对更清晰明了,并且需要一定数学思维来解决的题需要自求多福。