算法笔记-双指针/窗口滑动

image.png

窗口滑动

窗口滑动适用一些字符串、数组的题目

一般采用队列来解决问题

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

算法框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//滑动窗口算法框架(从左向右)
void sliding(string s) {
int left = 0, right = 0;//左右指针
while (right < s.size()) {
// c 是将移入窗口的字符
char c = s[right];
// 右移窗口
right++;
// 判断左侧窗口是否要收缩
while (window needs shrink) {
// d 是将移出窗口的字符
char d = s[left];
// 左移窗口
left++;
// 进行窗口内数据的一系列更新
...
}
}
}
//窗口滑动从两边向内收缩。题目:11. 盛最多水的容器
public int maxArea(int[] height) {
int n = height.length;
int ret = 0,left = 0,right = n-1;
while (left<right){
int area = Math.min(height[left],height[right])*(right-left);
ret = Math.max(area,ret);
if (height[left]<height[right])left++;
else right--;
}
return ret;
}

对于一些字符串的替换操作可以考虑将其转为数组进行,如果直接进行字符串拼接会比较耗性能

相关题目

无重复字符的最长子串(中等)

解题思路

窗口滑动,

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Solution {
public int lengthOfLongestSubstring(String s) {
//双指针窗口滑动
int left = 0, right = 0, ret = 0;
while (right < s.length()) {
String str = s.substring(left, right + 1);
ret = length > str.length() ? ret : str.length();//记录长度
right++;//增大窗口
while (right < s.length() && str.indexOf(s.charAt(right)) != -1) {
left++;//缩小窗口
str = s.substring(left, right);
}
}
return length;
}
}

/**
* 优化速度
*/
class Solution {
public int lengthOfLongestSubstring(String s) {
// 记录字符上一次出现的位置
int[] last = new int[128];
for(int i = 0; i < 128; i++) {
last[i] = -1;
}
int n = s.length();

int res = 0;
int start = 0; // 窗口开始位置
for(int i = 0; i < n; i++) {
int index = s.charAt(i);
start = Math.max(start, last[index] + 1);
res = Math.max(res, i - start + 1);
last[index] = i;
}

return res;
}
}

字符串转换整数 (atoi)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class Solution {
public int myAtoi(String str) {
int len = str.length();
// str.charAt(i) 方法回去检查下标的合法性,一般先转换成字符数组
char[] charArray = str.toCharArray();
// 1、去除前导空格
int index = 0;
while (index < len && charArray[index] == ' ')index++;
// 2、如果已经遍历完成(针对极端用例 " ")
if (index == len) return 0;
// 3、如果出现符号字符,仅第 1 个有效,并记录正负
int sign = 1;
char firstChar = charArray[index];
if (firstChar == '+') {
index++;
} else if (firstChar == '-') {
index++;
sign = -1;
}
// 4、将后续出现的数字字符进行转换
// 不能使用 long 类型,这是题目说的
int res = 0;
while (index < len) {
char currChar = charArray[index];
// 4.1 先判断不合法的情况
if (currChar > '9' || currChar < '0') break;
// 题目中说:环境只能存储 32 位大小的有符号整数,因此,需要提前判:断乘以 10 以后是否越界
if (res > Integer.MAX_VALUE / 10 || (res == Integer.MAX_VALUE / 10 && (currChar - '0') > Integer.MAX_VALUE % 10)) return Integer.MAX_VALUE;
if (res < Integer.MIN_VALUE / 10 || (res == Integer.MIN_VALUE / 10 && (currChar - '0') > -(Integer.MIN_VALUE % 10))) return Integer.MIN_VALUE;
// 4.2 合法的情况下,才考虑转换,每一步都把符号位乘进去
res = res * 10 + sign * (currChar - '0');
index++;
}
return res;
}
}

翻转字符串里的单词

image.png

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
class Solution {
public String reverseWords(String s) {
StringBuilder sb = trimSpaces(s);

// 翻转字符串
reverse(sb, 0, sb.length() - 1);

// 翻转每个单词
reverseEachWord(sb);

return sb.toString();
}

public StringBuilder trimSpaces(String s) {
int left = 0, right = s.length() - 1;
// 去掉字符串开头的空白字符
while (left <= right && s.charAt(left) == ' ') {
++left;
}

// 去掉字符串末尾的空白字符
while (left <= right && s.charAt(right) == ' ') {
--right;
}

// 将字符串间多余的空白字符去除
StringBuilder sb = new StringBuilder();
while (left <= right) {
char c = s.charAt(left);

if (c != ' ') {
sb.append(c);
} else if (sb.charAt(sb.length() - 1) != ' ') {
sb.append(c);
}

++left;
}
return sb;
}

public void reverse(StringBuilder sb, int left, int right) {
while (left < right) {
char tmp = sb.charAt(left);
sb.setCharAt(left++, sb.charAt(right));
sb.setCharAt(right--, tmp);
}
}

public void reverseEachWord(StringBuilder sb) {
int n = sb.length();
int start = 0, end = 0;

while (start < n) {
// 循环至单词的末尾
while (end < n && sb.charAt(end) != ' ') {
++end;
}
// 翻转单词
reverse(sb, start, end - 1);
// 更新start,去找下一个单词
start = end + 1;
++end;
}
}
}

