14-周赛348总结

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 的整数序列,其中包含从 1n 的每个数字恰好一次。

  • 思路:分类讨论

    找到数字 1 1 1 n n n位于的下标,记为index1indexN。我们需要把 1 1 1左移至位置 0 0 0,把 n n n右移至位置 n − 1 n-1 n1。根据index1indexN的大小情况有以下两种情况:

    • 如果 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+n1indexN
    • 如果 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+n2indexN
  • 实现

    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(nm2),对某列进行操作时对结果的贡献是 v a l ∗ ( n − m 1 ) val*(n-m_1) val(nm1)
  • 实现

    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】

给你两个数字字符串 num1num2 ,以及两个整数 max_summin_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 i1位数位之和为 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 i1位的数位之和
    • 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 i1位填充是否受限,以及当前填充的数是否达到上限值
    • 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)fmin1(0,0,true)

  • 实现

    • 由于数据范围1 <= num1 <= num2 <= 1022,使用BigInteger类找到 n u m 1 − 1 num1-1 num11对应的字符串【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 nnum2的长度
      • 空间复杂度: O ( n ∗ m a x _ s u m ) \mathcal{O}(n*max\_sum) O(nmax_sum)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值