K-Similar Strings solution and explanation
9 more videos on this problem
Leetcode 854(Hard) K-Similar Strings: Simple C++ SolutionShivam Patel · 17:45 · 4K views
LeetCode 854. K-Similar StringsHappy Coding · 31:46 · 2K views
K-Similar Strings | LeetCode 854 | Coding CampCoders Camp · 17:21 · 2K views
Leetcode 854. K-similar Strings Explanation & Code BFSBrittany · 10:04 · 943 views
854. K-Similar StringsMuerde un zombie · 9:49 · 327 views
K-Similar Strings | Leetcode 854 | C++Study with Tanuja · 18:45 · 55 views
No Sound Leetcode 854: K-Similar StringsExtremeProgrammer · 3:13 · 39 views
Leetcode 0854. K-Similar StringsFearless Learner · 7:47 · 30 views
Code Review: (Leetcode) K-similar strings in PythonRoel Van de Paar · 3:10 · 30 views
LeetCode 854: K-Similar StringsAlitaCode · 29:30 · 4 views
The problem
Strings s1 and s2 are k-similar (for some non-negative integer k) if we can swap the positions of two letters in s1 exactly k times so that the resulting string equals s2.
Given two anagrams s1 and s2, return the smallest k for which s1 and s2 are k-similar.
Example 1:
Input: s1 = "ab", s2 = "ba" Output: 1 Explanation: The two string are 1-similar because we can use one swap to change s1 to s2: "ab" --> "ba".
Example 2:
Input: s1 = "abc", s2 = "bca" Output: 2 Explanation: The two strings are 2-similar because we can use two swaps to change s1 to s2: "abc" --> "bac" --> "bca".
Constraints:
1 <= s1.length <= 20s2.length == s1.lengths1ands2contain only lowercase letters from the set{'a', 'b', 'c', 'd', 'e', 'f'}.s2is an anagram ofs1.
Approach overview
Problem Overview: You are given two anagram strings s1 and s2. A swap can exchange any two characters in s1. The goal is to compute the minimum number of swaps required to transform s1 into s2. Since both strings contain the same characters, the challenge is finding the smallest sequence of swaps that fixes mismatched positions.
Approach 1: Breadth-First Search (BFS) State Search (O(n!) time, O(n!) space)
This problem can be modeled as a shortest-path search where each string configuration is a node in a graph. Start from s1 and generate new states by swapping characters. A Breadth-First Search guarantees the first time you reach s2 uses the minimum number of swaps. To reduce branching, locate the first index where the current string differs from s2. Only swap that character with a later index that moves the correct target character into place. Each valid swap produces a new string state that gets pushed into a queue if it hasn't been visited. A visited set prevents revisiting permutations. This pruning drastically reduces the search space compared to exploring all possible swaps.
The key insight is that you should only fix the earliest mismatch instead of trying every swap pair. That greedy restriction turns a factorial search into a manageable BFS traversal. Each level of BFS represents one additional swap, so the level where s2 appears is the answer.
Approach 2: Greedy Depth-First Search (DFS) with Pruning (O(n!) worst-case time, O(n) recursion space)
A backtracking approach using Depth-First Search can also solve the problem efficiently with aggressive pruning. At each step, find the first mismatch between the current string and s2. Try swapping that character with positions later in the string that match the needed target character. After performing a swap, recursively continue fixing the remaining suffix. If the current swap count already exceeds the best solution found so far, prune that branch.
This greedy restriction ensures every swap immediately fixes at least one mismatch, preventing unnecessary permutations. Combined with pruning and early termination, the DFS approach often runs faster in practice because it avoids maintaining large BFS queues. The implementation relies heavily on efficient character swapping and careful backtracking.
Since the input is a string anagram pair, both approaches rely on the observation that optimal swaps always correct mismatched characters rather than introducing new mismatches.
Recommended for interviews: The BFS approach is the most common explanation in interviews because it clearly models the problem as a shortest transformation sequence. It demonstrates strong understanding of state-space search and pruning. The DFS with pruning solution is more optimized and often faster in practice, but BFS tends to be easier to reason about and prove correct during an interview discussion.
Approach 1: Breadth-First Search (BFS) Approach
This approach uses a Breadth-First Search (BFS) strategy to find the shortest sequence of swaps to transform s1 into s2. The BFS strategy helps in systematically exploring all possible strings derived from swapping two characters in s1, while ensuring the shortest path (minimum number of swaps) is found first. We use a queue to keep track of the strings arising from swaps at each level, ensuring only valid and minimal swap sequences are explored.
from collections import deque
def kSimilarity(s1, s2):
def neighbors(s):
for i in range(len(s)):
if s[i] != s2[i]:
break
for j in range(i + 1, len(s)):
if s[j] == s2[i]:
yield s[:i] + s[j] + s[i + 1:j] + s[i] + s[j + 1:]
queue = deque([(s1, 0)])
visited = {s1}
while queue:
s, k = queue.popleft()
if s == s2:
return k
for nei in neighbors(s):
if nei not in visited:
visited.add(nei)
queue.append((nei, k + 1))import java.util.*;
class Solution {
public int kSimilarity(String s1, String s2) {
Queue<String> queue = new LinkedList<>();
Set<String> visited = new HashSet<>();
queue.offer(s1);
visited.add(s1);
int swaps = 0;
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
String current = queue.poll();
if (current.equals(s2)) {
return swaps;
}
for (String next: neighbors(current, s2)) {
if (!visited.contains(next)) {
visited.add(next);
queue.offer(next);
}
}
}
swaps++;
}
return -1;
}
private List<String> neighbors(String s1, String s2) {
List<String> result = new ArrayList<>();
char[] arr = s1.toCharArray();
int i = 0;
while (arr[i] == s2.charAt(i)) i++;
for (int j = i + 1; j < arr.length; j++) {
if (arr[j] == s2.charAt(i) && arr[j] != s2.charAt(j)) {
swap(arr, i, j);
result.add(new String(arr));
swap(arr, i, j); // revert
}
}
return result;
}
private void swap(char[] arr, int i, int j) {
char temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}This Python solution uses a deque from the collections module to facilitate the BFS approach. We start by defining a neighbors function that generates valid strings by swapping characters in s1 to match s2 at each differing position iteratively. We traverse the queue using BFS, updating and expanding our exploration by examining neighbors—strings that can be formed by one swap—of the current state. This guarantees finding the minimum swaps needed when we first encounter s2.
Time Complexity: O(N!), where N is the length of the string due to the factorial nature of permutation swaps.
Space Complexity: O(N!), for tracking the visited states (permutations) and BFS queue size.
Approach 2: Greedy Depth-First Search (DFS) with Pruning
In this approach, we use a Depth-First Search (DFS) strategy augmented with greedy techniques and pruning to find the minimal swaps quickly. By always attempting swaps that immediately match a mismatched character to its correct position in s2, the algorithm reduces unnecessary search paths significantly. Pruning eliminates evidently suboptimal paths early, allowing us to focus computational effort on promising candidates that move closer to the solution.
#include <vector>
#include <string>
#include <algorithm>
class Solution {
public:
int kSimilarity(std::string s1, std::string s2) {
return dfs(s1, s2, 0);
}
private:
int dfs(std::string& s1, std::string& s2, int start) {
if (start == s1.size()) return 0;
if (s1 == s2) return 0;
if (s1[start] == s2[start])
return dfs(s1, s2, start + 1);
int minSwaps = INT_MAX;
for (int i = start + 1; i < s1.size(); ++i) {
if (s1[i] == s2[start] && s1[i] != s2[i]) {
std::swap(s1[start], s1[i]);
minSwaps = std::min(minSwaps, 1 + dfs(s1, s2, start + 1));
std::swap(s1[start], s1[i]); // revert
}
}
return minSwaps;
}
};using System;
using System.Collections.Generic;
public class Solution {
public int KSimilarity(string s1, string s2) {
return DFS(s1.ToCharArray(), s2.ToCharArray(), 0);
}
private int DFS(char[] s1, char[] s2, int start) {
if (start == s1.Length) return 0;
if (new string(s1) == new string(s2)) return 0;
if (s1[start] == s2[start])
return DFS(s1, s2, start + 1);
int minSwaps = int.MaxValue;
for (int i = start + 1; i < s1.Length; i++) {
if (s1[i] == s2[start] && s1[i] != s2[i]) {
Swap(s1, start, i);
minSwaps = Math.Min(minSwaps, 1 + DFS(s1, s2, start + 1));
Swap(s1, start, i); // revert
}
}
return minSwaps;
}
private void Swap(char[] arr, int i, int j) {
char temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}The C++ solution uses a recursive DFS enhanced by a greedy selection: when swapping characters, it prioritizes those that immediately resolve mismatches. The pruning occurs due to the minimum swaps tracking, which ensures only the most promising swaps are made (only those that better align s1 to s2).
Time Complexity: O(N!), where N is the length of the string as swaps might explore all possible permutations.
Space Complexity: O(N), due to the recursion depth used to maintain call stacks.
Approach 3: Default Approach
func kSimilarity(s1 string, s2 string) int {
next := func(s string) []string {
i := 0
res := []string{}
for ; s[i] == s2[i]; i++ {
}
for j := i + 1; j < len(s1); j++ {
if s[j] == s2[i] && s[j] != s2[j] {
res = append(res, s[:i]+string(s[j])+s[i+1:j]+string(s[i])+s[j+1:])
}
}
return res
}
q := []string{s1}
vis := map[string]bool{s1: true}
ans := 0
for {
for i := len(q); i > 0; i-- {
s := q[0]
q = q[1:]
if s == s2 {
return ans
}
for _, nxt := range next(s) {
if !vis[nxt] {
vis[nxt] = true
q = append(q, nxt)
}
}
}
ans++
}
}Complexity comparison
| Approach | Complexity |
|---|---|
| Breadth-First Search (BFS) Approach | Time Complexity: O(N!), where N is the length of the string due to the factorial nature of permutation swaps. |
| Greedy Depth-First Search (DFS) with Pruning | Time Complexity: O(N!), where N is the length of the string as swaps might explore all possible permutations. |
| Default Approach | – |
Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Breadth-First Search (BFS) | O(n!) worst case | O(n!) | When you want the clearest shortest-path formulation and guaranteed minimal swaps |
| Greedy DFS with Pruning | O(n!) worst case | O(n) | When optimizing search using pruning and minimizing memory usage |
Frequently asked questions
Is K-Similar Strings easy or hard?
K-Similar Strings is classified as a Hard problem because it requires modeling the task as a graph search over permutations and applying pruning to keep the state space manageable. Understanding why only certain swaps should be explored is the key insight that makes the problem tractable.
How to solve K-Similar Strings in O(n)?
An O(n) solution is not possible because the problem requires exploring multiple swap combinations to guarantee the minimum transformation sequence. The standard approach uses BFS or DFS with pruning over string states. These algorithms explore permutations but reduce the search space by only fixing mismatched positions.
What is the best approach for K-Similar Strings?
The most common solution uses Breadth-First Search (BFS) on string states. Each state represents a string configuration, and swaps generate neighboring states. By always fixing the first mismatched index and only swapping with characters that match the target, the branching factor stays small. BFS guarantees the minimum number of swaps because it explores transformations level by level.
What data structure is used in K-Similar Strings?
The BFS solution relies on a queue to process string states level by level and a hash set to track visited permutations. These structures prevent repeated exploration and ensure the algorithm finds the shortest swap sequence. The DFS approach instead uses recursion with pruning conditions.
What is the time complexity of K-Similar Strings?
The worst-case time complexity is O(n!) because every permutation of the string could theoretically be explored. In practice, pruning strategies significantly reduce the search space. Most optimized implementations behave closer to O(n^3)–O(n^4) for typical constraints due to fixing mismatches greedily.
K-Similar Strings Python or Java solution approach?
Both Python and Java implementations typically use BFS with a queue and a visited set. At each step, find the first mismatched index and generate new strings by swapping with characters that match the target string at that position. This keeps the number of generated states manageable.
Is K-Similar Strings asked at Google, Amazon, or Meta?
K-Similar Strings is considered a hard interview problem and appears in advanced algorithm rounds at companies like Google and Meta. It tests knowledge of BFS state search, pruning techniques, and reasoning about permutations. Candidates are often expected to explain the mismatch-based pruning optimization.