最小覆盖子串(hard)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
class Solution {
public String minWindow(String s, String t) {
if (s == null || s == "" || t == null || t == "" || s.length() < t.length()) {
return "";
}
//维护两个数组,记录已有字符串指定字符的出现次数,和目标字符串指定字符的出现次数
//ASCII表总长128
int[] need = new int[128];
int[] have = new int[128];

//将目标字符串指定字符的出现次数记录
for (int i = 0; i < t.length(); i++) {
need[t.charAt(i)]++;
}

//分别为左指针,右指针,最小长度(初始值为一定不可达到的长度)
//已有字符串中目标字符串指定字符的出现总频次以及最小覆盖子串在原字符串中的起始位置
int left = 0, right = 0, min = s.length() + 1, count = 0, start = 0;
while (right < s.length()) {
char r = s.charAt(right);
//说明该字符不被目标字符串需要,此时有两种情况
// 1.循环刚开始,那么直接移动右指针即可,不需要做多余判断
// 2.循环已经开始一段时间,此处又有两种情况
// 2.1 上一次条件不满足,已有字符串指定字符出现次数不满足目标字符串指定字符出现次数,那么此时
// 如果该字符还不被目标字符串需要,就不需要进行多余判断,右指针移动即可
// 2.2 左指针已经移动完毕,那么此时就相当于循环刚开始,同理直接移动右指针
if (need[r] == 0) {
right++;
continue;
}
//当且仅当已有字符串目标字符出现的次数小于目标字符串字符的出现次数时,count才会+1
//是为了后续能直接判断已有字符串是否已经包含了目标字符串的所有字符,不需要挨个比对字符出现的次数
if (have[r] < need[r]) {
count++;
}
//已有字符串中目标字符出现的次数+1
have[r]++;
//移动右指针
right++;
//当且仅当已有字符串已经包含了所有目标字符串的字符,且出现频次一定大于或等于指定频次
while (count == t.length()) {
//挡窗口的长度比已有的最短值小时,更改最小值,并记录起始位置
if (right - left < min) {
min = right - left;
start = left;
}
char l = s.charAt(left);
//如果左边即将要去掉的字符不被目标字符串需要,那么不需要多余判断,直接可以移动左指针
if (need[l] == 0) {
left++;
continue;
}
//如果左边即将要去掉的字符被目标字符串需要,且出现的频次正好等于指定频次,那么如果去掉了这个字符,
//就不满足覆盖子串的条件,此时要破坏循环条件跳出循环,即控制目标字符串指定字符的出现总频次(count)-1
if (have[l] == need[l]) {
count--;
}
//已有字符串中目标字符出现的次数-1
have[l]--;
//移动左指针
left++;
}
}
//如果最小长度还为初始值,说明没有符合条件的子串
if (min == s.length() + 1) {
return "";
}
//返回的为以记录的起始位置为起点,记录的最短长度为距离的指定字符串中截取的子串
return s.substring(start, start + min);
}
}

滑动窗口最大值(hard)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Solution {
//优先队列
public int[] maxSlidingWindow(int[] nums, int k) {
int left = 1;
int[] ans = new int[nums.length-(k-1)];
PriorityQueue<int[]> queue = new PriorityQueue<>(k, (o1, o2) -> o2[0]-o1[0]);
for (int i = 0; i < k; i++)queue.offer(new int[]{nums[i],i});
ans[0] = queue.peek()[0];
for(int i=k;i<nums.length;i++){
queue.offer(new int[]{nums[i],i});
while (queue.peek()[1] < left)queue.poll();
int[] max = queue.peek();
ans[left] = max[0];
left++;
}
return ans;
}
//双端队列
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
Deque<Integer> deque = new LinkedList<Integer>();
for (int i = 0; i < k; ++i) {
while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) {
deque.pollLast();
}
deque.offerLast(i);
}

int[] ans = new int[n - k + 1];
ans[0] = nums[deque.peekFirst()];
for (int i = k; i < n; ++i) {
while (!deque.isEmpty() && nums[i] >= nums[deque.peekLast()]) {
deque.pollLast();
}
deque.offerLast(i);
while (deque.peekFirst() <= i - k) {
deque.pollFirst();
}
ans[i - k + 1] = nums[deque.peekFirst()];
}
return ans;
}
}

比较版本号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Solution {
//思路:双指针
public int compareVersion(String version1, String version2) {
int m = version1.length();
int n = version2.length();
int i=0,j=0;
while(i<m||j<n){
//获取version1的版本
int v1 = 0;
for(;i<m&&version1.charAt(i)!='.';++i){
v1 = v1 * 10 + version1.charAt(i)-'0';
}
i++;
//获取version2的版本
int v2 = 0;
for(;j<n&&version2.charAt(j)!='.';++j){
v2 = v2 * 10 + version2.charAt(j)-'0';
}
j++;
//比较version1/version2的版本
if(v1!=v2)return v1>v2?1:-1;
}
return 0;
}
//暴力
public int compareVersions(String version1, String version2) {
String[] v1a = version1.split("\\.");
String[] v2a = version2.split("\\.");
int m = v1a.length;
int n = v2a.length;
for (int i = 0; i < Math.min(m,n); i++) {
String v1 = v1a[i];
String v2 = v2a[i];
int i1 = Integer.parseInt(v1);
int i2 = Integer.parseInt(v2);
if(i1==i2)continue;
return i1>i2?1:-1;
}
if (m>n)for (int i = n; i < m; i++)if (Integer.parseInt(v1a[i])!=0)return 1;
if (m<n)for (int i = m; i < n; i++)if (Integer.parseInt(v2a[i])!=0)return -1;
return 0;
}
}

最长连续序列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
//思路:set装载nums再遍历判断其是否存在连续
public int longestConsecutive(int[] nums) {
int ans = 0;
Set<Integer> set = new HashSet<>(nums.length);
for(int i:nums)set.add(i);
for(int i:set){
if(!set.contains(i-1)){//已经连续的上一次判断过了直接跳过
i++;
int count = 1;
while(set.contains(i)){
count++;
i++;
}
ans=Math.max(count,ans);
}
}
return ans;
}
}

