【LeetCode周赛】LeetCode第369场周赛

目录

  • 找出数组中的 K-or 值
  • 数组的最小相等和
  • 使数组变美的最小增量运算数

找出数组中的 K-or 值

给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。
nums 中的 K-or 是一个满足以下条件的非负整数:
只有在 nums 中,至少存在 k 个元素的第 i 位值为 1 ,那么 K-or 中的第 i 位的值才是 1 。
返回 nums 的 K-or 值。
注意 :对于整数 x ,如果 (2i AND x) == 2i ,则 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 < = n u m s . l e n g t h < = 50 1 <= nums.length <= 50 1<=nums.length<=50
0 < = n u m s [ i ] < 2 31 0 <= nums[i] < 2^{31} 0<=nums[i]<231
1 < = k < = n u m s . l e n g t h 1 <= k <= nums.length 1<=k<=nums.length

分析:
按照题目意思模拟即可,将nums数组中的每一个数字的各个位数中,值为1的位置记录下来,用一个数组维护即可。
代码:

class Solution {
public:
    int findKOr(vector<int>& nums, int k) {
        int res = 0;
        int n = nums.size();
        vector<int>cnt(35);
        for(int i = 0;i < n; i++){
            int x = nums[i],j = 0;
            while(x){
                if(x & 1)cnt[j]++;
                x >>= 1;
                j++;
            }
        }
        for(int i = 0; i <=31 ;i++){
            if(cnt[i] >= k)res += pow(2, i);
        }
        return res;
    }
};

数组的最小相等和

给你两个由正整数和 0 组成的数组 nums1 和 nums2 。
你必须将两个数组中的 所有 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 < = n u m s 1. l e n g t h , n u m s 2. l e n g t h < = 1 0 5 1 <= nums1.length, nums2.length <= 10^5 1<=nums1.length,nums2.length<=105
0 < = n u m s 1 [ i ] , n u m s 2 [ i ] < = 1 0 6 0 <= nums1[i], nums2[i] <= 10^6 0<=nums1[i],nums2[i]<=106

分析:
这个题目按照题目意思去分类讨论即可,但在赛中的时候,我分类讨论的方法很复杂。其实因为要使得两个数组各自的和最小且相等,不难想到的是,可以先把所有的数字0用1替代,分别求和先得到 s u m 1 sum1 sum1 s u m 2 sum2 sum2
那么如何保证两个数组的和 s u m 1 sum1 sum1 s u m 2 sum2 sum2相等呢?

  1. 首先,如果两边都有数字0,那么最后的答案肯定存在,因为将小的那一边(这里假设sum2小)中,取出一个0数字,将其改成sum1和sum2的差值,让sum2和sum1相等即可,所以最后答案是 m a x ( s u m 1 , s u m 2 ) max(sum1,sum2) max(sum1,sum2)
  2. 但是,如果有一个数组有0,而另一个数组没有0,答案是什么呢?假如现在 s u m 1 > s u m 2 sum1>sum2 sum1>sum2,且数组1中没有0,数组2中有0,那么很简单,将数组2中的0补上,使得 s u m 1 = s u m 2 sum1=sum2 sum1=sum2即可。但是反过来,如果数组1中有0,数组2中没有0,则答案肯定不存在。 s u m 2 > s u m 1 sum2>sum1 sum2>sum1同理。
  3. 此外,还有一种情况,如果两边数组 s u m 1 ! = s u m 2 sum1!=sum2 sum1=sum2,且两边都没有0,那么也是不存在答案。

代码:
赛时的代码,考虑了很多,仅供参考

class Solution {
public:
    long long minSum(vector<int>& nums1, vector<int>& nums2) {
        int zero1 = 0, zero2 = 0;
        long long sum1 = 0, sum2 = 0;
        for(auto x:nums1){
            if(x == 0)zero1++;
            sum1 += x;
        }
        for(auto x:nums2){
            if(x == 0)zero2++;
            sum2 += x;
        }
        long long res = 0;
        if(sum1 > sum2){
            long long esp = sum1 - sum2;
            if(zero2 == 0)res = 0;
            else if((long long)(esp + zero1) < zero2){
                if(zero1 == 0)res = 0;
                else res = sum2 + zero2;
            }
            else res = (long long)zero1 + esp + sum2;
        }
        else if(sum1 < sum2){
            long long esp = sum2 - sum1;
            if(zero1 == 0)res = 0;
            else if((long long)(esp + zero2) < zero1 ){
                if(zero2 == 0)res = 0;
                else res = sum1 + zero1;
            }
            else res = (long long)zero2 + esp + sum1;
        }
        else{
            if( (zero1 && (!zero2)) || (zero2 && (!zero1)) )res = 0;
            else res = sum1 + max(zero1, zero2);
        }
        if(res == 0)res = -1;
        return res;
    }
};

赛后修改

class Solution {
public:
    long long minSum(vector<int>& nums1, vector<int>& nums2) {
        bool zero1 = 0, zero2 = 0;
        long long sum1 = 0, sum2 = 0;
        for(auto x:nums1){
            if(x == 0)zero1 = true, ++sum1;
            sum1 += x;
        }
        for(auto x:nums2){
            if(x == 0)zero2 = true, ++sum2;
            sum2 += x;
        }
        if( (sum1 > sum2 && zero1 && (!zero2)) || (sum2 > sum1 && zero2 && (!zero1)) || (sum1 != sum2 && (!zero1) && (!zero2)) )return -1;
        return max(sum1, sum2);
    }
};

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

