Leetcode 73 数位和相等数对的最大和 | 等价多米诺骨牌对的数量

1 题目

2342. 数位和相等数对的最大和

给你一个下标从 0 开始的数组 nums ,数组中的元素都是 整数。请你选出两个下标 iji != j),且 nums[i] 的数位和 与 nums[j] 的数位和相等。

请你找出所有满足条件的下标 ij ,找出并返回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 <= 105
  • 1 <= 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),为后续可能的数对留更大的数。

四、边界情况分析

  1. 数组长度 < 2:直接返回 - 1(代码天然支持,因为无法组成数对)。
  2. 多个数对但和更小:比如 nums=[1,2,10,11],数字和分别是 1、2、1、2:
    • 1 和 10 的和 = 11,2 和 11 的和 = 13,res 最终是 13。
  3. 所有数的数字和都唯一:返回 - 1(初始值)。

五、总结

这道题的核心思路是:

  1. 分组:按数字各位和分组(哈希表);
  2. 择优:每组内保留最大数字,遇到同组数字时计算和并更新最大值;
  3. 结果:最终最大和即为答案,无则返回 - 1。

3 题目

1128. 等价多米诺骨牌对的数量

给你一组多米诺骨牌 dominoes

形式上,dominoes[i] = [a, b]dominoes[j] = [c, d] 等价 当且仅当 (a == cb == d) 或者 (a == db == 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 * 104
  • dominoes[i].length == 2
  • 1 <= 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. 统一骨牌的表示形式 :把每张骨牌转换成「标准化格式」(比如让小的数在前,大的数在后),这样等价的骨牌会有完全相同的标识。
    • 比如 [1,2][2,1] 都转换成 [1,2][3,4] 还是 [3,4]
  2. 统计每类骨牌的数量:用哈希表(或数组,因为骨牌数字 1-9,可压缩)统计每类标准化骨牌的出现次数。
  3. 计算总对数 :对每类骨牌的数量 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;
    }
};

六、关键知识点总结

  1. 标准化思想:把等价的对象转换成统一标识,是解决「等价匹配」问题的核心。
  2. 组合数公式 :n 个相同元素中选 2 个的组合数是 n*(n-1)/2,这是统计数对的关键(避免双重循环遍历,时间复杂度从 O (n²) 降到 O (n))。
  3. 数据结构选择
    • 哈希表:通用,适合范围不确定的场景;
    • 数组:适合范围固定且小的场景(本题 key 范围 11~99),效率更高(数组访问是 O (1),哈希表有哈希冲突开销)。

七、时间 / 空间复杂度

  • 时间复杂度:O (n),遍历骨牌数组 O (n),遍历统计数组 / 哈希表 O (1)(最多 90 个元素),整体 O (n),满足题目 n≤4*10⁴的要求。
  • 空间复杂度:O (1)(数组大小固定为 100,哈希表最多 90 个元素),属于常数级空间。
相关推荐
风筝在晴天搁浅3 分钟前
代码随想录 516.最长回文子序列
算法
灰灰勇闯IT19 分钟前
RN路由与状态管理:打造多页面应用
开发语言·学习·rn路由状态
菜鸟233号22 分钟前
力扣513 找树左下角的值 java实现
java·数据结构·算法·leetcode
亭上秋和景清23 分钟前
指针进阶:函数指针详解
开发语言·c++·算法
leoufung25 分钟前
LeetCode 22:Generate Parentheses 题解(DFS / 回溯)
算法·leetcode·深度优先
9ilk26 分钟前
【C++】--- C++11
开发语言·c++·笔记·后端
FMRbpm44 分钟前
队列练习--------最近的请求次数(LeetCode 933)
数据结构·c++·leetcode·新手入门
码农12138号1 小时前
服务端请求伪造-SSRF 学习笔记
笔记·web安全·网络安全·ctf·ssrf·服务端请求伪造
断剑zou天涯1 小时前
【算法笔记】bfprt算法
java·笔记·算法
中屹指纹浏览器1 小时前
指纹浏览器抗检测进阶:绕过深度风控的技术实践
服务器·网络·经验分享·笔记·媒体