LeetCode第230场周赛

周赛地址:https://leetcode-cn.com/contest/weekly-contest-230/

第一题:统计匹配检索规则的物品数量

第一题还是比较简单的,循环进行判断即可。

class Solution {
    public int countMatches(List<List<String>> items, String ruleKey, String ruleValue) {
        int count = 0;
        for (List<String> list : items) {
            if (ruleKey.equals("type")) {
                if (list.get(0).equals(ruleValue)) {
                    count++;
                }
            } else if (ruleKey.equals("color")) {
                if (list.get(1).equals(ruleValue)) {
                    count++;
                }
            } else if (ruleKey.equals("name")) {
                if (list.get(2).equals(ruleValue)) {
                    count++;
                }
            }
        }
        return count;
    }
}

第二题:最接近目标价格的甜点成本

先看题目给的数据范围,还是挺小的,暴力也可以过的,看到一篇不错的题解,整理一下思路,用到了三进制的思想。
基料是必须加入的,配料可以添加一种或多种,也可以不加,每种配料最多有2份,对于每种配料的取值,可以有0,1,2这3种情况,所以说可以用三进制表示。再看配料的数量m最大是10,那么总共有 3 10 = 59049 3^{10}=59049 310=59049种可能,还是可以接受的。基料必须选择,那么就是遍历所有的配料的情况,选择符合条件的即可。

class Solution {
    public int closestCost(int[] baseCosts, int[] toppingCosts, int target) {
        int baseLength = baseCosts.length, toppingLength = toppingCosts.length;
        int current = 0, diff = Integer.MAX_VALUE, result = target;
        for (int i = 0; i < baseLength; i++) {
            int total = (int) Math.pow(3, toppingLength);
            // 遍历每一种情况
            for (int j = 0; j < total; j++) {
                current = baseCosts[i];
                int temp = j;
                // 计算current
                for (int k = 0; k < toppingLength && temp != 0; k++) {
                    int mod = temp % 3;// 某一种配料选了几个
                    current += mod * toppingCosts[k];
                    temp /= 3;
                }
                if (target == current) {// 已经找到一个和target完全相同的情况,直接返回即可,不会有比它更接近的了
                    return target;
                }
                if (Math.abs(target - current) < diff) {
                    diff = Math.abs(target - current);
                    result = current;
                } else if (Math.abs(target - current) == diff) {
                    result = Math.min(result, current);
                }
            }
        }
        return result;
    }
}

第三题:通过最少操作次数使数组的和相等

自己写的时候,写了一大堆if,else,最后还没分析出来,把自己绕进去了。看题解看到一个非常巧妙的思路,记录下来。
为了便于分析,假设 s u m 1 < s u m 2 sum1<sum2 sum1<sum2,如果 s u m 1 > s u m 2 sum1>sum2 sum1>sum2,交换一下两者,继续分析。
d i f f = s u m 2 − s u m 1 diff=sum2-sum1 diff=sum2sum1,对nums1和nums2中的数据做变化的时候,记录变化量,当变化量≥diff的时候,就可以停止了。
因为sum1<sum2,所以nums1里的数据一定要增,nums2里的数据一定要减,大方向已经确定了。
对于数字1,它在nums1里的最大变化量是5,即1→6;它在nums2里的最大变化量是0,即1→1。
对于数字2,它在nums1里的最大变化量是4,即2→6;它在nums2里的最大变化量是1,即2→1。
对于数字3,它在nums1里的最大变化量是3,即3→6;它在nums2里的最大变化量是2,即3→1。
对于数字4,它在nums1里的最大变化量是2,即4→6;它在nums2里的最大变化量是3,即4→1。
对于数字5,它在nums1里的最大变化量是1,即5→6;它在nums2里的最大变化量是4,即5→1。
对于数字6,它在nums1里的最大变化量是0,即6→6;它在nums2里的最大变化量是5,即6→1。
于是,我们就统计nums1和nums2中的最大变化量,也就是以变化量作为下标,以频次作为值。
为了求得最小操作数,我们从变化量是5的开始计算,直到变化量是1为止。
在计算过程中,如果变化量的和≥diff,就表明存在一种方案,可以让 s u m 1 = = s u m 2 sum1==sum2 sum1==sum2
这里说一下为什么是大于等于,假设diff是3,我们选择的变化量是5,假设是6→1,此时变化量的和大于diff,我们其实可以让6→3,即可正好满足。
如果所有变化量都运算完,还没有满足变化量之和≥diff,表明不存在这种操作,使得 s u m 1 = = s u m 2 sum1==sum2 sum1==sum2

class Solution {
    public int minOperations(int[] nums1, int[] nums2) {
        int[] frequency = new int[6];
        int sum1 = 0, sum2 = 0, operation = 0;
        for (int i : nums1) {
            sum1 += i;
        }
        for (int i : nums2) {
            sum2 += i;
        }
        if (sum1 > sum2) {
            return minOperations(nums2, nums1);
        }
        int diff = sum2 - sum1;
        // nums1里是数据要增
        for (int i : nums1) {
            frequency[6 - i]++;
        }
        // nums2里的数据要减
        for (int i : nums2) {
            frequency[i - 1]++;
        }
        // 从变化量5开始,到1停止
        for (int i = 5; i > 0 && diff > 0; i--) {
            while (frequency[i] > 0 && diff > 0) {
                diff -= i;
                frequency[i]--;
                operation++;
            }
        }
        operation = diff > 0 ? -1 : operation;
        return operation;
    }
}

