1 题目
给你一个下标从 0 开始的数组 nums ,数组中的元素都是 正 整数。请你选出两个下标 i 和 j(i != j),且 nums[i] 的数位和 与 nums[j] 的数位和相等。
请你找出所有满足条件的下标 i 和 j ,找出并返回nums[i] + nums[j]可以得到的 最大值 *。*如果不存在这样的下标对,返回 -1。
示例 1:
输入:nums = [18,43,36,13,7]
输出:54
解释:满足条件的数对 (i, j) 为:
- (0, 2) ,两个数字的数位和都是 9 ,相加得到 18 + 36 = 54 。
- (1, 4) ,两个数字的数位和都是 7 ,相加得到 43 + 7 = 50 。
所以可以获得的最大和是 54 。
示例 2:
输入:nums = [10,12,19,14]
输出:-1
解释:不存在满足条件的数对,返回 -1 。
提示:
1 <= nums.length <= 1051 <= nums[i] <= 109
2 代码实现
cpp
class Solution {
public:
int maximumSum(vector<int>& nums) {
unordered_map<int ,int> dict ;
int res = -1 ;
for (int i : nums ){
int digitsSum = 0 ;
int temp = i ;
while (temp > 0 ){
digitsSum += temp % 10 ;
temp /= 10 ;
}
if (dict.count(digitsSum)){
res = max(res , dict[digitsSum] + i);
dict[digitsSum] = max(dict[digitsSum] , i);
}else {
dict[digitsSum] = i ;
}
}
return res ;
}
};
题解
一、题目核心理解
首先明确这道题的核心需求 :给定一个整数数组 nums,找到两个数,它们的各位数字之和相等 ,并且这两个数的和是所有符合条件的数对中最大的 ;如果不存在这样的数对,返回 -1。
举个例子:
- 输入
nums = [18,43,36,13,7]- 18:1+8=9;43:4+3=7;36:3+6=9;13:1+3=4;7:7
- 数字和为 9 的数对:18+36=54;数字和为 7 的数对:43+7=50
- 最大和是 54,因此返回 54。
- 输入
nums = [10,12,19,14]- 所有数的数字和分别是 1、3、10、5,无重复,返回 - 1。
二、代码逐行拆解
下面结合代码逻辑,从思路→实现→细节三层讲解:
cpp
class Solution {
public:
int maximumSum(vector<int>& nums) {
// 1. 哈希表:key=数字各位和,value=该和对应的「当前最大数字」
unordered_map<int ,int> dict ;
// 2. 结果初始化:默认-1(表示无符合条件的数对)
int res = -1 ;
// 3. 遍历数组中的每个数字
for (int i : nums ){
// 4. 计算当前数字i的「各位数字之和」
int digitsSum = 0 ; // 存储各位和
int temp = i ; // 临时变量,避免修改原数字i
while (temp > 0 ){ // 逐位拆解数字(比如i=18→temp=18→1→0)
digitsSum += temp % 10 ; // 取最后一位(18%10=8,1%10=1)
temp /= 10 ; // 去掉最后一位(18/10=1,1/10=0)
}
// 5. 核心逻辑:判断当前数字和是否已存在哈希表中
if (dict.count(digitsSum)){
// 5.1 若存在:说明能组成数对,更新最大和
res = max(res , dict[digitsSum] + i);
// 5.2 同时更新哈希表:保留该数字和对应的「更大的数」(为后续数对留更大值)
dict[digitsSum] = max(dict[digitsSum] , i);
}else {
// 5.3 若不存在:将该数字和+数字存入哈希表
dict[digitsSum] = i ;
}
}
// 6. 返回最终结果(无则-1,有则最大和)
return res ;
}
};
三、关键逻辑深度解析
1. 为什么用哈希表?
- 核心需求是「按数字和分组」,哈希表的
key正好可以作为「数字和」的分组标识,value存储该分组下的最大数字。 - 时间复杂度:遍历数组 O (n),每个数字拆解位数 O (位数)(最多 10 位),整体 O (n),效率极高。
2. 核心步骤的举例说明
以 nums = [18,43,36,13,7] 为例,一步步走:
| 遍历数字 i | 计算 digitsSum | 哈希表初始状态 | 逻辑判断 | res 更新 | 哈希表更新后 |
|---|---|---|---|---|---|
| 18 | 1+8=9 | 空 | 不存在 | -1 | {9:18} |
| 43 | 4+3=7 | {9:18} | 不存在 | -1 | {9:18,7:43} |
| 36 | 3+6=9 | {9:18,7:43} | 存在 | max(-1,18+36)=54 | {9:36,7:43}(保留更大的 36) |
| 13 | 1+3=4 | {9:36,7:43} | 不存在 | 54 | {9:36,7:43,4:13} |
| 7 | 7 | {9:36,7:43,4:13} | 存在 | max(54,43+7)=54 | {9:36,7:43,4:13}(43>7,不更新) |
最终 res=54,符合预期。
3. 为什么要更新哈希表的 value?
比如如果后续还有一个数字和为 9 的数(比如 45,4+5=9),此时哈希表中 9 对应的 value 是 36,45+36=81,比之前的 54 更大,res 会更新为 81;同时哈希表会把 9 的 value 更新为 45(因为 45>36),为后续可能的数对留更大的数。
四、边界情况分析
- 数组长度 < 2:直接返回 - 1(代码天然支持,因为无法组成数对)。
- 多个数对但和更小:比如 nums=[1,2,10,11],数字和分别是 1、2、1、2:
- 1 和 10 的和 = 11,2 和 11 的和 = 13,res 最终是 13。
- 所有数的数字和都唯一:返回 - 1(初始值)。
五、总结
这道题的核心思路是:
- 分组:按数字各位和分组(哈希表);
- 择优:每组内保留最大数字,遇到同组数字时计算和并更新最大值;
- 结果:最终最大和即为答案,无则返回 - 1。
3 题目
给你一组多米诺骨牌 dominoes 。
形式上,dominoes[i] = [a, b] 与 dominoes[j] = [c, d] 等价 当且仅当 (a == c 且 b == d) 或者 (a == d 且 b == c) 。即一张骨牌可以通过旋转 0 度或 180 度得到另一张多米诺骨牌。
在 0 <= i < j < dominoes.length 的前提下,找出满足 dominoes[i] 和 dominoes[j] 等价的骨牌对 (i, j) 的数量。
示例 1:
输入:dominoes = [[1,2],[2,1],[3,4],[5,6]]
输出:1
示例 2:
输入:dominoes = [[1,2],[1,2],[1,1],[1,2],[2,2]]
输出:3
提示:
1 <= dominoes.length <= 4 * 104dominoes[i].length == 21 <= dominoes[i][j] <= 9
4 代码实现
cpp
class Solution {
public:
int numEquivDominoPairs(vector<vector<int>>& dominoes) {
unordered_map<int ,int > countMap ;
int result = 0 ;
for (auto& dom : dominoes){
int a = dom[0] , b = dom[1] ;
int key = a < b ? (a * 10 + b) : (b * 10 + a );
countMap[key] ++;
}
for (auto& pair : countMap){
int k = pair.second;
if (k >= 2){
result += k * (k - 1 ) / 2 ;
}
}
return result ;
}
};
一点不会**,就知道计数和组合数是一样的。**先看题解吧别死磕了。
题解
一、题目核心拆解
首先明确核心问题:
- 两张多米诺骨牌
[a,b]和[c,d]等价的条件:(a=c且b=d)或(a=d且b=c)(即骨牌旋转后相同)。 - 要求统计所有满足
i<j的等价骨牌对数量。
关键观察:组合数规律
如果某类等价骨牌有 k 个,那么能组成的数对数量是 组合数 C (k,2) = k(k-1)/2 *(比如有 3 个相同的骨牌,数对是 1+2=3;有 2 个则是 1)。示例 2 中 [1,2] 出现 3 次(注意 [1,2] 和 [2,1] 算同一类),所以 C (3,2)=3,正好是答案。
二、解题思路
- 统一骨牌的表示形式 :把每张骨牌转换成「标准化格式」(比如让小的数在前,大的数在后),这样等价的骨牌会有完全相同的标识。
- 比如
[1,2]和[2,1]都转换成[1,2];[3,4]还是[3,4]。
- 比如
- 统计每类骨牌的数量:用哈希表(或数组,因为骨牌数字 1-9,可压缩)统计每类标准化骨牌的出现次数。
- 计算总对数 :对每类骨牌的数量
k,累加k*(k-1)/2即可。
三、代码实现(详细注释)
cpp
#include <vector>
#include <unordered_map>
using namespace std;
class Solution {
public:
int numEquivDominoPairs(vector<vector<int>>& dominoes) {
// 1. 哈希表:key=标准化后的骨牌标识(用数字拼接,比如[1,2]→12),value=该类骨牌的数量
unordered_map<int, int> countMap;
int result = 0;
// 2. 遍历所有骨牌,标准化+统计数量
for (auto& dom : dominoes) {
// 标准化:小的数在前,大的数在后,拼接成一个两位数(比如[2,1]→12)
int a = dom[0], b = dom[1];
int key = a < b ? (a * 10 + b) : (b * 10 + a);
// 统计该类骨牌的数量(先计数,再累加组合数)
countMap[key]++;
}
// 3. 遍历哈希表,计算每类骨牌的组合数并累加
for (auto& pair : countMap) {
int k = pair.second; // k是该类骨牌的数量
if (k >= 2) { // 至少2个才能组成数对
result += k * (k - 1) / 2;
}
}
return result;
}
};
四、逐行解析 + 示例验证
核心步骤 1:标准化骨牌
骨牌 [a,b] 标准化的目的是让等价骨牌有相同的 key:
- 比如
[1,2]→ 110+2=12;[2,1]→ 110+2=12; - 比如
[3,3]→ 310+3=33;[5,6]→510+6=56。
核心步骤 2:统计数量(示例验证)
示例 1:dominoes = [[1,2],[2,1],[3,4],[5,6]]
- 遍历过程:
[1,2]→ key=12 → countMap[12]=1;[2,1]→ key=12 → countMap[12]=2;[3,4]→ key=34 → countMap[34]=1;[5,6]→ key=56 → countMap[56]=1;
- 计算组合数:
- 12 类:k=2 → 2*1/2=1;
- 34、56 类:k=1 → 无贡献;
- 总结果 = 1(符合示例 1)。
示例 2:dominoes = [[1,2],[1,2],[1,1],[1,2],[2,2]]
- 遍历过程:
[1,2]→ key=12 → countMap[12]=1;[1,2]→ key=12 → countMap[12]=2;[1,1]→ key=11 → countMap[11]=1;[1,2]→ key=12 → countMap[12]=3;[2,2]→ key=22 → countMap[22]=1;
- 计算组合数:
- 12 类:k=3 → 3*2/2=3;
- 11、22 类:k=1 → 无贡献;
- 总结果 = 3(符合示例 2)。
五、优化:用数组替代哈希表(更高效)
因为骨牌数字是 1-9,标准化后的 key 范围是 11(1+1)到 99(9+9),总共 90 种可能(11~99,且十位≤个位),可以用数组替代哈希表,效率更高:
cpp
class Solution {
public:
int numEquivDominoPairs(vector<vector<int>>& dominoes) {
// 数组下标对应标准化后的key(11~99),初始值0
vector<int> count(100, 0);
int result = 0;
for (auto& dom : dominoes) {
int a = dom[0], b = dom[1];
int key = a < b ? (a * 10 + b) : (b * 10 + a);
count[key]++;
}
for (int k : count) {
if (k >= 2) {
result += k * (k - 1) / 2;
}
}
return result;
}
};
六、关键知识点总结
- 标准化思想:把等价的对象转换成统一标识,是解决「等价匹配」问题的核心。
- 组合数公式 :n 个相同元素中选 2 个的组合数是
n*(n-1)/2,这是统计数对的关键(避免双重循环遍历,时间复杂度从 O (n²) 降到 O (n))。 - 数据结构选择 :
- 哈希表:通用,适合范围不确定的场景;
- 数组:适合范围固定且小的场景(本题 key 范围 11~99),效率更高(数组访问是 O (1),哈希表有哈希冲突开销)。
七、时间 / 空间复杂度
- 时间复杂度:O (n),遍历骨牌数组 O (n),遍历统计数组 / 哈希表 O (1)(最多 90 个元素),整体 O (n),满足题目 n≤4*10⁴的要求。
- 空间复杂度:O (1)(数组大小固定为 100,哈希表最多 90 个元素),属于常数级空间。