Slowest Key: Finding the Longest Key Press
Slowest Key is LeetCode problem 1629. You are given a releaseTimes array and a keysPressed string. Each entry records when a key was released during a typing test. Your job is to figure out which key was held down the longest. If two keys tie for the longest press, you return the one that comes later in the alphabet.
This is a clean single-pass problem that tests your ability to compute derived values from an array and track a running maximum with a tie-breaking rule.
The problem
A keyboard test records n key presses. The i-th key pressed is keysPressed[i], and it was released at time releaseTimes[i]. The duration of the i-th key press is releaseTimes[i] - releaseTimes[i - 1], except for the first key press where the duration is simply releaseTimes[0] (since the test starts at time 0).
Return the key that was pressed for the longest total duration. If there is a tie, return the lexicographically largest key among the tied keys.
Example: releaseTimes = [9, 29, 49, 50], keysPressed = "cbcd". The durations are [9, 20, 20, 1]. Keys "b" and "c" both have duration 20. Since "c" is lexicographically larger, the answer is "c".
The brute force approach
For this problem, the "brute force" is already optimal. You need to look at every key press exactly once to compute its duration, so there is no way to skip any element. A single pass through the arrays is the best you can do.
Some problems have a naive O(n^2) solution that can be improved. This is not one of them. The natural approach of scanning left to right and tracking the best key is already O(n) time and O(1) space. There is nothing to optimize away.
That said, there is still a subtle piece to get right: the tie-breaking rule. You cannot just track the maximum duration. You also need to update your answer when a key matches the current best duration and is lexicographically larger.
The key insight
Compute each duration on the fly as releaseTimes[i] - releaseTimes[i - 1]. Track the best key and best duration as you go. Update when you find a strictly longer duration, or when the duration ties and the current key is lexicographically larger than the stored best. This handles everything in one pass with no extra storage.
Step-by-step walkthrough
Let's trace through the example releaseTimes = [9, 29, 49, 50], keysPressed = "cbcd" to see how the algorithm works.
slowestKey([9, 29, 49, 50], "cbcd"):Step 1: Initialize with key press 0.
releaseTimes[0] - 0 = 9 - 0 = 9best_key = "c", best_duration = 9The first key press starts at time 0. Key "c" was held for 9 time units. This is our starting best.
Step 2: i = 1, key = "b".
releaseTimes[1] - releaseTimes[0] = 29 - 9 = 2020 > 9? Yes, new bestbest_key = "b", best_duration = 20Duration 20 beats the previous best of 9. Update best_key to "b".
Step 3: i = 2, key = "c".
releaseTimes[2] - releaseTimes[1] = 49 - 29 = 2020 > 20? No. 20 == 20 and "c" > "b"? Yes, tie-breakbest_key = "c", best_duration = 20Same duration as the current best. Since "c" is lexicographically larger than "b", we update best_key to "c".
Step 4: i = 3, key = "d".
releaseTimes[3] - releaseTimes[2] = 50 - 49 = 11 > 20? No. 1 == 20? No. Skip.best_key = "c", best_duration = 20Duration 1 is much smaller than 20. No update needed.
Done: all keys processed.
The slowest key is "c". Both "b" and "c" had duration 20, but "c" wins the tie-break because it comes later in the alphabet.
The code
class Solution:
def slowestKey(self, releaseTimes, keysPressed):
best_key = keysPressed[0]
best_duration = releaseTimes[0]
for i in range(1, len(releaseTimes)):
duration = releaseTimes[i] - releaseTimes[i - 1]
if duration > best_duration or (duration == best_duration and keysPressed[i] > best_key):
best_key = keysPressed[i]
best_duration = duration
return best_key
The first key press is handled as a special case during initialization: its duration is releaseTimes[0] because the implicit start time is 0. After that, the loop starts at index 1 and computes each duration as the difference between consecutive release times.
The condition inside the loop handles both cases: a new maximum duration, or a tie that should be broken in favor of the lexicographically larger key. Python's string comparison operators work character by character, so keysPressed[i] > best_key does exactly what we need for single-character strings.
The tie-breaking condition keysPressed[i] > best_key uses Python's built-in string comparison, which is lexicographic. For single characters, this is equivalent to comparing their ASCII values. The letter "c" is greater than "b" because its ASCII code (99) is larger than "b"'s (98).
Complexity analysis
| Metric | Value |
|---|---|
| Time | O(n), one pass through the arrays |
| Space | O(1), only two tracking variables |
You visit each element exactly once and do constant work per element. No auxiliary data structures are needed.
The building blocks
Linear scan with running maximum
The core pattern here is scanning an array left to right while maintaining a running "best" value. You initialize with the first element, then compare each subsequent element against the current best. This exact pattern appears in problems like finding the maximum element, finding the best time to buy and sell stock, and computing maximum subarray sums.
best = initial_value
for element in collection:
if element is better than best:
best = element
The only variation across problems is what "better" means. Here, it means a longer duration or a tie with a lexicographically larger key.
Derived values from consecutive differences
Computing releaseTimes[i] - releaseTimes[i - 1] transforms an array of cumulative timestamps into an array of individual durations. This "consecutive difference" pattern is common in problems involving prefix sums, time intervals, and cumulative data. You often do not need to build the entire difference array. You can compute each difference on the fly, as we do here.
Tie-breaking with secondary criteria
When the primary comparison (duration) results in a tie, you fall back to a secondary criterion (lexicographic order). This compound comparison pattern shows up in sorting problems, priority queues, and any scenario where you rank items by multiple attributes. The key is combining both checks into a single conditional so you handle it in one pass.
Edge cases
- Single key press:
releaseTimes = [5],keysPressed = "a". The loop never runs. You returnkeysPressed[0]directly. - All durations equal: Every key was held for the same amount of time. The tie-breaking rule picks the lexicographically largest key among all of them.
- Same key pressed multiple times: The problem asks for the key with the longest single press, not the total time a key was held across all presses. If
"a"is pressed twice with durations 3 and 4, the relevant duration is 4, not 7. - Last key is the slowest: The algorithm handles this naturally since the loop processes every index including the last one.
- Strictly increasing durations: No ties occur. The last key press has the longest duration and becomes the answer.
From understanding to recall
You now understand the approach: scan left to right, compute each duration as a consecutive difference, and track the best with tie-breaking. The logic is simple. But simple does not mean easy to recall under pressure. The tie-breaking condition has two parts, and it is easy to accidentally use >= instead of > or forget the lexicographic comparison entirely.
Spaced repetition closes that gap. You write the solution from scratch, verify it, and come back tomorrow. Then in three days. Then a week. After a few cycles, the compound comparison if duration > best_duration or (duration == best_duration and keysPressed[i] > best_key) flows from your fingers without hesitation. CodeBricks drills the building blocks (linear scan, consecutive differences, tie-breaking) in isolation so they compose automatically when you encounter them together.
Related posts
- Maximum Average Subarray I - Another single-pass array problem where you compute derived values (subarray averages) and track a running maximum
- Find the Difference - A string comparison problem that uses character-level operations similar to the tie-breaking logic here
- Degree of an Array - Tracking multiple properties (frequency, first/last index) during a single pass through an array, with tie-breaking on a secondary criterion