【哈希表】A017_LC_面试题:稀疏相似度(暴力求并、较集 / 反向思维)

一、Problem

两个(具有不同单词的)文档的交集(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

二、Solution

方法一:暴力检查

思路

O(n^3) 暴力超时 17/33…

class Solution {
public:
    vector<string> computeSimilarities(vector<vector<int>>& docs) {
    	int n=docs.size();
	 	vector<string> ans;

    	for (int i=0; i<n-1; i++)
		for (int j=i+1; j<n; j++) {
			unordered_set<int> set, mix_set;
			for (int d : docs[i]) set.insert(d);
			unordered_set<int> uni_set(set.begin(), set.end());
			for (int d : docs[j]) {
				if (set.find(d) != set.end())
					mix_set.insert(d);
				uni_set.insert(d);
			}
			if (!mix_set.empty() && !uni_set.empty()) {
				int n=mix_set.size(), m=uni_set.size();
                char str[20];
                double x = (double) n/ m;
                sprintf(str, "%d,%d: %.4f", i, j, x + 1e-9);
                ans.emplace_back(str);
			}
		}	
		return ans;
    }
};

求并集和交集的代码可以优化一下,超时 22/33…

class Solution {
public:
    vector<string> computeSimilarities(vector<vector<int>>& docs) {
    	int n=docs.size();
	 	vector<string> ans;

    	for (int i=0; i<n-1; i++)
		for (int j=i+1; j<n; j++) {
			unordered_set<int> set;
			for (int d : docs[i]) set.insert(d);
			for (int d : docs[j]) set.insert(d);
			
			int uni=set.size(), mix=docs[i].size()+docs[j].size()-uni;
			if (mix>0 && uni>0) {
                char str[20];
                sprintf(str, "%d,%d: %.4f", i, j, (double) mix/uni+1e-9);
                ans.emplace_back(str);
			}
		}	
		return ans;
    }
};

复杂度分析

  • Time O ( n 3 ) O(n^3) O(n3)
  • Space O ( n ) O(n) O(n)

方法二:map

参考别人的思路:对于每一个数组 num,都用 map1 记录它出自 docs 的哪两个子数组中;

  • 如何求交集大小:对于 map1 中的所有 {i1, i2}{i2, i2} 出现的总次数就是 docs[i1] 和 docs[i2] 的交集大小
  • 如何求并集大小:在方法一优化的地方可以知道:并集大小 = 两个子数组的大小 - 它们交集的大小 (uni_sz = size1 + size2 - mix_sz)

525 ms…

class Solution {
    public List<String> computeSimilarities(int[][] docs) {
        Map<Integer, List<Integer>> map1=new HashMap<>();
        Map<List<Integer>, Integer> map2=new HashMap<>();
        
        int n=docs.length;
        for (int i=0; i<n; i++)
        for (int x : docs[i])
            map1.computeIfAbsent(x, v->new ArrayList<>()).add(i);

        for (List<Integer> idxes : map1.values()) {
            for (int i=0; i<idxes.size()-1; i++)
            for (int j=i+1; j<idxes.size(); j++) {
                List<Integer> t = Arrays.asList(idxes.get(i), idxes.get(j));
                map2.put(t, map2.getOrDefault(t,0)+1);
            }
        }

        List<String> ans = new LinkedList<>();
        for (Map.Entry<List<Integer>, Integer> e : map2.entrySet()) {
            List<Integer> pair = e.getKey();
            int i=pair.get(0), j=pair.get(1), mix_sz=e.getValue();
            int uni_sz = docs[i].length + docs[j].length - mix_sz;
            ans.add(i+","+j+": " + String.format("%.4f", 1.0*mix_sz/uni_sz));
        }
        return ans;
    }
}

复杂度分析

  • Time O ( ) O() O()
  • Space O ( ) O() O()

你可能感兴趣的:(#,哈希表)