想要精通算法和SQL的成长之路 - 无重复字符的最长子串
- 前言
- 一. 无重复字符的最长子串
- 二. 滑动窗口最大值
- 2.1 滑动窗口的基本操作
前言
想要精通算法和SQL的成长之路 - 系列导航
一. 无重复字符的最长子串
原题链接
思路如下:
- 用一个滑动窗口,该窗口区间范围内
[left,right]
的字符串我们认定为不包含重复字符。 - 我们用一个
HashMap
存储每个字符最后一次出现的索引位置,left
。 - 我们遍历字符串,下标是
right
。如果当前遍历的字符存在于我们这个HashMap
中,说明遇到重复字符了。就需要计算当前窗口的无重复字符长度。 - 开始计算当下窗口的长度,更新最大值。同时更新索引。
public int lengthOfLongestSubstring(String s) {
HashMap<Character, Integer> dic = new HashMap<>();
int length = s.length(), left = -1, max = 0;
for (int right = 0; right < length; right++) {
// 遇到重复字符了,更新最后一次出现的下标位置
if (dic.containsKey(s.charAt(right))) {
left = Math.max(left, dic.get(s.charAt(right)));
}
// 更新当前字符出现的最后一次位置
dic.put(s.charAt(right), right);
// 更新最大长度
max = Math.max(max, right - left);
}
return max;
}
二. 滑动窗口最大值
原题链接
2.1 滑动窗口的基本操作
首先说下滑动窗口的一个重要特性:
- 左右侧边界都可以改变,达到一个滑动的效果。
那么在Java
里面,我们可以利用双向队列的特性来代表滑动窗口。
LinkedList<Integer> queue = new LinkedList<>();
这么一个数据结构,它用文字表达就是:
队首 <----> 队尾
相关的操作就是:
- 获取队首元素或者移除:
addFirst | pollFirst
。 - 获取队尾元素或者移除:
addLast | pollLast
。
再回归我们本题,我们在滑动窗口里面存储什么东西比较合适?我们可以存储数组的下标,它有这么几个作用或者特性:
- 我们可以根据下标拿到对应的值。而且下标容易用来计算滑动窗口的大小和位置。
- 我们让滑动窗口存储的下标,让其对应的值,按照从大到小的顺序来排序。这样队首下标对应的元素就是我们需要的滑动窗口的最大值。
我们来看代码,几个重要的点就是:
- 遇到比较大的元素(比队尾的大),就要以此从队尾移除元素。保证下标值对应的数组元素从大到小
- 如果滑动窗口的大小超过了
k
,就要把队首元素移除。 - 只要满足了滑动窗口的大小,就可以把对应的最大值添加到结果集中。
public int[] maxSlidingWindow(int[] nums, int k) {
// 特判
if (nums == null || nums.length < 2) {
return nums;
}
// 双向队列,存储的是数组的下标,数组下标对应的值从大到小存储
LinkedList<Integer> queue = new LinkedList<>();
int[] res = new int[nums.length - k + 1];
for (int i = 0; i < nums.length; i++) {
// 1.把比当前元素值小的以此从队尾排出。队首(从大)--->队尾(到小)
while (!queue.isEmpty() && nums[queue.peekLast()] <= nums[i]) {
queue.pollLast();
}
// 将当前下标加入到队尾
queue.addLast(i);
// 2.若滑动窗口大小超了,把队首元素剔除
if (queue.peek() <= i - k) {
queue.poll();
}
// 3.如果满足了滑动窗口长度,取队首元素对应的值即可
if (i + 1 >= k) {
res[i + 1 - k] = nums[queue.peek()];
}
}
return res;
}