第四题:车队 II

取右为正方向,画水平坐标轴,在坐标轴上标记出各个点,代表汽车的起始位置,起始位置p越大,表示汽车越靠前。
由生活常识可知,后车追上前车需要满足: v 后 车 > v 前 车 v_{后车}>v_{前车} v>v,用时 t = Δ p Δ v = p 前 车 − p 后 车 v 后 车 − v 前 车 t=\frac{\Delta p}{\Delta v}=\frac{p_{前车}-p_{后车}}{v_{后车}-v_{前车}} t=ΔvΔp=vvpp
根据题意,后车追上前车,后车和前车融为一体,速度变为慢车的速度,也就是前车的速度。
假设从后向前方向分析,分别记为a车,b车,c车,满足 p a < p b < p c p_{a}<p_{b}<p_{c} pa<pb<pc。a,b,c车同时出发,a车追上b车用时2秒,b车追上c车用时1秒,那么,先发生的事件是b车追上c车,bc两车融为一体以c车速度前进,后发生的事件是a车追上c车。b车追上c车对a车追上c车不产生什么影响,可以看做没有b车,也就是只有a车去追c车。
假设有a、b、c、d这4辆车呢?也类似上面的关系:最后推出a车追d车,其实还是比较难分析的。
所以考虑从前向后分析。
最前面的车,它的answer值一定是-1,因为它前面没有车了。
其余的车,因为是从前向后遍历,我们只需要考虑当前车和它的前车,也就是当前车能不能追上前面的车,不需要考虑后车,也就是不用考虑当前车被追上的问题。
这里维护一个单调栈,栈底速度慢,栈顶速度快。最前面的车先入栈,然后依次分析后面的车。
在找前一辆可以追上的车的时候,只要栈里有车,即有前车,就需要一直向前找,直到找到栈是空的或者栈顶的那辆车是当前车可以追上的时候。
分几种情况:

  1. v 后 车 ≤ v 前 车 v_{后车} ≤ v_{前车} vv:后车追不上前车,后车的后车更没有机会追上前车了,可以把前车出栈。
  2. v 后 车 > v 前 车 v_{后车} > v_{前车} v>v:后车可以追上前车。
    1. t 后 车 追 上 前 车 < t 前 车 追 上 再 前 车 = a n s w e r [ s t a c k . p e e k ( ) ] t_{后车追上前车} < t_{前车追上再前车} = answer[stack.peek()] t<t=answer[stack.peek()]:当前分析的车可以追上邻近的前车。
    2. t 后 车 追 上 前 车 ≥ t 前 车 追 上 再 前 车 = a n s w e r [ s t a c k . p e e k ( ) ] t_{后车追上前车} ≥ t_{前车追上再前车} = answer[stack.peek()] tt=answer[stack.peek()]:当前分析的车还没有追上前车,前车已经追上再前车了,此时可以把前车(栈顶的车)剔除掉,也就是后车直接追再前车。

凡是算出结果的车都要入栈。另外就是要注意题目说的精确度问题,要用double类型,用float会出现浮点数误差。

class Solution {
    public double[] getCollisionTimes(int[][] cars) {
        int length = cars.length;
        double[] answer = new double[length];
        Stack<Integer> stack = new Stack<Integer>();
        answer[length - 1] = -1;// 最前车的结果是-1
        stack.push(length - 1);
        for (int i = length - 2; i >= 0; i--) {
            // 栈非空,就取栈顶,用当前的车和栈顶的车进行比较,看需不需要把栈顶车弹出
            while (!stack.isEmpty()) {
                int peek = stack.peek();
                // 当前车的速度≤栈顶车的速度,当前车追不上前车,那么当前车的后车也没有机会追上,可以让栈顶的车弹出
                if (cars[peek][1] >= cars[i][1]) {
                    stack.pop();
                } else {// 可以追上前车,判断是追上相邻的前车,还是更前面的车
                    double preAnswer = answer[peek];// 前车追上再前车的时间
                    double curAnswer = 1.0D * (cars[peek][0] - cars[i][0]) / (cars[i][1] - cars[peek][1]);
                    if (preAnswer > 0 && curAnswer >= preAnswer) {// 前车追再前车用时更短(需要把preAnswer==-1的情况排除掉)
                        stack.pop();
                    } else {// 当前车追前车用时更短
                        answer[i] = curAnswer;
                        break;
                    }
                }
            }
            // 走到这里有两种情况,一种是栈空了,一种是当前车找到了可以追上的前车
            // 有一辆车非常慢,栈里的每一辆车(当前车的所有前车)都追不上,栈都弹空了,这辆车的answer就是-1
            if (stack.isEmpty()) {
                answer[i] = -1;
            }
            stack.push(i);
        }
        return answer;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值