14-周赛348总结
T3还是想不到想不到,技巧性好强
T4数位dp花了点时间做出来了
最小化字符串长度【LC2716】
给你一个下标从 0 开始的字符串
s
,重复执行下述操作 任意 次:
- 在字符串中选出一个下标
i
,并使c
为字符串下标i
处的字符。并在i
左侧(如果有)和 右侧(如果有)各 删除 一个距离i
最近 的字符c
。请你通过执行上述操作任意次,使
s
的长度 最小化 。返回一个表示 最小化 字符串的长度的整数。
哈希表
-
思路
每次操作选择一个字符,可以删除其左右的相同字符,执行任意次后,最终每种字符只剩下一个,因此最终结果即为字符串中的字符种类数目
-
实现
class Solution { public int minimizedStringLength(String s) { int n = s.length(); Set<Character> set = new HashSet<>(); for (char c : s.toCharArray()){ set.add(c); } return set.size(); } }
- 复杂度
- 时间复杂度: O ( n ) \mathcal{O}(n) O(n)
- 空间复杂度: O ( C ) \mathcal{O}(C) O(C), C C C为字符集大小,本题中为26
- 复杂度
位运算
class Solution {
public int minimizedStringLength(String s) {
int mask = 0;
for (var c : s.toCharArray())
mask |= 1 << (c - 'a');
return Integer.bitCount(mask);
}
}
作者:灵茶山艾府
链接:https://leetcode.cn/problems/minimize-string-length/solutions/2296066/o1-kong-jian-wei-yun-suan-xie-fa-pythonj-7t4p/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
半有序排列【LC2717】
给你一个下标从 0 开始、长度为
n
的整数排列nums
。如果排列的第一个数字等于
1
且最后一个数字等于n
,则称其为 半有序排列 。你可以执行多次下述操作,直到将nums
变成一个 半有序排列 :
- 选择
nums
中相邻的两个元素,然后交换它们。返回使
nums
变成 半有序排列 所需的最小操作次数。排列 是一个长度为
n
的整数序列,其中包含从1
到n
的每个数字恰好一次。
-
思路:分类讨论
找到数字 1 1 1和 n n n位于的下标,记为
index1
和indexN
。我们需要把 1 1 1左移至位置 0 0 0,把 n n n右移至位置 n − 1 n-1 n−1。根据index1
和indexN
的大小情况有以下两种情况:- 如果 i n d e x 1 > i n d e x N index1 \gt indexN index1>indexN,那么移动1和移动n互不干扰,结果即为 i n d e x 1 + n − 1 − i n d e x N index1+n-1-indexN index1+n−1−indexN
- 如果 i n d e x 1 > i n d e x N index1 \gt indexN index1>indexN,那么移动1时会把n向右移动一位,那么可以减少一步操作,结果为 i n d e x 1 + n − 2 − i n d e x N index1+n-2-indexN index1+n−2−indexN
-
实现
class Solution { public int semiOrderedPermutation(int[] nums) { int n = nums.length; if (nums[0] == 1 && nums[n - 1] == n){ return 0; } int index1 = -1, indexN = -1; // 先找到1和n的下标 for (int i = 0; i < n; i++){ if (nums[i] == 1){ index1 = i; }else if(nums[i] == n){ indexN = i; } } int res = index1 + (n - 1 - indexN); if (index1 > indexN){// indexN后移一格 return res - 1; } return res; } }
- 复杂度
- 时间复杂度: O ( n ) \mathcal{O}(n) O(n)
- 空间复杂度: O ( 1 ) \mathcal{O}(1) O(1)
- 复杂度
查询后矩阵的和【LC2718】
给你一个整数
n
和一个下标从 0 开始的 二维数组queries
,其中queries[i] = [typei, indexi, vali]
。一开始,给你一个下标从 0 开始的
n x n
矩阵,所有元素均为0
。每一个查询,你需要执行以下操作之一:
- 如果
typei == 0
,将第indexi
行的元素全部修改为vali
,覆盖任何之前的值。- 如果
typei == 1
,将第indexi
列的元素全部修改为vali
,覆盖任何之前的值。请你执行完所有查询以后,返回矩阵中所有整数的和。
-
思路:逆序操作
- 如果对每个位置进行反复操作,那么只有最后一次的操作会计入答案,那么可以倒序操作
queries
- 在倒序求解答案时,如果某个位置已经有数字填入,那么不需要进行修改,那么我们需要关注的是 ⌈ \lceil ⌈每行或者每列还没有填过数字的数目 ⌋ \rfloor ⌋,因此可以使用两个哈希表分别记录已经填过数字的行号和列号,哈希表的大小记为 m 1 m_1 m1和 m 2 m_2 m2
- 那么某行某列还没有进行操作过时,对某行进行操作时对结果的贡献是 v a l ∗ ( n − m 2 ) val*(n-m_2) val∗(n−m2),对某列进行操作时对结果的贡献是 v a l ∗ ( n − m 1 ) val*(n-m_1) val∗(n−m1),
- 如果对每个位置进行反复操作,那么只有最后一次的操作会计入答案,那么可以倒序操作
-
实现
class Solution { public long matrixSumQueries(int n, int[][] queries) { long res = 0L; Set<Integer> visRow = new HashSet<>(); Set<Integer> visCol = new HashSet<>(); for (int i = queries.length - 1; i >= 0; i--){ if (queries[i][0] == 0 && !visRow.contains(queries[i][1])){// 对行操作 res += queries[i][2] * (n - visCol.size()); visRow.add(queries[i][1]); }else if(queries[i][0] == 1 && !visCol.contains(queries[i][1])){ res += queries[i][2] * (n - visRow.size()); visCol.add(queries[i][1]); } } return res; } }
- 复杂度
- 时间复杂度: O ( q ) \mathcal{O}(q) O(q),q为查询的个数
- 空间复杂度: O ( m i n { q , n } ) \mathcal{O}(min\{q,n\}) O(min{q,n}),哈希表中最多有 m i n { q , n } min\{q,n\} min{q,n}个数
- 复杂度
统计整数数目【LC2719】
给你两个数字字符串
num1
和num2
,以及两个整数max_sum
和min_sum
。如果一个整数x
满足以下条件,我们称它是一个好整数:
num1 <= x <= num2
min_sum <= digit_sum(x) <= max_sum
.请你返回好整数的数目。答案可能很大,请返回答案对
109 + 7
取余后的结果。注意,
digit_sum(x)
表示x
各位数字之和。
-
思路:数位dp
数字范围为 [ 0 , n ] [0,n] [0,n]时,定义 f ( i , s u m , i s L i m i t ) f(i,sum,isLimit) f(i,sum,isLimit)表示前 i − 1 i-1 i−1位数位之和为 s u m sum sum时,构造从左往右第 i i i位及其之后的数位,总数位之和在有效范围 [ m i n _ s u m , m a x _ s u m ] [min\_sum,max\_sum] [min_sum,max_sum]中的个数
- i i i表示当前构造至从左往右第 i i i位
- s u m sum sum表示第 0 0 0位至第 i − 1 i-1 i−1位的数位之和
-
i
s
L
i
m
i
t
isLimit
isLimit 表示当前是否受到了
n
n
n的约束。若为真,则第
i
i
i 位填入的数字至多为
s
[
i
]
s[i]
s[i],否则可以枚举至 9。如果在受到约束的情况下填了
s
[
i
]
s[i]
s[i],那么后续填入的数字仍会受到
n
n
n的约束。
- 取决于第 i − 1 i-1 i−1位填充是否受限,以及当前填充的数是否达到上限值
- i s N u m isNum isNum 可以忽略对于本题来说,由于前导零对答案无影响,isNum可以省略
那么最终答案即为 f m a x ( 0 , 0 , t r u e ) − f m i n − 1 ( 0 , 0 , t r u e ) f_{max}(0,0,true)-f_{min-1}(0,0,true) fmax(0,0,true)−fmin−1(0,0,true)
-
实现
- 由于数据范围
1 <= num1 <= num2 <= 1022
,使用BigInteger类找到 n u m 1 − 1 num1-1 num1−1对应的字符串【WA了一次,也可以实现字符串减法,偷懒了】 - 相减后取余会出现负值,因此需要先加上MOD再取余【WA了一次】
import java.math.*; class Solution { int MOD = (int)(1e9 + 7); int min_sum; int max_sum; public int count(String num1, String num2, int min_sum, int max_sum) { // 数位dp this.min_sum = min_sum; this.max_sum = max_sum; BigInteger a1 = new BigInteger(num1); BigInteger a2 = new BigInteger("1"); a1 = a1.subtract(a2); // Long down = Long.valueOf(num1) - 1; char[] s1 = a1.toString().toCharArray(); char[] s2 = num2.toCharArray(); int[][] dp1 = new int[s1.length][max_sum + 1]; int[][] dp2 = new int[s2.length][max_sum + 1]; for (int i = 0; i < s1.length; i++){ Arrays.fill(dp1[i], -1); } for (int i = 0; i < s2.length; i++){ Arrays.fill(dp2[i], -1); } // 统计[num1, num2]中数位之和小于等于max_sum大于等于min_sum的个数 return (f(s2, dp2, 0, 0, true) - f(s1,dp1, 0, 0, true) + MOD) % MOD;// 防止出现负数 } // 统计[0, num]中数位之和小于等于max_sum大于等于min_sum的个数 public int f(char[] s, int[][] dp, int i, int sum, boolean isLimit){ if (i == s.length) return sum >= min_sum ? 1 : 0;// 数位之和大于下限,数量+1 if (!isLimit && dp[i][sum] != -1) return dp[i][sum]; int res = 0; int up = isLimit ? s[i] - '0' : 9; for (int a = 0; a <= up; a++){ if (sum + a > maxSum) break;// 数位之和超上限,不可以继续构造 res = (f(s, dp, i + 1, sum + a, isLimit && a == s[i] - '0') + res) % MOD; } if (!isLimit){ dp[i][sum] = res; } return res; } }
- 复杂度
- 时间复杂度:
O
(
n
)
\mathcal{O}(n)
O(n),
n
n
n为
num2
的长度 - 空间复杂度: O ( n ∗ m a x _ s u m ) \mathcal{O}(n*max\_sum) O(n∗max_sum)
- 时间复杂度:
O
(
n
)
\mathcal{O}(n)
O(n),
n
n
n为
- 由于数据范围