Solve four algorithm design tasks
Company: TikTok
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Take-home Project
Quick Answer: This multi-part question evaluates algorithm design and data-structure competencies including wildcard string pattern matching and trade-offs between dynamic programming and greedy strategies, dynamic pair-sum maintenance under frequent updates, O(1) LRU cache design, and scheduling/ordering for time-constrained threat neutralization.
Wildcard Pattern Matching
Constraints
- 0 <= len(s), len(p) <= 2000
- s contains only lowercase English letters
- p contains lowercase English letters, '?' and '*'
- The match must cover the entire input string
Examples
Input: ("aa", "a")
Expected Output: False
Explanation: The single 'a' in the pattern cannot cover the two-character string 'aa'.
Input: ("aa", "*")
Expected Output: True
Explanation: '*' matches any sequence, including 'aa'.
Input: ("cb", "?a")
Expected Output: False
Explanation: '?' matches 'c', but the literal 'a' does not match 'b'.
Input: ("adceb", "*a*b")
Expected Output: True
Explanation: First '*' matches empty, 'a' matches 'a', second '*' matches 'dce', 'b' matches 'b'.
Input: ("acdcb", "a*c?b")
Expected Output: False
Explanation: There is no way to align '*' and '?' so the whole string matches.
Input: ("", "")
Expected Output: True
Explanation: Empty string matches empty pattern.
Input: ("", "*")
Expected Output: True
Explanation: '*' matches the empty sequence.
Input: ("", "?")
Expected Output: False
Explanation: '?' requires exactly one character, but the string is empty.
Input: ("abc", "***a***b***c***")
Expected Output: True
Explanation: Consecutive stars collapse; the literals a, b, c match in order.
Hints
- Define dp[i][j] = does s[:i] match p[:j]. Base case dp[0][0] = True.
- A '*' in the pattern can either match no characters (dp[i][j-1]) or extend over one more character of s (dp[i-1][j]).
- Initialize the first row: a prefix of stars can match the empty string, so dp[0][j] = dp[0][j-1] when p[j-1] == '*'.
- The greedy alternative tracks the most recent '*' position and the last s-index it matched, so you can backtrack when a literal mismatch occurs.
Dynamic Pair-Sum Counter
Constraints
- 1 <= len(A), len(B) <= 1e5
- Up to 1e5 add/count operations combined
- For add: 0 <= i < len(B), and val keeps B[i] within integer range
- Element and total values fit in a 32-bit signed integer
Examples
Input: ([1, 1, 2, 3], [1, 4, 5, 2, 5, 4], [('count', 7), ('add', 3, 2), ('count', 8), ('count', 4)])
Expected Output: [4, 2, 1]
Explanation: count(7): a=2 pairs with the two 5s (2), a=3 pairs with the two 4s (2) -> 4. After add(3,2) B[3] becomes 4 so B has three 4s. count(8): a=3 + each 4 -> 3 pairs? a=3 needs 5 (two 5s)=2; a=2 needs 6=0 -> 2. count(4): a=2 needs 2 (B has one 2)=1.
Input: ([1, 2, 3], [1, 2, 3], [('count', 4)])
Expected Output: [3]
Explanation: (1,3),(2,2),(3,1) all sum to 4 -> 3 pairs.
Input: ([0], [0], [('count', 0), ('add', 0, 5), ('count', 5), ('count', 0)])
Expected Output: [1, 1, 0]
Explanation: Initially (0,0)=0 -> 1. After add B[0]=5: count(5) -> (0,5)=1; count(0) -> no b equals 0 -> 0.
Input: ([-1, -2], [3, 4], [('count', 2), ('count', 1), ('add', 0, -2), ('count', 0)])
Expected Output: [2, 1, 1]
Explanation: count(2): (-1,3),(-2,4) -> 2. count(1): (-2,3) -> 1. After add B[0]=1: count(0): (-1,1) -> 1.
Input: ([5], [5], [('count', 100)])
Expected Output: [0]
Explanation: 5+5=10, never 100 -> 0 pairs.
Hints
- Keep a hash map of value -> frequency for B so each count is a sweep over A's distinct values with O(1) complement lookups.
- On add(i, val): decrement the count of the OLD B[i], mutate B[i], then increment the count of the NEW value. Never rescan B.
- Multiply A's value frequency by B's complement frequency to account for duplicate values on both sides.
- Iterate the array with fewer DISTINCT values (here A) during count to minimize work.
LRU Cache (O(1) get and put)
Constraints
- 1 <= cap <= 3000
- 0 <= key <= 1e4 (or arbitrary hashable keys)
- 0 <= value <= 1e5
- Up to 2e5 get/put operations
- Every get and put must be O(1)
Examples
Input: (2, [('put', 1, 1), ('put', 2, 2), ('get', 1), ('put', 3, 3), ('get', 2), ('put', 4, 4), ('get', 1), ('get', 3), ('get', 4)])
Expected Output: [1, -1, -1, 3, 4]
Explanation: get(1)=1 (and makes 1 MRU); put(3) evicts LRU key 2; get(2)=-1; put(4) evicts key 1; get(1)=-1; get(3)=3; get(4)=4.
Input: (1, [('put', 1, 1), ('get', 1), ('put', 2, 2), ('get', 1), ('get', 2)])
Expected Output: [1, -1, 2]
Explanation: Capacity 1: put(2) evicts key 1, so get(1)=-1 and get(2)=2.
Input: (2, [('get', 0)])
Expected Output: [-1]
Explanation: Nothing was ever inserted, so get returns -1.
Input: (2, [('put', 1, 10), ('put', 1, 20), ('get', 1)])
Expected Output: [20]
Explanation: The second put updates the value of an existing key to 20 without changing the count.
Input: (3, [('put', 1, 1), ('put', 2, 2), ('put', 3, 3), ('get', 1), ('put', 4, 4), ('get', 2), ('get', 1), ('get', 3), ('get', 4)])
Expected Output: [1, -1, 1, 3, 4]
Explanation: get(1)=1 makes 1 MRU; put(4) evicts the LRU key 2; get(2)=-1; the rest are present.
Hints
- Pair a hash map (key -> node) with a doubly linked list ordered most- to least-recently used.
- On get/put of an existing key, unlink the node and move it to the most-recently-used end.
- When a put overflows capacity, evict the node at the least-recently-used end and delete its hash-map entry.
- A sentinel head and tail node removes all null-edge special cases in the linked list.
Max Threats Neutralized Before Breach
Constraints
- n == len(dist) == len(speed)
- 1 <= n <= 1e5
- 1 <= dist[i], speed[i] <= 1e5
- Use exact division (compare dist[i] vs t*speed[i] with integers to avoid floating error in production)
Examples
Input: ([1, 3, 4], [1, 1, 1])
Expected Output: 3
Explanation: Arrivals [1,3,4]. t=0:1>0 ok; t=1:3>1 ok; t=2:4>2 ok. All three neutralized.
Input: ([1, 1, 2, 3], [1, 1, 1, 1])
Expected Output: 1
Explanation: Arrivals [1,1,2,3]. t=0:1>0 ok (neutralize 1); t=1:1<=1 breach -> return 1.
Input: ([3, 2, 4], [5, 3, 2])
Expected Output: 1
Explanation: Arrivals [0.6,0.667,2.0]. t=0:0.6>0 ok; t=1:0.667<=1 breach -> return 1.
Input: ([4, 2, 3], [2, 1, 1])
Expected Output: 3
Explanation: Arrivals [2.0,2.0,3.0]. t=0,1,2 all strictly greater -> all neutralized.
Input: ([10, 10, 10], [1, 1, 1])
Expected Output: 3
Explanation: Arrivals [10,10,10]; far away, all neutralized within 3 minutes.
Input: ([5], [1])
Expected Output: 1
Explanation: Single threat arrives at t=5; neutralized at t=0 -> 1.
Input: ([1], [1])
Expected Output: 1
Explanation: Threat arrives at t=1; at t=0 it has not arrived (1>0), so it is neutralized -> 1.
Hints
- The arrival time of threat i is dist[i] / speed[i]; neutralize threats in increasing order of arrival.
- After sorting, the threat you handle at minute t (0-indexed) is the t-th earliest arriver.
- A breach occurs at minute t when arrival[t] <= t (it arrives before or exactly as you would act).
- If no breach happens through all n minutes, you neutralize every threat -> return n. To avoid float precision, compare dist[i] <= t * speed[i] with integers.