The hard part of this problem is relabeling it. A pattern is just an ordered subsequence of three websites inside a single user’s visit history, and its score counts users — not visits — who exhibit that ordered triple. That reframing dictates the whole algorithm as three steps that never need rethinking:
- Group by user, sort by time. For each user, put their visited sites in timestamp order, because a pattern is about the order visits happen, not about the actual clocks.
- Enumerate every
i<j<ksubsequence per user, deduped per user. Each user contributes the set of all triples of positions in their timeline. The per-user set matters: the same user must never lift a pattern’s score more than once, even if they realize the identical triplet via different index combinations. - Count users per pattern, then pick the winner. The pattern with the most users wins; ties go to the lexicographically smallest triple.
The websites never need to be adjacent in time — the statement literally says they only need to share relative order. That is exactly why step 1 sorts timelines instead of doing any kind of substring search: a pattern is a subsequence, not a contiguous block, so once each user’s history is chronologically ordered, every triple of positions is a candidate.
Brute Force: Direct Subsequence Enumeration
Time O(U·L³)Space O(U·L³)The simplest correct translation of the definition: rebuild each user’s timeline in visit order, enumerate every triple of positions (i, j, k) with i < j < k, and tally how many distinct users produced each ordered triple. Group the tuples first (a plain dict), sort each user’s visits by timestamp (the pattern needs chronological order), and keep a per-user set so the same user cannot inflate a pattern’s score twice. Finally, pick the triple with the highest count, breaking ties lexicographically.
class Solution: def mostVisitedPattern( self, username: list[str], timestamp: list[int], website: list[str] ) -> list[str]: visits = {} for user, ts, site in zip(username, timestamp, website): visits.setdefault(user, []).append((ts, site)) for user in visits: visits[user].sort()
counter = {} for user in visits: sites = [site for _, site in visits[user]] m = len(sites) distinct = set() for i in range(m - 2): for j in range(i + 1, m - 1): for k in range(j + 1, m): distinct.add((sites[i], sites[j], sites[k])) for triple in distinct: counter[triple] = counter.get(triple, 0) + 1
best = min(counter, key=lambda t: (-counter[t], t)) return list(best)Why it is O(U·L³): with U users and L the length of the longest user timeline, enumerating every i<j<k triple costs up to C(L,3) pairs per user, so the innermost loops run O(U·L³) times. The two other forces in the problem are handled by bookkeeping, not by the loops: timestamps vanish once each timeline is sorted, and the within-user set guarantees a user shows up only once per pattern. Space matches time in the worst case because the counter can hold every distinct triple (O(U·L³)).
Dictionary Grouping with Pattern Counter
OptimalTime O(U·L³)Space O(U·L³)The streamlined version of the same idea, organized as the standard “dict-of-users → dict-of-patterns” pipeline. Sort each user’s visits once, generate their distinct triples with a set, and increment a per-pattern user counter — then a single pass over the counter returns the largest-count pattern with the lexicographic tie-break baked into the key. There is no asymptotically faster general approach: in the worst case a solution must examine every triple of every user, so O(U·L³) is unavoidable; this version is optimal in the sense of being the tightest standard implementation of that bound.
from collections import defaultdict
class Solution: def mostVisitedPattern( self, username: list[str], timestamp: list[int], website: list[str] ) -> list[str]: visits = defaultdict(list) for user, ts, site in zip(username, timestamp, website): visits[user].append((ts, site)) for user in visits: visits[user].sort()
cnt = defaultdict(int) for user in visits: sites = [site for _, site in visits[user]] m = len(sites) combos = set() for i in range(m - 2): for j in range(i + 1, m - 1): for k in range(j + 1, m): combos.add((sites[i], sites[j], sites[k])) for c in combos: cnt[c] += 1
return list(min(cnt, key=lambda t: (-cnt[t], t)))Watch the pipeline run to completion on the file example: the per-user timelines are built first, then james’s four triples light up one at a time (reusing his duplicate home from different positions), and finally the scoreboard crowns home, about, career because joe and mary both realize it:
Per-user timelines (sorted by visit time)
Position triple (i < j < k) in active user
Pattern scoreboard (count = number of users)
score counts USERS per distinct triple — the per-user set stops the same user boosting a pattern twice
Group every visit tuple by user, then sort each user's sites by timestamp. A pattern only cares about the ORDER a user visits sites, not the exact times, so 'home, about, career' works whether joe visits them at times 1-3 or 50-900.
Why use a per-user set while still being optimal: the set is what keeps the score a true user-count. Without it, a user whose visits produce the same site triple from two different index combinations (e.g. a timeline like [a, a, b, b] yields (a, a, b) twice) would count that pattern twice — reporting a score no user actually merits. That single set() call is the difference between a correct answer and an overcount, and it does not change the asymptotic bound. The lexicographic tie-break falls out of key=lambda t: (-cnt[t], t): a smaller triple wins whenever two counts tie. Space is O(U·L³) in the worst case, equal to the number of distinct triples stored.