给你一个下标从 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 = = n u m s . l e n g t h < = 1 0 5 3 <= n == nums.length <= 10^5 3<=n==nums.length<=105
0 < = n u m s [ i ] < = 1 0 9 0 <= nums[i] <= 10^9 0<=nums[i]<=109
0 < = k < = 1 0 9 0 <= k <= 10^9 0<=k<=109

分析:
如果数组中任何长度 大于或等于 3 的子数组,其 最大 元素都大于或等于 k ,则认为数组是一个 美丽数组 。其实意思就是只要任何一个长度为3的子数组,其中都有一个元素的值大于等于k即可。 因为任何一个长度大于3的子数组都会包含一个长度等于3的子数组。
那么我们如何去解答呢?
我们考虑第 i i i个元素是否要变成大于等于k的数,如果第 i i i个元素要增大到k,那么对其右边 i + 1 i+1 i+1的数字来说,其左边就有一个k了。
如果不增大,那么对其右边的数字来说,其左边就有一个没有达到k。
那么如果对 i + 2 i+2 i+2位置的数字来分析,如果 i + 1 i+1 i+1位置的数字也不去增大到k,那么其左边就有两个没有增大到k的数,那么它一定要增大(不去考虑右边的数,因为在后续讨论中,右边的数也会被涉及到的)。
那么这道题目可以使用dp的方法或者记忆化搜索来做。
d p [ i ] [ j ] dp[i][j] dp[i][j]表示到第i个位置,离其最近的增大到k的数字的距离。
状态转移方程为:
d p [ i ] [ 0 ] = m i n ( d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 2 ] ) + m a x ( 0 , k − n u m s [ i ] ) ; dp[i][0] = min({dp[i - 1][0], dp[i - 1][1], dp[i - 1][2]}) + max(0, k - nums[i]); dp[i][0]=min(dp[i1][0],dp[i1][1],dp[i1][2])+max(0,knums[i]);
d p [ i ] [ 1 ] = d p [ i − 1 ] [ 0 ] dp[i][1] = dp[i - 1][0] dp[i][1]=dp[i1][0]
d p [ i ] [ 2 ] = d p [ i − 1 ] [ 1 ] dp[i][2] = dp[i - 1][1] dp[i][2]=dp[i1][1]
d f s ( x , c n t ) dfs(x,cnt) dfs(x,cnt)表示到第x个位置时,前面有几个没有增到的k的数字。
记忆化搜索的方式为:
如果当前位置增大到k,则 d f s ( x + 1 , 0 ) + m a x ( 0 , k − n u m s [ i ] ) dfs(x + 1,0) + max(0, k - nums[i]) dfs(x+1,0)+max(0,knums[i])
如果 c n t < 2 cnt < 2 cnt<2,即前面有不超过2个没有增到k的数字,则当前位置可以不增加到k, d f s ( x + 1 , c n t + 1 ) dfs(x + 1,cnt + 1) dfs(x+1,cnt+1)

代码:
记忆化搜索

const int N = 1e5+5;
long long dp[N][3];
class Solution {
public:
    //当前位置x是否要增大到k,且当前位置的后面有cnt个数字没有增大到了k
    long long dfs(int x, int cnt, int k, vector<int>& nums){
        if(x >= nums.size())return 0;
        long long &res = dp[x][cnt];//引用类型 后续会更改
        if(res != -1)return res;
        res = dfs(x + 1, 0, k, nums) + max(0, k - nums[x]);//当前位置增大到k
        if(cnt < 2)res = min(res, dfs(x + 1, cnt + 1, k ,nums));//如果前面有2个数字都没增大,则当前位置必须增大到k
        return res;
    }
    long long minIncrementOperations(vector<int>& nums, int k) {
        int n = nums.size();
        memset(dp, -1, sizeof(dp));
        return dfs(0, 0, k, nums);
    }
};
class Solution:
    def minIncrementOperations(self, nums: List[int], k: int) -> int:
        n = len(nums)
        dp = [[-1] * 3 for _ in range(n)]
        def dfs(x, cnt):# 当前位置为x,cnt表示前面最近达到k的值离当前位置的距离
            if x >= n:
                return 0
            if dp[x][cnt] != -1:
                return dp[x][cnt]
            dp[x][cnt] = dfs(x + 1, 0) + max(0, k - nums[x])
            if cnt < 2:
                dp[x][cnt] = min(dp[x][cnt], dfs(x + 1,cnt + 1))
            return dp[x][cnt]
        return dfs(0, 0)

dp

class Solution {
public:
    long long minIncrementOperations(vector<int>& nums, int k) {
        int n = nums.size();
        vector<vector<long long>>dp(n, vector<long long>(3));
        dp[0][0] = max(0, k - nums[0]);
        for(int i = 1; i < n; i++){
            dp[i][0] = min({dp[i - 1][0], dp[i - 1][1], dp[i - 1][2]}) + max(0, k - nums[i]);
            dp[i][1] = dp[i - 1][0];
            dp[i][2] = dp[i - 1][1];
        }
        return min({dp[n - 1][0], dp[n - 1][1], dp[n - 1][2]});
    }
};
class Solution:
    def minIncrementOperations(self, nums: List[int], k: int) -> int:
        n = len(nums)
        dp = [[0] * 3 for _ in range(n)]
        # dp[i][j] 表示当前数字i,没有到达k的数字离当前位置的距离为j
        dp[0][0] = max(0, k - nums[0])
        for i in range(1, n):
            dp[i][0] = min(dp[i - 1][0], dp[i - 1][1], dp[i - 1][2]) + max(0, k - nums[i])
            dp[i][1] = dp[i - 1][0]
            dp[i][2] = dp[i - 1][1]
        return min(dp[n - 1][0], dp[n - 1][1], dp[n - 1][2])
        

你可能感兴趣的:(LeetCode,leetcode,算法,数据结构)