地址 https://leetcode-cn.com/problems/sparse-similarity-lcci/
两个(具有不同单词的)文档的交集(intersection)中元素的个数除以并集(union)中元素的个数,就是这两个文档的相似度。例如,{1, 5, 3} 和 {1, 7, 2, 3} 的相似度是 0.4,其中,交集的元素有 2 个,并集的元素有 5 个。给定一系列的长篇文档,每个文档元素各不相同,并与一个 ID 相关联。它们的相似度非常“稀疏”,也就是说任选 2 个文档,相似度都很接近 0。请设计一个算法返回每对文档的 ID 及其相似度。只需输出相似度大于 0 的组合。请忽略空文档。为简单起见,可以假定每个文档由一个含有不同整数的数组表示。 输入为一个二维数组 docs,docs[i] 表示 id 为 i 的文档。返回一个数组,其中每个元素是一个字符串,代表每对相似度大于 0 的文档,其格式为 {id1},{id2}: {similarity},其中 id1 为两个文档中较小的 id,similarity 为相似度,精确到小数点后 4 位。以任意顺序返回数组均可。 示例: 输入: [ [14, 15, 100, 9, 3], [32, 1, 9, 3, 5], [15, 29, 2, 6, 8, 7], [7, 10] ] 输出: [ "0,1: 0.2500", "0,2: 0.1000", "2,3: 0.1429" ] 提示: docs.length <= 500 docs[i].length <= 500 相似度大于 0 的文档对数不会超过 1000
解法
我们要使用哈希加速搜索比对的效率
如果能快速的知道 某两个不同文档的交集数目 就比较好做了
如何知道某个单词被那些文档包含呢? 遍历单词元素 依次去查询文档是否包含是最朴素的做法,但是效率太低。又可以考虑哈希加速保存和搜索
两重哈希的做法如下
首先 使用map<int, vector<int>> doc_idx 记录 某个单词被那些文档记录。
然后使用一个稀疏矩阵的数据结构vector<unordered_map<int, int>> graph 记录 某两个文档有多少个相同的单词数目
代码如下
unordered_map<int, vector<int>> doc_idx; for (int i = 0; i < docs.size(); i++) { for (auto& e : docs[i]) { doc_idx[e].push_back(i); } } vector<unordered_map<int, int>> gra(510); //稀疏矩阵图结构 for (auto& e : doc_idx) { vector<int> &p = e.second; for (int i = 0; i < p.size(); i++) { for(int j = i+1;j < p.size();j++){ int a = p[i]; int b = p[j]; gra[a][b]++; gra[b][a]++; } } }
两个结构的意义如下
doc_idx{7,{2,3,5,7}} 表示 7号单词被文档2, 3,5,7 同时拥有
gra{{2,3},5} 表示文档2,3拥有的相同单词数目为5个 也就是该两个文档的交集
那么 文档 2 3 的并集合数目 就是 文档[2]单词数目 + 文档[3]单词数目- 文档23交集数目
全部代码如下
class Solution { public: vector<string> computeSimilarities(vector<vector<int>>& docs) { vector<string> vs; unordered_map<int, vector<int>> doc_idx; for (int i = 0; i < docs.size(); i++) { for (auto& e : docs[i]) { doc_idx[e].push_back(i); } } vector<unordered_map<int, int>> gra(510); //稀疏矩阵图结构 for (auto& e : doc_idx) { vector<int> &p = e.second; for (int i = 0; i < p.size(); i++) { for(int j = i+1;j < p.size();j++){ int a = p[i]; int b = p[j]; gra[a][b]++; gra[b][a]++; } } } for (int i = 0; i < docs.size(); i++) { for (int j = i + 1; j < docs.size(); j++) { if (gra[i][j] != 0) { int num = docs[i].size() + docs[j].size() - gra[i][j]; double res = gra[i][j] * 1.0 / num; char s[20]; sprintf(s, "%d,%d: %.4f", i, j, res + 1e-9); //cout << s << endl; vs.push_back(string(s, s + 20)); } } } return vs; } };
最后要注意的是 由于数据精度问题 最后计算出来的结果要加上1e-9 才和答案一致