This is a collection of coding patterns I have learned to solve not only some of the most common problems, but the 14 patterns (yes, there are way more than 14, but the point here is taking 6 months of preparation and condensing it into a 30 minute read that would not take more than 1-2 weeks to master. I have found these problems and patterns to be the most useful in that the data structures and algorithms are used in many other problems and become familiar over time. Good luck!
Please feel free to comment if you got some value or find any errors!
Thanks!
- 1. Sliding Window
- 2. Two Pointers or Iterators
- 3. Fast and Slow Pointers
- 4. Merge Intervals
- 5. Cyclic Sort
- 6. In-place Reversal of a Linked List
- 7. Tree Breadth First Search
- 8. Tree Depth First Search
- 9. Two Heaps
- 10. Subsets
- 11. Modified Binary Search
- 12. Top K Elements
- 13. K-way Merge
- 14. Topological Sort
- Conclusion
Problem: Maximum Sum Subarray of Size K
Given an array of integers nums and an integer k, find the maximum sum of any contiguous subarray of size k.
Example: Input: nums = [2, 1, 5, 1, 3, 2], k = 3 Output: 9 (The subarray [5, 1, 3] has the maximum sum of 9)
#Pseudocode:
- Initialize
maxSum
to a negative value to handle negative elements. - Initialize
windowSum
to 0 to keep track of the sum of the current sliding window. - Iterate through the array from index 0 to length - 1:
- Add the current element to
windowSum
. - If the window size is equal to k, update
maxSum
to be the maximum ofmaxSum
andwindowSum
. - If the window size is greater than k, subtract the element from the left of the window and update
windowSum
.
- Add the current element to
- Return
maxSum
.
#JavaScript Solution:
function maxSumSubarray(nums, k) {
let maxSum = -Infinity;
let windowSum = 0;
for (let i = 0; i < nums.length; i++) {
windowSum += nums[i];
if (i >= k - 1) {
maxSum = Math.max(maxSum, windowSum);
windowSum -= nums[i - k + 1];
}
}
return maxSum;
}
#Python Solution:
def max_sum_subarray(nums, k):
max_sum = float('-inf')
window_sum = 0
for i in range(len(nums)):
window_sum += nums[i]
if i >= k - 1:
max_sum = max(max_sum, window_sum)
window_sum -= nums[i - k + 1]
return max_sum
Time Complexity - O(N), where N is the number of elements in the nums
array. We iterate through the array only once.
Space Complexity: O(1), as we use a constant amount of extra space.
Problem: Longest Substring Without Repeating Characters
Given a string s, find the length of the longest substring without repeating characters.
Example: Input: s = "abcabcbb" Output: 3 (The longest substring without repeating characters is "abc")
#Pseudocode:
- Initialize
maxLength
to 0 to keep track of the maximum length of the substring. - Initialize
start
to 0 to mark the start index of the current substring. - Create an empty
seen
dictionary to store the most recent index of each character. - Iterate through the string using the variable
end
:- If the current character is in
seen
and its index is greater than or equal tostart
:- Update
start
to be the next index of the current character.
- Update
- Update the
maxLength
to be the maximum of the currentmaxLength
andend - start + 1
. - Store the current index of the character in the
seen
dictionary.
- If the current character is in
- Return
maxLength
.
#JavaScript Solution:
function longestSubstring(s) {
let maxLength = 0;
let start = 0;
let seen = {};
for (let end = 0; end < s.length; end++) {
if (s[end] in seen && seen[s[end]] >= start) {
start = seen[s[end]] + 1;
}
maxLength = Math.max(maxLength, end - start + 1);
seen[s[end]] = end;
}
return maxLength;
}
#Python Solution:
def longest_substring(s):
max_length = 0
start = 0
seen = {}
for end in range(len(s)):
if s[end] in seen and seen[s[end]] >= start:
start = seen[s[end]] + 1
max_length = max(max_length, end - start + 1)
seen[s[end]] = end
return max_length
Time Complexity - O(N), where N is the length of the string s
. We iterate through the string only once.
Space Complexity: O(min(N, M)), where M is the size of the character set (e.g., ASCII or Unicode). In the worst case, the seen
dictionary can store all characters in the character set.
Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.
You may assume that each input would have exactly one solution, and you may not use the same element twice.
You can return the answer in any order.
- Initialize a dictionary,
numToIndex
, to store the index of each number encountered in the array. - Iterate over the array elements,
num
and their indices,i
:- Calculate the
complement
(target - num). - If the
complement
is already innumToIndex
, return[numToIndex[complement], i]
. - Otherwise, store the current
num
's index innumToIndex
.
- Calculate the
- If no pair is found, return
None
.
- Can the input array have duplicate elements?
- Are there guaranteed to be exactly one solution for each input?
- Can the input array be empty?
function twoSum(nums, target) {
let numToIndex = {};
for (let i = 0; i < nums.length; i++) {
let complement = target - nums[i];
if (complement in numToIndex) {
return [numToIndex[complement], i];
}
numToIndex[nums[i]] = i;
}
return null;
}
Time Complexity - O(N), where N is the number of elements in the nums
array.
Space Complexity - O(N), where N is the number of elements in the nums
array.
def two_sum(nums, target):
numToIndex = {}
for i, num in enumerate(nums):
complement = target - num
if complement in numToIndex:
return [numToIndex[complement], i]
numToIndex[num] = i
return None
Time Complexity - O(N), where N is the number of elements in the nums
array.
Space Complexity - O(N), where N is the number of elements in the nums
array.
You are given an integer array height of length n. There are n vertical lines drawn such that the two endpoints of the ith line are (i, 0) and (i, height[i]).
Find two lines that together with the x-axis form a container, such that the container contains the most water.
Return the maximum amount of water a container can store.
Notice that you may not slant the container.
- Initialize two pointers,
left
at the beginning andright
at the end of the array. - Initialize
maxArea
to store the maximum area found so far. - Iterate until
left
is less thanright
:- Calculate the
width
asright - left
. - Calculate the
height
as the minimum of the elements at indicesleft
andright
. - Calculate the
area
aswidth * height
. - Update
maxArea
to the maximum ofmaxArea
andarea
. - Move the pointer with the smaller element (the one that contributes less to the area) inward.
- Calculate the
- Return
maxArea
.
- Can the input array have negative integers?
- Can the input array have duplicate elements?
- Can the input array be empty?
function maxArea(height) {
let maxArea = 0;
let left = 0;
let right = height.length - 1;
while (left < right) {
let width = right - left;
let h = Math.min(height[left], height[right]);
let area = width * h;
maxArea = Math.max(maxArea, area);
if (height[left] < height[right]) {
left++;
} else {
right--;
}
}
return maxArea;
}
Time Complexity - O(N), where N is the number of elements in the height
array.
Space Complexity - O(1).
def max_area(height):
maxArea = 0
left = 0
right = len(height) - 1
while left < right:
width = right - left
h = min(height[left], height[right])
area = width * h
maxArea = max(maxArea, area)
if height[left] < height[right]:
left += 1
else:
right -= 1
return maxArea
Time Complexity - O(N), where N is the number of elements in the height
array.
Space Complexity - O(1).
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.
- Initialize
slow
andfast
pointers, both starting at the head of the linked list. - Iterate the pointers as long as
fast
andfast.next
are notnull
:- Move
slow
one step forward. - Move
fast
two steps forward. - If
slow
andfast
meet at the same node, returntrue
(cycle found).
- Move
- If the loop exits, return
false
(no cycle).
- Can the linked list have duplicate elements?
- Can the linked list be empty?
- Will the linked list always be singly linked?
function hasCycle(head) {
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) {
return true;
}
}
return false;
}
Time Complexity - O(N), where N is the number of nodes in the linked list. Space Complexity - O(1).
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def has_cycle(head):
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
Time Complexity - O(N), where N is the number of nodes in the linked list. Space Complexity - O(1).
Given the head of a singly linked list, return the middle node of the linked list.
If there are two middle nodes, return the second middle node.
- Initialize
slow
andfast
pointers, both starting at the head of the linked list. - Iterate the pointers as long as
fast
andfast.next
are notnull
:- Move
slow
one step forward. - Move
fast
two steps forward. - When
fast
reaches the end of the linked list,slow
will be at the middle node.
- Move
- Return the value of the middle node.
- Can the linked list have duplicate elements?
- Can the linked list be empty?
- Will the linked list always be singly linked?
function findMiddle(head) {
let slow = head;
let fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
}
return slow.val;
}
Time Complexity - O(N), where N is the number of nodes in the linked list. Space Complexity - O(1).
def find_middle(head):
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow.val
Time Complexity - O(N), where N is the number of nodes in the linked list. Space Complexity - O(1).
- Sort the intervals based on their start time.
- Initialize an empty list,
merged
, to store the merged intervals. - Iterate through the sorted intervals:
- If the current interval overlaps with the last interval in
merged
, merge them by updating the end time of the last interval. - Otherwise, add the current interval to
merged
.
- If the current interval overlaps with the last interval in
- Return the
merged
list.
- Can the intervals have negative start and end times?
- Can the intervals have overlapping ranges?
function mergeIntervals(intervals) {
intervals.sort((a, b) => a[0] - b[0]);
let merged = [intervals[0]];
for (let i = 1; i < intervals.length; i++) {
if (intervals[i][0] <= merged[merged.length - 1][1]) {
merged[merged.length - 1][1] = Math.max(merged[merged.length - 1][1], intervals[i][1]);
} else {
merged.push(intervals[i]);
}
}
return merged;
}
Time Complexity - O(N log N), where N is the number of intervals in the array due to sorting. Space Complexity - O(N) in the worst case when all intervals are non-overlapping and need to be merged.
def merge_intervals(intervals):
intervals.sort(key=lambda x: x[0])
merged = [intervals[0]]
for interval in intervals[1]:
if interval[0] <= merged[-1][1]:
merged[-1][1] = max(merged[-1][1], interval[1])
else:
merged.append(interval)
return merged
Time Complexity - O(N log N), where N is the number of intervals in the array due to sorting. Space Complexity - O(N) in the worst case when all intervals are non-overlapping and need to be merged.
- Initialize an empty list,
merged
, to store the merged intervals. - Iterate through the intervals:
- If the current interval ends before the new interval starts, add it to
merged
. - If the current interval starts after the new interval ends, add the new interval to
merged
and update it to the current interval. - Otherwise, merge the intervals by updating the start and end times of the new interval to cover both.
- If the current interval ends before the new interval starts, add it to
- Return the
merged
list.
- Can the intervals have negative start and end times?
- Can the intervals have overlapping ranges?
function insertInterval(intervals, newInterval) {
let merged = [];
let i = 0;
while (i < intervals.length && intervals[i][1] < newInterval[0]) {
merged.push(intervals[i]);
i++;
}
while (i < intervals.length && intervals[i][0] <= newInterval[1]) {
newInterval[0] = Math.min(newInterval[0], intervals[i][0]);
newInterval[1] = Math.max(newInterval[1], intervals[i][1]);
i++;
}
merged.push(newInterval);
while (i < intervals.length) {
merged.push(intervals[i]);
i++;
}
return merged;
}
Time Complexity - O(N), where N is the number of intervals in the intervals
array.
Space Complexity - O(N) in the worst case when all intervals need to be merged.
def insert_interval(intervals, new_interval):
merged = []
i = 0
while i < len(intervals) and intervals[i][1] < new_interval[0]:
merged.append(intervals[i])
i += 1
while i < len(intervals) and intervals[i][0] <= new_interval[1]:
new_interval[0] = min(new_interval[0], intervals[i][0])
new_interval[1] = max(new_interval[1], intervals[i][1])
i += 1
merged.append(new_interval)
while i < len(intervals):
merged.append(intervals[i])
i += 1
return merged
Time Complexity - O(N), where N is the number of intervals in the intervals
array.
Space Complexity - O(N) in the worst case when all intervals need to be merged.
- Use the cyclic sort algorithm to place each number in its correct position.
- Iterate through the sorted array to find the first missing number (the index that does not have the correct number).
- Return the missing number.
- Can the input array have negative integers?
- Can the input array have duplicate elements?
- Will there always be exactly one missing number?
function findMissingNumber(nums) {
const n = nums.length;
for (let i = 0; i < n; i++) {
while (nums[i] < n && nums[i] !== nums[nums[i]]) {
[nums[nums[i]], nums[i]] = [nums[i], nums[nums[i]]];
}
}
for (let i = 0; i < n; i++) {
if (nums[i] !== i) {
return i;
}
}
return n;
}
Time Complexity - O(N), where N is the number of elements in the nums
array.
Space Complexity - O(1).
def find_missing_number(nums):
n = len(nums)
for i in range(n):
while nums[i] < n and nums[i] != nums[nums[i]]:
nums[nums[i]], nums[i] = nums[i], nums[nums[i]]
for i in range(n):
if nums[i] != i:
return i
return n
Time Complexity - O(N), where N is the number of elements in the nums
array.
Space Complexity - O(1).
- Use the cyclic sort algorithm to place each number in its correct position.
- Iterate through the sorted array to find all missing numbers (the indices that do not have the correct number).
- Return the list of missing numbers.
- Can the input array have negative integers?
- Can the input array have duplicate elements?
- Will there be more than one missing number?
function findMissingNumbers(nums) {
const n = nums.length;
for (let i = 0; i < n; i++) {
while (nums[i] < n && nums[i] !== nums[nums[i]]) {
[nums[nums[i]], nums[i]] = [nums[i], nums[nums[i]]];
}
}
const missingNumbers = [];
for (let i = 0; i < n; i++) {
if (nums[i] !== i) {
missingNumbers.push(i);
}
}
return missingNumbers;
}
Time Complexity - O(N), where N is the number of elements in the nums
array.
Space Complexity - O(1).
def find_missing_numbers(nums):
n = len(nums)
for i in range(n):
while nums[i] < n and nums[i] != nums[nums[i]]:
nums[nums[i]], nums[i] = nums[i], nums[nums[i]]
missing_numbers = []
for i in range(n):
if nums[i] != i:
missing_numbers.append(i)
return missing_numbers
Time Complexity - O(N), where N is the number of elements in the nums
array.
Space Complexity - O(1).
<h1In-Place Reversal of Linked List:
- Initialize
prev
asnull
to keep track of the previous node. - Iterate through the linked list:
- Save the next node in
temp
. - Reverse the current node by updating its
next
toprev
. - Move
prev
to the current node. - Move to the next node using
temp
.
- Save the next node in
- Return
prev
, which will be the new head of the reversed linked list.
- Can the linked list be empty?
- Will the linked list always be singly linked?
function reverseLinkedList(head) {
let prev = null;
while (head) {
let temp = head.next;
head.next = prev;
prev = head;
head = temp;
}
return prev;
}
Time Complexity - O(N), where N is the number of nodes in the linked list. Space Complexity - O(1).
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reverse_linked_list(head):
prev = None
while head:
next_node = head.next
head.next = prev
prev = head
head = next_node
return prev
Time Complexity - O(N), where N is the number of nodes in the linked list. Space Complexity - O(1).
- Initialize
dummy
as a new node with value -1, pointing to the original head. - Initialize
prev
todummy
,current
tohead
, andreverse_tail
tocurrent
. - Iterate through the linked list until reaching position
m
:- Move
prev
tocurrent
. - Move
current
to the next node.
- Move
- Continue iterating and reverse the nodes from position
m
ton
:- Save the next node in
temp
. - Reverse the current node by updating its
next
toprev
. - Move
prev
to the current node. - Move
current
to the next node usingtemp
.
- Save the next node in
- Update the pointers to link the reversed section back into the original list.
- Return
dummy.next
, which will be the new head of the linked list.
- Can the linked list be empty?
- Will
m
andn
always be valid indices within the linked list?
function reverseLinkedListRange(head, m, n) {
let dummy = new ListNode(-1);
dummy.next = head;
let prev = dummy;
for (let i = 1; i < m; i++) {
prev = prev.next;
}
let current = prev.next;
let reverseTail = current;
let prevNext = null;
for (let i = m; i <= n; i++) {
let temp = current.next;
current.next = prevNext;
prevNext = current;
current = temp;
}
prev.next = prevNext;
reverseTail.next = current;
return dummy.next;
}
Time Complexity - O(N), where N is the number of nodes in the linked list. Space Complexity - O(1).
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def reverse_linked_list_range(head, m, n):
dummy = ListNode(-1)
dummy.next = head
prev = dummy
for _ in range(m - 1):
prev = prev.next
current = prev.next
reverse_tail = current
prev_next = None
for _ in range(n - m + 1):
temp = current.next
current.next = prev_next
prev_next = current
current = temp
prev.next = prev_next
reverse_tail.next = current
return dummy.next
Time Complexity - O(N), where N is the number of nodes in the linked list. Space Complexity - O(1).
- Initialize an empty list,
result
, to store the level order traversal. - Initialize a queue,
queue
, with the root node. - While the queue is not empty:
- Initialize an empty list,
currentLevel
, to store the nodes at the current level. - Get the size of the queue (
size
), which represents the number of nodes at the current level. - Iterate
size
times to process all nodes at the current level:- Dequeue the front node from the queue.
- Add the node's value to
currentLevel
. - Enqueue the left and right children of the node if they exist.
- Add
currentLevel
to theresult
.
- Initialize an empty list,
- Return the
result
.
- Can the tree have duplicate values?
- Can the tree be empty?
class TreeNode {
constructor(val = 0, left = null, right = null) {
this.val = val;
this.left = left;
this.right = right;
}
}
function levelOrderTraversal(root) {
if (!root) {
return [];
}
let result = [];
let queue = [root];
while (queue.length) {
let currentLevel = [];
let size = queue.length;
for (let i = 0; i < size; i++) {
let node = queue.shift();
currentLevel.push(node.val);
if (node.left) {
queue.push(node.left);
}
if (node.right) {
queue.push(node.right);
}
}
result.push(currentLevel);
}
return result;
}
Time Complexity - O(N), where N is the number of nodes in the binary tree. Space Complexity - O(N) in the worst case when the tree is balanced and each level has N/2 nodes in the queue.
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def level_order_traversal(root):
if not root:
return []
result = []
queue = [root]
while queue:
current_level = []
size = len(queue)
for _ in range(size):
node = queue.pop(0)
current_level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(current_level)
return result
Time Complexity - O(N), where N is the number of nodes in the binary tree. Space Complexity - O(N) in the worst case when the tree is balanced and each level has N/2 nodes in the queue.
- Initialize an empty list,
result
, to store the right side view nodes. - Initialize a queue,
queue
, with the root node. - While the queue is not empty:
- Initialize
currentLevel
to an empty list. - Get the size of the queue (
size
), which represents the number of nodes at the current level. - Iterate
size
times to process all nodes at the current level:- Dequeue the front node from the queue.
- If it is the last node at the current level, add its value to
currentLevel
. - Enqueue the left and right children of the node if they exist.
- Add the last node's value from
currentLevel
to theresult
.
- Initialize
- Return the
result
.
- Can the tree have duplicate values?
- Can the tree be empty?
class TreeNode {
constructor(val = 0, left = null, right = null) {
this.val = val;
this.left = left;
this.right = right;
}
}
function rightSideView(root) {
if (!root) {
return [];
}
let result = [];
let queue = [root];
while (queue.length) {
let currentLevel = [];
let size = queue.length;
for (let i = 0; i < size; i++) {
let node = queue.shift();
if (i === size - 1) {
currentLevel.push(node.val);
}
if (node.left) {
queue.push(node.left);
}
if (node.right) {
queue.push(node.right);
}
}
result.push(currentLevel[currentLevel.length - 1]);
}
return result;
}
Time Complexity - O(N), where N is the number of nodes in the binary tree. Space Complexity - O(N) in the worst case when the tree is a complete binary tree and the queue holds all nodes of the last level.
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def right_side_view(root):
if not root:
return []
result = []
queue = [root]
while queue:
current_level = []
size = len(queue)
for i in range(size):
node = queue.pop(0)
if i == size - 1:
current_level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(current_level[-1])
return result
Time Complexity - O(N), where N is the number of nodes in the binary tree. Space Complexity - O(N) in the worst case when the tree is a complete binary tree and the queue holds all nodes of the last level.
- Initialize an empty list,
result
, to store the inorder traversal. - Define a recursive helper function,
inorderTraversalRecursive
, that takes the current node as an argument:- If the current node is not
null
, recursively call the function on the left child. - Add the current node's value to
result
. - If the current node is not
null
, recursively call the function on the right child.
- If the current node is not
- Call the
inorderTraversalRecursive
function with the root node. - Return the
result
.
- Can the tree have duplicate values?
- Can the tree be empty?
class TreeNode {
constructor(val = 0, left = null, right = null) {
this.val = val;
this.left = left;
this.right = right;
}
}
function inorderTraversal(root) {
let result = [];
function inorderTraversalRecursive(node) {
if (!node) {
return;
}
inorderTraversalRecursive(node.left);
result.push(node.val);
inorderTraversalRecursive(node.right);
}
inorderTraversalRecursive(root);
return result;
}
Time Complexity - O(N), where N is the number of nodes in the binary tree. Space Complexity - O(N) for the recursion stack.
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def inorder_traversal(root):
result = []
def inorder_traversal_recursive(node):
if not node:
return
inorder_traversal_recursive(node.left)
result.append(node.val)
inorder_traversal_recursive(node.right)
inorder_traversal_recursive(root)
return result
Time Complexity - O(N), where N is the number of nodes in the binary tree. Space Complexity - O(N) for the recursion stack.
- Define a recursive helper function,
maxPathSumRecursive
, that takes the current node as an argument:- If the current node is
null
, return 0. - Recursively find the maximum path sum for the left and right subtrees using
maxPathSumRecursive
. - Calculate the local maximum path sum for the current node as the maximum value between:
- The current node's value.
- The sum of the current node's value and the maximum path sum from the left subtree (if positive).
- The sum of the current node's value and the maximum path sum from the right subtree (if positive).
- Update the
maxSum
as the maximum value between the current maximum path sum and the local maximum path sum. - Return the local maximum path sum.
- If the current node is
- Initialize
maxSum
to a minimum integer value. - Call the
maxPathSumRecursive
function with the root node. - Return
maxSum
.
- Can the tree have negative values?
- Can the tree have duplicate values?
- Can the tree be empty?
class TreeNode {
constructor(val = 0, left = null, right = null) {
this.val = val;
this.left = left;
this.right = right;
}
}
function maxPathSum(root) {
let maxSum = Number.MIN_SAFE_INTEGER;
function maxPathSumRecursive(node) {
if (!node) {
return 0;
}
let leftSum = Math.max(0, maxPathSumRecursive(node.left));
let rightSum = Math.max(0, maxPathSumRecursive(node.right));
let localSum = node.val + leftSum + rightSum;
maxSum = Math.max(maxSum, localSum);
return node.val + Math.max(leftSum, rightSum);
}
maxPathSumRecursive(root);
return maxSum;
}
Time Complexity - O(N), where N is the number of nodes in the binary tree. Space Complexity - O(H), where H is the height of the binary tree for the recursion stack.
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def max_path_sum(root):
max_sum = float("-inf")
def max_path_sum_recursive(node):
nonlocal max_sum
if not node:
return 0
left_sum = max(0, max_path_sum_recursive(node.left))
right_sum = max(0, max_path_sum_recursive(node.right))
local_sum = node.val + left_sum + right_sum
max_sum = max(max_sum, local_sum)
return node.val + max(left_sum, right_sum)
max_path_sum_recursive(root)
return max_sum
Time Complexity - O(N), where N is the number of nodes in the binary tree. Space Complexity - O(H), where H is the height of the binary tree for the recursion stack.
- Initialize a min heap,
minHeap
, to store the larger half of the numbers.
#Pseudocode:
- Initialize an empty list,
result
, to store the subsets. - Define a recursive helper function,
backtrack
, that takes the current index and the current subset as arguments. - Add the current subset to the
result
. - Iterate from the current index to the end of the
nums
array:- Include the current element in the subset.
- Recursively call
backtrack
with the next index and the updated subset. - Exclude the current element from the subset.
- Call the
backtrack
function with index 0 and an empty subset. - Return the
result
.
#Questions to Ask:
- Can the
nums
array have duplicate elements? - Can the
nums
array be empty?
#JavaScript Solution:
function subsets(nums) {
let result = [];
function backtrack(index, currentSubset) {
result.push([...currentSubset]);
for (let i = index; i < nums.length; i++) {
currentSubset.push(nums[i]);
backtrack(i + 1, currentSubset);
currentSubset.pop();
}
}
backtrack(0, []);
return result;
}
#Python Solution:
def subsets(nums):
result = []
def backtrack(index, current_subset):
result.append(current_subset[])
for i in range(index, len(nums)):
current_subset.append(nums[i])
backtrack(i + 1, current_subset)
current_subset.pop()
backtrack(0, [])
return result
Time Complexity - O(2^N), where N is the number of elements in the nums
array, as each element can either be included or excluded in the subsets.
Space Complexity: O(2^N) for the result
list.
#Pseudocode:
- Initialize an empty list,
result
, to store the subsets. - Define a recursive helper function,
backtrack
, that takes the current index and the current subset as arguments. - Add the current subset to the
result
. - Iterate from the current index to the end of the
nums
array:- If the current element is the same as the previous element and not the first element:
- Skip to the next element to avoid duplicate subsets.
- Include the current element in the subset.
- Recursively call
backtrack
with the next index and the updated subset. - Exclude the current element from the subset.
- If the current element is the same as the previous element and not the first element:
- Call the
backtrack
function with index 0 and an empty subset. - Return the
result
.
#Questions to Ask:
- Can the
nums
array have duplicate elements? - Can the
nums
array be empty?
#JavaScript Solution:
function subsetsWithDup(nums) {
let result = [];
function backtrack(index, currentSubset) {
result.push([...currentSubset]);
for (let i = index; i < nums.length; i++) {
if (i > index && nums[i] === nums[i - 1]) {
continue;
}
currentSubset.push(nums[i]);
backtrack(i + 1, currentSubset);
currentSubset.pop();
}
}
nums.sort((a, b) => a - b);
backtrack(0, []);
return result;
}
#Python Solution:
def subsetsWithDup(nums):
result = []
def backtrack(index, current_subset):
result.append(current_subset[])
for i in range(index, len(nums)):
if i > index and nums[i] == nums[i - 1]:
continue
current_subset.append(nums[i])
backtrack(i + 1, current_subset)
current_subset.pop()
nums.sort()
backtrack(0, [])
return result
Time Complexity - O(2^N), where N is the number of elements in the nums
array, as each element can either be included or excluded in the subsets.
Space Complexity: O(2^N) for the result
list.
#Pseudocode:
- Initialize
left
to 0 andright
to the last index of the array. - While
left
is less thanright
:- Calculate the middle index as
(left + right) / 2
. - If the middle element is greater than the last element (rightmost element):
- Update
left
tomid + 1
.
- Update
- Otherwise, update
right
tomid
.
- Calculate the middle index as
- Return the element at index
left
as the minimum element.
#JavaScript Solution:
function findMin(nums) {
let left = 0;
let right = nums.length - 1;
while (left < right) {
let mid = Math.floor((left + right) / 2);
if (nums[mid] > nums[right]) {
left = mid + 1;
} else {
right = mid;
}
}
return nums[left];
}
#Python Solution:
def find_min(nums):
left = 0
right = len(nums) - 1
while left < right:
mid = (left + right) // 2
if nums[mid] > nums[right]:
left = mid + 1
else:
right = mid
return nums[left]
Time Complexity - O(log N), where N is the number of elements in the array. Space Complexity: O(1).
#Pseudocode:
- Initialize
left
to 0 andright
to the last index of the array. - While
left
is less than or equal toright
:- Calculate the middle index as
(left + right) / 2
. - If the middle element is equal to the target, return its index.
- If the left half (from
left
tomid
) is sorted:- If the target is within the left half, update
right
tomid - 1
. - Otherwise, update
left
tomid + 1
.
- If the target is within the left half, update
- If the right half (from
mid
toright
) is sorted:- If the target is within the right half, update
left
tomid + 1
. - Otherwise, update
right
tomid - 1
.
- If the target is within the right half, update
- Calculate the middle index as
- Return -1, indicating that the target element is not found.
#JavaScript Solution:
function search(nums, target) {
let left = 0;
let right = nums.length - 1;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (nums[mid] === target) {
return mid;
}
if (nums[left] <= nums[mid])
#JavaScript Solution (Continued):
if (nums[left] <= nums[mid]) {
if (target >= nums[left] && target < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
} else {
if (target > nums[mid] && target <= nums[right]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;
}
#Python Solution:
def search(nums, target):
left = 0
right = len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
if nums[left] <= nums[mid]:
if target >= nums[left] and target < nums[mid]:
right = mid - 1
else:
left = mid + 1
else:
if target > nums[mid] and target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1
Time Complexity - O(log N), where N is the number of elements in the array. Space Complexity: O(1).
#Pseudocode:
- Sort the input array in descending order.
- Return the element at the (k - 1) index, which represents the kth largest element.
#JavaScript Solution:
function findKthLargest(nums, k) {
nums.sort((a, b) => b - a);
return nums[k - 1];
}
#Python Solution:
def find_kth_largest(nums, k):
nums.sort(reverse=True)
return nums[k - 1]
Time Complexity - O(N log N), where N is the number of elements in the array due to the sorting step. Space Complexity: O(1) (in-place sorting).
#Pseudocode:
- Create a frequency map to store the count of each element in the input array.
- Create a list of tuples where each tuple contains the element and its corresponding frequency.
- Sort the list of tuples based on the frequency in descending order.
- Return the first k elements from the sorted list.
#JavaScript Solution:
function topKFrequent(nums, k) {
let frequencyMap = new Map();
for (let num of nums) {
frequencyMap.set(num, (frequencyMap.get(num) || 0) + 1);
}
let sortedEntries = [...frequencyMap.entries()].sort((a, b) => b[1] - a[1]);
let result = [];
for (let i = 0; i < k; i++) {
result.push(sortedEntries[i][0]);
}
return result;
}
#Python Solution:
def top_k_frequent(nums, k):
frequency_map = {}
for num in nums:
frequency_map[num] = frequency_map.get(num, 0) + 1
sorted_entries = sorted(frequency_map.items(), key=lambda x: x[1], reverse=True)
return [entry[0] for entry in sorted_entries[:k]]
Time Complexity - O(N log N) for sorting, where N is the number of elements in the array. Space Complexity: O(N) for the frequency map.
#Pseudocode:
- Create a min heap and insert the first node from each list into the heap.
- Initialize a dummy head node and a current pointer.
- While the heap is not empty:
- Pop the smallest node from the heap.
- Append the popped node to the current pointer.
- Move the current pointer to the next node in the merged list.
- If the popped node has a next node, insert the next node into the heap.
- Return the next node of the dummy head, which represents the head of the merged list.
#JavaScript Solution:
class ListNode {
constructor(val = 0, next = null) {
this.val = val;
this.next = next;
}
}
function mergeKLists(lists) {
let heap = new MinHeap();
for (let list of lists) {
if (list) {
heap.insert(list);
}
}
let dummyHead = new ListNode();
let current = dummyHead;
while (!heap.isEmpty()) {
let node = heap.extractMin();
current.next = node;
current = current.next;
if (node.next) {
heap.insert(node.next);
}
}
return dummyHead.next;
}
class MinHeap {
constructor() {
this.heap = [];
}
insert(node) {
this.heap.push(node);
this.bubbleUp(this.heap.length - 1);
}
bubbleUp(index) {
while (index > 0) {
let parentIndex = Math.floor((index - 1) / 2);
if (this.heap[parentIndex].val <= this.heap[index].val) {
break;
}
[this.heap[parentIndex], this.heap[index]] = [this.heap[index], this.heap[parentIndex]];
index = parentIndex;
}
}
extractMin() {
if (this.isEmpty()) {
return null;
}
let minNode = this.heap[0];
let lastNode = this.heap.pop();
if (!this.isEmpty()) {
this.heap[0] = lastNode;
this.bubbleDown(0);
}
return minNode;
}
bubbleDown(index) {
let left = 2 * index + 1;
let right = 2 * index + 2;
let smallest = index;
if (left < this.heap.length && this.heap[left].val < this.heap[smallest].val) {
smallest = left;
}
if (right < this.heap.length && this.heap[right].val < this.heap[smallest].val) {
smallest = right;
}
if (smallest !== index) {
[this.heap[index], this.heap[smallest]] = [this.heap[smallest], this.heap[index]];
this.bubbleDown(smallest);
}
}
isEmpty() {
return this.heap.length === 0;
}
}
#Python Solution:
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def merge_k_lists(lists):
import heapq
heap = []
for list_node in lists:
if list_node:
heapq.heappush(heap, (list_node.val, list_node))
dummy_head = ListNode()
current = dummy_head
while heap:
val, node = heapq.heappop(heap)
current.next = node
current = current.next
if node.next:
heapq.heappush(heap, (node.next.val, node.next))
return dummy_head.next
#JavaScript Solution Explanation:
- We define a
ListNode
class to represent a node in the linked list. - The
mergeKLists
function takes a list of linked listslists
as input and returns the head of the merged sorted list. - We create a
MinHeap
class to implement a min heap data structure, where the smallest node is always at the top. - In the
mergeKLists
function, we create an instance of theMinHeap
class. - We insert the first node from each linked list into the heap.
- We create a dummy head node and a
current
pointer to keep track of the merged list. - While the heap is not empty:
- We extract the smallest node from the heap (the root of the heap).
- We append the extracted node to the
current
pointer. - If the extracted node has a next node, we insert the next node into the heap.
- We move the
current
pointer to the next node in the merged list.
- Finally, we return the head of the merged list (the next node of the dummy head).
#Python Solution Explanation:
- We import the
heapq
module to use a priority queue as a min heap. - The
merge_k_lists
function takes a list of linked listslists
as input and returns the head of the merged sorted list. - We create an empty
heap
to store tuples of(val, node)
whereval
is the value of the node andnode
is the node itself. - We push the first node from each linked list into the heap with their values as priorities.
- We create a dummy head node and a
current
pointer to keep track of the merged list. - While the heap is not empty:
- We extract the tuple with the smallest value (the root of the heap).
- We assign the node from the tuple to the
current.next
. - If the extracted node has a next node, we push the next node into the heap with its value as priority.
- We move the
current
pointer to the next node in the merged list.
- Finally, we return the head of the merged list (the next node of the dummy head).
Time Complexity - O(N log k), where N is the total number of nodes across all lists and k is the number of lists. The heap operations (insertion and extraction) take O(log k) time, and we perform them N times. Space Complexity: O(k), where k is the number of lists. The heap can contain at most k nodes at any time.
Given a collection of intervals, merge overlapping intervals.
Input: intervals = [[1, 3], [2, 6], [8, 10], [15, 18]] Output: [[1, 6], [8, 10], [15, 18]]
#Pseudocode:
- Sort the intervals based on the start time in ascending order.
- Initialize an empty list
merged
to store the merged intervals. - Iterate through the sorted intervals:
- If
merged
is empty or the current interval's start is greater than the end of the last interval inmerged
, append the current interval tomerged
. - Otherwise, update the end time of the last interval in
merged
if the end time of the current interval is greater.
- If
- Return the
merged
list.
#Questions to Ask:
- Can the input intervals be empty?
- Can the input intervals contain negative values?
- Can the input intervals have duplicate intervals?
#JavaScript Solution:
function merge(intervals) {
if (!intervals || intervals.length === 0) {
return [];
}
intervals.sort((a, b) => a[0] - b[0]);
let merged = [intervals[0]];
for (let i = 1; i < intervals.length; i++) {
let currentInterval = intervals[i];
let lastMerged = merged[merged.length - 1];
if (currentInterval[0] > lastMerged[1]) {
merged.push(currentInterval);
} else {
lastMerged[1] = Math.max(lastMerged[1], currentInterval[1]);
}
}
return merged;
}
#Python Solution:
def merge(intervals):
if not intervals:
return []
intervals.sort(key=lambda x: x[0])
merged = [intervals[0]]
for interval in intervals[1]:
current_start, current_end = interval
last_start, last_end = merged[-1]
if current_start > last_end:
merged.append(interval)
else:
merged[-1] = [last_start, max(current_end, last_end)]
return merged
Time Complexity - O(N log N) for sorting, where N is the number of intervals. The merging process takes O(N) time as we iterate through the sorted intervals only once.
Space Complexity -O(N) for the merged
list.
- Initialize an empty list,
result
, to store the topological order of courses. - Initialize a dictionary,
inDegrees
, to keep track of the indegree (number of incoming edges) for each course. - Initialize a queue,
queue
, with courses that have an indegree of 0. - While the queue is not empty:
- Dequeue a course from the queue and add it to the
result
. - Iterate through the prerequisites for the dequeued course:
- Decrement the indegree of the prerequisite course.
- If the indegree becomes 0, enqueue the prerequisite course to the queue.
- Dequeue a course from the queue and add it to the
- Check if the number of courses in the
result
is equal to the total number of courses. If not, it means there is a cycle, and we cannot complete all courses. - Return the
result
.
- Can there be duplicate courses?
- Can the prerequisites list have duplicate entries?
function canFinish(numCourses, prerequisites) {
let result = [];
let inDegrees = {};
let graph = {};
for (let [course, prereq] of prerequisites) {
if (!graph[prereq]) graph[prereq] = [];
graph[prereq].push(course);
inDegrees[course] = inDegrees[course] ? inDegrees[course] + 1 : 1;
if (!inDegrees[prereq]) inDegrees[prereq] = 0;
}
let queue = [];
for (let course in inDegrees) {
if (inDegrees[course] === 0) queue.push(course);
}
while (queue.length) {
let currCourse = queue.shift();
result.push(currCourse);
if (graph[currCourse]) {
for (let nextCourse of graph[currCourse]) {
inDegrees[nextCourse]--;
if (inDegrees[nextCourse] === 0) queue.push(nextCourse);
}
}
}
return result.length === numCourses;
}
Time Complexity - O(N + E), where N is the number of courses and E is the number of prerequisites.
Space Complexity - O(N + E), for the inDegrees
and graph
dictionaries, and the queue
.
def can_finish(num_courses, prerequisites):
result = []
in_degrees = {}
graph = {}
for course, prereq in prerequisites:
if prereq not in graph:
graph[prereq] = []
graph[prereq].append(course)
in_degrees[course] = in_degrees.get(course, 0) + 1
in_degrees[prereq] = in_degrees.get(prereq, 0)
queue = []
for course in in_degrees:
if in_degrees[course] == 0:
queue.append(course)
while queue:
curr_course = queue.pop(0)
result.append(curr_course)
if curr_course in graph:
for next_course in graph[curr_course]:
in_degrees[next_course] -= 1
if in_degrees[next_course] == 0:
queue.append(next_course)
return len(result) == num_courses
Time Complexity - O(N + E), where N is the number of courses and E is the number of prerequisites.
Space Complexity - O(N + E), for the in_degrees
and graph
dictionaries, and the queue
.
- Read and understand the problem statement carefully. Make sure to identify the input, output, and constraints.
- Clarify any doubts by asking questions about the problem.
- Identify the appropriate algorithm or data structure that can be used to solve the problem efficiently.
- Break down the problem into smaller sub-problems and try to solve each sub-problem step by step.
- Write the# pseudocode for the solution to the sub-problems and then combine them to form the final solution.
- Test the# pseudocode with different test cases to verify its correctness.
- Once the# pseudocode is correct, implement the solution in the desired programming language (JavaScript or Python).
- Test the code thoroughly with various test cases to ensure it works as expected.
- Analyze the time and space complexity of the solution to assess its efficiency.
- Optimize the solution if possible by reducing the time or space complexity or improving the code readability.
- Document the code properly with comments to explain the logic and any important steps.
- Submit the solution and discuss the approach and code with others to gain insights and learn from different perspectives.
I hope this article was helpful in understanding the process of solving coding problems. I have tried to explain the approach in detail with examples and# pseudocode. I have also provided the JavaScript and# Python solutions for each problem. I would recommend you to try solving the problems on your own before looking at the solutions. If you have any questions or feedback, please feel free to leave a comment below. Thank you for reading!