写在最前

数组 / 字符串

交替合并字符串

  • 方式一:交替添加字符。如果其中一个字符串比另一个长,会将多余的字符追加到合并后字符串的末尾。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class 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
16
class 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
13
class 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
2
3
4
5
6
7
8
9
10
11
12
13
class Solution {
public List<Boolean> kidsWithCandies(int[] candies, int extraCandies) {
ArrayList<Boolean> res = new ArrayList<>();
int max = 0;
for (int candy : candies) {
max = Math.max(max, candy);
}
for (int i = 0; i < candies.length; i++) {
res.add(candies[i] + extraCandies >= max);
}
return res;
}
}

种花问题

  • 贪心:当前位置为0,前面为0,后面为0,那就能种。需要注意判断一下边界条件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public boolean canPlaceFlowers(int[] flowerbed, int n) {
int count = 0;
for (int i = 0; i < flowerbed.length; i++) {
if (flowerbed[i] == 0
// 右边界
&& (i + 1 == flowerbed.length || flowerbed[i + 1] == 0)
// 左边界
&& (i == 0 || flowerbed[i - 1] == 0)) {
flowerbed[i] = 1;
count++;
}
}
return count >= 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
class 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
2
3
4
5
6
7
class Solution {
public String reverseWords(String s) {
String[] res = s.trim().split("\\s+");
Collections.reverse(Arrays.asList(res));
return String.join(" ", res);
}
}

除自身以外数组的乘积

  • 不让用除法,且要在O(n)的时间复杂度内完成,那就只能一层循环,可以通过计算前缀积和后缀积最后使其相乘得出结果
nums 1 2 3 4
pre 1 1 2 6
suf 24 12 4 1
res 24 12 8 6
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[] productExceptSelf(int[] nums) {
int n = nums.length;
// 前缀积计算
int[] pre = new int[n];
pre[0] = 1;
for (int i = 1; i < n; i++) {
pre[i] = nums[i - 1] * pre[i - 1];
}
// 后缀积计算
int[] suf = new int[n];
suf[n - 1] = 1;
for (int i = n - 2; i >= 0; i--) {
suf[i] = suf[i + 1] * nums[i + 1];
}
// 结果计算
int[] res = new int[n];
for (int i = 0; i < n; i++) {
res[i] = pre[i] * suf[i];
}
return res;
}
}
  • 降低到O(1)的空间复杂度:用res数组代替pre数组,用一个suf变量代替suf数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class 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
18
class 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
14
class 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
13
class 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
2
3
4
5
6
7
8
9
10
11
12
13
14
class Solution {
public int maxArea(int[] height) {
int ans = 0;
// 双指针一首一尾
int i = 0, j = height.length - 1;
while (i < j) {
// 移动短板,更新最大值
ans = height[i] < height[j] ?
Math.max(ans, (j - i) * height[i++]) :
Math.max(ans, (j - i) * height[j--]);
}
return ans;
}
}

K 和数对的最大数目

滑动窗口

子数组最大平均数 I

  • 计算固定长度区间内数的最大平均值,采用滑动窗口的思想,即维护一个长度为k的滑动窗口,先计算长度为k的窗口初值,然后每次窗口向右移动的时候,加上最右边的元素,减掉最左边的元素,更新窗口最大值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class 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
19
class 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
22
class 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
18
class 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
10
class 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
18
class 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
13
class 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
23
class 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
18
class 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
9
class 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 (前缀树)

搜索推荐系统

区间集合

无重叠区间

用最少数量的箭引爆气球

单调栈

每日温度

股票价格跨度