题目
Given an array of integers and an integer k, find out whether there are two distinct indices i and j in the array such that nums[i] = nums[j] and the absolute difference between i and j is at most k.
Example 1:
Input: nums = [1,2,3,1], k = 3
Output: true
Example 2:
Input: nums = [1,0,1,1], k = 1
Output: true
Example 3:
Input: nums = [1,2,3,1,2,3], k = 2
Output: false
解法思路(一)
- 外层循环游标
i
在[0, nums.length - 1)
间遍历,内层循环在[i + 1, k]
间遍历,暴力解法,当然可以,只不过时间复杂度是 O(N*K);
O(N) 时间复杂度的解法
- 在遍历开始前,把滑动窗口中
k
个元素先存入一个查找表,由于窗口中的元素有可能是重复的,所以使用 HashMap 统计窗口中每个元素出现的频次; - 当游标
i
在遍历[0, nums.length - 1)
的时候,只需要 O(1) 的时间就可以在nums[i]
对应的窗口中判断出是否有与其相等的元素;
解法实现(一)
时间复杂度
- O(N);
空间复杂度
- O(K);
关键词
滑动窗口
哈希表
HashMap
元素可能重复的查询表
窗口滑动的边界判断
实现细节
- 对
k >= nums.length
这个边界的判断,nums.length
指向数组最后一个元素的后面,是数组外的位置,如果k
的范围超过数组的边界,那么滑动窗口就缩小为最长到数组的边界nums.length - 1
; - 至于
k
的精确语义,根据题目中给的例子可以确定;
package leetcode._219;
import java.util.HashMap;
public class Solution219_1 {
public boolean containsNearbyDuplicate(int[] nums, int k) {
HashMap<Integer, Integer> windowK = new HashMap();
for (int i = 1; i <= (k >= nums.length ? nums.length - 1 : k); i++) {
if (windowK.containsKey(nums[i])) {
windowK.put(nums[i], windowK.get(nums[i]) + 1);
} else {
windowK.put(nums[i], 1);
}
}
for (int i = 0; i < nums.length - 1; i++) {
if (windowK.containsKey(nums[i])) {
return true;
} else if(i + k + 1 < nums.length){
if (windowK.containsKey(nums[i + k + 1])) {
windowK.put(nums[i + k + 1], windowK.get(nums[i + k + 1]) + 1);
} else {
windowK.put(nums[i + k + 1], 1);
}
}
if (windowK.containsKey(nums[i+1])) {
windowK.put(nums[i + 1], windowK.get(nums[i + 1]) - 1);
if (windowK.get(nums[i + 1]) == 0) {
windowK.remove(nums[i + 1]);
}
}
}
return false;
}
public static void main(String[] args) {
// int[] arr = {1, 2, 3, 1}; int k = 3;
// int[] arr = {1, 0, 1, 1}; int k = 1;
// int[] arr = {1, 2, 3, 1, 2, 3}; int k = 2;
int[] arr = {1}; int k = 1;
// int[] arr = {99, 99}; int k = 2;
boolean result = (new Solution219_1()).containsNearbyDuplicate(arr, k);
System.out.println(result);
}
}
解法思路(二)
- 解法思路(一)是“找自己”的元素“追着”窗口跑,解法思路(二)是“找自己”的元素“领着”窗口跑;
解法实现(二)
时间复杂度
- O(n);
空间复杂度
- O(k);
关键字
哈希表
HashSet
k
的几何意义
- “找自己”的元素最多在距离自己第 k 个的位置“找到自己”;
- 滑动窗口的长度是
k
;
关于滑动窗口
- 滑动窗口的长度是
k
; - 滑动窗口的起点是“找自己”的后一个元素;
- 滑动窗口的终点是距“找自己”第
k
远的元素; - 如果滑动窗口被撑到最长,也没能在其中找到“自己”,那么就要缩短滑动窗口,让距“找自己”第
k
个元素“出离”滑动窗口,并将“找自己”的元素纳入滑动窗口,与滑动窗口右邻接的元素成为新的“找自己”的元素;
关于滑动窗口中的元素为什么可以用 HashSet 盛装
- 滑动窗口的长度最大为
k
,滑动窗口被撑到最大只有当滑动窗口中没有相等的元素,如果滑动窗口在没有被撑到最大的时候,出现了一个滑动窗口中有的元素想进入滑动窗口,那它是进不去的,因为它已经找到了“自己”,算法运行结束;
package leetcode._219;
import java.util.HashSet;
// 219. Contains Duplicate II
// https://leetcode.com/problems/contains-duplicate-ii/description/
// 时间复杂度: O(n)
// 空间复杂度: O(k)
public class Solution219_2 {
public boolean containsNearbyDuplicate(int[] nums, int k) {
if(nums == null || nums.length <= 1)
return false;
if(k <= 0)
return false;
HashSet<Integer> record = new HashSet<Integer>();
for(int i = 0 ; i < nums.length; i ++){
if(record.contains(nums[i]))
return true;
record.add(nums[i]);
if(record.size() == k + 1)
record.remove(nums[i-k]);
}
return false;
}
private static void printBool(boolean b){
System.out.println(b ? "True" : "False");
}
public static void main(String[] args) {
int[] nums = {1, 2, 1};
int k = 1;
printBool((new Solution219_2()).containsNearbyDuplicate(nums, k));
}
}