Skip to main content
Back to problems
#3832
Hard Database

Find users with persistent behavior patterns

65.6% acceptance
Mar 16, 2026
23
1
Table: activity +--------------+---------+ | Column Name | Type | +--------------+---------+ | user_id | int | | action_date | date | | action | varchar | +--------------+---------+ (user_id, action_date, action) is the primary key (unique value) for this table. Each row represents a user performing a specific action on a given date. Write a solution to identify behaviorally stable users based on the following definition: A user is considered behaviorally stable if there exists a sequence of at least 5 consecutive days such that: The user performed exactly one action per day during that period. The action is the same on all those consecutive days. If a user has multiple qualifying sequences, only consider the sequence with the maximum length. Return the result table ordered by streak_length in descending order, then by user_id in ascending order. The result format is in the following example.

Solution

Pandas
Time O(1)
Space O(1)
LeetCode
solution.pandas
# Table: activity
#
# +--------------+---------+
# | Column Name  | Type    |
# +--------------+---------+
# | user_id      | int     |
# | action_date  | date    |
# | action       | varchar |
# +--------------+---------+
# (user_id, action_date, action) is the primary key (unique value) for this table.
# Each row represents a user performing a specific action on a given date.
#
# Write a solution to identify behaviorally stable users based on the following definition:
#
# A user is considered behaviorally stable if there exists a sequence of at least 5 consecutive days such that:
#
# The user performed exactly one action per day during that period.
#
# The action is the same on all those consecutive days.
#
# If a user has multiple qualifying sequences, only consider the sequence with the maximum length.
#
# Return the result table ordered by streak_length in descending order, then by user_id in ascending order.
#
# The result format is in the following example.
#
# Example 1:
# Input:
# activity table:
# +---------+-------------+--------+
# | user_id | action_date | action |
# +---------+-------------+--------+
# | 1       | 2024-01-01  | login  |
# | 1       | 2024-01-02  | login  |
# | 1       | 2024-01-03  | login  |
# | 1       | 2024-01-04  | login  |
# | 1       | 2024-01-05  | login  |
# | 1       | 2024-01-06  | logout |
# | 2       | 2024-01-01  | click  |
# | 2       | 2024-01-02  | click  |
# | 2       | 2024-01-03  | click  |
# | 2       | 2024-01-04  | click  |
# | 3       | 2024-01-01  | view   |
# | 3       | 2024-01-02  | view   |
# | 3       | 2024-01-03  | view   |
# | 3       | 2024-01-04  | view   |
# | 3       | 2024-01-05  | view   |
# | 3       | 2024-01-06  | view   |
# | 3       | 2024-01-07  | view   |
# +---------+-------------+--------+
# Output:
# +---------+--------+---------------+------------+------------+
# | user_id | action | streak_length | start_date | end_date   |
# +---------+--------+---------------+------------+------------+
# | 3       | view   | 7             | 2024-01-01 | 2024-01-07 |
# | 1       | login  | 5             | 2024-01-01 | 2024-01-05 |
# +---------+--------+---------------+------------+------------+
# Explanation:
# User 1:
# Performed login from 2024-01-01 to 2024-01-05 on consecutive days
# Each day has exactly one action, and the action is the same
# Streak length = 5 (meets minimum requirement)
# The action changes on 2024-01-06, ending the streak
# User 2:
# Performed click for only 4 consecutive days
# Does not meet the minimum streak length of 5
# Excluded from the result
# User 3:
# Performed view for 7 consecutive days
# This is the longest valid sequence for this user
# Included in the result
# The Results table is ordered by streak_length in descending order, then by user_id in ascending order

import pandas as pd

def find_behaviorally_stable_users(activity: pd.DataFrame) -> pd.DataFrame:
  # Count actions per user per day; keep only days with exactly one action
  daily = activity.groupby(['user_id', 'action_date']).agg(
    action_count=('action', 'count'),
    action=('action', 'first')
  ).reset_index()
  daily = daily[daily['action_count'] == 1].copy()

  daily['action_date'] = pd.to_datetime(daily['action_date'])
  daily = daily.sort_values(['user_id', 'action_date'])

  # Detect streak breaks: either different action or non-consecutive date
  daily['prev_date'] = daily.groupby('user_id')['action_date'].shift(1)
  daily['prev_action'] = daily.groupby('user_id')['action'].shift(1)
  daily['is_break'] = (
    (daily['action_date'] - daily['prev_date'] != pd.Timedelta(days=1)) |
    (daily['action'] != daily['prev_action'])
  )
  daily['is_break'] = daily['is_break'].fillna(True)

  daily['streak_id'] = daily.groupby('user_id')['is_break'].cumsum()

  streaks = daily.groupby(['user_id', 'streak_id', 'action']).agg(
    streak_length=('action_date', 'count'),
    start_date=('action_date', 'min'),
    end_date=('action_date', 'max')
  ).reset_index()

  streaks = streaks[streaks['streak_length'] >= 5]

  if streaks.empty:
    return pd.DataFrame(columns=['user_id', 'action', 'streak_length', 'start_date', 'end_date'])

  # Keep only the longest streak per user
  best = streaks.loc[streaks.groupby('user_id')['streak_length'].idxmax()]

  return best[['user_id', 'action', 'streak_length', 'start_date', 'end_date']].sort_values(
    ['streak_length', 'user_id'], ascending=[False, True]
  ).reset_index(drop=True)