验证IP地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class Solution {
//思路:
//1.ipv4: 点号分隔,4组,每组不能超过255,每组不能以0开头(可以单独是0)
//2.ipv6: 冒号分隔,8组,每组不能超过4位,每组不能为空
public String validIPAddress(String queryIP) {
String IPVv4 = "IPv4";
String IPVv6 = "IPv6";
String Neither = "Neither";
if(queryIP.indexOf(".")!=-1){
String[] ips = queryIP.split("\\.");
if (ips.length!=4||queryIP.charAt(queryIP.length()-1)=='.')return Neither;
for(String ip:ips){
if (ip.length()==0||(ip.length()!=1&&ip.charAt(0)=='0'))return Neither;
int x = 0;
for (int i = 0; i < ip.length(); i++) {
char c = ip.charAt(i);
x = x*10+c-'0';
//check less 255 and 0-9
if (x>255||!(c>='0'&&c<='9'))return Neither;
}
}
return IPVv4;
}
if(queryIP.indexOf(":")!=-1){
String[] ips = queryIP.split(":");
if (ips.length!=8||queryIP.charAt(queryIP.length()-1)==':')return Neither;
for(String ip:ips){
if (ip.length()==0||ip.length()>4)return Neither;
//check a-f or A-F
for(int i=0;i<ip.length();i++){
char c = ip.charAt(i);
if((c>='0'&&c<='9')||(c>='A'&&c<='F')||(c>='a'&&c<='f'))continue;
return Neither;
}
}
return IPVv6;
}
return Neither;
}
}

验证回文串(easy)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public boolean isPalindrome(String s) {
int left=0,right=s.length()-1;
while(left<right){
//30-57,65-90,97-122
while(left<right&&isNumberOrLetter(s,left))left++;
while(right>left&&isNumberOrLetter(s,right))right--;
char c1 = s.charAt(left);
char c2 = s.charAt(right);
c1 = c1 >= 'a' ? (char)(c1 - 32):c1;
c2 = c2 >= 'a' ? (char)(c2 - 32):c2;
if(c1!=c2)return false;
left++;
right--;
}
return true;
}
public boolean isNumberOrLetter(String s,int right){
return (s.charAt(right)<'0'||s.charAt(right)>'z'||(s.charAt(right)>'9'&&s.charAt(right)<'A')||(s.charAt(right)>'Z'&&s.charAt(right)<'a'));
}
}

寻找重复数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int findDuplicate(int[] nums) {
/**
快慢指针思想, fast 和 slow 是指针, nums[slow] 表示取指针对应的元素
注意 nums 数组中的数字都是在 1 到 n 之间的(在数组中进行游走不会越界),
因为有重复数字的出现, 所以这个游走必然是成环的, 环的入口就是重复的元素,
即按照寻找链表环入口的思路来做
**/
int fast = 0, slow = 0;
while(true) {
fast = nums[nums[fast]];
slow = nums[slow];
if(slow == fast) {
fast = 0;
while(nums[slow] != nums[fast]) {
fast = nums[fast];
slow = nums[slow];
}
return nums[slow];
}
}
}
}

学生分数的最小差值

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
//思路:排序+窗口滑动
public int minimumDifference(int[] nums, int k) {
Arrays.sort(nums);
int n = nums.length;
int ret = Integer.MAX_VALUE;
for(int i=0,j=k-1;j<n;i++,j++){
ret = Math.min(nums[j]-nums[i],ret);
}
return ret;
}
}

压缩字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
//你必须设计并实现一个只使用常量额外空间的算法来解决此问题。
//思路:窗口滑动
public int compress(char[] chars) {
int n = chars.length;
int left = 0,right = 0,index = 0;
while(right<n){
while(right<n && chars[left] == chars[right])right++;
int count = right-left;
chars[index++] = chars[left];
if (count>1){
String s = String.valueOf(count);
for (int i = 0; i < s.length(); i++) {
chars[index++] = s.charAt(i);
}
}
left=right;
}
return index;
}
}

反转字符串(easy)

1
2
3
4
5
6
7
8
9
10
11
12
class Solution {
public void reverseString(char[] s) {
int left = 0,right = s.length-1;
while(left<right){
char c = s[left];
s[left] = s[right];
s[right] = c;
left++;
right--;
}
}
}

最接近的三数之和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
//2.排序+双指针查询,和三数之和类型,排序数组,以第一个数为起点,确认另外两个数位置,和大于target,right--,和小于targe时left++
public int threeSumClosestBySort(int[] nums, int target) {
int n = nums.length;
Arrays.sort(nums);
int closestNum = nums[0]+nums[1]+nums[2];
for(int i=0;i<n;i++){
int l = i+1,r = n-1;
while(l<r){
int threeSum = nums[i]+nums[l]+nums[r];
if(Math.abs(threeSum-target)<Math.abs(closestNum-target))closestNum = threeSum;
if(threeSum>target)r--;
else if(threeSum<target)l++;
else return closestNum;//等于target的时候
}
}
return closestNum;
}
//1.暴力破解
public int threeSumClosestByBF(int[] nums, int target) {
int n = nums.length;
int ret = 10000;
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
for(int k=j+1;k<n;k++){
int v = nums[i]+nums[j]+nums[k];
int d = Math.abs(target-v);
if(d<Math.abs(ret-target))ret = v;
}
}
}
return ret;
}
}

