Check If String Is Transformable With Substring Sort Operations
HARDDescription
Given two strings s and t, transform string s into string t using the following operation any number of times:
- Choose a non-empty substring in
sand sort it in place so the characters are in ascending order.- For example, applying the operation on the underlined substring in
"14234"results in"12344".
- For example, applying the operation on the underlined substring in
Return true if it is possible to transform s into t. Otherwise, return false.
A substring is a contiguous sequence of characters within a string.
Example 1:
Input: s = "84532", t = "34852" Output: true Explanation: You can transform s into t using the following sort operations: "84532" (from index 2 to 3) -> "84352" "84352" (from index 0 to 2) -> "34852"
Example 2:
Input: s = "34521", t = "23415" Output: true Explanation: You can transform s into t using the following sort operations: "34521" -> "23451" "23451" -> "23415"
Example 3:
Input: s = "12345", t = "12435" Output: false
Constraints:
s.length == t.length1 <= s.length <= 105sandtconsist of only digits.
Approaches
Checkout 2 different approaches to solve Check If String Is Transformable With Substring Sort Operations. Click on different approaches to view the approach and algorithm in detail.
Optimized Greedy with Position Pointers
This approach refines the greedy strategy by optimizing the search for characters and their blockers. Instead of repeatedly scanning the string s, we pre-process it to store the indices of each character. This allows us to find the position of the next available character and check for blocking characters in constant time (per digit), leading to a much faster overall algorithm.
Algorithm
- First, perform an anagram check. If
sandtdon't have the same character counts, returnfalse. - Pre-process
sto store the indices of each digit. An array of queues or lists,positions, is suitable, wherepositions[d]holds a sorted list of indices where digitdappears ins. - Iterate through
tfromi = 0ton-1. Letcbe the charactert[i]. - The greedy strategy dictates we must use the leftmost available occurrence of
cfroms. We can get its index,j, by looking at the front of the queuepositions[c - '0']. - Now, check if this move is valid. For every digit
dthat is smaller thanc(i.e.,d < c - '0'), check if its leftmost available occurrence is at an index smaller thanj. If!positions[d].isEmpty()andpositions[d].peek() < j, it meanscis blocked by a smaller digitd. Returnfalse. - If
cis not blocked by any smaller digit, the move is valid. We consume this occurrence ofcby removing its index from the front of its queue (e.g.,positions[c - '0'].poll()). - If the loop completes successfully for all characters of
t, returntrue.
The core logic remains the same as the naive approach: a character c at index j in s can be moved to the current leftmost position if and only if there are no smaller available characters at indices less than j. The key to optimization is to avoid the O(N) scans in each step.
We can achieve this by pre-computing the locations of all digits. We use an array of queues, say positions, where positions[d] stores all indices of digit d in s in increasing order.
We then iterate through t. For each t[i], we identify the character c = t[i]. The greedy choice is to use the first available c from s, whose index j is at the front of positions[c - '0']. To check if this is a valid move, we must ensure that for any digit d < c - '0', the first available d is not to the left of j. We can quickly check this by peeking at the front of positions[d] for all d < c - '0'. If we find any such blocking character, we return false. Otherwise, the move is valid, and we remove j from its queue (positions[c - '0'].poll()) to mark it as used.
This pre-computation allows all lookups and checks inside the main loop to be very fast, reducing the complexity from quadratic to linear.
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
class Solution {
public boolean isTransformable(String s, String t) {
// Anagram check
int[] counts = new int[10];
for (char c : s.toCharArray()) {
counts[c - '0']++;
}
for (char c : t.toCharArray()) {
counts[c - '0']--;
}
for (int count : counts) {
if (count != 0) {
return false;
}
}
// Pre-process s to store indices of each digit
List<Queue<Integer>> positions = new ArrayList<>();
for (int i = 0; i < 10; i++) {
positions.add(new LinkedList<>());
}
for (int i = 0; i < s.length(); i++) {
positions.get(s.charAt(i) - '0').add(i);
}
// Greedily build t
for (int i = 0; i < t.length(); i++) {
int digit = t.charAt(i) - '0';
int pos = positions.get(digit).peek();
// Check for smaller blocking digits
for (int smallerDigit = 0; smallerDigit < digit; smallerDigit++) {
if (!positions.get(smallerDigit).isEmpty() && positions.get(smallerDigit).peek() < pos) {
return false;
}
}
// Consume the digit
positions.get(digit).poll();
}
return true;
}
}
Complexity Analysis
Pros and Cons
- Highly efficient with a linear time complexity, making it suitable for large inputs.
- It is an optimal solution for this problem.
- Requires more complex data structures (array of lists/queues) compared to the naive approach.
- Uses more space to store all indices from the original string.
Code Solutions
Checking out 3 solutions in different languages for Check If String Is Transformable With Substring Sort Operations. Click on different languages to view the code.
class Solution {
public
boolean isTransformable(String s, String t) {
Deque<Integer>[] pos = new Deque[10];
Arrays.setAll(pos, k->new ArrayDeque<>());
for (int i = 0; i < s.length(); ++i) {
pos[s.charAt(i) - '0'].offer(i);
}
for (int i = 0; i < t.length(); ++i) {
int x = t.charAt(i) - '0';
if (pos[x].isEmpty()) {
return false;
}
for (int j = 0; j < x; ++j) {
if (!pos[j].isEmpty() && pos[j].peek() < pos[x].peek()) {
return false;
}
}
pos[x].poll();
}
return true;
}
}
Video Solution
Watch the video walkthrough for Check If String Is Transformable With Substring Sort Operations
Similar Questions
5 related questions you might find useful
Algorithms:
Patterns:
Data Structures:
Subscribe to Scale Engineer newsletter
Learn about System Design, Software Engineering, and interview experiences every week.
No spam, unsubscribe at any time.