Notes & software courses · Free to learn
Aph's Blog
On this page

1657. Determine if Two Strings Are Close

👋 อ่านฟรีทั้งหมดบน Aph's Blog — เนื้อหาภาษาไทย ทำตามทีละหน้าใน sidebar ได้เลย หากมีข้อเสนอแนะหรืออยากให้เพิ่มหัวข้อไหน บอกได้เสมอ

Turn the two operations into three checkpoints — same length, same character set, same frequency pattern.

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 (ข้อจำกัด)
  • 1 <= word1.length, word2.length <= 10^5
  • word1 and word2 contain only lowercase English letters.

Understand the problem — what does Close mean?

Close does not mean “roughly similar.” It means you can turn one string into the other using only these two operations (any number of times):

  • Operation 1 — swap any two character positions → order does not matter; only which letters you have
  • Operation 2 — swap frequencies of existing letters A ↔ B as wholes → frequency counts can be remapped among letters that already exist; you cannot invent a new letter

Don’t simulate the swaps — that search blows up factorially. Translate the operations into checkable conditions instead.

Approach — decode into 3 hard rules

From the two operations, extract three checkpoints — all must pass for the strings to be close:

  1. Gate 1 · Same length — len(word1) == len(word2) (swaps / frequency remaps never change length)
  2. Gate 2 · Same character set — set(word1) == set(word2) (op 2 remaps frequencies only among existing letters; no foreign letters)
  3. Gate 3 · Same frequency pattern — sorted(Counter(word1).values()) == sorted(Counter(word2).values()) (op 2 can reassign counts, so compare the sorted bags of numbers, not which letter owns which count)
Common pitfalls

Don’t skip Gate 2. If you only check sorted frequencies, cabbba vs aabbss wrongly returns True even though s never appears in word1 — op 2 cannot invent it.

Walkthrough — word1 = "cabbba", word2 = "abbccc"

Run all three gates on the main example:

  1. Gate 1: len("cabbba") = 6, len("abbccc") = 6 → pass
  2. Gate 2: both sets = {a, b, c} → pass (no foreign letters)
  3. Gate 3: Counter("cabbba") = {c:1, a:2, b:3} · Counter("abbccc") = {a:1, b:2, c:3} → both sorted values = [1, 2, 3] → pass → True

Quick check on the other examples:

word1 / word2Gate 1 lengthGate 2 charsetGate 3 frequencyResult
abc / bcapass 3=3pass {a,b,c}pass [1,1,1]True
a / aafail 1≠2False
cabbba / abbcccpass 6=6pass {a,b,c}pass [1,2,3]True
cabbba / aabbsspass 6=6fail {a,b,c}≠{a,b,s}(freq bag [1,2,3] matches but useless)False

Try it yourself first

The three gates are above — write it yourself, then open the fold below when stuck or ready to compare.

Solution code · folded so you can try firstพับไว้ด้านใน — คลิกเมื่อพร้อมดู

Core: don’t simulate swaps — check three invariants in order, then return.

Python — runnablepython
from collections import Counter

def close_strings(word1, word2):
    # Gate 1: lengths must match
    if len(word1) != len(word2):
        return False
    # Gate 2: same character set (op 2 cannot invent letters)
    if set(word1) != set(word2):
        return False
    # Gate 3: same sorted frequency bag
    count1 = Counter(word1)
    count2 = Counter(word2)
    freq1 = sorted(count1.values())
    freq2 = sorted(count2.values())
    return freq1 == freq2

print(close_strings("abc", "bca"))        # True
print(close_strings("a", "aa"))           # False
print(close_strings("cabbba", "abbccc"))  # True
print(close_strings("cabbba", "aabbss"))  # False
Output
True
False
True
False

What to notice

  • Gate 1 filters short mismatches — different length ends immediately
  • Gate 2 blocks foreign letters (e.g. s for c) — skip it and matching frequency bags still give the wrong True
  • Gate 3 uses sorted(...values()) because op 2 remaps counts; only the bag of numbers must match
  • set(word1) reads clearer than set(Counter) — same key comparison
Time · Space

Time O(n + k log k) count O(n) and sort at most k = 26 · Space O(k) for Counters / sets (fixed 26 letters)

💡 Pattern takeaway

Weird operation problems often reduce to invariants — properties that stay true no matter how many times you apply the ops. Check the invariants instead of simulating. Here: same length · same character set · same frequency bag.