Monday, August 21st
String of substrings
Given a string, determine if the string can be built by a substring appended by multiple copies of itself
A photo shared via Wikipedia, provided under the Creative Commons Attribution-Share Alike 4.0 International license. No changes madeThe basic solution to the problem is basically brute force: create a string of various sizes beginning from the first character, and check if the following characters in the original string are equal to it. There are different ways to accomplish this idea. My first approach, detailed below, was successful, but had a runtime of almost 1200ms and performed poorly, 5th percentile compared to other leetcode solutions. So I tried a few different implementation techniques. The graph below shows the runtime for each solution.
Approach 1
So in my first solution, I iterated over a string length of 1 to string length n/2, created a substring of length i, then deleted it from the beginning of the string
for(int i=1; i<=s.size()/2; i++) {
if(s.size()%i!=0) continue;
string s2=s;
string sub=s2.substr(0,i);
s2.erase(0,i);
...
}
Then created a while loop that repeatedly checks if the next part of the string is equal to the substring. If so, continue erasing the beginning of the string and checking the front of the string against the designated substring
while(sub==s2.substr(0,i)) {
if(s2.length()==sub.length()) return true;
s2.erase(0,i);
}
If the execution doesn't reach the return true state, then return false
for(int i=1; i<=s.size()/2; i++) {
...
}
return false;
The full code of this first approach is below. It ran in 1191 ms
bool repeatedSubstringPattern(string s) {
for(int i=1; i<=s.size()/2; i++) {
if(s.size()%i!=0) continue;
string s2=s;
string sub=s2.substr(0,i);
s2.erase(0,i);
while(sub==s2.substr(0,i)) {
if(s2.length()==sub.length()) return true;
s2.erase(0,i);
}
}
return false;
}
Approach 2
In an attempt to improve solution 1, rather than erase the beginning of a string, I tracked an index to the original string of where the second substring starts. Instead of erasing part of the string then comparing the tested substring to the beginning of the new string, we track the index of the next substring in the original string.
The 2nd implementation below ran in 572 ms, running almost twice as fast as solution 1, but overall was still in the 9th percentile overall.
bool repeatedSubstringPattern(string s) {
for(int i=1; i<=s.size()/2; i++) {
string s2=s;
string sub=s2.substr(0,i);
int j=sub.size();
while(sub==s2.substr(j,i)) {
if(j+i==s2.size()) return true;
else if(j+i>s2.size()) break;
j+=sub.size();
}
}
return false;
}
Approach 3
The third approach implemented the use of indexed substrings in Approach 2, but instead ran the for loop in reverse, starting with s.size()/2 and iterating backward to i>=1. The result was a runtime of 382 seconds, another big improvement, but still only in the 12th percentile.
bool repeatedSubstringPattern(string s) {
for(int i=s.size()/2; i>=1; i--) {
string s2=s;
string sub=s2.substr(0,i);
int j=sub.size();
while(sub==s2.substr(j,i)) {
if(j+i==s2.size()) return true;
else if(j+i>s2.size()) break;
j+=sub.size();
}
}
return false;
}
A possible reason for the speed up is that, while counting down, it encompasses the basic substring when it's concatenated to other substrings. For example, consider the string 'abcabcabcabcabcabc'
Approach 1 or 2 would first test substring a, then substring ab, then substring abc, whereas approach 3 iterating backward would first test substring 'abcabcabc'. Counting down reaches matches faster since multiple substrings can occur in the test string.
Approach 4
To reduce the search space more, we first check is the length of a substring divides the length of the original string with no remainder
if(s.size()%i!=0) continue;
We used the same code as Approach 3, but inserted the above line of code at the beginning of the for loop. This led to a runtime 22ms, more than an order of magnitude faster than Approach 3. 47th percentile overall. Full code below
b
ool repeatedSubstringPattern(string s) {
for(int i=s.size()/2; i>=1; i--) {
if(s.size()%i!=0) continue;
string s2=s;
string sub=s2.substr(0,i);
int j=sub.size();
while(sub==s2.substr(j,i)) {
if(j+i==s2.size()) return true;
else if(j+i>s2.size()) break;
j+=sub.size();
}
}
return false;
}
Conclusion
There are several different ways to implement a basic idea. For this problem, the biggest improvements came from reducing the search space by testing if the length of a possible substring can evenly divide the original string.
Comments
Post a Comment