Leetcode [141, 142] Linked List Cycle,java解法

141 Linked List Cycle 

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.

Approach #1 Hash Set

public boolean hasCycle(ListNode head) {
    Set<ListNode> 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. 

Approach #2 Two Pointers

/**
 * 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:

      1. 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

      2. 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)

 

142. Linked List Cycle II

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.

Approach #1 Hash Table

public class Solution {
    public ListNode detectCycle(ListNode head) {
        Set<ListNode> visited = new HashSet<ListNode>();

        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.

Approach #2 Floyd's Tortoise and Hare

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, 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 < nF<n iterations, so it also runs in O(n) time.

    For acyclic lists, hare will reach the end of the list in roughly \dfrac{n}{2}2n​iterations, 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.

三、 关于链表Linked List

设计一个单链表:

/**
 * 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;
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值