力扣周赛总结(一)第288场 | 题1(按奇偶性交换)[优先队列]、题2(向表达式添加括号)[左右指针+模拟]、题3(K次增加后的最大乘积)[贪心]

239 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

比赛链接:力扣 第 288 场周赛

题1——按奇偶性交换后的最大数字


题目链接:6037. 按奇偶性交换后的最大数字

难度:简单

题意: 给出一个数字,可以任意交换两个相同奇偶性相同的数字,返回最大的结果

1)比赛时的思路:

暴力枚举

1.用 List 把数字的每一位都存下来 2. 遍历 i ∈ [0, n) ,j ∈ [i, n),即从i开始,每次都将i后面比较大的数放前面 3. 最后根据List将结果拼起来 时间复杂度:O(N^2^)

class Solution {
    public int largestInteger(int num) {
        if(num <= 10) return num;
        ArrayList<Integer>list = new ArrayList<>();
        while(num > 0){
            list.add(0, num % 10);
            num /= 10;
        }
        int n = list.size();
        for(int i = 0; i < n; i++){
            for(int j = i; j < n; j++){
                if(i != j){
                    int a = list.get(i);
                    int b = list.get(j);
                    if(b > a && ((a % 2) == (b % 2))){
                        list.set(i, b);
                        list.set(j, a);
                    }
                }
            }
        }
        int ans = 0;
        for(int i = 0; i < n; i++){
            ans = ans * 10 + list.get(i); 
        }
        return ans;
    }
}

2)看完题解后的思路:

优先队列

使用两个奇偶降序优先队列,将数字分为奇偶两个部分

将int型变量通过String.valueOf()转为String,在通过String.toCharArray转为char数组,遍历该数组,根据数字的奇偶性选择队列入队。

入队后,在依次取出,使用StringBuilder拼接成String,在转成数字。

注意在处理时,不能改变所有元素的所处位置奇偶性,此时只需用boolean记录一下之前当前位置的奇偶性即可。

AC代码:

class Solution {
    public int largestInteger(int num) {
        // 奇数优先队列
        Queue<Integer> odd = new PriorityQueue<>((a, b) -> (b - a));
        // 偶数优先队列
        Queue<Integer> even = new PriorityQueue<>((a, b) -> (b - a));
        char[] ch = String.valueOf(num).toCharArray();
        int n = ch.length;
        boolean[] isOdd = new boolean[n];
        // 入队
        for(int i = 0; i < n; i++){
            int a = ch[i] - '0';
            if(a % 2 == 0){
                even.offer(a);
            }
            else{
                isOdd[i] = true;
                odd.offer(a);
            }
        }
        // 出队
        StringBuilder s = new StringBuilder();
        for(int i = 0; i < n; i++)
                s.append(isOdd[i] ? odd.poll() : even.poll());
        return Integer.parseInt(s.toString());
    }
}

题2 —— 向表达式添加括号后的最小结果


题目链接:6038. 向表达式添加括号后的最小结果

难度:中等

题意:给出一字符串,形如a+b的形式,返回添加两个括号后,表达式的值为最小的结果字符串。

比如:12+34,则返回1(2+3)4,(乘号省略),此时结果最小。

思路: 1)比赛时的思路:

第一反应就是模拟,首先将字符串转化成字符数组,然后寻找左右括号的插入位置

后来发现可以使用之前复习到的"左右指针"的概念,即通过两个括号,将表达式分为四个部分,a*(b+c)*d

左指针从0开始,右指针从+加号开始。遍历每种情况,同时记录最优解(即最小值),如果比最优解还小,那么就计算最优解同时更新字符串。

最终没依然没AC,卡在了表达式计算上。

2)看完题解后的思路:

左右指针 + 模拟

同样是左右指针,将字符串分为三个部分即可,因为中间部分是直接相加的,一定存在。

左右部分是相乘的,可能不存在(比如左括号在最左边或者右括号在最右边)。

计算的思路就是分为三部分进行,最后就是枚举每一种可能

首先获取到➕的下标k,通过String类的indexOf方法则获取到下标。

leftleftrightright 分别是表示左右括号位置的左右指针。

枚举的范围是 left[0,k)left ∈ [0, k)right[k+1,n)right ∈[k+1, n)

重点是每次截取三部分的范围,首先要知道String的substring方法是左闭右开的,即[a, b) ,所以substring(0, 0) 的结果是空