最长不含重复字符的子字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class Solution {
//双指针
public int lengthOfLongestSubstring0(String s) {
int n = s.length();
int left = 0,right = 0;
int ret = 0;
while(right<n){
String str = s.substring(left,right+1);
right++;
ret = Math.max(str.length(),ret);
while(left<right&&right<n&&str.indexOf(s.charAt(right))!=-1){
left++;
str = s.substring(left,right);
}
}
return ret;
}
//动态规划 + 哈希表
public int lengthOfLongestSubstring1(String s) {
Map<Character, Integer> dic = new HashMap<>();
int res = 0, tmp = 0;
for(int j = 0; j < s.length(); j++) {
int i = dic.getOrDefault(s.charAt(j), -1); // 获取索引 i
dic.put(s.charAt(j), j); // 更新哈希表
tmp = tmp < j - i ? tmp + 1 : j - i; // dp[j - 1] -> dp[j]
res = Math.max(res, tmp); // max(dp[j - 1], dp[j])
}
return res;
}
//动态规划 + 线性遍历
public int lengthOfLongestSubstring2(String s) {
Map<Character, Integer> dic = new HashMap<>();
int res = 0, tmp = 0;
for(int j = 0; j < s.length(); j++) {
int i = j - 1;
while(i >= 0 && s.charAt(i) != s.charAt(j)) i--; // 线性查找 i
tmp = tmp < j - i ? tmp + 1 : j - i; // dp[j - 1] -> dp[j]
res = Math.max(res, tmp); // max(dp[j - 1], dp[j])
}
return res;
}
//哈希表+双指针
public int lengthOfLongestSubstring(String s) {
Map<Character, Integer> dic = new HashMap<>();
int i = -1, res = 0;
for(int j = 0; j < s.length(); j++) {
if(dic.containsKey(s.charAt(j)))i = Math.max(i, dic.get(s.charAt(j))); // 更新左指针 i
dic.put(s.charAt(j), j); // 哈希表记录
res = Math.max(res, j - i); // 更新结果
}
return res;
}
}

和为s的连续正数序列(easy)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
//双指针
public int[][] findContinuousSequence(int target) {
List<int[]> vec = new ArrayList<int[]>();
for (int l = 1, r = 2; l < r;) {
int sum = (l + r) * (r - l + 1) / 2;
if (sum == target) {
int[] res = new int[r - l + 1];
for (int i = l; i <= r; ++i) {
res[i - l] = i;
}
vec.add(res);
l++;
} else if (sum < target) {
r++;
} else {
l++;
}
}
return vec.toArray(new int[vec.size()][]);
}
}

392. 判断子序列(easy)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
//双指针
public boolean isSubsequence(String s, String t) {
int n = s.length(), m = t.length();
int i = 0, j = 0;
while (i < n && j < m) {
if (s.charAt(i) == t.charAt(j)) {
i++;
}
j++;
}
return i == n;
}
//api
public boolean isSubsequence0(String s, String t) {
int n = s.length();
int lastIndex = -1;
for(int i=0;i<n;i++){
int index = t.indexOf(s.charAt(i),lastIndex+1);
if(index==-1||index<lastIndex)return false;
lastIndex = index;
}
return true;
}
}

1094. 拼车(mid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Solution {
public boolean carPooling(int[][] trips, int capacity) {
int sites[] = new int[1001];
for (int[] trip : trips) {
// 上车加
sites[trip[1]] += trip[0];
// 下车减
sites[trip[2]] -= trip[0];
}
// 从始发站计数,超过capacity则超载
int total = 0;
for (int i : sites) {
total += i;
if (total > capacity) {
return false;
}
}
return true;
}
//排序+优先队列
public boolean carPooling0(int[][] trips, int capacity) {
int n = trips.length;
int empty = capacity;
//按照上车时间排序
Arrays.sort(trips, Comparator.comparingInt(o -> o[1]));
int preEnd = 0;
PriorityQueue<int[]> queue = new PriorityQueue<>(Comparator.comparingInt(o -> o[2]));
for(int[] trip:trips){
int start = trip[1],end = trip[2],num=trip[0];
//是否有座位可以回收
while(!queue.isEmpty()&&queue.peek()[2]<=start){
int[] p = queue.poll();
empty+=p[0];
}
//空位不足
if(start<preEnd||empty<num)return false;
//正常上车
queue.offer(trip);
empty-=num;
}
return true;
}
}

1288. 删除被覆盖区间(mid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
//[[1,4],[2,8],[3,6]]
public int removeCoveredIntervals(int[][] intervals) {
int ret = 0;
int n = intervals.length;
//按照开始时间排序,开始时间相同按照结束时间长的在前面
Arrays.sort(intervals,(o1,o2)->o1[0]==o2[0]?o2[1]-o1[1]:o1[0]-o2[0]);
int preStart = intervals[0][0],preEnd = intervals[0][1];
for(int i=1;i<n;i++){
int start = intervals[i][0],end = intervals[i][1];
if(preStart<=start&&end<=preEnd)ret++;
else{
preStart = start;
preEnd = end;
}
}
return n-ret;
}
}

15. 三数之和(mid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution {
//双指针+排序
//排序后循环确定第一个数,通过left,right指针在前后移动
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> ret = new ArrayList<>();
int n = nums.length;
Arrays.sort(nums);
for(int i=0;i<n;i++){
if(nums[i]>0)break;//直接退出
int left = i+1;
int right = n-1;
if(i>0&&nums[i]==nums[i-1])continue;//去重
while(left<right){
int n1 = nums[i];
int n2 = nums[left];
int n3 = nums[right];
if(n1+n2+n3==0){
ret.add(Arrays.asList(n1,n2,n3));
//去重
while(left<right&&n2==nums[left+1])left++;
while(left<right&&n3==nums[right-1])right--;
left++;
right--;
}
if(n1+n2+n3>0)right--;
if(n1+n2+n3<0)left++;
}
}
return ret;
}
}

18. 四数之和(mid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Solution {
//思路:排序+双指针移动
//类似三数之和,排序后优先确定前面两个数,在通过指针根据升序的列表进行查找
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> ret = new ArrayList<>();
if(nums==null||nums.length<4)return ret;
int n = nums.length;
Arrays.sort(nums);
//[-2,-1,0,0,1,2]
for(int i=0;i<n-3;i++){
if(i>0&&nums[i]==nums[i-1])continue;
if((long)nums[i]+nums[i+3]+nums[i+2]+nums[i+1]>target)continue;
if((long)nums[i]+nums[n-3]+nums[n-2]+nums[n-1]<target)continue;
for(int j=i+1;j<n-2;j++){
if(j>i+1&&nums[j]==nums[j-1])continue;
if((long)nums[i]+nums[j]+nums[j+2]+nums[j+1]>target)continue;
if((long)nums[i]+nums[j]+nums[n-2]+nums[n-1]<target)continue;
//确认后面两个数
int left = j + 1,right = n - 1;
while(left<right){
int sum = nums[i]+nums[j]+nums[left]+nums[right];
if(sum==target){
ret.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));
//跳过重复的
while(left<right&&nums[left]==nums[left+1])left++;
left++;
while(left<right&&nums[right]==nums[right-1])right--;
right--;
}else if(sum<target){
left++;
}else{
right--;
}
}
}
}
return ret;
}
}

