3. 无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
自己的做法:遍历每个字符作为起点,去寻找无重复字符的子串,时间复杂度
使用HashSet,空间复杂度
import java.util.HashSet;
class Solution {
public int lengthOfLongestSubstring(String s) {
if (s.equals("")) return 0;
HashSet<Character> hashset = new HashSet<>();
hashset.add(s.charAt(0));
int p = 0;
int q = 1;
int max = -1;
int len = 1;
while (true) {
if (q == s.length()){
if (max < len)
max = len;
break;
}
if (hashset.contains(s.charAt(q))) {
if (max < len)
max = len;
len = 1;
p = p + 1;
q = p + 1;
hashset.clear();
hashset.add(s.charAt(p));
} else {
len++;
hashset.add(s.charAt(q));
q = q + 1;
}
}
return max;
}
}
官方题解:
发现了什么?如果我们依次递增地枚举子串的起始位置,那么子串的结束位置也是递增的!这里的原因在于,假设我们选择字符串中的第k个字符作为起始位置,并且得到了不包含重复字符的最长子串的结束位置为。那么当我们选择第 k+1个字符作为起始位置时,首先从 k+1到的字符显然是不重复的,并且由于少了原本的第k个字符,我们可以尝试继续增大,直到右侧出现了重复字符为止。
滑动窗口,维护在窗口内没有重复字符
import java.util.HashSet;
class Solution {
public int lengthOfLongestSubstring(String s) {
HashSet<Character> hashSet = new HashSet<>();
int rk = -1;
int max = 0;
int len = 0;
for (int i = 0; i < s.length(); i++) {
if (i != 0)
hashSet.remove(s.charAt(i - 1));
while (rk + 1 < s.length() && !hashSet.contains(s.charAt(rk + 1))) {
rk++;
hashSet.add(s.charAt(rk));
}
if (max < hashSet.size())
max = hashSet.size();
}
return max;
}
}
11. 盛最多水的容器
给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器,且 n 的值至少为 2。
图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
双指针
class Solution {
public int maxArea(int[] height) {
int p = 0;
int q = height.length - 1;
int max = 0;
while (p != q) {
if (max < Math.min(height[p], height[q]) * (q - p))
max = Math.min(height[p], height[q]) * (q - p);
if (height[p] < height[q])
p++;
else
q--;
}
return max;
}
}
基本思路
对于数组height第p个元素到第q个元素,寻找最大面积。(不妨设height[p]
也就是说p指针向右移动,才有可能最大面积
问题转换为:对于数组height第p+1个元素到第q个元素,寻找最大面积。
扫描一遍数组,时间复杂度
空间复杂度
证明
移动q指针不会使得面积更大
假设q向左移动到
如果
如果
如果
也就是说移动height较大的指针,不会使得面积变大
只能移动height较小的指针
15. 三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
自己写的bug频出:-(
看了题解,思路如下:
有a b c三个指针,a遍历一遍数组
观察到当
也就是说a一定的时候,b向右移动,c向左移动
a遍历一遍,n个元素,b,c在a之后剩余的元素里面相向而行,总共走了n个元素
时间复杂度
排序需要
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> ans = new ArrayList<>();
for (int first = 0; first < nums.length; first++) {
int third = nums.length - 1;
if (first != 0 && nums[first] == nums[first - 1])
continue;
for (int second = first + 1; second < third; second++) {
if (second > first + 1 && nums[second] == nums[second - 1])
continue;
while (second<third && nums[first] + nums[second] + nums[third] > 0)
third--;
if(second == third) break;
if (nums[first] + nums[second] + nums[third] == 0) {
List<Integer> list = new ArrayList<Integer>();
list.add(nums[first]);
list.add(nums[second]);
list.add(nums[third]);
ans.add(list);
}
}
}
return ans;
}
}
16. 最接近的三数之和
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
示例:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。
基本思路
数组排序
三个指针
class Solution {
public int threeSumClosest(int[] nums, int target) {
Arrays.sort(nums);
int ans = 999999;
for (int first = 0; first < nums.length; first++) {
int second = first + 1;
int third = nums.length - 1;
while (second < third){
int tmp = nums[first] + nums[second] + nums[third];
if (Math.abs(tmp - target) < Math.abs(ans - target)){
ans = tmp;
}
if (tmp > target){
third--;
}
else if (tmp < target){
second++;
}
else{
return tmp;
}
}
}
return ans;
}
}
26. 删除排序数组中的重复项
给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例 1:
给定数组 nums = [1,1,2],
函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,0,1,1,1,2,2,3,3,4],
函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。
你不需要考虑数组中超出新长度后面的元素。
题解
class Solution {
public int removeDuplicates(int[] nums) {
int p = 1;
int q = 1;
while (q < nums.length){
if (nums[q] == nums[q-1])
q++;
else{
nums[p] = nums[q];
p++;
q++;
}
}
return p;
}
}
42. 接雨水
给定n个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
示例:
输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
双指针
class Solution {
public int trap(int[] height) {
if (height.length == 0 || (height.length == 1 && height[0] == 0)) return 0;
int hmax = height[0];
int index = 0;
for (int i = 1; i < height.length; i++) {
if (hmax < height[i]) {
hmax = height[i];
index = i;
}
}
int p = 0;
while (height[p] == 0)
p++;
int q = p + 1;
int water = 0;
while (p < index) {
if (height[q] < height[p]) {
water += height[p] - height[q];
q++;
} else {
p = q;
q = p + 1;
}
}
p = height.length - 1;
while (height[p] == 0)
p--;
q = p - 1;
while (p > index) {
if (height[q] < height[p]) {
water += height[p] - height[q];
q--;
} else {
p = q;
q = p - 1;
}
}
return water;
}
}
时间复杂度
空间复杂度
栈
import java.util.Deque;
import java.util.LinkedList;
class Solution {
public int trap(int[] height) {
if (height.length == 0 || (height.length == 1 && height[0] == 0)) return 0;
Deque<Integer> stack = new LinkedList<>();
int current = 0;
int ans = 0;
while (current < height.length) {
while (!stack.isEmpty() && height[current]>height[stack.peek()]){
int top = stack.pop();
if (stack.isEmpty())
break;
int distance = current - stack.peek() - 1;
int h = Math.min(height[stack.peek()], height[current]) - height[top];
ans += distance*h;
}
stack.push(current);
current++;
}
return ans;
}
}
121. 买卖股票的最佳时机
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
注意:你不能在买入股票前卖出股票。
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
思路
class Solution {
public int maxProfit(int[] prices) {
if (prices.length == 0) return 0;
int minprice = prices[0];
int ans = 0;
for (int i = 1; i < prices.length; i++) {
if (prices[i] < minprice)
minprice = prices[i];
if (ans < prices[i] - minprice)
ans = prices[i] - minprice;
}
return ans;
}
}
遍历一遍,时间复杂度
空间复杂度
209. 长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
示例:
输入:s = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
滑动窗口
维护一个窗口,这个窗口内的和比s大,就向右移动右指针,加入新数,这个窗口内的和比s小,就向右移动左指针,移除旧数
class Solution {
public int minSubArrayLen(int s, int[] nums) {
if (nums.length == 0) return 0;
int i = 0;
int j = 0;
int sum = nums[0];
int min = 999999;
while (i < nums.length && j < nums.length) {
while (sum < s) {
j++;
if (j >= nums.length) break;
sum += nums[j];
}
if (min > j - i + 1 && j < nums.length)
min = j - i + 1;
sum -= nums[i];
i++;
}
if (min == 999999) return 0;
return min;
}
}
左指针遍历一遍,
空间复杂度
从题解看到了前缀和+二分查找的方法
前缀和
对于第i个元素,希望通过二分查找找到bound,使得
自己写的一直通不过,这里摘抄官方题解
class Solution {
public int minSubArrayLen(int s, int[] nums) {
int n = nums.length;
if (n == 0) {
return 0;
}
int ans = Integer.MAX_VALUE;
int[] sums = new int[n + 1];
// 为了方便计算,令 size = n + 1
// sums[0] = 0 意味着前 0 个元素的前缀和为 0
// sums[1] = A[0] 前 1 个元素的前缀和为 A[0]
// 以此类推
for (int i = 1; i <= n; i++) {
sums[i] = sums[i - 1] + nums[i - 1];
}
for (int i = 1; i <= n; i++) {
int target = s + sums[i - 1];
int bound = Arrays.binarySearch(sums, target);
if (bound < 0) {
bound = -bound - 1;
}
if (bound <= n) {
ans = Math.min(ans, bound - (i - 1));
}
}
return ans == Integer.MAX_VALUE ? 0 : ans;
}
}