Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

1152. Analyze User Website Visit Pattern - Leetcode Solution

Problem Description

The "Analyze User Website Visit Pattern" problem asks us to analyze a list of website visits by users and determine the most commonly visited sequence of three websites (a "3-sequence") among all users.

Each visit is represented by three lists: username, timestamp, and website. Each index i in these lists represents a single visit: username[i] visited website[i] at timestamp[i].

The task is to:

  • For each user, build a sequence of websites they visited, ordered by timestamp.
  • For each user, find all unique 3-sequences (ordered triplets) of websites they visited (no repeats for the same user).
  • Across all users, find the 3-sequence visited by the largest number of users. If multiple sequences have the same highest count, return the lexicographically smallest one.
Constraints:
  • Each user’s visit list is sorted by timestamp.
  • Do not count duplicate 3-sequences for the same user.
  • Return the answer as a list of three website names.

Thought Process

To solve this problem, the first thought is to brute-force all possible 3-sequences for every user, count their occurrences, and then pick the most common one. However, this could be inefficient if users have many visits.

We need to:

  • Group visits by user and sort them by time.
  • Generate all unique 3-sequences per user (no repeats for the same user).
  • Count how many users have each 3-sequence, not how many total times it appears.
  • Handle ties by lexicographical order.
The challenge is in efficiently generating and counting these sequences, especially given the constraints about uniqueness per user and tie-breaking.

Solution Approach

Let's break down the solution step by step:

  1. Organize Visits by User:
    • Combine the input lists into a list of tuples: (username, timestamp, website).
    • Sort all visits by timestamp to ensure correct order.
    • Group each user's visits into a list, preserving the order.
  2. Generate Unique 3-Sequences per User:
    • For each user, generate all combinations of 3 websites in order (using combinations from the ordered list).
    • Use a set to avoid duplicate 3-sequences for the same user.
  3. Count 3-Sequence Occurrences Across Users:
    • For each 3-sequence, keep a mapping from the sequence to a set of users who have that sequence.
    • After processing all users, the count for each sequence is the size of its user set.
  4. Find the Most Common 3-Sequence:
    • Find the sequence(s) with the largest user count.
    • If there is a tie, select the lexicographically smallest one.
Justification:
  • Using a set for each user ensures we only count unique sequences per user.
  • Using a mapping from sequence to user set allows us to count distinct users for each sequence efficiently.
  • Sorting and grouping visits ensures the order of visits is correct for sequence generation.

Example Walkthrough

Sample 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"]


Step-by-step:

  1. Organize visits by user and sort:
    • joe: [("home",1), ("about",2), ("career",3)]
    • james: [("home",4), ("cart",5), ("maps",6), ("home",7)]
    • mary: [("home",8), ("about",9), ("career",10)]
  2. Generate unique 3-sequences per user:
    • joe: ("home","about","career")
    • james: ("home","cart","maps"), ("home","cart","home"), ("home","maps","home"), ("cart","maps","home")
    • mary: ("home","about","career")
  3. Count user occurrences for each sequence:
    • ("home","about","career"): joe, mary (2 users)
    • ("home","cart","maps"): james (1 user)
    • ("home","cart","home"): james (1 user)
    • ("home","maps","home"): james (1 user)
    • ("cart","maps","home"): james (1 user)
  4. Most common sequence:
    • ("home","about","career") is visited by 2 users, others by 1 user.
Answer: ["home","about","career"]

Time and Space Complexity

Brute-force Approach:

  • For each user with n visits, generate all possible ordered 3-sequences: O(n^3) per user.
  • Total time: O(U * n^3), where U is the number of users.
Optimized Approach:
  • For each user, generate all combinations of 3 websites: O(n^3) per user, but for small n this is manageable.
  • Grouping and sorting visits: O(N log N), where N is total number of visits.
  • Counting and finding max: O(K), where K is the number of unique 3-sequences.
  • Total Time Complexity: O(N log N + U * n^3 + K)
  • Space Complexity: O(K * U), for storing users per sequence.
The approach is efficient for reasonable input sizes (as in Leetcode constraints).

Summary

The key to solving the "Analyze User Website Visit Pattern" problem is to:

  • Group and order visits by user,
  • Generate all unique 3-sequences per user,
  • Count how many users have each sequence,
  • And select the most common one, breaking ties lexicographically.
This approach efficiently leverages sets and dictionaries (hash maps) to count distinct user occurrences, ensuring correctness and efficiency. The solution is elegant because it directly models the problem constraints and uses built-in data structures for clarity.

Code Implementation

from collections import defaultdict, Counter
from itertools import combinations

class Solution:
    def mostVisitedPattern(self, username, timestamp, website):
        # Step 1: Combine and sort visits by timestamp
        visits = sorted(zip(timestamp, username, website))
        user_webs = defaultdict(list)
        for t, u, w in visits:
            user_webs[u].append(w)
        
        # Step 2: Generate unique 3-sequences per user
        seq_users = defaultdict(set)
        for user, webs in user_webs.items():
            # All unique 3-sequences for this user
            seqs = set(combinations(webs, 3))
            for seq in seqs:
                seq_users[seq].add(user)
        
        # Step 3: Count and find the most common sequence
        max_count = 0
        res = tuple()
        for seq, users in seq_users.items():
            if len(users) > max_count or (len(users) == max_count and seq < res):
                max_count = len(users)
                res = seq
        return list(res)
      