88. 合并两个有序数组(easy)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
//双指针从后向前
public void merge(int[] nums1, int m, int[] nums2, int n) {
int len = m + n;
int n1 = m-1;
int n2 = n-1;
for(int i=len-1;i>=0;i--){
int num1 = n1>=0?nums1[n1]:Integer.MIN_VALUE;
int num2 = n2>=0?nums2[n2]:Integer.MIN_VALUE;
if(num1>num2){
nums1[i] = num1;
n1--;
} else {
nums1[i] = num2;
n2--;
}
}
}
}

75. 颜色分类(mid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
class Solution {
//一次遍历
//[2,0,2,1,1,0]
public void sortColors(int[] nums) {
int n = nums.length;
int p0 = 0, p1 = 0;
for (int i = 0; i < n; ++i) {
if (nums[i] == 1) {
int temp = nums[i];
nums[i] = nums[p1];
nums[p1] = temp;
++p1;
} else if (nums[i] == 0) {
int temp = nums[i];
nums[i] = nums[p0];
nums[p0] = temp;
//如果 p0 < p1,需要把1置换回去
//p0<p1时,我们已经将一些 1 连续地放在头部,此时一定会把一个 1 交换出去,导致答案错误。
//因此,如果 p0<p1那么我们需要再将 nums[i] 与 nums[p1] 进行交换,其中 i 是当前遍历到的位置,在进行了第一次交换后,
//nums[i] 的值为 1,我们需要将这个 1 放到「头部」的末端。
if (p0 < p1) {
temp = nums[i];
nums[i] = nums[p1];
nums[p1] = temp;
}
//移动p0的同时移动p1指针
++p0;
++p1;
}
}
}
//两次遍历
public void sortColors2(int[] nums) {
int n = nums.length;
if(n==1)return;
int pre = 0;
//把0全部提到前面
for(int i=0;i<n;i++){
if(nums[i]==0){
int num = nums[i];
nums[i] = nums[pre];
nums[pre] = num;
pre++;
}
}
//把1全部提到0前面
for(int i=pre;i<n;i++){
if(nums[i]==1){
int num = nums[i];
nums[i] = nums[pre];
nums[pre] = num;
pre++;
}
}
}
//三个指针划分边界
//思路:定义三个指针i0,i1,i2=0,
// - 每次遍历如果遇到0,可知当前0的个数+1,所以i0,需要设置为0后++,i1,i2依次往后移动。
// - 每次遍历如果遇到1,可知当前1的个数+1,所以i1,需要设置为1后++,i2依次往后移动。
// - 每次遍历如果遇到2,可知当前2的个数+2,所以i2,需要设置为2后++。
//为了保证当前设置的个数加一有效,需要在它后面的数都移动后在做自己指针的赋值
//通过以上的遍历,每次设置,(在原数组的基础上重新生成一个)
//1.为什么这样不会覆盖数组后面的数呢?
// 因为每次遍历的时候我们已经把比当前大的数往后移动了(覆盖)
//例如
//[1,2,0,1,2,0]
//第一轮nums[0]=1
//[1,2,0,1,2,0] i0=0,i1=0,i2=0,(初始化)
//[(2),2,0,1,2,0] i0=0,i1=0,i2=1,(nums[i2++]=2)
//[(1),2,0,1,2,0] i0=0,i1=1,i2=1,(nums[i1++]=1)
//第二轮nums[1]=2
//[1,2,0,1,2,0] i0=0,i1=1,i2=2,(nums[i2++]=2)
//第三轮nums[2]=0
//[1,2,2,1,2,0] i0=0,i1=1,i2=3,(nums[i2++]=2)
//[1,2,1,1,2,0] i0=0,i1=2,i2=3,(nums[i2++]=2)
//[0,2,1,1,2,0] i0=1,i1=2,i2=3,(nums[i2++]=2)
//第四轮nums[3]=1
//[0,2,1,2,2,0] i0=1,i1=2,i2=4,(nums[i2++]=2)
//[0,2,1,1,2,0] i0=1,i1=3,i2=4,(nums[i1++]=1)
//第五轮nums[4]=2
//[0,2,1,1,2,0] i0=1,i1=2,i2=5,(nums[i2++]=2)
//第六轮nums[5]=0
//[0,2,1,1,2,2] i0=1,i1=2,i2=6,(nums[i2++]=2)
//[0,2,1,1,2,2] i0=1,i1=3,i2=6,(nums[i1++]=1)
//[0,0,1,1,2,2] i0=1,i1=2,i2=6,(nums[i0++]=0)
public void sortColors(int[] nums) {
int n = nums.length;
int color0 = 0,color1 = 0,color2 = 0;
for(int i=0;i<n;i++){
int color = nums[i];
if(color==0){//顺序很重要
nums[color2++]=2;
nums[color1++]=1;
nums[color0++]=0;
} else if(color==1){
nums[color2++]=2;
nums[color1++]=1;
} else if(color==2){
nums[color2++]=2;
}
}
}
}

11. 盛最多水的容器(mid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
//思路:容量=Math.min(height[left],height[right])*(right-left)
//窗口滑动从两边向内收缩,优先丢弃高度小的
public int maxArea(int[] height) {
int n = height.length;
int ret = 0,left = 0,right = n-1;
while (left<right){
int area = Math.min(height[left],height[right])*(right-left);
ret = Math.max(area,ret);
if (height[left]<height[right])left++;
else right--;
}
return ret;
}
}

42. 接雨水(mid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// [0,1,0,2,1,0,1,3,2,1,2,1]    
// 动态规划(一个下标一个下标的计算他的可接水量)
// 下标i下雨后接到的雨水量为,两边柱子的最小值的高度-height[i],向左边扫描最大值,向右扫描最大值
public int trap(int[] height) {
int n = height.length;
if (n == 0) { return 0; }
//左边最大值
int[] leftMax = new int[n];
leftMax[0] = height[0];
for (int i = 1; i < n; ++i) leftMax[i] = Math.max(leftMax[i - 1], height[i]);
//右边最大值
int[] rightMax = new int[n];
rightMax[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; --i) rightMax[i] = Math.max(rightMax[i + 1], height[i]);
//计算结果(宽度为1无需乘)
int ans = 0;
for (int i = 0; i < n; ++i) ans += Math.min(leftMax[i], rightMax[i]) - height[i];
return ans;
}
// 窗口滑动(基于上面动态规划)
// 在上面的动态规划中需要维护两个数组leftMax和rightMax,
// 由于觉得下标i处可接雨水量由i两边最小height[]决定,
// 我们可以使用两个变量记录leftMax,rightMax
public int trap(int[] height) {
int ans = 0;
int left = 0, right = height.length - 1;
int leftMax = 0, rightMax = 0;
while (left < right) {
//更新leftMax和rightMax
leftMax = Math.max(leftMax, height[left]);
rightMax = Math.max(rightMax, height[right]);
// 如果 height[left] < height[right] ,因为右边高与height[left]
// 则说明下标left处可能会接到雨水,计算下标left的可接雨水为 leftMax - height[left] (因为rightMax必定大于leftMax)
if (height[left] < height[right]) {
ans += leftMax - height[left];
++left;
} else {
ans += rightMax - height[right];
--right;
}
}
return ans;
}
// 单调栈(栈底到栈顶递减)
public int trap(int[] height) {
int n = height.length;
int ret = 0;
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < n; i++) {
//如果当前元素大于栈顶,说明有栈顶元素是凹的则可以计算
while (!stack.isEmpty()&&height[stack.peek]<height[i]){
int midIndex = stack.pop();
if (stack.isEmpty())break;
int leftHight = stack.peek();
int h = Math.min(height[leftHight],height[i])-height[midIndex];
int width = i - leftHight - 1;
ret+=h*width;
}
stack.push(i);
}
return ret;
}

977. 有序数组的平方(easy)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Solution {
//双指针:如果left为负数判断其与right的各自平方大小
public int[] sortedSquares(int[] nums) {
int n = nums.length;
int[] ret = new int[n];
int left = 0,right = n-1;
int index = n - 1;
while(left<=right){
if(nums[left]<=0){
int ln = nums[left]*nums[left];
int rn = nums[right]*nums[right];
if(ln>rn){
ret[index--]=ln;
left++;
}else{
ret[index--]=nums[right]*nums[right];
right--;
}
}else{
ret[index--]=nums[right]*nums[right];
right--;
}
}
return ret;
}
}

350. 两个数组的交集 II(mid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
//哈希表,排序+双指针
public int[] intersect(int[] nums1, int[] nums2) {
List<Integer> list = new ArrayList<>();
int m = nums1.length;
int n = nums2.length;
Arrays.sort(nums1);
Arrays.sort(nums2);
//[4,5,9]
//[4,4,8,9,9]
int n1Index = 0;
int n2Index = 0;
while(n1Index<m||n2Index<n){
if(n1Index>=m){
n2Index++;
}else if(n2Index>=n)n1Index++;
else if(nums1[n1Index]==nums2[n2Index]){
list.add(nums1[n1Index]);
n1Index++;
n2Index++;
}else if(nums1[n1Index]<nums2[n2Index]){
n1Index++;
}else if(nums1[n1Index]>nums2[n2Index]){
n2Index++;
}

}
int[] ret = new int[list.size()];
for(int i=0;i<list.size();i++){
ret[i]=list.get(i);
}
return ret;
}
}

27. 移除元素(easy)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
//双指针后面替补
public int removeElement(int[] nums, int val) {
int n = nums.length;
int left = 0,right = n-1;
while(left<=right){
if(nums[left]==val){
while(right>=0&&nums[right]==val)right--;
if(left>right)break;//如果left>=right说明left相遇无需再交换num
nums[left] = nums[right];
right--;
}
left++;
}
return left;
}
}

