LC 24, 19, 160, 142 Second part of Linked List.
Second part of Linked List.
LC 206. Reverse Linked List
Logic
- Either create a new one or reverse the link.
# LC 206, reverse the link
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
parent = None
cur = head
while cur:
temp = cur.next
cur.next = parent
parent = cur
if not temp:
break
cur = temp
return cur
# LC 206, create a new returned linked list
def reverseList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
cur = head
ret = None
if cur is not None:
ret = ListNode(cur.val)
while cur.next is not None:
cur = cur.next
new_node = ListNode(cur.val, ret)
ret = new_node
return ret
19. Remove Nth Node From End of List
Logic
Add a node of Dummy head
to make the operation of deletion the same for all nodes, where dummy_head.next = head
, then just return the dummy_head.next
.
-
Duo ptrs with fast, slow stepping speed could help, where the fast ptr will tell when to stop, and at that time, the slow ptr will points to the exact node that we want to delete.
-
However, to delete
cur
, since this is single-direction linked list, we can’t just removecur
. We need to:cur
points to the node right before the node we want to delete,- then,
cur.next = cur.next.next
.
-
So
cur.next
is deleted. -
For this to happend, the fast ptr need to walk for
n+1
steps, instead ofn
. -
Then after steps are walked in the first
while
loop, one need to check again if alln+1
step (you could think as if+1
is for thedummy_head
as well) are all finished before the deletion.
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
dummy_head = ListNode(-1)
dummy_head.next = head
fast = dummy_head
# walks n + 1 steps
while fast is not None and n >= 0:
fast = fast.next
n -= 1
slow = dummy_head
while fast is not None:
slow = slow.next
fast = fast.next
if not slow or n > 0:
return dummy_head.next
if slow.next:
slow.next = slow.next.next
return dummy_head.next
160. Intersection of Two Linked Lists
Which is the same of LC 160. Intersection of Two Linked Lists
Logic:
Recursion
- The first thought is to solve it by recursion, and there are limited base cases:
- Either of
headA
orheadB
isNone
, then they won’t share any nodes with the other; headA == headB
, then they share all nodes;headA.next == headB
, then they share all nodes fromheadB
;headB.next == headA
, then they share all nodes fromheadA
;- Otherwise, we could just reduce one node from either side and it concludes all cases. Then we would also need to compare all result and return the longer one.
- Either of
- It was worth noticing that this method compare all node in
headA
andneadB
, which gave a Time complexity $ = O(m\cdot n)$, wherem = len(headA)
,n = len(headB)
.
# LC 160
def getIntersectionNode(
self, headA: ListNode, headB: ListNode
) -> Optional[ListNode]:
if headA is None or headB is None:
return None
if headA == headB:
return headA
if headA.next == headB:
return headB
if headB.next == headA:
return headA
ret1 = self.getIntersectionNode(headA.next,headB)
ret2 = self.getIntersectionNode(headA, headBnext)
if ret1 and ret2:
if ret1.next == ret2:
return ret1
return ret2
if ret1:
return ret1
return ret2
- Well, obviously, this is not good enough to be AC on LeetCode. Then I tried with brute force and store everything.
store and back comparison
- Basically, I can store every nodes from
headA
andheadB
, then campare them backwards to reduce the comparison runtime to O ( n ) O(n) O(n).
# LC 160 store and back search
def getIntersectionNode2(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
lst1 = []
lst2 = []
cur = headA
while cur:
lst1.append(cur)
cur = cur.next
cur = headB
while cur:
lst2.append(cur)
cur = cur.next
if len(lst1) == 0 or len(lst2) == 0:
return None
index = -1
while index >= - len(lst1) and index >= - len(lst2) and lst1[index] == lst2[index]:
index -= 1
if index < -1 and index + 1 >= - len(lst1):
return lst1[index + 1]
return None
Duo Ptrs
-
If I only need to compare two linked lists for the same length, could I just consume both lists to the exact length that I would want to compare?
- Yes! We only need to compare the whole length of the shorter linked list.
-
Then the logic basically becomes:
- Find the Shorter list;
- Eat the longer one to be left with exact length as the shorter list;
- Then compare them at the same rate.
# LC 160 duo ptrs
def getIntersectionNode3(self, headA: ListNode,headB: ListNode) -> Optional[ListNode]:
size1 = 0
size2 = 0
cur = headA
while cur:
size1 += 1
cur = cur.next
cur = headB
while cur:
size2 += 1
cur = cur.next
# print(size1, size2)
if size1 > size2:
cur = headA
cur2 = headB
else:
cur = headB
cur2 = headA
# print(cur)
# print(cur2)
diff = abs(size1 - size2)
while cur and diff > 0:
cur = cur.next
diff -= 1
while cur and cur2 and cur != cur2:
cur = cur.next
cur2 = cur2.next
return cur
19. Remove Nth Node From End of List
Logic
- The brute force “store and check” works with:
- runtime: O ( n l o g ( n ) ) O(n \ log(n)) O(n log(n))
- space: O ( n ) O(n) O(n)
def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
lst = set()
cur = head
cur2 = head
while cur:
if cur == cur2:
return cur
lst.add(cur)
cur = cur.next
return None
However, the duo ptrs method works elegantly based on a heavy math basis.
Quote from a physics perspective:
追及问题,相对速度与绝对速度 phys111
你把时间考虑进去
第一个图中 fast走了2n slow走了n 刚好入口相遇
出发点一样的 确实
都是环入口
是时间不一样
所以位移不一样
第二个图里面 高的那个弧线画的有点过了
应该是在环入口3前面一点点
刚好和slow相遇
这个你可以考虑
fast的绝对速度是俩个单位/单位时间
slow的绝对速度是1个/单位时间
那么fast相对于slow的相对速度就是1/个/单位时间
因为fast最多走2n
slow最多走n所以就相遇了
slow进圈后粒度是1
fast不可能跳过
考虑了相对速度 fast的速度也是1
就不能跳过
- Namely, choose the
slow
ptr as the inertial frame of reference. Thenfast
ptr is chansing up with a speed of1
$node / unit \ time $. - Since the granularity of
slow
ptr is1
. Thenfast
ptr will have to chase up toslow
in the first run.
Summary:
-
Overall, the linked list problems are not so difficult, but the math problem for the last problem is still chricky. May need to take a look at it another time.
-
Total time: 4-5 hrs ish.