The Sneaky Twins
A medium Python interview practice problem on DataDriven. Write the Python and run it against test cases, with instant feedback.
- Domain
- Python
- Difficulty
- medium
- Seniority
- L4
- Asked in
- Accordion
Problem
A genomics pipeline scans a long lowercase string sequence for every place a short marker could be hiding, even when its bases have been shuffled into a different order. Return the starting index of every window in sequence, equal in length to marker, that uses exactly the same bases as marker with the same counts. Indices come back in increasing order, and a marker longer than sequence yields nothing.
They look different but they are the same inside.
Example
Input:
sequence = "actgcatac", marker = "act"
Output:
[0,4,6]
Worked solution and explanation
What this really is
Strip the costume and this is an anagram scan: every fixed-width window of sequence whose letters are a reshuffle of marker, counts included. The trap is reading 'same bases' too literally or too loosely. Reach for marker in sequence or sequence.find(marker) and you only see the in-order hit, so the visible case comes back [0] instead of [0, 4, 6]. Compare set(window) to set(marker) instead and you accept windows with the right letters in the wrong amounts, handing back start indices that are not the marker at all. Once you are comparing counts, the next thing that separates candidates is not rebuilding those counts at every position: let one base enter and one leave as the window shifts, and each step costs constant work.
Walking the window
Step 1: Fingerprint the marker
Tally how many times each base appears in marker. That tally, not the order of the bases, is the fingerprint every matching window has to reproduce.
Step 2: Prime the first window
Tally the first len(marker) bases of sequence. If that tally already equals the marker's fingerprint, index 0 is a hit before the slide even starts.
Step 3: Shift one base at a time
Sliding the window one step right is exactly two edits: the base coming in gets its count bumped, the base going out gets decremented. Compare the updated tally to the marker's; when they agree, record the window's start index.
The solution
def find_marker_windows(sequence, marker):
if len(marker) > len(sequence):
return []
target = {}
for base in marker:
target[base] = target.get(base, 0) + 1
width = len(marker)
window = {}
for i in range(width):
base = sequence[i]
window[base] = window.get(base, 0) + 1
starts = []
if window == target:
starts.append(0)
for i in range(width, len(sequence)):
enter = sequence[i]
window[enter] = window.get(enter, 0) + 1
leave = sequence[i - width]
window[leave] -= 1
if window[leave] == 0:
del window[leave]
if window == target:
starts.append(i - width + 1)
return startsCommon follow-up questions
- How would you make the per-step comparison strictly O(1) instead of O(26)? (Tests using a single matches counter that tracks how many distinct bases currently have the correct count.)
- What happens if the marker contains a base that never appears in the sequence? (Tests awareness that a base absent from the sequence simply keeps the window tally from ever equaling the target, so no special case is needed.)
- How would you extend this to find near matches that tolerate one swapped base? (Tests relaxing the exact-match condition to allow a bounded count difference.)
- What changes if the sequence is 10 million bases long and arrives as a stream? (Tests recognizing that the algorithm is already linear and streaming-friendly, so length is not the bottleneck.)