哈希表与二分查找优化二元组统计问题
1. 题目背景与核心需求
这道来自蚂蚁集团2026年春招的编程题,考察的是对二元组数据的处理能力。题目描述如下:
给定两个整数数组nums1和nums2,统计满足以下条件的二元组(i,j)的数量:
- nums1[i] == nums2[j]
- i < j
这类问题在实际开发中非常常见,比如在电商系统中匹配用户行为日志、在推荐系统中对齐用户画像特征等。理解这类问题的解法,对处理真实业务中的数据集对齐场景很有帮助。
2. 暴力解法与复杂度分析
最直观的解法是双层循环遍历:
def count_pairs(nums1, nums2): count = 0 for i in range(len(nums1)): for j in range(len(nums2)): if nums1[i] == nums2[j] and i < j: count += 1 return count时间复杂度:O(n²)
空间复杂度:O(1)
虽然这种解法在小数据量时可行,但在面试中显然不够理想。蚂蚁的面试题通常期望候选人能给出更优解。
3. 哈希表优化解法
我们可以利用哈希表来优化时间复杂度:
from collections import defaultdict def count_pairs(nums1, nums2): num_map = defaultdict(list) for idx, num in enumerate(nums2): num_map[num].append(idx) count = 0 for i, num in enumerate(nums1): if num in num_map: # 找到nums2中所有等于nums1[i]且位置大于i的j for j in num_map[num]: if i < j: count += 1 return count时间复杂度:O(n+m),其中n和m分别是两个数组的长度
空间复杂度:O(m),用于存储nums2的索引
4. 进一步优化:二分查找
当nums2中存在大量重复元素时,可以进一步优化:
import bisect def count_pairs(nums1, nums2): num_map = defaultdict(list) for idx, num in enumerate(nums2): num_map[num].append(idx) count = 0 for i, num in enumerate(nums1): if num in num_map: # 使用二分查找找到第一个大于i的位置 indices = num_map[num] pos = bisect.bisect_right(indices, i) count += len(indices) - pos return count时间复杂度:O(n log m)
空间复杂度:O(m)
5. 多语言实现对比
5.1 Java实现
import java.util.*; public class Solution { public int countPairs(int[] nums1, int[] nums2) { Map<Integer, List<Integer>> numMap = new HashMap<>(); for (int j = 0; j < nums2.length; j++) { numMap.computeIfAbsent(nums2[j], k -> new ArrayList<>()).add(j); } int count = 0; for (int i = 0; i < nums1.length; i++) { if (numMap.containsKey(nums1[i])) { List<Integer> indices = numMap.get(nums1[i]); int pos = Collections.binarySearch(indices, i); if (pos < 0) pos = -pos - 1; count += indices.size() - pos; } } return count; } }5.2 C++实现
#include <vector> #include <unordered_map> #include <algorithm> using namespace std; int countPairs(vector<int>& nums1, vector<int>& nums2) { unordered_map<int, vector<int>> numMap; for (int j = 0; j < nums2.size(); ++j) { numMap[nums2[j]].push_back(j); } int count = 0; for (int i = 0; i < nums1.size(); ++i) { if (numMap.find(nums1[i]) != numMap.end()) { auto& indices = numMap[nums1[i]]; auto it = upper_bound(indices.begin(), indices.end(), i); count += distance(it, indices.end()); } } return count; }5.3 Python实现(最终优化版)
from collections import defaultdict import bisect def count_pairs(nums1, nums2): num_map = defaultdict(list) for idx, num in enumerate(nums2): num_map[num].append(idx) count = 0 for i, num in enumerate(nums1): if num in num_map: indices = num_map[num] pos = bisect.bisect_right(indices, i) count += len(indices) - pos return count6. 测试用例设计
全面的测试用例应该包括:
test_cases = [ # 基础测试 ([1,2,3], [1,2,3], 3), ([1,1,1], [1,1,1], 6), # 边界测试 ([], [], 0), ([1], [2], 0), # 特殊测试 ([1,2,3], [3,2,1], 1), ([1,2,2], [2,2,2], 4), # 大数据测试 (list(range(10000)), list(range(10000)), 49995000) ] for nums1, nums2, expected in test_cases: assert count_pairs(nums1, nums2) == expected7. 性能优化思考
在实际业务场景中,还可以考虑以下优化方向:
- 如果数组已经排序,可以使用双指针法进一步优化
- 对于特别大的数据集,可以考虑分块处理或使用布隆过滤器
- 在多线程环境下,可以将哈希表的构建和查询过程并行化
8. 常见面试问题
面试官可能会追问:
- 如何处理数据量特别大的情况?
- 如果nums2不能全部放入内存怎么办?
- 如何将这个算法扩展到分布式环境?
9. 实际业务应用
这类算法在以下场景中有广泛应用:
- 用户行为分析:匹配不同时间段的用户行为
- 推荐系统:对齐用户和物品的特征
- 数据清洗:找出不同数据源中的匹配项
10. 编码规范建议
在面试中写代码时要注意:
- 先明确输入输出和边界条件
- 写注释说明算法思路
- 变量命名要有意义
- 处理可能的异常情况
11. 复杂度分析进阶
对于更复杂的情况,可以考虑:
- 预处理时间复杂度
- 查询时间复杂度
- 空间复杂度的权衡
- 数据分布对算法性能的影响
12. 算法选择策略
选择算法时需要考虑:
- 数据规模
- 数据分布特征
- 内存限制
- 是否需要支持动态更新
13. 在线测试技巧
在在线编程平台测试时:
- 先通过所有示例测试用例
- 添加自己的边界测试
- 测试大数据量下的表现
- 检查内存使用情况
14. 面试评分标准
这类题目通常会考察:
- 基础编码能力
- 算法优化思路
- 代码规范性
- 问题分析能力
- 沟通表达能力
15. 学习资源推荐
想深入掌握这类问题可以学习:
- 《算法导论》中的哈希表相关章节
- LeetCode上的Two Sum系列题目
- 系统设计中的数据处理模式
- 分布式算法中的数据分片技术
16. 个人实战建议
根据我的面试经验:
- 先写出暴力解法,再逐步优化
- 主动分析时间/空间复杂度
- 讨论可能的优化方向
- 考虑实际业务中的约束条件
- 保持代码整洁和可读性