【算法竞赛01】leetcode第363场周赛

文章介绍了四个编程问题,涉及计算K置位元素和的LowBit模板、让学生保持开心的分组方法、最大合金数的二分搜索策略以及完全子集最大元素和的构造与求解。解决方案提供了Java和Python的代码实现及时间空间复杂度分析。
摘要由CSDN通过智能技术生成

前言

笔者为数不多AK的比赛

T1.计算K位置下标对应元素的和(Easy

题目链接

        计算K位置下标对应元素的和

题目描述

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

        请你用整数形式返回nums中的特定元素之 ,这些特定元素满足:其对应下标的二进制表示中恰存在k个置位。

        整数的二进制表示中的1就是这个整数的置位 。

        例如,21的二进制表示为10101,其中有3置位

输入范围
  • 1 <= nums.length <= 1000
  • 1 <= nums[i] <= 10^5
  • 0 <= k <= 10
问题分析&思路

        显然是LowBit模版题,可以用LowBit求置位数,也可以直接用库函数求置位数,以下展示LowBit做法。

代码实现(Java&Python)
//Java
class Solution {
    public int sumIndicesWithKSetBits(List<Integer> nums, int k) {
        int ans = 0;
        for(int i = 0;i<nums.size();i++){
            int cnt = 0;
            int tmp = i;
            while(tmp>0){
                cnt+=1;
                tmp&=tmp-1;
            }
            if(cnt == k){
                ans+=nums.get(i);
            }
        }
        return ans;
    }
}
#Python
class Solution:
    def sumIndicesWithKSetBits(self, nums: List[int], k: int) -> int:
        ans = 0
        for i,x in enumerate(nums):
            cnt = 0
            while i:
                cnt+=1
                i&=i-1
            if cnt == k:
                ans+=x
        return ans
时间&空间复杂度分析
  • 时间复杂度:\mathcal{O}(n\log k),n为数组长度,k为最大数的二进制位数。
  • 空间复杂度:\mathcal{O}(1)

T2.让所有学生保持开心的分组方法数(Medium

题目链接

                让所有学生保持开心的分组方法数

题目描述

        给你一个下标从 0 开始、长度为 n 的整数数组 nums ,其中 n 是班级中学生的总数。班主任希望能够在让所有学生保持开心的情况下选出一组学生:

        如果能够满足下述两个条件之一,则认为第 i 位学生将会保持开心:

  • 这位学生被选中,并且被选中的学生人数 严格大于 nums[i] 。
  • 这位学生没有被选中,并且被选中的学生人数 严格小于 nums[i] 。

        返回能够满足让所有学生保持开心的分组方法的数目。

输入范围
  • 1 <= nums.length <= 10^5
  • 0 <= nums[i] < nums.length
问题分析&思路

        考虑如何选择k个学生,用贪心的思想考虑,被选中的k个学生的nums都要尽可能小,以便保证被选中的学生人数严格大于被选中的学生的nums;而没被选择的n-k个学生的nums都要尽可能大,以便保证被选中的学生人数严格小于没被选中的学生的nums

        因此,一个合理的想法就是先把数组升序排序(显然原数组的下标顺序对于本题无影响),对于排序好后的数组,对于每个i(0<=i<n-1)如果nums[i]<i+1<nums[i+1],这就意味着可以选择前i+1个学生,同时后面n-i-1个未被选择的学生中的nums最小值是nums[i+1]>i+1个被选中的学生,可以保证这是一个合理的方案。

        特别地,我们要特判一个都不选全部都选这两种情况。

代码实现(Java&Python)
//Java
class Solution {
    public int countWays(List<Integer> nums) {
        Collections.sort(nums);//数组排序
        int choose = 0;
        int ans = 0;
        int n = nums.size();
        if(nums.get(0)>0){//特判一个都不选
            ans+=1;
        }
        for(int i = 0;i<n-1;i++){
            if(nums.get(i)<i+1&&i+1<nums.get(i+1)){//判断是可行的分配
                ans+=1;
            }
        }
        if(nums.get(n-1)<n){//特判全都选
            ans+=1;
        }
        return ans;
    }
}
#Python
class Solution:
    def countWays(self, nums: List[int]) -> int:
        nums.sort()
        ans = 0
        n = len(nums)
        if nums[0]>0:
            ans+= 1
        for i in range(0,n-1):
            if nums[i]<i+1 and nums[i+1]>i+1:
                ans+=1
        if n>nums[-1]:
            ans+=1
        return ans
时间&空间复杂度分析
  • 时间复杂度:\mathcal{O}(n\log n+n),n为数组长度,时间复杂度卡在排序上面。
  • 空间复杂度:\mathcal{O}(1)

T3.最大合金数(Medium

题目链接

       最大合金数

题目描述

        假设你是一家合金制造公司的老板,你的公司使用多种金属来制造合金。现在共有 n 种不同类型的金属可以使用,并且你可以使用 k 台机器来制造合金。每台机器都需要特定数量的每种金属来创建合金。

        对于第 i 台机器而言,创建合金需要 composition[i][j] 份 j 类型金属。最初,你拥有 stock[i] 份 i 类型金属,而每购入一份 i 类型金属需要花费 cost[i] 的金钱。

        给你整数 nkbudget,下标从 1 开始的二维数组 composition,两个下标从 1 开始的数组 stock 和 cost,请你在预算不超过 budget 金钱的前提下,最大化 公司制造合金的数量。

        所有合金都需要由同一台机器制造。

        返回公司可以制造的最大合金数。

输入范围
  • 1 <= n, k <= 100
  • 0 <= budget <= 10^8
  • composition.length == k
  • composition[i].length == n
  • 1 <= composition[i][j] <= 100
  • stock.length == cost.length == n
  • 0 <= stock[i] <= 10^8
  • 1 <= cost[i] <= 100
问题分析&思路

        根据题意,所有合金都需要由同一台机器制造,显然制造的合金数越多,花费就越多,具有单调性,因此,可以想到用二分答案的方法二分去查找答案以降低时间复杂度,达到高效寻找答案的目的。

        二分答案:即逆向验证,二分枚举可能的答案,然后对于答案进行判断,直至找到满足条件的答案。难点是判断函数的设计。

        本题判断函数(check(lim))可以这样设计:对于所制造的合金数lim,判断是否存在机器能够制造lim个合金并且开销小于等于budget

代码实现(Java&Python)
//Java
class Solution {
    public int maxNumberOfAlloys(int n, int k, int budget, List<List<Integer>> composition, List<Integer> stock, List<Integer> cost) {
        long l = 0;
        long r = 200000000;//因为答案的最大值是budget+max(stock) <=2*10^8,所以设r为2*10^8
        while(l<r){//二分答案查找模版
            long mid = (l+r+1)>>1;
            if(check(mid,n,k,budget,composition,stock,cost)) l = mid;
            else r = mid-1;
        }
        return (int)l;
    }
    public boolean check(long lim,int n,int k,int budget,List<List<Integer>> composition,List<Integer> stock, List<Integer> cost){
        for(int i = 0;i<k;i++){//遍历总共k台机器
            long c = 0;//计算每台机器的制作合金数目的开销
            List<Integer> lst = composition.get(i);
            for(int j = 0;j<n;j++){//遍历总共n种合金
                if(lst.get(j)*lim<=stock.get(j)) continue;
                c+=(lst.get(j)*lim - stock.get(j))*cost.get(j);//计算开销
            }   
            if(c<=budget) return true;//如果这台机器的总开销低于预算则可行
        }
        return false;//没有机器的开销低于预算
    }
}
#Python
class Solution:
    def maxNumberOfAlloys(self, n: int, k: int, budget: int, composition: List[List[int]], stock: List[int], cost: List[int]) -> int:
        
        def check(lim):
            for lst in composition:
                s = 0
                for i,x in enumerate(lst):
                    if x*lim>stock[i]:
                        s+=(x*lim-stock[i])*cost[i]
                if s<=budget:
                    return True
            return False
        
        l,r = 0,10**9+1
        while l<r:
            mid = (l+r+1)>>1
            if check(mid): l = mid
            else: r = mid - 1
        return l
时间&空间复杂度分析
  • 时间复杂度:\mathcal{O}(kn\log U),其中k为机器数,n为合金总数,U = budget+max(stock)
  • 空间复杂度:\mathcal{O}(1)

T4.完全子集的最大元素和(Hard

题目链接

        完全子集的最大元素和

题目描述

        给你一个下标从 1 开始、由 n 个整数组成的数组。

        如果一组数字中每对元素的乘积都是一个完全平方数,则称这组数字是一个 完全集 。

        下标集 {1, 2, ..., n} 的子集可以表示为 {i1, i2, ..., ik},我们定义对应该子集的 元素和 为 nums[i1] + nums[i2] + ... + nums[ik] 。

        返回下标集 {1, 2, ..., n} 的 完全子集 所能取到的 最大元素和 。

        完全平方数是指可以表示为一个整数和其自身相乘的数。

输入范围
  • 1 <= n == nums.length <= 10^4
  • 1 <= nums[i] <= 10^9
问题分析&思路

        由于所有的nums[i]都是正整数,显然是数越多,能取到的完全子集的元素和最大,所以以下完全集均考虑取在[1,n]范围内长度最大的完全集。

        很容易想到的一个最大完全集是{1,4,9,16,……,k^2}(k^2<=n<(k+1)^2)试想一下,对于在某个完全集中已经出现的数,是否有可能出现在另一个完全集中?

        答案显然是否定的,可以用反证法证明。假设x,y属于不同的完全集,而z同时属于x,y所属的完全集。则有x*z = a^2,y*z = b^2,那么有x*y = (a*b/z)^2,由于x,y都是正整数,显然a*b/z也是正整数,那么x,y相乘为完全平方数,是属于同一完全集的,因此假设不成立。自此,我们验证了各个完全集是独立的,即交集为空集。

        在得到了上述结论之后,我们已经构造了一个完全集为{1,4,9,16,......}那么我们考虑如何构造在[1,n]范围内的所有的完全集。显然{2*1,2*4,2*9,2*16,......}也是一个完全集,那么我们就很容易可以构造出[1,n]范围内的所有的最大完全集,即为k*{1,4,9,16,......}。最后我们输出所有完全集所对应下标的元素的最大和即可。

代码实现(Java&Python)

        

//Java
class Solution {
    public long maximumSum(List<Integer> nums) {
        List<Integer> pf = new ArrayList<>();//1,4,9,16....完全集
        int n = nums.size();
        if(n == 1) return nums.get(0);//特判数组长度为1的情况
        long ans = 0;
        int i = 1;
        while(i<=n){//构造1,4,9,16....完全集
            pf.add(i*i);
            i+=1;
        }
        int[] vis = new int[n+1];//判断某一下标是否已经加入集合
        for(i = 1;i<=n;i++){
            if(vis[i] == 1) continue;
            int cnt = 0;
            long s = 0;//记录某一完全集的最大元素和
            while(i*pf.get(cnt)<=n){//当该完全集的最大元素超过n时退出循环
                s+=nums.get(i*pf.get(cnt)-1);
                vis[i*pf.get(cnt)] = 1;
                cnt+=1;
            }
            ans = Math.max(ans,s);//ans即取为最大值
        }
        return ans; 
    }
}
#Python
class Solution:
    def maximumSum(self, nums: List[int]) -> int:
        n = len(nums)
        if n == 1:return nums[0]
        fa = [i for i in range(n+1)]
        ans = 0
        vis = [0]*(n+1)
        pf = [i**2 for i in range(1,n+1)]
        for i in range(1,n+1):
            if vis[i]: continue
            tmp = 0
            l = 0
            while pf[l]*i<=n:
                tmp+=nums[pf[l]*i-1]
                vis[pf[l]*i] = 1
                l+=1
            ans = max(tmp,ans)
        return ans
时间&空间复杂度分析
  • 时间复杂度:\mathcal{O}(n),循环次数可以近似看做n,因为每次遍历会确保把下标记录到vis数组中,因此每个下标仅访问操作一次,总共有n个下标,须访问n次。
  • 空间复杂度:\mathcal{O}(n)
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值