第 367 场力扣周赛

找出满足差值条件的下标 I

同下。

最短且字典序最小的美丽子字符串

滑动窗口。

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 {
public String shortestBeautifulSubstring(String s, int k) {
int n = s.length();

int lo = s.indexOf('1');
if (lo < 0) {
return "";
}

String ans = "";
int hi = lo, cnt = 0;
while (hi < n) {
cnt += s.charAt(hi++) - '0';
while (cnt > k || s.charAt(lo) == '0') {
cnt -= s.charAt(lo++) - '0';
}
if (cnt == k) {
String t = s.substring(lo, hi);
if (ans.isEmpty() || t.length() < ans.length() || t.length() == ans.length() && t.compareTo(ans) < 0) {
ans = t;
}
}
}
return ans;
}
}

找出满足差值条件的下标 II

维护最大值和最小值就行。

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[] findIndices(int[] nums, int indexDifference, int valueDifference) {
int n = nums.length;
int minIndex = 0, maxIndex = 0;
for (int j = indexDifference; j < n; j++) {
int i = j - indexDifference;

if (nums[i] > nums[maxIndex]) {
maxIndex = i;
} else if (nums[i] < nums[minIndex]) {
minIndex = i;
}

if (nums[maxIndex] - nums[j] >= valueDifference) {
return new int[]{maxIndex, j};
}
if (nums[j] - nums[minIndex] >= valueDifference) {
return new int[]{minIndex, j};
}
}
return new int[]{-1, -1};
}
}

构造乘积矩阵

前后缀分解,将二维数组看作一维数组。

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 {
private static final int MOD = 12345;

public int[][] constructProductMatrix(int[][] grid) {
int n = grid.length, m = grid[0].length;
int[][] p = new int[n][m];

long suf = 1;
for (int i = n - 1; i >= 0; i--) {
for (int j = m - 1; j >= 0; j--) {
p[i][j] = (int) suf;
suf = suf * grid[i][j] % MOD;
}
}

long pre = 1;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
p[i][j] = (int) (p[i][j] * pre % MOD);
pre = pre * grid[i][j] % MOD;
}
}

return p;
}
}

第 115 场力扣夜喵双周赛

上一个遍历的整数

模拟。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public List<Integer> lastVisitedIntegers(List<String> words) {
int idx = -1;
List<Integer> ans = new ArrayList<>();
List<Integer> aux = new ArrayList<>();
for (String word : words) {
if (word.equals("prev")) {
if (idx < 0) ans.add(-1);
else ans.add(aux.get(idx--));
} else {
aux.add(Integer.valueOf(word));
idx = aux.size() - 1;
}
}
return ans;
}
}

最长相邻不相等子序列 I

贪心。

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public List<String> getWordsInLongestSubsequence(int n, String[] words, int[] groups) {
List<String> ans = new ArrayList<>();
for (int i = 0; i < n; i++) {
if (i == n - 1 || groups[i] != groups[i + 1]) {
ans.add(words[i]);
}
}
return ans;
}
}

最长相邻不相等子序列 II

和最长递增子序列有点像,处理的时候记录一下路径就好。

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
class Solution {
public List<String> getWordsInLongestSubsequence(int n, String[] words, int[] groups) {
int pos = 0;
int[] from = new int[n];
int[] maxLen = new int[n];
Arrays.fill(maxLen, 1);

for (int i = 1; i < n; i++) {
for (int j = i - 1; j >= 0; j--) {
if (maxLen[i] < maxLen[j] + 1 && groups[i] != groups[j] && words[i].length() == words[j].length()) {
int cnt = 0;
for (int k = 0; k < words[i].length(); k++) {
if (words[i].charAt(k) != words[j].charAt(k) && ++cnt > 1) {
break;
}
}
if (cnt == 1) {
maxLen[i] = maxLen[j] + 1;
from[i] = j;
if (maxLen[i] > maxLen[pos]) {
pos = i;
}
}
}
}
}

int m = maxLen[pos];
LinkedList<String> ans = new LinkedList<>();
for (int i = 0; i < m; i++) {
ans.addFirst(words[pos]);
pos = from[pos];
}
return ans;
}
}

和带限制的子多重集合的数目

明显是多重背包问题,求背包中物品重量在 \([l,r]\) 之间的方案数,朴素的转移方程为:

