代码随想录算法训练营第四天 | 24. 两两交换链表中的节点、19.删除链表的倒数第N个节点、160.链表相交、142.环形链表II

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的操作(比如删除),一般情况下对于链表,递归相对更清晰明了,并且需要一定数学思维来解决的题需要自求多福。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值