两个(具有不同单词的)文档的交集(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”
]
【我是看了大佬的题解才知道的解题方法,下面是解题的关键点】
(1)先遍历所有文档的所有单词,mp1[word]返回数组,表示包含有单词word的文档id
生成mp1需要O(DW)复杂度,D表示文档数量,W表示每个文档的单词数(粗略认为W相等)。
(2)mp2的键值为一对数字(id1,id2),其中id1,id2为文档id,遍历mp1[word],(id1,id2)出现一次,mp2(id1,id2)++
(3)相似度(id1,id2) = mp2[id1][id2] / (docs[id1].size() + docs[id2].size() - mp2[id1][id2])
(4)C++注意精度误差问题,最后输出时需要加上1e-9
【我忘了一些基础的知识点,所以注释上了一些我不熟悉的东西】
class Solution {
public:
vector<string> computeSimilarities(vector<vector<int>>& docs) {
//先遍历所有文档的所有单词,mp1[word]返回数组,表示包含有单词word的文档id
unordered_map<int, vector<int>> mp1;
//map 容器中存储的数据是有序的,而 unordered_map 容器中是无序的。
//为了实现快速查找,map内部本身就是按序存储的(比如红黑树)。在我们插入键值对时,就会按照key的大小顺序进行存储,map默认是按key值从小到大排序的
for (int i = 0; i < docs.size(); ++i) {
for (auto& word : docs[i]) {
mp1[word].push_back(i);
}
}
map<pair<int, int>, int> mp2;
for (auto& item : mp1) {//遍历mp1
auto& ids = item.second;//mp1[word]返回数组,表示包含有单词word的文档id
for (int i = 0; i + 1 < ids.size(); ++i) {
for (int j = i + 1; j < ids.size(); ++j)
{
//id1 为两个文档中较小的 id,因为是顺序加入mp1,所以能够保证ids[i]
mp2[make_pair(ids[i], ids[j])]++;
}
}
}
vector<string> result;
char temp[30];
for (auto& item : mp2) {
int id1 = item.first.first;
int id2 = item.first.second;
double similary = (double)item.second / (docs[id1].size() + docs[id2].size() - item.second);
sprintf(temp, "%d,%d: %0.4lf", id1, id2, similary + 1e-9);//C++注意精度误差问题,最后输出时需要加上1e-9
result.push_back(temp);
//cout << temp << endl; // debug
}
return result;
}
};
【我觉得我用的不是力扣提示的两种方法之一,可能这两种方法比我用的效率高很多吧。但是我自己还想不出来程序具体怎么写】
解法一:
从一个简单的算法开始,将每个文档依次与其他文档进行比较。你如何尽快计算两个文档的相似度?要计算两个文档的相似性,可以尝试用某种方式重新组织数据。排序?使用其他的数据结构?
交集和并集之间是什么关系?你能用一个计算出另一个吗?要理解两个集合的交集和并集的关系,考虑用Venn图(一个圆与另一个圆重叠的图)。
解法二:
小的优化——例如,在每个数组中跟踪最小和最大元素。然后,在特定情况下,你可以快速计算出两个数组是否不重叠。这样做(以及其他类似的优化)的问题是,仍然需要将所有文档与其他文档进行比较。它没有利用相似度是“稀疏”的这一事实。考虑到我们有很多文档,真的不需要将所有文档与其他文档进行比较(即使比较运算速度很快)。所有这类解复杂度都是O(D²),其中D是文档的数量。我们不应该将所有的文档与其他文档进行比较。如果我们不能将所有文档与其他文档进行比较,那么就需要进一步比较其元素。考虑一个简单的解决方案,看看是否可以将其扩展到多个文档。
【方便大家测试】
#include
#include
#include
#include
#pragma warning( disable : 4996 ) //for sprintf;
using namespace std;
class Solution {
public:
vector<string> computeSimilarities(vector<vector<int>>& docs) {
//先遍历所有文档的所有单词,mp1[word]返回数组,表示包含有单词word的文档id
unordered_map<int, vector<int>> mp1;
//map 容器中存储的数据是有序的,而 unordered_map 容器中是无序的。
//为了实现快速查找,map内部本身就是按序存储的(比如红黑树)。在我们插入键值对时,就会按照key的大小顺序进行存储,map默认是按key值从小到大排序的
for (int i = 0; i < docs.size(); ++i) {
for (auto& word : docs[i]) {
mp1[word].push_back(i);
}
}
map<pair<int, int>, int> mp2;
//unorderedmap默认只能用基本类型,string和智能指针作为key,除非你提供hash函数。
for (auto& item : mp1) {//遍历mp1
auto& ids = item.second;//mp1[word]返回数组,表示包含有单词word的文档id
for (int i = 0; i + 1 < ids.size(); ++i) {
for (int j = i + 1; j < ids.size(); ++j)
{
//id1 为两个文档中较小的 id,因为是顺序加入mp1,所以能够保证ids[i]
mp2[make_pair(ids[i], ids[j])]++;
}
}
}
vector<string> result;
char temp[30];
for (auto& item : mp2) {
int id1 = item.first.first;
int id2 = item.first.second;
double similary = (double)item.second / (docs[id1].size() + docs[id2].size() - item.second);
sprintf(temp, "%d,%d: %0.4lf", id1, id2, similary + 1e-9);//C++注意精度误差问题,最后输出时需要加上1e-9
result.push_back(temp);
cout << temp << endl; // debug
}
return result;
}
};
int main()
{
vector<vector<int>> docs;
vector<int> docs_1;
docs_1.push_back(14);
docs_1.push_back(15);
docs_1.push_back(100);
docs_1.push_back(9);
docs_1.push_back(3);
docs.push_back(docs_1);
vector<int> docs_2;
docs_2.push_back(32);
docs_2.push_back(1);
docs_2.push_back(9);
docs_2.push_back(3);
docs_2.push_back(5);
docs.push_back(docs_2);
vector<int> docs_3;
docs_3.push_back(15);
docs_3.push_back(29);
docs_3.push_back(2);
docs_3.push_back(6);
docs_3.push_back(8);
docs_3.push_back(7);
docs.push_back(docs_3);
vector<int> docs_4;
docs_4.push_back(7);
docs_4.push_back(10);
docs.push_back(docs_4);
//返回一个数组,其中每个元素是一个字符串,代表每对相似度大于 0 的文档,其格式为 {id1},{id2}: {similarity}
Solution a;
vector<string> result=a.computeSimilarities(docs);
return 0;
}