Question link: https://leetcode.com/problems/repeated-substring-pattern/
class Solution { long magic_num = 31; long mod_num = 1000000000 + 7; public boolean repeatedSubstringPattern(String s) { // only lowercase // for - pick one substring // for - repeat and check if (s.isEmpty()) return false; // get target hash long target_hash = hash(s); // initailization Long now_hash = null; int i = 0; int str_size = s.length(); while (true) { if (i >= str_size) break; int substring_size = i + 1; // calculate hash if (now_hash == null) { now_hash = hash(s.substring(0, 1)); now_hash = mod(now_hash, mod_num); }else { // ab -> ab_ move left now_hash *= magic_num; now_hash = mod(now_hash, mod_num); // ab_ + c now_hash += s.charAt(i); now_hash = mod(now_hash, mod_num); } System.out.println("i: " + i); if (str_size % substring_size != 0 // must be a factor || str_size == substring_size) // must append at least once { i++; continue; } // assemble as a whole string for compare int chunk_num = str_size/substring_size; long now_assembled_hash = now_hash; for (int k = 1; k < chunk_num; k++) { // start from k = 1 // abc -> abc___ move left for (int m = 0; m < substring_size; m++) { now_assembled_hash *= magic_num; now_assembled_hash = mod(now_assembled_hash, mod_num); // be very careful about overflow } // abc___ + abc -> abcabc now_assembled_hash = mod(now_assembled_hash, mod_num); now_assembled_hash += now_hash; now_assembled_hash = mod(now_assembled_hash, mod_num); } // check hash (belive in god, believe in your hash function) System.out.println(chunk_num + "-" + now_assembled_hash + ":" + target_hash); if (now_assembled_hash == target_hash) { return true; } i++; } return false; } private long hash(String s) { long result = 0; for (int i = 0; i < s.length(); i++) { result *= magic_num; result = mod(result, mod_num); result += s.charAt(i); result = mod(result, mod_num); } return result; } private long mod(long num, long mod){ return (num % mod + mod) % mod; } }
pick substring and calculate rolloing hash:
hash_val = 0
"a" = hash_val * magic_num + "a"
"ab" = hash_val * magic_num + "b"
"abc" = hash_val * magic_num + "c"
...
check if current substring is a factor of original string length
if (str_size % substring_size != 0)
assemble current substring hash into a whole-string hash
compare hash value
if (now_assembled_hash == target_hash) {
return true;
}
In the implementation, we leverage hash to avoid compare each substring one by one.
As a result, our time complexity is:
n * (chunk_num * (substring_size))
furthermore, when substring_size goes up, chunk_num goes down.
Therefore, roughly speaking, our time complexity will lean toward to original string length
n * str_size
At least, this is my thought. Let's discuss!
note: in this implementation, please be very careful about overflow while calculating hash value. really an evil in the detail.