diff --git a/02. Linked lists/2.6. palindrome.py b/02. Linked lists/2.6. palindrome.py
new file mode 100644
index 0000000..f741a80
--- /dev/null
+++ b/02. Linked lists/2.6. palindrome.py
@@ -0,0 +1,15 @@
+# https://leetcode.com/problems/palindrome-linked-list/discuss/64500/11-lines-12-with-restore-O(n)-time-O(1)-space
+def isPalindrome(head) -> bool:
+ rev = None
+ slow = fast = head
+ while fast and fast.next:
+ fast = fast.next.next
+ rev, rev.next, slow = slow, rev, slow.next
+
+ if fast:
+ slow = slow.next
+
+ while rev and rev.val == slow.val:
+ rev = rev.next
+ slow = slow.next
+ return not rev
\ No newline at end of file
diff --git a/02. Linked lists/remove_ntg_node_from_tail.py b/02. Linked lists/remove_ntg_node_from_tail.py
new file mode 100644
index 0000000..7b1a637
--- /dev/null
+++ b/02. Linked lists/remove_ntg_node_from_tail.py
@@ -0,0 +1,14 @@
+# https://leetcode.com/problems/remove-nth-node-from-end-of-list/discuss/8802/3-short-Python-solutions
+class Solution:
+ def removeNthFromEnd(self, head, n):
+ slow = fast = head
+
+ for _ in range(n):
+ fast = fast.next
+ if not fast:
+ return head.next
+ while fast.next:
+ fast = fast.next
+ slow = slow.next
+ slow.next = slow.next.next
+ return head
\ No newline at end of file
diff --git a/04. Trees and graphs/max_depth_tree.md b/04. Trees and graphs/max_depth_tree.md
new file mode 100644
index 0000000..33fac6a
--- /dev/null
+++ b/04. Trees and graphs/max_depth_tree.md
@@ -0,0 +1,72 @@
+# Find Maximum depth of binary tree
+
+[LeetCode problem](https://leetcode.com/problems/maximum-depth-of-binary-tree/)
+
+Given the root of a binary tree, return its maximum depth. A binary tree's maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node.
+
+
+
+Example: input: [3,9,20,null,null,15,7], output: 3.
+
+## LeetCode initial code
+
+```python
+# Definition for a binary tree node.
+# class TreeNode:
+# def __init__(self, val=0, left=None, right=None):
+# self.val = val
+# self.left = left
+# self.right = right
+class Solution:
+ def maxDepth(self, root: Optional[TreeNode]) -> int:
+```
+
+## Recursive approach
+
+Depth first
+
+```python
+def maxDepth(self, root: Optional[TreeNode]) -> int:
+ def max_depth(root, depth):
+ if not root: return depth
+ return max(max_depth(root.left, depth+1), max_depth(root.right, depth+1))
+
+ return max_depth(root, 0)
+```
+
+## Iterative solution
+
+Breadth first
+
+```python
+def maxDepth(self, root: Optional[TreeNode]) -> int:
+
+ if not root:
+ return 0
+
+ queue = [root]
+ depth = 0
+
+ while True:
+
+ if not queue:
+ break
+
+ node_count = len(queue)
+ depth += 1
+
+ while node_count > 0:
+
+ node = queue[0]
+ queue.pop(0)
+
+ if node.left:
+ queue.append(node.left)
+ if node.right:
+ queue.append(node.right)
+
+ node_count -= 1
+
+ return depth
+
+```
diff --git a/04. Trees and graphs/symmetric_tree.py b/04. Trees and graphs/symmetric_tree.py
new file mode 100644
index 0000000..5a9f664
--- /dev/null
+++ b/04. Trees and graphs/symmetric_tree.py
@@ -0,0 +1,55 @@
+# Definition for a binary tree node.
+class TreeNode:
+ def __init__(self, val=0, left=None, right=None):
+ self.val = val
+ self.left = left
+ self.right = right
+
+# checks if tree is symmetric
+# https://leetcode.com/problems/symmetric-tree/
+
+# recursive approach, depth first
+class Solution:
+ def isSymmetric(self, root: Optional[TreeNode]) -> bool:
+ def symm(root1, root2):
+
+ if not root1 and not root2:
+ return True
+ if not root1 or not root2 or root1.val != root2.val:
+ return False
+ return symm(root1.left, root2.right) and symm(root1.right, root2.left)
+
+ if not root:
+ return True
+ return symm(root.left, root.right)
+
+
+# iterative approach, breadth first
+class Solution:
+ def isSymmetric(self, root: Optional[TreeNode]) -> bool:
+
+ if not root:
+ return True
+ queue = [(root.left, root.right)]
+
+ while True:
+
+ if not queue:
+ return True
+ count = len(queue)
+
+ while count > 0:
+
+ root1, root2 = queue[0]
+ queue.pop(0)
+
+ if not root1 and not root2:
+ return True
+ if not root1 or not root2 or root1.val != root2.val:
+ return False
+
+ if root1.left or root2.right:
+ queue.append((root1.left, root2.right))
+ if root1.right or root2.left:
+ queue.append((root1.right, root2.left))
+ count -= 1
diff --git a/10. Sorting and searching/README.md b/10. Sorting and searching/README.md
index 67c2358..f2f4fea 100644
--- a/10. Sorting and searching/README.md
+++ b/10. Sorting and searching/README.md
@@ -8,11 +8,29 @@
Start at the beginning, swap the first two elements if the first is greater than the second. Go to the next pair and repeat, and so on until reaching the end of the array.
+```python
+nums = [2, 1, 4, 6, 3, 8, 0]
+for k in range(len(nums)-1):
+ for i in range(len(nums) - k - 1):
+ if nums[i] > nums[i+1]:
+ nums[i], nums[i+1] = nums[i+1], nums[i]
+```
+
### Selection sort
> Runtime O(n2) average and worst case. Memory O(1)
-Simple but inefficient. Find the smallest element using a linear scan, move it to the front, swapping it with the first element. Then, find the second smallest and move it, doing a linear scan. And so on
+Simple but inefficient. Find the smallest element using a linear scan, move it to the front, swapping it with the first element. Then, find the second smallest and move it, doing a linear scan. And so on.
+
+```python
+nums = [2, 1, 4, 6, 3, 8, 0]
+for i in range(len(nums)):
+ min_pos = i
+ for j in range(i+1, len(nums)):
+ if nums[j] < nums[min_pos]:
+ min_pos = j
+ nums[i], nums[min_pos] = nums[min_pos], nums[i]
+```
### Merge sort
@@ -24,8 +42,44 @@ When merging, copy all the elements from the target array segment into a helper
Java algorithm in book.
+```python
+def merge_sort(arr):
+ if len(arr) > 1:
+
+ mid = len(arr)//2
+ left = arr[:mid]
+ right = arr[mid:]
+
+ merge_sort(left)
+ merge_sort(right)
+
+ i, j, k = 0, 0, 0
+ while i < len(left) and j < len(right):
+ if left[i] < right[j]:
+ arr[k] = left[i]
+ i += 1
+ else:
+ arr[k] = right[j]
+ j += 1
+ k += 1
+
+ while i < len(left):
+ arr[k] = left[i]
+ i += 1
+ k += 1
+
+ while j < len(right):
+ arr[k] = right[j]
+ j += 1
+ k += 1
+
+ return arr
+```
+
### Quick sort
+Similar to merge sort, divide and conquer.
+
> Runtime O(n log(n)) average, O(n2) worst case. Memory O(n log(n))
Pick a random element and partition the array, such that all numbers lower than the partitioning element come before all elements greater than it. Repeatedly partitioning the array (and its sub-arrays) around an element, the array is eventually sorted. But as the partitioned element is not guaranteed to be the median or close to it, the sorting could be very slow. That is why the worst case rutime is O(n2).
@@ -83,7 +137,7 @@ def binarySearch(arr, x):
l = 0
r = len(arr) - 1
while l <= r:
- mid = l + (r - l) // 2;
+ mid = l + (r - l) // 2
if arr[mid] == x:
return mid
diff --git a/10. Sorting and searching/binary_search.py b/10. Sorting and searching/binary_search.py
new file mode 100644
index 0000000..8f9ab1d
--- /dev/null
+++ b/10. Sorting and searching/binary_search.py
@@ -0,0 +1,33 @@
+# binary search in sorted array
+
+# recursive
+def binary_search_rec(nums, target, pos0, pos1):
+ if pos0 <= pos1:
+ midpoint = (pos0+pos1)//2
+ if target == nums[midpoint]:
+ return midpoint
+ elif target > nums[midpoint]:
+ return binary_search_rec(nums, target, midpoint+1, pos1)
+ elif target < nums[midpoint]:
+ return binary_search_rec(nums, target, pos0, midpoint-1)
+ else:
+ return -1
+
+# iterative
+def binary_search_iter(nums, target, pos0, pos1):
+ while True:
+ if pos0 <= pos1:
+ midpoint = (pos0+pos1)//2
+ if target == nums[midpoint]:
+ return midpoint
+ elif target > nums[midpoint]:
+ pos0 = midpoint + 1
+ elif target < nums[midpoint]:
+ pos1 = midpoint - 1
+ else:
+ return -1
+
+nums = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+target = 4
+res = binary_search_iter(nums, target, 0, len(nums)-1)
+print(res)
\ No newline at end of file
diff --git a/10. Sorting and searching/insertion_sort.py b/10. Sorting and searching/insertion_sort.py
new file mode 100644
index 0000000..5f676b8
--- /dev/null
+++ b/10. Sorting and searching/insertion_sort.py
@@ -0,0 +1,22 @@
+# efficient for small data values
+def insertionSort(arr):
+
+ # Traverse through 1 to len(arr)
+ for i in range(1, len(arr)):
+
+ key = arr[i]
+
+ # Move elements of arr[0..i-1], that are
+ # greater than key, to one position ahead
+ # of their current position
+ j = i-1
+ while j >= 0 and key < arr[j]:
+ arr[j + 1] = arr[j]
+ j -= 1
+ arr[j + 1] = key
+
+
+# Driver code to test above
+arr = [12, 11, 13, 5, 6]
+insertionSort(arr)
+print(arr)
diff --git a/10. Sorting and searching/merge_sort.py b/10. Sorting and searching/merge_sort.py
new file mode 100644
index 0000000..ecffe1f
--- /dev/null
+++ b/10. Sorting and searching/merge_sort.py
@@ -0,0 +1,35 @@
+def merge_sort(nums):
+ if len(nums) > 1:
+ mid = len(nums)//2
+ left = nums[:mid]
+ right = nums[mid:]
+
+ merge_sort(left)
+ merge_sort(right)
+
+ i, j, k = 0, 0, 0
+ while i < len(left) and j < len(right):
+ if left[i] < right[j]:
+ nums[k] = left[i]
+ i += 1
+ else:
+ nums[k] = right[j]
+ j += 1
+ k += 1
+
+ # finish merging remaining elements
+ while i < len(left):
+ nums[k] = left[i]
+ i += 1
+ k += 1
+
+ while j < len(right):
+ nums[k] = right[j]
+ j += 1
+ k += 1
+ return nums
+
+
+nums = [5, 4, 2, 1]
+res = merge_sort(nums)
+print(res)
\ No newline at end of file
diff --git a/10. Sorting and searching/quick_sort.py b/10. Sorting and searching/quick_sort.py
new file mode 100644
index 0000000..2025352
--- /dev/null
+++ b/10. Sorting and searching/quick_sort.py
@@ -0,0 +1,45 @@
+# Function to find the partition position
+def partition(array, low, high):
+
+ # Choose the rightmost element as pivot
+ pivot = array[high]
+
+ # Pointer for greater element
+ i = low - 1
+
+ # Traverse through all elements
+ # compare each element with pivot
+ for j in range(low, high):
+ if array[j] <= pivot:
+ # If element smaller than pivot is found
+ # swap it with the greater element pointed by i
+ i += 1
+ # Swapping element at i with element at j
+ array[i], array[j] = array[j], array[i]
+
+ # Swap the pivot element with the greater element specified by i
+ array[i + 1], array[high] = array[high], array[i + 1]
+
+ # Return the position from where partition is done
+ return i + 1
+
+# Function to perform quicksort
+def quick_sort(array, low, high):
+ if low < high:
+
+ # Find pivot element such that
+ # element smaller than pivot are on the left
+ # element greater than pivot are on the right
+ pi = partition(array, low, high)
+
+ # Recursive call on the left of pivot
+ quick_sort(array, low, pi-1)
+
+ # Recursive call on the right of pivot
+ quick_sort(array, pi+1, high)
+
+
+# Driver code
+array = [3, 5, 4, 2, 1, 6] # 10, 7, 8, 9, 1, 5
+quick_sort(array, 0, len(array)-1)
+print(f'Sorted array: {array}')