第 110 场力扣夜喵双周赛

取整购买后的账户余额

方法一:模拟

比赛时没看明白,写复杂了一点。

1
2
3
4
5
class Solution {
public int accountBalanceAfterPurchase(int purchaseAmount) {
return 100 - (purchaseAmount + 5) / 10 * 10;
}
}

复杂度分析

  • 时间复杂度:\(O(1)\)。
  • 空间复杂度:\(O(1)\)。

在链表中插入最大公约数

方法一:模拟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Solution {
public ListNode insertGreatestCommonDivisors(ListNode head) {
ListNode cur = head;
while (cur.next != null) {
cur.next = new ListNode(gcd(cur.val, cur.next.val), cur.next);
cur = cur.next.next;
}
return head;
}

private int gcd(int a, int b) {
if (b == 0) return a;
return gcd(b, a % b);
}
}

复杂度分析

  • 时间复杂度:\(O(n\log m)\),其中 \(m\) 表示节点的最大值。
  • 空间复杂度:\(O(1)\)。

使循环数组所有元素相等的最少秒数

方法一:枚举

假设最后数组中的元素是 \(x\),那么需要的最少秒数就是所有值为 \(x\) 的元素之间的最大间距的一半向上取整。由于数组是循环数组,我们可以在遍历时添加两次,或者在处理哈希表中的列表时特殊处理最后一个元素与第一个元素的间距。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public int minimumSeconds(List<Integer> nums) {
int n = nums.size();
Map<Integer, List<Integer>> map = new HashMap<>();
for (int i = 0; i < 2 * n; i++) {
map.computeIfAbsent(nums.get(i % n), k -> new ArrayList<>()).add(i);
}
int ans = Integer.MAX_VALUE;
for (var list : map.values()) {
int m = list.size(), max = 0;
for (int i = 0; i < m - 1; i++) {
max = Math.max(max, list.get(i + 1) - list.get(i) - 1);
}
ans = Math.min(ans, (max + 1) / 2);
}
return ans;
}
}

复杂度分析

  • 时间复杂度:\(O(n)\)。
  • 空间复杂度:\(O(n)\)。

使数组和小于等于 x 的最少时间

方法一:动态规划

比赛时其实很多点都想到了,当时遇到的问题就是不知道如何对 \(nums1[i]+nums2[i]\times t\) 排序,没想到要用动态规划,而且动态规划的建模方式有点技巧性,利用了排序来确定选择的第 \(j\) 个数就是在时间 \(j\) 操作的数。

状态定义:\(dp[i][j]\) 表示从前 \(i\) 个数中选择 \(j\) 个数进行操作,可以使元素和减少的最大值(相对于不进行任何操作)。因为我们将 \(aux\) 按照 \(nums_{2}\) 从小到大排序,所以如果 \(i\) 是选择的第 \(j\) 个数,那么就表示在时间 \(j\) 操作 \(i\),因此减少的时间为 \(nums_{1}[aux[i]]+nums_{2}[aux[i]]\times j\)。

状态转移方程:\(dp[i+1][j]=\max(dp[i][j],dp[i][j-1]+nums_{1}[aux[i]]+nums_{2}[aux[i]]\times j)\)。

可以将空间复杂度优化为 \(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
class Solution {
public int minimumTime(List<Integer> nums1, List<Integer> nums2, int x) {
int n = nums1.size(), sum1 =0, sum2 = 0;
Integer[] aux = new Integer[n];
for (int i = 0; i < n; i++) {
aux[i] = i;
sum1 += nums1.get(i);
sum2 += nums2.get(i);
}
Arrays.sort(aux, (a, b) -> nums2.get(a) - nums2.get(b));
// 动态规划
int[][] dp = new int[n + 1][n + 1];
for (int i = 0; i < n; i++) {
for (int j = n; j > 0; j--) {
dp[i + 1][j] = Math.max(dp[i][j], dp[i][j - 1] + nums1.get(aux[i]) + nums2.get(aux[i]) * j);
}
}
// 枚举答案
for (int i = 0; i <= n; i++) {
if (sum1 + sum2 * i - dp[n][i] <= x) {
return i;
}
}
return -1;
}
}

复杂度分析

  • 时间复杂度:\(O(n^{2})\)。
  • 空间复杂度:\(O(n^{2})\)。

第 356 场力扣周赛

满足目标工作时长的员工数目

方法一:遍历

1
2
3
4
5
6
7
class Solution {
public int numberOfEmployeesWhoMetTarget(int[] hours, int target) {
int ans = 0;
for (int x : hours) if (x >= target) ans++;
return ans;
}
}

复杂度分析

  • 时间复杂度:\(O(n)\)。
  • 空间复杂度:\(O(1)\)。

统计完全子数组的数目

方法一:暴力优化

比赛时本来是想滑窗的,但是当时没想通。而枚举左右端点再遍历的暴力方法,时间复杂度为 \(O(n^{3})\) 会超时。结果想半天发现可以使用前缀和的思路,先枚举左端点,然后一边遍历一边枚举右端点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public int countCompleteSubarrays(int[] nums) {
int n = nums.length;
Set<Integer> set = new HashSet<>();
for (int x : nums) set.add(x);
int cnt = set.size(), ans = 0;
// 至少要有 cnt 个元素
for (int i = 0; i <= n - cnt; i++) {
set.clear();
for (int j = i; j < n; j++) {
set.add(nums[j]);
if (set.size() == cnt) {
ans += n - j;
break;
}
}
}
return ans;
}
}

