Solve six algorithmic problems
Company: LinkedIn
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Onsite
Quick Answer: This multi-part question evaluates algorithmic problem solving, data-structure selection, complexity analysis, API design, and the handling of trees, arrays, grids, circular structures, and dynamic updates.
Part 1: Bulb Toggling Passes
Constraints
- 0 <= n <= 10^18
- Bulbs are 1-indexed
- A toggle changes off to on, or on to off
Examples
Input: (0,)
Expected Output: 0
Explanation: There are no bulbs, so the answer is 0.
Input: (1,)
Expected Output: 1
Explanation: Bulb 1 is toggled once, so it stays on.
Input: (3,)
Expected Output: 1
Explanation: Only bulb 1 remains on.
Input: (10,)
Expected Output: 3
Explanation: The bulbs left on are 1, 4, and 9.
Input: (25,)
Expected Output: 5
Explanation: The bulbs left on are the perfect squares up to 25.
Solution
def solution(n):
import math
if n <= 0:
return 0
return math.isqrt(n)Time complexity: O(1). Space complexity: O(1).
Hints
- A bulb ends up on only if it is toggled an odd number of times.
- Most divisors come in pairs. Which numbers have an unpaired divisor?
Part 2: Repeated Word-Distance Queries
Constraints
- 1 <= len(words) <= 10^5
- 1 <= len(queries) <= 10^5
- Each word is a non-empty string
- Total processing should avoid rescanning the full words array for every query
Examples
Input: (["practice", "makes", "perfect", "coding", "makes"], [("coding", "practice"), ("makes", "coding")])
Expected Output: [3, 1]
Explanation: coding is at index 3 and practice at 0, so distance 3. makes occurs at 1 and 4; the closest makes to coding is at 4, so distance 1.
Input: (["a", "b", "a"], [("a", "c")])
Expected Output: [-1]
Explanation: Word c does not appear.
Input: (["a", "x", "a", "a"], [("a", "a"), ("x", "x")])
Expected Output: [1, -1]
Explanation: For a, the closest distinct occurrences are at indices 2 and 3. For x, there is only one occurrence.
Input: (["the", "quick", "brown", "fox", "quick"], [("quick", "fox"), ("the", "brown"), ("quick", "quick"), ("fox", "quick")])
Expected Output: [1, 2, 3, 1]
Explanation: The last query is the same pair as the first, just reversed.
Solution
def solution(words, queries):
positions = {}
for i, word in enumerate(words):
positions.setdefault(word, []).append(i)
cache = {}
answer = []
for w1, w2 in queries:
key = (w1, w2) if w1 <= w2 else (w2, w1)
if key in cache:
answer.append(cache[key])
continue
if w1 not in positions or w2 not in positions:
cache[key] = -1
answer.append(-1)
continue
if w1 == w2:
pos = positions[w1]
if len(pos) < 2:
dist = -1
else:
dist = min(pos[i] - pos[i - 1] for i in range(1, len(pos)))
cache[key] = dist
answer.append(dist)
continue
a = positions[w1]
b = positions[w2]
i = j = 0
best = float('inf')
while i < len(a) and j < len(b):
best = min(best, abs(a[i] - b[j]))
if a[i] < b[j]:
i += 1
else:
j += 1
cache[key] = best
answer.append(best)
return answerTime complexity: O(n + sum over uncached queries of (occ(word1) + occ(word2)))). Space complexity: O(n + u).
Hints
- Store all positions for each distinct word in sorted order as you scan the list once.
- To compare two sorted position lists efficiently, try a two-pointer walk.
Part 3: Invert a Binary Tree
Constraints
- 0 <= number of list entries <= 10^4
- Node values are integers
- If level_order is empty, the tree is empty
Examples
Input: ([4, 2, 7, 1, 3, 6, 9],)
Expected Output: [4, 7, 2, 9, 6, 3, 1]
Explanation: Every left/right subtree pair is mirrored.
Input: ([2, 1, 3, None, None, 4],)
Expected Output: [2, 3, 1, None, 4]
Explanation: Node 3's single child moves from the left side to the right side after inversion.
Input: ([],)
Expected Output: []
Explanation: An empty tree stays empty.
Input: ([1],)
Expected Output: [1]
Explanation: A single-node tree is unchanged.
Input: ([1, None, 2],)
Expected Output: [1, 2]
Explanation: A node with only a right child becomes a node with only a left child.
Solution
def solution(level_order):
if not level_order:
return []
class Node:
__slots__ = ('val', 'left', 'right')
def __init__(self, val):
self.val = val
self.left = None
self.right = None
nodes = [None if v is None else Node(v) for v in level_order]
for i, node in enumerate(nodes):
if node is None:
continue
left_i = 2 * i + 1
right_i = 2 * i + 2
if left_i < len(nodes):
node.left = nodes[left_i]
if right_i < len(nodes):
node.right = nodes[right_i]
root = nodes[0]
if root is None:
return []
queue = [root]
head = 0
while head < len(queue):
node = queue[head]
head += 1
node.left, node.right = node.right, node.left
if node.left is not None:
queue.append(node.left)
if node.right is not None:
queue.append(node.right)
output = []
queue = [root]
head = 0
while head < len(queue):
node = queue[head]
head += 1
if node is None:
output.append(None)
continue
output.append(node.val)
queue.append(node.left)
queue.append(node.right)
while output and output[-1] is None:
output.pop()
return outputTime complexity: O(n). Space complexity: O(n).
Hints
- Inversion is local: at every node, swap the left and right pointers.
- A breadth-first traversal is an easy way to process every node iteratively.
Part 4: Count Land Clusters in a Grid
Constraints
- 0 <= number of rows <= 300
- 0 <= number of columns <= 300
- All rows have the same length
- Adjacency is only up, down, left, and right
Examples
Input: (["11000", "11000", "00100", "00011"],)
Expected Output: 3
Explanation: There are three separate land clusters.
Input: (["000", "000"],)
Expected Output: 0
Explanation: There is no land.
Input: (["1"],)
Expected Output: 1
Explanation: A single land cell is one cluster.
Input: (["10", "01"],)
Expected Output: 2
Explanation: Diagonal cells are not connected under 4-directional adjacency.
Input: ([],)
Expected Output: 0
Explanation: An empty grid has zero clusters.
Solution
def solution(grid):
if not grid:
return 0
rows = len(grid)
cols = len(grid[0]) if grid[0] else 0
if cols == 0:
return 0
g = [list(row) for row in grid]
count = 0
for r in range(rows):
for c in range(cols):
if g[r][c] != '1':
continue
count += 1
stack = [(r, c)]
g[r][c] = '0'
while stack:
x, y = stack.pop()
if x > 0 and g[x - 1][y] == '1':
g[x - 1][y] = '0'
stack.append((x - 1, y))
if x + 1 < rows and g[x + 1][y] == '1':
g[x + 1][y] = '0'
stack.append((x + 1, y))
if y > 0 and g[x][y - 1] == '1':
g[x][y - 1] = '0'
stack.append((x, y - 1))
if y + 1 < cols and g[x][y + 1] == '1':
g[x][y + 1] = '0'
stack.append((x, y + 1))
return countTime complexity: O(m * n). Space complexity: O(m * n) in the worst case due to the exploration stack.
Hints
- When you find an unvisited land cell, explore its whole component with DFS or BFS.
- Mark cells as visited so you do not count the same island more than once.
Part 5: Longest Run in a Circular Binary Array
Constraints
- 0 <= len(nums) <= 2 * 10^5
- 0 <= k <= len(nums)
- Each element of nums is either 0 or 1
- The chosen window length cannot exceed len(nums)
Examples
Input: ([1, 1, 0, 1], 0)
Expected Output: (3, 3)
Explanation: The best circular run is indices 3 -> 0 -> 1, which gives three 1s.
Input: ([1, 0, 1, 1, 0], 1)
Expected Output: (4, 0)
Explanation: Flipping the zero at index 1 gives a length-4 window starting at 0. Another length-4 answer starts at 2, but 0 is smaller.
Input: ([0, 0, 0], 2)
Expected Output: (2, 0)
Explanation: You can flip at most two zeros, so the best window length is 2.
Input: ([1, 1, 1], 1)
Expected Output: (3, 0)
Explanation: The whole array is already all 1s.
Input: ([], 1)
Expected Output: (0, -1)
Explanation: Empty input has no valid starting index.
Solution
def solution(nums, k):
n = len(nums)
if n == 0:
return (0, -1)
left = 0
zeros = 0
best_len = 0
best_start = 0
for right in range(2 * n):
if nums[right % n] == 0:
zeros += 1
while zeros > k or right - left + 1 > n:
if nums[left % n] == 0:
zeros -= 1
left += 1
if left < n:
cur_len = right - left + 1
if cur_len > best_len or (cur_len == best_len and left < best_start):
best_len = cur_len
best_start = left
return (best_len, best_start)Time complexity: O(n). Space complexity: O(1).
Hints
- A standard sliding window works for 'at most k zeros', but circularity suggests looking at a doubled array.
- Even with doubling, do not allow a window longer than the original array length.
Part 6: Peel Leaves by Rounds
Constraints
- 0 <= number of list entries <= 10^4
- Node values are integers
- The intended solution should run in O(n)
Examples
Input: ([1, 2, 3, 4, 5],)
Expected Output: [[4, 5, 3], [2], [1]]
Explanation: First remove leaves 4, 5, and 3; then 2 becomes a leaf; finally 1.
Input: ([1],)
Expected Output: [[1]]
Explanation: A single node is removed in the first round.
Input: ([],)
Expected Output: []
Explanation: An empty tree produces no rounds.
Input: ([1, 2, 3, None, 4],)
Expected Output: [[4, 3], [2], [1]]
Explanation: Nodes 4 and 3 are leaves first, then 2, then 1.
Input: ([1, 2, 3, 4, 5, 6, 7],)
Expected Output: [[4, 5, 6, 7], [2, 3], [1]]
Explanation: All nodes are grouped by their distance from the nearest leaf.
Solution
def solution(level_order):
if not level_order:
return []
class Node:
__slots__ = ('val', 'left', 'right')
def __init__(self, val):
self.val = val
self.left = None
self.right = None
nodes = [None if v is None else Node(v) for v in level_order]
for i, node in enumerate(nodes):
if node is None:
continue
left_i = 2 * i + 1
right_i = 2 * i + 2
if left_i < len(nodes):
node.left = nodes[left_i]
if right_i < len(nodes):
node.right = nodes[right_i]
root = nodes[0]
if root is None:
return []
import sys
sys.setrecursionlimit(1_000_000)
result = []
def dfs(node):
if node is None:
return -1
h_left = dfs(node.left)
h_right = dfs(node.right)
h = 1 + max(h_left, h_right)
if h == len(result):
result.append([])
result[h].append(node.val)
return h
dfs(root)
return resultTime complexity: O(n). Space complexity: O(n).
Hints
- Instead of physically removing leaves round by round, compute each node's height from the bottom.
- All nodes with the same bottom-up height are removed in the same round.