[LeetCode] 839. Similar String Groups 相似字符串組



Two strings `X` and `Y` are similar if we can swap two letters (in different positions) of `X`, so that it equals `Y`.

For example, "tars" and "rats" are similar (swapping at positions 0 and 2), and "rats" and "arts" are similar, but "star" is not similar to "tars""rats", or "arts".

Together, these form two connected groups by similarity: {"tars", "rats", "arts"} and {"star"}.  Notice that "tars" and "arts" are in the same group even though they are not similar.  Formally, each group is such that a word is in the group if and only if it is similar to at least one other word in the group.

We are given a list A of strings.  Every string in A is an anagram of every other string in A.  How many groups are there?

Example 1:

Input: ["tars","rats","arts","star"]
Output: 2

Note:

  1. A.length <= 2000
  2. A[i].length <= 1000
  3. A.length * A[i].length <= 20000
  4. All words in A consist of lowercase letters only.
  5. All words in A have the same length and are anagrams of each other.
  6. The judging time limit has been increased for this question.

這道題定義了字符串之間的一種相似關系,說是對於字符串X和Y,交換X中兩個不同位置上的字符,若可以得到Y的話,就說明X和Y是相似的。現在給了我們一個字符串數組,要將相似的字符串放到一個群組里,這里同一個群組里的字符串不必任意兩個都相似,而是只要能通過某些結點最終連着就行了,有點像連通圖的感覺,將所有連通的結點算作一個群組,問整個數組可以分為多少個群組。由於這道題的本質就是求連通圖求群組個數,既然是圖,考察的就是遍歷啦,就有 DFS 和 BFS 的解法。先來看 DFS 的解法,雖說本質是圖的問題,但並不是真正的圖,沒有鄰接鏈表啥的,這里判斷兩個結點是否相連其實就是判斷是否相似。所以可以寫一個判斷是否相似的子函數,實現起來也非常的簡單,只要按位置對比字符,若不相等則 diff 自增1,若 diff 大於2了直接返回 false,因為只有 diff 正好等於2或者0的時候才相似。題目中說了字符串之間都是異構詞,說明字符的種類個數都一樣,只是順序不同,就不可能出現奇數的 diff,而兩個字符串完全相等時也是滿足要求的,是相似的。下面來進行 DFS 遍歷,用一個 HashSet 來記錄遍歷過的字符串,對於遍歷到的字符串,若已經在 HashSet 中存在了,直接跳過,否則結果 res 自增1,並調用遞歸函數。這里遞歸函數的作用是找出所有相似的字符串,首先還是判斷當前字符串 str 是否訪問過,是的話直接返回,否則加入 HashSet 中。然后再遍歷一遍原字符串數組,每一個遍歷到的字符串 word 都和 str 檢測是否相似,相似的話就對這個 word 調用遞歸函數,這樣就可以找出所有相似的字符串啦,參見代碼如下:
解法一:
class Solution {
public:
    int numSimilarGroups(vector<string>& A) {
        int res = 0, n = A.size();
        unordered_set<string> visited;
        for (string str : A) {
			if (visited.count(str)) continue;
			++res;
			helper(A, str, visited);
		}
		return res;
   	}
   	void helper(vector<string>& A, string& str, unordered_set<string>& visited) {
   		if (visited.count(str)) return;
   		visited.insert(str);
   		for (string word : A) {
   			if (isSimilar(word, str)) {
   				helper(A, word, visited);
   			}
   		}
   	}
   	bool isSimilar(string& str1, string& str2) {
   		for (int i = 0, cnt = 0; i < str1.size(); ++i) {
   			if (str1[i] == str2[i]) continue;
   			if (++cnt > 2) return false;
   		}
   		return true;
   	}
};

