1604. Alert Using Same Key-Card Three or More Times in a One Hour Period
Problem Description
You're given information about employees using key-cards to access their office. Each key-card usage is recorded with the employee's name and the time they used it during a single day.
The security system needs to identify employees who use their key-card three or more times within any one-hour period and generate an alert for them.
Input:
keyName
: A list of strings representing employee nameskeyTime
: A list of strings representing the times when key-cards were used in 24-hour format "HH:MM"keyName[i]
andkeyTime[i]
together represent that employeekeyName[i]
used their key-card at timekeyTime[i]
Output:
- Return a list of unique employee names who triggered the alert (used their key-card 3+ times within a one-hour period)
- The names should be sorted alphabetically in ascending order
Important Notes:
- A one-hour period means any 60-minute window. For example:
"10:00"
to"11:00"
is within a one-hour period (exactly 60 minutes)"22:51"
to"23:52"
is NOT within a one-hour period (61 minutes)
- All times are from the same day
- An employee triggers an alert if ANY three of their key-card uses fall within a 60-minute window
Example scenario:
If an employee uses their key-card at "09:00"
, "09:30"
, and "09:50"
, all three uses are within the one-hour window from "09:00"
to "10:00"
, so this employee would trigger an alert.
Intuition
To find employees who used their key-card 3 or more times within a one-hour window, we need to examine all the times each employee used their card and check if any group of 3 uses falls within 60 minutes.
The key insight is that we need to group all key-card uses by employee name first, since we're looking for patterns in individual employees' usage. This naturally leads us to use a hash table where each employee's name maps to their list of access times.
Once we have all times for each employee, how do we efficiently check if any 3 uses are within a one-hour window? The crucial observation is that if we sort the times chronologically, then any 3 uses within a one-hour window must appear close together in the sorted list. Specifically, if three times are within a one-hour window, the earliest and latest of those three times must be at most 60 minutes apart.
This means we can use a sliding window approach: after sorting an employee's times, we can check consecutive groups of 3 times. For any position i
in the sorted list, we just need to check if times[i+2] - times[i] <= 60
. If this condition is true, then the three times at positions i
, i+1
, and i+2
are all within a one-hour window.
To make the time comparisons easier, we convert the time strings from "HH:MM" format to total minutes since midnight. For example, "09:30"
becomes 9 * 60 + 30 = 570
minutes. This allows us to directly subtract times to find the difference in minutes.
The algorithm becomes straightforward:
- Group all access times by employee name
- For each employee with 3+ accesses, sort their times
- Check if any window of 3 consecutive sorted times spans 60 minutes or less
- Collect all employees who trigger the alert and sort their names alphabetically
Learn more about Sorting patterns.
Solution Approach
The solution uses a hash table combined with sorting to efficiently identify employees who trigger the alert.
Step 1: Group access times by employee
We create a hash table d
using defaultdict(list)
where each key is an employee name and the value is a list of their access times. As we iterate through the input arrays keyName
and keyTime
, we:
- Convert each time string to minutes since midnight:
int(t[:2]) * 60 + int(t[3:])
t[:2]
extracts the hour portiont[3:]
extracts the minute portion- For example,
"09:30"
becomes9 * 60 + 30 = 570
- Append this converted time to the employee's list in the hash table
Step 2: Check each employee for alert conditions
We iterate through each employee in our hash table. For each employee:
- First check if they have at least 3 access times (
len(ts) > 2
). If not, skip them - Sort their access times in ascending order using
ts.sort()
- Use a sliding window to check consecutive groups of 3 times:
for i in range(n - 2): if ts[i + 2] - ts[i] <= 60:
- We iterate from index
0
ton-3
(wheren
is the total number of times) - At each position
i
, we check if the time ati+2
minus the time ati
is within 60 minutes - If we find such a window, we add the employee's name to our answer list and break (since we only need to add each name once)
- We iterate from index
Step 3: Sort and return the result
After checking all employees, we sort the answer list alphabetically using ans.sort()
and return it.
Time Complexity: O(n log n)
where n
is the total number of key-card uses, dominated by sorting operations
Space Complexity: O(n)
for storing the hash table and result list
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Input:
keyName = ["alice", "alice", "alice", "bob", "bob", "bob", "bob"]
keyTime = ["12:01", "12:10", "12:55", "08:00", "08:50", "09:15", "09:45"]
Step 1: Group access times by employee
First, we convert each time to minutes and group by employee name:
- "alice" at "12:01" β 12 * 60 + 1 = 721 minutes
- "alice" at "12:10" β 12 * 60 + 10 = 730 minutes
- "alice" at "12:55" β 12 * 60 + 55 = 775 minutes
- "bob" at "08:00" β 8 * 60 + 0 = 480 minutes
- "bob" at "08:50" β 8 * 60 + 50 = 530 minutes
- "bob" at "09:15" β 9 * 60 + 15 = 555 minutes
- "bob" at "09:45" β 9 * 60 + 45 = 585 minutes
Our hash table d
becomes:
{ "alice": [721, 730, 775], "bob": [480, 530, 555, 585] }
Step 2: Check each employee for alert conditions
For "alice":
- Has 3 access times, so we proceed
- Times are already sorted: [721, 730, 775]
- Check window starting at index 0: 775 - 721 = 54 minutes β€ 60 β
- Alice triggers an alert! Add "alice" to answer list
For "bob":
- Has 4 access times, so we proceed
- Times are already sorted: [480, 530, 555, 585]
- Check window starting at index 0: 555 - 480 = 75 minutes > 60 β
- Check window starting at index 1: 585 - 530 = 55 minutes β€ 60 β
- Bob triggers an alert! Add "bob" to answer list
Step 3: Sort and return the result
Answer list: ["alice", "bob"] After sorting alphabetically: ["alice", "bob"]
Output: ["alice", "bob"]
Both employees triggered the security alert because they each used their key-card 3 or more times within a 60-minute window.
Solution Implementation
1from collections import defaultdict
2from typing import List
3
4class Solution:
5 def alertNames(self, keyName: List[str], keyTime: List[str]) -> List[str]:
6 # Dictionary to store each person's access times
7 name_to_times = defaultdict(list)
8
9 # Convert time strings to minutes and group by name
10 for name, time_str in zip(keyName, keyTime):
11 # Convert "HH:MM" format to total minutes since midnight
12 hours = int(time_str[:2])
13 minutes = int(time_str[3:])
14 total_minutes = hours * 60 + minutes
15 name_to_times[name].append(total_minutes)
16
17 # List to store names that triggered alerts
18 alert_names = []
19
20 # Check each person's access times
21 for name, times in name_to_times.items():
22 num_times = len(times)
23
24 # Only check if person has 3 or more access times
25 if num_times > 2:
26 # Sort times in ascending order
27 times.sort()
28
29 # Check if any 3 consecutive times are within 60 minutes
30 for i in range(num_times - 2):
31 # If the time difference between i and i+2 is <= 60 minutes
32 if times[i + 2] - times[i] <= 60:
33 alert_names.append(name)
34 break # Found an alert, no need to check further
35
36 # Sort names alphabetically before returning
37 alert_names.sort()
38 return alert_names
39
1class Solution {
2 public List<String> alertNames(String[] keyName, String[] keyTime) {
3 // Map to store each person's name and their access times in minutes
4 Map<String, List<Integer>> nameToTimesMap = new HashMap<>();
5
6 // Convert each time entry to minutes and group by person name
7 for (int i = 0; i < keyName.length; i++) {
8 String name = keyName[i];
9 String time = keyTime[i];
10
11 // Convert time from "HH:MM" format to total minutes
12 int hours = Integer.parseInt(time.substring(0, 2));
13 int minutes = Integer.parseInt(time.substring(3));
14 int totalMinutes = hours * 60 + minutes;
15
16 // Add the time to the person's list of access times
17 nameToTimesMap.computeIfAbsent(name, k -> new ArrayList<>()).add(totalMinutes);
18 }
19
20 // List to store names that triggered alerts
21 List<String> alertedNames = new ArrayList<>();
22
23 // Check each person's access times for alerts
24 for (Map.Entry<String, List<Integer>> entry : nameToTimesMap.entrySet()) {
25 List<Integer> accessTimes = entry.getValue();
26 int timeCount = accessTimes.size();
27
28 // Only check if person has 3 or more access times
29 if (timeCount > 2) {
30 // Sort times in ascending order
31 Collections.sort(accessTimes);
32
33 // Check if any 3 consecutive times are within 60 minutes
34 for (int i = 0; i < timeCount - 2; i++) {
35 if (accessTimes.get(i + 2) - accessTimes.get(i) <= 60) {
36 // Alert triggered - add name and stop checking for this person
37 alertedNames.add(entry.getKey());
38 break;
39 }
40 }
41 }
42 }
43
44 // Sort names alphabetically before returning
45 Collections.sort(alertedNames);
46 return alertedNames;
47 }
48}
49
1class Solution {
2public:
3 vector<string> alertNames(vector<string>& keyName, vector<string>& keyTime) {
4 // Map to store each person's access times in minutes
5 unordered_map<string, vector<int>> nameToTimes;
6
7 // Convert all time strings to minutes and group by person name
8 for (int i = 0; i < keyName.size(); ++i) {
9 string name = keyName[i];
10 string timeStr = keyTime[i];
11
12 // Parse time string "HH:MM" to extract hours and minutes
13 int hours, minutes;
14 sscanf(timeStr.c_str(), "%d:%d", &hours, &minutes);
15
16 // Convert to total minutes since midnight
17 int totalMinutes = hours * 60 + minutes;
18 nameToTimes[name].emplace_back(totalMinutes);
19 }
20
21 // List to store names that triggered alerts
22 vector<string> alertedNames;
23
24 // Check each person's access times for security alerts
25 for (auto& [name, times] : nameToTimes) {
26 int timeCount = times.size();
27
28 // Only check if person has 3 or more access times
29 if (timeCount > 2) {
30 // Sort times to check consecutive access patterns
31 sort(times.begin(), times.end());
32
33 // Check if any 3 consecutive accesses are within 60 minutes
34 for (int i = 0; i < timeCount - 2; ++i) {
35 // If third access is within 60 minutes of first access
36 if (times[i + 2] - times[i] <= 60) {
37 alertedNames.emplace_back(name);
38 break; // Only add name once
39 }
40 }
41 }
42 }
43
44 // Sort names alphabetically before returning
45 sort(alertedNames.begin(), alertedNames.end());
46 return alertedNames;
47 }
48};
49
1/**
2 * Finds all people who used their key card 3 or more times within a one-hour period
3 * @param keyName - Array of names corresponding to each key card usage
4 * @param keyTime - Array of times (HH:MM format) when each key card was used
5 * @returns Array of unique names who triggered the alert, sorted alphabetically
6 */
7function alertNames(keyName: string[], keyTime: string[]): string[] {
8 // Map to store each person's access times in minutes
9 const accessTimesByPerson: { [name: string]: number[] } = {};
10
11 // Convert all access records to minutes and group by person
12 for (let i = 0; i < keyName.length; i++) {
13 const name = keyName[i];
14 const timeString = keyTime[i];
15
16 // Convert HH:MM format to total minutes since midnight
17 const hours = parseInt(timeString.slice(0, 2));
18 const minutes = parseInt(timeString.slice(3));
19 const totalMinutes = hours * 60 + minutes;
20
21 // Initialize array if this is the first access for this person
22 if (accessTimesByPerson[name] === undefined) {
23 accessTimesByPerson[name] = [];
24 }
25
26 accessTimesByPerson[name].push(totalMinutes);
27 }
28
29 // Array to store names of people who triggered alerts
30 const alertedNames: string[] = [];
31
32 // Check each person's access times for violations
33 for (const name in accessTimesByPerson) {
34 if (accessTimesByPerson.hasOwnProperty(name)) {
35 const accessTimes = accessTimesByPerson[name];
36
37 // Only check if person has 3 or more accesses
38 if (accessTimes.length > 2) {
39 // Sort times in ascending order
40 accessTimes.sort((a, b) => a - b);
41
42 // Check if any 3 consecutive accesses are within 60 minutes
43 for (let i = 0; i < accessTimes.length - 2; i++) {
44 const timeDifference = accessTimes[i + 2] - accessTimes[i];
45
46 if (timeDifference <= 60) {
47 alertedNames.push(name);
48 break; // Only add name once
49 }
50 }
51 }
52 }
53 }
54
55 // Sort names alphabetically before returning
56 alertedNames.sort();
57 return alertedNames;
58}
59
Time and Space Complexity
Time Complexity: O(n Γ log n)
The algorithm processes n
key-card records through the following steps:
- Initial iteration through all records to parse times and group by names:
O(n)
- For each unique person, their timestamps are sorted. In the worst case, one person has all
n
records, requiringO(n log n)
time for sorting - After sorting, checking consecutive windows of 3 elements takes
O(k)
time per person, wherek
is the number of records for that person - Final sorting of alert names:
O(m log m)
wherem
is the number of unique names, andm β€ n
The dominant operation is the sorting step, giving an overall time complexity of O(n Γ log n)
.
Space Complexity: O(n)
The space usage includes:
- Dictionary
d
storing all timestamps grouped by names:O(n)
space for storingn
timestamp values - Result list
ans
storing names that trigger alerts:O(m)
space wherem
is the number of unique names - The sorting operations use
O(log k)
auxiliary space for each person's timestamps
Since we store all n
input records in the dictionary structure, the overall space complexity is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrect Time Window Interpretation
The Problem:
A common mistake is misunderstanding what "within a one-hour period" means. Some developers might check if the difference between the first and last access is exactly 60 minutes, or they might use a strict less-than comparison (< 60
) instead of less-than-or-equal (<= 60
).
Incorrect Implementation:
# Wrong: Using strict less-than if times[i + 2] - times[i] < 60: # This misses exactly 60-minute windows alert_names.append(name) break
Why It's Wrong:
The problem states that accesses at "10:00" to "11:00" should trigger an alert (exactly 60 minutes). Using < 60
would miss this case.
Correct Solution:
# Correct: Using less-than-or-equal if times[i + 2] - times[i] <= 60: # Includes exactly 60-minute windows alert_names.append(name) break
Pitfall 2: Forgetting to Sort Times Before Checking
The Problem: The access times in the input aren't guaranteed to be in chronological order for each employee. If you don't sort the times before checking for the one-hour window, you might miss valid alert conditions.
Incorrect Implementation:
# Wrong: Not sorting times before checking
for name, times in name_to_times.items():
if len(times) > 2:
# Directly checking without sorting
for i in range(len(times) - 2):
if times[i + 2] - times[i] <= 60:
alert_names.append(name)
break
Why It's Wrong:
If an employee's times are ["09:50", "09:00", "09:30"]
, without sorting, you'd check if 09:30 - 09:50 <= 60, which gives a negative number and fails the check, missing the actual alert condition.
Correct Solution:
# Correct: Sort times before checking
times.sort() # Essential step before checking windows
for i in range(len(times) - 2):
if times[i + 2] - times[i] <= 60:
alert_names.append(name)
break
Pitfall 3: Checking Only Consecutive Pairs Instead of Windows of Three
The Problem: Some might mistakenly check if any two consecutive accesses are within 60 minutes, rather than checking if any three accesses fall within a 60-minute window.
Incorrect Implementation:
# Wrong: Checking pairs instead of windows of three
for i in range(len(times) - 1):
if times[i + 1] - times[i] <= 60: # This checks pairs, not groups of three
alert_names.append(name)
break
Why It's Wrong: The problem requires THREE or more uses within a one-hour period. Two uses within an hour don't trigger an alert.
Correct Solution:
# Correct: Check windows of three accesses
for i in range(len(times) - 2): # Note: -2 to ensure we have three elements
if times[i + 2] - times[i] <= 60: # Check if all three fit in 60 minutes
alert_names.append(name)
break
Which data structure is used to implement recursion?
Recommended Readings
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!