#include <vector>
#include <string>
#include <map>
#include <set>
#include <algorithm>
using namespace std;

class Solution {
public:
    vector<string> mostVisitedPattern(vector<string>& username, vector<int>& timestamp, vector<string>& website) {
        vector<tuple<int,string,string>> visits;
        int n = username.size();
        for (int i = 0; i < n; ++i) {
            visits.push_back({timestamp[i], username[i], website[i]});
        }
        sort(visits.begin(), visits.end());
        map<string, vector<string>> userWebs;
        for (auto& v : visits) {
            userWebs[get<1>(v)].push_back(get<2>(v));
        }
        map<vector<string>, set<string>> seqUsers;
        for (auto& uw : userWebs) {
            string user = uw.first;
            vector<string>& webs = uw.second;
            int m = webs.size();
            set<vector<string>> seqs;
            for (int i = 0; i < m; ++i)
                for (int j = i+1; j < m; ++j)
                    for (int k = j+1; k < m; ++k)
                        seqs.insert({webs[i], webs[j], webs[k]});
            for (auto& seq : seqs) {
                seqUsers[seq].insert(user);
            }
        }
        int maxCount = 0;
        vector<string> res;
        for (auto& su : seqUsers) {
            if ((int)su.second.size() > maxCount || 
                ((int)su.second.size() == maxCount && su.first < res)) {
                maxCount = su.second.size();
                res = su.first;
            }
        }
        return res;
    }
};
      
import java.util.*;

class Solution {
    public List<String> mostVisitedPattern(String[] username, int[] timestamp, String[] website) {
        int n = username.length;
        List<Visit> visits = new ArrayList<>();
        for (int i = 0; i < n; ++i) {
            visits.add(new Visit(timestamp[i], username[i], website[i]));
        }
        Collections.sort(visits, (a, b) -> Integer.compare(a.time, b.time));
        Map<String, List<String>> userWebs = new HashMap<>();
        for (Visit v : visits) {
            userWebs.computeIfAbsent(v.user, k -> new ArrayList<>()).add(v.web);
        }
        Map<String, Set<String>> seqUsers = new HashMap<>();
        for (String user : userWebs.keySet()) {
            List<String> webs = userWebs.get(user);
            int m = webs.size();
            Set<String> seqs = new HashSet<>();
            for (int i = 0; i < m; ++i)
                for (int j = i+1; j < m; ++j)
                    for (int k = j+1; k < m; ++k)
                        seqs.add(webs.get(i) + "|" + webs.get(j) + "|" + webs.get(k));
            for (String seq : seqs) {
                seqUsers.computeIfAbsent(seq, x -> new HashSet<>()).add(user);
            }
        }
        int maxCount = 0;
        String res = "";
        for (String seq : seqUsers.keySet()) {
            int size = seqUsers.get(seq).size();
            if (size > maxCount || (size == maxCount && seq.compareTo(res) < 0)) {
                maxCount = size;
                res = seq;
            }
        }
        return Arrays.asList(res.split("\\|"));
    }

    static class Visit {
        int time;
        String user;
        String web;
        Visit(int t, String u, String w) {
            time = t; user = u; web = w;
        }
    }
}
      
/**
 * @param {string[]} username
 * @param {number[]} timestamp
 * @param {string[]} website
 * @return {string[]}
 */
var mostVisitedPattern = function(username, timestamp, website) {
    // Step 1: Combine and sort visits by timestamp
    let visits = [];
    for (let i = 0; i < username.length; ++i) {
        visits.push([timestamp[i], username[i], website[i]]);
    }
    visits.sort((a, b) => a[0] - b[0]);
    let userWebs = {};
    for (let [t, u, w] of visits) {
        if (!userWebs[u]) userWebs[u] = [];
        userWebs[u].push(w);
    }
    // Step 2: Generate unique 3-sequences per user
    let seqUsers = {};
    for (let user in userWebs) {
        let webs = userWebs[user];
        let m = webs.length;
        let seqs = new Set();
        for (let i = 0; i < m; ++i)
            for (let j = i+1; j < m; ++j)
                for (let k = j+1; k < m; ++k)
                    seqs.add([webs[i], webs[j], webs[k]].join('|'));
        for (let seq of seqs) {
            if (!seqUsers[seq]) seqUsers[seq] = new Set();
            seqUsers[seq].add(user);
        }
    }
    // Step 3: Count and find the most common sequence
    let maxCount = 0;
    let res = "";
    for (let seq in seqUsers) {
        let size = seqUsers[seq].size;
        if (size > maxCount || (size === maxCount && (res === "" || seq < res))) {
            maxCount = size;
            res = seq;
        }
    }
    return res.split('|');
};