周赛地址: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=sum2−sum1,对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=v后车−v前车p前车−p后车。
根据题意,后车追上前车,后车和前车融为一体,速度变为慢车的速度,也就是前车的速度。
假设从后向前方向分析,分别记为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,因为它前面没有车了。
其余的车,因为是从前向后遍历,我们只需要考虑当前车和它的前车,也就是当前车能不能追上前面的车,不需要考虑后车,也就是不用考虑当前车被追上的问题。
这里维护一个单调栈,栈底速度慢,栈顶速度快。最前面的车先入栈,然后依次分析后面的车。
在找前一辆可以追上的车的时候,只要栈里有车,即有前车,就需要一直向前找,直到找到栈是空的或者栈顶的那辆车是当前车可以追上的时候。
分几种情况:
- v 后 车 ≤ v 前 车 v_{后车} ≤ v_{前车} v后车≤v前车:后车追不上前车,后车的后车更没有机会追上前车了,可以把前车出栈。
-
v
后
车
>
v
前
车
v_{后车} > v_{前车}
v后车>v前车:后车可以追上前车。
- 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()]:当前分析的车可以追上邻近的前车。
- 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()]:当前分析的车还没有追上前车,前车已经追上再前车了,此时可以把前车(栈顶的车)剔除掉,也就是后车直接追再前车。
凡是算出结果的车都要入栈。另外就是要注意题目说的精确度问题,要用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;
}
}