Data Structure using Python
Interview Questions with Answers

Explore Our Courses
1. What are the different data types in Python and how are they used in DS problems?
Answer:
Python has several built-in data types, commonly used in data structure problems:
Advanced types include collections.deque, defaultdict, Counter, OrderedDict for optimized DS operations.
Ace your Python DS interviews with confidence—practice the most asked questions today!
2. How is memory managed in Python during list and dictionary operations?
Answer:
Python uses automatic memory management via the Python Memory Manager, which includes:
- Private heap space: All objects and data structures are stored here.
- Dynamic resizing:
- Lists grow using over-allocation (extra space reserved).
- Dicts grow using rehashing and resizing as elements increase.
List Memory:
- Internally uses a resizable array.
- Amortized O(1) time for append due to geometric resizing.
Dictionary Memory:
- Uses a hash table.
- Keys are hashed, and collisions are resolved using open addressing.
Python automatically manages memory via:
- Reference counting
- Garbage collector for cyclic references
3. What is the difference between list, tuple, and set in Python?
Answer:
4. Explain the concept of mutability with respect to Python data structures.
Answer:
Mutability refers to whether the object’s content can be changed after creation.
Examples:
lst = [1, 2]; lst[0] = 5 # Mutable
tup = (1, 2); tup[0] = 5 # Error (immutable)
In DS problems:
- Use immutable types for hashable keys in sets/dicts.
- Use mutable types for dynamic updates.
5. How does Python manage references and garbage collection?
Answer:
Python uses reference counting and a cyclic garbage collector.
- Reference Counting: Each object keeps track of how many references point to it.
- When count = 0 → memory is deallocated.
- Garbage Collector (gc module):
- Detects and collects cyclic references (e.g., mutually referring lists).
- Uses generational GC:
- Objects are grouped in generations (0, 1, 2) based on age.
- Younger objects are collected more frequently.
Example:
import gc
gc.collect() # Manually trigger garbage collection
6. What are Python iterators and generators? How do they support DS operations?
Answer:
Iterator: Object with __iter__() and __next__() methods.
lst = [1, 2, 3]
it = iter(lst)
print(next(it)) # Output: 1
Generator: A special iterator using yield instead of return.
Benefits:
- Memory-efficient (lazy evaluation)
- Useful for large data streams (e.g., infinite series, tree traversal)
Example:
def countdown(n):
while n > 0:
yield n
n -= 1
Used in:
- Tree traversals
- Streaming data
- Efficient backtracking
7. What is the difference between is and == in Python?
Answer:
Use is
- To compare identity (e.g., is None)
- When checking for singleton objects
Use ==
- When comparing values/contents
8. Explain how slicing works in Python and give DS use cases.
Answer:
Slicing Syntax: sequence[start:stop:step]
Examples:
lst = [10, 20, 30, 40, 50]
lst[1:4] # [20, 30, 40]
lst[::-1] # [50, 40, 30, 20, 10] → reverse
Use Cases in DS:
- Reversing strings/lists
- Copying arrays (arr[:])
- Extracting subarrays or substrings
- Sliding window technique (arr[i:i+k])
9. How do you implement custom sorting using key and lambda in Python?
Answer:
Python’s sorted() and list.sort() accept a key argument for custom logic.
Examples:
# Sort by length
words = ['banana', 'apple', 'kiwi']
sorted_words = sorted(words, key=len)
# Sort by second element of tuple
pairs = [(1, 3), (2, 1), (5, 2)]
pairs.sort(key=lambda x: x[1])
Real-world DS Use:
- Sorting by frequency
- Custom comparators (e.g., sort by absolute value, sort strings by custom order)
10. Explain time complexity of built-in Python operations (append, pop, insert, sort).
Answer:
Note: Worst-case dict/set operations may degrade to O(n) due to hash collisions, but are rare with good hash functions.
11. Implement two-pointer approach to reverse a string in Python
Answer:
def reverse_string(s):
s = list(s) # Convert to list for in-place modification
left, right = 0, len(s) - 1
while left < right:
s[left], s[right] = s[right], s[left]
left += 1
right -= 1
return ''.join(s)
# Test
print(reverse_string("python")) # Output: "nohtyp"
12. How do you remove duplicates from a list while preserving order?
Answer:
def remove_duplicates(lst):
seen = set()
result = []
for item in lst:
if item not in seen:
seen.add(item)
result.append(item)
return result
# Test
print(remove_duplicates([1, 2, 2, 3, 1, 4])) # Output: [1, 2, 3, 4]
13. Given a rotated sorted array, write a function to search a target element
Answer:
def search_rotated_array(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
# Left half is sorted
if nums[left] <= nums[mid]:
if nums[left] <= target < nums[mid]:
right = mid - 1
else:
left = mid + 1
# Right half is sorted
else:
if nums[mid] < target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1 # Not found
# Test
print(search_rotated_array([4, 5, 6, 7, 0, 1, 2], 0)) # Output: 4
14. Implement Kadane’s algorithm to find the maximum subarray sum
Answer:
def max_subarray_sum(nums):
max_sum = current_sum = nums[0]
for num in nums[1:]:
current_sum = max(num, current_sum + num)
max_sum = max(max_sum, current_sum)
return max_sum
# Test
print(max_subarray_sum([-2,1,-3,4,-1,2,1,-5,4])) # Output: 6
15. Check if two strings are anagrams using Python dictionaries
Answer:
def are_anagrams(s1, s2):
if len(s1) != len(s2):
return False
count = {}
for c in s1:
count[c] = count.get(c, 0) + 1
for c in s2:
if c not in count:
return False
count[c] -= 1
if count[c] < 0:
return False
return True
# Test
print(are_anagrams("listen", "silent")) # Output: True
16. How would you implement run-length encoding of a string?
Answer:
def run_length_encode(s):
if not s:
return ""
result = []
count = 1
prev = s[0]
for c in s[1:]:
if c == prev:
count += 1
else:
result.append(prev + str(count))
prev = c
count = 1
result.append(prev + str(count))
return ''.join(result)
# Test
print(run_length_encode("aaabbc")) # Output: "a3b2c1"
17. Implement string compression using counts of repeated characters
Answer:
def compress_string(s):
if not s:
return ""
compressed = []
count = 1
prev = s[0]
for c in s[1:]:
if c == prev:
count += 1
else:
compressed.append(prev + str(count))
prev = c
count = 1
compressed.append(prev + str(count))
result = ''.join(compressed)
# Return original if compression doesn't reduce size
return result if len(result) < len(s) else s
# Test
print(compress_string("aabcccccaaa")) # Output: "a2b1c5a3"
18. Given a list of numbers, return indices of two numbers that add to a target (Two Sum)
Answer:
def two_sum(nums, target):
index_map = {} # num → index
for i, num in enumerate(nums):
complement = target - num
if complement in index_map:
return [index_map[complement], i]
index_map[num] = i
return [] # No valid pair found
# Test
print(two_sum([2, 7, 11, 15], 9)) # Output: [0, 1]
19. Explain sliding window technique with an example problem
Answer:
Sliding Window is used to optimize problems with contiguous subarrays.
Example Problem: Find the maximum sum of any subarray of size k.
def max_sum_subarray_k(nums, k):
window_sum = sum(nums[:k])
max_sum = window_sum
for i in range(k, len(nums)):
window_sum += nums[i] - nums[i - k]
max_sum = max(max_sum, window_sum)
return max_sum
# Test
print(max_sum_subarray_k([1, 4, 2, 10, 2, 3, 1, 0, 20], 4)) # Output: 24
20. Implement group anagrams problem using Python
Answer:
from collections import defaultdict
def group_anagrams(strs):
anagrams = defaultdict(list)
for word in strs:
# Sort characters to form a key
key = ''.join(sorted(word))
anagrams[key].append(word)
return list(anagrams.values())
# Test
print(group_anagrams(["eat", "tea", "tan", "ate", "nat", "bat"]))
# Output: [['eat', 'tea', 'ate'], ['tan', 'nat'], ['bat']]
Get closer to your dream job—master Data Structures in Python with real interview questions.
21. Implement a Singly Linked List in Python
Answer:
class Node:
def __init__(self, data):
self.data = data
self.next = None
class SinglyLinkedList:
def __init__(self):
self.head = None
def insert_end(self, data):
new_node = Node(data)
if not self.head:
self.head = new_node
return
curr = self.head
while curr.next:
curr = curr.next
curr.next = new_node
def display(self):
curr = self.head
while curr:
print(curr.data, end=" -> ")
curr = curr.next
print("None")
22. Reverse a Linked List (Iterative and Recursive)
Answer:
Iterative Approach
def reverse_iterative(head):
prev = None
curr = head
while curr:
next_node = curr.next
curr.next = prev
prev = curr
curr = next_node
return prev
Recursive Approach
def reverse_recursive(head):
if not head or not head.next:
return head
new_head = reverse_recursive(head.next)
head.next.next = head
head.next = None
return new_head
23. Detect a Cycle in a Linked List (Floyd’s Algorithm)
Answer:
def has_cycle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
if slow == fast:
return True
return False
24. Find the Middle of a Linked List
Answer:
def find_middle(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow # Middle node
25. Merge Two Sorted Linked Lists
Answer:
def merge_sorted_lists(l1, l2):
dummy = Node(0)
tail = dummy
while l1 and l2:
if l1.data <= l2.data:
tail.next = l1
l1 = l1.next
else:
tail.next = l2
l2 = l2.next
tail = tail.next
tail.next = l1 or l2
return dummy.next
26. Remove the Nth Node from the End of a Linked List
Answer:
def remove_nth_from_end(head, n):
dummy = Node(0)
dummy.next = head
fast = slow = dummy
for _ in range(n):
fast = fast.next
while fast.next:
fast = fast.next
slow = slow.next
slow.next = slow.next.next
return dummy.next
27. Check if a Linked List is a Palindrome
Answer:
def is_palindrome(head):
if not head or not head.next:
return True
# Find middle
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# Reverse second half
prev = None
while slow:
next_node = slow.next
slow.next = prev
prev = slow
slow = next_node
# Compare halves
left, right = head, prev
while right:
if left.data != right.data:
return False
left = left.next
right = right.next
return True
28. Add Two Numbers Represented by Linked Lists
Answer:
def add_two_numbers(l1, l2):
dummy = Node(0)
curr = dummy
carry = 0
while l1 or l2 or carry:
v1 = l1.data if l1 else 0
v2 = l2.data if l2 else 0
total = v1 + v2 + carry
carry = total // 10
curr.next = Node(total % 10)
curr = curr.next
if l1: l1 = l1.next
if l2: l2 = l2.next
return dummy.next
29. Flatten a Multilevel Linked List
Answer:
Assume each node has next and child pointers (like a doubly linked list with children).
class MultiLevelNode:
def __init__(self, data):
self.data = data
self.next = None
self.child = None
def flatten(head):
if not head:
return head
dummy = MultiLevelNode(0)
stack = [head]
prev = dummy
while stack:
curr = stack.pop()
prev.next = curr
prev = curr
if curr.next:
stack.append(curr.next)
if curr.child:
stack.append(curr.child)
curr.child = None
return dummy.next
30. Sort a Linked List Using Merge Sort
Answer:
def merge_sort(head):
if not head or not head.next:
return head
# Split the list into two halves
slow, fast = head, head.next
while fast and fast.next:
slow = slow.next
fast = fast.next.next
mid = slow.next
slow.next = None
left = merge_sort(head)
right = merge_sort(mid)
return merge_sorted_lists(left, right)
Note: merge_sorted_lists() is already defined in question 25.
31. Implement a Stack Using list and collections.deque
Answer:
Using list:
class StackList:
def __init__(self):
self.stack = []
def push(self, x):
self.stack.append(x) # O(1)
def pop(self):
return self.stack.pop() if self.stack else None # O(1)
def peek(self):
return self.stack[-1] if self.stack else None
def is_empty(self):
return not self.stack
Using collections.deque:
from collections import deque
class StackDeque:
def __init__(self):
self.stack = deque()
def push(self, x):
self.stack.append(x)
def pop(self):
return self.stack.pop() if self.stack else None
def peek(self):
return self.stack[-1] if self.stack else None
def is_empty(self):
return not self.stack
32. Evaluate a Postfix Expression Using Stack
Answer:
def evaluate_postfix(expression):
stack = []
for token in expression.split():
if token.isdigit():
stack.append(int(token))
else:
b = stack.pop()
a = stack.pop()
if token == '+': stack.append(a + b)
elif token == '-': stack.append(a - b)
elif token == '*': stack.append(a * b)
elif token == '/': stack.append(int(a / b)) # Truncate towards 0
return stack[0]
# Test
print(evaluate_postfix("2 3 1 * + 9 -")) # Output: -4
33. Implement a Queue Using Two Stacks
Answer:
class QueueTwoStacks:
def __init__(self):
self.in_stack = []
self.out_stack = []
def enqueue(self, x):
self.in_stack.append(x)
def dequeue(self):
if not self.out_stack:
while self.in_stack:
self.out_stack.append(self.in_stack.pop())
return self.out_stack.pop() if self.out_stack else None
def peek(self):
if not self.out_stack:
while self.in_stack:
self.out_stack.append(self.in_stack.pop())
return self.out_stack[-1] if self.out_stack else None
def is_empty(self):
return not self.in_stack and not self.out_stack
34. Design a Min-Stack (Retrieve Min in O(1))
Answer:
class MinStack:
def __init__(self):
self.stack = []
self.min_stack = []
def push(self, val):
self.stack.append(val)
if not self.min_stack or val <= self.min_stack[-1]:
self.min_stack.append(val)
def pop(self):
if self.stack:
val = self.stack.pop()
if val == self.min_stack[-1]:
self.min_stack.pop()
def top(self):
return self.stack[-1] if self.stack else None
def get_min(self):
return self.min_stack[-1] if self.min_stack else None
35. Check for Balanced Parentheses Using Stack
Answer:
def is_balanced(expr):
stack = []
pairs = {')': '(', '}': '{', ']': '['}
for char in expr:
if char in '([{':
stack.append(char)
elif char in ')]}':
if not stack or stack[-1] != pairs[char]:
return False
stack.pop()
return not stack
# Test
print(is_balanced("{[()]}")) # Output: True
36. Implement an LRU Cache Using OrderedDict
Answer:
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity):
self.cache = OrderedDict()
self.capacity = capacity
def get(self, key):
if key not in self.cache:
return -1
self.cache.move_to_end(key) # Recently used
return self.cache[key]
def put(self, key, value):
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.capacity:
self.cache.popitem(last=False) # Remove least recently used
37. Implement a Circular Queue in Python
Answer:
class CircularQueue:
def __init__(self, k):
self.queue = [None] * k
self.capacity = k
self.front = self.rear = -1
def enqueue(self, val):
if (self.rear + 1) % self.capacity == self.front:
return False # Full
if self.front == -1:
self.front = 0
self.rear = (self.rear + 1) % self.capacity
self.queue[self.rear] = val
return True
def dequeue(self):
if self.front == -1:
return False # Empty
if self.front == self.rear:
self.front = self.rear = -1
else:
self.front = (self.front + 1) % self.capacity
return True
def Front(self):
return -1 if self.front == -1 else self.queue[self.front]
def Rear(self):
return -1 if self.rear == -1 else self.queue[self.rear]
def is_empty(self):
return self.front == -1
def is_full(self):
return (self.rear + 1) % self.capacity == self.front
38. Solve the Sliding Window Maximum Using deque
Answer:
from collections import deque
def sliding_window_max(nums, k):
dq = deque()
result = []
for i in range(len(nums)):
while dq and dq[0] < i - k + 1:
dq.popleft()
while dq and nums[dq[-1]] < nums[i]:
dq.pop()
dq.append(i)
if i >= k - 1:
result.append(nums[dq[0]])
return result
# Test
print(sliding_window_max([1,3,-1,-3,5,3,6,7], 3)) # Output: [3,3,5,5,6,7]
39. What is the Difference Between collections.deque and queue.Queue?
Answer:
Use deque for single-threaded fast queue/stack.
Use queue.Queue for multi-threaded applications.
40. Simulate a Browser Back/Forward Feature Using Two Stacks
Answer:
class Browser:
def __init__(self):
self.back_stack = []
self.forward_stack = []
self.current = None
def visit(self, url):
if self.current:
self.back_stack.append(self.current)
self.current = url
self.forward_stack.clear()
def back(self):
if self.back_stack:
self.forward_stack.append(self.current)
self.current = self.back_stack.pop()
return self.current
def forward(self):
if self.forward_stack:
self.back_stack.append(self.current)
self.current = self.forward_stack.pop()
return self.current
# Test
b = Browser()
b.visit("A")
b.visit("B")
b.visit("C")
print(b.back()) # Output: B
print(b.back()) # Output: A
print(b.forward()) # Output: B
Crack coding rounds faster with expert-level Python DS interview prep—start now!
41. Implement In-order, Pre-order, and Post-order Traversal
Answer:
class TreeNode:
def __init__(self, val=0):
self.val = val
self.left = None
self.right = None
# In-order: Left → Root → Right
def inorder(root):
if root:
inorder(root.left)
print(root.val, end=' ')
inorder(root.right)
# Pre-order: Root → Left → Right
def preorder(root):
if root:
print(root.val, end=' ')
preorder(root.left)
preorder(root.right)
# Post-order: Left → Right → Root
def postorder(root):
if root:
postorder(root.left)
postorder(root.right)
print(root.val, end=' ')
42. Check if a Binary Tree is Balanced
Answer:
A binary tree is balanced if for every node, the difference between the heights of left and right subtrees is not more than 1.
def is_balanced(root):
def check(node):
if not node:
return 0
left = check(node.left)
if left == -1: return -1
right = check(node.right)
if right == -1: return -1
if abs(left - right) > 1: return -1
return max(left, right) + 1
return check(root) != -1
43. Find the Lowest Common Ancestor in a Binary Tree
Answer:
def lowest_common_ancestor(root, p, q):
if not root or root == p or root == q:
return root
left = lowest_common_ancestor(root.left, p, q)
right = lowest_common_ancestor(root.right, p, q)
return root if left and right else left or right
44. Serialize and Deserialize a Binary Tree
Answer:
Preorder-based Serialization
class Codec:
def serialize(self, root):
def dfs(node):
if not node:
vals.append("X")
return
vals.append(str(node.val))
dfs(node.left)
dfs(node.right)
vals = []
dfs(root)
return ','.join(vals)
def deserialize(self, data):
def dfs():
val = next(vals)
if val == "X":
return None
node = TreeNode(int(val))
node.left = dfs()
node.right = dfs()
return node
vals = iter(data.split(','))
return dfs()
45. Convert a BST to a Sorted Doubly Linked List
Answer:
def bst_to_dll(root):
def inorder(node):
nonlocal prev, head
if not node:
return
inorder(node.left)
if prev:
prev.right = node
node.left = prev
else:
head = node
prev = node
inorder(node.right)
prev = head = None
inorder(root)
return head
46. Check if a Binary Tree is a Subtree of Another
Answer:
def is_subtree(s, t):
def is_same(a, b):
if not a and not b:
return True
if not a or not b or a.val != b.val:
return False
return is_same(a.left, b.left) and is_same(a.right, b.right)
if not s:
return False
if is_same(s, t):
return True
return is_subtree(s.left, t) or is_subtree(s.right, t)
47. Count the Number of Leaf Nodes in a Binary Tree
Answer:
def count_leaves(root):
if not root:
return 0
if not root.left and not root.right:
return 1
return count_leaves(root.left) + count_leaves(root.right)
48. Implement Level Order Traversal Using a Queue
Answer:
from collections import deque
def level_order(root):
if not root:
return []
result = []
queue = deque([root])
while queue:
node = queue.popleft()
result.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return result
49. Construct a Binary Tree from Inorder and Preorder Traversals
Answer:
def build_tree(preorder, inorder):
if not preorder or not inorder:
return None
root = TreeNode(preorder[0])
idx = inorder.index(preorder[0])
root.left = build_tree(preorder[1:idx+1], inorder[:idx])
root.right = build_tree(preorder[idx+1:], inorder[idx+1:])
return root
50. Validate if a Given Binary Tree is a Binary Search Tree (BST)
Answer:
def is_valid_bst(root):
def validate(node, low=float('-inf'), high=float('inf')):
if not node:
return True
if not (low < node.val < high):
return False
return validate(node.left, low, node.val) and validate(node.right, node.val, high)
return validate(root)
51. Implement BFS and DFS for a Graph in Python
Answer:
from collections import deque, defaultdict
# Assume graph is represented as an adjacency list: {node: [neighbors]}
def bfs(graph, start):
visited = set()
queue = deque([start])
while queue:
node = queue.popleft()
if node not in visited:
visited.add(node)
print(node, end=' ')
for neighbor in graph[node]:
if neighbor not in visited:
queue.append(neighbor)
def dfs(graph, node, visited=None):
if visited is None:
visited = set()
if node not in visited:
visited.add(node)
print(node, end=' ')
for neighbor in graph[node]:
dfs(graph, neighbor, visited)
52. Detect a Cycle in a Directed Graph Using DFS
python
CopyEdit
def has_cycle(graph):
visited = set()
rec_stack = set()
def dfs(node):
if node in rec_stack:
return True
if node in visited:
return False
visited.add(node)
rec_stack.add(node)
for neighbor in graph[node]:
if dfs(neighbor):
return True
rec_stack.remove(node)
return False
for node in graph:
if dfs(node):
return True
return False
53. Find the Shortest Path in an Unweighted Graph Using BFS
Answer:
def shortest_path(graph, start, target):
queue = deque([(start, [start])])
visited = set([start])
while queue:
node, path = queue.popleft()
if node == target:
return path
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, path + [neighbor]))
return [] # No path
54. Implement Dijkstra’s Algorithm Using heapq
Answer:
import heapq
def dijkstra(graph, start):
dist = {node: float('inf') for node in graph}
dist[start] = 0
pq = [(0, start)] # (distance, node)
while pq:
curr_dist, node = heapq.heappop(pq)
if curr_dist > dist[node]:
continue
for neighbor, weight in graph[node]:
new_dist = curr_dist + weight
if new_dist < dist[neighbor]:
dist[neighbor] = new_dist
heapq.heappush(pq, (new_dist, neighbor))
return dist
55. Explain Union-Find and Implement It With Path Compression
Answer:
Union-Find (Disjoint Set) tracks connected components.
- Find(x): Returns the root of the set.
- Union(x, y): Merges two sets.
- Optimized with:
- Path Compression
- Union by Rank
class UnionFind:
def __init__(self, size):
self.parent = list(range(size))
self.rank = [0] * size
def find(self, x):
if self.parent[x] != x:
self.parent[x] = self.find(self.parent[x]) # Path Compression
return self.parent[x]
def union(self, x, y):
rootX, rootY = self.find(x), self.find(y)
if rootX == rootY:
return
if self.rank[rootX] > self.rank[rootY]:
self.parent[rootY] = rootX
elif self.rank[rootX] < self.rank[rootY]:
self.parent[rootX] = rootY
else:
self.parent[rootY] = rootX
self.rank[rootX] += 1
56. Implement Topological Sort (Kahn’s Algorithm)
Answer:
from collections import deque, defaultdict
def kahn_topological_sort(graph):
in_degree = {node: 0 for node in graph}
for neighbors in graph.values():
for neighbor in neighbors:
in_degree[neighbor] += 1
queue = deque([node for node in graph if in_degree[node] == 0])
result = []
while queue:
node = queue.popleft()
result.append(node)
for neighbor in graph[node]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
if len(result) != len(graph):
return [] # Cycle detected
return result
57. Design a Trie for Prefix Search and Autocomplete
Answer:
class TrieNode:
def __init__(self):
self.children = {}
self.is_end = False
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, word):
node = self.root
for ch in word:
node = node.children.setdefault(ch, TrieNode())
node.is_end = True
def search(self, word):
node = self.root
for ch in word:
if ch not in node.children:
return False
node = node.children[ch]
return node.is_end
def starts_with(self, prefix):
node = self.root
for ch in prefix:
if ch not in node.children:
return []
node = node.children[ch]
return self._autocomplete(node, prefix)
def _autocomplete(self, node, prefix):
results = []
if node.is_end:
results.append(prefix)
for ch, child in node.children.items():
results.extend(self._autocomplete(child, prefix + ch))
return results
58. Find Connected Components in an Undirected Graph
Answer:
def connected_components(graph):
visited = set()
components = []
def dfs(node, component):
visited.add(node)
component.append(node)
for neighbor in graph[node]:
if neighbor not in visited:
dfs(neighbor, component)
for node in graph:
if node not in visited:
component = []
dfs(node, component)
components.append(component)
return components
59. Explain and Implement a Segment Tree for Range Sum Query
Answer:
Segment Tree: Binary tree structure for fast range queries and updates.
class SegmentTree:
def __init__(self, arr):
self.n = len(arr)
self.tree = [0] * (4 * self.n)
self.build(arr, 0, 0, self.n - 1)
def build(self, arr, node, l, r):
if l == r:
self.tree[node] = arr[l]
else:
mid = (l + r) // 2
self.build(arr, 2*node+1, l, mid)
self.build(arr, 2*node+2, mid+1, r)
self.tree[node] = self.tree[2*node+1] + self.tree[2*node+2]
def query(self, node, l, r, ql, qr):
if ql > r or qr < l: return 0 # No overlap
if ql <= l and r <= qr: return self.tree[node] # Total overlap
mid = (l + r) // 2
return self.query(2*node+1, l, mid, ql, qr) + self.query(2*node+2, mid+1, r, ql, qr)
def range_sum(self, left, right):
return self.query(0, 0, self.n - 1, left, right)
60. What is a Heap in Python? Implement a Min/Max Heap Manually
Answer:
A heap is a binary tree where each node follows the heap property:
- Min-Heap: Parent ≤ Children
- Max-Heap: Parent ≥ Children
Python’s heapq supports min-heap by default.
Manual Min-Heap Implementation:
class MinHeap:
def __init__(self):
self.heap = []
def insert(self, val):
self.heap.append(val)
self._heapify_up(len(self.heap) - 1)
def extract_min(self):
if not self.heap:
return None
min_val = self.heap[0]
self.heap[0] = self.heap.pop()
self._heapify_down(0)
return min_val
def _heapify_up(self, i):
parent = (i - 1) // 2
if i > 0 and self.heap[i] < self.heap[parent]:
self.heap[i], self.heap[parent] = self.heap[parent], self.heap[i]
self._heapify_up(parent)
def _heapify_down(self, i):
smallest = i
l, r = 2*i + 1, 2*i + 2
if l < len(self.heap) and self.heap[l] < self.heap[smallest]:
smallest = l
if r < len(self.heap) and self.heap[r] < self.heap[smallest]:
smallest = r
if smallest != i:
self.heap[i], self.heap[smallest] = self.heap[smallest], self.heap[i]
self._heapify_down(smallest)