The Problem
This problem is described:
Two strings are considered close if you can attain one from the other using the following operations:
Operation 1: Swap any two existing characters.
- For example,
abcde -> aecdb
Operation 2: Transform every occurrence of one existing character into another existing character, and do the same with the other character.
- For example,
aacabb -> bbcbaa (all a's turn into b's, and all b's turn into a's)
You can use the operations on either string as many times as necessary.
Given two strings, word1 and word2, return true if word1 and word2 are close, and false otherwise.
Example 1:
Input: word1 = "abc", word2 = "bca"
Output: true
Explanation: You can attain word2 from word1 in 2 operations.
Apply Operation 1: "abc" -> "acb"
Apply Operation 1: "acb" -> "bca"
Example 2:
Input: word1 = "a", word2 = "aa"
Output: false
Explanation: It is impossible to attain word2 from word1, or vice versa, in any number of operations.
Example 3:
Input: word1 = "cabbba", word2 = "abbccc"
Output: true
Explanation: You can attain word2 from word1 in 3 operations.
Apply Operation 1: "cabbba" -> "caabbb"
Apply Operation 2: "caabbb" -> "baaccc"
Apply Operation 2: "baaccc" -> "abbccc"
Constraints:
Key Insights
This problem does not seem that approachable until coming to the following two key insights:
You can freely reorder the existing letters in a string by swapping any two existing letters. This is done through operation 1 in the problem description. For example, abcde -> aecdb .
You can also freely reassign the frequencies that any letter occurs. This is done by transforming every occurrence of one existing character into another existing character, and doing the same with the other character. This is done with operation 2 in the problem description. For example, aacabb -> bbcbaa , (all a's turn into b's, and all b's turn into a's).
These insights are not easy to come by at first glance. A good way to approach something like this is to play around with the two operations with a few example strings. While doing this, think about questions like:
What is the operation really doing?
Is there a more basic, more abstract way to describe what the operation is doing?
If that doesn't work, luckily for this problem, the two insights are listed in the hints for the problem.
Solution
Since you can freely reorder the strings, and freely reassign the frequencies of the letters to other letters, this means that you just need to find a way to test whether the strings consist of the same letters, and whether the letter frequencies (separate from any particular letters) are the same for both strings.
Here is one way to solve the problem, annotated with explanations along the way:
var closeStrings = function(word1, word2) {
if (word1.length !== word2.length)
return false;
const data1 = getData(word1);
const data2 = getData(word2);
for (let i = 0; i < data1.ltrs.length; i++) {
if(data1.ltrs[i] !== data2.ltrs[i]
|| data1.times[i] !== data2.times[i])
return false;
}
return true;
};
function getData(word) {
const map = {};
for (const char of word) {
if(!map[char]) map[char] = 0;
map[char]++;
}
return {
ltrs: Object.keys(map).sort(),
times: Object.values(map).sort((a, b) => a - b)
}
}
Here is the solution without the comments:
var closeStrings = function(word1, word2) {
if (word1.length !== word2.length)
return false;
const data1 = getData(word1);
const data2 = getData(word2);
for (let i = 0; i < data1.ltrs.length; i++) {
if(data1.ltrs[i] !== data2.ltrs[i]
|| data1.times[i] !== data2.times[i])
return false;
}
return true;
};
function getData(word) {
const map = {};
for (const char of word) {
if(!map[char]) map[char] = 0;
map[char]++;
}
return {
ltrs: Object.keys(map).sort(),
times: Object.values(map).sort((a, b) => a - b)
}
}