K件物品的最大和
https://leetcode.cn/problems/k-items-with-the-maximum-sum/
解析:这是简单的分类讨论问题,分情况讨论即可:
如果,那么答案为k
如果,那么答案为nums1
如果,那么答案为nums1 - (k - nums1 - nums0)
代码:
class Solution {
public int kItemsWithMaximumSum(int numOnes, int numZeros, int numNegOnes, int k) {
return (k <= numOnes) ? k : ((k <= numOnes + numZeros) ? numOnes : 2 * numOnes + numZeros - k);
}
}
质数减法运算
https://leetcode.cn/problems/prime-subtraction-operation/
解析:考虑贪心方法,从后往前逐个元素实现严格递增,并确保每一个元素如有需要,尽可能减掉更小的质数。可以证明,“是否存在解”当且仅当“以上贪心方法是否可构造出解”。
从右到左遍历。当遍历到第k个元素时,nums[k+1: n]已是严格单调递增状态。如果,说明该元素需要执行减操作,且至少需要减掉,于是我们找到比大的第一个质数,如果该质数大于等于nums[k],说明无解。否则执行in-place的减操作后,继续向左遍历。
如果整个数组遍历完均未触发“无解”条件,那么就返回“有解”。
于是,该问题转化为“如何以最低的代价找到比k大的最小质数”?因为题目限定了数字范围为1000以内,所以我们提前生成1000以内的所有质数供二分查找即可。方法见https://zhuanlan.zhihu.com/p/100051075。
代码:
class Solution {
private final static int UPPER = 1000;
private final static int[] primes = new int[168];
// 线性筛实现
static {
boolean[] is_prime = new boolean[UPPER + 1];
List<Integer> prime_list = new ArrayList<>();
int p = 0;
for(int i = 2; i <= UPPER; i++){
if(!is_prime[i]){primes[p++]=i;}
for(int j = 0; j <= p; j++){
if(i * primes[j] <= 1000){
is_prime[i * primes[j]] = true;
}
if(i % primes[j] == 0){
break;
}
}
}
}
public boolean primeSubOperation(int[] nums) {
for(int i = nums.length - 2; i >= 0; i--){
if(nums[i] >= nums[i+1]){
int sub = find(nums[i] - nums[i+1]);
if(sub == -1 || sub >= nums[i]){
return false;
}
nums[i] -= sub;
}
}
return true;
}
private int find(int target){
/*
找到比target大的最小质数,如果没有则返回-1
*/
int i = 0;
int j = primes.length - 1;
if(target < primes[i]){return primes[0];}
if(target >= primes[j]){return -1;}
while(i < j - 1){
int mid = (i + j) / 2;
if(primes[mid] > target){
j = mid;
}
else{
i = mid;
}
}
return primes[j];
}
}
使数组元素全部相等的最少操作次数
https://leetcode.cn/problems/minimum-operations-to-make-all-array-elements-equal/
解析:假设nums长度为n,queries长度为q,那么结果为
此方法的时间复杂度为O(n*q),为了减少重复运算,我们采用以下步骤:
首先排序nums,时间复杂度为O(n*log(n))
然后计算nums的所有元素的前缀和,时间复杂度为O(n)
对于queries中的每一个元素queries[i],用二分法将nums分割为两部分,左半边都比queries[i]小,右半边都比queries[i]小。假设分割点为k,那么
其中queries部分的求和可以转化为乘法,nums部分的求和可以通过前缀和得到,时间复杂度为O(q*log(n))。
总的时间复杂度是O((n+q)*log(n))。
class Solution {
public List<Long> minOperations(int[] nums, int[] queries) {
Arrays.sort(nums);
long[] cumsum = new long[nums.length];
cumsum[0] = nums[0];
for(int i = 1; i < nums.length; i++){cumsum[i] = cumsum[i-1] + nums[i];}
List<Long> res = new ArrayList<>();
for(int query: queries){
if(query <= nums[0]){
res.add(cumsum[nums.length - 1] - (long)query * nums.length);
}
else if(query >= nums[nums.length - 1]){
res.add((long)query * nums.length - cumsum[nums.length - 1]);
}
else{
int ind = find(nums, query);
res.add((long)query * (ind+1) - cumsum[ind] + (cumsum[nums.length - 1] - cumsum[ind]) - (long)query * (nums.length-ind-1));
}
}
return res;
}
private int find(int[] nums, int query){
int i = 0;
int j = nums.length - 1;
while(i < j - 1){
int mid = (i + j) / 2;
if(nums[mid] == query){return mid;}
else if(nums[mid] < query){i = mid;}
else{j = mid;}
}
return i;
}
}
收集树中金币
https://leetcode.cn/problems/collect-coins-in-a-tree/submissions/
解析:考虑一个分步剔除的过程,一步步的剔除不需要遍历的节点:
第一步:去除所有不含金币的子树。具体做法为,通过拓扑排序,循环去除不含金币的叶节点,直至图中所有叶节点均含金币。
第二步:此时,非叶节点是否有金币已经无需关注,因为遍历途中顺路就能收集到。在剩下的点中,去除所有叶节点和它们的紧邻节点。
遍历剩下的节点需要的边数乘2即是答案。
class Solution {
public int collectTheCoins(int[] coins, int[][] edges) {
// 建图,用于拓扑排序
int n = coins.length;
List<Integer> graphArr[] = new ArrayList[n]; // 存储每个点的临近点
Arrays.setAll(graphArr, g -> new ArrayList<>());
int[] degree = new int[n]; // 存储每个点的度数
for(int i = 0; i < n - 1; i++){
graphArr[edges[i][0]].add(edges[i][1]);
graphArr[edges[i][1]].add(edges[i][0]);
degree[edges[i][0]]++;
degree[edges[i][1]]++;
}
// 循环去除无金币的叶子节点,直至图中所有叶子均有金币
Queue<Integer> q = new LinkedList<>();
for(int i = 0; i < n; i++){
if(degree[i] == 1 && coins[i] == 0){q.add(i);}
}
while(!q.isEmpty()){
for(int y: graphArr[q.remove()]){
if(--degree[y] == 1 && coins[y] == 0){
q.add(y);
}
}
}
//找到剩余节点距离最近的有金币叶节点的距离
int[] dist = new int[n];
for(int i = 0; i < n; i++){
if(degree[i] == 1 && coins[i] == 1){
q.add(i);
}
}
while(!q.isEmpty()){
int x = q.remove();
for(int y: graphArr[x]){
if(--degree[y] == 1){
dist[y] = dist[x] + 1;
q.add(y);
}
}
}
//计算多少条边的两个端点距离不少于2,乘2即是答案
int res = 0;
for(int i = 0; i < n - 1; i++){
if(dist[edges[i][0]] >= 2 && dist[edges[i][1]] >= 2){res += 2;}
}
return res;
}
}