12-周赛338总结
前三题做的很快,第三题先超时了一次又因为数据WA了一次,看到第四题,果断放弃,然后恶补了一下拓扑排序
最近事情好多,加油!!
K 件物品的最大和【LC2600】
袋子中装有一些物品,每个物品上都标记着数字
1
、0
或-1
。给你四个非负整数
numOnes
、numZeros
、numNegOnes
和k
。袋子最初包含:
numOnes
件标记为1
的物品。numZeroes
件标记为0
的物品。numNegOnes
件标记为-1
的物品。现计划从这些物品中恰好选出
k
件物品。返回所有可行方案中,物品上所标记数字之和的最大值。
-
思路:贪心
每次优先取数字较大的数字,取完了再取次大的,保证最终数字之和最大
-
实现
class Solution { public int kItemsWithMaximumSum(int numOnes, int numZeros, int numNegOnes, int k) { if (k <= numOnes){ return k; }else if (k > numOnes && k <= numOnes + numZeros){ return numOnes; }else{ return numOnes - (k - numOnes - numZeros); } } }
- 复杂度
- 时间复杂度: O ( 1 ) O(1) O(1)
- 空间复杂度: O ( 1 ) O(1) O(1)
- 复杂度
质数减法运算【LC2601】
给你一个下标从 0 开始的整数数组
nums
,数组长度为n
。你可以执行无限次下述运算:
- 选择一个之前未选过的下标
i
,并选择一个 严格小于nums[i]
的质数p
,从nums[i]
中减去p
。如果你能通过上述运算使得
nums
成为严格递增数组,则返回true
;否则返回false
。严格递增数组 中的每个元素都严格大于其前面的元素
倒序遍历
-
思路:贪心+枚举
倒序遍历数组,如果 n u m s [ i ] ≥ n u m s [ i + 1 ] nums[i]\ge nums[i+1] nums[i]≥nums[i+1],那么需要将 n u m s [ i ] nums[i] nums[i]减去一个质数 p p p使数组满足严格递增。
- 为了使整个数组都严格递增,我们应该使 p p p尽可能小,使 n u m s [ i ] nums[i] nums[i]尽可能大。【贪心】
- n u m s [ i ] < n u m s [ i + 1 ] nums[i]<nums[i+1] nums[i]<nums[i+1]时,需要减去的最小值为 n u m s [ i ] − ( n u m s [ i + 1 ] − 1 ) nums[i] - (nums[i+1] - 1) nums[i]−(nums[i+1]−1),然后我们可以从该值开始找到最小的质数,作为 p p p值,如果无法找到符合条件的 p p p值,那么返回false
-
实现
class Solution { public boolean primeSubOperation(int[] nums) { int n = nums.length; for (int i = n - 2; i >= 0; i--){ if (nums[i] >= nums[i + 1]){// nums[i]需要至少减小至 nums[i+1] - 1 nums[i] - (nums[i+1] - 1) boolean flag = false; for (int j = nums[i] - (nums[i+1] - 1); j < nums[i]; j++){ if (isPrime(j)){ nums[i] -= j; flag = true; break; } } if (!flag) return false; } } return true; } public boolean isPrime(int num){ if (num <= 1) return false; for (int i = 2; i < num; i++){ if (num % i == 0){ return false; } } return true; } }
- 复杂度
- 时间复杂度: O ( n C ) O(nC) O(nC)
- 空间复杂度: O ( 1 ) O(1) O(1)
- 复杂度
正序遍历
-
思路
倒序遍历是保持增序的同时尽可能保留最大值,那么正序遍历是保持增序的同时尽可能将值减小
-
实现:二分查找
假设前一个数字为 n u m s [ i − 1 ] nums[i-1] nums[i−1],当前数组为 n u m s [ i ] nums[i] nums[i],设 p p p是使 n u m s [ i ] − p > n u m s [ i − 1 ] nums[i]-p \gt nums[i-1] nums[i]−p>nums[i−1]的最大质数,即 p p p是小于 n u m s [ i ] − n u m s [ i − 1 ] nums[i]-nums[i-1] nums[i]−nums[i−1]的最大质数,那么我们可以预处理质数列表,然后使用二分查找得到最大质数
class Solution { private final static int MX = 1000; private final static int[] primes = new int[169]; static { var np = new boolean[MX + 1]; int pi = 1; // primes[0] = 0 避免二分越界 for (int i = 2; i <= MX; ++i) if (!np[i]) { primes[pi++] = i; // 预处理质数列表 for (int j = i; j <= MX / i; ++j) np[i * j] = true; } } public boolean primeSubOperation(int[] nums) { int pre = 0; for (int x : nums) { if (x <= pre) return false; int j = lowerBound(primes, x - pre); pre = x - primes[j - 1]; } return true; } // 见 https://www.bilibili.com/video/BV1AP41137w7/ private int lowerBound(int[] nums, int target) { int left = -1, right = nums.length; // 开区间 (left, right) while (left + 1 < right) { // 区间不为空 // 循环不变量: // nums[left] < target // nums[right] >= target int mid = left + (right - left) / 2; if (nums[mid] < target) left = mid; // 范围缩小到 (mid, right) else right = mid; // 范围缩小到 (left, mid) } return right; } } 作者:灵茶山艾府 链接:https://leetcode.cn/problems/prime-subtraction-operation/solutions/2191560/jian-ji-xie-fa-shai-zhi-shu-er-fen-cha-z-wj7i/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
- 复杂度
- 时间复杂度: O ( n l o g U ) O(nlogU) O(nlogU), U U U是1000以内的质数个数
- 空间复杂度: O ( 1 ) O(1) O(1)
- 复杂度
使数组元素全部相等的最少操作次数【LC2602】
给你一个正整数数组
nums
。同时给你一个长度为
m
的整数数组queries
。第i
个查询中,你需要将nums
中所有元素变成queries[i]
。你可以执行以下操作 任意 次:
- 将数组里一个元素 增大 或者 减小
1
。请你返回一个长度为
m
的数组answer
,其中answer[i]
是将nums
中所有元素变成queries[i]
的 最少 操作次数。注意,每次查询后,数组变回最开始的值。
-
思路:排序+二分查找+前缀和
首先,大于 q q q的数字都需要减小至 q q q,小于 q q q的数字都需要增大至 q q q,而数组值都变为 q q q后的和固定为 q ∗ n q*n q∗n,因此我们可以先将数组排序,然后使用二分查找找到小于等于 q q q的最大值的下标,此时我们就将数组分为大于 q q q和小于 q q q的两堆,然后通过前缀和数组快速计算出这两堆的和,将其分别与都为 q q q的和相比就可以得到需要的操作数
-
实现
就是说,注意越界
class Solution { public List<Long> minOperations(int[] nums, int[] queries) { List<Long> res = new ArrayList<>(); Long sum = 0L; int n = nums.length; long[] preSum = new long[n + 1]; Arrays.sort(nums); for (int i = 0; i < n; i++){ preSum[i + 1] = preSum[i] + nums[i]; } for (int q : queries){ int index = binarySearch(nums, q); long low = preSum[index + 1]; long high = preSum[n] - preSum[index + 1]; long count1 = (long)q * (index + 1) - low; long count2 = high - (long)q * (n - index - 1); res.add(count1 + count2); } return res; } public int binarySearch(int[] nums, int target){ int l = 0, r = nums.length - 1; while (l <= r){ int mid = (l + r) >> 1; if (nums[mid] <= target){ l = mid + 1; }else{ r = mid - 1; } } return r; } }
-
复杂度
-
时间复杂度: O ( m l o g n ) O(mlogn) O(mlogn),n为数组长度,m为查询长度
-
空间复杂度: O ( n ) O(n) O(n)
-
-
收集树中金币【LC2603】
给你一个
n
个节点的无向无根树,节点编号从0
到n - 1
。给你整数n
和一个长度为n - 1
的二维整数数组edges
,其中edges[i] = [ai, bi]
表示树中节点ai
和bi
之间有一条边。再给你一个长度为n
的数组coins
,其中coins[i]
可能为0
也可能为1
,1
表示节点i
处有一个金币。一开始,你需要选择树中任意一个节点出发。你可以执行下述操作任意次:
- 收集距离当前节点距离为
2
以内的所有金币,或者- 移动到树中一个相邻节点。
你需要收集树中所有的金币,并且回到出发节点,请你返回最少经过的边数。
如果你多次经过一条边,每一次经过都会给答案加一。
-
思路:拓扑排序
- 首先,我们可以去掉不包含金币的子树,因为访问其中任何一个点都毫无意义。
- 如果所有在叶子上的金币全部都能收集到,那么我们可以收集到树上所有金币。因此可以去除两轮叶子,剩余的结点即为必须经过的结点【两轮拓扑排序】
- 从距离叶子为2的节点处出发【局部最优】,收集树中所有的金币,并且回到出发节点时经过的边数最少【全局最优】。
-
实现
- 无向图,节点度为1时为叶子
class Solution { public int collectTheCoins(int[] coins, int[][] edges) { int n = coins.length; List<Integer> g[] = new ArrayList[n]; Arrays.setAll(g, e -> new ArrayList<>()); var deg = new int[n]; for (var e : edges) { int x = e[0], y = e[1]; g[x].add(y); g[y].add(x); // 建图 ++deg[x]; ++deg[y]; } // 用拓扑排序「剪枝」:去掉没有金币的子树 var q = new ArrayDeque<Integer>(); for (int i = 0; i < n; ++i) if (deg[i] == 1 && coins[i] == 0) // 无金币叶子 q.add(i); while (!q.isEmpty()) { int x = q.peek(); q.pop(); for (int y : g[x]) if (--deg[y] == 1 && coins[y] == 0) q.add(y); } // 再次拓扑排序 for (int i = 0; i < n; ++i) if (deg[i] == 1 && coins[i] == 1) // 有金币叶子 q.add(i); if (q.size() <= 1) return 0; // 至多一个有金币的叶子,直接收集 var time = new int[n]; while (!q.isEmpty()) { int x = q.peek(); q.pop(); for (int y : g[x]) if (--deg[y] == 1) { time[y] = time[x] + 1; // 记录入队时间 q.add(y); } } // 统计答案 int ans = 0; for (var e : edges) if (time[e[0]] >= 2 && time[e[1]] >= 2) ans += 2; return ans; } } 作者:灵茶山艾府 链接:https://leetcode.cn/problems/collect-coins-in-a-tree/solutions/2191371/tuo-bu-pai-xu-ji-lu-ru-dui-shi-jian-pyth-6uli/ 来源:力扣(LeetCode) 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
-
复杂度
-
时间复杂度: O ( E + V ) O(E+V) O(E+V), E E E表示邻边的跳数,V为结点的个数
-
空间复杂度: O ( V ) O(V) O(V)
-
-
升级:如果把题目中的 2 换成 0,1,2,3,⋯ ,n−1,你能把这些情况对应的答案全部算出来吗?
遍历所有的边,如果两个节点的入队时间均大于q(最大距离),那么表示这条边必须要经过,结果+2