Off-by-one on range boundaries
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Break down a hard problem into reliable checkpoints, edge-case handling, and complexity trade-offs.
Table: app_events
+------------------+----------+ | Column Name | Type | +------------------+----------+ | event_id | int | | user_id | int | | event_timestamp | datetime | | event_type | varchar | | session_id | varchar | | event_value | int | +------------------+----------+ event_id is the unique identifier for this table. event_type can be app_open, click, scroll, purchase, or app_close. session_id groups events within the same user session. event_value represents: for purchase - amount in dollars, for scroll - pixels scrolled, for others - NULL.
Write a solution to identify zombie sessions, sessions where users appear active but show abnormal behavior patterns. A session is considered a zombie session if it meets ALL the following criteria:
30 minutes.5 scroll events.0.20 .Return the result table ordered by scroll_count in descending order, then by session_id in ascending order.
The result format is in the following example.
Example:
Input:
app_events table:
+----------+---------+---------------------+------------+------------+-------------+ | event_id | user_id | event_timestamp | event_type | session_id | event_value | +----------+---------+---------------------+------------+------------+-------------+ | 1 | 201 | 2024-03-01 10:00:00 | app_open | S001 | NULL | | 2 | 201 | 2024-03-01 10:05:00 | scroll | S001 | 500 | | 3 | 201 | 2024-03-01 10:10:00 | scroll | S001 | 750 | | 4 | 201 | 2024-03-01 10:15:00 | scroll | S001 | 600 | | 5 | 201 | 2024-03-01 10:20:00 | scroll | S001 | 800 | | 6 | 201 | 2024-03-01 10:25:00 | scroll | S001 | 550 | | 7 | 201 | 2024-03-01 10:30:00 | scroll | S001 | 900 | | 8 | 201 | 2024-03-01 10:35:00 | app_close | S001 | NULL | | 9 | 202 | 2024-03-01 11:00:00 | app_open | S002 | NULL | | 10 | 202 | 2024-03-01 11:02:00 | click | S002 | NULL | | 11 | 202 | 2024-03-01 11:05:00 | scroll | S002 | 400 | | 12 | 202 | 2024-03-01 11:08:00 | click | S002 | NULL | | 13 | 202 | 2024-03-01 11:10:00 | scroll | S002 | 350 | | 14 | 202 | 2024-03-01 11:15:00 | purchase | S002 | 50 | | 15 | 202 | 2024-03-01 11:20:00 | app_close | S002 | NULL | | 16 | 203 | 2024-03-01 12:00:00 | app_open | S003 | NULL | | 17 | 203 | 2024-03-01 12:10:00 | scroll | S003 | 1000 | | 18 | 203 | 2024-03-01 12:20:00 | scroll | S003 | 1200 | | 19 | 203 | 2024-03-01 12:25:00 | click | S003 | NULL | | 20 | 203 | 2024-03-01 12:30:00 | scroll | S003 | 800 | | 21 | 203 | 2024-03-01 12:40:00 | scroll | S003 | 900 | | 22 | 203 | 2024-03-01 12:50:00 | scroll | S003 | 1100 | | 23 | 203 | 2024-03-01 13:00:00 | app_close | S003 | NULL | | 24 | 204 | 2024-03-01 14:00:00 | app_open | S004 | NULL | | 25 | 204 | 2024-03-01 14:05:00 | scroll | S004 | 600 | | 26 | 204 | 2024-03-01 14:08:00 | scroll | S004 | 700 | | 27 | 204 | 2024-03-01 14:10:00 | click | S004 | NULL | | 28 | 204 | 2024-03-01 14:12:00 | app_close | S004 | NULL | +----------+---------+---------------------+------------+------------+-------------+
Output:
+------------+---------+--------------------------+--------------+ | session_id | user_id | session_duration_minutes | scroll_count | +------------+---------+--------------------------+--------------+ | S001 | 201 | 35 | 6 | +------------+---------+--------------------------+--------------+
Explanation:
The result table is ordered by scroll_count in descending order, then by session_id in ascending order.
Problem summary: Table: app_events +------------------+----------+ | Column Name | Type | +------------------+----------+ | event_id | int | | user_id | int | | event_timestamp | datetime | | event_type | varchar | | session_id | varchar | | event_value | int | +------------------+----------+ event_id is the unique identifier for this table. event_type can be app_open, click, scroll, purchase, or app_close. session_id groups events within the same user session. event_value represents: for purchase - amount in dollars, for scroll - pixels scrolled, for others - NULL. Write a solution to identify zombie sessions, sessions where users appear active but show abnormal behavior patterns. A session is considered a zombie session if it meets ALL the following criteria: The session duration is more than 30 minutes. Has at least 5 scroll events. The click-to-scroll ratio is less than 0.20 . No purchases were made
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: General problem-solving
{"headers":{"app_events":["event_id","user_id","event_timestamp","event_type","session_id","event_value"]},"rows":{"app_events":[[1,201,"2024-03-01 10:00:00","app_open","S001",null],[2,201,"2024-03-01 10:05:00","scroll","S001",500],[3,201,"2024-03-01 10:10:00","scroll","S001",750],[4,201,"2024-03-01 10:15:00","scroll","S001",600],[5,201,"2024-03-01 10:20:00","scroll","S001",800],[6,201,"2024-03-01 10:25:00","scroll","S001",550],[7,201,"2024-03-01 10:30:00","scroll","S001",900],[8,201,"2024-03-01 10:35:00","app_close","S001",null],[9,202,"2024-03-01 11:00:00","app_open","S002",null],[10,202,"2024-03-01 11:02:00","click","S002",null],[11,202,"2024-03-01 11:05:00","scroll","S002",400],[12,202,"2024-03-01 11:08:00","click","S002",null],[13,202,"2024-03-01 11:10:00","scroll","S002",350],[14,202,"2024-03-01 11:15:00","purchase","S002",50],[15,202,"2024-03-01 11:20:00","app_close","S002",null],[16,203,"2024-03-01 12:00:00","app_open","S003",null],[17,203,"2024-03-01 12:10:00","scroll","S003",1000],[18,203,"2024-03-01 12:20:00","scroll","S003",1200],[19,203,"2024-03-01 12:25:00","click","S003",null],[20,203,"2024-03-01 12:30:00","scroll","S003",800],[21,203,"2024-03-01 12:40:00","scroll","S003",900],[22,203,"2024-03-01 12:50:00","scroll","S003",1100],[23,203,"2024-03-01 13:00:00","app_close","S003",null],[24,204,"2024-03-01 14:00:00","app_open","S004",null],[25,204,"2024-03-01 14:05:00","scroll","S004",600],[26,204,"2024-03-01 14:08:00","scroll","S004",700],[27,204,"2024-03-01 14:10:00","click","S004",null],[28,204,"2024-03-01 14:12:00","app_close","S004",null]]}}Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #3673: Find Zombie Sessions
// Auto-generated Java example from py.
class Solution {
public void exampleSolution() {
}
}
// Reference (py):
// # Accepted solution for LeetCode #3673: Find Zombie Sessions
// import pandas as pd
//
//
// def find_zombie_sessions(app_events: pd.DataFrame) -> pd.DataFrame:
// if not pd.api.types.is_datetime64_any_dtype(app_events["event_timestamp"]):
// app_events["event_timestamp"] = pd.to_datetime(app_events["event_timestamp"])
//
// grouped = app_events.groupby(["session_id", "user_id"])
//
// result = grouped.agg(
// session_duration_minutes=(
// "event_timestamp",
// lambda x: (x.max() - x.min()).total_seconds() // 60,
// ),
// scroll_count=("event_type", lambda x: (x == "scroll").sum()),
// click_count=("event_type", lambda x: (x == "click").sum()),
// purchase_count=("event_type", lambda x: (x == "purchase").sum()),
// ).reset_index()
//
// result = result[
// (result["session_duration_minutes"] >= 30)
// & (result["click_count"] / result["scroll_count"] < 0.2)
// & (result["purchase_count"] == 0)
// & (result["scroll_count"] >= 5)
// ]
//
// result = result.sort_values(
// by=["scroll_count", "session_id"], ascending=[False, True]
// ).reset_index(drop=True)
//
// return result[["session_id", "user_id", "session_duration_minutes", "scroll_count"]]
// Accepted solution for LeetCode #3673: Find Zombie Sessions
// Auto-generated Go example from py.
func exampleSolution() {
}
// Reference (py):
// # Accepted solution for LeetCode #3673: Find Zombie Sessions
// import pandas as pd
//
//
// def find_zombie_sessions(app_events: pd.DataFrame) -> pd.DataFrame:
// if not pd.api.types.is_datetime64_any_dtype(app_events["event_timestamp"]):
// app_events["event_timestamp"] = pd.to_datetime(app_events["event_timestamp"])
//
// grouped = app_events.groupby(["session_id", "user_id"])
//
// result = grouped.agg(
// session_duration_minutes=(
// "event_timestamp",
// lambda x: (x.max() - x.min()).total_seconds() // 60,
// ),
// scroll_count=("event_type", lambda x: (x == "scroll").sum()),
// click_count=("event_type", lambda x: (x == "click").sum()),
// purchase_count=("event_type", lambda x: (x == "purchase").sum()),
// ).reset_index()
//
// result = result[
// (result["session_duration_minutes"] >= 30)
// & (result["click_count"] / result["scroll_count"] < 0.2)
// & (result["purchase_count"] == 0)
// & (result["scroll_count"] >= 5)
// ]
//
// result = result.sort_values(
// by=["scroll_count", "session_id"], ascending=[False, True]
// ).reset_index(drop=True)
//
// return result[["session_id", "user_id", "session_duration_minutes", "scroll_count"]]
# Accepted solution for LeetCode #3673: Find Zombie Sessions
import pandas as pd
def find_zombie_sessions(app_events: pd.DataFrame) -> pd.DataFrame:
if not pd.api.types.is_datetime64_any_dtype(app_events["event_timestamp"]):
app_events["event_timestamp"] = pd.to_datetime(app_events["event_timestamp"])
grouped = app_events.groupby(["session_id", "user_id"])
result = grouped.agg(
session_duration_minutes=(
"event_timestamp",
lambda x: (x.max() - x.min()).total_seconds() // 60,
),
scroll_count=("event_type", lambda x: (x == "scroll").sum()),
click_count=("event_type", lambda x: (x == "click").sum()),
purchase_count=("event_type", lambda x: (x == "purchase").sum()),
).reset_index()
result = result[
(result["session_duration_minutes"] >= 30)
& (result["click_count"] / result["scroll_count"] < 0.2)
& (result["purchase_count"] == 0)
& (result["scroll_count"] >= 5)
]
result = result.sort_values(
by=["scroll_count", "session_id"], ascending=[False, True]
).reset_index(drop=True)
return result[["session_id", "user_id", "session_duration_minutes", "scroll_count"]]
// Accepted solution for LeetCode #3673: Find Zombie Sessions
// Rust example auto-generated from py reference.
// Replace the signature and local types with the exact LeetCode harness for this problem.
impl Solution {
pub fn rust_example() {
// Port the logic from the reference block below.
}
}
// Reference (py):
// # Accepted solution for LeetCode #3673: Find Zombie Sessions
// import pandas as pd
//
//
// def find_zombie_sessions(app_events: pd.DataFrame) -> pd.DataFrame:
// if not pd.api.types.is_datetime64_any_dtype(app_events["event_timestamp"]):
// app_events["event_timestamp"] = pd.to_datetime(app_events["event_timestamp"])
//
// grouped = app_events.groupby(["session_id", "user_id"])
//
// result = grouped.agg(
// session_duration_minutes=(
// "event_timestamp",
// lambda x: (x.max() - x.min()).total_seconds() // 60,
// ),
// scroll_count=("event_type", lambda x: (x == "scroll").sum()),
// click_count=("event_type", lambda x: (x == "click").sum()),
// purchase_count=("event_type", lambda x: (x == "purchase").sum()),
// ).reset_index()
//
// result = result[
// (result["session_duration_minutes"] >= 30)
// & (result["click_count"] / result["scroll_count"] < 0.2)
// & (result["purchase_count"] == 0)
// & (result["scroll_count"] >= 5)
// ]
//
// result = result.sort_values(
// by=["scroll_count", "session_id"], ascending=[False, True]
// ).reset_index(drop=True)
//
// return result[["session_id", "user_id", "session_duration_minutes", "scroll_count"]]
// Accepted solution for LeetCode #3673: Find Zombie Sessions
// Auto-generated TypeScript example from py.
function exampleSolution(): void {
}
// Reference (py):
// # Accepted solution for LeetCode #3673: Find Zombie Sessions
// import pandas as pd
//
//
// def find_zombie_sessions(app_events: pd.DataFrame) -> pd.DataFrame:
// if not pd.api.types.is_datetime64_any_dtype(app_events["event_timestamp"]):
// app_events["event_timestamp"] = pd.to_datetime(app_events["event_timestamp"])
//
// grouped = app_events.groupby(["session_id", "user_id"])
//
// result = grouped.agg(
// session_duration_minutes=(
// "event_timestamp",
// lambda x: (x.max() - x.min()).total_seconds() // 60,
// ),
// scroll_count=("event_type", lambda x: (x == "scroll").sum()),
// click_count=("event_type", lambda x: (x == "click").sum()),
// purchase_count=("event_type", lambda x: (x == "purchase").sum()),
// ).reset_index()
//
// result = result[
// (result["session_duration_minutes"] >= 30)
// & (result["click_count"] / result["scroll_count"] < 0.2)
// & (result["purchase_count"] == 0)
// & (result["scroll_count"] >= 5)
// ]
//
// result = result.sort_values(
// by=["scroll_count", "session_id"], ascending=[False, True]
// ).reset_index(drop=True)
//
// return result[["session_id", "user_id", "session_duration_minutes", "scroll_count"]]
Use this to step through a reusable interview workflow for this problem.
Two nested loops check every pair or subarray. The outer loop fixes a starting point, the inner loop extends or searches. For n elements this gives up to n²/2 operations. No extra space, but the quadratic time is prohibitive for large inputs.
Most array problems have an O(n²) brute force (nested loops) and an O(n) optimal (single pass with clever state tracking). The key is identifying what information to maintain as you scan: a running max, a prefix sum, a hash map of seen values, or two pointers.
Review these before coding to avoid predictable interview regressions.
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.