代码随想录-Day04

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

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

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode temp = dummyHead;
        while (temp.next != null && temp.next.next != null) {
            ListNode node1 = temp.next;
            ListNode node2 = temp.next.next;
            temp.next = node2;
            node1.next = node2.next;
            node2.next = node1;
            temp = node1;
        }
        return dummyHead.next;
    }
}

这段代码定义了一个名为Solution的类,其中包含一个方法swapPairs,用于实现链表中每相邻两个节点的值进行交换。链表节点的定义(ListNode)虽未直接给出,但可根据上下文推断其结构大致如下:

class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}

方法逻辑解析:

  1. 创建哑节点(dummyHead):

    ListNode dummyHead = new ListNode(0);
    dummyHead.next = head;
    ListNode temp = dummyHead;
    
    • 创建一个值为0的哑节点dummyHead,其作用是为了简化边界条件处理,避免在交换头节点时的特殊判断。
    • dummyHead的下一个节点指向原链表的头节点head
    • 引入一个辅助指针temp指向dummyHead,用于遍历链表。
  2. 循环交换节点:

    while (temp.next != null && temp.next.next != null) {
        // 交换逻辑
    }
    
    • 循环条件确保至少有两个节点可以参与交换,直到遍历到倒数第二个节点为止。
  3. 具体交换操作:

    ListNode node1 = temp.next;
    ListNode node2 = temp.next.next;
    temp.next = node2;
    node1.next = node2.next;
    node2.next = node1;
    temp = node1;
    
    • node1node2分别暂存待交换的两个节点。
    • 更新temp.nextnode2,完成第一次交换,此时temp后的两个节点已经交换。
    • 更新node1.nextnode2.next,准备将node1置于node2之后。
    • 更新node2.nextnode1,完成第二次交换,恢复链表的连续性。
    • 移动tempnode1,准备下一轮交换。
  4. 返回结果:

    return dummyHead.next;
    
    • 由于最初的头节点可能已被交换,直接返回dummyHead.next作为新链表的头节点。

总结

这段代码通过引入哑节点简化了链表头节点处理的问题,使用循环和简单的指针操作实现了链表中相邻节点值的成对交换,其时间复杂度为O(n),空间复杂度为O(1),是一种高效且简洁的解决方案。

为了将 swapPairs 方法改成符合ACM竞赛模式,我们需要调整代码以使用标准输入和输出,并定义 ListNode 类。以下是修改后的代码:

import java.util.Scanner;

class ListNode {
    int val;
    ListNode next;

    ListNode(int x) {
        val = x;
    }
}

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        ListNode temp = dummyHead;
        while (temp.next != null && temp.next.next != null) {
            ListNode node1 = temp.next;
            ListNode node2 = temp.next.next;
            temp.next = node2;
            node1.next = node2.next;
            node2.next = node1;
            temp = node1;
        }
        return dummyHead.next;
    }
}

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // 读取链表的元素个数
        int n = scanner.nextInt();

        // 创建链表并读取链表元素
        ListNode dummy = new ListNode(0);
        ListNode current = dummy;
        for (int i = 0; i < n; i++) {
            current.next = new ListNode(scanner.nextInt());
            current = current.next;
        }

        // 创建Solution对象并调用swapPairs方法
        Solution solution = new Solution();
        ListNode result = solution.swapPairs(dummy.next);

        // 输出结果链表
        current = result;
        while (current != null) {
            System.out.print(current.val + " ");
            current = current.next;
        }
        System.out.println();
    }
}

说明

  1. 输入格式

    • 第一行输入一个整数 n,表示链表的元素个数。
    • 接下来的一行输入 n 个整数,表示链表的元素。
  2. 输出格式

    • 输出经过两两交换后的链表,每个元素之间用空格隔开。

使用示例

假设输入如下:

4
1 2 3 4

程序会输出:

2 1 4 3

因为链表 [1, 2, 3, 4] 经过两两交换后,结果链表为 [2, 1, 4, 3]

