Linked Lists Questions Part 2
================================
161. (LC 19) Remove Nth Node From End of List
-------------------------------------------------
`19. Remove Nth Node From End of List `_
Medium
| Other names: Remove the Nth last element from a list
| **Keys** (final conclusion):
| -Two pointers. Start each at dummy.
| Move p1 for i in range(n).
| -Then move both p1,p2 while p1.next.
| (So we advance p1 n-steps forward before moving p1, p2 in unison until p1 reaches list last node.)
::
### My V (LC accepted 20, 98)
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
dummy=ListNode()
dummy.next=head
p1=p2=dummy
for _ in range(n):
p1=p1.next
while p1.next:
p1=p1.next
p2=p2.next
p2.next=p2.next.next
return dummy.next
::
### Illustration 1 (general)
# We use the principle of two iterators.
# Dummy X X X X X X X
# ^
| To get to 3rd X from the end.
| -i1 (iterator 1) from head 3 times.
| -i2 from till i1 runs off the list.
| -i2 is now at the node before the marked X
::
# X X X X X X X
# i1 ^
# X X X X X X X
# i2 ^ i1
| NOTE:
| I1 starts at the head.
| I2 starts at the dummy_head (to get the node before the node in question)
::
### Illustration 2 (details)
# E.g. n=3
# #1
# L 0->A->B->C->D->E->F->G->H
# DH f Nth
# -Create dummy head
# -first=dummy.next (=list head)
# #2
# Loop n times
# 0->A->B->C->D->E->F->G->H
# f f1 f2 f3 N
# #3
# second=dummy_head
# -move second till first doesn't run off the list
# 0->A->B->C->D->E->F->G->H
# s s5 N f3-5
# Here it takes 5 steps.
# Now second is just before Nth node.
# We point second PAST the Nth:
# second.next = second.next.next
| **Solution 1**
| Time O(n), Space O(1)
::
class Solution:
def removeNthFromEnd(self, head, n: int):
dummy_head = ListNode(0, head)
first = dummy_head.next
for _ in range(n):
first = first.next
second = dummy_head
while first:
first, second = first.next, second.next
second.next = second.next.next
return dummy_head.next
162. (LC 83) Remove Duplicates from Sorted List
---------------------------------------------------
`83. Remove Duplicates from Sorted List `_
Easy
| **Keys:**
| -temporary node (tmp=cur.next)
| -nested while loop (delete while the same value)
| Input: singly linked sorted list
| Exploit the sorted nature of the list.
| Remove all successive nodes with the same value as the current node.
Move next while cur==next. Delete. Make next cur. ::
# 2->2->3->5
# C N N
::
### Solution 1 (LC accepted)
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
cur = head
while cur:
next = cur.next #**
while next and next.val == cur.val:
next = next.next
cur.next = next
cur = next
return head
T O(n), M O(1).
| **Issue**
| This way does not work for case [0,0,0,0,0] (returns [] instead of [0])
| (Though works for other cases including [1,1,1,1,1], [10,10,10]).
Initiating pointers like::
# dummy > 0,0,0
# p1 p2
def deleteDuplicates(self, head: Optional[ListNode]):
dummy = ListNode()
p1 = dummy
p2 = dummy.next = head
while p2:
while p2 and (p1.val == p2.val):
p2 = p2.next
p1.next = p2
p1 = p2
p2 = p2.next if p2 else None
return dummy.next
This works, initiating pointers like::
# 0,0,0
# p1 (p2 initiated as first step of the loop) #**
def deleteDuplicates(self, head: Optional[ListNode]):
p1=head
while p1:
p2=p1.next #**
while p2 and p1.val == p2.val:
p2 = p2.next
p1.next = p2
p1 = p2
return head
Solution 1 My V (LC accepted 60,60%) ::
class Solution:
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
cur=head
dummy=ListNode()
dummy.next=head
while cur and cur.next:
tmp = cur.next
while tmp and (cur.val == tmp.val):
tmp = tmp.next
cur.next = tmp
cur = tmp
return dummy.next
163. (LC 61) Rotate List
---------------------------
`61. Rotate List `_
Medium
Other names: Right shift for singly linked list.
| **Solution 1** [:ref:`2 `] LC accepted
| Steps:
| -compute list len + find tail
| -k=k%n
| -make a cycle connecting tail to head
| -find node just before the new head:
| steps to new head = n-k
| iterate from tail
| assign new_head
| -break cycle:
| point new_tail to None
::
class Solution:
def rotateRight(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
if not head:
return head
# Compute len of list + find tail
tail, n = head, 1 # tail is current, because we want to stop at tail, we call it tail NOW
while tail.next:
n += 1
tail = tail.next
k %= n
if k == 0:
return head
# Connect tail to head making a cycle
tail.next = head
steps_to_new_head, new_tail = n - k, tail
while steps_to_new_head:
steps_to_new_head -= 1
new_tail = new_tail.next
# If we found new tail, then next node is new head
new_head = new_tail.next
# Break the cycle
new_tail.next = None
return new_head
My V (LC accepted 20,90%)::
class Solution:
def rotateRight(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
if not head or not head.next or k==0:
return head
dummy=ListNode()
dummy.next=head
# find list len
list_len=0
p1 = dummy
while p1.next:
p1=p1.next
list_len+=1
#How many rotations we need (if k > list len)
rotations = k % list_len
if rotations == 0:
return dummy.next
#Advance new p2 to node at which rotations should start
steps = list_len - rotations
p2=dummy
for _ in range(steps):
p2=p2.next
#make p2.next new head
head = p2.next
#p2 is new tail (breaking list)
p2.next = None
#connect 2 sublists
p1.next = dummy.next
return head
**Solution 2** [:ref:`7 `] LC accepted ::
class Solution:
def rotateRight(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
if head is None or head.next is None:
return head
cur, n = head, 0 #1
while cur:
n += 1
cur = cur.next
k %= n #2
if k == 0:
return head
fast = slow = head
for _ in range(k): #3
fast = fast.next
while fast.next:
fast, slow = fast.next, slow.next
new_tail = slow
new_head = slow.next
new_tail.next = None
fast.next = head #4
return new_head
| #1
| Compute n, len of list.
| #2
| k can be greater than list len, remove that difference with k=k%n
| #3
| E.g., k=3, original list:
| 2->3->5->3->2
| Move F from head, k-rotations:
| 2->3->5->3->2
| F F1 F2 F3
| Move S from head, till F runs off the list:
| 2->3->5->3->2
| S F3
| 2->3->5->3->2
| S S1 F F1
| 2->3->5->3->2
| nT nH
| S1 is new tail.
| S1.next is new head
| #4
| 2->3->5->3->2
| H nT nH F
| Connect Fast to head.
::
class Solution:
def rotateRight(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
if head is None or head.next is None:
return head
cur, n = head, 0
while cur:
n += 1
cur = cur.next
k %= n
if k == 0:
return head
fast = slow = head
for _ in range(k):
fast = fast.next
while fast.next:
fast, slow = fast.next, slow.next
ans = slow.next
slow.next = None
fast.next = head
return ans
164 (LC 328) Odd Even Linked List
------------------------------------
`328. Odd Even Linked List `_
Medium
Note, you might be asked to connect evens+odds, or odds+evens. ::
### My V (LC accepted 97,75%)
# In place
class Solution:
def oddEvenList(self, head: Optional[ListNode]) -> Optional[ListNode]:
if not head or not head.next:
return head
dummy = ListNode()
dummy.next = head
p1=head #pointer for odds
p2=head.next #pointer for evens
tmp=p2 #store head of evens (to then connect odds tail to evens head)
while p1.next and p2.next: #important and
p1.next = p2.next
p1 = p1.next
p2.next = p1.next
p2 = p2.next
p1.next = tmp
return dummy.next
| **In short** (evens+odds):
| -Initiate 2 new lists, for evens and odds (using dummy heads)
| -tails, turn = [even_dummy_head, odd_dummy_head], 0
| -Iterate the original list
| Alternating between odds and evens tail, tail.next = cur
| Keep moving the tail.
| -point odds tail to None
| -connect 2 lists (point evens tail to odds head)
::
### Solution 1 (odds + evens)
def odd_even_merge(head):
if not head:
return head
even_dummy_head, odd_dummy_head = ListNode(0), ListNode(0)
tails, turn = [odd_dummy_head, even_dummy_head], 0
cur=head
while cur:
tails[turn].next = cur
cur=cur.next
tails[turn] = tails[turn].next
turn ^= 1
tails[1].next = None
tails[0].next = even_dummy_head.next
return odd_dummy_head.next
### Solution 1-2 (evens+odds) EPI
def even_odd_merge(head):
if not head:
return head
even_dummy_head, odd_dummy_head = ListNode(0), ListNode(0) #*1
tails, turn = [even_dummy_head, odd_dummy_head], 0 #*2
cur=head
while cur:
tails[turn].next = cur #*3
cur=cur.next
tails[turn] = tails[turn].next
turn ^= 1 # Alternate between even/odd
tails[1].next = None #4
tails[0].next = odd_dummy_head.next #5
return even_dummy_head.next
| **Explained**
| 0->1->2->3->4
| #1
| Initiate two dummy heads.
| Start building two new lists, for evens and odds.
| De
| Do
| #2
| Keep track of the current tail for each list.
| turn variable to track which list we work with, alternate with XOR 1.
| De
| Tail e
| #3
| Point current tail to current node we work with.
| Move current, move tail.
| De->0
| Te..Te
| Do
| #4
| We will connect evens+odds, so odds tail will be the merged list Tail that points to None.
| #5
| Connecting even + odd (evens tail next = odds_dummy next, i.e. odds head)
| T O(N), S O(1)
| We avoid extra space by reusing the existing list nodes.
165. (LC 234) Palindrome Linked List
------------------------------------------
`234. Palindrome Linked List `_
Easy
| **Steps:**
| -Find half of the list with slow, fast (slow stops at half)
| -helper func to reverse second half (reverseList(slow))
| -compare two halves, iterating from respective heads
| (we do change the list in place. There is no req not to. When so, reverse back again.)
My note, finding the half works for both odd and even num of nodes in list. ::
# 2->3->5->3->2
# s s1 s2
# f f1 f2
# M
# 2->3->3->2
# s s1 s2
# f f1 f2
# M
| Second half might be longer, but we compare halves till we run out of ONE of them:
| while second_half_iter and first_half_iter:
::
### Solution 1 (T O(N), S O(1))
class Solution:
def isPalindrome(self, head: Optional[ListNode]) -> bool:
# find the middle (slow)
slow = fast = head
while fast and fast.next:
slow, fast = slow.next, fast.next.next
def reverse_linked_list(head: ListNode) -> ListNode:
prev, curr = None, head
while curr:
temp = curr.next
curr.next = prev
prev = curr
curr = temp
return prev
first_half_iter = head
second_half_iter = reverse_linked_list(slow)
while second_half_iter and first_half_iter:
if second_half_iter.val != first_half_iter.val:
return False
second_half_iter, first_half_iter = second_half_iter.next, first_half_iter.next
return True
### My V (LC accepted 95,70)
class Solution:
def isPalindrome(self, head: Optional[ListNode]) -> bool:
if not head.next:
return True
dummy = ListNode()
dummy.next = head
s=f= dummy
# Find last node of 1st sublist (i.e. linked list middle)
while f and f.next: #takes care of cases: list len odd, len even
s = s.next
f = f.next.next
# Reverse 2nd sublist
prev = None
cur = s.next
while cur:
tmp = cur.next
cur.next = prev
prev = cur
cur = tmp
# Compare nodes from both ends
iter1 = dummy.next
iter2 = prev
while iter2:
if iter1.val != iter2.val:
return False
iter1 = iter1.next
iter2 = iter2.next
return True
166. (LC 86) Partition List
--------------------------------
`86. Partition List `_
Medium
| Other names: Implement linked list pivoting
| NOTE
| We preserve the relative order. We are NOT sorting.
| (If we wanted an ideal sorting, we would need a 3rd list, with values =x.
| So LC just makes it easier for you.)
| T O(N), S O(1).
| **Keys:**
| -Make 2 new separate lists for 1)values=x
| Iterate the input list to populate the 2 lists.
| -Combine 2 lists (don't forget to point tail2 to None).
::
### My submit 2 (LC accepted 40, 98)
class Solution:
def partition(self, head: Optional[ListNode], x: int) -> Optional[ListNode]:
head1 = tail1 = ListNode()
head2 = tail2 = ListNode()
while head:
if head.val < x:
tail1.next = head
tail1 = tail1.next
else:
tail2.next = head
tail2 = tail2.next
head = head.next
# Combine 2 lists
tail1.next = head2.next
tail2.next = None #omitting this gets you "Error - Found cycle in the ListNode"
return head1.next
167. (LC 2) Add Two Numbers
--------------------------------
`2. Add Two Numbers `_
Medium
| **Notes on task**
| List stores a number digits in reverse order.
| Better thinking: the least significant digit comes first
| **Why**
| (this is how we add anyway, starting with the LSD).
| The list that you return is in the same order.
| Such a representation can be used to represent unbounded integers.
| **Hints**
| Use grade-school alg. First solve assuming no carry.
| (Why not convert to int. Ints word length is fixed by the machine architecture,
| while lists can be arbitrary long.)
**Solution 1 V1** [:ref:`2 `] (LC accepted 90, 70) ::
def add_two_nums(L1, L2):
place_iter = dummy_head = ListNode()
carry = 0
while L1 or L2 or carry:
data = carry + (L1.val if L1 else 0) + (L2.val if L2 else 0)
L1 = L1.next if L1 else None
L2 = L2.next if L2 else None
place_iter.next = ListNode(data % 10)
carry, place_iter = data // 10, place_iter.next
return dummy_head.next
**Solution 1 V2** [:ref:`10 `] ::
class Solution:
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
dummy = ListNode()
cur = dummy
carry = 0
while l1 or l2 or carry:
v1 = l1.val if l1 else 0
v2 = l2.val if l2 else 0
# new digit
val = v1 + v2 + carry
carry = val // 10
val = val % 10
cur.next = ListNode(val)
# update ptrs
cur = cur.next
l1 = l1.next if l1 else None
l2 = l2.next if l2 else None
return dummy.next
**My V** (LC accepted 50, 9%) ::
class Solution:
def addTwoNumbers(self, L1: Optional[ListNode], L2: Optional[ListNode]) -> Optional[ListNode]:
dummy = iter3 = ListNode()
iter1 = L1
iter2 = L2
carry=0
while iter1 or iter2 or carry:
val1 = iter1.val if iter1 else 0
val2 = iter2.val if iter2 else 0
res = val1 + val2 + carry
ans = res % 10
carry = res // 10
iter3.next = ListNode(ans)
iter1 = iter1.next if iter1 else iter1
iter2 = iter2.next if iter2 else iter2
iter3 = iter3.next
return dummy.next
168. (LC 138) Copy List with Random Pointer
-----------------------------------------------
`138. Copy List with Random Pointer `_
Medium
| **Notes on task**
| They speak of random being an index.
| But really node.random works in the same way as node.next (just points not to the
| next node, but to some random node.)
| Also, they confusedly refer to a node having [val, random_index]. While really
| node does NOT have the .random_index attribute on it. So a node has no info about
| the index of random node it points to. A node just points to AN index with node.random.
| **Steps**
| -Use two passes and a hash map
| Pass 1: creating hash map {oldNode: newNode}
| Here we create copies of nodes (just separate nodes, even without links yet).
| (With hash map we want to solve the case when we have to random point node3 to node5 e.g.
| So we use hash map to locate the copy of the 5th node.)
| Pass 2: leverage the hash map
| copy = oldToCopy[cur]
| Get copy of the node and point its copy.next, copy.random to the node in the hash table:
| copy.next = oldToCopy[cur.next]
| copy.random = oldToCopy[cur.random]
**Solution 1** [:ref:`10 `] ::
"""
# Definition for a Node.
class Node:
def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
self.val = int(x)
self.next = next
self.random = random
"""
class Solution:
def copyRandomList(self, head: "Node") -> "Node":
oldToCopy = {None: None}
cur = head
while cur:
copy = Node(cur.val)
oldToCopy[cur] = copy
cur = cur.next
cur = head
while cur:
copy = oldToCopy[cur]
copy.next = oldToCopy[cur.next]
copy.random = oldToCopy[cur.random]
cur = cur.next
return oldToCopy[head]
**My Vs** ::
### My V2 (LC accepted 70, 60%)
class Solution:
def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
if not head:
return
d = {}
cur = head
while cur:
new_node = Node(cur.val)
d[cur] = new_node
cur = cur.next
cur2 = head
while cur2:
d[cur2].next = d[cur2.next] if cur2.next else None
d[cur2].random = d[cur2.random] if cur2.random else None
cur2 = cur2.next
return d[head]
### My V (LC accepted 35, 80%)
class Solution:
def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
d = {None: None} # {oldNode: newNode}
iter1 = head
while iter1:
node = Node(iter1.val)
d[iter1] = node
iter1 = iter1.next
iter1 = head
while iter1:
iter2 = d[iter1]
iter2.random = d[iter1.random]
iter2.next = d[iter1.next]
iter1 = iter1.next
# iter2 = iter2.next Don't need it
return d[head]
169. (LC 146) LRU Cache
---------------------------
`LC 146. LRU Cache `_
Medium
**LL + HASH MAP**
The standard algorithm for this task is to use a linked list and a hash map.
.. admonition:: Example 1.
::
# Hash map "_map": LL list "_keys":
# 1: 1 < 2 < _ < _ < 5 _ < _
# 2: ^----------------| _keys.push_front(5)
#
# .. E.g. get(5), we move node 5 up front in LL.
# 5: Get iter of 5 querying the hash map.
# New position of 5 is _keys.begin()
| **Why hash + queue is not the way**
| Example 2.
| size=2
| put(1,1), put(2,2), get(1), get(1), get(1), put(3,3)
| hash={1:1,2:2}
| Before put(3)
::
# q=[1,2,1,1,1]
# X X X
Because just popping from one end doesn't give the correct key to remove from hash
before put(3). We might need to remove from the middle, not just from one end.
| **Why LL**
| Because we can remove from LL in O(1).
| Remove 5 from LL in example 1.
| The problem is that accessing in LL is O(n).
| To access 5 in O(1), we store its position (iterator) as a second value in hash map.
| **OrderedDict**
| The combination LL + hash map is implemented in
| -OrderedDict() in Python
| -LinkedHashMap<> in Java
| (These two use LL+map internally.)
| (The dict keeps the order of keys insertion.)
| So we don't need to do it ourselves.
| (In C++ have to use LL + hash map.)
C++
^^^^^^^^^^
[:ref:`14 `] LC accepted 65, 40.
.. code-block:: cpp
class LRUCache {
int _capacity;
list _keys;
unordered_map::iterator>> _map;
public:
LRUCache(int capacity) : _capacity(capacity){ }
int get(int key) {
if(_map.find(key) != _map.end()){
_keys.erase(_map[key].second); //get position of key from map, erase from LL
_keys.push_front(key); //move to front of LL
_map[key].second = _keys.begin(); //record key new position in map
return _map[key].first;
}
return -1;
}
void put(int key, int value) {
//IF KEY IN MAP, replace it:
if(_map.find(key) != _map.end()){
_keys.erase(_map[key].second); //remove from LL
_keys.push_front(key); //add to front
_map[key] = {value, _keys.begin()}; //record in map, {value, new position of key}
} else {
//KEY NOT IN MAP AND WE ARE BEYOND OUR CAPACITY
if(_keys.size() == _capacity){
_map.erase(_keys.back()); //erase from map key that is least recent in LL
_keys.pop_back(); //also remove that key from LL
}
_keys.push_front(key); //add new key to LL, map
_map[key] = {value, _keys.begin()};
}
}
};
**Python** ::
### Solution 1 (LC accepted 99,90%, submission 2: 88,90%). From LC site users solutions.
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity: int):
self.capacity = capacity
self.cache = OrderedDict()
def get(self, key: int) -> int:
if key in self.cache:
# Move the accessed key to the end
self.cache.move_to_end(key) #OrderedDict method
return self.cache[key]
return -1
def put(self, key: int, value: int) -> None:
if key in self.cache:
# Update the value and move the key to the end
self.cache[key] = value
self.cache.move_to_end(key)
else:
# Add a new key-value pair
if len(self.cache) >= self.capacity:
# Evict the least recently used key (first key in OrderedDict), FIFO
self.cache.popitem(last=False) #if last=True (default) then LIFO
self.cache[key] = value
If you can't remember ".move_to_end()",
can pop(key) and then insert it back into dict.
170. (LC 707) Design Linked List
-------------------------------------
`707. Design Linked List `_
Medium
(Hints = optimizations)
| **Note:**
| You can be give invalid/out of range indices for methods addAtIndex, deleteAtIndex.
| Note, here we have to add ListNode class ourselves.
| (But it seem you can instantiate from it without defining it, although the
| boilerplate omits it.)
| **Optimizations:**
| -keep track of list size!!
| -don't implement addAtHead, addAtTail. addAtIndex has all the edge cases.
| -consider instead of self.head=None, having self.dummy=ListNode() (so actual node with val 0)
-Common error is to omit the test in A LOT of places.
**Solution 1** [:ref:`7 `] ::
class MyLinkedList:
def __init__(self):
self.dummy = ListNode()
self.cnt = 0
def get(self, index: int) -> int:
if index < 0 or index >= self.cnt:
return -1
cur = self.dummy.next
for _ in range(index):
cur = cur.next
return cur.val
def addAtHead(self, val: int) -> None:
self.addAtIndex(0, val)
def addAtTail(self, val: int) -> None:
self.addAtIndex(self.cnt, val)
def addAtIndex(self, index: int, val: int) -> None:
if index > self.cnt:
return
pre = self.dummy
for _ in range(index):
pre = pre.next
pre.next = ListNode(val, pre.next)
self.cnt += 1
def deleteAtIndex(self, index: int) -> None:
if index >= self.cnt:
return
pre = self.dummy
for _ in range(index):
pre = pre.next
t = pre.next
pre.next = t.next
t.next = None
self.cnt -= 1
::
### My V (LC accepted: T30% S70%)
class ListNode: #you don't have to include it, though the boilerplate omits it
def __init__(self, val=0):
self.val = val
self.next = None
class MyLinkedList:
def __init__(self):
self.head = None
def get(self, index: int) -> int:
if not self.head:
return -1
elif index == 0:
return self.head.val
i = 0
cur = self.head
while cur.next and i != index:
cur = cur.next
i+=1
if i != index:
return -1
else:
return cur.val
def addAtHead(self, val: int) -> None:
if not self.head:
self.head = ListNode(val)
else:
tmp = self.head
self.head = ListNode(val)
self.head.next = tmp
def addAtTail(self, val: int) -> None:
new_node = ListNode(val)
if not self.head:
self.head = new_node
else:
cur = self.head
while cur.next:
cur = cur.next
cur.next = new_node
def addAtIndex(self, index: int, val: int) -> None:
if not self.head and index > 0:
return
new_node = ListNode(val)
if index == 0:
if not self.head:
self.head = new_node
else:
tmp = self.head
self.head = new_node
self.head.next = tmp
else:
cur = self.head
i=0
while cur.next and i != index-1:
cur = cur.next
i+=1
if i == index-1:
tmp = cur.next
cur.next = new_node
new_node.next = tmp
def deleteAtIndex(self, index: int) -> None:
if index == 0:
self.head = self.head.next
else:
cur = self.head
i = 0
while cur.next and i != index - 1:
cur = cur.next
i+=1
if i == index-1 and cur.next: # and cur.next for when we are given index>len
tmp = cur.next
cur.next = cur.next.next
tmp.next = None
171. (LC 23) Merge k Sorted Lists
------------------------------------
`23. Merge k Sorted Lists `_
Hard
| **Clarification on task:**
| /lists[index] gives you the head of a linked list, so head is not lists[index[0]]
| /EACH LL is sorted, but lists are NOT sorted relative to their first value, e.g.:
| lists = [[1],[0]]
| So it is a must to start merging using a dummy node.
| lists[i].val not necessarily a smaller value than lists[i+1].val
|
| **Keys:**
| -Yes, reminiscent of a brute force. Because we merge lists in pairs. No fancies there.
| -We use a helper function to merge 2 lists.
| My V:
| -While len of lists > 1, pop 2 lists from list, merge with a helper, append merged list to lists.
| -In merge function use dummy head, p1,p2 are heads of 2 lists.
| lists = [[1,4,5],[1,3,4],[2,6]]
| merge([1,4,5],[1,3,4])
| lists = [[2,6],[1,1,3,4,4,5]]
| merge([2,6], [1,1,3,4,4,5])
| lists = [[1,1,2,3,4,4,5,6]]
| **Optimization**
| Is when we merge lists of about the same len. So pop lists from front, append to back.
| It brings time from over 1000 ms to under 100 ms in Python3.
::
### My V (LC accepted 25, 75%)
class Solution:
def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
def merge_lists(l1, l2):
dummy = ListNode()
cur = dummy
while l1 and l2:
if l1.val <= l2.val:
cur.next = l1
l1 = l1.next
else:
cur.next = l2
l2 = l2.next
cur = cur.next
cur.next = l1 if l1 else l2
return dummy.next
if len(lists) == 1:
return lists[0]
if not lists:
return None
while len(lists) > 1:
lists.append(merge_lists(lists.pop(0), lists.pop(0)))
return lists[0]
| OR:
| -Main alg: while + in range loops
| Take lists in pairs, merge them, put into a temporary list of lists.
| After each full pass, make lists=newMergedLists.
| Start anew with this new list of lists, till you are left with 1 list.
**Solution 1** [:ref:`10 `] O(N log K) ::
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
if not lists or len(lists) == 0:
return None
while len(lists) > 1: #**1
mergedLists = []
for i in range(0, len(lists), 2):
l1 = lists[i]
l2 = lists[i + 1] if (i + 1) < len(lists) else None #*2
mergedLists.append(self.mergeList(l1, l2))
lists = mergedLists
return lists[0]
def mergeList(self, l1, l2):
dummy = ListNode()
tail = dummy
while l1 and l2:
if l1.val < l2.val:
tail.next = l1
l1 = l1.next
else:
tail.next = l2
l2 = l2.next
tail = tail.next
if l1:
tail.next = l1
if l2:
tail.next = l2
return dummy.next
#**1
Merge taking lists in pairs from the original list of lists.
You will end up with twice as little lists. ::
# in range loop
# l1,l2,l3,l4
# l5 l6
# while loop
Make the resulting new lists -> the new list of lists
Repeat while till you are left with 1 list.
| #**2
| l2 = lists[i + 1] if (i + 1) < len(lists) else None #*2
| Account for the case when there is an odd num of lists.
| Then it is ok to use our mergeList() func on list1 + None.
C++
^^^^^^^
.. code-block:: cpp
//My V (LC accepted 20(40ms), 97)
class Solution {
private:
ListNode* merge(ListNode* l1, ListNode* l2){
ListNode n;
ListNode* dummy = &n;
ListNode* cur = dummy;
while(l1 && l2) {
if(l1->val < l2->val){
cur->next = l1;
l1 = l1->next;
} else{
cur->next = l2;
l2 = l2->next;
}
cur = cur->next;
}
cur->next = l1 ? l1 : l2;
return dummy->next;
}
public:
ListNode* mergeKLists(vector& lists) {
if(lists.empty())
return nullptr;
if(lists.size() == 1)
return lists[0];
while(lists.size() != 1){
ListNode* l1 = lists.front();
lists.erase(lists.begin());
ListNode* l2 = lists.front();
lists.erase(lists.begin());
lists.push_back(merge(l1,l2));
}
return lists[0];
}
};
//My V (LC accepted 80 (13 ms), 40)
//trying to optimize erasing from vector from front, i.e. try using deque
class Solution {
private:
ListNode* merge(ListNode* l1, ListNode* l2){
ListNode n;
ListNode* dummy = &n;
ListNode* cur = dummy;
while(l1 && l2) {
if(l1->val < l2->val){
cur->next = l1;
l1 = l1->next;
} else{
cur->next = l2;
l2 = l2->next;
}
cur = cur->next;
}
cur->next = l1 ? l1 : l2;
return dummy->next;
}
public:
ListNode* mergeKLists(vector& lists) {
deque listsD(lists.begin(), lists.end());
if(lists.empty())
return nullptr;
if(lists.size() == 1)
return lists[0];
while(listsD.size() != 1){
ListNode* l1 = listsD.front();
listsD.pop_front();
ListNode* l2 = listsD.front();
listsD.pop_front();
listsD.push_back(merge(l1,l2));
}
return listsD[0];
}
};
172. (LC 25) Reverse Nodes in k-Group
----------------------------------------
`25. Reverse Nodes in k-Group `_
Hard
CHALLENGING
| The difficulty:
| E.g.
| 1>2>3>4>5 -->
| 2>1>4>3>5 (after reversal 1 points at 4)
| **Notes:**
| -we have a dummy node because we are potentially changing the list's head.
| -Save node right before our group (that node is not part of the group)
| groupPrev = dummy
| -Save node after our group (not part of the group)
| groupNext = kth.next
| #**1
| Reversing group
| Note that in the classic 'reverse LL' alg we have:
| prev=None
| But because here we reverse a group in LL, not the entire LL, if we use prev=None,
| we will break the LL. Hence we use:
| prev=kth.next
**Solution 1** [:ref:`10 `] ::
class Solution:
def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
dummy = ListNode(0, head)
groupPrev = dummy
while True:
kth = self.getKth(groupPrev, k)
if not kth:
break
groupNext = kth.next
# reverse group **1
prev, curr = kth.next, groupPrev.next
while curr != groupNext:
tmp = curr.next
curr.next = prev
prev = curr
curr = tmp
tmp = groupPrev.next
groupPrev.next = kth
groupPrev = tmp
return dummy.next
def getKth(self, curr, k):
while curr and k > 0:
curr = curr.next
k -= 1
return curr