7-周赛333总结

7-周赛333总结

还是只过了前两题,第三题又写了好久好久,然后也不知道错在了哪里,只过了部分题解,也许是思考不全面吧。下次也许先做第四题更好…第四题今天花了点时间 做出来了个大概 开心 :happy:

合并两个二维数组 - 求和法【LC2570】

给你两个 二维 整数数组 nums1nums2.

  • nums1[i] = [idi, vali] 表示编号为 idi 的数字对应的值等于 vali
  • nums2[i] = [idi, vali] 表示编号为 idi 的数字对应的值等于 vali

每个数组都包含 互不相同 的 id ,并按 id 以 递增 顺序排列。

请你将两个数组合并为一个按 id 以递增顺序排列的数组,并符合下述条件:

  • 只有在两个数组中至少出现过一次的 id 才能包含在结果数组内。
  • 每个 id 在结果数组中 只能出现一次 ,并且其对应的值等于两个数组中该 id 所对应的值求和。如果某个数组中不存在该 id ,则认为其对应的值等于 0

返回结果数组。返回的数组需要按 id 以递增顺序排列。

  • 思路:双指针、归并排序

    使用双指针指向数组nums1nums2中的元素,优先取id较小的指针对应的二元组,如果两个指针指向的id相同,那么二元组的值为两个指针对应的值之和

  • 实现

    由于不确定有多少元素重复,因此可以先将结果存储在动态数组中,最后在将结果赋值至int数组中

    class Solution {
        public int[][] mergeArrays(int[][] nums1, int[][] nums2) {
            int n = nums1.length, m = nums2.length;
            List<int[]> list = new ArrayList<>();
            int i = 0, j = 0;
            while (i < n || j < m){
                int[] add = new int[2];
                if (j == m || (i < n && nums1[i][0] < nums2[j][0])){
                    add[0] = nums1[i][0];
                    add[1] = nums1[i][1];
                    i++;
                }else if(i == n || (j < m && nums1[i][0] > nums2[j][0])){
                    add[0] = nums2[j][0];
                    add[1] = nums2[j][1];
                    j++;
                }else{
                    add[0] = nums1[i][0];
                    add[1] = nums1[i][1] + nums2[j][1]; 
                    i++;
                    j++;
                }
                list.add(add);
            }
            int[][] res = new int[list.size()][2];
            for (int k = 0; k < list.size(); k++){
                res[k] = list.get(k);
            }
            return res;
    
        }
    }
    
    • 复杂度
      • 时间复杂度: O ( n + m ) O(n+m) O(n+m)
      • 空间复杂度: O ( n + m ) O(n+m) O(n+m),动态数组的额外空间

将整数减少到零需要的最少操作数【LC2571】

给你一个正整数 n ,你可以执行下述操作 任意 次:

  • n 加上或减去 2 的某个

返回使 n 等于 0 需要执行的 最少 操作数。

如果 x == 2i 且其中 i >= 0 ,则数字 x2 的幂。

  • 思路:贪心

    由于操作只能加上或减去 2 的某个 ,因此采用位运算的方式解决此题,求取n的二进制形式所有1变为0时所需要的操作数。

    从最低位开始,找到每个值为1的位,假定为第 i i i位,而第一个为0的位为第 j j j位,当连续1的个数等于1时,采用减幂消除1;当连续1的个数大于1时,采用加幂进位的方式消除1

    • 当连续1的个数等于1时,减去该位对应的幂,将该位变为0,所需要的操作数为1次;

    • 而当连续1的个数大于1时,可以通过加2的最低1位对应的幂进位,此时所有的连续1变为0,然后产生了进位,第 j j j位变为1,所需要的操作数也为1次;【使用减幂方式所需要的操作数为 j − i j-i ji,而使用进位的方式所需要的操作数为2次,并且消除进位产生的1的同时可能会消除更多的1,因此连续1的个数大于1时,采用进位的方式消除1时最优的】

    局部最优和全局最优

    • 局部最优:将连续的所有1变为0的次数最少
    • 全局最优:将n变为0的操作数最少
  • 实现

    class Solution {
        public int minOperations(int n) {
            
            // 如果遇到连续1个数大于1时,进位 操作数加1
            // 连续1个数等于1,直接减操作
            int res = 0;
            int i = 0;
            while (n != 0){
                // 找到从左到右第一个1
                while (((n >> i) & 1) == 0){
                    i++;
                }
                int j = i;
                // 找到从左到右第一个0
                while (((n >> j) & 1)  == 1){
                    j++;
                }
                if (j - i > 1){
                    n += Math.pow(2, i);
                    res++;
                }else{
                    n -= Math.pow(2, i);
                    res++;
                }
                i = j;
            }
            return res;
            
    
        }
    }
    
    • 复杂度
      • 时间复杂度: O ( l o g n ) O(logn) O(logn)
      • 空间复杂度: O ( 1 ) O(1) O(1)
  • 优化:

    使用位运算获取最低位 lb = n & -n,然后使用n & (lb << 1)) > 0判断是否有连续1

    class Solution {
        public int minOperations(int n) {
            int ans = 1;
            while ((n & (n - 1)) > 0) { // n 不是 2 的幂次
                int lb = n & -n;
                if ((n & (lb << 1)) > 0) n += lb; // 多个连续 1
                else n -= lb; // 单个 1
                ++ans;
            }
            return ans;
        }
    }
    
    作者:灵茶山艾府
    链接:https://leetcode.cn/problems/minimum-operations-to-reduce-an-integer-to-0/solutions/2120204/ji-yi-hua-sou-suo-by-endlesscheng-cm6l/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    
    class Solution {
        public int minOperations(int n) {
            int ans = 0;
            while (n != 0) { 
                int lb = n & -n;
                if ((n & (lb << 1)) > 0) n += lb; // 多个连续 1
                else n -= lb; // 单个 1
                ++ans;
            }
            return ans;
        }
    }
    
    

