代码随想录算法训练营day04|24. 两两交换链表中的节点 19.删除链表的倒数第N个节点 面试题 02.07. 链表相交 142.环形链表II

181 阅读5分钟

24. 两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

24. 两两交换链表中的节点 - 力扣(Leetcode)

思路:

  1. 当链表为空或者链表只有头节点这一个节点时,不用进行交换。
  2. 当链表长度>=2时,
    • 新链表的头节点一定是原链表的第二个节点,因此,在交换前需要将这个节点进行保存。
    • 如果链表长度为偶数,则链表中所有节点都可以进行两两交换;如果链表长度为奇数,则链表的最后一个元素不用进行交换。
    • 在一次两两交换中,主要涉及到的节点有3个,即要进行交换的两个节点和第一个节点的前驱节点。一次两两交换的过程如图: 两两交换链表中的节点.jpg
  3. 对于链表的头节点,可以使用一个虚拟头节点,令其指向头结点,使得头两个节点的处理方法与其他节点的处理方法相同。
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode swapPairs(ListNode head) {

        if(head==null||head.next==null){
            return head;
        }

        // head有两个或以上的节点,返回必然是第二个节点,在交换前先保存起来
        ListNode res=head.next;

        //两两交换
        ListNode dummy=new ListNode(-1);
        dummy.next=head;

        ListNode first=head,second=head.next,nextFirst=null;
        while(first!=null&&second!=null){
            nextFirst=second.next;

            second.next=first;
            first.next=nextFirst;
            dummy.next=second;

            dummy=first;
            first=nextFirst;
            second=(first==null)?null:first.next;
        }

        return res;
    }
}

19.删除链表的倒数第N个节点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

19. 删除链表的倒数第 N 个结点 - 力扣(Leetcode)

思路:
AB从同一位置出发,其中A呆在原地不动,B向前走n步,那么以B为原点,A相对于B的距离为-n步。此后,AB同步调前进,当B到达终点时,A就达到距离终点n步的位置。
将这种方法类比到链表中,让两个指针都从头节点或虚拟头节点出发,其中一个指针先向前走n个结点,之后,两个节点同步调前进,直到先出发的节点走到链表的最后一个节点。 边界条件:删除的节点是头节点时,先出发的节点最终指向了空指针。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode pre=head,end=head;

        for(int i=0;i<n;i++){
            end=end.next;
        }

        if(end==null){
            //删除的是头节点
            return head.next;
        }

        while(end.next!=null){
            pre=pre.next;
            end=end.next;
        }

        // 要删除的是pre的后继结点,且此节点必然存在
        // pre.next不是空指针
        // 使用end保存pre的新的后继结点
        end=pre.next.next;

        pre.next=end;

        return head;

    }
}

面试题 02.07. 链表相交

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。

思路:
两个单链表如果相交,那么这两链表从相交的节点到最后一个节点为止都是相同的节点。将两条链表从末尾对齐,如果一条链表比较长,相交的部分不可能出现在这条链表前部多出来的一部分。
因此,可以先求出两条链表的长度差distance,将两个指针分别指向两条链表的头节点,让指向长链表的指针率先移动distance个节点,这样,两条链表就相当于同一起点和同一终点。
然后使用这两个指针分别对两条链表进行遍历,并比较它们指向的节点是否是同一个节点。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        int lenA=getLength(headA);
        int lenB=getLength(headB);
        
        //为避免讨论A长还是B长
        //让head指向较长的链表
        //让head指向较短的链表
        ListNode headL=lenA>lenB?headA:headB;
        ListNode headS=lenA>lenB?headB:headA;

        for(int i=0;i<Math.abs(lenA-lenB);i++){
            headL=headL.next;
        }

        while(headL!=null){
            if(headL==headS){
                return headL;
            }
            headL=headL.next;
            headS=headS.next;
        }

        return null;
    }

    private int getLength(ListNode head){
        int len=0;
        while(head!=null){
            len++;
            head=head.next;
        }
        return len;
    }
}

142.环形链表II

给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。 不允许修改 链表。

142. 环形链表 II - 力扣(Leetcode)

参考:programmercarl.com/0142.%E7%8E…

思路:设置slowfast指针,slow每次走一个节点,fast每次走两个结点,如果链表中存在环,因为两个指针的速度不同,无法保持相对静止,必然会在环中某一个节点相遇。如果fast在过程中指向了null,这意味着fast遍历完了整个链表,链表没有形成环。
本题的难点在于如何计算出环的入口。结论1是两个指针第一次相遇时slow在环内未走完一圈(证明看参考链接)。
设头节点到入口处的距离是xx,入口到相遇点的距离是yy,相遇点到入口点的距离是zz
当两个指针相遇时,slow的路程是x+yx+yfast的路程是x+y+n×(xy+z)x+y+n \times (xy+z)
因为 2×v(slow)=v(fast)2 \times v(slow) = v(fast)
所以 2×(x+y)=x+y+n×(y+z) 2\times (x+y) = x+y+n \times (y+z)
要求xx,移项得:x=(n1)×(y+z)+zx=(n-1) \times (y+z)+z。 因此当n=1n=1时,x=zx=z
这意味着同时从头节点和相遇点出发,以相同速度移动,两指针再次相遇时,相遇点就是环的入口节点。

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        //边界条件,空链表或只有头节点的链表,不会形成环
        if(head==null||head.next==null){
            return null;
        }

        ListNode slow=head,fast=head;
        while(fast!=null&&fast.next!=null){
            slow=slow.next;
            fast=fast.next.next;
            if(slow==fast){
                //到达相遇点
                // fast从相遇出发
                // slow从头节点出发
                slow=head;
                while(slow!=fast){
                    slow=slow.next;
                    fast=fast.next;
                }
                return fast;
            }
        }

        return null;
    }
}