$$ dp[i][j]=\sum_{k=0}^{cnt[i]}{(dp[i-1][j-k\cdot w[i]])} $$

这样做的时间复杂度为 \(O(rn)\),其中 \(n\) 为 \(nums\) 的长度。在题目的数据范围下,复杂度达到 \(4\cdot 1e8\) 数量级,会导致超时。优化方式如下,当 \(j\geq(cnt[i]+1)\cdot w[i]\) 且 \(w[i]\neq 0\) 时,有:

$$ dp[i][j-w[i]]=dp[i-1][j-w[i]]+dp[i-1][j-2\cdot w[i]]+\cdots+dp[i-1][j-(cnt[i]+1)\cdot w[i]] $$
$$ dp[i][j]=dp[i-1][j]+dp[i-1][j-w[i]]+\cdots+dp[i-1][j-cnt[i]\cdot w[i]] $$

然后错位相减,得到:

$$ dp[i][j]=dp[i][j-w[i]]+(dp[i-1][j]-dp[i-1][j-(cnt[i]+1)\cdot w[i]]) $$

当 \(j\geq(cnt[i]+1)\cdot w[i]\) 且 \(w[i]=0\) 时,直接使用朴素转移方程,得到:

$$ dp[i][j]=\sum_{k=0}^{cnt[i]}{(dp[i-1][j])} $$

同理可得,当 \(w[i]\leq j<(cnt[i]+1)\cdot w[i]\) 时,错位相减得到:

$$ dp[i][j]=dp[i][j-w[i]]+dp[i-1][j] $$

当 \(j<w[i]\) 时,直接使用朴素转移方程,得到:

$$ dp[i][j]=dp[i-1][j] $$

这样做的时间复杂度为 \(O(r\sqrt{S})\),其中 \(S\) 表示 \(nums\) 的元素和,\(\sqrt{S}\) 表示 \(nums\) 中不同元素的最大数量。不同元素的最大数量满足 \(\frac{(0+(x-1))\cdot x}{2}\leq S\),解得 \(x\leq \frac{1+\sqrt{1+8\cdot S}}{2}\)。还有一些常数级别的优化,比如减少遍历的上界等。

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
class Solution {
private static final int MOD = (int) 1e9 + 7;

public int countSubMultisets(List<Integer> nums, int l, int r) {
int n = nums.size();

Map<Integer, Integer> map = new HashMap<>();
for (int x : nums) {
map.merge(x, 1, Integer::sum);
}

int zero = map.getOrDefault(0, 0);
map.remove(0);

int m = map.size();
int[] f = new int[r + 1];
int[] g = new int[r + 1];
f[0] = zero + 1;

for (var e : map.entrySet()) {
int w = e.getKey(), c = e.getValue();
System.arraycopy(f, 0, g, 0, r + 1);
for (int j = w; j <= r; j++) {
f[j] = (f[j - w] + f[j]) % MOD;
if (j - (c + 1) * w >= 0) {
f[j] = (f[j] - g[j - (c + 1) * w] + MOD) % MOD;
}
}
}

int ans = 0;
for (int i = l; i <= r; i++) {
ans = (ans + f[i]) % MOD;
}
return ans;
}
}

分开处理,先做前缀和,再倒序减去某个前缀和,就可以不使用辅助数组 \(g\):

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
class Solution {
private static final int MOD = (int) 1e9 + 7;

public int countSubMultisets(List<Integer> nums, int l, int r) {
int n = nums.size();

Map<Integer, Integer> map = new HashMap<>();
for (int x : nums) {
map.merge(x, 1, Integer::sum);
}

int zero = map.getOrDefault(0, 0);
map.remove(0);

int m = map.size();
int[] f = new int[r + 1];
f[0] = zero + 1;

for (var e : map.entrySet()) {
int w = e.getKey(), c = e.getValue();
for (int j = w; j <= r; j++) {
f[j] = (f[j - w] + f[j]) % MOD;
}
for (int j = r; j >= (c + 1) * w; j--) {
f[j] = (f[j] - f[j - (c + 1) * w] + MOD) % MOD;
}
}

int ans = 0;
for (int i = l; i <= r; i++) {
ans = (ans + f[i]) % MOD;
}
return ans;
}
}

第 366 场力扣周赛

