Repeated String Transformation
A character-to-character mapping defines a transformation from a character to a character . For example, denotes that the character should be transformed to the character . Note that transformations are directed: is different from .
A transformation can be applied to a string, which results in a new string where all s are replaced
with s. For example, applying the transformation to hello world
results in cello world
.
Given a set of transformations, repeatedly apply them to a string until it is no longer possible to apply any more transformations.
For example, if we have two transformations , the output for hello world
should be bello world
:
the first h
becomes c
which then turns into a b
.
It is guaranteed that the set of transformations will not contain a cycle, direct or indirect (i.e. ).
Input Specification
- The first line will contain an integer , denoting the number of transformations.
- The next lines will each contain two lowercase characters and , denoting a character-to-character mapping from to .
- The final line will contain a string , which is guaranteed to only contain lowercase characters.
Output Specification
Output the result of repeatedly applying the set of transformations to .
Examples
Example 1
Input
5
a b
b c
c d
d e
e f
abcdefg
Output
ffffffg
Explanation
The set of transformations looks like a chain, as follows:
It is clear that all of the characters along the chain will be transformed to f
.
Thus, a
, b
, c
, d
, e
, and f
all become f
. The final g
is left as is.
Example 2
Input
2
h f
f k
hiss
Output
kiss
Explanation
The turns into a which then turns into a .
Example 3
Input
2
c a
b c
b
Output
a
Example 4
Input
2
a c
b a
bbbb
Output
cccc
Model Solutions
Note: Weak test data caused several unintended solutions (which should not have passed) to get through. (Those who solved the problem at our meeting will get points regardless of the correctness of their solution this week.) We apologize for the confusion!
Solution 1: Brute Force
Intuition
Just do as the problem states: apply each transformation repeatedly in a loop, until none of the transformations have any effect.
Code
t = int(input())
transformations = []
for _ in range(t):
a, b = input().split()
transformations.append((a, b))
s = input()
while True:
changed = False
for a, b in transformations:
replaced = s.replace(a, b)
if s != replaced:
changed = True
s = replaced
if not changed:
break
print(s)
Implementation Details
changed
keeps track of whether any transformation has resulted in the string changing in the current iteration. To replace the characters, we use str.replace
.
Example
2
h f
f k
hiss
- First iteration.
- Apply the first transformation, , resulting in
fiss
. - Apply the second transformation, , resulting in
kiss
. - As the string changed, the loop continues.
- Apply the first transformation, , resulting in
- Second iteration.
- Apply the first transformation, . No effect.
- Apply the second transformation, . No effect.
- As the string did not change, break the loop.
- Output
kiss
.
Time Complexity
About where is the alphabet. Each iteration of the while
loop attempts to apply all transformations on the string of length . Thus the time complexity of each iteration is .
Moreover, the number of iterations is bounded by the size of the alphabet , given that there are no cycles (indirect or direct) in the set of transformations.
Solution 2: Hash Tables
Intuition
The key insight here is to apply transformations character-by-character rather than on complete strings. That is, to obtain the transformed string for , go through each character of and find the character that it resolves to rather than transforming all of at once.
We can do this with a dictionary that maps a character to what character it resolves to. For example, given the following input:
5
a b
b c
c d
d e
e f
abcdefg
We would have the following dictionary:
{
"a": "b",
"b": "c",
"c": "d",
"d": "e",
"e": "f",
}
To look up what a character resolves to, it suffices to look it up in the dictionary repeatedly as long as there is an entry for it. For example, to resolve the character , we perform the following steps:
- Check if there is an entry for .
- Yes; so set the current character to and continue.
- Check if there is an entry for .
- Yes; so set the current character to and continue.
- Check if there is an entry for .
- Yes; so set the current character to and continue.
- Check if there is an entry for .
- Yes; so set the current character to and continue.
- Check if there is an entry for .
- Yes; so set the current character to and continue.
- Check if there is an entry for .
- No; so stop here.
This eventually resolves to , which indicates that should become which is correct.
Code
resolves_to = {}
t = int(input())
for _ in range(t):
a, b = input().split()
resolves_to[a] = b
s = input()
output = []
for c in s:
while c in resolves_to:
c = resolves_to[c]
output.append(c)
print("".join(output))
Time Complexity
. Consider the following loop:
while c in resolves_to:
c = resolves_to[c]
First, observe that as there are no loops, changes each iteration.
is the alphabet, there are possible characters, so resolves_to
can have at max keys.
In other words, there are only possible values for , and as 's value changes each iteration,
the number of iterations is bounded by .
As there are iterations where work is being done in each, the overall time complexity is .