1.题目
在歌曲列表中,第 i 首歌曲的持续时间为 time[i] 秒。
返回其总持续时间(以秒为单位)可被 60 整除的歌曲对的数量。形式上,我们希望索引的数字 i 和 j 满足 i < j 且有 (time[i] + time[j]) % 60 == 0。
示例 1:
输入:[30,20,150,100,40]
输出:3
解释:这三对的总持续时间可被 60 整数:
(time[0] = 30, time[2] = 150): 总持续时间 180
(time[1] = 20, time[3] = 100): 总持续时间 120
(time[1] = 20, time[4] = 40): 总持续时间 60
示例 2:
输入:[60,60,60]
输出:3
解释:所有三对的总持续时间都是 120,可以被 60 整数。
提示:
1 <= time.length <= 60000
1 <= time[i] <= 500
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/pairs-of-songs-with-total-durations-divisible-by-60
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
2题解
一次遍历+递推:
实际我们可以把余数桶认为是一个等待配对的“登记表”,每一次得到一个时间值我们都按照如下操作进行处理:
假设之前的“登记表”上的已有的配对都已经被正确处理了,待处理的只有这个新的时间值
检查“登记表”中是否有需要它进行配对的值,有多少个就又增加了多少个配对
本次配对完成,把当前时间值登记入表,等待后面的时间和它进行配对
本解法当然不会分别记录每个新加入的时间对应的配对数量,但是每次处理新的时间会更新结果计数,实际就是递推状态的记录,实际仍然隐含了递推的想法。只要确定了最初没有处理时间时的配对数量作初始值,即可开始从头递推,显然初始值是 0。
这样,利用动态增长的“登记表”就能只使用一次遍历,并且规避了解法一中的二次计数问题,而且也不需要对余数 30 的情况进行特殊处理。
class Solution
{
public:
int numPairsDivisibleBy60(vector<int>& time)
{
int res = 0;
vector<int> ans(60);
for(int num : time)
{
int mod = num % 60;
int target = mod == 0 ? 0 : 60 - mod;
res += ans[target]; //找满足条件的target的次数
ans[mod]++;
}
return res;
}
};
链接:https://leetcode-cn.com/problems/pairs-of-songs-with-total-durations-divisible-by-60/solution/jian-duan-you-rong-yi-li-jie-de-on-suan-fa-by-ciel/
两次遍历:
class Solution
{
public:
//2次遍历
int numPairsDivisibleBy60(vector<int>& time)
{
int rs = 0;
vector<int>hash(60); //0-59
for (int num : time)
{
hash[num%60]++;
}
//20 30 40
//1 2 2
for (int num : time)
{
//遍历到一个数字 就让他-1,因为已经被用过了 比如20和40匹配 后面避免40和20匹配
int mod = num % 60;
hash[mod]--;
int target = mod == 0 ? 0 : 60 - mod;
rs += hash[target];
}
return rs;
}
};