玩转lee219 存在重复元素 lee3无重复字符的最长子串 lee220

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

思路 使用hashset 滑动窗口

维护一个哈希表,里面始终最多包含 k 个元素
当出现重复值时则说明在 k 距离内存在重复元素
每次遍历一个元素则将其加入哈希表中,如果哈希表的大小大于 k,则移除最前面的数字
时间复杂度:O(n),n 为数组长度

public boolean containsNearbyDuplicate(int[] nums, int k) {
    Set<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;
}

lee3

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
标签:滑动窗口
暴力解法时间复杂度较高,会达到 O(n^2)O(n

故而采取滑动窗口的方法降低时间复杂度
定义一个 map 数据结构存储 (k, v),其中 key 值为字符,value 值为字符位置 +1,加 1 表示从字符位置后一个才开始不重复

我们定义不重复子串的开始位置为 start,结束位置为 end
随着 end 不断遍历向后,会遇到与 [start, end] 区间内字符相同的情况,此时将字符作为 key 值,获取其 value 值,并更新 start,此时 [start, end] 区间内不存在重复字符

无论是否更新 start,都会更新其 map 数据结构和结果 ans。

时间复杂度:O(n)O(n)

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length(), ans = 0;
        Map<Character, Integer> map = new HashMap<>();
        for (int end = 0, start = 0; end < n; end++) {
            char alpha = s.charAt(end);
            if (map.containsKey(alpha)) {
                start = Math.max(map.get(alpha), start);
            }
            ans = Math.max(ans, end - start + 1);
            map.put(s.charAt(end), end + 1);
        }
        return ans;
    }
}

lee220 无重复最长字串

给定一个整数数组,判断数组中是否有两个不同的索引 i 和 j,使得 nums [i] 和 nums [j] 的差的绝对值最大为 t,并且 i 和 j 之间的差的绝对值最大为 ķ。
示例 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

思路

如果窗口中维护的元素是有序的,只需要用二分搜索检查条件二是否是满足的就可以了。
利用自平衡二叉搜索树,可以在对数时间内通过 插入 和 删除 来对滑动窗口内元素排序。
算法

方法一真正的瓶颈在于检查第二个条件是否满足需要扫描滑动窗口中所有的元素。因此我们需要考虑的是有没有比全扫描更好的方法。

如果窗口内的元素是有序的,那么用两次二分搜索就可以找到
x+t 和 x−t这两个边界值了。

然而不幸的是,窗口中的元素是无序的。这里有一个初学者非常容易犯的错误,那就是将滑动窗口维护成一个有序的数组。虽然在有序数组中 搜索 只需要花费对数时间,但是为了让数组保持有序,我们不得不做插入和删除的操作,而这些操作是非常不高效的。想象一下,如果你有一个
k大小的有序数组,当你插入一个新元素x的时候。虽然可以在O(logk)
时间内找到这个元素应该插入的位置,但最后还是需要O(k)的时间来将
x插入这个有序数组。因为必须得把当前元素应该插入的位置之后的所有元素往后移一位。当你要删除一个元素的时候也是同样的道理。在删除了下标为i的元素之后,还需要把下标i之后的所有元素往前移一位。因此,这种做法并不会比方法一更好。

**为了能让算法的效率得到真正的提升,我们需要引入一个支持 插入,搜索,删除操作的动态 数据结构,那就是自平衡二叉搜索树。**自平衡 这个词的意思是,这个树在随机进行插入,删除操作之后,它会自动保证树的高度最小。为什么一棵树需要自平衡呢?这是因为在二叉搜索树上的大部分操作需要花费的时间跟这颗树的高度直接相关。
假设这棵树上节点总数为

一个平衡树能把高度维持在h=logn。因此这棵树上支持在 O(h)=O(logn) 时间内完成 插入,搜索,删除 操作。

下面给出整个算法的伪代码:

初始化一颗空的二叉搜索树 set
对于每个元素x,遍历整个数组在 set 上查找大于等于x的最小的数,如果
s−x≤t则返回 true
在 set 上查找小于等于x的最大的数,如果x−g≤t则返回 true
在 set 中插入x如果树的大小超过了k, 则移除最早加入树的那个数。
返回 false我们把大于等于 x 的最小的数 s 当做 x 在 BST 中的后继节点。同样的,我们能把小于等于 x 最大的数 g 当做 x 在 BST 中的前继节点。
s 和 g 这两个数是距离 x 最近的数。因此只需要检查它们和 x 的距离就能知道条件二是否满足了。

class Solution {
    public boolean containsNearbyAlmostDuplicate(int[] nums, int k, int t) {
        TreeSet<Integer> set = new TreeSet<>();
        for (int i = 0; i < nums.length; i++) {
        //  ceiling(E e) 方法返回在这个集合中大于或者等于给定元素的最小元素,如果不存在这样的元素,返回null
        // Find the successor of current element
        Integer s = set.ceiling(nums[i]);
        if (s != null && s <= nums[i] + t) return true;
        //floor(E e) 方法返回在这个集合中小于或者等于给定元素的最大元素,如果不存在这样的元素,返回null
        // Find the predecessor of current element
        Integer g = set.floor(nums[i]);
        if ( g != null && nums[i]<=g+t ) return true;
        set.add(nums[i]);
        if (set.size() > k) {
            set.remove(nums[i - k]);
        }
    }
    return false;
    }
}
发布了98 篇原创文章 · 获赞 2 · 访问量 1652
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 数字20 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览