代码随想录第四天

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

题目链接/文章讲解/视频讲解: https://programmercarl.com/0024.%E4%B8%A4%E4%B8%A4%E4%BA%A4%E6%8D%A2%E9%93%BE%E8%A1%A8%E4%B8%AD%E7%9A%84%E8%8A%82%E7%82%B9.html

思路

链表: 0 -> 1 -> 2 -> 3

假设两两交换的元素为链表中的1、2,那么切断2的前后连接

重建连接,将2指向1,1指向3,0指向2

那么就是需要3步:

考虑实施的顺序

完整代码

// 递归版本
class Solution {
    public ListNode swapPairs(ListNode head) {
        // base case 退出提交
        if(head == null || head.next == null) return head;
        // 获取当前节点的下一个节点
        ListNode next = head.next;
        // 进行递归
        ListNode newNode = swapPairs(next.next);
        // 这里进行交换
        next.next = head;
        head.next = newNode;

        return next;
    }
} 
class Solution {
  public ListNode swapPairs(ListNode head) {
        ListNode dumyhead = new ListNode(-1); // 设置一个虚拟头结点
        dumyhead.next = head; // 将虚拟头结点指向head,这样方便后面做删除操作
        ListNode cur = dumyhead;
        ListNode temp; // 临时节点,保存两个节点后面的节点
        ListNode firstnode; // 临时节点,保存两个节点之中的第一个节点
        ListNode secondnode; // 临时节点,保存两个节点之中的第二个节点
        while (cur.next != null && cur.next.next != null) {
            temp = cur.next.next.next;
            firstnode = cur.next;
            secondnode = cur.next.next;
            cur.next = secondnode;       // 步骤一
            secondnode.next = firstnode; // 步骤二
            firstnode.next = temp;      // 步骤三
            cur = firstnode; // cur移动,准备下一轮交换
        }
        return dumyhead.next;  
    }
}

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

双指针的操作,要注意,删除第N个节点,那么我们当前遍历的指针一定要指向 第N个节点的前一个节点,建议先看视频。

题目链接/文章讲解/视频讲解:https://programmercarl.com/0019.%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E7%9A%84%E5%80%92%E6%95%B0%E7%AC%ACN%E4%B8%AA%E8%8A%82%E7%82%B9.html

思路

首先创建了一个虚拟头节点 dummy,将其 next 指针指向原链表的头节点。这样可以避免删除头节点时需要特殊处理。

接着,我们创建了快慢指针 fastslow,初始时均指向虚拟头节点 dummy。将 fast 指针向前移动 n+1 步后,fastslow 之间的距离为 n+1,这样当 fast 指向最后一个节点时,slow 指向的就是倒数第 n+1 个节点。

最后,我们将 slownext 指针指向 slow.next.next,即删除倒数第 n 个节点。注意,这里我们没有修改节点的值,只是修改了节点之间的连接关系。

时间复杂度为 O(L),其中 L 是链表的长度。由于我们只需遍历一次链表,因此时间复杂度为线性。空间复杂度为 O(1),只需要常数级别的额外空间来存储指针。

步骤

  1. 创建虚拟头节点 dummy,将其 next 指针指向原链表的头节点。
  2. 创建快慢指针 fastslow,初始时均指向虚拟头节点 dummy
  3. fast 指针向前移动 n+1 步,此时 fastslow 之间的距离为 n+1。
  4. 同时移动 fastslow 指针,直到 fast 指向最后一个节点,此时 slow 指针指向倒数第 n+1 个节点。
  5. slownext 指针指向 slow.next.next,即删除倒数第 n 个节点。
  6. 返回虚拟头节点 dummynext 指针,即为删除倒数第 n 个节点后的链表的头节点。

完整代码

public ListNode removeNthFromEnd(ListNode head, int n) {
    // 创建虚拟头节点
    ListNode dummy = new ListNode(-1);
    dummy.next = head;
    // 创建快慢指针
    ListNode fast = dummy;
    ListNode slow = dummy;
    // 将 fast 指针向前移动 n+1 步
    for (int i = 0; i < n + 1; i++) {
        fast = fast.next;
    }
    // 同时移动 fast 和 slow 指针
    while (fast != null) {
        fast = fast.next;
        slow = slow.next;
    }
    // 将 slow 的 next 指针指向 slow.next.next,即删除倒数第 n 个节点
    slow.next = slow.next.next;
    // 返回虚拟头节点的 next 指针,即为删除倒数第 n 个节点后的链表的头节点
    return dummy.next;
}

面试题 02.07. 链表相交

