Leetcode75
写在最前
- 力扣官方的学习计划,链接:https://leetcode.cn/studyplan/leetcode-75/
数组 / 字符串
交替合并字符串
- 方式一:交替添加字符。如果其中一个字符串比另一个长,会将多余的字符追加到合并后字符串的末尾。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17class Solution {
public static String mergeAlternately(String word1, String word2) {
int l1 = word1.length();
int l2 = word2.length();
int minLen = Math.min(l1, l2);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < minLen; i++) {
sb.append(word1.charAt(i));
sb.append(word2.charAt(i));
}
if (minLen == l1)
sb.append(word2.substring(minLen));
else
sb.append(word1.substring(minLen));
return sb.toString();
}
} - 方式二:使用两个指针i和j分别指向两个字符串的当前字符位置。通过一个循环,不断将对应位置的字符添加到结果字符串中,直到其中一个字符串遍历完。然后将剩余未遍历完的字符串直接追加到结果字符串末尾。这种方法避免了substring操作,可以稍微提高性能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class Solution {
public static String mergeAlternately(String word1, String word2) {
int l1 = word1.length();
int l2 = word2.length();
int i = 0;
int j = 0;
StringBuilder sb = new StringBuilder();
while (i < l1 || j < l2) {
if (i < l1)
sb.append(word1.charAt(i++));
if (j < l2)
sb.append(word2.charAt(j++));
}
return sb.toString();
}
}
字符串的最大公因子
- 题目找str1和str2的最大公子串,那么str1和str2都是n个子串组成的,那么如果不满足
(str1 + str2).equals(str2 + str1)
,那么直接返回空字符串(例如示例三),反之如果满足,则str1和str2长度的最大公因子就是子串长度,我们用substring()可以获取到子串。1
2
3
4
5
6
7
8
9
10
11
12
13class Solution {
// 求最大公因子
public static int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}
public String gcdOfStrings(String str1, String str2) {
// 不满足条件,返回空字符串
if (!(str1 + str2).equals(str2 + str1)) return "";
// 满足条件,求str1和str2的最大公因子
return str1.substring(0, gcd(str1.length(), str2.length()));
}
}
拥有最多糖果的孩子
- 找出最多糖果的数量,
其余孩子糖果数 + 额外糖果数 >= 最多糖果数
,结果为true
1 | class Solution { |
种花问题
- 贪心:当前位置为0,前面为0,后面为0,那就能种。需要注意判断一下边界条件
1 | class Solution { |
反转字符串中的元音字母
- 双指针,首尾遍历元音字母,找到后交换位置
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
28class Solution {
public String reverseVowels(String s) {
int l = 0, r = s.length() - 1;
char[] str = s.toCharArray();
while (l < r) {
while (l < s.length() && !isTarget(str[l]))
l++;
while (r > 0 && !isTarget(str[r]))
r--;
if (l < r) {
swap(str, l, r);
l++;
r--;
}
}
return new String(str);
}
boolean isTarget(char c) {
return "AEIOUaeiou".indexOf(c) >= 0;
}
void swap(char[] str, int i, int j) {
char tmp = str[i];
str[i] = str[j];
str[j] = tmp;
}
}
反转字符串中的单词
- 先去除首尾空格,然后正则匹配连续空格切分,最后翻转列表,加入空格拼接,返回结果
1 | class Solution { |
除自身以外数组的乘积
- 不让用除法,且要在O(n)的时间复杂度内完成,那就只能一层循环,可以通过计算前缀积和后缀积最后使其相乘得出结果
nums | 1 | 2 | 3 | 4 |
---|---|---|---|---|
pre | 1 | 1 | 2 | 6 |
suf | 24 | 12 | 4 | 1 |
res | 24 | 12 | 8 | 6 |
1 | class Solution { |
- 降低到O(1)的空间复杂度:用res数组代替pre数组,用一个suf变量代替suf数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class Solution {
public int[] productExceptSelf(int[] nums) {
int n = nums.length;
int[] res = new int[n];
res[0] = 1;
for (int i = 1; i < n; i++) {
res[i] = nums[i - 1] * res[i - 1];
}
int suf = 1;
for (int i = n - 1; i >= 0; i--) {
res[i] *= suf;
suf *= nums[i];
}
return res;
}
}
递增的三元子序列
压缩字符串
双指针
移动零
- 方式一:双指针遍历,快指针遍历不为0的元素,赋值给慢指针,快指针遍历到末尾结束,慢指针将剩余元素设0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Solution {
public void moveZeroes(int[] nums) {
if (nums == null || nums.length == 0)
return;
int fast = 0, slow = 0;
// 快指针遍历不为0的元素
for (; fast < nums.length; fast++) {
if (nums[fast] != 0) {
// 赋值给慢指针
nums[slow++] = nums[fast];
}
}
// 慢指针将剩余元素设0
for (; slow < nums.length; slow++) {
nums[slow] = 0;
}
}
} - 方式二
1
2
3
4
5
6
7
8
9
10
11
12
13
14class Solution {
public void moveZeroes(int[] nums) {
if (nums == null || nums.length == 0)
return;
int fast = 0, slow = 0;
for (; fast < nums.length; fast++) {
if (nums[fast] != 0) {
int tmp = nums[fast];
nums[fast] = nums[slow];
nums[slow++] = tmp;
}
}
}
}
判断子序列
- 双指针遍历,如果s是t的子序列,那么s中的所有元素都会按顺序在t中依次出现,一个指针遍历s,另一个指针遍历t,最终判断指针是否遍历到了s的末尾
1
2
3
4
5
6
7
8
9
10
11
12
13class Solution {
public boolean isSubsequence(String s, String t) {
int m = s.length(), n = t.length();
int i = 0, j = 0;
while (i < m && j < n) {
if (s.charAt(i) == t.charAt(j)) {
i++;
}
j++;
}
return i == m;
}
}
盛最多水的容器
- 设双指针
i
,j
分别指向height[0]
,height[n - 1]
,并逐渐向内收缩,那么面积S(i, j) = (j - i) * min(height[i], height[j])
,此时的面积取决于min(height[i], height[j])
- 如果向内移动短板,那么
min(height[i], height[j])
是可能增大的(也有可能变小),即移动短板面积可能会增大 - 如果向内移动长板,那么
min(height[i], height[j])
是减小或不变的,即移动长板面积一定会变小
- 如果向内移动短板,那么
- 所以为了求出最大面积,肯定是不能移动长板的,只能移动短板赌一波,每次移动短板之后更新最大值即可。
1 | class Solution { |
K 和数对的最大数目
滑动窗口
子数组最大平均数 I
- 计算固定长度区间内数的最大平均值,采用滑动窗口的思想,即维护一个长度为k的滑动窗口,先计算长度为k的窗口初值,然后每次窗口向右移动的时候,加上最右边的元素,减掉最左边的元素,更新窗口最大值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class Solution {
public static double findMaxAverage(int[] nums, int k) {
int sum = 0;
// 计算长度为k的窗口的初值
for (int i = 0; i < k; i++) {
sum += nums[i];
}
int res = sum;
// 每次向右移动时
for (int i = k; i < nums.length; i++) {
// 加上最右端元素,减去最左端元素
sum = sum + nums[i] - nums[i - k];
// 更新最大值
res = Math.max(res, sum);
}
// 取平均
return 1.0 * res / k;
}
}
定长子串中元音的最大数目
- 一样的套路,唯一一点区别就是需要判断新加入元素和剔除元素是否为元音字母
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class Solution {
public int maxVowels(String s, int k) {
int current = 0;
char[] chars = s.toCharArray();
for (int i = 0; i < k; i++) {
current += isVowel(chars[i]);
}
int res = current;
for (int i = k; i < chars.length; i++) {
current = current + isVowel(chars[i]) - isVowel(chars[i - k]);
res = Math.max(res, current);
}
return res;
}
int isVowel(char c) {
return (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') ? 1 : 0;
}
}
最大连续1的个数 III
- 将问题转换为:找一个最长子数组,该子数组最多包含k个0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22class Solution {
public int longestOnes(int[] nums, int k) {
int res = 0, count = 0, left = 0, right = 0, n = nums.length;
// 右边界遍历到结尾
while (right < n) {
// 右边界右移,如果遇到0,则计数+1
if (nums[right] == 0)
count++;
// 如果此时left ~ right的子数组中的0已经超过了k个
while (count > k) {
// 左边界右移,如果左边界遇到了0,则将其移除,计数-1
if (nums[left++] == 0)
count--;
}
// 每次操作完毕更新结果
res = Math.max(res, right - left + 1);
// 右边界右移
right++;
}
return res;
}
}
删掉一个元素以后全为1的最长子数组
- 与上题类似,将其转换为:找一个最长子数组,最多只包含1个0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Solution {
public int longestSubarray(int[] nums) {
int res = 0, count = 0, left = 0, right = 0, n = nums.length;
while(right < n) {
if(nums[right] == 0) {
count++;
}
while(count > 1) {
if(nums[left++] == 0) {
count--;
}
}
res = Math.max(res, right - left + 1);
right++;
}
return res - 1;
}
}
前缀和
找到最高海拔
- 简单的前缀和
1
2
3
4
5
6
7
8
9
10class Solution {
public int largestAltitude(int[] gain) {
int res = 0, sum = 0;
for(int x : gain) {
sum += x;
res = Math.max(res, sum);
}
return res;
}
}
寻找数组的中心下标
- 求前缀和和后缀和,然后从左往右遍历,找到第一个前缀和和后缀和相等的下标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Solution {
public int pivotIndex(int[] nums) {
int n = nums.length;
int[] preSum = new int[n];
preSum[0] = 0;
int[] extSum = new int[n];
extSum[n - 1] = 0;
for (int i = 1; i < n; i++) {
preSum[i] = preSum[i - 1] + nums[i - 1];
extSum[n - 1 - i] = extSum[n - i] + nums[n - i];
}
for (int i = 0; i < n; i++) {
if (preSum[i] == extSum[i])
return i;
}
return -1;
}
} - 优化思路:如果有符合条件的中心下标,则有
左求和 × 2 + 中心下表值 = 总和
1
2
3
4
5
6
7
8
9
10
11
12
13class Solution {
public int pivotIndex(int[] nums) {
int sum = Arrays.stream(nums).sum();
int leftSum = 0;
for (int i = 0; i < nums.length; i++) {
if (leftSum * 2 + nums[i] == sum) {
return i;
}
leftSum += nums[i];
}
return -1;
}
}
哈希表 / 哈希集合
找出两数组的不同
- 请叫我API侠
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class Solution {
public List<List<Integer>> findDifference(int[] nums1, int[] nums2) {
HashSet<Integer> set1 = new HashSet<>();
HashSet<Integer> set2 = new HashSet<>();
for (int n : nums2) {
set2.add(n);
}
for (int n : nums1) {
set1.add(n);
}
for (int n : nums2) {
set1.remove(n);
}
for (int n : nums1) {
set2.remove(n);
}
List<List<Integer>> res = new ArrayList<>();
res.add(set1.stream().toList());
res.add(set2.stream().toList());
return res;
}
} - 更方便的API
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18class Solution {
public List<List<Integer>> findDifference(int[] nums1, int[] nums2) {
HashSet<Integer> set1 = new HashSet<>();
HashSet<Integer> set2 = new HashSet<>();
for (int n : nums2) {
set2.add(n);
}
for (int n : nums1) {
set1.add(n);
set2.remove(n);
}
for (int n : nums2) {
set1.remove(n);
}
return List.of(List.copyOf(set1), List.copyOf(set2));
}
}
独一无二的出现次数
- 哈希表存储每个数字出现的次数,判断value是否有重复值,把value存入HashSet,观察value的个数会不会减少即可
1
2
3
4
5
6
7
8
9class Solution {
public boolean uniqueOccurrences(int[] arr) {
HashMap<Integer, Integer> map = new HashMap<>();
for (int n : arr) {
map.put(n, map.get(n) == null ? 0 : map.get(n) + 1);
}
return new HashSet<>(map.values()).size() == map.size();
}
}
确定两个字符串是否接近
相等行列对
栈
从字符串中移除星号
行星碰撞
字符串解码
队列
最近的请求次数
Dota2 参议院
链表
删除链表的中间节点
奇偶链表
反转链表
链表最大孪生和
二叉树 - 深度优先搜索
二叉树的最大深度
叶子相似的树
统计二叉树中好节点的数目
路径总和 III
二叉树中的最长交错路径
二叉树的最近公共祖先
二叉树 - 广度优先搜索
二叉树的右视图
最大层内元素和
二叉搜索树
二叉搜索树中的搜索
删除二叉搜索树中的节点
图 - 深度优先搜索
钥匙和房间
省份数量
重新规划路线
除法求值
图 - 广度优先搜索
迷宫中离入口最近的出口
腐烂的橘子
堆 / 优先队列
数组中的第K个最大元素
无限集中的最小数字
最大子序列的分数
雇佣 K 位工人的总代价
二分查找
猜数字大小
咒语和药水的成功对数
寻找峰值
爱吃香蕉的珂珂
回溯
电话号码的字母组合
组合总和 III
动态规划 - 一维
第 N 个泰波那契数
使用最小花费爬楼梯
打家劫舍
多米诺和托米诺平铺
动态规划 - 多维
不同路径
最长公共子序列
买卖股票的最佳时机含手续费
编辑距离
位运算
比特位计数
只出现一次的数字
或运算的最小翻转次数
前缀树
实现 Trie (前缀树)
搜索推荐系统
区间集合
无重叠区间
用最少数量的箭引爆气球
单调栈
每日温度
股票价格跨度
评论