leetcode 查找算法(三)

接着leetcode 查找算法(二)写的


滑动数组

219 存在重复元素II

题目描述

给定一个整数数组和一个整数 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临时空间

220. 存在重复元素 III

题目描述

在整数数组 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]元素,获取桶号

二分查找

35 搜索插入位置

题目描述

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

你可以假设数组中无重复元素。

示例 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)

540 有序数组中的单一元素

题目描述

给定一个只包含整数的有序数组,每个元素都会出现两次,唯有一个数只会出现一次,找出这个数。

示例 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)
posted @ 2020-08-30 18:44  BMDACM  阅读(246)  评论(0编辑  收藏  举报