可以将这段代码保存到一个文件中,比如 Main.java,然后在命令行中使用 javac Main.java 进行编译,使用 java Main 运行,并在运行时通过标准输入提供数据。

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

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
在这里插入图片描述

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0, head);
        int length = getLength(head);
        ListNode cur = dummy;
        for (int i = 1; i < length - n + 1; ++i) {
            cur = cur.next;
        }
        cur.next = cur.next.next;
        ListNode ans = dummy.next;
        return ans;
    }

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

这段代码定义了一个名为Solution的类,其中包含两个方法:removeNthFromEndgetLength。这两个方法共同作用于单链表(ListNode类型),用于实现从链表的末尾移除第n个节点的功能。

方法一:removeNthFromEnd

功能:移除链表中倒数第n个节点,并返回修改后的链表头节点。

参数

  • ListNode head:链表的头节点。
  • int n:要移除的节点位置,从链表末尾计数。

逻辑

  1. 创建哑节点(dummy node):首先创建一个值为0的新节点dummy,并将它的下一个节点指向head。这样做是为了处理边界情况,即当移除头节点时,可以直接通过dummy返回新的头节点。
  2. 计算链表长度:调用getLength方法获取链表的长度。
  3. 定位并移除目标节点:从哑节点开始遍历,遍历到倒数第n+1个节点(即需要移除节点的前一个节点)停止,然后更新该节点的next指针,绕过待移除节点。
  4. 返回结果:返回dummy.next,即修改后的链表头节点。

方法二:getLength

功能:计算链表的长度。

参数

  • ListNode head:链表的头节点。

逻辑

  • 通过一个循环遍历链表,每遇到一个节点就将长度加1,直到链表末尾(head == null)。

总结

这段代码通过引入哑节点简化了对头节点的特殊处理,先计算链表长度,再通过一次遍历定位并移除目标节点,实现了从链表末尾移除指定节点的功能。这种方法的时间复杂度为O(N),空间复杂度为O(1),其中N为链表长度。

为了将 removeNthFromEnd 方法改成符合ACM竞赛模式,我们需要调整代码以使用标准输入和输出,并定义 ListNode 类。以下是修改后的代码:

import java.util.Scanner;

class ListNode {
    int val;
    ListNode next;

    ListNode(int x) {
        val = x;
    }

    ListNode(int x, ListNode next) {
        val = x;
        this.next = next;
    }
}

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0, head);
        int length = getLength(head);
        ListNode cur = dummy;
        for (int i = 1; i < length - n + 1; ++i) {
            cur = cur.next;
        }
        cur.next = cur.next.next;
        return dummy.next;
    }

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

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // 读取链表的元素个数
        int n = scanner.nextInt();

        // 创建链表并读取链表元素
        ListNode dummy = new ListNode(0);
        ListNode current = dummy;
        for (int i = 0; i < n; i++) {
            current.next = new ListNode(scanner.nextInt());
            current = current.next;
        }

        // 读取要删除的第 n 个节点
        int k = scanner.nextInt();

        // 创建Solution对象并调用removeNthFromEnd方法
        Solution solution = new Solution();
        ListNode result = solution.removeNthFromEnd(dummy.next, k);

        // 输出结果链表
        current = result;
        while (current != null) {
            System.out.print(current.val + " ");
            current = current.next;
        }
        System.out.println();
    }
}

说明

  1. 输入格式

    • 第一行输入一个整数 n,表示链表的元素个数。
    • 接下来的一行输入 n 个整数,表示链表的元素。
    • 最后一行输入一个整数 k,表示要从链表末尾删除的第 k 个节点。
  2. 输出格式

    • 输出删除第 k 个节点后的链表,每个元素之间用空格隔开。

使用示例

假设输入如下:

5
1 2 3 4 5
2

程序会输出:

1 2 3 5

因为链表 [1, 2, 3, 4, 5] 中删除倒数第 2 个节点后,结果链表为 [1, 2, 3, 5]

可以将这段代码保存到一个文件中,比如 Main.java,然后在命令行中使用 javac Main.java 进行编译,使用 java Main 运行,并在运行时通过标准输入提供数据。

