Quick Overview

This question evaluates a candidate's ability to design and implement efficient data structures for constant-time operations and to reason about graph connectivity and component counting, falling under the Coding & Algorithms domain and requiring practical implementation with algorithmic complexity reasoning.

Implement Cache and Count Components

Company: Amazon

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: medium

Interview Round: Technical Screen

You are asked to solve two independent coding problems in this session, each followed by a short discussion. This mirrors a 60-minute Amazon SDE coding round: implement a correct, efficient solution, then walk through complexity and edge cases. ### Constraints & Assumptions - **Problem 1 (LRU Cache):** `1 <= capacity`. Keys and values are integers. `get` and `put` are each called many times (assume up to ~$10^5$ operations), so both must run in $O(1)$ average time — a per-operation linear scan will not pass. - **Problem 2 (Connected Groups):** `1 <= n`; nodes are labeled `0` to `n - 1`. `edges[i] = [a, b]` is undirected, `0 <= a, b < n`. The graph may contain self-loops, duplicate edges, and isolated nodes. Assume up to ~$10^5$ nodes and edges. - Single-threaded; you do not need to make either structure thread-safe unless the interviewer asks. ### Part 1: Implement an LRU Cache Design a fixed-capacity **least-recently-used** cache supporting these operations, each in $O(1)$ average time: - `LRUCache(int capacity)`: initialize the cache with a positive capacity. - `int get(int key)`: return the value for `key` if present, else `-1`. A successful `get` marks `key` as the **most recently used** item. - `void put(int key, int value)`: insert or update `key`. If inserting a new key would exceed `capacity`, **evict the least recently used** key first. A `put` also marks `key` as most recently used. ```hint Two structures together No single built-in structure gives you both $O(1)$ keyed lookup *and* $O(1)$ recency reordering. Combine one structure for lookup with another for ordering. ``` ```hint Ordering structure For the recency side, you need a structure that lets you detach an arbitrary element and re-insert it at one end in $O(1)$ — *provided* you already hold a reference to it. Think about which linked structure supports $O(1)$ removal from the middle, and how the lookup structure could hand you that reference directly. ``` ```hint Edge cases to handle Updating an existing key (move, don't duplicate); evicting only when a *new* key overflows capacity; using sentinel head/tail dummy nodes to avoid null checks on every splice. ``` ### Part 2: Count Connected Groups in an Undirected Graph Given an integer `n` (nodes labeled `0` to `n - 1`) and a list of undirected `edges`, return the number of connected components. Isolated nodes count as their own group. ```text Input: n = 5, edges = [[0,1], [1,2], [3,4]] Output: 2 Explanation: Nodes {0,1,2} form one group, and nodes {3,4} form another. ``` ```hint How do you count "reachable groups"? There are two classic families here: one explores the graph directly and counts how many separate explorations it takes to cover every node; the other incrementally merges nodes as you process edges and asks how many independent clusters remain. Decide which fits before committing to data structures. ``` ```hint Why isolated nodes matter A node with no edges is still a component. Initialize as if every node is its own group, then merge — don't only iterate over endpoints that appear in `edges`. ``` ### Clarifying Questions to Ask - For the LRU cache, can `capacity` be `0`, or is it guaranteed positive? Are keys/values always non-negative? - On a `put` that updates an existing key, should it count as a "use" that refreshes recency? (Standard LeetCode semantics: yes.) - For the graph, can `edges` contain self-loops (`[a, a]`) or duplicate edges? Should they be tolerated? - Are node labels guaranteed dense (`0..n-1`), or could they be sparse / non-integer? - What are the expected input sizes — does this need to be $O(1)$ / near-linear, or is a simpler approach acceptable? ### What a Strong Answer Covers - **LRU:** correct $O(1)$ design (hash map + doubly linked list, or an ordered-dict equivalent) with the move-to-front, update-in-place, and eviction logic all correct. - Clean handling of the update-existing-key case without leaking a duplicate node, and eviction triggered only on genuine overflow. - **Graph:** a correct linear-time component count (DFS/BFS or Union-Find) that accounts for isolated nodes, self-loops, and duplicate edges. - Accurate time and space complexity for both ($O(1)$ per cache op; $O(n + m)$ for the graph), stated explicitly. - Communication: stating assumptions, dry-running a small example, and naming trade-offs (e.g. DFS recursion depth vs. iterative/Union-Find). ### Follow-up Questions - How would you make the LRU cache thread-safe, and where would lock contention hurt? Could you shard it? - How would you extend the cache to an **LFU** (least-frequently-used) eviction policy while keeping operations $O(1)$? - For the graph, how would you also return the **size of the largest** connected component, or the sizes of all components? - If the graph edges arrive as a stream (online), which approach scales better, and what does Union-Find's near-constant amortized cost depend on?

Quick Answer: This question evaluates a candidate's ability to design and implement efficient data structures for constant-time operations and to reason about graph connectivity and component counting, falling under the Coding & Algorithms domain and requiring practical implementation with algorithmic complexity reasoning.

Part 1: Implement an LRU Cache

Design a fixed-capacity least-recently-used cache. The cache stores integer keys and integer values. A `get` operation returns the value for a key if present, otherwise `-1`, and makes that key the most recently used. A `put` operation inserts or updates a key-value pair and makes that key the most recently used. If inserting causes the cache to exceed capacity, evict the least recently used key. Implement this through the function `solution(capacity, operations)`, where operations encode cache calls.

Constraints

  • 1 <= capacity <= 100000
  • 0 <= len(operations) <= 200000
  • Each operation is either `[0, key]` or `[1, key, value]`
  • Keys and values fit in signed 32-bit integers
  • Both `get` and `put` should run in O(1) average time

Examples

Input: (2, [[1, 1, 1], [1, 2, 2], [0, 1], [1, 3, 3], [0, 2], [1, 4, 4], [0, 1], [0, 3], [0, 4]])

Expected Output: [1, -1, -1, 3, 4]

Explanation: Key 2 is evicted when key 3 is inserted, and key 1 is evicted when key 4 is inserted.

Input: (2, [[1, 1, 1], [1, 2, 2], [1, 1, 10], [1, 3, 3], [0, 2], [0, 1], [0, 3]])

Expected Output: [-1, 10, 3]

Explanation: Updating key 1 makes it most recently used, so key 2 is evicted when key 3 is inserted.

Hints

  1. Use a hash map to find a cache entry by key in O(1) average time.
  2. Use a doubly linked list to maintain least-recently-used to most-recently-used order, moving nodes whenever they are accessed.

Part 2: Count Connected Groups in an Undirected Graph

You are given `n` nodes labeled from `0` to `n - 1` and a list of undirected edges. Return the number of connected groups, also called connected components, in the graph. Nodes with no edges count as their own connected groups.

Constraints

  • 0 <= n <= 100000
  • 0 <= len(edges) <= 200000
  • Each edge has exactly two integers `[a, b]`
  • For every edge, 0 <= a < n and 0 <= b < n
  • Duplicate edges and self-loops may appear and should not change the answer incorrectly

Examples

Input: (5, [[0, 1], [1, 2], [3, 4]])

Expected Output: 2

Explanation: Nodes {0, 1, 2} form one group and nodes {3, 4} form another.

Input: (4, [])

Expected Output: 4

Explanation: With no edges, every node is isolated and forms its own group.

Hints

  1. You can traverse the graph with DFS or BFS and count how many times you start from an unvisited node.
  2. Alternatively, use Union-Find: start with `n` groups and merge the groups for each edge.

Loading coding console...