leetcode第369周赛

2917. 找出数组中的 K-or 值

给你一个下标从 0 开始的整数数组 nums 和一个整数 k

nums 中的 K-or 是一个满足以下条件的非负整数:

  • 只有在 nums 中,至少存在 k 个元素的第 i 位值为 1 ,那么 K-or 中的第 i 位的值才是 1 。

返回 numsK-or 值。

注意 :对于整数 x ,如果 (2^i AND x) == 2^i ,则 x 中的第 i 位值为 1 ,其中 AND 为按位与运算符。

示例 1:

复制代码
输入:nums = [7,12,9,8,9,15], k = 4
输出:9
解释:nums[0]、nums[2]、nums[4] 和 nums[5] 的第 0 位的值为 1 。
nums[0] 和 nums[5] 的第 1 位的值为 1 。
nums[0]、nums[1] 和 nums[5] 的第 2 位的值为 1 。
nums[1]、nums[2]、nums[3]、nums[4] 和 nums[5] 的第 3 位的值为 1 。
只有第 0 位和第 3 位满足数组中至少存在 k 个元素在对应位上的值为 1 。因此,答案为 2^0 + 2^3 = 9 。

示例 2:

复制代码
输入:nums = [2,12,1,11,4,5], k = 6
输出:0
解释:因为 k == 6 == nums.length ,所以数组的 6-or 等于其中所有元素按位与运算的结果。因此,答案为 2 AND 12 AND 1 AND 11 AND 4 AND 5 = 0 。

示例 3:

复制代码
输入:nums = [10,8,5,9,11,6,8], k = 1
输出:15
解释:因为 k == 1 ,数组的 1-or 等于其中所有元素按位或运算的结果。因此,答案为 10 OR 8 OR 5 OR 9 OR 11 OR 6 OR 8 = 15 。

提示:

  • 1 <= nums.length <= 50
  • 0 <= nums[i] < 2^31
  • 1 <= k <= nums.length

思路:

简单题目,直接遍历就好了。

最多只有31位,而且数组长度也才50。重点是 (2^i AND x) == 2^i

两层遍历,外层范围[0-31],内层范围[0-n],n是数组的长度。

ac code:

java 复制代码
class Solution {
    public int findKOr(int[] nums, int k) {
        int ans = 0;
            for (int j=0;j<31;j++) {
                int cnt = 0;
                for (int num : nums) {
                    if ((num & (1 << j)) == (1 << j)) {
                    cnt += 1;
                }
                if (cnt >= k) {
                    ans = ans + (1 << j);
                    break;
                }
                }
                
            }
        return ans;
    }
}

2918. 数组的最小相等和

给你两个由正整数和 0 组成的数组 nums1nums2

你必须将两个数组中的所有 0 替换为 严格 正整数,并且满足两个数组中所有元素的和 相等

返回 最小 相等和 ,如果无法使两数组相等,则返回 -1

示例 1:

复制代码
输入:nums1 = [3,2,0,1,0], nums2 = [6,5,0]
输出:12
解释:可以按下述方式替换数组中的 0 :
- 用 2 和 4 替换 nums1 中的两个 0 。得到 nums1 = [3,2,2,1,4] 。
- 用 1 替换 nums2 中的一个 0 。得到 nums2 = [6,5,1] 。
两个数组的元素和相等,都等于 12 。可以证明这是可以获得的最小相等和。

示例 2:

复制代码
输入:nums1 = [2,0,2,0], nums2 = [1,4]
输出:-1
解释:无法使两个数组的和相等。

提示:

  • 1 <= nums1.length, nums2.length <= 10^5
  • 0 <= nums1[i], nums2[i] <= 10^6

思路:

直接模拟。答案分几种情况,只要捋清楚即可。

1、nums1不存在0:

1)nums2不存在0:

value1(nums1的sum值,同下)!= value2(nums2的sum值,同下)那么就return -1

2)nums2存在0:

value1 <= (value2+cnt2(代表nums2种0的个数,同下)):因为0是严格替换成了正整数,那么最小也是1,已经比value1还要大了,再加上正整数,不可能使得value2变小,所以,return -1 ;

value1 > value 2:因为可以换成任意正整数,所以,value2肯定可以变大成任意值。那么最小的话,肯定就是value1,所以return value1即可。

2、nums1存在0:

1)nums2 不存在0:

同理,(value1 + cnt1) >= value2 即可return -1;