链表相交

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
在这里插入图片描述

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        Set<ListNode> visited = new HashSet<ListNode>();
        ListNode temp = headA;
        while (temp != null) {
            visited.add(temp);
            temp = temp.next;
        }
        temp = headB;
        while (temp != null) {
            if (visited.contains(temp)) {
                return temp;
            }
            temp = temp.next;
        }
        return null;
    }
}

这段代码是一个Java程序,实现了一个名为Solution的类,其中包含一个方法getIntersectionNode。这个方法用于找到两个链表(ListNode类型)的交点,即第一个同时存在于两个链表中的节点。如果两个链表没有交点,该方法返回null。这里的ListNode是一个链表节点类,虽然具体定义没有给出,但通常包含一个值字段(val)和一个指向下一个节点的指针(next)。

方法的具体实现思路是:

  1. 使用HashSet记录第一个链表的所有节点:

    • 首先,程序创建一个名为visited的哈希集(HashSet),用于存储链表A中访问过的节点。
    • 通过一个while循环遍历链表A(headA),将遍历到的每个节点都添加到visited集合中。这样,集合visited最终将包含链表A的所有唯一节点。
  2. 检查第二个链表的节点是否存在于哈希集中:

    • 然后,程序开始遍历链表B(headB),使用另一个while循环,并在每次迭代中检查当前节点temp是否已经在visited集合中。
    • 如果发现某个节点存在于visited集合中,这意味着该节点同时存在于链表A和B中,即为两个链表的交点,于是直接返回这个节点。
    • 如果遍历完链表B都没有找到交点,则返回null,表示两个链表没有交集。

这种方法的时间复杂度为O(m+n),其中m和n分别是两个链表的长度。空间复杂度也是O(min(m,n)),因为在最坏的情况下,需要存储一个链表的所有节点。这是一种简单直观的解法,但不是最优的,最优解法(如双指针法)可以在O(m+n)时间内完成,且只需O(1)的额外空间。
为了将 getIntersectionNode 方法改成符合ACM竞赛模式,我们需要调整代码以使用标准输入和输出,并定义 ListNode 类。以下是修改后的代码:

import java.util.Scanner;
import java.util.HashSet;
import java.util.Set;

class ListNode {
    int val;
    ListNode next;

    ListNode(int x) {
        val = x;
        next = null;
    }
}

class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        Set<ListNode> visited = new HashSet<>();
        ListNode temp = headA;
        while (temp != null) {
            visited.add(temp);
            temp = temp.next;
        }
        temp = headB;
        while (temp != null) {
            if (visited.contains(temp)) {
                return temp;
            }
            temp = temp.next;
        }
        return null;
    }
}

public class Main {
    private static ListNode buildList(Scanner scanner) {
        int n = scanner.nextInt();
        if (n == 0) return null;
        ListNode dummy = new ListNode(0);
        ListNode current = dummy;
        for (int i = 0; i < n; i++) {
            current.next = new ListNode(scanner.nextInt());
            current = current.next;
        }
        return dummy.next;
    }

    private static void printList(ListNode head) {
        while (head != null) {
            System.out.print(head.val + " ");
            head = head.next;
        }
        System.out.println();
    }

    private static ListNode findNode(ListNode head, int pos) {
        while (pos-- > 0 && head != null) {
            head = head.next;
        }
        return head;
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // Build first linked list
        ListNode headA = buildList(scanner);

        // Build second linked list
        ListNode headB = buildList(scanner);

        // Read intersection position for the first list (0-indexed, -1 if no intersection)
        int intersectA = scanner.nextInt();

        // Read intersection position for the second list (0-indexed, -1 if no intersection)
        int intersectB = scanner.nextInt();

        // If both positions are valid, create intersection
        if (intersectA != -1 && intersectB != -1) {
            ListNode nodeA = findNode(headA, intersectA);
            ListNode nodeB = findNode(headB, intersectB);
            if (nodeA != null && nodeB != null) {
                nodeB.next = nodeA;
            }
        }

        // Create Solution object and find the intersection node
        Solution solution = new Solution();
        ListNode intersection = solution.getIntersectionNode(headA, headB);

        // Output the result
        if (intersection != null) {
            System.out.println(intersection.val);
        } else {
            System.out.println("null");
        }
    }
}

