Leetcode: Linked Lists Flashcards
Add two numbers
You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order, and each of their nodes contains a single digit. Add the two numbers and return the sum as a linked list.
You may assume the two numbers do not contain any leading zero, except the number 0 itself.
Loop through both, add values to list, create a list to int and add them, then create a linked list from the values is brute.
- Can be done in-line
- Requires a carry variable to account for 6+8=14, the 1 needs to be carried to the next iteration
- The new linked list must be created in reverse order. this requires a previous variable=None to start and it will be similar to how you reverse a linked list
- The new node needs to be created with the remainder of the carry (modulus)
Time: Linear O(max(n1, n2))
Space: Linear O(max(n1, n2))
class Solution:
- —def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
- ——-current = response = ListNode(0)
- ——-carry = 0
- ——-while l1 or l2:
- —————carry += l1.val
- —————l1 = l1.next
- ———–if l2:
- —————carry += l2.val
- —————l2 = l2.next
- ———–div, mod = divmod(carry, 10)
- ———–carry = div
- ———–current.next = ListNode(mod)
- ———–current = current.next
- ——-if carry > 0:
- ———–current.next = ListNode(carry)
- ——-return response.next
Find the intersection of linked list
You are given two singly linked lists. The lists intersect at some node. Find, and return the node. Note: the lists are non-cyclical.
Example:
A = 1 -> 2 -> 3 -> 4 B = 6 -> 3 -> 4
- intersection of the linked list means that the nodes are of equal value,
1-2- 3 -4
/
6 - calculate lengths of linked lists
- if one is larger than the other, increment through the linked list until they are the same size, for i in range(l1-l2), n1=n1.next
- loop through each linked list and check if the nodes are equal, cannot check on value bc if a node is null you get npe
time: O (n1+n2)
space: O 1, we do not store any new values, we only change the head pointer of n1 or n2
def get_length(self, node):
- -if not node:
- —return 0
- -else:
- —return 1+self.get_length(node.next)
def optimal(self, n1, n2):
- -l1, l2 = self.get_length(n1), self.get_length(n2)
- -cur_1, cur_2 = n1, n2
- —if l1>l2:
- —–for i in range(l1-l2):
- ——-cur_1=cur_1.next
- —else:
- —–for i in range(l2-l1):
- ——-cur_2=cur_2.next
- —while cur_1 != cur_2:
- —–cur_1=cur_1.next
- —–cur_2=cur_2.next
- —return cur_1
Consecutive nodes sum to 0
Hi, here’s your problem today. This problem was recently asked by Uber:
Given a linked list of integers, remove all consecutive nodes that sum up to 0.
Example:
Input: 10 -> 5 -> -3 -> -3 -> 1 -> 4 -> -4
Output: 10
The consecutive nodes 5 -> -3 -> -3 -> 1 sums up to 0 so that sequence should be removed. 4 -> -4 also sums up to 0 too so that sequence should also be removed.
- can be solved by finding prefix sum if l1+l2 = l1+l2+…+l5, meaning that l3 + … + l5 = 0, then l3 + … + l5 is the consecutive sequence of nodes we want to delete.
- If it’s a array we could just remove numbers from index of l3 to l5.
- If it’s a linked list, we could let l2.next = l5.next, we then need to have two pointers, one point to l2 and the other point to l5
- if you do not create a dummy head with .next=first value, then you should realize thatmore:
Intuition and Algorithm
Imagine that you have this Linked list:
head: [3,4,2,-6, 1,1,5, -6]What elements should I remove?
We will accumulate the values.
head’ : [3, 7,9,3,4,5,10,4]
(Like an array), head’[i] = head[i] + head’ [i-1]
If we see repeated elements then we have to deleted as follows.
time: O(n), 2n because we loop through the linked list twice
space: O(n), worst-case we store all the values in the map
class Solution:
- —def very_difficult(self, node):
- ——-dummy_head=start = Node(0, node)
- ——-map={}
- ——-sum=0
- ——-while start:
- ———–sum+=start.value
- ———–map[sum]=start
- ———–start=start.next
- ——-start=dummy_head
- ——-sum=0
- ——-while start:
- ———–sum+=start.value
- ———–start.next=map[sum].next
- ———–start=start.next
- ——-return dummy_head.next
list strategies for linked lists
- linked lists go in circles if they are circular they loop around eventually (think of a fast/slow runner on a track)
- Know how to swap 2 node in a position, you need temp variables and such (swap nodes in pairs)
- fast/slow node using while fast and fast.next is a very common idea
- Think of kth element from end, that is a way to get a fixed depth on a linked list without knowing the length
- think of linked list with random pointer problem
merge two sorted lists
Merge two sorted linked lists and return it as a sorted list. The list should be made by splicing together the nodes of the first two lists.
brute force is recursive, it will take O(n+m) time and space because of the recursive calls
- —def recursive(self, l1, l2):
- ——-if not l1:
- ———–return l2
- ——-elif not l2:
- ———–return l1
- ——-if l1.val < l2.val:
- ———–l1.next=self.recursive(l1.next, l2)
- ———–return l1
- ——-else:
- ———–l2.next=self.recursive(l1, l2.next)
- ———–return l2
- Iterative is better for space
- very similar to merge sort
- while both ll’s are not none, we compare the heads and set the current.next value to the node that has the lesser value, and increment
- once one of them is none, it means the remaining ll is sorted and we can set current.next = head of the last ll, creating the sorted list
- return head.next
time: O(n+m), we traverse both ll’s fully
space: constant
- —def answer(self, l1: ListNode, l2: ListNode) -> ListNode:
- ——-current=head=ListNode(0)
- ——-while l1 and l2:
- ———–if l1.val less than l2.val:
- —————current.next = l1
- —————current=current.next
- —————l1=l1.next
- ———–else:
- —————current.next=l2
- —————current=current.next
- —————l2=l2.next
- ——-# to get this, you probably have to visualize it and write it out
- ——-current.next = l1 if l1 else l2
- ——-return head.next
Linked list cycle
Given head, the head of a linked list, determine if the linked list has a cycle in it.
There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the next pointer. Internally, pos is used to denote the index of the node that tail’s next pointer is connected to. Note that pos is not passed as a parameter.
Return true if there is a cycle in the linked list. Otherwise, return false
brute is to use a hashmap, but you can reduce the space complexity
- —def base(self, node):
- ——-m={}
- ——-current=node
- ——-while current:
- ———–if current in m:
- —————return True
- ———–m[current]=True
- ———–current=current.next
——–return False
- how can we traverse a linked list? normally with next, or with a fast/slow with slow.next, fast.next.next
- if the ll is a cycle, slow.next will eventually equal fast.next.next
- first iteration we must check slow.next vs fast.next.next, or just make sure not to check the first nodes against each other bc they will be equal
time: O(2n), if it’s not cyclic it is just N, if it is it would be 2n to account for the slow iterations catching up with the fast ones. thinking about a fast/slow runner, the slow run would make 2 laps before they meet
space: constant, we create no new objects
class Solution:
- —def is_cyclic(self, node):
- ——-slow=fast=node
- ——-while slow and fast and fast.next:
- ———–if slow.next is fast.next.next:
- —————return True
- ———–slow=slow.next
- ———–fast=fast.next.next
- ——-return False
- —def recursive(self, head: Node) -> bool:
- ——-if not head or not head.next:
- ———–return False
- ——-return self.helper(head, head)
- —def helper(self, slow, fast):
- ——-if not slow or not fast or not fast.next:
- ———–return False
- ——-if slow.next is fast.next.next:
- ———–return True
- ——-elif self.helper(slow.next, fast.next.next):
- ———–return True
Remove Kth element
Given the head of a linked list, remove the nth node from the end of the list and return its head.
brute force is to calculate length of list with 1 pass, create the current=dummy=node(0, head), then subtract length-index to figure out when we stop traversing the list to delete the node. This takes at most 2 passes
- single pass knowing that if we are 3 from the end, if we create 2 pointers, pointer a starts 3 ahead of the pointer b, when pointer a reaches the end, b will be at the pointer 3 from the end
- when we have to do things in 1 pass, we typically need multiple pointers to the same object and we need to modify them in different ways, for linked lists, fast/slow
- cannot return head bc when we remove the first node, head will still equal the first node, we need to make a dummy head and return dummy.next
REMEMBER when you need to skip over a node, it is better to do node.next = node.next.next instead of doing node.next = other.next bc we know for a fact we can skip over the node doing node.next.next (I got it wrong in this problem bc I did slow.next = fast.next, but that doesn’t work in all cases)
time: linear, 1 pass, 2 pointers
space, constant
class Solution:
- —def answer(self, head, n):
- ——-dummy_head = ListNode(0, head)
- ——-slow = fast = dummy_head
- ——-for _ in range(n):
- ———–fast = fast.next
- ——-while fast and fast.next:
- ———–slow = slow.next
- ———–fast = fast.next
- ——-slow.next = slow.next.next
- ——-return dummy_head.next
- takes 2 iterations
- —def removeNthFromEnd(self, head, n):
- ——-def get_length(node):
- ———–count = 0
- ———–while node:
- —————count += 1
- —————node = node.next
- ———–return count
- ——-l = get_length(head)
- ——-if l == n:
- ———–return head.next
- ——-prev = None
- ——-cur = head
- ——-for _ in range(l - n):
- ———–prev = cur
- ———–cur = cur.next
- ——-prev.next = cur.next
- ——-return head
swap nodes in pairs
Given a linked list, swap every two adjacent nodes and return its head. You must solve the problem without modifying the values in the list’s nodes (i.e., only nodes themselves may be changed.)
- recursive is develop a base case, make sure head and head.next are valid, and swap the nodes
- iterative is the same, but we need to start off with an initial case and then do a while loop
time: O(n), linear,
space: recursive is linear bc of recursive space, iterative is constant space
class Solution:
- —def swapPairs(self, head: ListNode):
- ——-if not head or not head.next:
- —————return head
- ——-first=head
- ——-second=head.next
- ——-third=head.next.next
- ——-second.next=first
- ——-first.next=self.swapPairs(third)
- ——-return second
- —def iterative(self, head):
- ——-if not head or not head.next:
- —————return head
- ——-dummy_head=Node(0, head)
- ——-current=dummy_head.next
- ——-prev=dummy_head
- ——-while current and current.next:
- ———–first=current
- ———–second=current.next
- ———–third=second.next
- ———–second.next=first
- ———–first.next=third
- ———–prev.next=second
- ———–current=third
- ———–prev=first
- ——-return dummy_head.next
Palindrome linked list
determine if a linked list is a palindrome
1, 2, 2, 1
1, 2, 3, 2, 1
1, 1, 1, 2, 3, 1,
- to get to a midpoint of a linked list, perform slow=fast=node, while fast and fast.next. you need fast and fast.next bc on even linked lists, fast.next is valid but it will equal None, so None.next = NPE
- slow points to the middle, the reverse of the middle will be equal to the current during a while loop for both, so while slow, check if current!=slow, if true return False, if not True
- for recursive, we have to make sure each previous if/else statement is invalid when we hit the next one. the second if statement will change slow.next, which can affect the fast.next loop since we are changing the same objects in memory. we need to add if slow and fast and fast.next to prevent this, bc if slow==None, we won’t have the first or second if loop
time: O(n/2 + n/2 + n/2), we loop through half the node to get to the middle with slow, reverse slow, compare slow to current
space: constant, we do not create any additional objects
class Palindrome(object):
- —def answer(self, node):
- ——-slow=fast=current=node
- ——-while fast and fast.next:
- ———–fast=fast.next.next
- ———–slow=slow.next
- ——-slow=self.reverse(slow)
- ——-while slow:
- ———–if slow.value!=current.value:
- —————return False
- ———–slow=slow.next
- ———–current=current.next
- ——-return True
- —def reverse(self, node):
- ——-prev=None
- ——-while node:
- ———–_next=node.next
- ———–node.next=prev
- ———–prev=node
- ———–node=_next
- ——-return prev
Convert Binary Search Tree to Sorted Doubly Linked List
Convert a Binary Search Tree to a sorted Circular Doubly-Linked List in place.
You can think of the left and right pointers as synonymous to the predecessor and successor pointers in a doubly-linked list. For a circular doubly linked list, the predecessor of the first element is the last element, and the successor of the last element is the first element.
We want to do the transformation in place. After the transformation, the left pointer of the tree node should point to its predecessor, and the right pointer should point to its successor. You should return the pointer to the smallest element of the linked list.
brute is to do inorder traversal to create a list, create the circular dll with that list
- can create circular dll with 1 loop using first/current pointers
- when node.left == None, that root is the first value, so set that to first if first==None
- then set current=root, which equals first
- else, current.right=root, root.left=current, then swap current for root
- dont forget to call inorder(root.right)!
- at the end, connect the list, first.left=current, current.right=first, return first
- you are modifying the original tree nodes, but we only modify root.left if there is no root.left and we do not modify root.right, so we can still traverse root.right
time: O(n), we traverse the whole tree and create the dll in 1 pass
space: O(N) the recursion depth, Log(n) if it is balanced
class Node:
- —def __init__(self, val, left=None, right=None):
- ——-self.val = val
- ——-self.left = left
- ——-self.right = right
class Solution:
- —def treeToDoublyList(self, root: ‘Node’) -> ‘Node’:
- ——-if not root:
- ———–return None
- ——-self.first, self.current = None, None
- ——-self.inorder(root)
- ——-self.current.right=self.first
- ——-self.first.left=self.current
- ——-return self.first
- —def inorder(self, root):
- ——-if root:
- ———–self.inorder(root.left)
- ———–if not self.first:
- —————self.first=root
- ———–else:
- —————self.current.right = root
- —————root.left = self.current
- ———–self.current=root
- ———–self.inorder(root.right)
- —def custom(self, root: ‘Node’) -> ‘Node’:
- ——-if not root:
- ———–return None
- ——-node_list=[]
- ——-self.inorder(root, node_list)
- ——-head = self.create_ll(node_list)
- ——-return head
- —def inorder(self, root, node_list):
- ——-if root:
- ———–self.inorder(root.left, node_list)
- ———–node_list.append(root.val)
- ———–self.inorder(root.right, node_list)
- —def create_ll(self, node_list):
- ——-_next=None
- ——-current=head=None
- ——-for x in node_list:
- ———–if not head:
- —————head = Node(x)
- —————current=head
- ———–else:
- —————current.right=Node(x, left=current)
- —————current=current.right
- ——-current.right=head
- ——-head.left=current
- ——-return head
create a circular doubly linked list
class Node:
- —def __init__(self, data):
- ——-self.data = data
- ——-self.next = None
- ——-self.prev = None
# Function to insert at the end def insertEnd(value) : ----global start ---- ----# If the list is empty, create a ----# single node circular and doubly list ----if (start == None) :
- ——-new_node = Node(0)
- ——-new_node.data = value
- ——-new_node.next = new_node.prev = new_node
- ——-start = new_node
- ——-return
—-# If list is not empty
- —# Find last node */
- —last = (start).prev
- —# Create Node dynamically
- —new_node = Node(0)
- —new_node.data = value
- —# Start is going to be next of new_node
- —new_node.next = start
- —# Make new node previous of start
- —(start).prev = new_node
- —# Make last previous of new node
- —new_node.prev = last
- —# Make new node next of old last
- —last.next = new_node
# Function to insert Node at the beginning # of the List, def insertBegin( value) : ----global start ---- ----# Pointer points to last Node ----last = (start).prev
- —new_node = Node(0)
- —new_node.data = value # Inserting the data
- —# setting up previous and
- —# next of new node
- —new_node.next = start
- —new_node.prev = last
- —# Update next and previous pointers
- —# of start and last.
- —last.next = (start).prev = new_node
- —# Update start pointer
- —start = new_node
# Function to insert node with value as value1. # The new node is inserted after the node with # with value2 def insertAfter(value1, value2) : ----global start ----new_node = Node(0) ----new_node.data = value1 # Inserting the data
- —# Find node having value2 and
- —# next node of it
- —temp = start
- —while (temp.data != value2) :
- ——-temp = temp.next
- —next = temp.next
- —# insert new_node between temp and next.
- —temp.next = new_node
- —new_node.prev = temp
- —new_node.next = next
- —next.prev = new_node
def display() :
- —global start
- —temp = start
- —print (“Traversal in forward direction:”)
- —while (temp.next != start) :
- ——-print (temp.data, end = “ “)
- ——-temp = temp.next
- —print (temp.data)
- —print (“Traversal in reverse direction:”)
- —last = start.prev
- —temp = last
- —while (temp.prev != last) :
- ——-print (temp.data, end = “ “)
- ——-temp = temp.prev
- —print (temp.data)
copy list with random pointer
A linked list of length n is given such that each node contains an additional random pointer, which could point to any node in the list, or null.
Construct a deep copy of the list. The deep copy should consist of exactly n brand new nodes, where each new node has its value set to the value of its corresponding original node. Both the next and random pointer of the new nodes should point to new nodes in the copied list such that the pointers in the original list and copied list represent the same list state. None of the pointers in the new list should point to nodes in the original list.
For example, if there are two nodes X and Y in the original list, where X.random –> Y, then for the corresponding two nodes x and y in the copied list, x.random –> y.
Return the head of the copied linked list.
The linked list is represented in the input/output as a list of n nodes. Each node is represented as a pair of [val, random_index] where:
val: an integer representing Node.val
random_index: the index of the node (range from 0 to n-1) that the random pointer points to, or null if it does not point to any node.
Your code will only be given the head of the original linked list.
iterative/recursive brute force involve a hashmap with the key being the real node and the value being the copy node. Check if the node already exists, if it does return it, else make one and return it. Both are linear time and space. For recursive problem remember to watch for cycles and think of it like a binary tree
Questions: Can you modify the original input? will the input always be of class Node? can the random pointers point to the same node? will there be nodes with duplicate values?
- need to find a way to relate the original.random to the new.random, but the random values are not guaranteed to be in any order
- we must modify the original list by interweaving the original list with the new list
- we need node1 - node1* - node2 - node2*
- we create the weaved linked list with 1 while loop just using the nexts, then a second while loop to set the copy.random = current.random.next since each original.next points to its respective copy value, we can find the copy.random value in constant time
- we can unweave this in the same while loop if we are very careful, but we can also just do 3 while loops for each step
time: O(n), we loop through the original list and copy list
space: constant,
class Solution:
- —def optimal(self, head):
- ——-if not head:
- ———–return None
- ——-current = head
- ——-copy_head = copy_current = None
- ——-while current:
- ———–if not copy_head:
- —————copy_head = copy_current = Node(current.val)
- ———–else:
- —————copy_current = Node(current.val)
- ———–_next = current.next
- ———–current.next = copy_current
- ———–copy_current.next = _next
- ———–current = current.next.next
- ——-current = head
- ——-copy_current = copy_head
- ——-while current:
- ———–if current.random:
- —————copy_current.random = current.random.next
- ———–else:
- —————copy_current.random = None
- ———–current = current.next.next
- ———–if current:
- —————copy_current.next = copy_current.next.next
- ———–else:
- —————copy_current.next = None
- ———–copy_current = copy_current.next
- ——-return copy_head
- —def remakes_input(self, head):
- ——-if not head:
- ———–return None
- ——-copy_head = copy = None
- ——-cur = head
- ——-while cur:
- ———–if not copy_head:
- —————copy_head = copy = Node(cur.val)
- ———–else:
- —————copy = Node(cur.val)
- ———–hold = cur.next
- ———–cur.next = copy
- ———–copy.next = hold
- ———–cur = hold
- ——-cur = head
- ——-copy = copy_head
- ——-while cur:
- ———–if cur.random:
- —————copy.random = cur.random.next
- ———–else:
- —————copy.random = None
- ———–if copy.next:
- —————copy = copy.next.next
- ———–cur = cur.next.next
- ——-cur = head
- ——-copy = copy_head
- ——-while cur:
- ———–cur.next = cur.next.next
- ———–if copy.next:
- —————copy.next = copy.next.next
- ———–copy = copy.next
- ———–cur = cur.next
- ——-return copy_head
Design a linked list
Design your implementation of the linked list. You can choose to use a singly or doubly linked list.
A node in a singly linked list should have two attributes: val and next. val is the value of the current node, and next is a pointer/reference to the next node.
If you want to use the doubly linked list, you will need one more attribute prev to indicate the previous node in the linked list. Assume all nodes in the linked list are 0-indexed.
Implement the MyLinkedList class:
MyLinkedList() Initializes the MyLinkedList object. int get(int index) Get the value of the indexth node in the linked list. If the index is invalid, return -1. void addAtHead(int 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. void addAtTail(int val) Append a node of value val as the last element of the linked list. void addAtIndex(int index, int val) Add a node of value val before the indexth node in the linked list. If index equals the length of the linked list, the node will be appended to the end of the linked list. If index is greater than the length, the node will not be inserted. void deleteAtIndex(int index) Delete the indexth node in the linked list, if the index is valid.
add at head is constant, the rest are linear time.
- the difficult part here is indexing each different operation. get you need to make sure the index > size - 1, add at index checks index > size, if either are true we stop
- make sure to increment/decrement the size at each add and delete operation
- when doing test caes, test on a linked lith with 0 - 1 - 2, bc that does everything you need
class ListNode:
- —def __init__(self, x):
- ——-self.val = x
- ——-self.next = None
class MyLinkedList:
- —def __init__(self):
- ——-self.size = 0
- ——-self.head = ListNode(0)
- —def get(self, index: int) -> int:
- ——-if index > self.size - 1:
- ———–return -1
- ——-cur = self.head.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.size, val)
- —def addAtIndex(self, index: int, val: int) -> None:—-
- ——-if index > self.size:
- ———–return
- ——-cur = self.head.next
- ——-prev = self.head
- ——-for _ in range(index):
- ———–prev = cur
- ———–cur = cur.next
- ——-new = ListNode(val)
- ——-prev.next = new
- ——-new.next = cur
- ——-self.size += 1
- —def deleteAtIndex(self, index: int) -> None:
- ——-if index > self.size - 1:
- ———–return
- ——-cur = self.head.next
- ——-prev = self.head
- ——-for _ in range(index):
- ———–prev = cur
- ———–cur = cur.next
- ——-prev.next = cur.next
- ——-self.size -= 1
class MyLinkedList:
- —def __init__(self):
- ——-self.head = ListNode(0)
- —def get(self, index: int) -> int:
- ——-index += 1
- ——-cur = self.head
- ——-while index > 0 and cur:
- ———–cur = cur.next
- ———–index -= 1
- ——-return -1 if not cur else cur.val
- —def addAtHead(self, val: int) -> None:
- ——-hold = self.head.next
- ——-_new = ListNode(val)
- ——-self.head.next = _new
- ——-_new.next = hold
- —def addAtTail(self, val: int) -> None:
- ——-cur = self.head
- ——-prev = None
- ——-while cur:
- ———–prev = cur
- ———–cur = cur.next
- ——-prev.next = ListNode(val)
- —def addAtIndex(self, index: int, val: int) -> None:
- ——-if index == 0:
- ———–self.addAtHead(val)
- ——-else:
- ———–index += 1
- ———–cur = self.head
- ———–prev = None
- ———–while index > 0 and cur:
- —————prev = cur
- —————cur = cur.next
- —————index -= 1
- ———–if index == 0:
- —————if not cur:
- ——————-prev.next = ListNode(val)
- —————else:
- ——————-_new = ListNode(val)
- ——————-prev.next = _new
- ——————-_new.next = cur——–
- —def deleteAtIndex(self, index: int) -> None:
- ——-if index == 0:
- ———–self.head = self.head.next
- ——-else:
- ———–index += 1
- ———–cur = self.head
- ———–while index > 0 and cur:
- —————prev = cur
- —————cur = cur.next
- —————index -= 1
- ———–if index == 0 and cur:
- —————prev.next = cur.next
Design a doubly linked list
Design your implementation of the linked list. You can choose to use a singly or doubly linked list.
A node in a singly linked list should have two attributes: val and next. val is the value of the current node, and next is a pointer/reference to the next node.
If you want to use the doubly linked list, you will need one more attribute prev to indicate the previous node in the linked list. Assume all nodes in the linked list are 0-indexed.
Implement the MyLinkedList class:
MyLinkedList() Initializes the MyLinkedList object. int get(int index) Get the value of the indexth node in the linked list. If the index is invalid, return -1. void addAtHead(int 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. void addAtTail(int val) Append a node of value val as the last element of the linked list. void addAtIndex(int index, int val) Add a node of value val before the indexth node in the linked list. If index equals the length of the linked list, the node will be appended to the end of the linked list. If index is greater than the length, the node will not be inserted. void deleteAtIndex(int index) Delete the indexth node in the linked list, if the index is valid.
- think outside the box, make sure to utilize a size variable, don’t be dumb and use a while loop!
- we can reduce linear operations by checking if it is larger than half the size, if it is, starting from the back is better
- show off your developer skills, no need to retype addAtHeadl/tail code incorporate that into your addAtIndex code!
class ListNode:
- —def __init__(self, x, next = None, prev = None):
- ——-self.val = x
- ——-self.next = next
- ——-self.prev = prev
class MyLinkedList:
- —def __init__(self):
- ——-self.size = 0
- ——-self.head, self.tail = ListNode(0), ListNode(0)
- ——-self.head.next = self.tail
- ——-self.tail.prev = self.head
- —def get(self, index: int) -> int:
- ——-if index > self.size - 1:
- ———–return -1
- ——-if index > self.size // 2:
- ———–cur = self.tail
- ———–for _ in range(self.size - index):
- —————cur = cur.prev
- ———–return cur.val
- ——-else:
- ———–cur = self.head.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.size, val)
- —def addAtIndex(self, index: int, val: int) -> None:—-
- ——-if index > self.size:
- ———–return
- ——-if index > self.size // 2:
- ———–cur = self.tail
- ———–for _ in range(self.size - index):
- —————cur = cur.prev
- ——-else:
- ———–cur = self.head.next
- ———–for _ in range(index):
- —————cur = cur.next
- ——-prev = cur.prev
- ——-new = ListNode(val)
- ——-prev.next = new
- ——-new.prev = prev
- ——-new.next = cur
- ——-cur.prev = new
- ——-self.size += 1
- —def deleteAtIndex(self, index: int) -> None:
- ——-if index > self.size - 1:
- ———–return
- ——-if index > self.size // 2:
- ———–cur = self.tail
- ———–for _ in range(self.size - index):
- —————cur = cur.prev
- ——-else:
- ———–cur = self.head.next
- ———–for _ in range(index):
- —————cur = cur.next
- ——-cur.prev.next = cur.next
- ——-cur.next.prev = cur.prev
- ——-self.size -= 1
linked list cycle 2
Given the head of a linked list, return the node where the cycle begins. If there is no cycle, return null.
There is a cycle in a linked list if there is some node in the list that can be reached again by continuously following the next pointer. Internally, pos is used to denote the index of the node that tail’s next pointer is connected to (0-indexed). It is -1 if there is no cycle. Note that pos is not passed as a parameter.
Do not modify the linked list.
brute force is to use a hashmap
- fast/slow pointers on steroids
- once you find a cycle, you need a pointer to that intersection and the head, then increment them each by 1 point. This is somehow proven that they will intersect at some point
- moral of the story is to try random things. You need to find a cycle, you get a pointer, the only other data point you have is the head, try double incrementing or single incrementing
time: O(n). finding the cycle takes linear time, a proof shows it is F + C - h where F are the nodes not in the cycle and C are nodes in the cycle. The second part will take linear time aswell
space: constant
class Solution:
- —def detectCycle(self, head):
- ——-cycle_node = self.find_cycle(head)
- ——-if cycle_node:
- ———–start = head
- ———–while start != cycle_node:
- —————start = start.next
- —————cycle_node = cycle_node.next
- ———–return start
- ——-else:
- ———–return None
- —def find_cycle(self, head):
- ——-cycle = False
- ——-slow = fast = head
- ——-while slow and fast and fast.next:
- ———–if slow.next == fast.next.next:
- —————return fast.next.next
- ———–slow = slow.next
- ———–fast = fast.next.next
- ——-return None