[LeetCode]-链表-2

前言

记录 LeetCode 刷题中遇到的链表相关题目,第二篇

142. 环形链表 II

环形链表2示例图

我们使用快慢双指针来完成这道题
假设链表有环,如上图所示,绿色部分即为环,从链表头 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 a = (n - 1)(b + c) + c a=(n1)(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; //prv指向前半部分链表
    boolean rec(ListNode n){
    
    
        if(n.next != null){
    
    
            if(!rec(n.next)) return false; //如果后面的节点已经出现了不相等的情况则直接返回false
        }
        if(n.val == prv.val){
    
     //否则就比较当前节点跟对应的前半部分中的节点是否相等,同时更新prv
            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;
        }
        //找到后半部分的起始节点,模拟几个例子可以发现,当fast不能继续往前走两步时
        //如果fast此时就是null,那么slow的位置就是后半部分的起始节点
        //不然的话就是fast的next是null,那么slow应该再走一步才是后半部分的起始节点
        if(fast != null) slow = slow.next;
        prv = head;
        return rec(slow);
    }
}

86. 分割链表

快慢双指针:保证慢指针往前的 (包括慢指针本身) 都是小于 x 的节点,慢指针的下一个就是大于等于 x 的节点;由快指针去找到小于 x 的节点,然后将其插入到慢指针的后面。直到快指针指向 null,算法结束

扫描二维码关注公众号,回复: 16538476 查看本文章

注意点:

  1. 为了避免插入到头节点之前的情况,设置一个 dummy 节点作为临时头节点
  2. 由于要把快指针的节点插到慢指针之后,那么就要把插入前快指针的前驱节点跟其后继节点相连,由于是单向链表,所以要维护快指针的前驱节点 fastPrv
public ListNode partition(ListNode head, int x) {
    
    
    ListNode dummy = new ListNode(-1,head);
    ListNode slow = dummy;
    //让 slow 指向第一个大于等于 x 的节点的之前一个节点
    while(slow.next != null && slow.next.val < x) slow = slow.next;
    ListNode fast = head,fastPrv = dummy;
    //让 fast 指向第一个大于等于 x 的节点
    while(fast != null && fast.val < x){
    
    
        fast = fast.next;
        fastPrv = fastPrv.next;
    }
    while(true){
    
    
    	//快指针向后查找小于等于 x 的节点
        while(fast != null && fast.val >= x){
    
    
            fast = fast.next;
            fastPrv = fastPrv.next;
        }
        if(fast == null) break; //注意及时判断是否遇到 null 及时 break
        //下面就是将fast所指节点插入到slow的后面,同时移动fast 跟 slow
        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++;
    }
    //k可能比原链表长度大,实际需要旋转的只有k%len个单位
    //如果k恰好为链表长度的倍数那就不用旋转 (旋转后跟原来链表是一样的)
    int m = k % len;
    if(m == 0) return head;
    //连成循环链表
    tmp.next = head;
    //查找旋转后链表最后一个节点,让tmp指向它
    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--;
    //让l1指向较长的链。如果count小于0,说明l2更长,那就交换。相加后的值存于l1中
    if(count < 0) {
    
      
        ListNode t = l1;
        l1 = l2;
        l2 = t;
    } 
    //head用于记录相加后链表的头节点
    //last用于记录上一个值小于9的节点,这样当某一位相加后大于9,就让last节点值增1,
    //然后让last节点与当前节点之间的所有节点 (值都为9) 的值都置为0,完成进位操作
    //在最前面加一个值为0的节点作为初始的last节点,防止最高位也产生进位,如果最终该节点值仍为0则删除该节点
    last = head = new ListNode(0); 
    head.next = l1;  //相加后的值存于l1中
    for(int i = Math.abs(count); i != 0; i--){
    
     //取l1中后面的与l2等长的部分与l2相加
        if(l1.val != 9) last = l1;
        l1 = l1.next;
    }
    int tmp;
    while(l1 != null){
    
    
        tmp = l1.val + l2.val;
        //发生进位,则更新last到l1之间所有数位的值,同时l1会成为新的last
        if(tmp > 9){
    
                       
            tmp -= 10;
            last.val += 1;
            last = last.next;
            while(last != l1){
    
    
                last.val = 0;
                last = last.next;
            }
        }
        //没有发生进位且tmp小于9的话,last就应该更新,指向l1;如果tmp就等于9,那last就保持不变
        else if(tmp != 9) last = l1; 
        l1.val = tmp;
        l1 = l1.next;
        l2 = l2.next;
    }
    return head.val == 1 ? head : head.next; //head等于1说明原来两个链表的最高位发生了进位,返回head
}

上面的做法应该算是时间上最优解了

剑指 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;
}

猜你喜欢

转载自blog.csdn.net/Pacifica_/article/details/124881023