在枚举中使用 left,right表示当前左右括号插入的位置,那么则有:

左侧部分截取为[0, left-1] ,中间部分截取为[left, right-1],右边部分为[right, n-1]

AC代码:

class Solution {
    public String minimizeResult(String exp) {
        String ans = "";
        int mini = Integer.MAX_VALUE;
        int n = exp.length();
        int k = exp.indexOf("+");
        // 左右括号指针
        for(int left = 0; left < k; left++){
            for(int right = k + 1;right < n; right++){
                // 往left插入左括号
                String a = exp.substring(0, left);
                // 往right插入右括号
                String s = exp.substring(left, right+1);
                // 计算往right插入右括号前,右括号的部分
                String b = exp.substring(right+1);
                // 计算表达式结果
                int sum = cal(a, s, b);
                // 更新最优解
                if(sum < mini){
                    mini = sum;
                    ans = String.format("%s(%s)%s", a, s, b);
                }
            }
        }
        return ans;
    }
    public int cal(String a, String s, String b){
        String[] split = s.split("\\+");
        int res = toInt(split[0]) + toInt(split[1]);
        if(!a.isEmpty()) res *= toInt(a);
        if(!b.isEmpty()) res *= toInt(b);
        return res;
    }
    public int toInt(String x){ return Integer.parseInt(x); }
}

题3——K 次增加后的最大乘积

题目链接: 6039. K 次增加后的最大乘积

难度: 简单

题意:给定一组数字,要求在限制的k次对某一位数字加1,使得最终每一位的乘积结果最大

1)比赛时思路:

比赛时思路比较乱,想着既然要操作k次,那么我该如何对这k次操作,每一次都往最大的数加1?这样的话每次都需要查找最大数

又或者先排序,然后分k个给每一个数字,多余的就重复继续分?

似乎都不太行,总之,思路很乱,没有想能用什么数据结构,也没办法模拟出来

2)看题解后的思路:

贪心 + 优先队列

看了题解豁然开朗,原来这是道贪心题,每次只需要增加最小的那个数就好。

当我们对一个数加1后会出现两种情况,① 这个数仍然是最小的数 ② 这个数已不是最小的数(说明之前有相同最小的数)

通过 升序的优先队列 可以很好的解决这个问题。

进行 k 次操作,每次操作都从优先队列取值,加一后继续入队。

最后将优先队列的元素相乘即为最终的结果:

AC代码:

class Solution {
    public int maximumProduct(int[] nums, int k) {
        Queue<Integer> q = new PriorityQueue<Integer> ();
        for(int num : nums) q.offer(num);
        for(int i = 0; i < k; i++)
            q.offer(q.poll() + 1);
        long ans = 1, MAX = 1000000007;
        while(!q.isEmpty()){
            ans = (ans * q.poll()) % MAX;
        }
        return (int) ans;
    }
}

题4——花园的最大总美丽值

题目链接:6040. 花园的最大总美丽值

题目难度: 困难

题意: 给出一维数组,表示n个花园里种的花数,现剩下 x 朵花,可以选择任意花园种花,只能种不能采,实现一个种花策略能使得最后根据题目标准判定花园美丽值(分数)达到最大。返回这个最大值:

每个花园分为不完善和完善两种:若该花园种的花数目达到 target朵,则称为 完善花园

花园总美丽值(即分数)的计算方法:

(完善花园数目 * 给定的full值 + 不完善花园中最少的花数目 * 给定的 partial值)

1)比赛时思路:

比赛时没有看这题,一般第四题都比较难,这题在比赛中的AC情况(203/6926),这里先写一点目前的思路吧...

读完这题我想到了这几天做到的股票问题,股票问题主要是购票和售票,在指定天数里,每天只能购票或者售票,最后求出最大的利润。

这一题则是种花,在原有的基础上种花,有n个花园,我可以选择其中1个花园种,也可以选择任意个,而且自己的花不用必须种完(想种就种)

目前的思路就是对题目进行了一点分析,看了会题解还是不太懂,放弃了,之后先把前三题掌握再说

总结


题1——按奇偶性交换后的最大数字(优先队列) 题2——向表达式添加括号后的最小值(左右指针 + 模拟) 题3——K次增加后的最大乘积(贪心 + 优先队列)(每次增加取最小的)

看完题解才发现,解题思路不难理解,就是比赛的时候没想出来,这里主要是对 优先队列、贪心使用不熟练,希望下次比赛再接再厉吧。