Given a linked list, determine if it has a cycle in it.
To represent a cycle in the given linked list, we use an integer pos
which represents the position (0-indexed) in the linked list where tail connects to. If pos
is -1
, then there is no cycle in the linked list.
Example 1:
Input: head = [3,2,0,-4], pos = 1 Output: true Explanation: There is a cycle in the linked list, where tail connects to the second node.
Example 2:
Input: head = [1,2], pos = 0 Output: true Explanation: There is a cycle in the linked list, where tail connects to the first node.
Example 3:
Input: head = [1], pos = -1 Output: false Explanation: There is no cycle in the linked list.
public boolean hasCycle(ListNode head) {
Set nodesSeen = new HashSet<>();
while (head != null) {
if (nodesSeen.contains(head)) {
return true;
} else {
nodesSeen.add(head);
}
head = head.next;
}
return false;
}
Complexity analysis
Time complexity : O(n). We visit each of the nn elements in the list at most once. Adding a node to the hash table costs only O(1)time.
Space complexity: O(n). The space depends on the number of elements added to the hash table, which contains at most nn elements.
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) return false;
// unlike int index in arrays, we now use Listnode as pointers
ListNode slow = head;
ListNode fast = head.next;
while (fast.next != null && fast.next.next != null) {
if (slow == fast) return true;
slow = slow.next;
fast = fast.next.next;
}
return false;
}
}
Complexity analysis
Time complexity : O(n). Let us denote nn as the total number of nodes in the linked list. To analyze its time complexity, we consider the following two cases separately.
List has no cycle:
The fast pointer reaches the end first and the run time depends on the list's length, which is O(n)O(n).
List has a cycle:
We break down the movement of the slow pointer into two steps, the non-cyclic part and the cyclic part:
The slow pointer takes "non-cyclic length" steps to enter the cycle. At this point, the fast pointer has already reached the cycle. \text{Number of iterations} = \text{non-cyclic length} = NNumber of iterations=non-cyclic length=N
Both pointers are now in the cycle. Consider two runners running in a cycle - the fast runner moves 2 steps while the slow runner moves 1 steps at a time. Since the speed difference is 1, it takes \dfrac{\text{distance between the 2 runners}}{\text{difference of speed}}difference of speeddistance between the 2 runners loops for the fast runner to catch up with the slow runner. As the distance is at most "\text{cyclic length K}cyclic length K" and the speed difference is 1, we conclude that
\text{Number of iterations} = \text{almost}Number of iterations=almost "\text{cyclic length K}cyclic length K".
Therefore, the worst case time complexity is O(N+K), which is O(n)O(n).
Space complexity : O(1). We only use two nodes (slow and fast) so the space complexity is O(1)
Given a linked list, return the node where the cycle begins. If there is no cycle, return null
.
To represent a cycle in the given linked list, we use an integer pos
which represents the position (0-indexed) in the linked list where tail connects to. If pos
is -1
, then there is no cycle in the linked list.
Note: Do not modify the linked list.
Example 1:
Input: head = [3,2,0,-4], pos = 1 Output: tail connects to node index 1 Explanation: There is a cycle in the linked list, where tail connects to the second node.
Example 2:
Input: head = [1,2], pos = 0 Output: tail connects to node index 0 Explanation: There is a cycle in the linked list, where tail connects to the first node.
Example 3:
Input: head = [1], pos = -1 Output: no cycle Explanation: There is no cycle in the linked list.
public class Solution {
public ListNode detectCycle(ListNode head) {
Set visited = new HashSet();
ListNode node = head;
while (node != null) {
if (visited.contains(node)) {
return node;
}
visited.add(node);
node = node.next;
}
return null;
}
}
Complexity Analysis
Time complexity : O(n)O(n)
For both cyclic and acyclic inputs, the algorithm must visit each node exactly once. This is transparently obvious for acyclic lists because the nnth node points to null
, causing the loop to terminate. For cyclic lists, the if
condition will cause the function to return after visiting the nnth node, as it points to some node that is already in visited
. In both cases, the number of nodes visited is exactly nn, so the runtime is linear in the number of nodes.
Space complexity : O(n)O(n)
For both cyclic and acyclic inputs, we will need to insert each node into the Set
once. The only difference between the two cases is whether we discover that the "last" node points to null
or a previously-visited node. Therefore, because the Set
will contain nn distinct nodes, the memory footprint is linear in the number of nodes.
public class Solution {
private ListNode getIntersect(ListNode head) {
ListNode tortoise = head;
ListNode hare = head;
// A fast pointer will either loop around a cycle and meet the slow
// pointer or reach the `null` at the end of a non-cyclic list.
while (hare != null && hare.next != null) {
tortoise = tortoise.next;
hare = hare.next.next;
if (tortoise == hare) {
return tortoise;
}
}
return null;
}
public ListNode detectCycle(ListNode head) {
if (head == null) {
return null;
}
// If there is a cycle, the fast/slow pointers will intersect at some
// node. Otherwise, there is no cycle, so we cannot find an entrance to
// a cycle.
ListNode intersect = getIntersect(head);
if (intersect == null) {
return null;
}
// To find the entrance to the cycle, we have two pointers traverse at
// the same speed -- one from the front of the list, and the other from
// the point of intersection.
ListNode ptr1 = head;
ListNode ptr2 = intersect;
while (ptr1 != ptr2) {
ptr1 = ptr1.next;
ptr2 = ptr2.next;
}
return ptr1;
}
}
Complexity Analysis
Time complexity : O(n)O(n)
For cyclic lists, For acyclic lists, hare
and tortoise
will point to the same node after F+C-hF+C−h iterations, as demonstrated in the proof of correctness. F+C-h \leq F+C = nF+C−h≤F+C=n, so phase 1 runs in O(n) time. Phase 2 runs for F < nFhare
will reach the end of the list in roughly \dfrac{n}{2}2niterations, causing the function to return before phase 2. Therefore, regardless of which category of list the algorithm receives, it runs in time linearly proportional to the number of nodes.
Space complexity : O(1)
Floyd's Tortoise and Hare algorithm allocates only pointers, so it runs with constant overall memory usage.
设计一个单链表:
/**
* A singly linked list with a left sentinel node.
*/
class MyLinkedList {
/** A very simple node class. */
private static class Node {
int val;
Node next;
}
// Predecessor of the first element
private Node headPred;
// Predecessor of the tail
private Node tailPred;
private int length;
/** Initialize your data structure here. */
public MyLinkedList() {
headPred = new Node();
tailPred = headPred;
length = 0;
}
/** Get the value of the index-th node in the linked list. If the index is invalid, return -1. */
public int get(int index) {
if ((index < 0) || (index >= length)) {
return -1;
}
return findPred(index).next.val;
}
/** Add a node of value val before the first element of the linked list. After the insertion, the new node will be the first node of the linked list. */
public void addAtHead(int val) {
if (length == 0) {
addAtTail(val);
} else {
addAfter(headPred, val);
}
}
/** Append a node of value val to the last element of the linked list. */
public void addAtTail(int val) {
addAfter(tailPred, val);
tailPred = tailPred.next;
}
/** Add a node of value val before the index-th node in the linked list. If index equals to the length of linked list, the node will be appended to the end of linked list. If index is greater than the length, the node will not be inserted. */
public void addAtIndex(int index, int val) {
if (index < 0) {
addAtHead(val);
} else if (index == length) {
addAtTail(val);
} else if ((index >= 0) && (index < length)) {
addAfter(findPred(index), val);
}
}
/** Delete the index-th node in the linked list, if the index is valid. */
public void deleteAtIndex(int index) {
if ((index >= 0) && (index < length)) {
Node pred = findPred(index);
if (index == length - 1) { // Remove last element
// Move tail to the left
tailPred = pred;
}
pred.next = pred.next.next;
--length;
}
}
/** Return the predecessor of the index-th node. */
private Node findPred(int index) {
Node pred = headPred;
for (int i = 0; i < index; ++i) {
pred = pred.next;
}
return pred;
}
/** Add an element after the given node. */
private void addAfter(Node pred, int val) {
Node node = new Node();
node.val = val;
node.next = pred.next;
pred.next = node;
++length;
}
}