复杂度分析

  • 时间复杂度:\(O(n^{2})\)。
  • 空间复杂度:\(O(n)\)。

方法二:滑动窗口

枚举右端点,并且让窗口是完全子数组的前提下,使左端点尽可能靠右,此时所有小于等于左端点的位置,都满足条件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public int countCompleteSubarrays(int[] nums) {
int n = nums.length;
Set<Integer> set = new HashSet<>();
for (int x : nums) set.add(x);
int cnt = set.size();
int lo = 0, hi = 0, ans = 0;
Map<Integer, Integer> map = new HashMap<>();
while (hi < n) {
map.merge(nums[hi++], 1, Integer::sum);
if (map.size() == cnt) {
while (map.get(nums[lo]) > 1) {
map.merge(nums[lo++], -1, Integer::sum);
}
ans += lo + 1;
}
}
return ans;
}
}

复杂度分析

  • 时间复杂度:\(O(n)\)。
  • 空间复杂度:\(O(n)\)。

包含三个字符串的最短字符串

方法一:枚举

枚举字符串 \(a,b,c\) 的全排列,然后从前往后合并,以消除公共字符。需要注意,如果字符串存在包含关系,则不需要进行合并操作。

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
class Solution {
public String minimumString(String a, String b, String c) {
List<String> list = new ArrayList<>();
list.add(merge(merge(a, b), c));
list.add(merge(merge(a, c), b));
list.add(merge(merge(b, a), c));
list.add(merge(merge(b, c), a));
list.add(merge(merge(c, a), b));
list.add(merge(merge(c, b), a));
list.sort((s1, s2) -> {
int m = s1.length(), n = s2.length();
if (m != n) return m - n;
return s1.compareTo(s2);
});
return list.get(0);
}

private String merge(String a, String b) {
if (a.contains(b)) return a;
int m = a.length(), n = b.length();
for (int i = Math.min(m, n); ; i--) {
if (a.substring(m - i).equals(b.substring(0, i))) {
return a + b.substring(i);
}
}
}
}

复杂度分析

  • 时间复杂度:\(O(n^{2})\),其中 \(n\) 为字符串 \(a,b,c\) 长度的最大值。
  • 空间复杂度:\(O(n)\)。

统计范围内的步进数字数目

方法一:数位DP

感觉有点像 DFS,枚举当前位的数字,多传递一个参数 isLimit 可以省去很多判断。

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

public int countSteppingNumbers(String low, String high) {
int m = low.length(), n = high.length();
// dp[i][j] 表示 i 位数的最高位为 j 的步进数字的数目
int[][] dp = new int[n][10];
Arrays.fill(dp[0], 1);
for (int i = 1; i < n; i++) {
for (int j = 0; j < 10; j++) {
if (j - 1 >= 0) dp[i][j] = (dp[i][j] + dp[i - 1][j - 1]) % MOD;
if (j + 1 <= 9) dp[i][j] = (dp[i][j] + dp[i - 1][j + 1]) % MOD;
}
}
// 字符串不方便做减法,所以先减,如果 low 是步进数字则加回来
return (f(dp, high, 0, -1, true) - f(dp, low, 0, -1, true) + valid(low) + MOD) % MOD;
}

private int f(int[][] dp, String s, int i, int pre, boolean isLimit) {
int n = s.length();
// 如果数字不为空,则计数值加一
if (i == n) return pre != -1 ? 1 : 0;
if (pre != -1 && !isLimit) return dp[n - i][pre];
int cur = s.charAt(i) - '0', res = 0;
int hi = isLimit ? cur : 9;
// 如果选 0 并且数字为空,则表示跳过当前位
for (int j = 0; j <= hi; j++) {
if (pre == -1 || Math.abs(j - pre) == 1) {
res = (res + f(dp, s, i + 1, (pre == -1 && j == 0) ? -1 : j, isLimit && j == hi)) % MOD;
}
}
return res;
}

private int valid(String s) {
int n = s.length();
for (int i = 1; i < n; i++) {
if (Math.abs(s.charAt(i) - s.charAt(i - 1)) != 1) {
return 0;
}
}
return 1;
}
}

复杂度分析

  • 时间复杂度:\(O(nm^{2})\),其中 \(n\) 为 high 的长度,\(m = 10\)。
  • 空间复杂度:\(O(nm)\)。