Four coding assessment questions I practiced, each with the problem, a worked example, an accepted solution in Python, and the traps that cost points.
How to use this#
- Read the problem and try it yourself before looking at the solution.
- Every question has a complexity limit. The brute-force answer is usually too slow, so the point is spotting the shortcut.
- The Gotchas under each question are the edge cases that broke my first attempt.
1. Robot left or right#
Problem#
A robot on a horizontal line takes a string of commands. Each L moves it one step left and each R moves it one step right. After running every command, where does it stop compared with where it started?
- Return
"L"if it ends up to the left. - Return
"R"if it ends up to the right. - Return
""(empty string) if it ends up where it started.
Time complexity must be better than O(n²).
Examples#
| commands | result |
|---|---|
"LLR" | "L" |
"RRL" | "R" |
"RLRL" | "" |
"" | "" |
Approach#
Only the final position matters, not the path. Keep one counter: add 1 for R, subtract 1 for L, and check the sign at the end.
Solution#
def solution(commands: str) -> str:
pos = 0
for c in commands:
pos += 1 if c == "R" else -1
if pos < 0:
return "L"
if pos > 0:
return "R"
return ""
A one-liner that's also O(n):
def solution(commands: str) -> str:
diff = commands.count("R") - commands.count("L")
return "R" if diff > 0 else "L" if diff < 0 else ""
Complexity#
- Time: O(n). Each command is read once.
- Space: O(1).
Gotchas#
- Ending at the start returns an empty string, not
"0"orNone.
2. Match animations to songs#
Problem#
songs is an array of strings like "name:length", and animations is an array in the same format. An animation fits a song if its length divides the song's length exactly. For each song, pick the fitting animation with the lowest index in animations.
Return one string per song, in song order, formatted as "animationName:timesPlayed", where timesPlayed is song length ÷ animation length. The same animation can be used for several songs, and every song is guaranteed to have a fit.
Time complexity must be better than O(songs × animations).
Constraints: 1 ≤ songs.length ≤ 100, 1 ≤ song length ≤ 50,000.
Example#
songs = ["notion:180", "voyage:185", "sample:180"]
animations = ["circles:360", "squares:180", "lines:37"]
result = ["squares:1", "lines:5", "squares:1"]
notion:180: 360 doesn't divide 180, but 180 does, sosquares:1.voyage:185: neither 360 nor 180 divides 185, but 37 does (37 × 5 = 185), solines:5.sample:180: same asnotion, sosquares:1.
Approach#
Don't test every animation against every song. Two observations:
- When several animations have the same length, only the first one can ever win. Store
length → (index, name)in a dict, keeping the first occurrence. - A song of length L has at most about 2√L divisors, and they come in pairs (
dandL / d). Walkdfrom 1 to √L, look up both halves of each pair in the dict, and keep the match with the lowest index.
Songs with the same length always get the same answer, so cache by length.
Solution#
def solution(songs, animations):
# animation length -> (index, name); keep only the first (lowest-index) one per length
first = {}
for i, a in enumerate(animations):
name, length = a.rsplit(":", 1)
first.setdefault(int(length), (i, name))
cache = {} # song length -> answer, so repeated lengths are O(1)
result = []
for s in songs:
song_len = int(s.rsplit(":", 1)[1])
if song_len not in cache:
best = None # (index, name, animation length)
d = 1
while d * d <= song_len:
if song_len % d == 0:
for div in (d, song_len // d):
if div in first and (best is None or first[div][0] < best[0]):
best = (*first[div], div)
d += 1
cache[song_len] = f"{best[1]}:{song_len // best[2]}"
result.append(cache[song_len])
return result
Complexity#
- Time: O(A + S·√L), where A is the number of animations, S the number of songs and L the longest song. The worst case is about 100 × 224 ≈ 22,000 steps, however many animations there are.
- Space: O(A) for the dict.
Gotchas#
- Lowest index, not smallest length. Compare the stored index, not the divisor.
- Check both divisors in each pair. Testing only
dmisses large animation lengths like 180 for a song of 180. - If the input has spaces around the colon, add
.strip()to the name.
3. Currency trading strategy#
Problem#
A trading bot trades one currency and does one thing each day. rates[i] is the price on day i, and strategy[i] is the action:
-1buys one unit (profit goes down byrates[i]).0holds (no change).1sells one unit (profit goes up byrates[i]).
Profit is the total of sell prices minus the total of buy prices, and it can be negative. You may improve the strategy once:
- Pick exactly
kconsecutive days (kis even). - Set the first half of that range to
0. - Set the second half to
1.
Return the maximum profit possible.
Examples#
rates = [2, 4, 1, 5, 10, 6]
strategy = [-1, 1, 0, 1, -1, 0]
k = 4
result = 18
The original strategy makes −2 + 4 + 0 + 5 − 10 + 0 = −3. Trying each window of 4 days:
| window | new strategy | profit |
|---|---|---|
| days 0–3 | [0, 0, 1, 1, -1, 0] | −4 |
| days 1–4 | [-1, 0, 0, 1, 1, 0] | 13 |
| days 2–5 | [-1, 1, 0, 0, 1, 1] | 18 |
rates = [2, 4, 1, 5, 2, 6, 7]
strategy = [1, 1, 1, 1, 1, 1, 1]
k = 2
result = 27
Every day already sells, so any change turns a sell into a hold and loses money. The best move is not to change anything: 2 + 4 + 1 + 5 + 2 + 6 + 7 = 27.
Approach#
Rebuilding the strategy for every window is O(n·k). Instead, look at what one window changes:
- The original profit inside the window disappears (the first half becomes holds, and the second half is overwritten).
- Every rate in the second half is added (those days now sell).
So for any window:
profit = base − (original profit inside the window) + (sum of rates in the second half)
Both sums can be updated in O(1) as the window slides one day to the right: add the day entering, subtract the day leaving.
Solution#
def solution(rates, strategy, k):
n, h = len(rates), k // 2
base = sum(r * s for r, s in zip(rates, strategy))
# window starting at day 0
window = sum(rates[j] * strategy[j] for j in range(k)) # original profit inside the window
second = sum(rates[h:k]) # second half becomes all sells
best = base - window + second
for i in range(1, n - k + 1):
window += rates[i + k - 1] * strategy[i + k - 1] - rates[i - 1] * strategy[i - 1]
second += rates[i + k - 1] - rates[i + h - 1]
best = max(best, base - window + second)
return max(base, best) # leaving the strategy unchanged is allowed
Complexity#
- Time: O(n). One pass to get the base profit and one sliding pass.
- Space: O(1).
Gotchas#
- The change is optional. Return
max(base, best), notbest. Without it, the all-sells example returns 26 instead of 27. - Second half sliding: when the window moves right by one, the day entering the second half is
i + k − 1and the day leaving it isi + h − 1(it moves into the first half, where it becomes a hold). - If you get the first window's value back (−4 on the first example), the loop isn't running or is using
min. Print each window's profit to check.
4. Group chat mention statistics#
Problem#
members is a list of user ids and messages is a list of chat messages. A mention starts with @ and is followed by one or more ids separated by commas, like @id1 or @id1,id123,id983. An id is id followed by a number from 1 to 999.
Count how many messages mention each member. A member mentioned several times in one message counts once for that message.
Return strings formatted as "id=count", sorted by count (highest first), then by id in ascending string order to break ties.
Rules about the text:
- Mentions are separated from other words by spaces, unless they're at the start or end of a message.
@can also appear in normal words (ex@mple,appro@ch,mentions@), but never as the first character of a word that isn't a mention.- Mentions can include ids that aren't members. Ignore them.
Time complexity must be better than O(members × messages × longest message).
Constraints: 2 ≤ members.length ≤ 50, 3 ≤ members[i].length ≤ 5.
Example#
members = ["id123", "id234", "id7", "id321"]
messages = [
"Hey @id123,id321 review this PR please! @id123 what do you think about this approach?",
"Hey @id7 nice appro@ch! Great job! @id800 what do you think?",
"@id123,id321 thx!"
]
result = ["id123=2", "id321=2", "id7=1", "id234=0"]
- Message 1 mentions
id123twice, but it only counts once. It also mentionsid321. - Message 2 mentions
id7.appro@chisn't a mention, andid800isn't a member. - Message 3 mentions
id123andid321. id123andid321tie at 2, so they're sorted by id:"id123"comes before"id321".
Approach#
Don't search every message once per member. Read each message once, collect the ids it mentions into a set (so duplicates count once), then add 1 for each id in the set that's a member. Sort once at the end.
The regex (?<!\S)@ only matches an @ at the start of the message or right after whitespace, which skips appro@ch and mentions@.
Solution#
import re
# a mention: "@" at the start of a word, then ids separated by "," (a space after the comma is tolerated)
MENTION = re.compile(r"(?<!\S)@(id\d+(?:,\s*id\d+)*)")
def solution(members, messages):
counts = {m: 0 for m in members}
for msg in messages:
mentioned = set() # a member counts at most once per message
for ids in MENTION.findall(msg):
mentioned.update(re.findall(r"id\d+", ids))
for uid in mentioned:
if uid in counts:
counts[uid] += 1
ranked = sorted(members, key=lambda m: (-counts[m], m))
return [f"{m}={counts[m]}" for m in ranked]
Complexity#
- Time: O(total message length + M log M), where M is the number of members. Each message is read once, each count update is O(1), and the sort covers at most 50 members.
- Space: O(M).
Gotchas#
- Once per message: collect ids into a set before counting.
- Exact id match: a mention of
id12must not count forid123orid1. Looking up the whole id in a dict handles this. - Tie-break is string order, so
"id123"comes before"id7"even though 123 > 7. - Members with 0 mentions still appear in the output.
- Output format is
"id123=2", with no spaces around=.
Patterns to remember#
| Question | Brute force | Trick |
|---|---|---|
| Robot left or right | Simulate every step | Only the net count matters |
| Songs and animations | Check every song against every animation | Dict of first animation per length, then walk divisors up to √L |
| Currency strategy | Rebuild the strategy for each window | Profit change per window, updated with a sliding window |
| Mention statistics | Search each message for each member | One pass per message, a set for de-duplication, a dict for counts |
Comments