LeetCode 1890 - The Latest Login in 2020
The problem provides a database table Logins that records user login events, with columns userid and timestamp. Each combination of (userid, timestamp) is unique, ensuring that every row corresponds to a distinct login.
Difficulty: 🟢 Easy
Topics: Database
Solution
Problem Understanding
The problem provides a database table Logins that records user login events, with columns user_id and time_stamp. Each combination of (user_id, time_stamp) is unique, ensuring that every row corresponds to a distinct login. The task is to determine, for each user who logged in at least once during the year 2020, the latest login timestamp in that year. Users who did not log in during 2020 should be excluded from the output.
In essence, the input is a set of login events across multiple years, and the expected output is a table with two columns: user_id and last_stamp, where last_stamp is the most recent login of that user in 2020. Important points include filtering by year 2020 and correctly identifying the maximum timestamp per user. Edge cases include users with multiple logins in 2020, users with no logins in 2020, and timestamps at the boundary of 2020 (like 2020-01-01 00:00:00 or 2020-12-31 23:59:59).
Approaches
The simplest approach is brute force. One could iterate over all logins, filter out rows not in 2020, and then for each user, track the latest timestamp seen so far. While this is conceptually correct, it requires iterating multiple times or performing a join/aggregation in SQL without leveraging built-in functions, which could be inefficient for large datasets.
The optimal approach leverages SQL aggregation. By filtering logins to only those in 2020, and then grouping by user_id, we can directly apply the MAX() function on time_stamp to find the latest login per user. This takes advantage of SQL's set-based operations and avoids manual iteration.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n) | O(u) | Iterate through all logins, store latest per user in memory (u = number of users) |
| Optimal | O(n) | O(u) | Filter in SQL for 2020, group by user_id, compute MAX per group |
Algorithm Walkthrough
- Filter the
Loginstable to retain only rows wheretime_stampfalls in the year 2020. This ensures we exclude logins from other years. - Group the filtered rows by
user_id. This step collects all login timestamps for each user in 2020 into a logical group. - Apply the aggregation function
MAX(time_stamp)to each group to determine the latest login timestamp for that user. - Return a table with
user_idandlast_stampcolumns, wherelast_stampis the computed maximum timestamp for each user. The order of rows is irrelevant.
Why it works: SQL's GROUP BY with MAX() guarantees that, for each user, the maximum timestamp is selected, and filtering ensures only logins within 2020 are considered. This ensures correctness without missing any users or including invalid years.
Python Solution
Although this is a database problem, we can simulate it in Python using a dictionary to represent groups.
from typing import List, Dict
from datetime import datetime
def latest_login(logins: List[Dict[str, str]]) -> List[Dict[str, str]]:
latest_per_user = {}
for row in logins:
user_id = row['user_id']
timestamp = datetime.strptime(row['time_stamp'], '%Y-%m-%d %H:%M:%S')
if timestamp.year == 2020:
if user_id not in latest_per_user or timestamp > latest_per_user[user_id]:
latest_per_user[user_id] = timestamp
result = [{'user_id': uid, 'last_stamp': latest_per_user[uid].strftime('%Y-%m-%d %H:%M:%S')}
for uid in latest_per_user]
return result
The Python implementation iterates through all login rows, filters for 2020, and maintains a dictionary keyed by user_id storing the latest timestamp. At the end, the dictionary is converted into the expected output format.
Go Solution
package main
import (
"time"
)
type Login struct {
UserID int
TimeStamp string
}
type Result struct {
UserID int
LastStamp string
}
func LatestLogin(logins []Login) []Result {
latestPerUser := make(map[int]time.Time)
for _, login := range logins {
t, _ := time.Parse("2006-01-02 15:04:05", login.TimeStamp)
if t.Year() == 2020 {
if existing, ok := latestPerUser[login.UserID]; !ok || t.After(existing) {
latestPerUser[login.UserID] = t
}
}
}
results := []Result{}
for uid, ts := range latestPerUser {
results = append(results, Result{UserID: uid, LastStamp: ts.Format("2006-01-02 15:04:05")})
}
return results
}
In Go, map[int]time.Time is used to store the latest timestamp per user. Parsing and formatting timestamps follow Go conventions. The logic mirrors the Python implementation.
Worked Examples
Example 1 input table:
| user_id | time_stamp |
|---|---|
| 6 | 2020-06-30 15:06:07 |
| 6 | 2021-04-21 14:06:06 |
| 6 | 2019-03-07 00:18:15 |
| 8 | 2020-02-01 05:10:53 |
| 8 | 2020-12-30 00:46:50 |
| 2 | 2020-01-16 02:49:50 |
| 2 | 2019-08-25 07:59:08 |
| 14 | 2019-07-14 09:00:00 |
| 14 | 2021-01-06 11:59:59 |
Step by step in Python:
-
Initialize
latest_per_user = {}. -
Process each row:
-
User 6: 2020-06-30 -> latest[6] = 2020-06-30
-
User 8: 2020-02-01 -> latest[8] = 2020-02-01
-
User 8: 2020-12-30 -> update latest[8] = 2020-12-30
-
User 2: 2020-01-16 -> latest[2] = 2020-01-16
-
Users 6, 8, 2 remain, 14 is excluded.
-
Output table:
| user_id | last_stamp |
|---|---|
| 6 | 2020-06-30 15:06:07 |
| 8 | 2020-12-30 00:46:50 |
| 2 | 2020-01-16 02:49:50 |
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Single pass over all login records to filter and compare timestamps. |
| Space | O(u) | Store latest timestamp for each user who logged in during 2020. |
Time complexity is linear in the number of login records. Space complexity depends on the number of unique users with 2020 logins.
Test Cases
logins = [
{'user_id': 6, 'time_stamp': '2020-06-30 15:06:07'},
{'user_id': 6, 'time_stamp': '2021-04-21 14:06:06'},
{'user_id': 6, 'time_stamp': '2019-03-07 00:18:15'},
{'user_id': 8, 'time_stamp': '2020-02-01 05:10:53'},
{'user_id': 8, 'time_stamp': '2020-12-30 00:46:50'},
{'user_id': 2, 'time_stamp': '2020-01-16 02:49:50'},
{'user_id': 2, 'time_stamp': '2019-08-25 07:59:08'},
{'user_id': 14, 'time_stamp': '2019-07-14 09:00:00'},
{'user_id': 14, 'time_stamp': '2021-01-06 11:59:59'},
]
result = latest_login(logins)
assert any(r['user_id'] == 6 and r['last_stamp'] == '2020-06-30 15:06:07' for r in result) # latest login for 6
assert any(r['user_id'] == 8 and r['last_stamp'] == '2020-12-30 00:46:50' for r in result) # latest login for 8
assert any(r['user_id'] == 2 and r['last_stamp'] == '2020-01-16 02:49:50' for r in result) # latest login for 2
assert all(r['user_id'] != 14 for r in result) # user 14 should not appear
``