说明

  1. 输入格式

    • 第一个整数表示第一个链表的长度 n
    • 接下来 n 个整数表示第一个链表的元素。
    • 第二个整数表示第二个链表的长度 m
    • 接下来 m 个整数表示第二个链表的元素。
    • 一个整数 intersectA 表示第一个链表中的交叉点(0索引,-1表示没有交叉)。
    • 一个整数 intersectB 表示第二个链表中的交叉点(0索引,-1表示没有交叉)。
  2. 输出格式

    • 输出交点的值,如果没有交点则输出 “null”。

使用示例

假设输入如下:

5
4 1 8 4 5
6
5 6 1 8 4 5
2
3

程序会输出:

8

因为链表在节点值为 8 处相交。

可以将这段代码保存到一个文件中,比如 Main.java,然后在命令行中使用 javac Main.java 进行编译,使用 java Main 运行,并在运行时通过标准输入提供数据。

方法二:双指针

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }
        ListNode pA = headA, pB = headB;
        while (pA != pB) {
            pA = pA == null ? headB : pA.next;
            pB = pB == null ? headA : pB.next;
        }
        return pA;
    }
}

这段代码同样定义了一个名为Solution的类,其中包含一个方法getIntersectionNode,用于寻找两个链表(由ListNode节点组成)的交点。与之前提供的解决方案不同,这个方法采用了“双指针”技巧,其核心思想是让两个指针分别从两个链表的头开始遍历,当一个指针到达链表末尾时,将其重新定位到另一个链表的头部继续遍历。这种方法假设链表是循环的,或者两个链表在某个节点相遇(如果有交点的话)。下面是详细解释:

  • 参数说明:

    • headA:链表A的头节点。
    • headB:链表B的头节点。
  • 逻辑步骤:

    1. 初始化指针:定义两个指针pApB,分别指向两个链表的头节点headAheadB

    2. 循环遍历:进入一个循环,条件是pApB不相等。在循环体内:

      • 如果pA走到链表末尾(即pA == null),则将其重置为headB的头节点,继续遍历。
      • 如果pB走到链表末尾(即pB == null),则将其重置为headA的头节点,继续遍历。
      • 分别将pApB向后移动一位(pA = pA.nextpB = pB.next)。
    3. 返回交点:当pA == pB时,说明它们相遇在交点,直接返回该节点。如果循环结束仍未相遇,则说明没有交点,但由于题目的设定保证了如果有交点一定会在有限步内相遇,所以这种情况理论上不会发生,但正确的做法是在循环外部加一个返回null的逻辑以应对非预期情况,尽管在这个特定的循环逻辑中这不是必须的,因为循环必定会结束于一个交点或两指针同时为null(这在给定的代码逻辑中不会发生)。

  • 时间复杂度:O(N+M),其中N和M分别是两个链表的长度。在最坏的情况下,两个指针会遍历完两个链表的全部节点。

  • 空间复杂度:O(1),只使用了两个指针,没有使用额外的存储空间。

这种方法巧妙地利用了链表可能形成环的特性(即便原始链表不构成环,通过相互遍历也形成了一个逻辑上的环),从而避免了额外的存储空间(如哈希表)需求,提高了空间效率。

为了将 getIntersectionNode 方法改成符合ACM竞赛模式,我们需要调整代码以使用标准输入和输出,并定义 ListNode 类。以下是修改后的代码:

import java.util.Scanner;

class ListNode {
    int val;
    ListNode next;

    ListNode(int x) {
        val = x;
        next = null;
    }
}

class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }
        ListNode pA = headA, pB = headB;
        while (pA != pB) {
            pA = pA == null ? headB : pA.next;
            pB = pB == null ? headA : pB.next;
        }
        return pA;
    }
}

