Daily Coding Problem Solutions: Two Sum, Binary Tree Level Order Traversal, LRU Cache, and Trie
Table of Contents
Daily Coding Problem: Get exceptionally good at coding interviews by solving one problem every day - Drill Questions
Two Sum Problem drill daily_coding_problem_wu_miller
Explain the "Two Sum" problem and provide an efficient solution.
Answer
The "Two Sum" problem asks to find two numbers in an array that add up to a target sum. An efficient solution uses a hash table:
- Create an empty hash table.
- Iterate through the array.
- For each element x, calculate the complement (target - x).
- If the complement exists in the hash table, return the pair.
- If not, add x to the hash table.
- If no solution is found, return an indication that no such pair exists.
This approach has O(n) time complexity and O(n) space complexity.
def two_sum(nums, target): seen = {} for i, num in enumerate(nums): complement = target - num if complement in seen: return [seen[complement], i] seen[num] = i return [] # No solution found # Example usage print(two_sum([2, 7, 11, 15], 9)) # Output: [0, 1]
Binary Tree Level Order Traversal drill daily_coding_problem_wu_miller
Describe the process of performing a level order traversal on a binary tree and implement it.
Answer
Level order traversal of a binary tree visits nodes level by level, from left to right. The process is:
- Start with the root node.
- Use a queue to keep track of nodes at each level.
While the queue is not empty: a. Get the size of the queue (number of nodes at current level). b. Process all nodes at this level:
- Remove node from queue front.
- Add its value to current level's result.
- Enqueue its left and right children (if they exist).
c. Move to the next level.
Here's an implementation in Python:
from collections import deque class TreeNode: def __init__(self, val=0, left=None, right=None): self.val = val self.left = left self.right = right def levelOrder(root): if not root: return [] result = [] queue = deque([root]) while queue: level_size = len(queue) current_level = [] for _ in range(level_size): node = queue.popleft() 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 # Example usage root = TreeNode(3) root.left = TreeNode(9) root.right = TreeNode(20) root.right.left = TreeNode(15) root.right.right = TreeNode(7) print(levelOrder(root)) # Output: [[3], [9, 20], [15, 7]]
This solution has a time complexity of O(n) and a space complexity of O(w), where n is the number of nodes and w is the maximum width of the tree.
Implement LRU Cache drill daily_coding_problem_wu_miller
Explain the concept of an LRU (Least Recently Used) Cache and implement it with O(1) time complexity for both get and put operations.
Answer
An LRU Cache is a data structure that stores a limited number of items, discarding the least recently used item when it reaches capacity. The key features are:
- Fixed capacity
- Fast access (O(1) for get and put)
- Tracks of order of use
To implement an LRU Cache with O(1) operations:
- Use a hash map for fast key-value lookups
- Use a doubly linked list to maintain order of use
- Move accessed items to the front of the list
- Remove items from the back when at capacity
Here's a Python implementation:
class Node: def __init__(self, key, val): self.key = key self.val = val self.prev = None self.next = None class LRUCache: def __init__(self, capacity): self.capacity = capacity self.cache = {} self.head = Node(0, 0) self.tail = Node(0, 0) self.head.next = self.tail self.tail.prev = self.head def _add_node(self, node): node.prev = self.head node.next = self.head.next self.head.next.prev = node self.head.next = node def _remove_node(self, node): prev = node.prev new = node.next prev.next = new new.prev = prev def _move_to_head(self, node): self._remove_node(node) self._add_node(node) def get(self, key): if key in self.cache: node = self.cache[key] self._move_to_head(node) return node.val return -1 def put(self, key, value): if key in self.cache: node = self.cache[key] node.val = value self._move_to_head(node) else: node = Node(key, value) self.cache[key] = node self._add_node(node) if len(self.cache) > self.capacity: lru = self.tail.prev self._remove_node(lru) del self.cache[lru.key] # Example usage cache = LRUCache(2) cache.put(1, 1) cache.put(2, 2) print(cache.get(1)) # returns 1 cache.put(3, 3) # evicts key 2 print(cache.get(2)) # returns -1 (not found) cache.put(4, 4) # evicts key 1 print(cache.get(1)) # returns -1 (not found) print(cache.get(3)) # returns 3 print(cache.get(4)) # returns 4
This implementation ensures O(1) time complexity for both get and put operations.
Implement Trie (Prefix Tree) drill daily_coding_problem_wu_miller
Describe the structure of a Trie (Prefix Tree) and implement its basic operations: insert, search, and startsWith.
Answer
A Trie, or Prefix Tree, is a tree-like data structure used to store and retrieve strings efficiently. It's particularly useful for prefix-based operations. The key features are:
- Each node represents a character in a string
- The root represents an empty string
- Each path from root to a node forms a prefix
- Nodes can have a boolean flag to mark the end of a word
Basic operations:
- Insert: Add a word to the trie
- Search: Check if a word exists in the trie
- StartsWith: Check if any word in the trie starts with a given prefix
Here's a Python implementation:
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 char in word: if char not in node.children: node.children[char] = TrieNode() node = node.children[char] node.is_end = True def search(self, word): node = self._find_prefix(word) return node is not None and node.is_end def startsWith(self, prefix): return self._find_prefix(prefix) is not None def _find_prefix(self, prefix): node = self.root for char in prefix: if char not in node.children: return None node = node.children[char] return node # Example usage trie = Trie() trie.insert("apple") print(trie.search("apple")) # returns True print(trie.search("app")) # returns False print(trie.startsWith("app")) # returns True trie.insert("app") print(trie.search("app")) # returns True
This implementation provides efficient operations:
- Insert: O(m) time complexity, where m is the length of the word
- Search: O(m) time complexity
- StartsWith: O(m) time complexity
The space complexity is O(n*m), where n is the number of words and m is the average length of words.