DSAPrep
MediumArrays & Hashing

Analyze User Website Visit Pattern

You are given three equal-length arrays: username, website, and timestamp. The tuple [username[i], website[i], timestamp[i]] means user username[i] visited website[i] at time timestamp[i].

A pattern is a list of three websites (not necessarily distinct). For example, ["home", "away", "love"] and ["leetcode", "love", "leetcode"] are both patterns.

The score of a pattern is the number of users who visited all three websites in exactly the order the pattern lists them. The sites do not need to be visited contiguously, only in that relative order within a user's visit history.

Return the pattern with the largest score. If several patterns share the largest score, return the lexicographically smallest one. The websites in a pattern need not be distinct, and a user may exhibit the same pattern at most once for scoring purposes.

Example 1

            Input: username = [joe, joe, joe, james, james, james, james, mary, mary, mary], timestamp = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], website = [home, about, career, home, cart, maps, home, home, about, career]
            Output: [home, about, career]
            

            
                Explanation: joe and mary both visit home, then about, then career, so (home, about, career) has score 2 — the maximum. Every pattern james realizes (e.g. (home, cart, maps), (cart, maps, home)) is realized only by him, so each has score 1. No user visits home three times, so (home, home, home) scores 0.
              
          

Example 2

            Input: username = [ua, ua, ua, ub, ub, ub], timestamp = [1, 2, 3, 4, 5, 6], website = [a, b, a, a, b, c]
            Output: [a, b, a]
            

            
                Explanation: ua visits (a, b, a); ub visits (a, b, c). Both score 1 and no other pattern appears, so the lexicographically smaller one wins: (a, b, a) < (a, b, c).
              
          

Constraints

  • 3 <= username.length <= 50
  • 1 <= username[i].length <= 10
  • timestamp.length == username.length and 1 <= timestamp[i] <= 10^9
  • website.length == username.length and 1 <= website[i].length <= 10
  • username[i] and website[i] consist of lowercase English letters
  • At least one user visited at least three websites
  • All tuples [username[i], timestamp[i], website[i]] are distinct
View original on LeetCode ↗

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:

  1. 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.
  2. Enumerate every i<j<k subsequence 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.
  3. 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:

group + sortuser:

Per-user timelines (sorted by visit time)

joe
homet1aboutt2careert3
james
homet4cartt5mapst6homet7
mary
homet8aboutt9careert10

Position triple (i < j < k) in active user

enumeration complete — no triple forming this step

Pattern scoreboard (count = number of users)

no patterns scored yet

score counts USERS per distinct triple — the per-user set stops the same user boosting a pattern twice

1 / 12
currentcomparingseenresult

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.