public class Main {
    private static ListNode buildList(Scanner scanner) {
        int n = scanner.nextInt();
        if (n == 0) return null;
        ListNode dummy = new ListNode(0);
        ListNode current = dummy;
        for (int i = 0; i < n; i++) {
            current.next = new ListNode(scanner.nextInt());
            current = current.next;
        }
        return dummy.next;
    }

    private static void printList(ListNode head) {
        while (head != null) {
            System.out.print(head.val + " ");
            head = head.next;
        }
        System.out.println();
    }

    private static ListNode findNode(ListNode head, int pos) {
        while (pos-- > 0 && head != null) {
            head = head.next;
        }
        return head;
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // Build first linked list
        ListNode headA = buildList(scanner);

        // Build second linked list
        ListNode headB = buildList(scanner);

        // Read intersection position for the first list (0-indexed, -1 if no intersection)
        int intersectA = scanner.nextInt();

        // Read intersection position for the second list (0-indexed, -1 if no intersection)
        int intersectB = scanner.nextInt();

        // If both positions are valid, create intersection
        if (intersectA != -1 && intersectB != -1) {
            ListNode nodeA = findNode(headA, intersectA);
            ListNode nodeB = findNode(headB, intersectB);
            if (nodeA != null && nodeB != null) {
                nodeB.next = nodeA;
            }
        }

        // Create Solution object and find the intersection node
        Solution solution = new Solution();
        ListNode intersection = solution.getIntersectionNode(headA, headB);

        // Output the result
        if (intersection != null) {
            System.out.println(intersection.val);
        } else {
            System.out.println("null");
        }
    }
}

说明

  1. 输入格式

    • 第一个整数表示第一个链表的长度 n
    • 接下来 n 个整数表示第一个链表的元素。
    • 第二个整数表示第二个链表的长度 m
    • 接下来 m 个整数表示第二个链表的元素。
    • 一个整数 intersectA 表示第一个链表中的交叉点(0索引,-1表示没有交叉)。
    • 一个整数 intersectB 表示第二个链表中的交叉点(0索引,-1表示没有交叉)。
  2. 输出格式

    • 输出交点的值,如果没有交点则输出 “null”。

使用示例

假设输入如下:

5
4 1 8 4 5
6
5 6 1 8 4 5
2
3

程序会输出:

8

因为链表在节点值为 8 处相交。

你可以将这段代码保存到一个文件中,比如 Main.java,然后在命令行中使用 javac Main.java 进行编译,使用 java Main 运行,并在运行时通过标准输入提供数据。

142. 环形链表 II

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

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

不允许修改 链表。

方法一:哈希表

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode pos = head;
        Set<ListNode> visited = new HashSet<ListNode>();
        while (pos != null) {
            if (visited.contains(pos)) {
                return pos;
            } else {
                visited.add(pos);
            }
            pos = pos.next;
        }
        return null;
    }
}

这段代码定义了一个名为Solution的类,其中包含一个方法detectCycle,用于检测一个链表(ListNode类型)中是否存在环,并返回环中的任意一个入口节点。如果链表中没有环,则返回null。这里的ListNode通常包含两个成员:一个值(val)和一个指向下一个节点的指针(next)。

