From 5ee0e45620b895edfd2acb8b09d96f16fd1a4ba3 Mon Sep 17 00:00:00 2001 From: Sanket Kale Date: Thu, 25 Jun 2026 21:56:42 -0500 Subject: [PATCH] Completed Linked-List 1 --- Problem1-recursive-1.py | 44 ++++++++++++++++++++++++++++++ Problem1-recursive-2.py | 59 +++++++++++++++++++++++++++++++++++++++++ Problem1.py | 42 +++++++++++++++++++++++++++++ Problem2.py | 56 ++++++++++++++++++++++++++++++++++++++ Problem3.py | 45 +++++++++++++++++++++++++++++++ 5 files changed, 246 insertions(+) create mode 100644 Problem1-recursive-1.py create mode 100644 Problem1-recursive-2.py create mode 100644 Problem1.py create mode 100644 Problem2.py create mode 100644 Problem3.py diff --git a/Problem1-recursive-1.py b/Problem1-recursive-1.py new file mode 100644 index 00000000..f657cfff --- /dev/null +++ b/Problem1-recursive-1.py @@ -0,0 +1,44 @@ +## Problem1 (https://leetcode.com/problems/reverse-linked-list/)# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next +class Solution: + def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: + """ + Time Complexity: O(n) + - We traverse each node in the linked list exactly once, where n is the + number of nodes in the list. + + Space Complexity: O(n) + - Since this is a recursive approach, the space complexity is proportional + to the maximum depth of the recursion stack. The recursion goes n levels + deep (one for each node), requiring O(n) extra space. + """ + def helper(current, prev): + # Base Case: When 'current' becomes None, we've reached the end of + # the original list. 'prev' is now resting on the last node processed, + # which is the new head of the fully reversed list. + if current is None: + return prev + + # 1. Temporarily store the next node so we don't lose the rest of the list + temp = current.next + + # 2. Reverse the link: point the current node backwards to the 'prev' node + current.next = prev + + # 3. Shift 'prev' forward to the current node for the next step + prev = current + + # 4. Shift 'current' forward to the original next node (saved in temp) + current = temp + + # 5. Make the recursive call to process the next node in the list + # Crucially, we return the result of this call so the new head + # bubbles all the way back up to the top. + return helper(current, prev) + + # Start the recursion with 'current' at the head, and 'prev' as None + # (since the new tail must point to None). + return helper(head, None) \ No newline at end of file diff --git a/Problem1-recursive-2.py b/Problem1-recursive-2.py new file mode 100644 index 00000000..ac1419a2 --- /dev/null +++ b/Problem1-recursive-2.py @@ -0,0 +1,59 @@ +## Problem1 (https://leetcode.com/problems/reverse-linked-list/) +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next +class Solution: + def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: + """ + Time Complexity: O(n) + - We traverse each node in the linked list exactly once during the + recursive calls, where n is the number of nodes. + + Space Complexity: O(n) + - The recursive approach requires space on the call stack proportional + to the maximum depth of the recursion, which is n levels deep. + """ + + # This will hold the reference to the new head of the fully reversed list + # (which is the last node of the original list). + reversedHead = None + + def helper(head): + nonlocal reversedHead + + # Base Case: If the list is empty or we've reached the last node. + # We set 'reversedHead' to this last node and begin returning up the stack. + if head is None or head.next is None: + reversedHead = head + return + + # Recursively travel to the very end of the list first. + # This ensures we reverse from the tail backwards. + helper(head.next) + + # --- Logic for reversing the current pair of nodes --- + + # 'temp' is the next node in the original list. + temp = head.next + + # Clever trick: Because the deeper recursive call already processed 'temp', + # it set 'temp.next' (which is head.next.next) to None. + # By assigning head.next = head.next.next, we are effectively setting + # head.next = None, ensuring the old forward pointer is broken. + head.next = head.next.next + + # Reverse the pointer: make the next node point backwards to the current node. + temp.next = head + + # Initiate the recursive traversal starting from the original head + helper(head) + + # Return the new head that was captured during the base case of the recursion + return reversedHead \ No newline at end of file diff --git a/Problem1.py b/Problem1.py new file mode 100644 index 00000000..102143e9 --- /dev/null +++ b/Problem1.py @@ -0,0 +1,42 @@ +## Problem1 (https://leetcode.com/problems/reverse-linked-list/) +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next +class Solution: + def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]: + """ + Time Complexity: O(n) + - Where n is the number of nodes in the linked list. We traverse the list exactly once. + + Space Complexity: O(1) + - We only use a constant amount of extra space for the pointers (prev, current, temp), + regardless of the size of the linked list. + """ + + # 'prev' will keep track of the previous node. + # It starts as None because the new tail of the reversed list must point to None. + prev = None + + # 'current' starts at the head and is used to traverse the original list. + current = head + + # Loop until we reach the end of the list + while current is not None: + # 1. Store the next node temporarily so we don't lose the rest of the list + # when we break the current link. + temp = current.next + + # 2. Reverse the current node's pointer to point backwards to the 'prev' node. + current.next = prev + + # 3. Move the 'prev' pointer one step forward to the 'current' node. + prev = current + + # 4. Move the 'current' pointer one step forward to the 'temp' node we saved earlier. + current = temp + + # Once the loop finishes, 'current' will be None, and 'prev' will be resting + # on the last node we processed, which is the new head of the reversed list. + return prev \ No newline at end of file diff --git a/Problem2.py b/Problem2.py new file mode 100644 index 00000000..9c90be5e --- /dev/null +++ b/Problem2.py @@ -0,0 +1,56 @@ +## Problem2 (https://leetcode.com/problems/remove-nth-node-from-end-of-list/) +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next + +class Solution: + def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: + """ + Time Complexity: O(L) + where L is the number of nodes in the linked list. The algorithm makes a + single pass through the list with the 'fast' pointer. + + Space Complexity: O(1) + because we only use a constant amount of extra space for the dummy, slow, + fast, and temp pointers, regardless of the list size. + """ + + # 1. Create a dummy node that points to the head. + # This simplifies edge cases, such as when the list has only one node + # or when we need to remove the very first node in the list. + dummy = ListNode() + dummy.next = head + + # 2. Initialize two pointers (slow and fast), both starting at the dummy node. + slow = dummy + fast = dummy + count = 0 + + # 3. Move the fast pointer forward by n + 1 steps. + # This creates a gap of exactly 'n' nodes between the slow and fast pointers. + while count <= n: + fast = fast.next + count += 1 + + # 4. Move both pointers at the same pace until 'fast' reaches the end. + # Because we maintained the gap of 'n' nodes, when 'fast' falls off the end + # (becomes None), 'slow' will land exactly on the node right BEFORE the one + # we need to remove. + while fast is not None: + slow = slow.next + fast = fast.next + + # 5. Remove the nth node from the end. + # 'slow.next' is the node we want to remove. We store it in 'temp'. + temp = slow.next + + # Reroute the pointer to skip the target node entirely. + slow.next = slow.next.next + + # Detach the removed node's next pointer to cleanly remove it from the list. + temp.next = None + + # 6. Return the head of the modified list (which is the node after dummy). + return dummy.next \ No newline at end of file diff --git a/Problem3.py b/Problem3.py new file mode 100644 index 00000000..11ad4ed3 --- /dev/null +++ b/Problem3.py @@ -0,0 +1,45 @@ +## Problem3 (https://leetcode.com/problems/linked-list-cycle-ii/) +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, x): +# self.val = x +# self.next = None + +class Solution: + def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]: + # Time Complexity: O(N) - N is the number of nodes in the list. In the worst case, + # we might iterate through the list a constant number of times, which simplifies to O(N). + # Space Complexity: O(1) - We only use two pointers (slow and fast), requiring constant extra memory. + + slow = head + fast = head + + # Phase 1: Detect if a cycle exists + while fast is not None and fast.next is not None: + # Advance pointers first: slow takes 1 step, fast takes 2 steps + slow = slow.next + fast = fast.next.next + + # If the pointers meet, a cycle exists. Break to move to Phase 2. + if slow == fast: + break + + # If the while loop exited because we reached the end of the list (a None value), + # it means the list is linear and has no cycle. + if fast is None or fast.next is None: + return None + + # Phase 2: Find the exact node where the cycle begins + # Reset one of the pointers (let's use fast) back to the head of the list. + fast = head + + # Move both pointers exactly 1 step at a time. + # Mathematically, the distance from the head to the cycle start is equal + # to the distance from the meeting point to the cycle start. + # Therefore, the exact node where they collide again is the start of the cycle. + while slow != fast: + fast = fast.next + slow = slow.next + + # Both pointers now point to the start of the cycle, so return either one. + return fast \ No newline at end of file