面试题 16.06. 最小差(mid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
//排序+双指针
public int smallestDifference(int[] a, int[] b) {
int m = a.length;
int n = b.length;
int ret = 2147483647;
Arrays.sort(a);
Arrays.sort(b);
int ai = 0,bi = 0;
while(ai<m&&bi<n){
long reduce = Math.abs((long)(a[ai]-b[bi]));
ret = (int)Math.min(ret,reduce);
if(a[ai]<b[bi])ai++;
else bi++;
}
return ret;
}
}

986. 区间列表的交集(mid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
//双指针:优先使用end
public int[][] intervalIntersection(int[][] firstList, int[][] secondList) {
int m = firstList.length;
int n = secondList.length;
List<int[]> list = new ArrayList<>();
int first = 0,secode = 0;
while(first<m&&secode<n){
int[] f = firstList[first];//first区间
int[] s = secondList[secode];//second区间
int left = Math.max(s[0],f[0]);//交集左区间
int right = Math.min(s[1],f[1]);//交集右区间
if(left<=right)list.add(new int[]{left,right});//只有left<=right才有交集
if(f[1]>s[1]){//通过右区间移动右区间小的列表
secode++;
}else if(f[1]==s[1]){
secode++;
first++;
}else{
first++;
}
}
return list.toArray(new int[list.size()][]);
}
}

