题目描述
这是 LeetCode 上的「3. 无重复字符的最长子串」,难度为 「Medium」。
给定一个字符串,请你找出其中不含有重复字符的「最长子串」的长度。
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3
示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1
示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
示例 4:
输入: s = ""
输出: 0
提示:
- 0 <= s.length <= 5 *
s由英文字母、数字、符号和空格组成
双指针 + 哈希表解法(滑动窗口)
定义两个指针 i,j,表示当前处理到的子串是 [i,j],[i,j] 始终满足无重复字符。
从前往后进行扫描,同时维护一个哈希表记录 [i,j] 中每个字符出现的次数。
遍历过程中,j 不断自增,同时将第 j 个字符在哈希表中出现的次数加一。
当满足 map.get(r) > 1 代表此前出现过第 j 位对应的字符。此时应该更新 i 的位置(使其右移),直到不满足 map.get(r) > 1 ,代表 [i,j] 恢复满足无重复字符的条件。
同时使用 [i,j] 长度更新最大长度。
class Solution {
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> map = new HashMap<>();
int ans = 0;
for (int i = 0, j = 0; j < s.length(); j++) {
char r = s.charAt(j);
map.put(r, map.getOrDefault(r, 0) + 1);
while (map.get(r) > 1) {
char l = s.charAt(i);
map.put(l, map.get(l) - 1);
i++;
}
ans = Math.max(ans, j - i + 1);
}
return ans;
}
}
11. 盛最多水的容器
给你 n 个非负整数 a1,a2,...,a``n,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明: 你不能倾斜容器。
示例 1:
输入: [1,8,6,2,5,4,8,3,7]
输出: 49
解释: 图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例 2:
输入: height = [1,1]
输出: 1
示例 3:
输入: height = [4,3,2,1,4]
输出: 16
示例 4:
输入: height = [1,2,1]
输出: 2
提示:
-
n == height.length -
2 <= n <= 105 -
0 <= height[i] <= 104
双指针 + 贪心解法
先用两个指针 i 和 j 指向左右边界,然后考虑指针应该怎么移动。
由于构成矩形的面积,取决于 i 和 j 之间的距离(记为 w) 和 i 和 j 下标对应的高度的最小值(记为 h)。
首先无论是 i 指针往右移动还是 j 指针往左移动都会导致 w 变小,所以想要能够枚举到更大的面积,我们应该让 h 在指针移动后变大。
不妨假设当前情况是 height[i] < heigth[j](此时矩形的高度为 height[i]),然后分情况讨论:
- 让
i和j两者高度小的指针移动,即i往右移动: -
- 移动后,i 指针对应的高度变小,即
height[i] > height[i + 1]:w和h都变小了,面积一定变小 - 移动后,i 指针对应的高度不变,即
height[i] = height[i + 1]:w变小,h不变,面积一定变小 - 移动后,i 指针对应的高度变大,即
height[i] < height[i + 1]:w变小,h变大,面积可能会变大
- 移动后,i 指针对应的高度变小,即
- 让
i和j两者高度大的指针移动,即j往左移动: -
- 移动后,j 指针对应的高度变小,即
height[j] > height[j - 1]:w变小,h可能不变或者变小(当height[j - 1] >= height[i]时,h不变;当height[j - 1] < height[i]时,h变小),面积一定变小 - 移动后,j 指针对应的高度不变,即
height[j] = height[j - 1]:w变小,h不变,面积一定变小 - 移动后,j 指针对应的高度变大,即
height[j] < height[j - 1]:w变小,h不变,面积一定变小
- 移动后,j 指针对应的高度变小,即
综上所述,我们只有将高度小的指针往内移动,才会枚举到更大的面积:
class Solution {
public int maxArea(int[] height) {
int n = height.length;
int i = 0, j = n - 1;
int ans = 0;
while (i < j) {
ans = Math.max(ans, (j - i) * Math.min(height[i], height[j]));
if (height[i] < height[j]) {
i++;
} else {
j--;
}
}
return ans;
}
}
1438. 绝对差不超过限制的最长连续子数组
给你一个整数数组 nums ,和一个表示限制的整数 limit,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 limit 。
如果不存在满足条件的子数组,则返回 0 。
示例 1:
输入: nums = [8,2,4,7], limit = 4
输出: 2
解释: 所有子数组如下:
[8] 最大绝对差 |8-8| = 0 <= 4.
[8,2] 最大绝对差 |8-2| = 6 > 4.
[8,2,4] 最大绝对差 |8-2| = 6 > 4.
[8,2,4,7] 最大绝对差 |8-2| = 6 > 4.
[2] 最大绝对差 |2-2| = 0 <= 4.
[2,4] 最大绝对差 |2-4| = 2 <= 4.
[2,4,7] 最大绝对差 |2-7| = 5 > 4.
[4] 最大绝对差 |4-4| = 0 <= 4.
[4,7] 最大绝对差 |4-7| = 3 <= 4.
[7] 最大绝对差 |7-7| = 0 <= 4.
因此,满足题意的最长子数组的长度为 2 。
示例 2:
输入: nums = [10,1,2,4,7,2], limit = 5
输出: 4
解释: 满足题意的最长子数组是 [2,4,7,2],其最大绝对差 |2-7| = 5 <= 5 。
示例 3:
输入: nums = [4,2,2,2,4,4,2,2], limit = 0
输出: 3
提示:
1 <= nums.length <= 10^51 <= nums[i] <= 10^90 <= limit <= 10^9
双指针 解法
上述解法我们是在对 len 进行二分,而事实上我们可以直接使用「双指针」解法找到最大值。
始终让右端点 r 右移,当不满足条件时让 l 进行右移。
同时,还是使用「单调队列」保存我们的区间最值,这样我们只需要对数组进行一次扫描即可得到答案。
class Solution {
public int longestSubarray(int[] nums, int limit) {
int n = nums.length;
int ans = 0;
Deque<Integer> max = new ArrayDeque<>(), min = new ArrayDeque<>();
for (int r = 0, l = 0; r < n; r++) {
while (!max.isEmpty() && nums[r] >= nums[max.peekLast()]) {
max.pollLast();
}
max.addLast(r);
while (!min.isEmpty() && nums[r] <= nums[min.peekLast()]) {
min.pollLast();
}
min.addLast(r);
while (Math.abs(nums[max.peekFirst()] - nums[min.peekFirst()]) > limit) {
l++;
if (max.peekFirst() < l) max.pollFirst();
if (min.peekFirst() < l) min.pollFirst();
}
ans = Math.max(ans, r - l + 1);
}
return ans;
}
}
15. 三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c , 使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意: 答案中不可以包含重复的三元组。
示例 1:
输入: nums = [-1,0,1,2,-1,-4]
输出: [[-1,-1,2],[-1,0,1]]
示例 2:
输入: nums = []
输出: []
示例 3:
输入: nums = [0]
输出: []
提示:
0 <= nums.length <= 3000-105 <= nums[i] <= 105
排序+双指针解法
对数组进行排序,使用三个指针 i、j 和 k 分别代表要找的三个数。
- 通过枚举
i确定第一个数,另外两个指针j,k分别从左边i + 1和右边n - 1往中间移动,找到满足nums[i] + nums[j] + nums[k] == 0的所有组合。 j和k指针的移动逻辑,分情况讨论sum = nums[i] + nums[j] + nums[k]:-
sum> 0:k左移,使sum变小sum< 0:j右移,使sum变大sum= 0:找到符合要求的答案,存起来
由于题目要求答案不能包含重复的三元组,所以在确定第一个数和第二个数的时候,要跳过数值一样的下标(在三数之和确定的情况下,确保第一个数和第二个数不会重复,即可保证三元组不重复)。
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
int n = nums.length;
List<List<Integer>> ans = new ArrayList<>();
for (int i = 0; i < n; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
for (int j = i + 1, k = n - 1; j < k; j++) {
if (j > i + 1 && nums[j] == nums[j - 1]) continue;
while (k - 1 > j && nums[i] + nums[j] + nums[k - 1] >= 0) k--;
if (nums[i] + nums[j] + nums[k] == 0) {
ans.add(Arrays.asList(nums[i], nums[j], nums[k]));
}
}
}
return ans;
}
}
16. 最接近的三数之和
给你一个长度为 n 的整数数组 nums **和 一个目标值 target。请你从 nums **中选出三个整数,使它们的和与 target 最接近。
返回这三个数的和。
假定每组输入只存在恰好一个解。
示例 1:
输入: nums = [-1,2,1,-4], target = 1
输出: 2
解释: 与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
示例 2:
输入: nums = [0,0,0], target = 1
输出: 0
提示:
3 <= nums.length <= 1000-1000 <= nums[i] <= 1000-104 <= target <= 104
排序 + 双指针解法
这道题的思路和 15. 三数之和 区别不大。
对数组进行排序,使用三个指针 i、j 和 k 分别代表要找的三个数。
- 通过枚举
i确定第一个数,另外两个指针j,k分别从左边i + 1和右边n - 1往中间移动,找到满足nums[i] + nums[j] + nums[k]最接近target的唯一解。 j和k指针的移动逻辑,分情况讨论sum = nums[i] + nums[j] + nums[k]:-
sum>target:k左移,使sum变小sum<target:j右移,使sum变大sum=target:找到最符合要求的答案,直接返回
为了更快找到答案,对于相同的 i,可以直接跳过下标。
class Solution {
public int threeSumClosest(int[] nums, int t) {
Arrays.sort(nums);
int ans = nums[0] + nums[1] + nums[2];
int n = nums.length;
for (int i = 0; i < n; i++) {
if (i > 0 && nums[i] == nums[i - 1]) continue;
int j = i + 1, k = n - 1;
while (j < k) {
int sum = nums[i] + nums[j] + nums[k];
if (Math.abs(sum - t) < Math.abs(ans - t)) ans = sum;
if (ans == t) {
return t;
} else if (sum > t) {
k--;
} else if (sum < t) {
j++;
}
}
}
return ans;
}
}
18. 四数之和
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < na、b、c和d互不相同nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
示例 1:
输入: nums = [1,0,-1,0,-2,2], target = 0
输出: [[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
示例 2:
输入: nums = [2,2,2,2,2], target = 8
输出: [[2,2,2,2]]
提示:
1 <= nums.length <= 200-109 <= nums[i] <= 109-109 <= target <= 109
排序 + 双指针解法
这道题的思路和 15. 三数之和(中等)、16. 最接近的三数之和(中等)类似。
对数组进行排序,使用四个指针 i、j 、k 和 p 分别代表要找的四个数。
- 通过枚举
i确定第一个数,枚举j确定第二个数,另外两个指针k和p分别从左边j + 1和右边n - 1往中间移动,找到满足nums[i] + nums[j] + nums[k] + nums[p] == t的所有组合。 k和p指针的移动逻辑,分情况讨论sum = nums[i] + nums[j] + nums[k] + nums[p]:-
sum>target:p左移,使sum变小sum<target:k右移,使sum变大sum=target:将组合加入结果集,k右移继续进行检查
题目要求不能包含重复元素,所以我们要对 i、j 和 k 进行去重,去重逻辑是对于相同的数,只使用第一个。
class Solution {
public List<List<Integer>> fourSum(int[] nums, int t) {
Arrays.sort(nums);
int n = nums.length;
List<List<Integer>> ans = new ArrayList<>();
for (int i = 0; i < n; i++) { // 确定第一个数
if (i > 0 && nums[i] == nums[i - 1]) continue; // 对第一个数进行去重(相同的数只取第一个)
for (int j = i + 1; j < n; j++) { // 确定第二个数
if (j > i + 1 && nums[j] == nums[j - 1]) continue; // 对第二个数进行去重(相同的数只取第一个)
// 确定第三个数和第四个数
int k = j + 1, p = n - 1;
while (k < p) {
// 对第三个数进行去重(相同的数只取第一个)
while (k > j + 1 && k < n && nums[k] == nums[k - 1]) k++;
// 如果 k 跳过相同元素之后的位置超过了 p,本次循环结束
if (k >= p) break;
int sum = nums[i] + nums[j] + nums[k] + nums[p];
if (sum == t) {
ans.add(Arrays.asList(nums[i], nums[j], nums[k], nums[p]));
k++;
} else if (sum > t) {
p--;
} else if (sum < t) {
k++;
}
}
}
}
return ans;
}
}