LeetCode 1010. 总持续时间可被 60 整除的歌曲
好的!让我们一步步理解 LeetCode 1010. 总持续时间可被 60 整除的歌曲 这道题,并使用 C++ 实现高效解法。
题目分析
题目描述:
给定一个整数数组 time
,其中每个元素表示一首歌曲的持续时间(以秒为单位)。请计算总持续时间(两首歌曲的持续时间之和)可被 60 整除的歌曲对的数量。
示例:
输入:time = [30, 20, 150, 100, 40]
输出:3
解释:
- (30, 150):30 + 150 = 180(180 % 60 = 0)
- (20, 100):20 + 100 = 120(120 % 60 = 0)
- (20, 40):20 + 40 = 60(60 % 60 = 0)
暴力解法(超时)
思路:
枚举所有可能的歌曲对 (i, j)
(i < j
),检查它们的持续时间之和是否能被 60 整除。
代码:
class Solution {
public:int numPairsDivisibleBy60(vector<int>& time) {int count = 0;for (int i = 0; i < time.size(); i++) {for (int j = i + 1; j < time.size(); j++) {if ((time[i] + time[j]) % 60 == 0) {count++;}}}return count;}
};
复杂度:
- 时间复杂度:O(n²),其中 n 是数组长度。
- 空间复杂度:O(1)。
优化解法:余数统计 + 互补余数
核心思路:
- 余数分解:将每首歌曲的持续时间
t
对 60 取余,得到余数r = t % 60
。 - 互补余数:对于每个余数
r
,其互补余数为(60 - r) % 60
。 - 哈希表统计:使用数组
remainderCount
统计每个余数出现的次数。遍历数组时,累加当前余数的互补余数的出现次数,并更新当前余数的计数。
代码:
class Solution {
public:int numPairsDivisibleBy60(vector<int>& time) {vector<int> remainderCount(60, 0);int result = 0;for (int t : time){int remainder = t % 60;int target = (60 - remainder) % 60;result += remainderCount[target];remainderCount[remainder]++;}return result;}
};
复杂度:
- 时间复杂度:O(n),其中 n 是数组长度。
- 空间复杂度:O(1),因为余数数组的大小固定为 60。
详细解释
-
余数分解:
对于任意整数t
,可以分解为t = 60k + r
(其中r
是余数,0 ≤ r < 60
)。
若有两个数t1 = 60k1 + r1
和t2 = 60k2 + r2
,则它们的和为:
[
t1 + t2 = 60(k1 + k2) + (r1 + r2)
]
当且仅当(r1 + r2) % 60 = 0
时,(t1 + t2) % 60 = 0
。 -
互补余数:
- 若
r1 = 0
,则r2
必须为 0。 - 若
r1 ≠ 0
,则r2 = 60 - r1
。
例如: r1 = 20
,则r2 = 40
(因为20 + 40 = 60
)。r1 = 30
,则r2 = 30
(因为30 + 30 = 60
)。
- 若
-
哈希表统计:
- 使用数组
remainderCount
记录每个余数的出现次数。 - 遍历数组时,对于当前余数
r
,累加remainderCount[(60 - r) % 60]
,再更新remainderCount[r]
。 - 这样可以确保每个配对只被计算一次(例如
(i, j)
不会重复计算为(j, i)
)。
- 使用数组
示例验证
输入:time = [30, 20, 150, 100, 40]
步骤如下:
- 处理 30:余数
r = 30
,互补余数target = 30
,remainderCount[30] = 0
,结果 += 0,更新remainderCount[30] = 1
。 - 处理 20:余数
r = 20
,互补余数target = 40
,remainderCount[40] = 0
,结果 += 0,更新remainderCount[20] = 1
。 - 处理 150:余数
r = 30
,互补余数target = 30
,remainderCount[30] = 1
,结果 += 1,更新remainderCount[30] = 2
。 - 处理 100:余数
r = 40
,互补余数target = 20
,remainderCount[20] = 1
,结果 += 1,更新remainderCount[40] = 1
。 - 处理 40:余数
r = 40
,互补余数target = 20
,remainderCount[20] = 1
,结果 += 1,更新remainderCount[40] = 2
。
最终结果:0 + 0 + 1 + 1 + 1 = 3
,与预期一致。
总结
通过余数统计和互补余数的思想,我们将时间复杂度从 O(n²) 优化到 O(n),空间复杂度为 O(1)。这种方法适用于所有类似的“和能被 k 整除”的问题,核心在于利用余数的互补性快速查找配对。