*无平方子集计数【LC2572】

给你一个正整数数组 nums

如果数组 nums 的子集中的元素乘积是一个 无平方因子数 ,则认为该子集是一个 无平方 子集。

无平方因子数 是无法被除 1 之外任何平方数整除的数字。

返回数组 nums无平方非空 的子集数目。因为答案可能很大,返回对 109 + 7 取余的结果。

nums非空子集 是可以由删除 nums 中一些元素(可以不删除,但不能全部删除)得到的一个数组。如果构成两个子集时选择删除的下标不同,则认为这两个子集不同。

  • 思路:

    • 如果两个无平方因子数的最大公因数为1,那么它们可以存在一个子集当中。

    • 由于本题中nums的数值大小小于等于30,因此可以对该范围的数进行预处理,判断每个数是否是无平方因子数,如果是无平方因子数则求出每个数对应质因数的集合,并使用状态压缩法表示,当mask的第 i i i位为1是第 i i i个质数在集合中;如果不是无平方因子数,那么mask设为-1。

    • 题意可转化为「选一些不相交的质数集合,它们的并集恰好为集合 j j j的方案数」。【01背包问题】

      物品即为每个数字分解的质因数集合,背包容量为背包可以容纳的质因数对应的状态码,当物品对应质因数集合是背包容量的子集时,则可以向该背包放入该物品

  • 二维动态规划

    1. 确定dp数组(dp table)以及下标的含义

      dp[i][j]表示 从前i个元素中取若干元素,质因数出现的情况为j的方案数

    2. 确定递推公式

      • 不放元素i: d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j] = dp[i-1][j] dp[i][j]=dp[i1][j]

      • 放元素i:当nums[i]对应的状态码为mask,前一个状态与mask不能相交,因此前一个状态码为mask^j

        d p [ i ] [ j ] = d p [ i − 1 ] [ j ⊕ m a s k ] dp[i][j] = dp[i-1][j\oplus mask] dp[i][j]=dp[i1][jmask]

      动态规划公式为
      d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j ⊕ m a s k ] dp[i][j] = dp[i-1][j]+ dp[i-1][j \oplus mask] dp[i][j]=dp[i1][j]+dp[i1][jmask]

    3. dp数组如何初始化

      dp[0][0] = 1;
      
    4. 确定遍历顺序

      二维dp,遍历顺序不影响结果

      先遍历物品,再遍历背包重量,确定物品i能否放进背包j中

    5. 举例推导dp数组

    最后结果即为 ∑ d p [ n − 1 ] [ j ] \sum dp[n-1][j] dp[n1][j]

    class Solution {
        private static final int[] PRIMES = new int[]{2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
        private static final int MOD = (int) 1e9 + 7, MX = 30, N_PRIMES = PRIMES.length, M = 1 << N_PRIMES;
        private static final int[] NSQ_TO_MASK = new int[MX + 1]; // NSQ_TO_MASK[i] 为 i 对应的质数集合(用二进制表示)
    
        static {
            for (int i = 2; i <= MX; ++i)
                for (int j = 0; j < N_PRIMES; ++j) {
                    int p = PRIMES[j];
                    if (i % p == 0) {
                        if (i % (p * p) == 0) { // 有平方因子
                            NSQ_TO_MASK[i] = -1;
                            break;
                        }
                        NSQ_TO_MASK[i] |= 1 << j; // 把 j 加到集合中
                    }
                }
        }
    
        public int squareFreeSubsets(int[] nums) {
            int n = nums.length;
            int[][] dp = new int[n + 1][M]; // f[i][j] 表示恰好组成集合 j 的方案数 
            // Arrays.fill(dp[0], 1);// 空集的方案数为 1
            dp[0][0] = 1;
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < M; j++) dp[i + 1][j] = dp[i][j];// 不选 mask
                int mask = NSQ_TO_MASK[nums[i]];
                if (mask >= 0) // x 是 NSQ
                    for (int j = mask; j <= M - 1; j++){
                        if ((j | mask) == j)  // mask 是 j 的子集
                            dp[i + 1][j] = (dp[i + 1][j] + dp[i][j ^ mask]) % MOD; // 选 mask
                    }
                        
            }
            int ans = 0;
            for (int j = 0; j < M; j++){
                ans = (ans + dp[n][j]) % MOD;
            }
            return ans - 1;
        }
    }
    
    • 复杂度
      • 时间复杂度: O ( n M ) O(nM) O(nM),n为数组长度,M为状态压缩后包含质因子情况的总数
      • 空间复杂度: O ( n ∗ M ) O(n*M) O(nM),dp数组的额外空间
  • 优化:一维dp

    先遍历物品,再从后往前遍历背包重量,确定物品i能否放进背包j中

    class Solution {
        private static final int[] PRIMES = new int[]{2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
        private static final int MOD = (int) 1e9 + 7, MX = 30, N_PRIMES = PRIMES.length, M = 1 << N_PRIMES;
        private static final int[] NSQ_TO_MASK = new int[MX + 1]; // NSQ_TO_MASK[i] 为 i 对应的质数集合(用二进制表示)
    
        static {
            for (int i = 2; i <= MX; ++i)
                for (int j = 0; j < N_PRIMES; ++j) {
                    int p = PRIMES[j];
                    if (i % p == 0) {
                        if (i % (p * p) == 0) { // 有平方因子
                            NSQ_TO_MASK[i] = -1;
                            break;
                        }
                        NSQ_TO_MASK[i] |= 1 << j; // 把 j 加到集合中
                    }
                }
        }
    
        public int squareFreeSubsets(int[] nums) {
            var f = new int[M]; // f[j] 表示恰好组成集合 j 的方案数
            f[0] = 1; // 空集的方案数为 1
            for (int x : nums) {
                int mask = NSQ_TO_MASK[x];
                if (mask >= 0) // x 是 NSQ
                    for (int j = M - 1; j >= mask; --j)
                        if ((j | mask) == j)  // mask 是 j 的子集
                            f[j] = (f[j] + f[j ^ mask]) % MOD; // 不选 mask + 选 mask
            }
            var ans = 0L;
            for (int v : f) ans += v;
            return (int) ((ans - 1) % MOD); // -1 去掉空集
        }
    }
    
    作者:灵茶山艾府
    链接:https://leetcode.cn/problems/count-the-number-of-square-free-subsets/solutions/2121032/liang-chong-xie-fa-01bei-bao-zi-ji-zhuan-3ooi/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    
    • 复杂度
      • 时间复杂度: O ( n M ) O(nM) O(nM),n为数组长度,M为状态压缩后包含质因子情况的总数
      • 空间复杂度: O ( M ) O(M) O(M),dp数组的额外空间

找出对应 LCP 矩阵的字符串【LC2573】

对任一由 n 个小写英文字母组成的字符串 word ,我们可以定义一个 n x n 的矩阵,并满足:

  • lcp[i][j] 等于子字符串 word[i,...,n-1]word[j,...,n-1] 之间的最长公共前缀的长度。

给你一个 n x n 的矩阵 lcp 。返回与 lcp 对应的、按字典序最小的字符串 word 。如果不存在这样的字符串,则返回空字符串。

对于长度相同的两个字符串 ab ,如果在 ab 不同的第一个位置,字符串 a 的字母在字母表中出现的顺序先于 b 中的对应字母,则认为字符串 a 按字典序比字符串 b 小。例如,"aabd" 在字典上小于 "aaca" ,因为二者不同的第一位置是第三个字母,而 'b' 先于 'c' 出现。

按列扫描

  • 思路:按规则按列逐个构造

    • 首先,由于题目要求返回的合法字符串是字典顺序最小的,因此第一个字符一定是a

    • 然后从开头到末尾逐个构造字符,当我们构造第 i i i个字符时,前 j ∈ [ 0 , i − 1 ] j \in [0,i-1] j[0,i1]个字符是确定的,那么当 l c p [ j ] [ i ] > 0 lcp[j][i]>0 lcp[j][i]>0时,第 i i i个字符就等于第 j j j个字符,直接退出本层循环;如果第 i i i个字符与前面的字符均不相同,那么第 i i i个字符为当前已经构造的最大字符的下一个字符。

    • 需要注意的是,如果构造的字符大于z,那么不符合题意,返回空字符串

    • 最后还需要验证构造的字符串是否满足矩阵,如果不满足,也返回空字符串。

      验证方法为动态规划

      • 如果 s [ i ] = s [ j ] s[i]=s[j] s[i]=s[j],那么 l c p [ i ] [ j ] = l c p [ i + 1 ] [ j + 1 ] + 1 lcp[i][j]=lcp[i+1][j+1]+1 lcp[i][j]=lcp[i+1][j+1]+1
      • 如果 s [ i ] ≠ s [ j ] s[i]\ne s[j] s[i]=s[j],那么 l c p [ i ] [ j ] = 0 lcp[i][j]=0 lcp[i][j]=0
  • 实现

    按列扫描lcp数组

    class Solution {
        public String findTheString(int[][] lcp) {
            int n = lcp.length;
            // 如果矩阵对称并且值小于等于字符串长度时,有满足的字符串【不全面】
            // for (int i = 0; i < n; i++){
            //     for (int j = 0; j < n; j++){
            //         if (lcp[i][j] != lcp[j][i] || lcp[i][j] > Math.min(n - i, n - j)){
            //             return "";
            //         }
            //         if (i == j && lcp[i][j] != n - i){
            //             return "";
            //         }
            //     }
            // }
            StringBuilder sb = new StringBuilder();
            sb.append('a');// 第0个字符一定是a 
            char c = 'a';
            for (int i = 1; i < n; i++){// 按顺序找第1-n-1个字符
                for (int j = 0; j < i; j++){// 根据与前面字符的关系确定                
                    int count = lcp[j][i];// s[j,j+count-1] s[i,i+count-1] 
                    if (count > 0){// s[i] = s[j]
                        sb.append(sb.charAt(j)); 
                        break;                  
                    }
                }
                if (sb.length() < i + 1){
                    if (c == 'z') return "";
                    c += 1;
                    sb.append(c);
                }
            }
            // 验证
             for (int i = n - 1; i >= 0; --i)
                for (int j = n - 1; j >= 0; --j) {
                    int actualLCP = sb.charAt(i) != sb.charAt(j) ? 0 : i == n - 1 || j == n - 1 ? 1 : lcp[i + 1][j + 1] + 1;
                    if (lcp[i][j] != actualLCP) return "";
                }
            return sb.toString();
    
        }
    
    }
    
    • 复杂度
      • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
      • 空间复杂度: O ( 1 ) O(1) O(1)

按行扫描

  • 思路

    • 如果 l c p [ i ] [ j ] > 0 lcp[i][j]>0 lcp[i][j]>0时, s [ i ] s[i] s[i]一定等于 s [ j ] s[j] s[j],而第一个字符一定是a,因此可以先扫描lcp[0],如果 l c p [ 0 ] [ j ] > 0 lcp[0][j]>0 lcp[0][j]>0,那么该字符一定是a
    • 然后进行下一轮,本轮的字符为b,找到从左到右数首个没有填入字符的位置 i i i,将其填入当前已经构造的最大字符的下一个字符,然后再扫描lcp[i],如果 l c p [ 0 ] [ j ] > 0 lcp[0][j]>0 lcp[0][j]>0,那么填入本轮的字符。往复循环,直至 i i i大于字符串长度,或者字符超过了z
    • 如果退出循环时, i i i小于 n n n,表示没有构造完,直接返回空字符串
    • 最后,进行验证,验证方法同上
  • 实现

    class Solution {
        public String findTheString(int[][] lcp) {
            int i = 0, n = lcp.length;
            var s = new char[n];
            for (char c = 'a'; c <= 'z'; ++c) {
                while (i < n && s[i] > 0) ++i;
                if (i == n) break; // 构造完毕
                for (int j = i; j < n; ++j)
                    if (lcp[i][j] > 0) s[j] = c;
            }
            while (i < n) if (s[i++] == 0) return ""; // 没有构造完
    
            // 直接在原数组上验证
            for (i = n - 1; i >= 0; --i)
                for (int j = n - 1; j >= 0; --j) {
                    int actualLCP = s[i] != s[j] ? 0 : i == n - 1 || j == n - 1 ? 1 : lcp[i + 1][j + 1] + 1;
                    if (lcp[i][j] != actualLCP) return "";
                }
            return new String(s);
        }
    }
    
    作者:灵茶山艾府
    链接:https://leetcode.cn/problems/find-the-string-with-lcp/solutions/2120175/tan-xin-gou-zao-yan-zheng-o1e-wai-kong-j-82ik/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    
    • 复杂度
      • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
      • 空间复杂度: O ( 1 ) O(1) O(1)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值