LeetCode-440周赛
LeetCode — 440周赛
题目列表
一、将水果放入篮子 II
本题由于数据范围比较小,所以我们可以暴力模拟水果的放置过程,代码如下
// C++
class Solution {
public:
int numOfUnplacedFruits(vector<int>& fruits, vector<int>& baskets) {
int n = fruits.size();
int cnt = n;
for(int x : fruits){
for(int& y : baskets){
if(y > 0 && x <= y){
cnt--;
y = -1;
break;
}
}
}
return cnt;
}
};
# Python
class Solution:
def numOfUnplacedFruits(self, fruits: List[int], baskets: List[int]) -> int:
cnt = len(fruits)
for x in fruits:
for i, y in enumerate(baskets):
if y > 0 and x <= y:
baskets[i] = -1
cnt -= 1
break
return cnt
二、选出和最大的 K 个元素
在所有满足
n u m s 1 [ j ] nums1[j]
n
u
m
s
1
[
j
] 小于
n u m s 1 [ i ] nums1[i]
n
u
m
s
1
[
i
] 的下标
j j
j 所对应的
n u m s 2 [ j ] nums2[j]
n
u
m
s
2
[
j
] 中选出最多
k k
k 个数,使得这些数字之和最大。
思路如下:
如何快速找到小于
n u m s 1 [ j ] nums1[j]
n
u
m
s
1
[
j
] 小于
n u m s 1 [ i ] nums1[i]
n
u
m
s
1
[
i
] 的下标
j j
j 。我们可以对
n u m s 1 nums1
n
u
m
s
1 数组进行排序,那么在
n u m s 1 [ i ] nums1[i]
n
u
m
s
1
[
i
] 左边的数字都比它小
如何求
k k
k 个数字的最大和,本质就是维护最大的
k k
k 个
n u m s 2 [ j ] nums2[j]
n
u
m
s
2
[
j
] ,这可以用最小堆来做:堆顶的元素是
k k
k 个数中的最小值,如果新加入的数字大于最小值,就加入堆中,同时丢弃最小值,否则则什么也不做,同时我们还要维护堆中元素之和
注意:题目要求返回的
a n s ans
an
s 中的
a n s [ i ] ans[i]
an
s
[
i
] 要对应原始数组
n u m s 1 nums1
n
u
m
s
1 中的
n u m s 1 [ i ] nums1[i]
n
u
m
s
1
[
i
] ,所以我们不能对
n u m s 1 nums1
n
u
m
s
1 直接进行排序,我们只要通过下标对
n u m s 1 nums1
n
u
m
s
1 进行排序即可,具体看代码
代码如下
// C++
class Solution {
public:
vector<long long> findMaxSum(vector<int>& nums1, vector<int>& nums2, int k) {
int n = nums1.size();
vector<int> idx(n);
iota(idx.begin(), idx.end(), 0); // 给 idx 数组赋值 0,1,2,...
ranges::sort(idx, {}, [&](auto x){ return nums1[x]; }); // 对下标进行排序
long long sum = 0;
vector<long long> ans(n);
priority_queue<int, vector<int>, greater<>> pq; // 最小堆
for(int i = 0, j = 0; i < n; i++){
int x = idx[i]; // 注意这里的 i,j 需要通过 idx 进行映射到真正的下标
while(j < i && nums1[idx[j]] < nums1[x]){ // 比 nums1[x] 小的考虑入堆
pq.push(nums2[idx[j]]);
sum += nums2[idx[j]];
if(pq.size() > k){
sum -= pq.top();
pq.pop();
}
j++;
}
ans[x] = sum;
}
return ans;
}
};
# Python
class Solution:
def findMaxSum(self, nums1: List[int], nums2: List[int], k: int) -> List[int]:
n = len(nums1)
idx = [0] * n
for i in range(n):
idx[i] = i
idx.sort(key=lambda x : nums1[x])
j = sum = 0
pq = []
ans = [0] * n
for i in range(n):
x = idx[i]
while j < i and nums1[idx[j]] < nums1[x]:
heappush(pq, nums2[idx[j]])
sum += nums2[idx[j]]
if len(pq) > k:
sum -= heappop(pq)
j += 1
ans[x] = sum
return ans
三、将水果放入篮子 III
和第一题同样的题目,但是数据范围变大了,不能暴力模拟了,现在的关键是如何快速的找到
b a s k e t s baskets
ba
s
k
e
t
s 中最左边的第一个大于等于
f r u i t s [ i ] fruits[i]
f
r
u
i
t
s
[
i
] 的数字,如何做呢?思路如下:
假设我们已知
[ l , r ] [l,r]
[
l
,
r
] 区间的最大值
m x mx
m
x ,那么我们就能快速的判断出
[ l , r ] [l,r]
[
l
,
r
] 内是否有值
≥ f r u i t s [ i ] \ge fruits[i]
≥
f
r
u
i
t
s
[
i
] ,从而决定是否进入区间去寻找
在
[ l , r ] [l,r]
[
l
,
r
] 中,我们同样可以将区间分半成
[ l , m i d ] [l,mid]
[
l
,
mi
d
] 和
[ m i d + 1 , r ] [mid+1,r]
[
mi
d
1
,
r
] ,然后用同样的方法先去判断
[ l , m i d ] [l,mid]
[
l
,
mi
d
] 中是否有值
≥ f r u i t s [ i ] \ge fruits[i]
≥
f
r
u
i
t
s
[
i
] ,如果没有则去
[ m i d + 1 , r ] [mid+1,r]
[
mi
d
1
,
r
] 中找
如此,问题的规模不断的被减半,直到
l
r l=r
l
=
r 我们找到了一个
b a s k e t s [ l ] ≥ f r u i t s [ i ] baskets[l]\ge fruits[i]
ba
s
k
e
t
s
[
l
]
≥
f
r
u
i
t
s
[
i
] ,由于我们每次都是优先去左边查找,所以找到的
b a s k e t s [ l ] baskets[l]
ba
s
k
e
t
s
[
l
] 就是
b a s k e t s baskets
ba
s
k
e
t
s 中最左边的第一个大于等于
f r u i t s [ i ] fruits[i]
f
r
u
i
t
s
[
i
] 的数。
同时,由于问题规模不断减半,所以查询的时间复杂度为
O ( l o g N ) O(logN)
O
(
l
o
g
N
)
综上所述,我们需要维护区间最大值,同时还要查询和进行单点修改,这些都可以用线段树来实现
具体代码如下
// C++
class SegTree{
public:
SegTree(int n) : t(n << 2) {}
void maintain(int o){
t[o] = max(t[o << 1], t[o << 1|1]);
}
void build(int o, int l, int r, const vector<int>& a){
if(l == r){
t[o] = a[l];
return;
}
int m = l + (r - l) / 2;
build(o << 1, l, m, a);
build(o << 1|1, m + 1, r, a);
maintain(o);
}
void update(int o, int l, int r, int i, int val = -1){
if(l == r){
t[o] = val;
return;
}
int m = l + (r - l) / 2;
if(i <= m) update(o << 1, l, m, i, val);
else update(o << 1 | 1, m + 1, r, i, val);
maintain(o);
}
int query(int o, int l, int r, int val){
if(t[o] < val){
return -1;
}
if(l == r){
return l;
}
int m = l + (r - l) / 2;
int i = query(o << 1, l, m, val);
if(i < 0)
i = query(o << 1 | 1, m + 1, r, val);
return i;
}
private:
vector<int> t; // 记录最大值
};
class Solution {
public:
int numOfUnplacedFruits(vector<int>& fruits, vector<int>& baskets) {
int n = fruits.size(), cnt = n;
SegTree t(n);
t.build(1, 0, n - 1, baskets);
for(int x : fruits){
int i = t.query(1, 0, n - 1, x);
if(i >= 0){
t.update(1, 0, n - 1, i);
cnt--;
}
}
return cnt;
}
};
# Python
class SegTree:
def __init__(self, a:List[int]):
n = len(a)
self.t = [0] * (n << 2)
self.build(1, 0, n - 1, a)
def maintain(self, o:int):
self.t[o] = max(self.t[o<<1], self.t[o<<1|1])
def build(self, o:int, l:int, r:int, a:List[int]):
if l == r:
self.t[o] = a[l]
return
m = (l + r) // 2
self.build(o << 1, l, m, a)
self.build(o << 1|1, m + 1, r, a)
self.maintain(o)
def update(self, o:int, l:int, r:int, i:int, val:int):
if l == r:
self.t[o] = val
return
m = (l + r) // 2
if i <= m:
self.update(o << 1, l, m, i, val)
else:
self.update(o << 1|1, m + 1, r, i, val)
self.maintain(o)
def query(self, o:int, l:int, r:int, val:int)->int:
if self.t[o] < val:
return -1
if l == r:
return l
m = (l + r) // 2
i = self.query(o << 1, l, m, val)
if i < 0:
i = self.query(o << 1|1, m + 1, r, val)
return i
class Solution:
def numOfUnplacedFruits(self, fruits: List[int], baskets: List[int]) -> int:
n = cnt = len(fruits)
t = SegTree(baskets)
for x in fruits:
i = t.query(1, 0, n - 1, x)
if i >= 0:
t.update(1, 0, n - 1, i, -1)
cnt -= 1
return cnt
四、删除一个冲突对后最大子数组数目
题目问删除一个冲突后,能得到的合法子数组的最大个数。大致思路如下
1、考虑不删除冲突,能得到的合法子数组的个数,记为
b a s e base
ba
se
2、考虑去掉一个冲突对后,会增加多少个合法子数组 记为
e x t r a extra
e
x
t
r
a
答案为
b a s e + m a x ( e x t r a ) base + max(extra)
ba
se
ma
x
(
e
x
t
r
a
)
如何求出
b a s e ? base?
ba
se
? 对于求解子数组的个数问题,我们一般通过枚举左端点
/ /
/ 右端点,来逐一统计合法子数组的个数,然后相加得到。这里我们从后往前枚举左端点
如何求出
e x t r a ? extra?
e
x
t
r
a
? 我们同样去模拟去掉一个冲突之后,哪些左端点的合法子数组增加了即可
代码如下
// C++
class Solution {
public:
long long maxSubarrays(int n, vector<vector<int>>& conflictingPairs) {
long long ans = 0;
// 只需要知道前最小值,次小值即可
vector<array<int, 2>> g(n + 1, {n + 1, n + 1});
for(auto& conflict: conflictingPairs){
int x = conflict[0], y = conflict[1];
if(x > y) swap(x, y);
if(y < g[x][0]) g[x][1] = g[x][0], g[x][0] = y;
else if(y < g[x][1]) g[x][1] = y;
}
vector<long long> extra(n + 2);
int b0 = n + 1, b1 = n + 1;
for(int i = n; i > 0; i--){
auto& v = g[i];
if(v[0] < b0) b1 = b0, b0 = v[0];
else if(v[0] < b1) b1 = v[0];
if(v[1] < b0) b1 = b0, b0 = v[1];
else if(v[1] < b1) b1 = v[1];
ans += b0 - i;
extra[b0] += b1 - b0;
}
return ans + ranges::max(extra);
}
};
// 优化
class Solution {
public:
long long maxSubarrays(int n, vector<vector<int>>& conflictingPairs) {
long long ans = 0;
// 只需要知道前最小值,次小值即可
vector<array<int, 2>> g(n + 1, {n + 1, n + 1});
for(auto& conflict: conflictingPairs){
int x = conflict[0], y = conflict[1];
if(x > y) swap(x, y);
if(y < g[x][0]) g[x][1] = g[x][0], g[x][0] = y;
else if(y < g[x][1]) g[x][1] = y;
}
// vector<long long> extra(n + 2);
// 由于 b0 是单调递减的,所以可以用 pre_b0, mx 来维护 extra 的最大值
int b0 = n + 1, b1 = n + 1, pre_b0 = n + 1;
long long s = 0, mx = 0;
for(int i = n; i > 0; i--){
auto& v = g[i];
if(v[0] < b0) b1 = b0, b0 = v[0];
else if(v[0] < b1) b1 = v[0];
if(v[1] < b0) b1 = b0, b0 = v[1];
else if(v[1] < b1) b1 = v[1];
ans += b0 - i;
if(pre_b0 != b0){
pre_b0 = b0;
s = 0;
}
s += b1 - b0;
mx = max(mx, s);
}
return ans + mx;
}
};
# Python
class Solution:
def maxSubarrays(self, n: int, conflictingPairs: List[List[int]]) -> int:
g = [[n+1, n+1] for _ in range(n + 1)]
for conflict in conflictingPairs:
x, y = conflict[0], conflict[1]
if x > y:
x, y = y, x
if y < g[x][0]:
g[x][1], g[x][0] = g[x][0], y
elif y < g[x][1]:
g[x][1] = y
base = mx = s = 0
b0 = b1 = pre_b0 = n + 1
for i in range(n, 0, -1):
if g[i][0] < b0:
b0, b1 = g[i][0], b0
elif g[i][0] < b1:
b1 = g[i][0]
if g[i][1] < b0:
b0, b1 = g[i][1], b0
elif g[i][1] < b1:
b1 = g[i][1]
if b0 != pre_b0:
pre_b0 = b0
s = 0
base += b0 - i
s += b1 - b0
mx = max(mx, s)
return base + mx