方法的逻辑非常直接:

  1. 初始化:首先,创建一个名为pos的指针指向链表的头节点head,并初始化一个哈希集合(HashSetvisited,用于记录已访问过的节点。

  2. 遍历链表:接下来,进入一个while循环,条件是pos不为null。在循环中:

    • 检查当前节点pos是否已经被访问过(即visited集合中是否包含pos)。
      • 如果已访问过,说明当前节点处于环中,直接返回该节点作为环的入口。
      • 如果未访问过,将当前节点添加到visited集合中,并将pos指针移动到下一个节点(pos = pos.next)。
  3. 未发现环:如果遍历完整个链表都没有发现重复的节点,说明链表中没有环,最后返回null

时间复杂度:O(N),其中N是链表中的节点数量。在最坏的情况下,需要遍历整个链表。

空间复杂度:O(N),因为在最坏情况下,需要将链表中所有节点都存储在哈希集合中。

这种方法简单且易于理解,但需要注意,当链表很长时,哈希集合的使用会增加额外的空间开销。对于空间敏感的应用场景,可以考虑使用“快慢指针”(Floyd判圈算法)方法来检测环,该方法仅使用常数级别的额外空间。

为了将 detectCycle 方法改成符合ACM竞赛模式,我们需要调整代码以使用标准输入和输出,并定义 ListNode 类。以下是修改后的代码:

import java.util.Scanner;
import java.util.HashSet;
import java.util.Set;

class ListNode {
    int val;
    ListNode next;

    ListNode(int x) {
        val = x;
        next = null;
    }
}

class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode pos = head;
        Set<ListNode> visited = new HashSet<>();
        while (pos != null) {
            if (visited.contains(pos)) {
                return pos;
            } else {
                visited.add(pos);
            }
            pos = pos.next;
        }
        return null;
    }
}

public class Main {
    private static ListNode buildList(Scanner scanner) {
        int n = scanner.nextInt();
        if (n == 0) return null;
        ListNode dummy = new ListNode(0);
        ListNode current = dummy;
        for (int i = 0; i < n; i++) {
            current.next = new ListNode(scanner.nextInt());
            current = current.next;
        }
        return dummy.next;
    }

    private static void printList(ListNode head) {
        while (head != null) {
            System.out.print(head.val + " ");
            head = head.next;
        }
        System.out.println();
    }

    private static ListNode findNode(ListNode head, int pos) {
        while (pos-- > 0 && head != null) {
            head = head.next;
        }
        return head;
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // Build the linked list
        ListNode head = buildList(scanner);

        // Read the position of the cycle node (0-indexed, -1 if no cycle)
        int cyclePos = scanner.nextInt();

        // If cyclePos is valid, create cycle
        ListNode cycleNode = findNode(head, cyclePos);
        if (cycleNode != null) {
            ListNode lastNode = cycleNode;
            while (lastNode.next != null) {
                lastNode = lastNode.next;
            }
            lastNode.next = cycleNode;
        }

        // Create Solution object and find the node where the cycle begins
        Solution solution = new Solution();
        ListNode cycleStart = solution.detectCycle(head);

        // Output the result
        if (cycleStart != null) {
            System.out.println(cycleStart.val);
        } else {
            System.out.println("null");
        }
    }
}

说明

  1. 输入格式

    • 第一个整数表示链表的长度 n
    • 接下来 n 个整数表示链表的元素。
    • 一个整数 cyclePos 表示循环开始的位置(0索引,-1表示没有循环)。
  2. 输出格式

    • 输出循环开始的节点的值,如果没有循环则输出 “null”。

使用示例

假设输入如下:

5
3 2 0 -4 1
1

程序会输出:

2

因为链表在节点值为 2 处开始循环。

你可以将这段代码保存到一个文件中,比如 Main.java,然后在命令行中使用 javac Main.java 进行编译,使用 java Main 运行,并在运行时通过标准输入提供数据。

方法二:快慢指针

public class Solution {
    public ListNode detectCycle(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode slow = head, fast = head;
        while (fast != null) {
            slow = slow.next;
            if (fast.next != null) {
                fast = fast.next.next;
            } else {
                return null;
            }
            if (fast == slow) {
                ListNode ptr = head;
                while (ptr != slow) {
                    ptr = ptr.next;
                    slow = slow.next;
                }
                return ptr;
            }
        }
        return null;
    }
}

这段代码是使用Java编写的,它定义了一个名为Solution的类,其中包含一个方法detectCycle,用于检测一个链表(由ListNode节点组成)中是否存在环,并返回环中的入口节点。如果链表无环,则返回null。这里利用了著名的“快慢指针”(Floyd判圈算法)技术来解决此问题。

