leetcode 查找算法(三)
接着leetcode 查找算法(二)写的
滑动数组
题目描述
给定一个整数数组和一个整数 k,判断数组中是否存在两个不同的索引 i 和 j,使得 nums [i] = nums [j],并且 i 和 j 的差的 绝对值 至多为 k。
示例 1:
输入: nums = [1,2,3,1], k = 3
输出: true示例 2:
输入: nums = [1,0,1,1], k = 1
输出: true示例 3:
输入: nums = [1,2,3,1,2,3], k = 2
输出: false
解题思路
两种方法:1、暴力法 最容易想到 但是效率低
2、采用滑动窗口k 用集合set存放key值,用K的大小限定set.size() 达到更快的查询速度
方法一
public boolean containsNearbyDuplicate(int[] nums, int k) {
// 1 异常处理
if (nums.length < 2) return false;
// 2
int n = nums.length;
int count = 0;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (Math.abs(i - j) <= k && nums[i] == nums[j]) {
count++;
}
}
}
return count > 0;
}
复杂度分析
- 时间复杂度:O(n2)
- 空间复杂度:O(1)
方法二
public boolean containsNearbyDuplicate2(int[] nums, int k) {
if (nums.length < 2) return false;
HashSet<Integer> set = new HashSet<>();
for (int i = 0; i < nums.length; i++) {
if (set.contains(nums[i])) return true;
set.add(nums[i]);
if (set.size() > k) {
set.remove(nums[i - k]);
}
}
return false;
}
复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(k) -- 存储set临时空间
题目描述
在整数数组 nums 中,是否存在两个下标 i 和 j,使得 nums [i] 和 nums [j] 的差的绝对值小于等于 t ,且满足 i 和 j 的差的绝对值也小于等于 ķ 。
如果存在则返回 true,不存在返回 false。
示例 1:
输入: nums = [1,2,3,1], k = 3, t = 0
输出: true示例 2:
输入: nums = [1,0,1,1], k = 1, t = 2
输出: true示例 3:
输入: nums = [1,5,9,1,5,9], k = 2, t = 3
输出: false
解题思路
方法一:暴力算法 通过 耗时703 ms,在所有 Java 提交中击败了5.07%的用户
方法二:散列表 + 桶
方法一
public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
if (nums.length < 2) return false;
int count = 0;
long tt = t; // 不转换为long类型 会溢出
int n = nums.length;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (Math.abs(i - j) <= k && Math.abs(((long)nums[i] - (long)nums[j])) <= tt)
count++;
}
}
return count > 0;
}
复杂度分析
- 时间复杂度:O(n2)
- 空间复杂度:O(1)
方法二
算法思想:参考讨论版
private int sz;//桶大小
public boolean containsNearbyAlmostDuplicate2(int[] nums, int k, int t) {
if(t < 0) return false; //绝对值不可能为负
sz = t + 1;
Map<Integer, Long> map = new HashMap<>(); //哈希表,桶号映射到值,值用Long防止溢出
for(int i = 0; i < nums.length; i++)
{
int key = getID(nums[i]); //得到桶号
if(map.containsKey(key)) // 同一个桶,差的绝对值必然满足要求
return true;
if(map.containsKey(key - 1) && Math.abs(map.get(key - 1) - nums[i]) <= t) //检查相邻桶
return true;
if(map.containsKey(key + 1) && Math.abs(map.get(key + 1) - nums[i]) <= t)
return true;
map.put(key, (long)nums[i]); //将该值放入桶中
if(i >= k) map.remove(getID(nums[i - k]));//为了始终满足下标的差的绝对值要求
}
return false;
}
//计算桶号
private int getID(int num) {
return (num < 0) ? num / sz - 1 : num / sz; //防止0号桶同时存在正负数干扰判断
}
复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(n) -- map存储nums[i]元素,获取桶号
二分查找
题目描述
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。
示例 1:
输入: [1,3,5,6], 5
输出: 2示例 2:
输入: [1,3,5,6], 2
输出: 1示例 3:
输入: [1,3,5,6], 7
输出: 4示例 4:
输入: [1,3,5,6], 0
输出: 0
解题思路
本题既有查找又含插入,采用二分查找解决查找问题,时间复杂度为O(logn),采用一次遍历解决插入问题,最好时间复杂度为O(1)最坏为O(n),故最坏为O(n)
代码
public int searchInsert(int[] nums, int target) {
// 1 当数组为空时,直接将target插入,返回第一个位置
if (nums.length < 1) {
return 0;
}
// 2 当数组中有1个及以上元素时
int n = nums.length;
int left = 0;
int right = n - 1;
while (left <= right) {
int middle = left + (right - left) / 2;
if (nums[middle] == target) {
return middle;
} else if (nums[middle] < target) {
left = middle + 1;
} else if (nums[middle] > target) {
right = middle - 1;
}
}
// 走到这一步说明没查到,就需要进行插入操作
for (int i = 0; i < n; i++) {
if (nums[i] > target) {
return i;
}
}
return n; // 遍历完在尾端插入
}
复杂度分析
- 时间复杂度:O(n) + O(logn)
- 空间复杂度:O(1)
题目描述
给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。
示例 1:
输入: [1,1,2,3,3,4,4,8,8]
输出: 2示例 2:
输入: [3,3,7,7,10,11,11]
输出: 10注意: 您的方案应该在 O(log n)时间复杂度和 O(1)空间复杂度中运行。
解题思路
法一:暴力法
法二:因为是有序数组,所以可以用二分法。
取数组中间的数,当中间数的下标为奇数,说明前后元素的个数为奇数,偶数则剩余个数为偶数。奇数时:当nums[h]等于[h+1],唯一数处于前h-1,反之处于后h+1。偶数时:当nums[h]等于[h+1],唯一数处于后h+2,反之处于前h.(就是要保证剩余查找元素个数奇数)
方法一:暴力法
public int singleNonDuplicate(int[] nums) {
HashMap<Integer, Integer> map = new HashMap<>();
int n = nums.length;
for (int i = 0; i < n; i++) {
map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);
}
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
if (entry.getValue() == 1) {
return entry.getKey();
}
}
return 0; // 题目中假设一定存在 所以此处不会执行
}
复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(n)
方法二:二分法
public int singleNonDuplicate(int[] nums) {
int left = 0;
int right = nums.length - 1;
int middle;
while (left < right) {
middle = left + (right - left) / 2;
if ((middle&1)== 1) { // 下标为奇数
if (nums[middle] == nums[middle+1]) {
right = middle - 1;
} else {
left = middle + 1;
}
} else { // 下标为偶数
if (nums[middle] == nums[middle+1]) {
left = middle + 2;
} else {
right = middle - 1;
}
}
}
return nums[left]; // 返回的一定是左侧的元素
}
复杂度分析
- 时间复杂度:O(logn)
- 空间复杂度:O(1)