Python + DSA: 50 Practice Problems
with Answers
Each problem includes a concise Python solution and time/space complexity. Use these as
templates in interviews.
1) Reverse a string
def reverse_string(s: str) -> str:
return s[::-1]
# Complexity: O(n) time, O(n) space (for new string)
2) Check palindrome
def is_palindrome(s: str) -> bool:
s = ''.join(ch.lower() for ch in s if ch.isalnum())
return s == s[::-1]
# Complexity: O(n) time, O(n) space
3) Factorial (loop & recursion)
def fact_iter(n: int) -> int:
res = 1
for i in range(2, n+1):
res *= i
return res
def fact_rec(n: int) -> int:
return 1 if n <= 1 else n * fact_rec(n-1)
# Complexity: O(n) time; O(1) space iter, O(n) stack rec
4) Fibonacci sequence
def fib(n: int) -> int:
if n <= 1:
return n
a, b = 0, 1
for _ in range(2, n+1):
a, b = b, a+b
return b
# Complexity: O(n) time, O(1) space
5) Prime number check
def is_prime(n: int) -> bool:
if n < 2:
return False
if n % 2 == 0:
return n == 2
i = 3
while i * i <= n:
if n % i == 0:
return False
i += 2
return True
# Complexity: O(sqrt(n)) time, O(1) space
6) Find GCD & LCM
def gcd(a: int, b: int) -> int:
while b:
a, b = b, a % b
return a
def lcm(a: int, b: int) -> int:
return abs(a*b) // gcd(a, b) if a and b else 0
# Complexity: O(log min(a,b)) time, O(1) space
7) Armstrong number
def is_armstrong(n: int) -> bool:
s = str(n)
p = len(s)
return n == sum(int(ch)**p for ch in s)
# Complexity: O(d) time, O(1) space
8) Count vowels in string
def count_vowels(s: str) -> int:
vowels = set('aeiouAEIOU')
return sum(1 for ch in s if ch in vowels)
# Complexity: O(n) time, O(1) space
9) Anagram check
from collections import Counter
def are_anagrams(a: str, b: str) -> bool:
a = ''.join(sorted(ch.lower() for ch in a if ch.isalnum()))
b = ''.join(sorted(ch.lower() for ch in b if ch.isalnum()))
return a == b
# Complexity: O(n log n) time, O(n) space
10) Longest word in string
def longest_word(s: str) -> str:
words = s.split()
return max(words, key=len) if words else ''
# Complexity: O(n) time, O(k) space (k words)
11) Remove duplicates from list
def remove_dups(lst):
seen = set()
out = []
for x in lst:
if x not in seen:
seen.add(x)
out.append(x)
return out
# Complexity: O(n) time, O(n) space
12) Find max & min in list
def min_max(lst):
if not lst:
return None, None
mn = mx = lst[0]
for x in lst[1:]:
if x < mn: mn = x
if x > mx: mx = x
return mn, mx
# Complexity: O(n) time, O(1) space
13) Second largest element
def second_largest(nums):
first = second = None
for x in nums:
if first is None or x > first:
second, first = first, x
elif x != first and (second is None or x > second):
second = x
return second
# Complexity: O(n) time, O(1) space
14) Merge two sorted arrays
def merge_sorted(a, b):
i = j = 0
res = []
while i < len(a) and j < len(b):
if a[i] <= b[j]:
res.append(a[i]); i += 1
else:
res.append(b[j]); j += 1
res.extend(a[i:]); res.extend(b[j:])
return res
# Complexity: O(n+m) time, O(n+m) space
15) Rotate array by k steps
def rotate(nums, k):
n = len(nums)
k %= n
def rev(i, j):
while i < j:
nums[i], nums[j] = nums[j], nums[i]
i += 1; j -= 1
rev(0, n-1); rev(0, k-1); rev(k, n-1)
return nums
# Complexity: O(n) time, O(1) space
16) Missing number in array (0..n)
def missing_number(nums):
n = len(nums)
return n*(n+1)//2 - sum(nums)
# Complexity: O(n) time, O(1) space
17) Find duplicates in array
def find_duplicates(nums):
from collections import Counter
c = Counter(nums)
return [x for x, f in c.items() if f > 1]
# Complexity: O(n) time, O(n) space
18) Two Sum problem
def two_sum(nums, target):
idx = {}
for i, x in enumerate(nums):
if target - x in idx:
return idx[target - x], i
idx[x] = i
return None
# Complexity: O(n) time, O(n) space
19) Move zeroes to end
def move_zeroes(nums):
j = 0
for i, x in enumerate(nums):
if x != 0:
nums[j], nums[i] = nums[i], nums[j]
j += 1
return nums
# Complexity: O(n) time, O(1) space
20) Kadane’s algorithm (max subarray sum)
def max_subarray(nums):
best = curr = nums[0]
for x in nums[1:]:
curr = max(x, curr + x)
best = max(best, curr)
return best
# Complexity: O(n) time, O(1) space
21) Binary search
def binary_search(a, target):
l, r = 0, len(a) - 1
while l <= r:
m = (l + r) // 2
if a[m] == target:
return m
if a[m] < target:
l = m + 1
else:
r = m - 1
return -1
# Complexity: O(log n) time, O(1) space
22) Linear search
def linear_search(a, target):
for i, x in enumerate(a):
if x == target:
return i
return -1
# Complexity: O(n) time, O(1) space
23) Merge sort
def merge_sort(a):
if len(a) <= 1:
return a
mid = len(a)//2
left = merge_sort(a[:mid])
right = merge_sort(a[mid:])
return merge_sorted(left, right)
# Complexity: O(n log n) time, O(n) space
24) Quick sort (in-place)
def quick_sort(nums):
def part(l, r):
pivot = nums[r]
i = l
for j in range(l, r):
if nums[j] <= pivot:
nums[i], nums[j] = nums[j], nums[i]
i += 1
nums[i], nums[r] = nums[r], nums[i]
return i
def qs(l, r):
if l < r:
p = part(l, r)
qs(l, p-1); qs(p+1, r)
qs(0, len(nums)-1)
return nums
# Avg: O(n log n), worst: O(n^2); O(log n) space (stack)
25) Bubble sort
def bubble_sort(a):
n = len(a)
for i in range(n):
swapped = False
for j in range(0, n-i-1):
if a[j] > a[j+1]:
a[j], a[j+1] = a[j+1], a[j]
swapped = True
if not swapped: break
return a
# Complexity: O(n^2) time, O(1) space
26) Insertion sort
def insertion_sort(a):
for i in range(1, len(a)):
key = a[i]; j = i - 1
while j >= 0 and a[j] > key:
a[j+1] = a[j]; j -= 1
a[j+1] = key
return a
# Complexity: O(n^2) time, O(1) space
27) Stack implementation
class Stack:
def __init__(self):
self.data = []
def push(self, x): self.data.append(x)
def pop(self): return self.data.pop() if self.data else None
def peek(self): return self.data[-1] if self.data else None
def empty(self): return not self.data
# Operations: O(1) amortized
28) Queue implementation (deque)
from collections import deque
class Queue:
def __init__(self):
self.d = deque()
def enqueue(self, x): self.d.append(x)
def dequeue(self): return self.d.popleft() if self.d else None
def empty(self): return not self.d
# Operations: O(1) amortized
29) Queue using two stacks
class MyQueue:
def __init__(self):
self.s1, self.s2 = [], []
def enqueue(self, x):
self.s1.append(x)
def dequeue(self):
if not self.s2:
while self.s1:
self.s2.append(self.s1.pop())
return self.s2.pop() if self.s2 else None
# Amortized O(1) per op
30) Balanced parentheses
def is_balanced(s: str) -> bool:
pairs = {')':'(', ']':'[', '}':'{'}
stack = []
for ch in s:
if ch in '([{':
stack.append(ch)
elif ch in ')]}':
if not stack or stack[-1] != pairs[ch]:
return False
stack.pop()
return not stack
# Complexity: O(n) time, O(n) space
31) Reverse a linked list
class Node:
def __init__(self, val, nxt=None):
self.val, self.next = val, nxt
def reverse_list(head):
prev, curr = None, head
while curr:
nxt = curr.next
curr.next = prev
prev = curr
curr = nxt
return prev
# Complexity: O(n) time, O(1) space
32) Detect cycle in linked list (Floyd)
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
# Complexity: O(n) time, O(1) space
33) Middle element of linked list
def middle_node(head):
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
# Complexity: O(n) time, O(1) space
34) Merge two sorted linked lists
def merge_lists(l1, l2):
dummy = tail = Node(0)
while l1 and l2:
if l1.val <= l2.val:
tail.next, l1 = l1, l1.next
else:
tail.next, l2 = l2, l2.next
tail = tail.next
tail.next = l1 or l2
return dummy.next
# Complexity: O(n+m) time, O(1) space
35) Remove duplicates in linked list (unsorted)
def remove_dups_linked(head):
seen = set()
prev, curr = None, head
while curr:
if curr.val in seen:
prev.next = curr.next
else:
seen.add(curr.val)
prev = curr
curr = curr.next
return head
# Complexity: O(n) time, O(n) space
36) Implement hash map using dict (demo)
# Python dict is a hash map; example frequency counter:
def freq_map(iterable):
d = {}
for x in iterable:
d[x] = d.get(x, 0) + 1
return d
# Avg O(1) per op
37) First non-repeating char in string
def first_unique_char(s: str) -> int:
from collections import Counter
c = Counter(s)
for i, ch in enumerate(s):
if c[ch] == 1:
return i
return -1
# Complexity: O(n) time, O(1) space (alphabet bounded) or O(n)
38) Count character frequency
def char_freq(s: str):
from collections import Counter
return dict(Counter(s))
# Complexity: O(n) time, O(k) space
39) Find intersection of two arrays
def intersection(a, b):
sa, sb = set(a), set(b)
return list(sa & sb)
# Complexity: O(n+m) time, O(n+m) space
40) Union of arrays
def union(a, b):
return list(set(a) | set(b))
# Complexity: O(n+m) time, O(n+m) space
41) Longest common prefix (strings)
def longest_common_prefix(strs):
if not strs:
return ''
shortest = min(strs, key=len)
for i, ch in enumerate(shortest):
for s in strs:
if s[i] != ch:
return shortest[:i]
return shortest
# Complexity: O(n * m) time, O(1) space
42) Longest palindromic substring (expand centers)
def longest_palindrome(s: str) -> str:
if not s: return ''
start, end = 0, 0
def expand(l, r):
while l >= 0 and r < len(s) and s[l] == s[r]:
l -= 1; r += 1
return l+1, r-1
for i in range(len(s)):
l1, r1 = expand(i, i)
l2, r2 = expand(i, i+1)
if r1 - l1 > end - start:
start, end = l1, r1
if r2 - l2 > end - start:
start, end = l2, r2
return s[start:end+1]
# Complexity: O(n^2) time, O(1) space
43) Tower of Hanoi (moves list)
def hanoi(n, src='A', aux='B', dst='C'):
moves = []
def move(k, a, b, c):
if k == 0: return
move(k-1, a, c, b)
moves.append((a, c))
move(k-1, b, a, c)
move(n, src, aux, dst)
return moves
# Complexity: O(2^n) time, O(n) space
44) Print all subsets of a set
def subsets(nums):
res = [[]]
for x in nums:
res += [curr + [x] for curr in res]
return res
# Complexity: O(n * 2^n) time, O(2^n) space
45) Binary tree traversal (inorder, preorder, postorder)
class TNode:
def __init__(self, val, left=None, right=None):
self.val, self.left, self.right = val, left, right
def inorder(root):
return inorder(root.left) + [root.val] + inorder(root.right) if root
else []
def preorder(root):
return [root.val] + preorder(root.left) + preorder(root.right) if
root else []
def postorder(root):
return postorder(root.left) + postorder(root.right) + [root.val] if
root else []
# Complexity: O(n) time, O(h) stack
46) Height of binary tree
def height(root):
if not root: return -1 # edges height; use 0 for nodes height
return 1 + max(height(root.left), height(root.right))
# Complexity: O(n) time, O(h) space
47) Lowest common ancestor (BST)
def lca_bst(root, p, q):
cur = root
while cur:
if p.val < cur.val and q.val < cur.val:
cur = cur.left
elif p.val > cur.val and q.val > cur.val:
cur = cur.right
else:
return cur
# Complexity: O(h) time, O(1) space
48) Graph using adjacency list
def build_graph(edges, directed=False):
g = {}
for u, v in edges:
g.setdefault(u, []).append(v)
if not directed:
g.setdefault(v, []).append(u)
return g
# Complexity: O(V+E)
49) BFS traversal
from collections import deque
def bfs(graph, start):
visited, order = set([start]), []
q = deque([start])
while q:
u = q.popleft()
order.append(u)
for v in graph.get(u, []):
if v not in visited:
visited.add(v); q.append(v)
return order
# Complexity: O(V+E) time, O(V) space
50) DFS traversal
def dfs(graph, start):
visited, order = set(), []
def rec(u):
visited.add(u); order.append(u)
for v in graph.get(u, []):
if v not in visited:
rec(v)
rec(start)
return order
# Complexity: O(V+E) time, O(V) space