Minimum Money Required Before Transactions

Minimum Money Required Before Transactions

You are given a 0-indexed 2D integer array transactions , where transactions[i] = [costi, cashbacki] .

The array describes transactions, where each transaction must be completed exactly once in some order. At any given moment, you have a certain amount of money . In order to complete transaction $i$, $money \geq {cost}_i$ must hold true. After performing a transaction, money becomes $money - {cost}_i + {cashback}_i$.

Return the minimum amount of money required before any transaction so that all of the transactions can be completed regardless of the order of the transactions.

Example 1:

Input: transactions = [[2,1],[5,0],[4,2]]
Output: 10
Explanation:
Starting with money = 10, the transactions can be performed in any order.
It can be shown that starting with money < 10 will fail to complete all transactions in some order.

Example 2:

Input: transactions = [[3,0],[0,3]]
Output: 3
Explanation:
- If transactions are in the order [[3,0],[0,3]], the minimum money required to complete the transactions is 3.
- If transactions are in the order [[0,3],[3,0]], the minimum money required to complete the transactions is 0.
Thus, starting with money = 3, the transactions can be performed in any order.

Constraints:

$1 \leq transactions.length \leq {10}^{5}$
$transactions[i].length == 2$
$0 \leq {cost}_i, {cashback}_i \leq {10}^{9}$

 

解题思路

  对于某种顺序确定后如何求最大值呢,假设已经完成了前$i-1$笔交易,那么第$i$笔的交易应该要满足$$\mathrm{money} - (a_0 - b_0) - (a_1 - b_1) - \cdots - (a_{i-1} - b_{i-1}) \geq a_i$$即$$\mathrm{money} \geq (a_0 - b_0) + (a_1 - b_1) + \cdots + (a_{i-1} + b_{i-1}) + a_i$$

  $\mathrm{money}$要取最小值,那么就要遍历所有情况,取不等式右边的最大值。那么最大值会出现在哪一种情况呢,从集合的角度来看,根据$a_i$的不同把方案分成若干个集合,不等式右边的最大值一定会在其中一个集合出现。接着枚举$a_i$,当$a_i$固定后,要使得右式取最大值等价于求$(a_0 - b_0) + (a_1 - b_1) + \cdots + (a_{i-1} + b_{i-1})$的最大值,当每一项都是正数时,即$a_k - b_k > 0$,就可以取到最大值。因此一开始可以枚举 transactions 数组,把$a_k - b_k > 0$的交易累加,就可以取到最大值,然后枚举$a_i$,如果有$a_i - b_i > 0$那么总和减去$a_i - b_i$再加上$a_i$就可以快速算出不等式右边的值。

  AC代码如下:

 1 class Solution {
 2 public:
 3     long long minimumMoney(vector<vector<int>>& transactions) {
 4         long long sum = 0;
 5         for (auto &p : transactions) {
 6             if (p[0] - p[1] > 0) sum += p[0] - p[1];
 7         }
 8         long long ret = 0;
 9         for (auto &p : transactions) {
10             long long t = sum;
11             if (p[0] - p[1] > 0) t -= p[0] - p[1];
12             ret = max(ret, t + p[0]);
13         }
14         return ret;
15     }
16 };

  还有一种做法,非常感谢laj大佬提供的思路。

  先说一下算法的执行过程,首先根据$a_i - b_i$的正负性分成两组,其中对于$a_i - b_i \geq 0$这组的元素按照$b_i$进行升序排序,对于$a_i - b_i < 0$这组的元素按照$a_i$进行降序排序。然后对这两组进行合并,其中$a_i - b_i \geq 0$的在前部分,$a_i - b_i < 0$的在后部分。接下来枚举每一个元素,用$r$来表示上一次交易结束后剩余的金额,对于某个交易$i$,如果有$r < a_i$,意味着初始时至少要再补$a_i - r$这么多的金额,同时需要把$r$更新成$a_i$,然后更新$r = r - a_i + b_i$。最后返回答案(也就是每次交易前要补的金额的总和)。

  下面来证明这种做法是对的。

  要使得能完成所有交易的最少钱数最大,意味着每次交易前所需的金额应尽可能的大,即在第$i$笔交易前所需的金额$\sum\limits_{k=1}^{i-1}{(a_k-b_k)}+a_i$应尽可能的大。那么是否存在一种交易顺序,使得在某笔交易前的$\sum\limits_{k=1}^{i-1}{(a_k-b_k)}+a_i$能取到最大值呢?

  在所有交易顺序构成的集合中,我们先考虑其中一个子集,这个子集内的交易顺序都满足$a_i - b_i \geq 0$在前部分,$a_i - b_i < 0$的在后部分,现在要证明能完成所有交易的最少钱数最大值必定在这个子集中。假设在某个交易顺序中,第$i$笔交易满足$a_i - b_i < 0$,第$i + 1$笔交易满足$a_{i+1} - b_{i+1} \geq 0$。现在来看看交换第$i$笔和第$i+1$笔交易前后所需的金额大小,需要注意的是,交换后只会对进行第$i$笔和第$i+1$笔交易前所需的金额数产生影响,其他笔交易的所需金额不会发生改变。

  进行第$i$笔交易前需要的金额 进行第$i+1$笔交易前需要的金额
