diff --git a/src/main/java/strings/Bombe.java b/src/main/java/strings/Bombe.java new file mode 100644 index 0000000..a5251c5 --- /dev/null +++ b/src/main/java/strings/Bombe.java @@ -0,0 +1,132 @@ +package strings; +import java.util.HashMap; + +/** + * Author: Alexis Englebert + * + * Context: Oh no, there’s a bomb in your kitchen! + * You must defuse it before it explodes. To do so, you need to solve a puzzle. + * You have a grid with n rows and m columns. Each cell contains a color. + * Each row has two arrows, one pointing left and the other pointing right. + * When you press the right arrow, all the colors in the row are shifted by one to the right. + * When you press the left arrow, they are shifted by one to the left. + * + * The rows wrap around (are cyclic), so if a color moves off one side of a row, + * it re-enters from the other side. + * + * Your goal is to find two rows that are identical after entering a sequence of arrows. + * If you find two identical rows, you have defused the bomb. + * If you can’t find two identical rows, the bomb explodes! + * + * You must return the indices of the two identical rows in increasing order. + * If you don’t find two identical rows, return [-1, -1]. + * + * Time complexity O(n · m) + * + * Exemple: + * n = 4, m = 5 + * RGRBB + * RBBGB + * BBBRB + * GRBBR + * + * The answer is the pair (1, 4) because after one shift to the right on the 4th row, + * they are equals (GRBBR -> RGRBB) + */ + +public class Bombe { + static final long MOD = (1L << 60); + static final long A = 91138; + static long[] hashes; + // store the base at the ith power to avoid recomputing it every time. + static long[] baseExponent; + //BEGIN STRIP + static int _n, _m; + //END STRIP + + /** + * Rotate A given hashed string to the right. + * @param hash The input hashed string + * @param toRemove The character to rotate + * @return The hash of the string rotated to the right. + */ + public static long rotateHash(long hash, char toRemove) { + //BEGIN STRIP + hash -= ( toRemove * baseExponent[_m - 1]) % MOD; + hash %= MOD; + hash *= A; + hash %= MOD; + hash += toRemove; + hash %= MOD; + //END STRIP + return hash; + } + /** + * @param n The number of columns in the grid + * @param m The number of rows in the grid + * @param grid The input grid + * @return A list of 2 elements representing the indices of the two rows, or [-1, -1] + * if no rows are the same. + */ + public static int[] solve(int n, int m, char [][] grid) { + //TODO + baseExponent = new long[m]; + hashes = new long[n]; + baseExponent[0] = 1; + + //BEGIN STRIP + _m = m; + _n = n; + HashMap match = new HashMap<>(); + //END STRIP + for (int i = 1; i < m; i++) { + baseExponent[i] = (baseExponent[i - 1] * A) % MOD; + baseExponent[i] %= MOD; + } + for (int i = 0; i < n; i++) { + long hash = 0; + + for (int j = 0; j < m; j++) { + hash += grid[i][j] * baseExponent[j]; + hash %= MOD; + } + hashes[i] = hash; + } + // STUDENT return -1; + // BEGIN STRIP + for(int i = 0; i < hashes.length; i++) { + match.put(hashes[i], i); + } + + int left = Integer.MAX_VALUE; + int right = Integer.MAX_VALUE; + for (int i = 0; i < n; i++) { + for (int j = 0; j < m; j++) { + hashes[i] = rotateHash(hashes[i], grid[i][m - j - 1]); + if (!match.containsKey(hashes[i])) { + match.put(hashes[i], i); + } + + if(match.get(hashes[i]) != i) { + int a = match.get(hashes[i])+1; + int b = i+1; + int mini = Math.min(a, b); + int maxi = Math.max(a, b); + if(mini <= left) { + left = mini; + right = Math.min(right, maxi); + } + } + } + } + if(left != Integer.MAX_VALUE && right != Integer.MAX_VALUE){ + return new int[]{left, right}; + } + // END STRIP + return new int[]{-1, -1}; + + } + +} + + diff --git a/src/test/java/graphs/ElectricityTest.java b/src/test/java/graphs/ElectricityTest.java index 54ae371..8d79361 100644 --- a/src/test/java/graphs/ElectricityTest.java +++ b/src/test/java/graphs/ElectricityTest.java @@ -78,5 +78,4 @@ public void complexityTest1() { assertEquals(answer, Electricity.minimumSpanningCost(n,edges)); } - } diff --git a/src/test/java/strings/BombeTest.java b/src/test/java/strings/BombeTest.java new file mode 100644 index 0000000..6e53bec --- /dev/null +++ b/src/test/java/strings/BombeTest.java @@ -0,0 +1,248 @@ +package strings; + +import org.javagrader.Grade; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.security.SecureRandom; + +import strings.Bombe.*; + +public class BombeTest { + + + public static String randRow(int n, String letters) { + SecureRandom rnd = new SecureRandom(); + StringBuilder sb = new StringBuilder(n); + for (int i = 0; i < n; i++) { + sb.append(letters.charAt(rnd.nextInt(letters.length()))); + } + return sb.toString(); + } + + public static String rotateString(String s, int k) { + if (s == null || s.isEmpty()) return s; + List list = new ArrayList<>(s.length()); + for (char c : s.toCharArray()) list.add(c); + Collections.rotate(list, -k); + StringBuilder sb = new StringBuilder(list.size()); + for (char c : list) sb.append(c); + return sb.toString(); + } + + @Test + @Grade(value = 1) + public void sampleTest1() { + char[][] bomb = new char[][]{ + "RGRBB".toCharArray(), + "RBBGB".toCharArray(), + "BBBRB".toCharArray(), + "GRBBR".toCharArray() + }; + + assertArrayEquals(new int[]{1, 4}, Bombe.solve(bomb.length, bomb[0].length, bomb)); + } + + @Test + @Grade(value = 1) + public void sampleTest2() { + char[][] bomb = new char[][]{ + "RRRRRR".toCharArray(), + "GGGGGG".toCharArray(), + "BBBBBB".toCharArray(), + "RGRGRG".toCharArray() + }; + + assertArrayEquals(new int[]{-1, -1}, Bombe.solve(bomb.length, bomb[0].length, bomb)); + } + + @Test + @Grade(value = 1) + public void subPatternExists() { + char[][] bomb = new char[][]{ + "RGRGRGRGRGRGRGRG".toCharArray(), + "GRGRGRGRGRGRGRGR".toCharArray(), + }; + assertArrayEquals(new int[]{1, 2}, Bombe.solve(bomb.length, bomb[0].length, bomb)); + } + + @Test + @Grade(value = 1) + public void almostRotation() { + char[][] bomb = new char[][]{ + "RGBRGBRGB".toCharArray(), + "RGBRGBRGG".toCharArray(), + }; + assertArrayEquals(new int[]{-1, -1}, Bombe.solve(bomb.length, bomb[0].length, bomb)); + } + + @Test + @Grade(value = 1) + public void minOrder() { + char[][] bomb = new char[][]{ + "aaa".toCharArray(), + "bbb".toCharArray(), + "aaa".toCharArray(), + "bbb".toCharArray(), + "ccc".toCharArray(), + "ccc".toCharArray() + }; + assertArrayEquals(new int[]{1, 3}, Bombe.solve(bomb.length, bomb[0].length, bomb)); + } + + @Test + @Grade(value = 1) + public void minOrder2() { + char[][] bomb = new char[][]{ + "aaa".toCharArray(), + "aab".toCharArray(), + "aac".toCharArray(), + "aab".toCharArray(), + "aaa".toCharArray(), + "aab".toCharArray() + }; + assertArrayEquals(new int[]{1, 5}, Bombe.solve(bomb.length, bomb[0].length, bomb)); + } + + @Test + @Grade(value = 1) + public void uniqueCharPerRow() { + char[][] bomb = new char[][]{ + "rrrrrrrrrrrr".toCharArray(), + "gggggggggggg".toCharArray(), + "cccccccccccc".toCharArray(), + "aaaaaaaaaaaa".toCharArray(), + "bbbbbbbbbbbb".toCharArray() + }; + + assertArrayEquals(new int[]{-1, -1}, Bombe.solve(bomb.length, bomb[0].length, bomb)); + } + + @Test + @Grade(value = 1, cpuTimeout = 1000, unit = TimeUnit.MILLISECONDS) + public void complexityTestBoom1() { // n*m <= 5*10^5 (on donne une bonne marge) + char[][] bomb = new char[100][5000]; + final String letters = "abcdefghijklmnopqrstuvwxyz"; + + for(int i = 0; i < 100; i++) { + bomb[i] = randRow(5000, letters).toCharArray(); + } + assertArrayEquals(new int[]{-1, -1}, Bombe.solve(bomb.length, bomb[0].length, bomb)); + } + @Test + @Grade(value = 1, cpuTimeout = 1000, unit = TimeUnit.MILLISECONDS) + public void complexityTestBoom2() { // n*m <= 5*10^5 (on donne une bonne marge) + char[][] bomb = new char[5000][100]; + final String letters = "abcdefghijklmnopqrstuvwxyz"; + + for(int i = 0; i < 5000; i++) { + bomb[i] = randRow(100, letters).toCharArray(); + } + assertArrayEquals(new int[]{-1, -1}, Bombe.solve(bomb.length, bomb[0].length, bomb)); + } + + @Test + @Grade(value = 1, cpuTimeout = 1000, unit = TimeUnit.MILLISECONDS) + public void complexityTestBoom3() { + char[][] bomb = new char[50][10000]; + final String letters = "abcdefghijklmnopqrstuvwxyz"; + + for(int i = 0; i < 50; i++) { + bomb[i] = randRow(10000, letters).toCharArray(); + } + assertArrayEquals(new int[]{-1, -1}, Bombe.solve(bomb.length, bomb[0].length, bomb)); + } + + @Test + @Grade(value = 1, cpuTimeout = 1000, unit = TimeUnit.MILLISECONDS) + public void avoidSortOptimization() { + char[][] bomb = new char[50][10000]; + final String letters = "ab"; + + for(int i = 0; i < 50; i++) { + bomb[i] = randRow(10000, letters).toCharArray(); + } + assertArrayEquals(new int[]{-1, -1}, Bombe.solve(bomb.length, bomb[0].length, bomb)); + } + + @Test + @Grade(value = 1, cpuTimeout = 1000, unit = TimeUnit.MILLISECONDS) + public void avoidSortOptimization2() { + char[][] bomb = new char[50][10000]; + final String letters = "abcd"; + + for(int i = 0; i < 50; i++) { + bomb[i] = randRow(10000, letters).toCharArray(); + } + assertArrayEquals(new int[]{-1, -1}, Bombe.solve(bomb.length, bomb[0].length, bomb)); + } + + @Test + @Grade(value = 1, cpuTimeout = 1000, unit = TimeUnit.MILLISECONDS) + public void complexityTestSolution1() { + char[][] bomb = new char[50][10000]; + final String letters = "abcdefghijklmnopqrstuvwxyz"; + + for(int i = 0; i < bomb.length; i++) { + bomb[i] = (randRow(bomb[0].length, letters).toCharArray()); + } + SecureRandom rnd = new SecureRandom(); + int first = rnd.nextInt(bomb.length); + int second = first; + while(second == first) { + second = rnd.nextInt(bomb.length); + } + + bomb[first] = bomb[second]; + String rotated = rotateString(new String(bomb[first]), rnd.nextInt(2*bomb[0].length)); + bomb[first] = rotated.toCharArray(); + assertArrayEquals(new int[]{Math.min(first+1, second+1), Math.max(first+1, second+1)}, Bombe.solve(bomb.length, bomb[0].length, bomb)); + } + @Test + @Grade(value = 1, cpuTimeout = 1000, unit = TimeUnit.MILLISECONDS) + public void complexityTestSolution2() { + char[][] bomb = new char[10000][50]; + final String letters = "abcdefghijklmnopqrstuvwxyz"; + + for(int i = 0; i < bomb.length; i++) { + bomb[i] = (randRow(bomb[0].length, letters).toCharArray()); + } + SecureRandom rnd = new SecureRandom(); + int first = rnd.nextInt(bomb.length); + int second = first; + while(second == first) { + second = rnd.nextInt(bomb.length); + } + + bomb[first] = bomb[second]; + String rotated = rotateString(new String(bomb[first]), rnd.nextInt(2*bomb[0].length)); + bomb[first] = rotated.toCharArray(); + assertArrayEquals(new int[]{Math.min(first+1, second+1), Math.max(first+1, second+1)}, Bombe.solve(bomb.length, bomb[0].length, bomb)); + } + + @Test + @Grade(value = 1, cpuTimeout = 1000, unit = TimeUnit.MILLISECONDS) + public void complexityTestSolution3() { + char[][] bomb = new char[707][707]; + final String letters = "abcdefghijklmnopqrstuvwxyz"; + + for(int i = 0; i < bomb.length; i++) { + bomb[i] = (randRow(bomb[0].length, letters).toCharArray()); + } + SecureRandom rnd = new SecureRandom(); + int first = rnd.nextInt(bomb.length); + int second = first; + while(second == first) { + second = rnd.nextInt(bomb.length); + } + + bomb[first] = bomb[second]; + String rotated = rotateString(new String(bomb[first]), rnd.nextInt(2*bomb[0].length)); + bomb[first] = rotated.toCharArray(); + assertArrayEquals(new int[]{Math.min(first+1, second+1), Math.max(first+1, second+1)}, Bombe.solve(bomb.length, bomb[0].length, bomb)); + } + +}