value1 < value2,则 return value2

2)nums2 存在0:

因为0最小也是换成1,所以value的范围其实是可以确定的。例如nums1值的范围是[value1 + cnt1(nums1存在0的个数), 正无穷)

那么nums2也是同理。所以,返回的值取范围交集即可。return Math.max(value1+cnt1, value2+cnt2),为什么是max呢?因为交集!!! 不懂得可以画个图,或者举几个例子。

捋清楚之后,按照分类写清楚就行( 之前没捋清楚还wa了一次。。。)

具体细节,看代码

ac code

java 复制代码
class Solution {
    public long minSum(int[] nums1, int[] nums2) {
        long value1 = 0; // nums1的sum
        long value2 = 0;  // nums2的sum
        int cnt1 = 0;  // num1的0的个数
        int cnt2 = 0;   // num2的0的个数
        for (int num : nums1) {
            if (num == 0) {
                cnt1 += 1;
            }
            value1 += num;
        }
        for (int num : nums2) {
            if (num == 0) {
                cnt2 += 1;
            }
            value2 += num;
        }
        // 需要判断value1 如果小于 value2 + cnt2,那么无论如何都不可能
        if (cnt1 == 0 && value1 <= (value2+cnt2)) {
            if (cnt2 == 0 && value1 == value2) {
                return value1;
            } else if (value1 == (value2+cnt2)) {
                return value1;
            }
           return -1;
        }
        if (cnt2 == 0 && value2 <= (value1+cnt1)) {
            if (cnt1 == 0 && value1 == value2) {
                return value1;
            } else if (value2 == (value1+cnt1)) {
                return value2;
            }
            return -1;
        }
        if (cnt1 == 0) {
            return value1;
        }
        if (cnt2 == 0) {
            return value2;
        }
        return Math.max(value1+cnt1, value2+cnt2); 
        
    }
}

2919. 使数组变美的最小增量运算数

给你一个下标从 0 开始、长度为 n 的整数数组 nums ,和一个整数 k

你可以执行下述 递增 运算 任意 次(可以是 0 次):

  • 从范围 [0, n - 1] 中选择一个下标 i ,并将 nums[i] 的值加 1

如果数组中任何长度 大于或等于 3 的子数组,其 最大 元素都大于或等于 k ,则认为数组是一个 美丽数组

以整数形式返回使数组变为 美丽数组 需要执行的 最小 递增运算数。

子数组是数组中的一个连续 非空 元素序列。

示例 1:

复制代码
输入:nums = [2,3,0,0,2], k = 4
输出:3
解释:可以执行下述递增运算,使 nums 变为美丽数组:
选择下标 i = 1 ,并且将 nums[1] 的值加 1 -> [2,4,0,0,2] 。
选择下标 i = 4 ,并且将 nums[4] 的值加 1 -> [2,4,0,0,3] 。
选择下标 i = 4 ,并且将 nums[4] 的值加 1 -> [2,4,0,0,4] 。
长度大于或等于 3 的子数组为 [2,4,0], [4,0,0], [0,0,4], [2,4,0,0], [4,0,0,4], [2,4,0,0,4] 。
在所有子数组中,最大元素都等于 k = 4 ,所以 nums 现在是美丽数组。
可以证明无法用少于 3 次递增运算使 nums 变为美丽数组。
因此,答案为 3 。

示例 2:

复制代码
输入:nums = [0,1,3,3], k = 5
输出:2
解释:可以执行下述递增运算,使 nums 变为美丽数组:
选择下标 i = 2 ,并且将 nums[2] 的值加 1 -> [0,1,4,3] 。
选择下标 i = 2 ,并且将 nums[2] 的值加 1 -> [0,1,5,3] 。
长度大于或等于 3 的子数组为 [0,1,5]、[1,5,3]、[0,1,5,3] 。
在所有子数组中,最大元素都等于 k = 5 ,所以 nums 现在是美丽数组。
可以证明无法用少于 2 次递增运算使 nums 变为美丽数组。 
因此,答案为 2 。

示例 3:

复制代码
输入:nums = [1,1,2], k = 1
输出:0
解释:在这个示例中,只有一个长度大于或等于 3 的子数组 [1,1,2] 。
其最大元素 2 已经大于 k = 1 ,所以无需执行任何增量运算。
因此,答案为 0 。

提示:

  • 3 <= n == nums.length <= 10^5
  • 0 <= nums[i] <= 10^9
  • 0 <= k <= 10^9

