Skip to content

Commit 87446bf

Browse files
committed
Complete Chapter 10 (Hash Tables & Sets): two sum, duplicates, consecutive sequence, sudoku, first unique, hash map design, roman numerals
1 parent 43f3dc4 commit 87446bf

10 files changed

Lines changed: 1497 additions & 0 deletions

File tree

‎CodingInterviewFightClub/src/SUMMARY.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,13 @@
114114
- [9.5 Longest Common Prefix](ch09-strings/longest-common-prefix.md)
115115
- [9.6 Reverse Words In A String](ch09-strings/reverse-words-in-a-string.md)
116116
- [9.7 Validate IP Address](ch09-strings/validate-ip-address.md)
117+
118+
- [10. Hash Tables & Sets](ch10-hash-tables/index.md)
119+
- [10.0 Pattern Primer: O(1) Lookup, Three Moves](ch10-hash-tables/pattern-primer.md)
120+
- [10.1 Two Sum](ch10-hash-tables/two-sum.md)
121+
- [10.2 Contains Duplicate II](ch10-hash-tables/contains-duplicate-ii.md)
122+
- [10.3 Longest Consecutive Sequence](ch10-hash-tables/longest-consecutive-sequence.md)
123+
- [10.4 Valid Sudoku](ch10-hash-tables/valid-sudoku.md)
124+
- [10.5 First Unique Character](ch10-hash-tables/first-unique-character.md)
125+
- [10.6 Design HashMap](ch10-hash-tables/design-hash-map.md)
126+
- [10.7 Roman To Integer](ch10-hash-tables/roman-to-integer.md)
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# 10.2 Contains Duplicate II
2+
3+
> **Source:** [`src/main/kotlin/array/hashtable/ContainsDuplicate_II.kt`](https://github.com/arpanpathak/AdvancedAlgorithmPatterns/blob/main/src/main/kotlin/array/hashtable/ContainsDuplicate_II.kt)
4+
> **Pattern:** value -> last index · **Core page**
5+
6+
## The Problem
7+
8+
Given an integer array `nums` and an integer `k`, return `true` if there exist two **distinct** indices `i` and `j` with `nums[i] == nums[j]` and `|i - j| <= k`.
9+
10+
- Constraints: $1 \le n \le 10^5$; $0 \le k \le 10^5$.
11+
12+
## Examples
13+
14+
```
15+
Input: nums = [1,2,3,1], k = 3 -> Output: true (the two 1s are 3 apart)
16+
Input: nums = [1,0,1,1], k = 1 -> Output: true (adjacent 1s)
17+
Input: nums = [1,2,3,1,2,3], k = 2 -> Output: false (nearest equal pair is 3 apart)
18+
```
19+
20+
## Intuition — for each value, remember only its *most recent* occurrence
21+
22+
For each index `i`, the question is: *"did this exact value appear within the last k positions?"* That's a **sliding-window membership test** with a twist — the window is by *position*, and we need the *distance* to the previous occurrence.
23+
24+
The map stores `value -> index of its most recent occurrence`. For each `nums[i]`:
25+
26+
- if it was seen before at `prev` and `i - prev <= k` → answer is `true`;
27+
- regardless, **update** `map[nums[i]] = i` — the current index becomes the new "most recent".
28+
29+
**Why store only the most recent index?** Any occurrence older than the most recent is *farther away* than it is. If `i - mostRecent > k`, then `i - anyOlder >= i - mostRecent > k` too — an older occurrence can never satisfy the distance bound if the newest one can't. The "nearest duplicate" is always the most recent one, so one slot per value is enough. (This is the same "keep the best-so-far" compression as the [Chapter 3](../ch03-arrays/index.md) two-pointer minimums.)
30+
31+
**The `k`-window alternative:** a sliding `HashSet` of size `k` (add, and when `i > k`, remove `nums[i-k-1]`) also works and is the more *literal* translation of "window of size k". The map version is one structure and stores strictly less when values repeat.
32+
33+
## Approach 1 — Check all pairs (too slow)
34+
35+
For each value, compare all pairs of its occurrences: $O(n^2)$ in the worst case (all values equal).
36+
37+
## Approach 2 — Value-to-last-index map (the repo's version, optimal)
38+
39+
```kotlin
40+
class ContainsDuplicate_II {
41+
/**
42+
* @param nums input array
43+
* @param k max allowed distance between equal values
44+
* @return true iff some value repeats within distance k
45+
*/
46+
fun containsNearbyDuplicate(nums: IntArray, k: Int): Boolean {
47+
val map = mutableMapOf<Int, Int>() // value -> most recent index
48+
49+
for (i in nums.indices) {
50+
val previousIndex = map[nums[i]]
51+
52+
previousIndex?.let {
53+
if (i - it <= k) return true // within distance k
54+
}
55+
map[nums[i]] = i // refresh the most recent index
56+
}
57+
return false
58+
}
59+
}
60+
```
61+
62+
```java
63+
import java.util.*;
64+
65+
public class ContainsDuplicateII {
66+
/**
67+
* @param nums input array
68+
* @param k max allowed distance between equal values
69+
* @return true iff some value repeats within distance k
70+
*/
71+
public boolean containsNearbyDuplicate(int[] nums, int k) {
72+
Map<Integer, Integer> map = new HashMap<>(); // value -> most recent index
73+
74+
for (int i = 0; i < nums.length; i++) {
75+
Integer prev = map.get(nums[i]);
76+
if (prev != null && i - prev <= k) return true;
77+
map.put(nums[i], i);
78+
}
79+
return false;
80+
}
81+
}
82+
```
83+
84+
```cpp
85+
#include <unordered_map>
86+
#include <vector>
87+
88+
class ContainsDuplicateII {
89+
public:
90+
/**
91+
* @param nums input array
92+
* @param k max allowed distance between equal values
93+
* @return true iff some value repeats within distance k
94+
*/
95+
bool containsNearbyDuplicate(std::vector<int>& nums, int k) {
96+
std::unordered_map<int, int> map; // value -> most recent index
97+
98+
for (int i = 0; i < (int)nums.size(); i++) {
99+
if (map.count(nums[i]) && i - map[nums[i]] <= k) return true;
100+
map[nums[i]] = i;
101+
}
102+
return false;
103+
}
104+
};
105+
```
106+
107+
```python
108+
def contains_nearby_duplicate(nums: list[int], k: int) -> bool:
109+
"""
110+
@param nums: input array
111+
@param k: max allowed distance between equal values
112+
@return: true iff some value repeats within distance k
113+
"""
114+
last = {} # value -> most recent index
115+
for i, num in enumerate(nums):
116+
if num in last and i - last[num] <= k:
117+
return True
118+
last[num] = i
119+
return False
120+
```
121+
122+
```rust
123+
use std::collections::HashMap;
124+
125+
impl Solution {
126+
/// @param nums input array
127+
/// @param k max allowed distance between equal values
128+
/// @return true iff some value repeats within distance k
129+
pub fn contains_nearby_duplicate(nums: Vec<i32>, k: i32) -> bool {
130+
let mut last: HashMap<i32, usize> = HashMap::new(); // value -> most recent index
131+
132+
for (i, &num) in nums.iter().enumerate() {
133+
if let Some(&prev) = last.get(&num) {
134+
if i - prev <= k as usize { return true; }
135+
}
136+
last.insert(num, i);
137+
}
138+
false
139+
}
140+
}
141+
```
142+
143+
## Dry run
144+
145+
**Input:** `nums = [1,0,1,1]`, `k = 1`.
146+
147+
```
148+
map = {}
149+
i=0 (1): not seen. store 1 -> 0. map={1:0}
150+
i=1 (0): not seen. store 0 -> 1. map={1:0, 0:1}
151+
i=2 (1): seen at 0: 2-0 = 2 > k=1 -> no. refresh: 1 -> 2. map={1:2, 0:1}
152+
i=3 (1): seen at 2: 3-2 = 1 <= k=1 -> return true ✓
153+
```
154+
155+
The refresh at `i=2` is the whole trick: the duplicate at index 0 is now *irrelevant* — index 2 is closer to any future 1. The stale `1 -> 0` entry is overwritten, so the distance check always runs against the nearest candidate.
156+
157+
## Complexity
158+
159+
**Time.** One pass, $O(1)$ map ops:
160+
161+
$$
162+
T(n) = O(n)
163+
$$
164+
165+
**Space.** One entry per distinct value:
166+
167+
$$
168+
S(n) = O(n)
169+
$$
170+
171+
## Variants & follow-ups
172+
173+
- **Contains Duplicate I / III** — I is "any duplicate at all" (a plain set); III is "distance AND value-difference bounds" (a sorted structure like a `TreeSet` of window values — the $O(\log k)$ per-op version of this page).
174+
- **Longest Substring Without Repeating Characters** (`src/main/kotlin/string/sliding_window/`) — the same "value -> last index" map, but tracking the *window's left boundary* instead of returning early.
175+
- **Degree Of An Array** (`src/main/kotlin/array/hashtable/DegreeOfAnArray.kt`) — value -> first/last occurrence maps; the "state per value" move extended to two slots per value.
176+
- **Interview follow-up:** "Why does overwriting with the latest index never lose the answer?" Distance to the *nearest* duplicate is always via the *most recent* occurrence — any older occurrence is strictly farther. So the single-slot-per-value compression is exact, not approximate.

0 commit comments

Comments
 (0)