Leetcode---110双周赛

题目列表

6990. 取整购买后的账户余额

6940. 在链表中插入最大公约数

6956. 使循环数组所有元素相等的最少秒数

6987. 使数组和小于等于 x 的最少时间

一、取证购买后的账户余额

 这道题,没什么好说的,就是用100减去四舍五入后的purchaseAmount

代码如下


int accountBalanceAfterPurchase(int purchaseAmount){
    int x=purchaseAmount%10;
    purchaseAmount-=x;
    if(x>=5){
        purchaseAmount+=10;
    }
    return 100-purchaseAmount;
}

//更简洁的写法
int accountBalanceAfterPurchase(int purchaseAmount){
    return 100-(purchaseAmount+5)/10*10;
}

 二、在链表中插入最大公约数

 这是一道很标准的链表题,也没啥难度,关键是理顺结点之间的连接,这题的关键就是求最大公约数,当然你也可以选择暴力,这题的时间复杂度允许暴力求解,但这里要介绍另一种方法---辗转相除法,代码如下

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode*BuyNode(int x){
    struct ListNode*newnode=(struct ListNode*)malloc(sizeof(struct ListNode));
    newnode->val=x;
    newnode->next=NULL;
    return newnode;
}
//辗转相除法
int Get(int x,int y){
    //原理:(x,y)的最大公约数==(y,(x%y))的最大公约数,具体证明过程在下面
    while(x%y){
        int tmp=y;
        y=x%y;
        x=tmp;
    }
    return y;
}
struct ListNode* insertGreatestCommonDivisors(struct ListNode* head){
    if(head->next==NULL)return head;
    struct ListNode*pre=head,*cur=head->next;
    while(cur){
        struct ListNode*newnode=BuyNode(Get(pre->val,cur->val));
        pre->next=newnode;
        newnode->next=cur;
        pre=cur;
        cur=cur->next;
    }
    return head;
}

 辗转相除法的原理简要证明如下

 三、使循环数组所有元素相等的最小秒数

 这题还是很有趣的,题目要求让所有的数字变的相同,并且每个数字只能影响它前后两个数字,这题的难点在于选哪个数字作为最终要相等的数,以及确定之后怎么计算需要多少秒,我们先来看第二个问题,因为第一个问题其实是由第二个问题决定的,或者这么说,就算解决了第一个问题我们依旧得考虑第二个问题,这里我们直接从第二个问题入手

 代码如下

class Solution {
public:
    int minimumSeconds(vector<int>& nums) {
        map<int,vector<int>> hash;//存放相同数字出现的下标
        for (int i=0;i<nums.size();i++)
            hash[nums[i]].push_back(i);
        int ans=nums.size();
        for (auto &p:hash) {
            vector<int> &now=p.second;
            //这里是将每个数字第一次出现的下标+n,方便计算第一次和最后一次区间内的数字个数,因为数组是循环数组
            now.push_back(now[0]+nums.size());
            int cur=0;
            for (int i=0;i+1<now.size();i++) {
                cur=max(cur,now[i+1]-now[i]);
            }
            ans=min(ans,cur/2);
        }
        return ans;
    }
};

四、使数组和小于等于x的最小时间

 这题的思路确实很难想到,其实这题本质还是一个枚举答案,但我们要首先明白一点,就是操作的秒数最大是多少?或者这么问一个位置能被置为0两次吗?很显然不能,因为如果同一个位置被置为0两次,那么第一次置为0就是无效操作,等于白花时间,这种操作很明显不可能是最小秒数,所以这题的答案最大是n(数组的大小)

好,现在我们知道了答案枚举的范围,那么我们先枚举看,能不能从中找到一些规律,如下

从上面的举例中,我们不难得到一个思路:在nums2中找长度为 t(即操作的时间) 的子序列,让它的数之和最小,然后在和其他没有被置0位置的数相加,判断是否小于x,并且只要选的子序列的数确定,我们就能根据上面的规律,一步算出它的最小值。

但是这么做有个难点:就是我们需要记录子序列选的数是那些数,然后计算子序列外的数字之和

正难则反 => 既然+很难,我们来想想-
t秒后的最小值 = t秒内不做操作的数字总和 - 选择的子序列能被减去的最大值
如果我们在t秒内什么都没有做,总和应该是sum1+t*sum2
并且子序列能被减去的最大值,只和子序列有关,没必要管其他位置的数,显然,难度降低了,子序列的每一项应该为nums1[i]+j*nums2[i]

 现在,我们只要得到个数确定的子序列所能减去的最大值就能开始枚举答案了,所以问题变成了求长度为 j 的子序列,能减去的最大值(动态规划)

dp[ i ] [ j ]代表在前 i 个元素中,长度为 j 的子序列的最大值

dp[i][j]=dp[i-1][j]                                        ---第i个不选
dp[i][j]=dp[i-1][j-1]+j*nums2[i]+nums1[i]   ---第i个选
dp[i][j] = max{ dp[i-1][j] , dp[i-1][j-1]+j*nums2[i] }

代码如下


struct Num{
    int num1;
    int num2;
};
int cmp(struct Num*p1,struct Num*p2){
    return p1->num2-p2->num2;
}
int minimumTime(int* nums1, int nums1Size, int* nums2, int nums2Size, int x){
    int sum1=0,sum2=0,n=nums1Size;
    struct Num arr[n];
    for(int i=0;i<n;i++){
        sum1+=nums1[i];
        sum2+=nums2[i];
        arr[i].num1=nums1[i];
        arr[i].num2=nums2[i];
    }
    qsort(arr,n,sizeof(struct Num),cmp);//这里的排序是为了让nums2[i]*j,这两个数对应起来,否则会报错
    int f[n+1][n+1];
    memset(f,0,sizeof(f));
    for(int i=0;i<n;i++){
        for(int j=n;j>0;j--){
            f[i+1][j]=fmax(f[i][j],f[i][j-1]+j*arr[i].num2+arr[i].num1);
        }
    }

    for(int t=0;t<=n;t++)
        if(sum1+t*sum2-f[n][t]<=x)
            return t;
    return -1;
}

//优化空间复杂度
struct Num{
    int num1;
    int num2;
};
int cmp(struct Num*p1,struct Num*p2){
    return p1->num2-p2->num2;
}
int minimumTime(int* nums1, int nums1Size, int* nums2, int nums2Size, int x){
    int sum1=0,sum2=0,n=nums1Size;
    struct Num arr[n];
    for(int i=0;i<n;i++){
        sum1+=nums1[i];
        sum2+=nums2[i];
        arr[i].num1=nums1[i];
        arr[i].num2=nums2[i];
    }
    qsort(arr,n,sizeof(struct Num),cmp);
    int f[n+1];
    memset(f,0,sizeof(f));
    for(int i=0;i<n;i++){
        for(int j=n;j>0;j--){
            f[j]=fmax(f[j],f[j-1]+j*arr[i].num2+arr[i].num1);
        }
    }

    for(int t=0;t<=n;t++)
        if(sum1+t*sum2-f[t]<=x)
            return t;
    return -1;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值