Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions Problem1-recursive-1.py
Original file line number Diff line number Diff line change
@@ -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)
59 changes: 59 additions & 0 deletions Problem1-recursive-2.py
Original file line number Diff line number Diff line change
@@ -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
42 changes: 42 additions & 0 deletions Problem1.py
Original file line number Diff line number Diff line change
@@ -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
56 changes: 56 additions & 0 deletions Problem2.py
Original file line number Diff line number Diff line change
@@ -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
45 changes: 45 additions & 0 deletions Problem3.py
Original file line number Diff line number Diff line change
@@ -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