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
maxSumto a negative value to handle negative elements. - Initialize
windowSumto 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
maxSumto be the maximum ofmaxSumandwindowSum. - 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_sumTime 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
maxLengthto 0 to keep track of the maximum length of the substring. - Initialize
startto 0 to mark the start index of the current substring. - Create an empty
seendictionary to store the most recent index of each character. - Iterate through the string using the variable
end:- If the current character is in
seenand its index is greater than or equal tostart:- Update
startto be the next index of the current character.
- Update
- Update the
maxLengthto be the maximum of the currentmaxLengthandend - start + 1. - Store the current index of the character in the
seendictionary.
- 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_lengthTime 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,
numand their indices,i:- Calculate the
complement(target - num). - If the
complementis 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 NoneTime 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,
leftat the beginning andrightat the end of the array. - Initialize
maxAreato store the maximum area found so far. - Iterate until
leftis less thanright:- Calculate the
widthasright - left. - Calculate the
heightas the minimum of the elements at indicesleftandright. - Calculate the
areaaswidth * height. - Update
maxAreato the maximum ofmaxAreaandarea. - 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 maxAreaTime 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
slowandfastpointers, both starting at the head of the linked list. - Iterate the pointers as long as
fastandfast.nextare notnull:- Move
slowone step forward. - Move
fasttwo steps forward. - If
slowandfastmeet 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 FalseTime 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
slowandfastpointers, both starting at the head of the linked list. - Iterate the pointers as long as
fastandfast.nextare notnull:- Move
slowone step forward. - Move
fasttwo steps forward. - When
fastreaches the end of the linked list,slowwill 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.valTime 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
mergedlist.
- 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 mergedTime 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
mergedand 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
mergedlist.
- 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 mergedTime 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 nTime 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_numbersTime 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
prevasnullto keep track of the previous node. - Iterate through the linked list:
- Save the next node in
temp. - Reverse the current node by updating its
nexttoprev. - Move
prevto 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 prevTime Complexity - O(N), where N is the number of nodes in the linked list. Space Complexity - O(1).
- Initialize
dummyas a new node with value -1, pointing to the original head. - Initialize
prevtodummy,currenttohead, andreverse_tailtocurrent. - Iterate through the linked list until reaching position
m:- Move
prevtocurrent. - Move
currentto the next node.
- Move
- Continue iterating and reverse the nodes from position
mton:- Save the next node in
temp. - Reverse the current node by updating its
nexttoprev. - Move
prevto the current node. - Move
currentto 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
mandnalways 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.nextTime 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
sizetimes 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
currentLevelto 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 resultTime 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
currentLevelto an empty list. - Get the size of the queue (
size), which represents the number of nodes at the current level. - Iterate
sizetimes 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
currentLevelto 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 resultTime 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
inorderTraversalRecursivefunction 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 resultTime 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
maxSumas 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
maxSumto a minimum integer value. - Call the
maxPathSumRecursivefunction 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_sumTime 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
numsarray:- Include the current element in the subset.
- Recursively call
backtrackwith the next index and the updated subset. - Exclude the current element from the subset.
- Call the
backtrackfunction with index 0 and an empty subset. - Return the
result.
#Questions to Ask:
- Can the
numsarray have duplicate elements? - Can the
numsarray 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 resultTime 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
numsarray:- 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
backtrackwith 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
backtrackfunction with index 0 and an empty subset. - Return the
result.
#Questions to Ask:
- Can the
numsarray have duplicate elements? - Can the
numsarray 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 resultTime 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
leftto 0 andrightto the last index of the array. - While
leftis less thanright:- Calculate the middle index as
(left + right) / 2. - If the middle element is greater than the last element (rightmost element):
- Update
lefttomid + 1.
- Update
- Otherwise, update
righttomid.
- Calculate the middle index as
- Return the element at index
leftas 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
leftto 0 andrightto the last index of the array. - While
leftis 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
lefttomid) is sorted:- If the target is within the left half, update
righttomid - 1. - Otherwise, update
lefttomid + 1.
- If the target is within the left half, update
- If the right half (from
midtoright) is sorted:- If the target is within the right half, update
lefttomid + 1. - Otherwise, update
righttomid - 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 -1Time 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
ListNodeclass to represent a node in the linked list. - The
mergeKListsfunction takes a list of linked listslistsas input and returns the head of the merged sorted list. - We create a
MinHeapclass to implement a min heap data structure, where the smallest node is always at the top. - In the
mergeKListsfunction, we create an instance of theMinHeapclass. - We insert the first node from each linked list into the heap.
- We create a dummy head node and a
currentpointer 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
currentpointer. - If the extracted node has a next node, we insert the next node into the heap.
- We move the
currentpointer 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
heapqmodule to use a priority queue as a min heap. - The
merge_k_listsfunction takes a list of linked listslistsas input and returns the head of the merged sorted list. - We create an empty
heapto store tuples of(val, node)wherevalis the value of the node andnodeis 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
currentpointer 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
currentpointer 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
mergedto store the merged intervals. - Iterate through the sorted intervals:
- If
mergedis 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
mergedif the end time of the current interval is greater.
- If
- Return the
mergedlist.
#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 mergedTime 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
resultis 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_coursesTime 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!