分类求和并作差

数学性质,\([1,n]\) 中能被 \(m\) 整除的数有 \(\lfloor \frac{n}{m}\rfloor\) 个。

1
2
3
4
5
class Solution {
public int differenceOfSums(int n, int m) {
return (1 + n) * n / 2 - (1 + (n / m)) * (n / m) * m;
}
}

最小处理时间

排序 + 贪心。

1
2
3
4
5
6
7
8
9
10
11
class Solution {
public int minProcessingTime(List<Integer> processorTime, List<Integer> tasks) {
Collections.sort(processorTime);
Collections.sort(tasks, (a , b) -> b - a);
int n = processorTime.size(), ans = 0;
for (int i = 0; i < n; i++) {
ans = Math.max(ans, processorTime.get(i) + tasks.get(i * 4));
}
return ans;
}
}

执行操作使两个字符串相等

方法一:动态规划

今天脑子有点笨啊,本来做出来了,但是我将 dp 的初始值设置为 Integer.MAX_VALUE,将 dfs 不满足条件时的返回值也设置为该值,而判断是否记忆化的条件也设置为该值,所以所有不满足条件的方案都没有记忆化上。

我经常会写出从上到下记忆化的代码,但是每次都比从下到上的记忆化慢,经过分析,原因如下:从下到上的记忆化,只要该节点计算过,就会直接返回;而从上到下的记忆化,只有在当前节点的值比记忆化的值大时,才会直接返回,也就是说,这样的代码只会将所有大于记忆化的值的方案给剪枝掉,所有小于记忆化的值的方案会重复计算。

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 {
public int minOperations(String s1, String s2, int x) {
int n = s1.length();
int[][][] dp = new int[n][n + 1][2];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n + 1; j++) {
Arrays.fill(dp[i][j], -1);
}
}
int ans = dfs(0, 0, 0, s1, s2, x, dp);
return ans >= (int) 1e9 ? -1 : ans;
}

private int dfs(int i, int c, int r, String s1, String s2, int x, int[][][] dp) {
if (i == s1.length()) {
if (c % 2 == 0 && r == 0) {
return -c / 2 * x;
}
return (int) 1e9;
}
if (dp[i][c][r] != -1) return dp[i][c][r];
int res = 0;
if ((s1.charAt(i) == s2.charAt(i)) == (r == 0)) {
res = dfs(i + 1, c, 0, s1, s2, x, dp);
} else {
res = dfs(i + 1, c + 1, 0, s1, s2, x, dp) + x;
res = Math.min(res, dfs(i + 1, c, 1, s1, s2, x, dp) + 1);
}
return dp[i][c][r] = res;
}
}

方法二:动态规划

时间复杂度 \(O(n)\),空间复杂度 \(O(1)\) 的解法,其实比赛时第一眼差不多就想到这种解法,但是没有细想。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public int minOperations(String s1, String s2, int x) {
int n = s1.length();
int pre = -1, cnt = 0;
int dp0 = (int) 1e9, dp1 = 0;
for (int i = 0; i < n; i++) {
if (s1.charAt(i) != s2.charAt(i)) {
cnt ^= 1;
int t = dp1;
dp1 = Math.min(dp1 + (cnt == 1 ? x : 0), dp0 + i - pre);
dp0 = t;
pre = i;
}
}
return cnt == 1 ? -1 : dp1;
}
}

对数组执行操作使平方和最大

挺简单一道题,T3 卡太久,脑子短路没时间做这题。对于每一位,每次操作其实就是交换两个数之间的 \(0\) 和 \(1\),我们应该总是把 \(1\) 交换到更大的数上,这样平方和最大。所以统计每一位的 \(1\) 的个数,贪心的组合成最大的数,然后取平方加入答案即可。

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 {
private static final int MOD = (int) 1e9 + 7;

public int maxSum(List<Integer> nums, int k) {
int[] cnt = new int[30];
for (int x : nums) {
for (int i = 0; i < 30; i++) {
cnt[i] += x >> i & 1;
}
}

long ans = 0L;
while (k-- != 0) {
int x = 0;
for (int i = 0; i < 30; i++) {
if (cnt[i] > 0) {
cnt[i]--;
x |= 1 << i;
}
}
ans = (ans + (long) x * x) % MOD;
}

return (int) ans;
}
}