7-周赛333总结
还是只过了前两题,第三题又写了好久好久,然后也不知道错在了哪里,只过了部分题解,也许是思考不全面吧。下次也许先做第四题更好…第四题今天花了点时间 做出来了个大概 开心 :happy:
合并两个二维数组 - 求和法【LC2570】
给你两个 二维 整数数组 nums1
和 nums2.
nums1[i] = [idi, vali]
表示编号为idi
的数字对应的值等于vali
。nums2[i] = [idi, vali]
表示编号为idi
的数字对应的值等于vali
。
每个数组都包含 互不相同 的 id ,并按 id 以 递增 顺序排列。
请你将两个数组合并为一个按 id 以递增顺序排列的数组,并符合下述条件:
- 只有在两个数组中至少出现过一次的 id 才能包含在结果数组内。
- 每个 id 在结果数组中 只能出现一次 ,并且其对应的值等于两个数组中该 id 所对应的值求和。如果某个数组中不存在该 id ,则认为其对应的值等于
0
。
返回结果数组。返回的数组需要按 id 以递增顺序排列。
- 思路:双指针、归并排序
使用双指针指向数组nums1
和nums2
中的元素,优先取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 ) ,动态数组的额外空间
将整数减少到零需要的最少操作数【LC2571】
给你一个正整数
n
,你可以执行下述操作 任意 次:
n
加上或减去2
的某个 幂返回使
n
等于0
需要执行的 最少 操作数。如果
x == 2i
且其中i >= 0
,则数字x
是2
的幂。
思路:贪心
由于操作只能加上或减去 2
的某个 幂,因此采用位运算的方式解决此题,求取n
的二进制形式所有1变为0时所需要的操作数。
从最低位开始,找到每个值为1的位,假定为第i ii位,而第一个为0的位为第j jj位,当连续1的个数等于1时,采用减幂消除1;当连续1的个数大于1时,采用加幂进位的方式消除1
- 当连续1的个数等于1时,减去该位对应的幂,将该位变为0,所需要的操作数为1次;
- 而当连续1的个数大于1时,可以通过加2的最低1位对应的幂进位,此时所有的连续1变为0,然后产生了进位,第j 位变为1,所需要的操作数也为1次;【使用减幂方式所需要的操作数为j − i j-ij−i,而使用进位的方式所需要的操作数为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; } }
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 位为1是第i 个质数在集合中;如果不是无平方因子数,那么mask设为-1。
题意可转化为「选一些不相交的质数集合,它们的并集恰好为集合 j 的方案数」。【01背包问题】
原文链接:https://blog.csdn.net/Tikitian/article/details/129132416
- 物品即为每个数字分解的质因数集合,背包容量为背包可以容纳的质因数对应的状态码,当物品对应质因数集合是背包容量的子集时,则可以向该背包放入该物品
- 二维动态规划
- 确定dp数组(dp table)以及下标的含义
- dp[i][j]表示 从前i个元素中取若干元素,质因数出现的情况为j的方案数。
- 确定递推公式
3.dp数组如何初始化
dp[0][0] = 1;
4.确定遍历顺序
二维dp,遍历顺序不影响结果
先遍历物品,再遍历背包重量,确定物品i能否放进背包j中
5.举例推导dp数组
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; } }
优化:一维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) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
找出对应 LCP 矩阵的字符串【LC2573】
对任一由
n
个小写英文字母组成的字符串word
,我们可以定义一个n x n
的矩阵,并满足:
lcp[i][j]
等于子字符串word[i,...,n-1]
和word[j,...,n-1]
之间的最长公共前缀的长度。给你一个
n x n
的矩阵lcp
。返回与lcp
对应的、按字典序最小的字符串word
。如果不存在这样的字符串,则返回空字符串。对于长度相同的两个字符串
a
和b
,如果在a
和b
不同的第一个位置,字符串a
的字母在字母表中出现的顺序先于b
中的对应字母,则认为字符串a
按字典序比字符串b
小。例如,"aabd"
在字典上小于"aaca"
,因为二者不同的第一位置是第三个字母,而'b'
先于'c'
出现。
按列扫描
- 思路:按规则按列逐个构造
- 首先,由于题目要求返回的合法字符串是字典顺序最小的,因此第一个字符一定是
a
实现
按列扫描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(); } }
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 ( 1 )