A repeated value within k positions
You're given an integer array nums and an integer k ≥ 0. Return true
if you can find two different indices i and j with nums[i] == nums[j] and
|i − j| ≤ k, and false if you can't.
Examples
| Input | Output | Why |
|---|---|---|
nums = [1, 2, 3, 1], k = 3 | true | There's a 1 at 0 and another at 3, so they're 3 apart. |
nums = [1, 0, 1, 1], k = 1 | true | The 1s at 2 and 3 are next to each other. |
nums = [1, 2, 3, 1, 2, 3], k = 2 | false | Each of the repeats is 3 apart, and that's too far. |
Constraints
- 1 ≤ n ≤ 10⁵
- −10⁹ ≤ nums[i] ≤ 10⁹
- 0 ≤ k ≤ 10⁵
Hints
Hint 1
Of all the earlier copies of nums[j], you only ever need to look at one of them. Which one is it, and why can you ignore the others?
Hint 2
Keep a map from each value to the last index where you've seen it.
Hint 3
At index j, look up nums[j]. If you last saw it at i and j − i ≤ k, you're done and can return true. If not, write j over its old entry and move on.
Where people lose marks
- Keeping the first index of a value in place of the last. Take a value we see at 0, at 10 and again at 12, with k = 3: if we kept 0 we would say no, and the right answer is yes.
- Putting the value into the map before we look it up, so that every element finds itself at a distance of 0.
- Testing
j − i < kwhere the problem says≤ k. One of the examples has a pair that is exactly k apart, to catch this.
Dry run on [1,2,3,1,2,3],2
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 7nums10j2132132435
1 has not been seen. Record last[1] = 0.
-
Step 2 / 7nums1021j32132435last[1]0
2 has not been seen. Record last[2] = 1.
-
Step 3 / 7nums102132j132435last[1]0last[2]1
3 has not been seen. Record last[3] = 2.
-
Step 4 / 7nums10i213213j2435last[1]0last[2]1last[3]2
1 was last at 0, 3 back: more than k = 2. Overwrite with 3.
-
Step 5 / 7nums1021i321324j35last[1]3last[2]1last[3]2
2 was last at 1, 3 back: more than k = 2. Overwrite with 4.
-
Step 6 / 7nums102132i132435jlast[1]3last[2]4last[3]2
3 was last at 2, 3 back: more than k = 2. Overwrite with 5.
-
Step 7 / 7nums102132132435last[1]3last[2]4last[3]5
No value repeats within 2 positions. Return false.
How to think about it
This is the problem that's usually listed as Contains Duplicate II.
The double loop checks every element against the k before it. That's O(nk), and most of that work is
thrown away. Say nums[j] has turned up before. Of its earlier copies, the one that's nearest to
j is the last one you saw, and if even that copy is more than k away, so are all of the older ones.
So the only thing you need to remember about a value is the last index where you saw it.
We'll sweep once from the left with a map from value to last index. At j we look up
nums[j], and if we find it at i with j − i ≤ k we can return true. If we
don't, we store j as its new last index, on top of the old one.
If you're short of memory, a set of the last k values will do the same job in O(k). You add
nums[j] to it and drop nums[j − k] once it holds more than k values, so you've got a
fixed sliding window with a set for its state.
Complexity. Time O(n) · Space O(n)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def nearby_repeat(nums, k):
last = {} # value -> last index where it appeared
for j, x in enumerate(nums):
if x in last and j - last[x] <= k: # look up first...
return True
last[x] = j # ...then record the newest index
return False#include <unordered_map>
#include <vector>
using namespace std;
bool solve(vector<int> nums, int k) {
unordered_map<int, int> last; // value -> last index
for (int j = 0; j < (int)nums.size(); ++j) {
auto it = last.find(nums[j]);
if (it != last.end() && j - it->second <= k) return true; // look up first...
last[nums[j]] = j; // ...then record
}
return false;
}import java.util.*;
class Solution {
public boolean nearbyRepeat(int[] nums, int k) {
Map<Integer, Integer> last = new HashMap<>(); // value -> last index
for (int j = 0; j < nums.length; j++) {
Integer i = last.get(nums[j]);
if (i != null && j - i <= k) return true; // look up first...
last.put(nums[j], j); // ...then record
}
return false;
}
}function nearbyRepeat(nums, k) {
const last = new Map(); // value -> last index
for (let j = 0; j < nums.length; j++) {
const i = last.get(nums[j]);
if (i !== undefined && j - i <= k) return true; // look up first...
last.set(nums[j], j); // ...then record
}
return false;
}use std::collections::HashMap;
fn solve(nums: &[i32], k: usize) -> bool {
let mut last: HashMap<i32, usize> = HashMap::new(); // value -> last index
for (j, &x) in nums.iter().enumerate() {
if let Some(&i) = last.get(&x) {
if j - i <= k { return true; } // look up first...
}
last.insert(x, j); // ...then record
}
false
}import qualified Data.Map.Strict as M
solve :: [Int] -> Int -> Bool
solve xs k = go M.empty (zip [0..] xs)
where
-- the map holds the last index of every value seen so far
go _ [] = False
go m ((j, x):rest) = case M.lookup x m of
Just i | j - i <= k -> True
_ -> go (M.insert x j m) rest
Variants to try
- Can you do it in O(k) memory? (You'd keep a set of only the last k values, and you'd drop the oldest one as you go.)
- What if you're looking for two values within k positions of each other whose difference is at most t? (You can put the window into buckets of width t + 1, or you can keep it in a sorted set, and either of those will work.)
- If you're told the array is sorted, do you still need a map at all? (You don't. Any copy of a value would have to sit right next to it, so you'd compare neighbours and check that k is at least 1.)
Next problems
Pair with a given sum in an unsorted array
Every pair is n² work. Remembering what you have already passed makes it n, and the order of two lines decides whether it is right.
Open the problem → Medium15 minLongest run of consecutive integers
Sorting gives the answer in O(n log n). A set gives it in O(n), and the reason it is not O(n²) is the interesting part.
Open the problem → Easy15 minCan one word be renamed into another?
You keep a map from the letters of s to the letters of t, and the part people miss is that you need one going back the other way as well.
Open the problem →Write a solution and run it against the real test table.
Keep going
Pair with a given sum in an unsorted array
Every pair is n² work. Remembering what you have already passed makes it n, and the order of two lines decides whether it is right.
Open the problem → Medium15 minLongest run of consecutive integers
Sorting gives the answer in O(n log n). A set gives it in O(n), and the reason it is not O(n²) is the interesting part.
Open the problem → Easy15 minCan one word be renamed into another?
You keep a map from the letters of s to the letters of t, and the part people miss is that you need one going back the other way as well.
Open the problem → Arrays and hashingThe pattern behind it
Trade memory for time: a hash map answers "have I seen this?" in O(1), and most array problems reduce to asking it the right question.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.