2561-重排水果

Raphael Liu Lv10

你有两个果篮,每个果篮中有 n 个水果。给你两个下标从 0 开始的整数数组 basket1basket2
,用以表示两个果篮中每个水果的成本。

你希望两个果篮相等。为此,可以根据需要多次执行下述操作:

  • 选中两个下标 ij ,并交换 basket1 中的第 i 个水果和 basket2 中的第 j 个水果。
  • 交换的成本是 min(basket1i,basket2j)

根据果篮中水果的成本进行排序,如果排序后结果完全相同,则认为两个果篮相等。

返回使两个果篮相等的最小交换成本,如果无法使两个果篮相等,则返回 __-1 __ 。

示例 1:

**输入:** basket1 = [4,2,2,2], basket2 = [1,4,1,2]
**输出:** 1
**解释:** 交换 basket1 中下标为 1 的水果和 basket2 中下标为 0 的水果,交换的成本为 1 。此时,basket1 = [4,1,2,2] 且 basket2 = [2,4,1,2] 。重排两个数组,发现二者相等。

示例 2:

**输入:** basket1 = [2,3,4,1], basket2 = [3,2,5,1]
**输出:** -1
**解释:** 可以证明无法使两个果篮相等。

提示:

  • basket1.length == bakste2.length
  • 1 <= basket1.length <= 105
  • 1 <= basket1i,basket2i <= 109

首先,把两个数组中都有的数去掉,那么每个剩余数字的出现次数必须为偶数。这可以用哈希表来统计。

设处理后的剩余数组分别 a 和 b。

贪心地想,如果要交换 a 中最小的数,那么找一个 b 中最大的数是最合适的;对于 b 中最小的数也同理。

那么把 a 从小到大排序,b 从大到小排序,两两匹配。

但是,还有一种方案。

把 basket}_1 和 basket}_2 中的最小值 mn 当作「工具人」,对于 a[i] 和 b[i] 的交换,可以分别和 mn 交换一次,就相当于 a[i] 和 b[i] 交换了。

因此每次交换的代价为

\min(a[i], b[i], 2\cdot\textit{mn})

累加代价,即为答案。

上式也表明,如果工具人也在需要交换的数字中,那么它的最小代价必然是和其他数交换,不会发生工具人和工具人交换的情况。

设 m 为 a 的长度。代码实现时,由于 \min(a[i], b[i]) 的数字都在 a 的某个前缀与 b 某个后缀中,而剩下没有选的数(a 的后缀和 b 的前缀)不比这 m 个数小,所以取出的数一定是这 2m 个数中最小的 m 个数。

更详细的证明:设选了 a[0],\cdots,a[i] 和 b[i+1],\cdots,b[m-1],由于 a[i+1]\ge b[i+1] 且 a[i+1] \ge a[i],所以 a[i+1] \ge 任意已选数字,进而推出 a[i+2],\cdots,a[m-1] 都是大于等于任意已选数字的;对于 b[i] 和 b[i-1],\cdots,b[0] 同理。所以剩下没有选的数字都比已选数字大,进而说明已选数字是这 2m 个数中最小的 m 个数。

那么可以直接把 a 和 b 拼起来,从小到大排序后,遍历前一半的数即可(排序可以用快速选择代替,见 C++)。

附:视频讲解

[sol1-Python3]
1
2
3
4
5
6
7
8
9
10
11
12
13
class Solution:
def minCost(self, basket1: List[int], basket2: List[int]) -> int:
cnt = Counter()
for x, y in zip(basket1, basket2):
cnt[x] += 1
cnt[y] -= 1
mn = min(cnt)
a = []
for x, c in cnt.items():
if c % 2: return -1
a.extend([x] * (abs(c) // 2))
a.sort() # 也可以用快速选择
return sum(min(x, mn * 2) for x in a[:len(a) // 2])
[sol1-Java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public long minCost(int[] basket1, int[] basket2) {
var cnt = new HashMap<Integer, Integer>();
for (int i = 0; i < basket1.length; ++i) {
cnt.merge(basket1[i], 1, Integer::sum);
cnt.merge(basket2[i], -1, Integer::sum);
}

int mn = Integer.MAX_VALUE;
var a = new ArrayList<Integer>();
for (var e : cnt.entrySet()) {
int x = e.getKey(), c = e.getValue();
if (c % 2 != 0) return -1;
mn = Math.min(mn, x);
for (c = Math.abs(c) / 2; c > 0; --c)
a.add(x);
}

long ans = 0;
a.sort((x, y) -> x - y); // 也可以用快速选择
for (int i = 0; i < a.size() / 2; ++i)
ans += Math.min(a.get(i), mn * 2);
return ans;
}
}
[sol1-C++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public:
long long minCost(vector<int> &basket1, vector<int> &basket2) {
unordered_map<int, int> cnt;
for (int i = 0; i < basket1.size(); ++i) {
++cnt[basket1[i]];
--cnt[basket2[i]];
}

int mn = INT_MAX;
vector<int> a;
for (auto [x, c] : cnt) {
if (c % 2) return -1;
mn = min(mn, x);
for (c = abs(c) / 2; c > 0; --c)
a.push_back(x);
}

long ans = 0;
nth_element(a.begin(), a.begin() + a.size() / 2, a.end()); // 快速选择
for (int i = 0; i < a.size() / 2; ++i)
ans += min(a[i], mn * 2);
return ans;
}
};
[sol1-Go]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func minCost(basket1, basket2 []int) (ans int64) {
cnt := map[int]int{}
for i, x := range basket1 {
cnt[x]++
cnt[basket2[i]]--
}

mn, a := math.MaxInt, []int{}
for x, c := range cnt {
if c%2 != 0 {
return -1
}
mn = min(mn, x)
for c = abs(c) / 2; c > 0; c-- {
a = append(a, x)
}
}

sort.Ints(a) // 也可以用快速选择
for _, x := range a[:len(a)/2] {
ans += int64(min(x, mn*2))
}
return
}

func abs(x int) int { if x < 0 { return -x }; return x }
func min(a, b int) int { if b < a { return b }; return a }

复杂度分析

  • 时间复杂度:O(n\log n) 或 O(n),其中 n 为 basket}_1 的长度。用快速选择可以做到 O(n)(见 C++)。
  • 空间复杂度:O(n)。
 Comments
On this page
2561-重排水果