【力扣一刷】代码随想录day4( 链表总结、24、 19、面试题 02.07、142)

【链表总结】

1、链表的特性:查慢改快

2、虚拟头节点:解决链表题的重要技巧,用于避免对头节点的特殊处理

3、建议的链表题刷题顺序:

707.设计链表 - 掌握节点的定义、查询、插入和删除操作,熟悉虚拟头节点的技巧

206.反转链表、24.两两交换链表中的节点 - 掌握节点交换和节点遍历

142.环形链表II(结合数学证明) - 掌握快慢指针的巧妙应用

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

方法  三指针法

1、特殊情况处理:如果链表为空或者只有1个节点,则直接返回head

2、基本情况处理:当链表中含有2个及以上节点时,返回值一定是head.next

  • 交换节点  

        涉及三个节点,前节点pre、当前节点cur、后节点after

  • 继续遍历 

        需要注意,步骤一后,cur和after在链表中的顺序发生变化,重新赋值时要谨慎处理

/**
 * 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;  // 如果0个/1个节点,直接返回head

        ListNode target = head.next;
        ListNode pre = new ListNode(-1, head);
        ListNode cur = head;
        ListNode after = cur.next;
        
        // 将当前节点cur与下一个节点after进行交换(如果只剩一个节点就不进入循环)
        while (cur != null && after != null){
            // 交换节点
            cur.next = after.next;
            after.next = cur;
            pre.next = after;

            // 继续遍历
            if (cur.next == null){ // 后面没节点了
                break;
            }
            else{  // 后面还有节点
                pre = cur;
                cur = pre.next;
                after = cur.next;
            }
        }
        return target;
    }
}

时间复杂度: O(n)

空间复杂度: O(1)

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

方法一  两次for循环

第一次for循环:计算节点的个数size

第二次for循环:令cur指向目标节点的上一个节点,删除目标节点

/**
 * 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 prehead = new ListNode(-1, head);
        ListNode cur = head;

        // 计算节点的数量
        int size = 0;
        while (cur != null){
            size++;
            cur = cur.next;
        }

        // 找到要删除节点的前一个节点
        cur = prehead;
        for (int i = 0; i < size - n; i++){
           cur = cur.next; 
        }
        cur.next = cur.next.next; // 删除目标节点

        return prehead.next;
    }
}

时间复杂度: O(n)

空间复杂度: O(1)

方法二  快慢指针(双指针技巧)

思路:

1、快指针先走n+1步,目的是最后让slow指向目标节点的前一个节点

2、快慢指针一起走,直至快指针指向末尾的null

/**
 * 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 prehead = new ListNode(-1, head);
        ListNode fast = prehead;
        ListNode slow = prehead;

        // 让快指针先走n+1步,则最后slow会在倒数第n个节点的上一个节点
        for (int i = 0; i < n + 1; i++)
        {
            fast = fast.next;
        }

        // 快慢指针一起走,直到快指针指向末尾的null
        while(fast != null){
            fast = fast.next;
            slow = slow.next;
        }
        
        // 删除节点
        slow.next = slow.next.next;

        return prehead.next;
    }
}

时间复杂度: O(n)

空间复杂度: O(1)

【面试题 02.07. 链表相交 】

方法  双指针

思路:尾部对齐,从重叠的第一个节点同时开始遍历

1、分别计算两个链表的长度

2、链表长的节点先走delta步(delta是指长度差)

3、遍历相同个数的节点,寻找相交的节点

注意:相交节点是指内存地址相同的节点,不是值相同的节点

/**
 * 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) {
        ListNode curA = headA;
        ListNode curB = headB;

        // 分别计算两个链表的长度
        int sizeA = 0;
        int sizeB = 0;
        while (curA != null){
            sizeA++;
            curA = curA.next;
        }
        while (curB != null){
            sizeB++;
            curB = curB.next;
        }

        // 链表长的节点先走delta步
        int delta = Math.abs(sizeA - sizeB);
        curA = headA;
        curB = headB;
        if (sizeA > sizeB){
            for (int i = 0; i < delta; i++){
                curA = curA.next;
            }
        }
        else{
            for (int i = 0; i < delta; i++){
                curB = curB.next;
            }
        }

        // 遍历相同个数的节点,寻找相交的节点
        while (curA != null){
            if (curA == curB){
                return curA;
            }
            curA = curA.next;
            curB = curB.next;
        }
        return null;
    }
}

时间复杂度: O(m+n),m是链表A的长度,n是链表B的长度

空间复杂度: O(1)

【142.环形链表II 】

方法  快慢指针

如何证明 x = z ?点此处查看

思路:

1、确定是否有环

无环:fast到达null,返回null

有环:fast和slow相遇,记录第一次相遇的位置index

2、利用 x = z 找出环开始的节点

令一个指针从链表头head出发’(走x),另一个指针从相遇节点index出发(走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) {
        ListNode fast = head;
        ListNode slow = head;
        ListNode index = null;

        while (fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
            // 如果相遇,让index指向相遇的节点
            if (fast == slow){
                index = fast;
                break;
            }
        }
        // 如果快慢指针没相遇,则index未被赋值,依然为null,即无环,返回null
        if (index == null) {
            return null;
        }

        // 有环,继续寻找环的开始节点
        slow = head;
        fast = index;
        while (slow != fast){
            slow = slow.next;
            fast = fast.next;
        }  

        // while循环结束后,slow=fast,快慢指针相遇,刚好是环开始的节点,返回fast/slow即可
        return fast;
    }
}

时间复杂度: O(n)

空间复杂度: O(1)

  • 12
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值