80. 删除有序数组中的重复项 II(mid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
//双指针:left指针记录当前需要设置的值,right指针向后查找不重复的数
public int removeDuplicates(int[] nums) {
int n = nums.length;
int ret = 0;
int left = 0,right = 0,prev = -100000,prevCount = 1;
while(right<n){
//存在重复三个数
if(prevCount>=2&&prev==nums[right]){
//向右查找数来填充left下标的数
while(right<n&&nums[right]==prev)right++;
if(right>=n)break;//超出长度直接退出
}
nums[left]=nums[right];//
prevCount = nums[left]==prev?prevCount+1:1;
prev = nums[left];
left++;
right++;
}
return left;
}
}

567. 字符串的排列(mid)

给你两个字符串 s1 和 s2 ,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true ;否则,返回 false 。换句话说,s1 的排列之一是 s2 的 子串 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
//双指针+窗口滑动:数组need记录s1需要的元素,leftright在s2窗口滑动
public boolean checkInclusion(String s1, String s2) {
int m = s1.length();
int n = s2.length();
int[] need = new int[26];
for(int i=0;i<m;i++)need[s1.charAt(i)-'a']++;
int count = 0;
int[] have = new int[26];
int left = 0,right = 0;
while(right<n){
char c = s2.charAt(right);
if(need[c-'a']==0){
count = 0;
Arrays.fill(have,0);
right++;
left = right;
continue;
}
//缩小窗口
while(need[c-'a']<=have[c-'a']){
char lc = s2.charAt(left);
left++;
have[lc-'a']--;
count--;
}
have[c-'a']++;
count++;
right++;
if(count==s1.length())return true;
}
return false;
}
}

1004. 最大连续1的个数 III(mid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
//窗口滑动:只要left,right直接存在0个数小于等于k则可计算长度
public int longestOnes(int[] nums, int k) {
int n = nums.length;
int ret = 0;
int left = 0,sum = 0;
for(int i=0;i<n;i++){
sum+=nums[i];
//[1,1,1,0,0,[0],1,1,1,1,0], K = 2
//缩小窗口,全部都是1则sum=i+1,满足题意则sum+k>=i+1-left
while(left<n&+k<i-left+1){
sum-=nums[left];
left++;
}
ret = Math.max(ret,i-left+1);
}
return ret;
}
}

763. 划分字母区间(mid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
//思路:记录其出现的最右位置,
public List<Integer> partitionLabels(String s) {
int n = s.length();
int[] right = new int[26];
List<Integer> ret = new ArrayList<>();
for(int i=0;i<n;i++){
right[s.charAt(i)-'a']=i;
}
int i=0;
while(i<n){
int rightMost = right[s.charAt(i)-'a'];
//判断(i,rightMost]中检查是否有元素最后出现在rightMost后面
for(int j=i+1;j<rightMost;j++){
int jcRight = right[s.charAt(j)-'a'];
if(jcRight>rightMost){
rightMost = jcRight;
}
}
ret.add(rightMost-i+1);
i = rightMost+1;
}
return ret;
}
}

