【leetcode】面试题17.26稀疏相似度 解题笔记及代码

题目:面试题17.26

稀疏相似度

两个(具有不同单词的)文档的交集(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是文档的数量。我们不应该将所有的文档与其他文档进行比较。

如果我们不能将所有文档与其他文档进行比较,那么就需要进一步比较其元素。考虑一个简单的解决方案,看看是否可以将其扩展到多个文档。

我自己电脑上debug时的完整代码

【方便大家测试】

#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;
}

你可能感兴趣的:(leetcode题,算法)