交换前 $\sum\limits_{k=1}^{i-1}{(a_k-b_k)}+a_i$ $\sum\limits_{k=1}^{i}{(a_k-b_k)}+a_{i+1}$
交换后 $\sum\limits_{k=1}^{i-1}{(a_k-b_k)}+a_{i+1}$ $\sum\limits_{k=1}^{i-1}{(a_k-b_k)}+a_{i+1}-b_{i+1}+a_i$

化简,对每个式子减去$\sum\limits_{k=1}^{i-1}{(a_k-b_k)}$,得到:

  进行第$i$笔交易前需要的金额 进行第$i+1$笔交易前需要的金额
交换前 $a_i$ $a_i - b_i + a_{i+1}$
交换后 $a_{i+1}$ $a_{i+1} - b_{i+1} + a_{i}$

  可以发现,由于$a_i - b_i < 0$,因此有$a_{i+1} > a_i - b_i + a_{i+1}$;由于$a_i - b_i \geq 0$,因此有$a_{i+1} - b_{i+1} + a_{i} \geq a_i$,即交换后的两笔交易所需的金额比交换前的要大,因此更大的金额会出现在交换后的交易顺序中。因此对于某个交易顺序,只要发现前一笔交易的$a_i - b_i < 0$,而后一笔交易$a_{i+1} - b_{i+1} \geq 0$,那么我们就交换这两笔交易的顺序,这样总是可以把交易的顺序变成$a_i - b_i \geq 0$在前部分,$a_i - b_i < 0$的在后部分,且所需的交易金额会变大。

  现在我们把答案的范围缩小到这个子集中,再去看看在这个子集中,答案是否存在于某个更小的子集中。

  先考虑交易顺序的前部分,即满足$a_i - b_i \geq 0$的部分。假设某个交易顺序中第$i$笔交易和第$i+1$笔交易满足$b_i > b_{i+1}$,现在来看看交换第$i$笔和第$i+1$笔交易前后所需的金额大小(与上面得到的结果一样):

  进行第$i$笔交易前需要的金额 进行第$i+1$笔交易前需要的金额
交换前 $a_i$ $a_i - b_i + a_{i+1}$
交换后 $a_{i+1}$ $a_{i+1} - b_{i+1} + a_{i}$

  由于此时$a_{i+1} - b_{i+1} \geq 0$,因此$a_{i+1} - b_{i+1} + a_{i} \geq a_i$,同时又因为$b_i > b_{i+1}$,因此$a_{i+1} - b_{i+1} + a_{i} > a_i - b_i + a_{i+1}$,即交换后的第$i+1$笔交易前需要的金额都比交换前的两笔交易所需的金额要大,因此更大的金额会出现在交换后的交易顺序中,即出现在满足$b_i < b_{i+1}$的交易顺序中。因此在交易顺序的前部分,如果发现前一笔交易的$b_i$比后一笔交易的$b_{i+1}$大,那么就交换这两笔交易。

  同理可证对于交易的后部分,即满足$a_i - b_i < 0$的部分,更大的交易金额会出现在满足$a_i > a_{i+1}$的交易顺序中。

  因此最大的交易金额必定会出现在这样的交易顺序中:交易顺序满足$a_i - b_i \geq 0$在前部分,$a_i - b_i < 0$的在后部分,在前部分中满足$b_i < b_{i+1}$,在后部分中满足$a_i > a_{i+1}$。

  AC代码如下:

 1 class Solution {
 2 public:
 3     long long minimumMoney(vector<vector<int>>& transactions) {
 4         vector<vector<int>> v1, v2;
 5         for (auto &p : transactions) {
 6             if (p[0] - p[1] >= 0) v1.push_back(p);
 7             else v2.push_back(p);
 8         }
 9         
10         sort(v1.begin(), v1.end(), [&](vector<int> &a, vector<int> &b) {
11             return a[1] < b[1];
12         });
13         sort(v2.begin(), v2.end(), [&](vector<int> &a, vector<int> &b) {
14             return a[0] > b[0];
15         });
16         v1.insert(v1.end(), v2.begin(), v2.end());
17         
18         long long ret = 0, r = 0;
19         for (auto &p : v1) {
20             if (r < p[0]) {
21                 ret += p[0] - r;
22                 r = p[0];
23             }
24             r -= p[0] - p[1];
25         }
26         
27         return ret;
28     }
29 };

  当然也可以按照上面证明的过程那样去求最大值,AC代码如下:

 1 class Solution {
 2 public:
 3     long long minimumMoney(vector<vector<int>>& transactions) {
 4         vector<vector<int>> v1, v2;
 5         for (auto &p : transactions) {
 6             if (p[0] - p[1] >= 0) v1.push_back(p);
 7             else v2.push_back(p);
 8         }
 9         
10         sort(v1.begin(), v1.end(), [&](vector<int> &a, vector<int> &b) {
11             return a[1] < b[1];
12         });
13         sort(v2.begin(), v2.end(), [&](vector<int> &a, vector<int> &b) {
14             return a[0] > b[0];
15         });
16         v1.insert(v1.end(), v2.begin(), v2.end());
17 
18         long long ret = 0, r = 0;
19         for (auto &p : v1) {
20             ret = max(ret, r + p[0]);
21             r += p[0] - p[1];
22         }
23         
24         return ret;
25     }
26 };

 

参考资料

  y总,第一名是谁,可以讲一下第一名的代码吗?力扣第87场双周赛:https://www.bilibili.com/video/BV1FV4y1u79H

posted @ 2022-09-19 19:44  onlyblues  阅读(26)  评论(0编辑  收藏  举报
Web Analytics