167. 两数之和 II - 输入有序数组(mid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class Solution {
//双指针
public int[] twoSum(int[] numbers, int target) {
int low = 0, high = numbers.length - 1;
while (low < high) {
int sum = numbers[low] + numbers[high];
if (sum == target) {
return new int[]{low + 1, high + 1};
} else if (sum < target) {
++low;
} else {
--high;
}
}
return new int[]{-1, -1};
}
//双指针+二分
public int[] twoSum0(int[] numbers, int target) {
int n = numbers.length;
int[] ret = new int[2];
for(int i=0;i<n;i++){
int need = target-numbers[i];
int left = i+1,right = n-1;
while(left<=right){
int mid = left+(right-left)/2;
if(numbers[mid]==need){
ret[0] = i+1;
ret[1] = mid+1;
return ret;
}else if(numbers[mid]>need){
right = mid - 1;
}else if(numbers[mid]<need){
left = mid + 1;
}
}
}
return ret;
}
}

16. 最接近的三数之和(mid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
//双指针(类似三数之和)
public int threeSumClosest(int[] nums, int target) {
int n = nums.length;
int ret = 100000;
Arrays.sort(nums);//排序
for(int i=0;i<n;i++){//确定第一个数
int left = i+1,right = n-1;//再确定第二第三个数
while(left<right){
int num = nums[i]+nums[left]+nums[right];
int closest = Math.abs(target-num);
ret = closest<Math.abs(ret-target)?num:ret;
if(closest==0)return num;
else if(num>target){
right--;
}else {
left++;
}
}
}
return ret;
}
}

30. 串联所有单词的子串(hard)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
public static List<Integer> solution2(String s, String[] words) {
List<Integer> res = new ArrayList<>();
Map<String, Integer> wordsMap = new HashMap<>();
if (s.length() == 0 || words.length == 0) return res;
for (String word: words) {
// 主串s中没有这个单词,直接返回空
if (s.indexOf(word) < 0) return res;
// map中保存每个单词,和它出现的次数
wordsMap.put(word, wordsMap.getOrDefault(word, 0) + 1);
}
// 每个单词的长度, 总长度
int oneLen = words[0].length(), wordsLen = oneLen * words.length;
// 主串s长度小于单词总和,返回空
if (wordsLen > s.length()) return res;
// 只讨论从01,..., oneLen-1 开始的子串情况,
// 每次进行匹配的窗口大小为 wordsLen,每次后移一个单词长度,由左右窗口维持当前窗口位置
for (int i = 0; i < oneLen; ++i) {
// 左右窗口
int left = i, right = i, count = 0;
// 统计每个符合要求的word
Map<String, Integer> subMap = new HashMap<>();
// 右窗口不能超出主串长度
while (right + oneLen <= s.length()) {
// 得到一个单词
String word = s.substring(right, right + oneLen);
// 有窗口右移
right += oneLen;
// words[]中没有这个单词,那么当前窗口肯定匹配失败,直接右移到这个单词后面
if (!wordsMap.containsKey(word)) {
left = right;
// 窗口内单词统计map清空,重新统计
subMap.clear();
// 符合要求的单词数清0
count = 0;
} else {
// 统计当前子串中这个单词出现的次数
subMap.put(word, subMap.getOrDefault(word, 0) + 1);
++count;
// 如果这个单词出现的次数大于words[]中它对应的次数,又由于每次匹配和words长度相等的子串
// 如 ["foo","bar","foo","the"] "| foobarfoobar| foothe"
// 第二个bar虽然是words[]中的单词,但是次数超了,那么右移一个单词长度后 "|barfoobarfoo|the"
// bar还是不符合,所以直接从这个不符合的bar之后开始匹配,也就是将这个不符合的bar和它之前的单词(串)全移出去
while (subMap.getOrDefault(word, 0) > wordsMap.getOrDefault(word, 0)) {
// 从当前窗口字符统计map中删除从左窗口开始到数量超限的所有单词(次数减一)
String w = s.substring(left, left + oneLen);
subMap.put(w, subMap.getOrDefault(w, 0) - 1);
// 符合的单词数减一
--count;
// 左窗口位置右移
left += oneLen;
}
// 当前窗口字符串满足要求
if (count == words.length) res.add(left);
}
}
}
return res;
}

159. 至多包含两个不同字符的最长子串(mid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
//双指针+滑动窗口
public int lengthOfLongestSubstringTwoDistinct(String s) {
int n = s.length();
int left = 0, right = 0,ret = 0;
Map<Character,Integer> map = new HashMap<>();
while(right<n){
char c = s.charAt(right);
int count = map.getOrDefault(c,0);
map.put(c,count+1);
//缩小窗口
while(left<right&↦.size()>2){
char lc = s.charAt(left);
int lcount = map.getOrDefault(lc,0);
if(lcount==1)map.remove(lc);
else map.put(lc,lcount-1);
left++;
}
ret = Math.max(right-left+1,ret);
right++;
}
return ret;
}
}

340. 至多包含 K 个不同字符的最长子串(mid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution {
public int lengthOfLongestSubstringKDistinct(String s, int k) {
int n = s.length();
if(k==0)return 0;
int left = 0, right = 0,ret = 0;
Map<Character,Integer> map = new HashMap<>();
while(right<n){
char c = s.charAt(right);
int count = map.getOrDefault(c,0);
map.put(c,count+1);
//缩小窗口
while(left<right&↦.size()>k){
char lc = s.charAt(left);
int lcount = map.getOrDefault(lc,0);
if(lcount==1)map.remove(lc);
else map.put(lc,lcount-1);
left++;
}
ret = Math.max(right-left+1,ret);
right++;
}
return ret;
}
}

713. 乘积小于 K 的子数组(mid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public int numSubarrayProductLessThanK(int[] nums, int k) {
int n = nums.length, ret = 0;
int prod = 1, i = 0;
for (int j = 0; j < n; j++) {
prod *= nums[j];
while (i <= j && prod >= k) {
prod /= nums[i];
i++;
}
ret += j - i + 1;
}
return ret;
}
}

面试题 01.05. 一次编辑(mid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution {
//详细分析可能出现的结果(模拟+双指针)
public boolean oneEditAway(String first, String second) {
int m = first.length();
int n = second.length();
if(Math.abs(m-n)>1)return false;
//分析一下存在的情况
//1.长度相同最多只能有一个不同(替换)
if(m==n){
int diff = 0;
for(int i=0;i<m;i++)if(first.charAt(i)!=second.charAt(i))diff++;
return diff<=1;
}
//2.两个字符长度不一致(差值是1)
//2.1 长的删除掉一个字符
//2.2 短的增加一个字符
//两种操作其实是一种结果(使用一种判断就行)
int i1 = 0;
int i2 = 0;
String longStr = m>n?first:second;
String shortStr = m<n?first:second;
int diff = 0;
while(i1<longStr.length()&&i2<shortStr.length()){
if(longStr.charAt(i1)==shortStr.charAt(i2)){
i1++;
i2++;
continue;
}
if(longStr.charAt(i1)!=shortStr.charAt(i2)&&diff>0){
return false;
}else{
diff++;
i1++;
}
}
return true;
}
}

825. 适龄的朋友(mid)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
//排序+双指针
//1 2 3 5 13 14 17 33
//条件2包含条件3
//取反条件2、3得出 ages[x] >= ages[y] > 0.5 * ages[x] + 7 满足才能发请求
//1.可知当 ages[x]<14 上式不会成立,所以只要考虑 >=15的情况,ages[y]范围(0.5 * ages[x] + 7,ages[x]],
public int numFriendRequests(int[] ages) {
int n = ages.length;
int ret = 0,left = 0,right = 0;
Arrays.sort(ages);
//遍历获取每个数的可请求好友区间[left,right]
for(int age:ages){
if(age<15)continue;//无需考虑小于15的情况
while(ages[left]<=0.5 * age + 7)left++;//需要满足上面的取值区间
//right+1防止索引越界
//如果右边界指针 right 指向的下一个元素满足 ages[right+1]≤ages[x],那么就将右边界向后移动一个位置
while(right + 1 < n && ages[right+1] <= age)right++;
ret+=right-left;
}
return ret;
}
}

相关资料


算法笔记-双指针/窗口滑动
https://mikeygithub.github.io/2019/11/19/yuque/算法笔记-双指针!窗口滑动/
作者
Mikey
发布于
2019年11月19日
许可协议