🎮 每日一练-区间合并问题(附快速幂)
🤔 区间合并的思路:首先按照左端点从小到大对区间进行排序,然后对于第 i 个区间,不断地向右扩展与它有交集的区间。
直接上例题 🌰!
1. 合并区间
以数组 intervals
表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi]
。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
示例 1:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。
提示:
1 <= intervals.length <= 104
intervals[i].length == 2
0 <= starti <= endi <= 104
✏️ 题解
class Solution {
public int[][] merge(int[][] intervals) {
// 先对数组根据元素左边界排序,然后再遍历合并
Arrays.sort(intervals, new Comparator<int[]>() {
@Override
public int compare(int[] a,int[] b){
return Integer.compare(a[0], b[0]);
}
});
int left = intervals[0][0];
int right = intervals[0][1];
int cnt = 0;
for(int i = 1; i < intervals.length; i++){
int[] cur = intervals[i];
if(right < cur[0]){
// 两区间不想交
intervals[cnt][0] = left;
intervals[cnt][1] = right;
left = cur[0];
right = cur[1];
cnt++;
} else if(right < cur[1]){
right = cur[1];
}
}
intervals[cnt][0] = left;
intervals[cnt][1] = right;
cnt++;
int[][] ans = new int[cnt][2];
for(int i = 0; i < cnt; i++){
ans[i] = intervals[i];
}
return ans;
}
}
2. 统计将重叠区间合并成组的方案数
给你一个二维整数数组 ranges
,其中 ranges[i] = [starti, endi]
表示 starti
到 endi
之间(包括二者)的所有整数都包含在第 i
个区间中。
你需要将 ranges
分成 两个 组(可以为空),满足:
- 每个区间只属于一个组。
- 两个有 交集 的区间必须在 同一个 组内。
如果两个区间有至少 一个 公共整数,那么这两个区间是 有交集 的。
- 比方说,区间
[1, 3]
和[2, 5]
有交集,因为2
和3
在两个区间中都被包含。
请你返回将 ranges
划分成两个组的 总方案数 。由于答案可能很大,将它对 109 + 7
取余 后返回。
示例 1:
输入:ranges = [[6,10],[5,15]]
输出:2
解释:
两个区间有交集,所以它们必须在同一个组内。
所以有两种方案:
- 将两个区间都放在第 1 个组中。
- 将两个区间都放在第 2 个组中。
示例 2:
输入:ranges = [[1,3],[10,20],[2,5],[4,8]]
输出:4
解释:
区间 [1,3] 和 [2,5] 有交集,所以它们必须在同一个组中。
同理,区间 [2,5] 和 [4,8] 也有交集,所以它们也必须在同一个组中。
所以总共有 4 种分组方案:
- 所有区间都在第 1 组。
- 所有区间都在第 2 组。
- 区间 [1,3] ,[2,5] 和 [4,8] 在第 1 个组中,[10,20] 在第 2 个组中。
- 区间 [1,3] ,[2,5] 和 [4,8] 在第 2 个组中,[10,20] 在第 1 个组中。
提示:
1 <= ranges.length <= 105
ranges[i].length == 2
0 <= starti <= endi <= 109
✏️ 题解
class Solution {
public int countWays(int[][] ranges) {
// 感觉和“合并区间”很像:
// https://leetcode.cn/problems/merge-intervals/?envType=study-plan-v2&envId=kuaishou-2023-fall-sprint
// 先对数组排序,排序之后再计数(转变为数学问题)
int n = ranges.length;
final int MOD = 1000000007;
if(n == 1)return 2;
Arrays.sort(ranges,new Comparator<int[]>(){
@Override
public int compare(int[] a, int[] b){
return a[0] - b[0];
}
});
int left = ranges[0][0];
int right = ranges[0][1];
int cnt = 0; // 总区间数目
for(int i = 1; i < n; i++){
int[] cur = ranges[i];
if(right < cur[0]){
cnt++;
left = cur[0];
right = cur[1];
}else if(right < cur[1]){
right = cur[1];
}
}
cnt++;
return (int)fastPow(2, cnt, MOD)%MOD;
}
public long fastPow(long x, long n, long mod) {
long res = 1;
for (; n != 0; n >>= 1) {
if ((n & 1) != 0) {
res = res * x % mod;
}
x = x * x % mod;
}
return res;
}
}
3. 附加:快速幂
从例题 2 中我们可以看到,题目要求我们对结果取模以防止溢出,一种比较直接粗暴的方法就是在 for 循环中每次乘 2 操作都取模,还有一种方法就是快速幂!
快速幂让我们可以快速计算$x^n$并且防止溢出(对 mod 取模),同时比循环 n 次计算时间复杂度更低,有时候题目超时可以尝试采取该做法。我们也来看一道题🌰
3.1 数组元素的最小非零乘积
给你一个正整数 p
。你有一个下标从 1 开始的数组 nums
,这个数组包含范围 [1, 2p - 1]
内所有整数的二进制形式(两端都 包含)。你可以进行以下操作 任意 次:
- 从
nums
中选择两个元素x
和y
。 - 选择
x
中的一位与y
对应位置的位交换。对应位置指的是两个整数 相同位置 的二进制位。
比方说,如果 x = 11***0***1
且 y = 00***1***1
,交换右边数起第 2
位后,我们得到 x = 11***1***1
和 y = 00***0***1
。
请你算出进行以上操作 任意次 以后,nums
能得到的 最小非零 乘积。将乘积对 109 + 7
取余 后返回。
注意:答案应为取余 之前 的最小值。
示例 1:
输入:p = 1
输出:1
解释:nums = [1] 。
只有一个元素,所以乘积为该元素。
示例 2:
输入:p = 2
输出:6
解释:nums = [01, 10, 11] 。
所有交换要么使乘积变为 0 ,要么乘积与初始乘积相同。
所以,数组乘积 1 * 2 * 3 = 6 已经是最小值。
示例 3:
输入:p = 3
输出:1512
解释:nums = [001, 010, 011, 100, 101, 110, 111]
- 第一次操作中,我们交换第二个和第五个元素最左边的数位。
- 结果数组为 [001, 110, 011, 100, 001, 110, 111] 。
- 第二次操作中,我们交换第三个和第四个元素中间的数位。
- 结果数组为 [001, 110, 001, 110, 001, 110, 111] 。
数组乘积 1 * 6 * 1 * 6 * 1 * 6 * 7 = 1512 是最小乘积。
提示:
1 <= p <= 60
✏️ 题解
class Solution {
public int minNonZeroProduct(int p) {
if(p == 1)return 1;
// 要计算最小乘积,此时的两个数的差值要尽可能大
// A = 2^p - 1; B = A - 1
// 则最小值为 1 * B * 1*B * ... * 1*B 一共B/2对1*B,然后再*A 即可
long mod = 1000000007;
long x = fastPow(2, p, mod) - 1;
long y = (long) 1 << (p - 1);
return (int) (fastPow(x - 1, y - 1, mod) * x % mod);
}
// 快速幂:计算 x 的 n 次方,然后模 mod
public long fastPow(long x, long n, long mod) {
long res = 1;
for (; n != 0; n >>= 1) {
if ((n & 1) != 0) {
res = res * x % mod;
}
x = x * x % mod;
}
return res;
}
}