逻辑解释:

  1. 初始化:首先,定义两个指针slowfast,均初始化为链表的头节点head。这两个指针将在链表中前进,但fast指针每次移动两个节点,而slow指针每次移动一个节点。

  2. 寻找交点:进入一个循环,条件是fast不为nullfast.next也不为null(防止空指针异常)。在循环中,slowfast按照各自的速率前进。如果链表中存在环,fast指针最终会追上slow指针(在环内的某个节点相遇)。

  3. 判断是否有环:当fast == slow时,说明链表中存在环。

  4. 寻找环的入口

    • 一旦发现环,定义一个新的指针ptr从链表头开始移动,同时保持slow指针继续在环内移动。两者以相同的速度(每次移动一个节点)前进,当它们相遇时的节点即为环的入口节点,因为从头节点和环内某点出发,两者到环入口的距离相等。
  5. 无环处理:如果循环结束,说明链表无环,直接返回null

时间复杂度和空间复杂度:

  • 时间复杂度:O(N),其中N是链表中的节点数量。在最坏情况下,快指针会遍历链表两次(一次追上慢指针,一次与慢指针从头节点同时出发再次相遇)。
  • 空间复杂度:O(1),只使用了常数个额外指针,没有使用额外的数据结构。

这种方法相比使用哈希集合减少了空间复杂度,是非常高效的链表环检测算法。

为了将 detectCycle 方法改成符合ACM竞赛模式,我们需要调整代码以使用标准输入和输出,并定义 ListNode 类。以下是修改后的代码:

import java.util.Scanner;

class ListNode {
    int val;
    ListNode next;

    ListNode(int x) {
        val = x;
        next = null;
    }
}

class Solution {
    public ListNode detectCycle(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode slow = head, fast = head;
        while (fast != null) {
            slow = slow.next;
            if (fast.next != null) {
                fast = fast.next.next;
            } else {
                return null;
            }
            if (fast == slow) {
                ListNode ptr = head;
                while (ptr != slow) {
                    ptr = ptr.next;
                    slow = slow.next;
                }
                return ptr;
            }
        }
        return null;
    }
}

public class Main {
    private static ListNode buildList(Scanner scanner) {
        int n = scanner.nextInt();
        if (n == 0) return null;
        ListNode dummy = new ListNode(0);
        ListNode current = dummy;
        for (int i = 0; i < n; i++) {
            current.next = new ListNode(scanner.nextInt());
            current = current.next;
        }
        return dummy.next;
    }

    private static void printList(ListNode head) {
        while (head != null) {
            System.out.print(head.val + " ");
            head = head.next;
        }
        System.out.println();
    }

    private static ListNode findNode(ListNode head, int pos) {
        while (pos-- > 0 && head != null) {
            head = head.next;
        }
        return head;
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // Build the linked list
        ListNode head = buildList(scanner);

        // Read the position of the cycle node (0-indexed, -1 if no cycle)
        int cyclePos = scanner.nextInt();

        // If cyclePos is valid, create cycle
        ListNode cycleNode = findNode(head, cyclePos);
        if (cycleNode != null) {
            ListNode lastNode = cycleNode;
            while (lastNode.next != null) {
                lastNode = lastNode.next;
            }
            lastNode.next = cycleNode;
        }

        // Create Solution object and find the node where the cycle begins
        Solution solution = new Solution();
        ListNode cycleStart = solution.detectCycle(head);

        // Output the result
        if (cycleStart != null) {
            System.out.println(cycleStart.val);
        } else {
            System.out.println("null");
        }
    }
}

说明

  1. 输入格式

    • 第一个整数表示链表的长度 n
    • 接下来 n 个整数表示链表的元素。
    • 一个整数 cyclePos 表示循环开始的位置(0索引,-1表示没有循环)。
  2. 输出格式

    • 输出循环开始的节点的值,如果没有循环则输出 “null”。

使用示例

假设输入如下:

5
3 2 0 -4 1
1

程序会输出:

2

因为链表在节点值为 2 处开始循环。

你可以将这段代码保存到一个文件中,比如 Main.java,然后在命令行中使用 javac Main.java 进行编译,使用 java Main 运行,并在运行时通过标准输入提供数据。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值