思路:

挺有意思的一道题目,算是益智题了。一开始想到的是滑动窗口,最小长度3,然后将窗口内最大值进行增大到k值,后来发现不对,因为窗口内最大值并不一定是最优的,那么就会希望有一个后悔的操作,比如增大了a,但是发现不是最优的,想要增大相邻的b。如何"后悔"增大某个数字?

举个例子:

43,31,14,4

73

如果按照原本的想法,增大窗口内最大值,窗口长度是3。那么应该增大43,然后窗口向右滑动后,没有满足条件的k值,则增大31到k值。这样发现,一共花费了30 + 42 = 62。

但是如果我们仅仅只增大31呢? 那么其实就只需要花费42即可。

此时,我们可以考虑下,如果我们选择了43的时候,如果后续需要后悔,那么对于相邻的31是不是需要进行变大操作。

一步步来看:([xxx]表示窗口)

43,31,14\],4 经过操作 假设先按照之前的贪心的想法,先把43进行变动为 \[73,31,14\],4 此时我们已经花费了30了,如果只是单纯将31 -\> 73 是需要42,目前已经花费了30,那么就还需要12,所以,我们可以将31同步转换为61,同理14同步转换为44,即 \[73,61,44\],4 所以在下一个窗口后那么就是: 73,\[61,44,4

这个时候我们就只需要花费12 就可以满足条件,这样相当于就是执行了后悔的操作。是不是很巧妙的一个办法。

而且,我们还需要注意,窗口尽可能往后取值。

具体实现细节可以看看代码。

ac code

java 复制代码
class Solution {
    public long minIncrementOperations(int[] nums, int k) {
        int first = nums[0];
        int second = nums[1];
        int third = nums[2];
        int n = nums.length;
        long ans = 0;
        // 窗口长度为3
        for (int i=3;i<=n;i++) {
            // 如果没有满足条件的值才需要进行变换
            if (first < k && second < k && third < k) {
                // 找到最大值
                int tmp = Math.max(first, Math.max(second, third));
                ans += (k - tmp); // 计算代价
                if (third == tmp) { // 如果是最后一个的话,直接变就行,因为它在窗口待最久
                    third = k;
                } else if (second == tmp) { // 如果是第二个,只需要把后面的加上后悔操作即可,毕竟第一个马上要出窗口了
                    second = k;
                    third += (k - tmp);
                } else { // 同上
                    first = k;
                    second += (k - tmp);
                    third += (k - tmp);
                }
            }
            // 窗口向右滑动
            if (i < n) {
                first = second;
                second = third;
                third = nums[i];
            }
            
        }
        return ans;
    }
}

2920. 收集所有金币可获得的最大积分

节点 0 处现有一棵由 n 个节点组成的无向树,节点编号从 0n - 1 。给你一个长度为 n - 1 的二维 整数 数组 edges ,其中 edges[i] = [ai, bi] 表示在树上的节点 aibi 之间存在一条边。另给你一个下标从 0 开始、长度为 n 的数组 coins 和一个整数 k ,其中 coins[i] 表示节点 i 处的金币数量。

从根节点开始,你必须收集所有金币。要想收集节点上的金币,必须先收集该节点的祖先节点上的金币。

节点 i 上的金币可以用下述方法之一进行收集:

  • 收集所有金币,得到共计 coins[i] - k 点积分。如果 coins[i] - k 是负数,你将会失去 abs(coins[i] - k) 点积分。
  • 收集所有金币,得到共计 floor(coins[i] / 2) 点积分。如果采用这种方法,节点 i 子树中所有节点 j 的金币数 coins[j] 将会减少至 floor(coins[j] / 2)

返回收集 所有 树节点的金币之后可以获得的最大积分。

示例 1:

复制代码
输入:edges = [[0,1],[1,2],[2,3]], coins = [10,10,3,3], k = 5
输出:11                        
解释:
使用第一种方法收集节点 0 上的所有金币。总积分 = 10 - 5 = 5 。
使用第一种方法收集节点 1 上的所有金币。总积分 = 5 + (10 - 5) = 10 。
使用第二种方法收集节点 2 上的所有金币。所以节点 3 上的金币将会变为 floor(3 / 2) = 1 ,总积分 = 10 + floor(3 / 2) = 11 。
使用第二种方法收集节点 3 上的所有金币。总积分 =  11 + floor(1 / 2) = 11.
可以证明收集所有节点上的金币能获得的最大积分是 11 。 

示例 2:

复制代码
输入:edges = [[0,1],[0,2]], coins = [8,4,4], k = 0
输出:16
解释:
使用第一种方法收集所有节点上的金币,因此,总积分 = (8 - 0) + (4 - 0) + (4 - 0) = 16 。

提示:

  • n == coins.length
  • 2 <= n <= 10^5
  • 0 <= coins[i] <= 10^4
  • edges.length == n - 1
  • 0 <= edges[i][0], edges[i][1] < n
  • 0 <= k <= 10^4

思路:

树上dp,不太会。。。 放下别人的题解。。。

把 floor(coins[i] / 2) 看成右移操作。

一个数最多右移多少次,就变成 000 了?在本题的数据范围下,这至多是 141414 次。

同时,右移操作是可以叠加的,我们可以记录子树中的节点值右移了多少次。

所以可以定义 dfs(i,j)\textit{dfs}(i,j)dfs(i,j) 表示子树 iii 在已经右移 jjj 次的前提下,最多可以得到多少积分。

用「选或不选」来思考,即是否右移:

不右移:答案为 (coins[i]>>j)−k(\textit{coins}[i]>>j)-k(coins[i]>>j)−k 加上每个子树 ch\textit{ch}ch 的 dfs(ch,j)\textit{dfs}(ch,j)dfs(ch,j)。

右移:答案为 coins[i]>>(j+1)\textit{coins}[i]>>(j+1)coins[i]>>(j+1) 加上每个子树 ch\textit{ch}ch 的 dfs(ch,j+1)\textit{dfs}(ch,j+1)dfs(ch,j+1)。

这两种情况取最大值。

作者:灵茶山艾府

java 复制代码
class Solution {
    public int maximumPoints(int[][] edges, int[] coins, int k) {
        int n = coins.length;
        List<Integer>[] g = new ArrayList[n];
        Arrays.setAll(g, e -> new ArrayList<>());
        for (int[] e : edges) {
            int x = e[0], y = e[1];
            g[x].add(y);
            g[y].add(x);
        }
        int[][] memo = new int[n][14];
        for (int[] m : memo) {
            Arrays.fill(m, -1); // -1 表示没有计算过
        }
        return dfs(0, 0, -1, memo, g, coins, k);
    }

    private int dfs(int i, int j, int fa, int[][] memo, List<Integer>[] g, int[] coins, int k) {
        if (memo[i][j] != -1) { // 之前计算过
            return memo[i][j];
        }
        int res1 = (coins[i] >> j) - k;
        int res2 = coins[i] >> (j + 1);
        for (int ch : g[i]) {
            if (ch == fa) continue;
            res1 += dfs(ch, j, i, memo, g, coins, k); // 不右移
            if (j < 13) { // j+1 >= 14 相当于 res2 += 0,无需递归
                res2 += dfs(ch, j + 1, i, memo, g, coins, k); // 右移
            }
        }
        return memo[i][j] = Math.max(res1, res2); // 记忆化
    }
}
相关推荐
业精于勤的牙9 分钟前
三角形最小路径和(二)
算法
风筝在晴天搁浅10 分钟前
hot100 239.滑动窗口最大值
数据结构·算法·leetcode
夏乌_Wx22 分钟前
练题100天——DAY31:相对名次+数组拆分+重塑矩阵
数据结构·算法
LYFlied22 分钟前
【算法解题模板】-解二叉树相关算法题的技巧
前端·数据结构·算法·leetcode
Ven%1 小时前
【AI大模型算法工程师面试题解析与技术思考】
人工智能·python·算法
天勤量化大唯粉1 小时前
枢轴点反转策略在铜期货中的量化应用指南(附天勤量化代码)
ide·python·算法·机器学习·github·开源软件·程序员创富
爱学习的小仙女!1 小时前
算法效率的度量 时间复杂度 空间复杂度
数据结构·算法
AndrewHZ1 小时前
【复杂网络分析】什么是图神经网络?
人工智能·深度学习·神经网络·算法·图神经网络·复杂网络
Swizard1 小时前
拒绝“狗熊掰棒子”!用 EWC (Elastic Weight Consolidation) 彻底终结 AI 的灾难性遗忘
python·算法·ai·训练
fab 在逃TDPIE2 小时前
Sentaurus TCAD 仿真教程(十)
算法