指尖劃過的軌跡,藏着最細膩的答案~
題目:
給你兩個長度為 n 的整數數組,fruits 和 baskets,其中 fruits[i] 表示第 i 種水果的 數量,baskets[j] 表示第 j 個籃子的 容量。
你需要對 fruits 數組從左到右按照以下規則放置水果:
- 每種水果必須放入第一個 容量大於等於 該水果數量的 最左側可用籃子 中。
- 每個籃子只能裝 一種 水果。
- 如果一種水果 無法放入 任何籃子,它將保持 未放置。 返回所有可能分配完成後,剩餘未放置的水果種類的數量。
示例 1
輸入: fruits = [4,2,5], baskets = [3,5,4]
輸出: 1
解釋:
- fruits[0] = 4 放入 baskets[1] = 5。
- fruits[1] = 2 放入 baskets[0] = 3。
- fruits[2] = 5 無法放入 baskets[2] = 4。
由於有一種水果未放置,我們返回 1。
示例 2
輸入: fruits = [3,6,1], baskets = [6,4,7]
輸出: 0
解釋:
- fruits[0] = 3 放入 baskets[0] = 6。
- fruits[1] = 6 無法放入 baskets[1] = 4(容量不足),但可以放入下一個可用的籃子 baskets[2] = 7。
- fruits[2] = 1 放入 baskets[1] = 4。
由於所有水果都已成功放置,我們返回 0。
提示:
n == fruits.length == baskets.length
1 <= n <= 10^5
1 <= fruits[i], baskets[i] <= 10^9
分析:
線段樹本質上是一棵二叉樹。靈神的線段樹模版~ 貼在這裏:
// 模板來源 https://leetcode.cn/circle/discuss/mOr1u6/
// 線段樹有兩個下標,一個是線段樹節點的下標,另一個是線段樹維護的區間的下標
// 節點的下標:從 1 開始,如果你想改成從 0 開始,需要把左右兒子下標分別改成 node*2+1 和 node*2+2
// 區間的下標:從 0 開始
template<typename T>
class SegmentTree {
// 注:也可以去掉 template<typename T>,改在這裏定義 T
// using T = pair<int, int>;
int n;
vector<T> tree;
// 合併兩個 val
T merge_val(T a, T b) const {
return max(a, b); // **根據題目修改**
}
// 合併左右兒子的 val 到當前節點的 val
void maintain(int node) {
tree[node] = merge_val(tree[node * 2], tree[node * 2 + 1]);
}
// 用 a 初始化線段樹
// 時間複雜度 O(n)
void build(const vector<T>& a, int node, int l, int r) {
if (l == r) { // 葉子
tree[node] = a[l]; // 初始化葉節點的值
return;
}
int m = (l + r) / 2;
build(a, node * 2, l, m); // 初始化左子樹
build(a, node * 2 + 1, m + 1, r); // 初始化右子樹
maintain(node);
}
void update(int node, int l, int r, int i, T val) {
if (l == r) { // 葉子(到達目標)
// 如果想直接替換的話,可以寫 tree[node] = val
tree[node] = merge_val(tree[node], val);
return;
}
int m = (l + r) / 2;
if (i <= m) { // i 在左子樹
update(node * 2, l, m, i, val);
} else { // i 在右子樹
update(node * 2 + 1, m + 1, r, i, val);
}
maintain(node);
}
T query(int node, int l, int r, int ql, int qr) const {
if (ql <= l && r <= qr) { // 當前子樹完全在 [ql, qr] 內
return tree[node];
}
int m = (l + r) / 2;
if (qr <= m) { // [ql, qr] 在左子樹
return query(node * 2, l, m, ql, qr);
}
if (ql > m) { // [ql, qr] 在右子樹
return query(node * 2 + 1, m + 1, r, ql, qr);
}
T l_res = query(node * 2, l, m, ql, qr);
T r_res = query(node * 2 + 1, m + 1, r, ql, qr);
return merge_val(l_res, r_res);
}
public:
// 線段樹維護一個長為 n 的數組(下標從 0 到 n-1),元素初始值為 init_val
SegmentTree(int n, T init_val) : SegmentTree(vector<T>(n, init_val)) {}
// 線段樹維護數組 a
SegmentTree(const vector<T>& a) : n(a.size()), tree(2 << bit_width(a.size() - 1)) {
build(a, 1, 0, n - 1);
}
// 更新 a[i] 為 merge_val(a[i], val)
// 時間複雜度 O(log n)
void update(int i, T val) {
update(1, 0, n - 1, i, val);
}
// 返回用 merge_val 合併所有 a[i] 的計算結果,其中 i 在閉區間 [ql, qr] 中
// 時間複雜度 O(log n)
T query(int ql, int qr) const {
return query(1, 0, n - 1, ql, qr);
}
// 獲取 a[i] 的值
// 時間複雜度 O(log n)
T get(int i) const {
return query(1, 0, n - 1, i, i);
}
};
int main() {
SegmentTree t(8, 0LL); // 如果這裏寫 0LL,那麼 SegmentTree 存儲的就是 long long 數據
t.update(0, 1LL << 60);
cout << t.query(0, 7) << endl;
vector<int> nums = {3, 1, 4, 1, 5, 9, 2, 6};
// 注意:如果要讓 SegmentTree 存儲 long long 數據,需要傳入 vector<long long>
SegmentTree t2(nums); // 這裏 SegmentTree 存儲的是 int 數據
cout << t2.query(0, 7) << endl;
return 0;
}
作者:靈茶山艾府
鏈接:https://leetcode.cn/discuss/post/mOr1u6/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
本題我們從二分的角度來使用線段樹,即左子樹與右子樹的深度遞歸。
對於x = fruits[i]來説,我們二分的從 baskets中找到第一個大於等於x的籃子來裝水果,具體的:
- 如果整棵樹的最大值都比x小,説明當前沒有籃子可以裝的下x水果,直接返回-1,表示沒有滿足的下標;所以線段樹要維護區間最大值。
- 否則,遞歸左子樹(二分的左區間);
- 如果左子樹沒有滿足條件的葉子結點,則遞歸右子樹(二分的右區間);
- 如果最終有滿足條件的葉子結點,則將該葉子結點賦值為-1,表示已經使用,然後更新區間最大值,返回該葉子結點的下標。
二分結束後,如果返回值不是-1,説明找到了可以裝x水果的籃子,否則沒有符合條件的籃子,將最終結果加1。
AC代碼:
class SegmentTree {
vector<int> mx;
void maintain(int node) {
mx[node] = max(mx[node * 2], mx[node * 2 + 1]);
}
void build (const vector<int>& a, int node, int l, int r) {
if (l == r) {
mx[node] = a[l];
return ;
}
int mid = (r - l) / 2 + l;
build(a, node * 2, l, mid);
build(a, node * 2 + 1, mid + 1, r);
maintain(node);
}
public:
SegmentTree(const vector<int>& a) {
size_t n = a.size();
mx.resize(2 << bit_width(n - 1));
build(a, 1, 0, n - 1);
}
// 找區間內第一個>=x 的數,並更新為-1,返回這個數的下標,沒有則返回-1
int findFirstAndUpdate(int node, int l, int r, int x) {
if (mx[node] < x) {
return -1;
}
if (l == r) {
mx[node] = -1;
return l;
}
int mid = (r - l) / 2 + l;
int i = findFirstAndUpdate(node * 2, l, mid, x);
if (i == -1) {
i = findFirstAndUpdate(node * 2 + 1, mid + 1, r, x);
}
maintain(node);
return i;
}
};
class Solution {
public:
int numOfUnplacedFruits(vector<int>& fruits, vector<int>& baskets) {
SegmentTree st(baskets);
int ans = 0;
int n = fruits.size();
for (int f : fruits) {
if (st.findFirstAndUpdate(1, 0, n - 1, f) == -1) {
ans++;
}
}
return ans;
}
};