Google Interview Question
Software DevelopersCountry: United States
Interview Type: Phone Interview
I think that the output should also contain [bc, za] as it corresponds to ROT_2(ZA) = BC
public class RotFinder {
String alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
Set<Pair<String>> findRotationPairs(List<String> input) {
Set<Pair<String>> pairs = new HashSet<>();
for (int i = 0 ; i < input.size() ; i++) {
for (int j = i + 1 ; j < input.size() ; j++) {
String source = input.get(i);
String target = input.get(j);
if (source.length() < 1) {
break;
}
if (source.length() == target.length()) {
char sourceLetter= source.charAt(0);
char targetLetter = target.charAt(0);
int rot = findRot(sourceLetter, targetLetter);
boolean recordPair = false;
for (int k = 1 ; k < source.length() ; k++) {
sourceLetter = source.charAt(k);
targetLetter = target.charAt(k);
int sourceIndex = alpha.indexOf(sourceLetter);
int targetIndex = (sourceIndex + rot) % 26;
if (targetLetter == alpha.charAt(targetIndex)) {
if (k == source.length() - 1) {
recordPair = true;
}
} else {
break;
}
}
if (recordPair) {
pairs.add(new Pair<>(source, target));
}
}
}
}
return pairs;
}
int findRot(char source, char target) {
for (int i = 0 ; i < 26 ; i++) {
char checked = alpha.charAt((alpha.indexOf(source) + i) % 26);
if (checked == target) {
return i;
}
}
return -1;
}
class Pair<T> {
T first;
T second;
Pair(T first, T second) {
this.first = first;
this.second = second;
}
public String toString() {
return "[" + first + ", " + second + "] ";
}
}
public static void main (String[] args) {
List<String> strings = new LinkedList<>();
strings.add("AB");
strings.add("BC");
strings.add("FOO");
strings.add("ZA");
strings.add("BAZ");
strings.add("IRR");
System.out.println(Arrays.toString(new RotFinder().findRotationPairs(strings).toArray()));
}
}
You don't need to find rotation n all. Just find index of both character
Say cik and cjk obtained at index i and j of kth item.
Index of the is i1 and i2.
If i2 < i1 then
Return 26-(i1-i2)
Otherwise
Return i1-i2
Maybe something like this?
def source_and_rotation(words: List[str]) -> List[List[str]]:
def rot_char_by(c, i):
shift = ord('A')
return chr((((ord(c) - shift) + i) % N) + shift)
N = len(string.ascii_lowercase)
transformed_to_origins = defaultdict(set)
for w in words:
for i in range(1, N):
transformed_to_origins["".join([rot_char_by(c, i) for c in w])].add(w)
res = []
in_result = set()
for w in words:
if w in transformed_to_origins:
for t in transformed_to_origins[w]:
if t != w and (w, t) not in in_result:
res.append([w, t])
in_result.add((w, t)), in_result.add((t, w))
return res
def source_and_rotation(words: List[str]) -> List[List[str]]:
def rot_char_by(c, i):
shift = ord('A')
return chr((((ord(c) - shift) + i) % N) + shift)
N = len(string.ascii_lowercase)
transformed_to_origins = defaultdict(set)
for w in words:
for i in range(1, N):
transformed_to_origins["".join([rot_char_by(c, i) for c in w])].add(w)
res = []
in_result = set()
for w in words:
if w in transformed_to_origins:
for t in transformed_to_origins[w]:
if t != w and (w, t) not in in_result:
res.append([w, t])
in_result.add((w, t)), in_result.add((t, w))
return res
Here is an idea - construct a prefix tree, where each node is character, child of a node exists if and only if there is a word in original collection that has these characters going one after another. E.g. for collection ['bar', 'baz', 'cat'], the tree would look like:
Tree:
[z]
/
[b] -> [a]
\
[r]
[c] -> [a] -> [t]
Now for each word in the collection, and for every rotation you can descend in the tree. If path exists - the rotation exists too. Note - it is also important to mark last symbol of a string as "terminal" in the tree, so that algorithm would not match "AB", "BCZ" as ["AB", BC'] -> C is not terminal.
Here is the full implementation in JS:
var alphabet = Array.from('ABCDEFGHIJKLMNOPQRSTUVWXYZ');
var charIndex = makeCharIndex(alphabet);
console.log(getRotationPairs(['AB', 'BC', 'BCD', 'FOO', 'ZA', 'BAZ']));
function getRotationPairs(wordCollection) {
var prefixTree = makePrefixTree(wordCollection);
var rotationPairs = [];
wordCollection.forEach(word => {
var foundPairs = getRotationPairsForWord(word, prefixTree);
rotationPairs = rotationPairs.concat(foundPairs)
});
return rotationPairs;
}
function getRotationPairsForWord(word, prefixTree) {
var foundPairs = [];
for (var rotationNumber = 1; rotationNumber < alphabet.length; ++rotationNumber) {
var foundRotation = findRotationInTree(rotationNumber, word, prefixTree);
if (foundRotation) {
foundPairs.push([word, foundRotation]);
}
}
return foundPairs;
}
function findRotationInTree(rotationNumber, word, prefixTree) {
var currentNode = prefixTree;
var rotatedWord = '';
for (var i = 0; i < word.length; ++i) {
var char = rotate(word[i], rotationNumber);
if (!currentNode[char]) return;
rotatedWord += char;
currentNode = currentNode[char];
}
if (currentNode.isTerminal) return rotatedWord;
}
function rotate(char, offset) {
return alphabet[(charIndex[char] + offset) % alphabet.length];
}
function makePrefixTree(wordCollection) {
var tree = Object.create(null);
wordCollection.forEach(addWordToTree);
return tree;
function addWordToTree(word) {
var currentNode = tree;
for (var i = 0; i < word.length; ++i) {
var char = word[i];
var nextNode = currentNode[char];
if (!nextNode) {
nextNode = currentNode[char] = Object.create(null);
}
currentNode = nextNode;
}
if (word.length > 0) {
// So that we can check if word truly ends here.
currentNode.isTerminal = true;
}
}
}
function makeCharIndex(alphabet) {
var charIndex = {};
alphabet.forEach((char, index) => charIndex[char] = index);
return charIndex;
}
Tree is constructed in O(m) time, where `m` is total number of characters in your collection. The lookup for rotation is also linear function of characters count, so the overall performance is `O(m)`, `m` - total number of characters, and a constant factor of rotation counts (length of your alphabet).
public static void rotTrans(String [] list){
HashMap<String,ArrayList<String>> map = new HashMap<String,ArrayList<String>>();
String key;
int len = 0,i = 0, j = 0;
for(String s: list){
key = keyGen(s);
if(map.containsKey(key)){
map.get(key).add(s);
}else{
map.put(key, new ArrayList<String>());
map.get(key).add(s);
}
}
for(String k : map.keySet())
len += map.get(k).size() - 1;
String [][] result = new String [len][2];
for(Map.Entry<String,ArrayList<String>> entry : map.entrySet()){
if(entry.getValue().size() > 1){
for(j = 1; j < entry.getValue().size(); j++){
result[i][0] = entry.getValue().get(0);
result[i][1] = entry.getValue().get(j);
i++;
}
}
}
System.out.println(Arrays.deepToString(result));
}
public static String keyGen(String str){
if(str.length() <= 1) return "1";
StringBuilder sb = new StringBuilder();
char ch1,ch2;
ch1 = str.charAt(0);
sb.append(1);
for(int i = 1; i < str.length(); i++){
ch2 = str.charAt(i);
if(ch2 >= ch1){
sb.append((ch2 - ch1) + 1);
}else{
sb.append((ch1 - 'Z' + 1) + (('A' - ch2) + 1));
}
ch1 = ch2;
}
return sb.toString();
}
const normalize = string => string.split('').map(x => x.charCodeAt(0));
const transform = arr => arr.map(val => ((val - arr[0]) + 26) % 26);
const normalizeString = string => transform( normalize(string)).join('');
const generateBuckets = strings => {
const buckets = { };
const values = strings.forEach(string => {
const normal = normalizeString(string);
if (buckets[normal] === undefined){ buckets[normal] = [ ] };
buckets[normal].push(string);
});
return buckets;
};
I assume, to each word, we can apply any number of ROT_1 or any number of ROT_25.
#include <unordered_map>
#include <vector>
#include <string>
#include <iostream>
using namespace std;
pair<string, string> GetPatterns(const string& s)
{
string p1, p2;
for (int i = 0; i + 1 < s.size(); ++i)
{
int delta1 = (static_cast<int>(s[i + 1]) - s[i]);
if (delta1 < 0)
{
delta1 += 26;
}
int delta2 = (static_cast<int>(s[i]) - s[i + 1]);
if (delta2 < 0)
{
delta2 += 26;
}
p1 += to_string( delta1) + ",";
p2 += to_string(-delta2) + ",";
}
return pair<string, string>(p1, p2);
};
vector<string> RotationDups(const vector<string>& words)
{
vector<string> out;
unordered_map<string, int> patterns;
for (auto w : words)
{
pair<string, string> p = GetPatterns(w);
++patterns[p.first];
++patterns[p.second];
}
for (auto w : words)
{
pair<string, string> p = GetPatterns(w);
if (patterns[p.first] > 1 ||
patterns[p.second] > 1)
{
out.push_back(w);
}
}
return out;
}
int main()
{
vector<string> out = RotationDups({"AB", "BC", "FOO", "ZA", "BAZ"});
for (auto w : out)
{
cout << w << ", ";
}
cout << "\n";
return 0;
}
Here is some code;
Bruteforce and optimised;
package Java;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Author: Nitin Gupta(nitin.gupta@walmart.com)
* Date: 16/04/19
* Description:
*/
public class StringRotationMatch {
static class Pair {
String s1, s2;
@Override
public String toString() {
return "{" + s1 + "," + s2 + '}';
}
public Pair(String s1, String s2) {
this.s1 = s1;
this.s2 = s2;
}
}
public static void main(String args[]) {
String[] arr1 = {"AB", "BC", "FOO", "ZA", "BAZ"};
System.out.println(rotationPairsBruteForce(arr1));
System.out.println(rotationPairs(arr1));
String[] arr2 = {"AB", "BC", "FOO", "ZA", "BAZ", "CBA"};
System.out.println(rotationPairsBruteForce(arr2));
System.out.println(rotationPairs(arr2));
}
//O(Ln^2)
private static List<Pair> rotationPairsBruteForce(String arr[]) {
String str = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";
List<Pair> out = new ArrayList<>();
for (int i = 0; i < arr.length; i++) { //O(n)
for (int j = i + 1; j < arr.length; j++) { //O(n)
if (i == j)
continue;
String s1 = arr[i];
String s2 = arr[j];
if (s1.length() != s2.length())
continue;
char c1 = s1.charAt(0);
char c2 = s2.charAt(0);
int diff = getDiff(c1, c2, str);
int k;
for (k = 1; k < s1.length(); k++) { //O(L)
if (diff != getDiff(s1.charAt(k), s2.charAt(k), str))
break;
}
if (k == s1.length()) {
out.add(new Pair(s1, s2));
}
}
}
return out;
}
private static int getDiff(char c1, char c2, String str) { //O(1)
int index1 = str.indexOf(c1);
int index2 = str.indexOf(c2);
if (index2 < index1)
return 26 - (index1 - index2);
return index2 - index1;
}
//Optimized-> O(nL)
private static List<Pair> rotationPairs(String[] arr) {
List<Pair> out = new ArrayList<>();
List<String> transformed = transform(arr); //O(nL)
Map<String, List<Integer>> duplicates = new HashMap<>();
int i = 0;
//This loop will run at most O(n) time since in inner loop even all of them map to one entry only, then each element will touch at most 2 times
for (String s : transformed) { //O(n)
List<Integer> entries = duplicates.getOrDefault(s, new ArrayList<>());
for (Integer entry : entries) {
out.add(new Pair(arr[entry], arr[i]));
}
entries.add(i);
duplicates.put(s, entries);
i++;
}
return out;
}
//O(nL)
private static List<String> transform(String ar[]) {
List<String> transformed = new ArrayList<>();
for (int i = 0; i < ar.length; i++) { //O(n)
String x = ar[i];
int delta = 'A' - x.charAt(0);
StringBuilder str = new StringBuilder();
for (int j = 0; j < x.length(); j++) { //O(L)
char current = x.charAt(j);
current = (char) ((int) current + delta);
if (current < 'A')
current = (char) ((int) current + 26);
str.append(current);
}
transformed.add(str.toString());
}
return transformed;
}
}
Rotate every string to start from 'a' and map keys to value as list of strings. if list has more than one string than add that to resulting set.
public List<List<String>> similarStrings(List<String> stringList){
if(stringList.size() == 0)return null;
List<List<String>> sameStrings = new ArrayList<>();
Map<String, List<String>> map = new HashMap<>();
for(String str : stringList){
if(str.length() > 0){
String rotatedString = rotateString(str);
map.putIfAbsent(rotatedString, new ArrayList<String>());
map.get(rotatedString).add(str);
}
}
for(Map.Entry<String, List<String>> e : map.entrySet()){
if(e.getValue().size() > 1){
sameStrings.add(e.getValue());
}
}
return sameStrings;
}
private String rotateString(String str){
if(str.length() == 0)return str;
StringBuilder sb = new StringBuilder();
str = str.toLowerCase();
char lastChar = 'a';
sb.append('a');
for(int i = 1; i < str.length();i++){
char curr = str.charAt(i);
char prev = str.charAt(i - 1);
char rotatedChar = 0;
if(curr > prev){
int delta = curr - prev;
rotatedChar = (char) (lastChar + delta);
}else if(curr == prev){
rotatedChar = lastChar;
}else if(curr < prev){
int delta = prev - curr;
if(lastChar - delta >= 'a'){
rotatedChar = (char) (lastChar - delta);
}else{
delta = 'a' - delta;
rotatedChar = (char) (('z' - delta) + 1);
}
}
sb.append(rotatedChar);
lastChar = rotatedChar;
}
return sb.toString();
}
You can rotate all strings in the input list such that they all begin with the character "A". Afterwards, it's a simple matter of printing all pairs of duplicate strings (mapped to their original string).
This has a runtime of O(n).
- Anonymous May 04, 2018