本题没有视频讲解,大家注意 数值相同,不代表指针相同。

题目链接/文章讲解:https://programmercarl.com/%E9%9D%A2%E8%AF%95%E9%A2%9802.07.%E9%93%BE%E8%A1%A8%E7%9B%B8%E4%BA%A4.html

思路

首先通过遍历链表得到它们的长度 lenA 和 lenB。然后根据长度差,将指针 pA 和 pB 移动到同一起点位置。

接着,我们同时移动指针 pA 和 pB,如果它们相等,则表示找到了相交点,返回相交点即可。如果同时为空,则表示两个链表没有相交点,返回 null。

时间复杂度为 O(m + n),其中 m 和 n 分别是链表 headA 和 headB 的长度。由于我们需要遍历两个链表,因此时间复杂度为线性。空间复杂度为 O(1),只需要常数级别的额外空间来存储指针和计算长度。

步骤

  1. 分别遍历链表 headA 和 headB,得到它们的长度分别为 lenA 和 lenB。
  2. 让指针 pA 指向 headA 的头节点,指针 pB 指向 headB 的头节点。
  3. 如果 lenA > lenB,则将指针 pA 向前移动 lenA - lenB 步,使得 pA 和 pB 处于同一起点位置。 如果 lenB > lenA,则将指针 pB 向前移动 lenB - lenA 步,使得 pA 和 pB 处于同一起点位置。
  4. 同时移动指针 pA 和 pB,直到它们相等或者同时为空。如果相等,则表示找到了相交点,返回相交点即可。 如果同时为空,则表示两个链表没有相交点,返回 null。

完整代码

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    // 获取链表A和链表B的长度
    int lenA = getLength(headA);
    int lenB = getLength(headB);
    
    // 将指针pA指向链表A的头节点,指针pB指向链表B的头节点
    ListNode pA = headA;
    ListNode pB = headB;
    
    // 让指针pA和pB处于同一起点位置
    if (lenA > lenB) {
        int diff = lenA - lenB;
        while (diff > 0) {
            pA = pA.next;
            diff--;
        }
    } else if (lenB > lenA) {
        int diff = lenB - lenA;
        while (diff > 0) {
            pB = pB.next;
            diff--;
        }
    }
    
    // 同时移动指针pA和pB,直到它们相等或者同时为空
    while (pA != null && pB != null) {
        if (pA == pB) {
            return pA; // 找到相交点
        }
        pA = pA.next;
        pB = pB.next;
    }
    
    return null; // 两个链表没有相交点,返回null
}

// 计算链表的长度
private int getLength(ListNode head) {
    int length = 0;
    ListNode current = head;
    while (current != null) {
        length++;
        current = current.next;
    }
    return length;
}

142.环形链表II

题目链接/文章讲解/视频讲解:https://programmercarl.com/0142.%E7%8E%AF%E5%BD%A2%E9%93%BE%E8%A1%A8II.html

思路

使用快慢指针找到链表中的相遇点。然后将慢指针 slow 重置为链表头节点,同时让 slow 和 fast 指针每次移动一步,直到它们再次相遇。相遇点即为环的起始节点。

时间复杂度为 O(n),其中 n 是链表的长度。由于我们使用快慢指针遍历链表,时间复杂度为线性。空间复杂度为 O(1),只需要常数级别的额外空间来存储指针。

步骤

  1. 使用两个指针 slow 和 fast,初始时都指向链表头节点 head。
  2. slow 指针每次移动一步,fast 指针每次移动两步,直到两个指针相遇或者 fast 指针达到链表末尾(即链表没有环)。
  3. 如果 fast 指针达到链表末尾,说明链表没有环,返回 null。
  4. 如果两个指针相遇,将 slow 指针重置为链表头节点 head,并且让 slow 和 fast 指针同时每次移动一步,直到它们再次相遇。 相遇点即为环的起始节点。

完整代码

public ListNode detectCycle(ListNode head) {
    ListNode slow = head;
    ListNode fast = head;
    
    // 快慢指针找到相遇点
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        if (slow == fast) {
            break;
        }
    }
    
    // 如果fast指针达到链表末尾,说明链表没有环,返回null
    if (fast == null || fast.next == null) {
        return null;
    }
    
    // 将slow指针重置为链表头节点
    slow = head;
    
    // slow和fast指针同时每次移动一步,直到它们再次相遇
    while (slow != fast) {
        slow = slow.next;
        fast = fast.next;
    }
    
    return slow; // 返回相遇点,即环的起始节点
}

你可能感兴趣的:(java,开发语言)