我們也可以使用 BFS 遍歷來做,用一個 bool 型數組來標記訪問過的單詞,同時用隊列 queue 來輔助計算。遍歷所有的單詞,假如已經訪問過了,則直接跳過,否則就要標記為 true,然后結果 res 自增1,這里跟上面 DFS 的解法原理一樣,要一次找完和當前結點相連的所有結點,只不過這里用了迭代的 BFS 的寫法。先將當前字符串加入隊列 queue 中,然后進行 while 循環,取出隊首字符串,再遍歷一遍所有字符串,遇到訪問過的就跳過,然后統計每個字符串和隊首字符串之間的不同字符個數,假如最終 diff 為0的話,說明是一樣的,此時不加入隊列,但是要標記這個字符串為 true;若最終 diff 為2,說明是相似的,除了要標記字符串為 true,還要將其加入隊列進行下一輪查找,參見代碼如下:
解法二:
class Solution {
public:
    int numSimilarGroups(vector<string>& A) {
        int res = 0, n = A.size();
        vector<bool> visited(n);
        queue<string> q;
        for (int i = 0; i < n; ++i) {
        	if (visited[i]) continue;
        	visited[i] = true;
        	++res;
        	q.push(A[i]);
        	while (!q.empty()) {
        		string t = q.front(); q.pop();
        		for (int j = 0; j < n; ++j) {
        			if (visited[j]) continue;
        			int diff = 0;
        			for (int k = 0; k < A[j].size(); ++k) {
        				if (t[k] == A[j][k]) continue;
        				if (++diff > 2) break;
        			}
                    if (diff == 0) visited[j] = true;
        			if (diff == 2) {
        				visited[j] = true;
        				q.push(A[j]);
        			}
        		}
        	}
        }
        return res;
    }
};

對於這種群組歸類問題,很適合使用聯合查找 Union Find 來做,LeetCode 中也有其他用到這個思路的題目,比如 [Friend Circles](http://www.cnblogs.com/grandyang/p/6686983.html),[Accounts Merge](http://www.cnblogs.com/grandyang/p/7829169.html),[Redundant Connection II](http://www.cnblogs.com/grandyang/p/8445733.html),[Redundant Connection](http://www.cnblogs.com/grandyang/p/7628977.html),[Number of Islands II](http://www.cnblogs.com/grandyang/p/5190419.html),[Graph Valid Tree](http://www.cnblogs.com/grandyang/p/5257919.html),和 [Number of Connected Components in an Undirected Graph](http://www.cnblogs.com/grandyang/p/5166356.html)。都是要用一個 root 數組,每個點開始初始化為不同的值,如果兩個點屬於相同的組,就將其中一個點的 root 值賦值為另一個點的位置,這樣只要是相同組里的兩點,通過 getRoot 函數得到相同的值。所以這里對於每個結點 A[i],都遍歷前面所有結點 A[j],假如二者不相似,直接跳過;否則將 A[j] 結點的 root 值更新為i,這樣所有相連的結點的 root 值就相同了,一個群組中只有一個結點的 root 值會保留為其的初始值,所以最后只要統計到底還有多少個結點的 root 值還是初始值,就知道有多少個群組了,參見代碼如下:
解法三:
class Solution {
public:
    int numSimilarGroups(vector<string>& A) {
        int res = 0, n = A.size();
        vector<int> root(n);
        for (int i = 0; i < n; ++i) root[i] = i;
        for (int i = 1; i < n; ++i) {
            for (int j = 0; j < i; ++j) {
                if (!isSimilar(A[i], A[j])) continue;
                root[getRoot(root, j)] = i;
            }
        }
        for (int i = 0; i < n; ++i) {
        	if (root[i] == i) ++res;
        }
        return res;
    }
    int getRoot(vector<int>& root, int i) {
        return (root[i] == i) ? i : getRoot(root, root[i]);
    }
    bool isSimilar(string& str1, string& str2) {
   		for (int i = 0, cnt = 0; i < str1.size(); ++i) {
   			if (str1[i] == str2[i]) continue;
   			if (++cnt > 2) return false;
   		}
   		return true;
   	}
};

Github 同步地址:

https://github.com/grandyang/leetcode/issues/839


類似題目:

Friend Circles

Accounts Merge

Redundant Connection II

Redundant Connection

Number of Islands II

Graph Valid Tree

Number of Connected Components in an Undirected Graph


參考資料:

https://leetcode.com/problems/similar-string-groups/

https://leetcode.com/problems/similar-string-groups/discuss/200934/My-Union-Find-Java-Solution

https://leetcode.com/problems/similar-string-groups/discuss/282212/Java-two-solutions-BFS-and-Union-Find

https://leetcode.com/problems/similar-string-groups/discuss/296251/DFS-with-Explanation-(Also-Highlighting-Misleading-Strategy)


[LeetCode All in One 題目講解匯總(持續更新中...)](https://www.cnblogs.com/grandyang/p/4606334.html)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM