├── Rakefile ├── .gitignore ├── Gemfile ├── lib ├── junit-4.12.jar └── hamcrest-core-1.3.jar ├── install-java8.sh ├── test.sh ├── src ├── DirectedNode.java ├── LinkedListStack.java ├── HashTable.java ├── LinkedListMap.java ├── SingleLinkNode.java ├── GrowingArray.java ├── BinaryTreeNode.java ├── DoubleLinkNode.java ├── LinkedList.java ├── Debugger.java ├── Chapter18.java ├── Chapter2.java ├── Chapter4.java ├── Chapter1.java ├── Chapter17.java ├── Chapter10.java ├── Chapter3.java └── Chapter11.java ├── test ├── Chapter10Test.java ├── HashTableTest.java ├── SingleLinkNodeTest.java ├── ChapterTestBase.java ├── Chapter18Test.java ├── Chapter11Test.java ├── RunJunit.java ├── Chapter2Test.java ├── Chapter1Test.java ├── Chapter17Test.java ├── Chapter3Test.java ├── Chapter9Test.java └── Chapter4Test.java ├── Guardfile ├── compile.sh ├── study_guide.md └── README.md /Rakefile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | classes 2 | Gemfile.lock 3 | *.swp 4 | *.save 5 | plan.md 6 | *~ 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | gem 'guard' 3 | gem 'rake' 4 | gem 'guard-test' 5 | -------------------------------------------------------------------------------- /lib/junit-4.12.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcel-valdez/coding_problems/HEAD/lib/junit-4.12.jar -------------------------------------------------------------------------------- /lib/hamcrest-core-1.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcel-valdez/coding_problems/HEAD/lib/hamcrest-core-1.3.jar -------------------------------------------------------------------------------- /install-java8.sh: -------------------------------------------------------------------------------- 1 | sudo add-apt-repository ppa:webupd8team/java 2 | sudo apt-get update 3 | sudo apt-get install oracle-java8-installer 4 | 5 | sudo update-java-alternatives -s java-8-oracle -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | OUTPUT_DIR=$1 2 | CLASSPATH=$2 3 | TEST_NAME=$3 4 | 5 | # compile all source files, appends a : to the end 6 | JAR_CLASSPATH=$(find "$CLASSPATH" -iregex .*\.jar | tr '\n' ' ' | sed 's/[ ][ ]*/:/g') 7 | # run test 8 | if [ $? -eq 0 ]; then 9 | java -classpath "$JAR_CLASSPATH$OUTPUT_DIR" RunJunit "$OUTPUT_DIR" $TEST_NAME 10 | fi 11 | 12 | exit $? 13 | -------------------------------------------------------------------------------- /src/DirectedNode.java: -------------------------------------------------------------------------------- 1 | package structures; 2 | 3 | import java.util.*; 4 | 5 | public class DirectedNode { 6 | private final T value; 7 | private List links; 8 | 9 | public DirectedNode(T value) { 10 | this.value = value; 11 | this.links = new ArrayList(); 12 | } 13 | 14 | public T value() { return this.value; } 15 | 16 | public Iterable links() { return this.links; } 17 | public void linkTo(DirectedNode node) { this.links.add(node); } 18 | } 19 | -------------------------------------------------------------------------------- /test/Chapter10Test.java: -------------------------------------------------------------------------------- 1 | package chapter10; 2 | 3 | import static debug.Debugger.DEBUG; 4 | import static org.hamcrest.Matcher.*; 5 | import static org.hamcrest.MatcherAssert.*; 6 | import static org.hamcrest.core.Is.*; 7 | import static org.hamcrest.core.IsEqual.*; 8 | import static org.hamcrest.core.IsNull.*; 9 | 10 | 11 | import java.lang.*; 12 | import java.util.*; 13 | import java.util.stream.*; 14 | 15 | import org.hamcrest.core.*; 16 | import org.junit.*; 17 | import org.junit.rules.*; 18 | import structures.*; 19 | import test.*; 20 | 21 | public class Chapter10Test extends ChapterTestBase { 22 | 23 | @Test 24 | public void testFindWords() { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | @dir_path = File.dirname(__FILE__) 2 | @output_dir = "#{@dir_path}/classes/" 3 | @src_dir = "#{@dir_path}/src/" 4 | @test_dir = "#{@dir_path}/test/" 5 | @lib_dir = "#{@dir_path}/lib/" 6 | @compile_cmd = "#{@dir_path}/compile.sh #{@src_dir} #{@test_dir} #{@output_dir} #{@lib_dir}" 7 | @test_cmd = "#{@dir_path}/test.sh #{@output_dir} #{@lib_dir}" 8 | 9 | 10 | guard :test do 11 | watch(%r|^src/(.*).java$|) do |match| 12 | executeTest("#{match[1]}Test") 13 | end 14 | 15 | 16 | watch(%r|^test/(.*Test).java$|) do |match| 17 | executeTest(match[1]) 18 | end 19 | 20 | watch(%r|^test/(RunJunit).java$|) do |match| 21 | executeTest(match[1]) 22 | end 23 | 24 | def executeTest(testName) 25 | puts `#{@compile_cmd}` 26 | if $?.success? 27 | puts `#{@test_cmd} #{testName}` 28 | end 29 | end 30 | end 31 | 32 | -------------------------------------------------------------------------------- /compile.sh: -------------------------------------------------------------------------------- 1 | SRC_DIR=$1 2 | TEST_DIR=$2 3 | OUTPUT_DIR=$3 4 | CLASSPATH=$4 5 | 6 | __clean() { 7 | if [ -d "$OUTPUT_DIR" ]; then 8 | working_dir=$(pwd) 9 | cd "$OUTPUT_DIR" 10 | rm -fr * 11 | cd "$working_dir" 12 | fi 13 | } 14 | 15 | __compile() { 16 | if [ ! -d "$1" ]; then 17 | mkdir "$1" 18 | fi 19 | 20 | javac -classpath "$JAR_CLASSPATH$OUTPUT_DIR" -d "$OUTPUT_DIR" $(find "$1" -iregex .*\.java) 21 | } 22 | 23 | # create classes directory if it does not exist 24 | if [ ! -e "$OUTPUT_DIR" ]; then 25 | mkdir "$OUTPUT_DIR" 26 | fi 27 | 28 | # gather libraries, appends a : to the end 29 | JAR_CLASSPATH=$(find "$CLASSPATH" -iregex .*\.jar | tr '\n' ' ' | sed 's/[ ][ ]*/:/g') 30 | # compile all source files 31 | if [ $? -eq 0 ]; then 32 | __clean $OUTPUT_DIR 33 | # compile all test files 34 | __compile $SRC_DIR 35 | if [ $? -eq 0 ]; then 36 | __compile $TEST_DIR 37 | else 38 | exit 1 39 | fi 40 | else 41 | exit 1 42 | fi 43 | 44 | exit $? 45 | -------------------------------------------------------------------------------- /study_guide.md: -------------------------------------------------------------------------------- 1 | Study plan sorted by order of cost-effectiveness 2 | ------------------------------------------------ 3 | 4 | ### MVP 5 | - [ ] Chapter 1: Arrays and Strings 6 | - [ ] Chapter 2: Linked Lists 7 | - [ ] Chapter 3: Stacks and Queues 8 | - [ ] Chapter 4: Trees and Graphs 9 | - [ ] Chapter 9: Recursion and Dynamic Programming 10 | - [ ] Chapter 11: Sorting and Searching 11 | - [ ] Chapter 17: Moderate 12 | 13 | ### HIGH VALUE 14 | - [ ] Chapter 10: Scalability and Memory Limits 15 | - [ ] Chapter 18: Hard 16 | - [ ] Chapter 5: Bit Manipulation 17 | - [ ] Chapter 12: Testing 18 | - 19 | ### RELATED TO THINGS RARELY ASKED 20 | - [ ] Chapter 7: Mathematics and Probability 21 | - [ ] Chapter 16: Threads and Locks 22 | - [ ] Chapter 15: Databases 23 | - [ ] Chapter 13: C and C++ 24 | - 25 | ### USELESS AND BEYOND 26 | - [ ] Chapter 13: Java 27 | - Too basic to be of worth. 28 | - [ ] Chapter 6: Brain Teasers 29 | - Nobody does brain teasers anymore. 30 | - [ ] Chapter 8: Object-oriented design 31 | - The reason this was useless in my case, is that the information it has is very generic. 32 | -------------------------------------------------------------------------------- /src/LinkedListStack.java: -------------------------------------------------------------------------------- 1 | package structures; 2 | 3 | public class LinkedListStack { 4 | private SingleLinkNode head; 5 | private volatile int size; 6 | 7 | public LinkedListStack() { 8 | size = 0; 9 | head = null; 10 | } 11 | 12 | public void push(T value) { 13 | if(this.head == null) { 14 | this.head = new SingleLinkNode<>(value); 15 | } else { 16 | SingleLinkNode node = new SingleLinkNode<>(value); 17 | node.next(head); 18 | this.head = node; 19 | } 20 | 21 | this.size++; 22 | } 23 | 24 | public T pop() { 25 | if(this.head == null) { 26 | throw new IllegalStateException("Stack is empty, cannot pop"); 27 | } 28 | 29 | T value = this.peek(); 30 | this.head = this.head.next(); 31 | this.size--; 32 | return value; 33 | } 34 | 35 | public T peek() { 36 | return this.head.value(); 37 | } 38 | 39 | public int size() { 40 | return this.size; 41 | } 42 | 43 | public boolean isEmpty() { 44 | return this.size() == 0; 45 | } 46 | 47 | public String toString() { 48 | if(this.head == null) { return ""; } 49 | else { return this.head.toString(); } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/HashTable.java: -------------------------------------------------------------------------------- 1 | package structures; 2 | 3 | public class HashTable { 4 | /** 5 | * TODO: Use a binary search tree for collisions instead of a linked list 6 | */ 7 | private LinkedListMap[] elements; 8 | private int collisionCount; 9 | 10 | public HashTable() { 11 | this(5); 12 | } 13 | 14 | public HashTable(int initialSize) { 15 | this.elements = new LinkedListMap[initialSize]; 16 | this.collisionCount = 0; 17 | } 18 | 19 | 20 | public int collisionCount() { 21 | return this.collisionCount; 22 | } 23 | 24 | public void put(Object key, Object value) { 25 | if(key == null) { 26 | throw new RuntimeException("The key should never be null"); 27 | } 28 | 29 | if(elements[hash(key)] == null) { 30 | elements[hash(key)] = new LinkedListMap(); 31 | } 32 | 33 | LinkedListMap bucket = elements[hash(key)]; 34 | bucket.add(key, value); 35 | if(bucket.size() > 1) { 36 | collisionCount++; 37 | } 38 | } 39 | 40 | public Object get(Object key) { 41 | if(key == null) { 42 | throw new RuntimeException("The key should never be null"); 43 | } 44 | 45 | return elements[hash(key)].get(key); 46 | } 47 | 48 | private int hash(Object key) { 49 | int hash = key.hashCode() & 0x7fffffff; 50 | return hash % elements.length; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/LinkedListMap.java: -------------------------------------------------------------------------------- 1 | package structures; 2 | /** 3 | * TODO: Write a test class for the linked list structure 4 | */ 5 | public class LinkedListMap { 6 | 7 | private Node head = null; 8 | private int size = 0; 9 | 10 | public void add(Object key, Object value) { 11 | if(key == null) { 12 | throw new RuntimeException("Key should never be null"); 13 | } 14 | 15 | Node node = new Node(key, value); 16 | if(head == null) { 17 | head = node; 18 | } else { 19 | head.next(node); 20 | } 21 | 22 | this.size++; 23 | } 24 | 25 | public Object get(Object key) { 26 | if(key == null) { 27 | return null; 28 | } 29 | 30 | Node current = head; 31 | while(current != null) { 32 | if(current.key.equals(key)) { 33 | return current.value; 34 | } 35 | current = head.next(); 36 | } 37 | 38 | return null; 39 | } 40 | 41 | public int size() { 42 | return size; 43 | } 44 | 45 | public static class Node { 46 | final Object key; 47 | final Object value; 48 | private Node next; 49 | 50 | public Node(Object key, Object value) { 51 | this.key = key; 52 | this.value = value; 53 | } 54 | 55 | public void next(Node next) { 56 | this.next = next; 57 | } 58 | 59 | public Node next() { 60 | return this.next; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/SingleLinkNode.java: -------------------------------------------------------------------------------- 1 | package structures; 2 | 3 | public class SingleLinkNode { 4 | 5 | private final T value; 6 | private SingleLinkNode next; 7 | 8 | public SingleLinkNode(T value) { 9 | this.value = value; 10 | } 11 | 12 | public void next(SingleLinkNode node) { 13 | this.next = node; 14 | } 15 | 16 | public SingleLinkNode next() { 17 | return this.next; 18 | } 19 | 20 | public T value() { 21 | return this.value; 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | StringBuilder sb = new StringBuilder(); 27 | SingleLinkNode node = this; 28 | while(node != null) { 29 | if(node.value == null) { 30 | sb.append(""); 31 | } else { 32 | sb.append(node.value.toString()); 33 | } 34 | 35 | if(node.next != null) { 36 | sb.append("->"); 37 | } 38 | 39 | node = node.next; 40 | } 41 | 42 | return sb.toString(); 43 | } 44 | 45 | public int hashCode() { 46 | int hashCode = 0; 47 | if(this.value != null) { 48 | hashCode += 31 * this.value.hashCode(); 49 | } 50 | 51 | if(this.next() != null) { 52 | hashCode += 17 * this.next().hashCode(); 53 | } 54 | 55 | return hashCode; 56 | } 57 | 58 | @Override 59 | public boolean equals(Object other) { 60 | if(!(other instanceof SingleLinkNode)) { 61 | return false; 62 | } 63 | SingleLinkNode otherNode = (SingleLinkNode)other; 64 | if(this.value == null) { 65 | if(otherNode.value != null) { 66 | return false; 67 | } 68 | } else if(!this.value.equals(otherNode.value)) { 69 | return false; 70 | } 71 | 72 | if(this.next == null) { 73 | return otherNode.next == null; 74 | } 75 | 76 | return this.next.equals(otherNode.next); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /test/HashTableTest.java: -------------------------------------------------------------------------------- 1 | package structures; 2 | 3 | import org.junit.*; 4 | import java.lang.*; 5 | 6 | public class HashTableTest { 7 | private HashTable target; 8 | 9 | @Test 10 | public void testConstructor() { 11 | new HashTable(); 12 | new HashTable(10); 13 | } 14 | 15 | @Test 16 | public void testPutSingle() { 17 | //given 18 | HashTable target = new HashTable(); 19 | String value = "theValue"; 20 | Object key = new Object(); 21 | //when 22 | target.put(key, value); 23 | //then 24 | Assert.assertEquals(value, target.get(key)); 25 | } 26 | 27 | @Test 28 | public void testPutMultiple() { 29 | //given 30 | this.target = new HashTable(); 31 | String[] values = { "1", "2", "3", "4", "5" }; 32 | Object[] keys = { 33 | Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4), 34 | Integer.valueOf(5) 35 | }; 36 | //when 37 | checkHashTable(values, keys); 38 | } 39 | 40 | @Test 41 | public void testPutMultipleWithCollisions() { 42 | //given 43 | this.target = new HashTable(); 44 | String[] values = { "1", "2", "3", "4", "5" }; 45 | Object[] keys = { 46 | "asldkfj", "qoweir", "x.c,vb", "9218347", "/.,123e" 47 | }; 48 | //when 49 | checkHashTable(values, keys); 50 | } 51 | 52 | @Test 53 | public void testPutMultipleWithCustomSize() { 54 | //given 55 | this.target = new HashTable(100); 56 | String[] values = { "1", "2", "3", "4", "5" }; 57 | Object[] keys = { 58 | "asldkfj", "qoweir", "x.c,vb", "9218347", "/.,123e" 59 | }; 60 | //then 61 | checkHashTable(values, keys); 62 | Assert.assertEquals( 63 | "If the hash table is of size 100, there should be no collisions with 5 entries", 64 | 0, target.collisionCount()); 65 | } 66 | 67 | private void checkHashTable(Object[] values, Object[] keys) { 68 | //when 69 | for(int i = 0; i < keys.length; i++) { 70 | target.put(keys[i], values[i]); 71 | } 72 | //then 73 | for(int i = 0; i < keys.length; i++) { 74 | Assert.assertEquals(values[i], target.get(keys[i])); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /test/SingleLinkNodeTest.java: -------------------------------------------------------------------------------- 1 | package structures; 2 | 3 | import static org.hamcrest.Matcher.*; 4 | import static org.hamcrest.MatcherAssert.*; 5 | import static org.hamcrest.core.Is.*; 6 | import static org.hamcrest.core.IsEqual.*; 7 | 8 | import java.lang.*; 9 | import java.util.*; 10 | import java.util.stream.*; 11 | 12 | import org.hamcrest.core.*; 13 | import org.junit.*; 14 | import org.junit.rules.*; 15 | 16 | public class SingleLinkNodeTest { 17 | @Test 18 | public void testEqualsAreDifferent() { 19 | checkAreDifferent(node("A"), node("B")); 20 | checkAreDifferent(node("A"), node(null)); 21 | checkAreDifferent(node(null), node("A")); 22 | checkAreDifferent(node(null), node("A")); 23 | } 24 | 25 | @Test 26 | public void testEqualsAreDifferentLists() { 27 | checkAreDifferent(node("A"), list("A", "B")); 28 | checkAreDifferent(list("A", null), list("A", "B")); 29 | checkAreDifferent(list("A", "C"), list("A", "B")); 30 | checkAreDifferent(list("A", "C"), list("B", "C")); 31 | } 32 | 33 | private static void checkAreDifferent(SingleLinkNode node, SingleLinkNode other) { 34 | // when 35 | boolean actual = node.equals(other); 36 | 37 | // then 38 | Assert.assertFalse(actual); 39 | } 40 | 41 | @Test 42 | public void testEqualsAreEqual() { 43 | checkAreEqual(node("A"), node("A")); 44 | checkAreEqual(node(null), node(null)); 45 | } 46 | 47 | @Test 48 | public void testEqualsAreEqualLists() { 49 | checkAreEqual(list("A", "B"), list("A", "B")); 50 | checkAreEqual(list("A", null), list("A", null)); 51 | } 52 | 53 | private static void checkAreEqual(SingleLinkNode node, SingleLinkNode other) { 54 | // when 55 | boolean actual = node.equals(other); 56 | 57 | // then 58 | Assert.assertTrue(actual); 59 | } 60 | 61 | private static SingleLinkNode list(T ... values) { 62 | final SingleLinkNode[] result = new SingleLinkNode[1]; 63 | Stream.of(values) 64 | .map(value -> node(value)) 65 | .reduce(null, 66 | (list, node) -> { 67 | if(list != null) { 68 | list.next(node); 69 | } else { 70 | result[0] = node; 71 | } 72 | 73 | return node; 74 | } 75 | ); 76 | 77 | return result[0]; 78 | } 79 | 80 | private static SingleLinkNode node(T value) { 81 | return new SingleLinkNode(value); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/GrowingArray.java: -------------------------------------------------------------------------------- 1 | package structures; 2 | 3 | import java.lang.*; 4 | import java.lang.reflect.*; 5 | 6 | @SuppressWarnings({"unchecked"}) 7 | public class GrowingArray { 8 | private T[] array; 9 | private int count = 0; 10 | private Class componentType; 11 | 12 | public GrowingArray(Class arrayType, int size) { 13 | componentType = arrayType; 14 | array = createArray(arrayType, size); 15 | } 16 | 17 | private T[] createArray(Class type, int size) { 18 | return (T[]) Array.newInstance(type, size); 19 | } 20 | 21 | public void add(T element) { 22 | array[count] = element; 23 | count++; 24 | growIfBig(); 25 | } 26 | 27 | private void growIfBig() { 28 | if(count >= (array.length * 0.75d)) { 29 | T[] bigger = createArray(componentType, array.length * 2); 30 | System.arraycopy(array, 0, bigger, 0, count); 31 | array = bigger; 32 | } 33 | } 34 | 35 | public T getLast() { 36 | return this.array[count - 1]; 37 | } 38 | 39 | public T remove(int index) { 40 | if(index < 0 || index >= count) { 41 | throw new RuntimeException("Index out of bounds. Index: " + index + ", Size: " + count); 42 | } 43 | 44 | T item = this.array[index]; 45 | System.arraycopy(array, index+1, array, index, count - index - 1); 46 | count--; 47 | shrinkIfSmall(); 48 | 49 | return item; 50 | } 51 | 52 | public void insert(int index, T value) { 53 | count++; 54 | for(int i = count; i > index; i--) { 55 | array[i] = array[i-1]; 56 | } 57 | 58 | array[index] = value; 59 | growIfBig(); 60 | } 61 | 62 | public T removeLast() { 63 | T last = this.array[count -1]; 64 | this.array[count - 1] = null; 65 | count--; 66 | 67 | shrinkIfSmall(); 68 | 69 | return last; 70 | } 71 | 72 | private void shrinkIfSmall() { 73 | if(count <= array.length * 0.25d) { 74 | T[] smaller = createArray(componentType, array.length / 2); 75 | System.arraycopy(array, 0, smaller, 0, count); 76 | array = smaller; 77 | } 78 | } 79 | 80 | public T get(int index) { 81 | return array[index]; 82 | } 83 | 84 | public T[] getShallowCopy() { 85 | T[] copy = createArray(componentType, count); 86 | System.arraycopy(array, 0, copy, 0, count); 87 | return copy; 88 | } 89 | 90 | // Gets the number of elemetns in the array 91 | public int count() { 92 | return this.count; 93 | } 94 | 95 | // Gets the size of the buffer 96 | public int size() { 97 | return this.array.length; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What is this? 2 | 3 | It is a set of tests and solutions to the coding problems in the book 4 | "Cracking the Coding Interview". 5 | 6 | ## Requirements 7 | 8 | - Java 8 9 | - ruby 1.9+ 10 | 11 | 12 | ## Structure 13 | 14 | ### Solutions are under the src directory 15 | 16 | ``` 17 | /src/Chapter1.java 18 | /src/Chapter2.java 19 | ... 20 | /src/ChapterN.java 21 | ``` 22 | 23 | ### Tests are under the test directory 24 | 25 | ``` 26 | /test/Chapter1Test.java 27 | /test/Chapter2Test.java 28 | ... 29 | /test/ChapterNTest.java 30 | ``` 31 | 32 | ## How To 33 | 34 | ### Work on solutions 35 | 36 | To start working on solutions you need the continuous 37 | runner to execute. It will execute the test of the 38 | corresponding solution that you modified, or if the 39 | test is modified, it will execute the test that was 40 | modified, there is currently no way to execute all tests 41 | or to execute tests that correspond to utility files, 42 | this can be done later. 43 | 44 | 45 | ```bash 46 | # install all required gems 47 | bundle install 48 | # execute the continuous runner (Guard) 49 | bundle exec guard 50 | ``` 51 | ## Recommended Study Plan 52 | 53 | ### MVP 54 | 55 | You should solve all of these problems at the minimum, to even have a chance at success. 56 | 57 | - [ ] Chapter 1: Arrays and Strings 58 | - [ ] Chapter 2: Linked Lists 59 | - [ ] Chapter 3: Stacks and Queues 60 | - [ ] Chapter 4: Trees and Graphs 61 | - [ ] Chapter 9: Recursion and Dynamic Programming 62 | - [ ] Chapter 11: Sorting and Searching 63 | - [ ] Chapter 17: Moderate 64 | 65 | ### HIGH VALUE 66 | 67 | Solving these significatively increases your chances of success. 68 | 69 | - [ ] Chapter 10: Scalability and Memory Limits 70 | - [ ] Chapter 18: Hard 71 | - [ ] Chapter 5: Bit Manipulation 72 | - [ ] Chapter 12: Testing 73 | 74 | ### RELATED TO THINGS RARELY ASKED 75 | 76 | Solving these will increase your chances if you don't already know these subjects, but if you've worked with these before, they won't help a lot. 77 | 78 | - [ ] Chapter 7: Mathematics and Probability 79 | - [ ] Chapter 16: Threads and Locks 80 | - [ ] Chapter 15: Databases 81 | - [ ] Chapter 13: C and C++ 82 | 83 | ### NOT VERY USEFUL 84 | 85 | I found these to be of low value and having little to no useful information that one could benefit from in order to pass the interviews. 86 | 87 | - [ ] Chapter 13: Java 88 | - Too basic to be of worth. 89 | - [ ] Chapter 6: Brain Teasers 90 | - Nobody does brain teasers anymore. 91 | - [ ] Chapter 8: Object-oriented design 92 | - The reason this was useless in my case, is that the information it has is very generic. I did my master's thesis on this subject. 93 | -------------------------------------------------------------------------------- /test/ChapterTestBase.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import static org.hamcrest.Matcher.*; 4 | import static org.hamcrest.MatcherAssert.*; 5 | import static org.hamcrest.core.Is.*; 6 | import static org.hamcrest.core.IsNot.*; 7 | import static org.hamcrest.core.IsEqual.*; 8 | import static org.hamcrest.core.IsNull.*; 9 | 10 | 11 | import java.lang.*; 12 | import java.util.*; 13 | import java.util.stream.*; 14 | 15 | import org.hamcrest.*; 16 | import org.hamcrest.core.*; 17 | import org.junit.*; 18 | import org.junit.rules.*; 19 | import structures.*; 20 | 21 | public abstract class ChapterTestBase { 22 | @Rule 23 | public ErrorCollector errors = new ErrorCollector(); 24 | 25 | @After 26 | public void disableDebugger() { 27 | debug.Debugger.DEBUG.disable(); 28 | } 29 | 30 | protected void isTrue(String reason, T value) { 31 | errors.checkThat(reason, value, is(true)); 32 | } 33 | 34 | protected void isTrue(T value) { 35 | errors.checkThat(value, is(true)); 36 | } 37 | 38 | protected void isTrueThat(String reason, T value, Matcher matcher) { 39 | errors.checkThat(reason, value, matcher); 40 | } 41 | 42 | protected void isTrueThat(T value, Matcher matcher) { 43 | errors.checkThat(value, matcher); 44 | } 45 | 46 | protected void isFalse(String reason, T value) { 47 | errors.checkThat(reason, value, is(false)); 48 | } 49 | 50 | protected void isFalse(T value) { 51 | errors.checkThat(value, is(false)); 52 | } 53 | 54 | protected void isFalseThat(String reason, T value, Matcher matcher) { 55 | errors.checkThat(reason, value, not(matcher)); 56 | } 57 | 58 | protected void isFalseThat(T value, Matcher matcher) { 59 | errors.checkThat(value, not(matcher)); 60 | } 61 | 62 | protected void areEqual(Object actual, Object expected) { 63 | errors.checkThat(actual, equalTo(expected)); 64 | } 65 | 66 | protected void areEqual(String message, Object actual, Object expected) { 67 | errors.checkThat(message, actual, equalTo(expected)); 68 | } 69 | 70 | protected void areNotEqual(Object actual, Object expected) { 71 | errors.checkThat(actual, not(equalTo(expected))); 72 | } 73 | 74 | protected void areNotEqual(String message, Object actual, Object expected) { 75 | errors.checkThat(message, actual, not(equalTo(expected))); 76 | } 77 | 78 | protected static int[] popAll(LinkedListStack stack) { 79 | int[] result = new int[stack.size()]; 80 | for(int i = 0; i < result.length; i++) { 81 | result[i] = stack.pop(); 82 | } 83 | 84 | return result; 85 | } 86 | 87 | protected static void pushAll(LinkedListStack stack, int ... input) { 88 | for(int number : input) { 89 | stack.push(number); 90 | } 91 | } 92 | 93 | public static String format(String format, Object ... args) { 94 | return String.format(format, args); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/BinaryTreeNode.java: -------------------------------------------------------------------------------- 1 | package structures; 2 | 3 | public class BinaryTreeNode { 4 | private final T value; 5 | private BinaryTreeNode left; 6 | private BinaryTreeNode right; 7 | private BinaryTreeNode parent; 8 | 9 | public BinaryTreeNode(T value) { this.value = value; } 10 | 11 | public T value() { return this.value; } 12 | 13 | public void parent(BinaryTreeNode parent) { this.parent = parent; } 14 | public BinaryTreeNode parent() { return this.parent; } 15 | 16 | public void left(BinaryTreeNode left) { 17 | if(left != null) { 18 | left.parent(this); 19 | } 20 | 21 | this.left = left; 22 | } 23 | 24 | public BinaryTreeNode removeLeft() { 25 | BinaryTreeNode node = this.left; 26 | 27 | this.left.parent(null); 28 | this.left = null; 29 | 30 | return node; 31 | } 32 | 33 | public BinaryTreeNode left() { return this.left; } 34 | 35 | public void right(BinaryTreeNode right) { 36 | if(right != null) { 37 | right.parent(this); 38 | } 39 | 40 | this.right = right; 41 | } 42 | 43 | public BinaryTreeNode removeRight() { 44 | BinaryTreeNode node = this.right; 45 | 46 | this.right.parent(null); 47 | this.right = null; 48 | 49 | return node; 50 | } 51 | 52 | public BinaryTreeNode right() { return this.right; } 53 | 54 | public String toString() { 55 | StringBuilder sb = new StringBuilder(); 56 | sb.append(String.valueOf(value)); 57 | if(left() != null) { 58 | sb.append("\n"); 59 | toString(sb, left(), 0); 60 | } 61 | 62 | if(right() != null) { 63 | sb.append("\n"); 64 | toString(sb, right(), 0); 65 | } 66 | 67 | return sb.toString(); 68 | } 69 | 70 | public boolean equals(Object other) { 71 | if(!(other instanceof BinaryTreeNode)) { 72 | return false; 73 | } 74 | 75 | BinaryTreeNode node = (BinaryTreeNode) other; 76 | return equal(this.value(), node.value()) 77 | && equal(this.left(), node.left()) 78 | && equal(this.right(), node.right()); 79 | } 80 | 81 | private static boolean equal(Object a, Object b) { 82 | if(a == null || b == null) { return a == b; } 83 | 84 | return a.equals(b); 85 | } 86 | 87 | private static void toString(StringBuilder sb, BinaryTreeNode node, int parentLevel) { 88 | appendLevels(sb, parentLevel); 89 | if(node != null) { 90 | sb.append("|-"); 91 | sb.append(String.valueOf(node.value)); 92 | if(node.left() != null) { 93 | sb.append("\n"); 94 | toString(sb, node.left(), parentLevel + 1); 95 | } 96 | 97 | if(node.right() != null) { 98 | sb.append("\n"); 99 | toString(sb, node.right(), parentLevel + 1); 100 | } 101 | 102 | if(node.right() != null || node.left() != null) { 103 | sb.append("\n"); 104 | appendLevels(sb, parentLevel + 1); 105 | } 106 | } 107 | } 108 | 109 | private static void appendLevels(StringBuilder sb, int levels) { 110 | for(int i = 0; i < levels; i++) { sb.append("| "); } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/DoubleLinkNode.java: -------------------------------------------------------------------------------- 1 | package structures; 2 | 3 | public class DoubleLinkNode { 4 | 5 | private final T value; 6 | private DoubleLinkNode next; 7 | private DoubleLinkNode prev; 8 | 9 | 10 | public DoubleLinkNode(T value) { 11 | this.value = value; 12 | } 13 | 14 | public void next(DoubleLinkNode node) { 15 | if(this.next() != null) { 16 | this.next().prev = null; 17 | } 18 | 19 | if(node != null) { 20 | if(node.prev() != null) { 21 | node.prev().next(null); 22 | } 23 | 24 | node.prev = this; 25 | } 26 | 27 | this.next = node; 28 | } 29 | 30 | public DoubleLinkNode next() { 31 | return this.next; 32 | } 33 | 34 | public void prev(DoubleLinkNode prev) { 35 | if(prev != null) { 36 | prev.next(this); 37 | } else if(this.prev() != null) { 38 | this.prev().next(null); 39 | } 40 | } 41 | 42 | public DoubleLinkNode prev() { 43 | return this.prev; 44 | } 45 | 46 | public void unlink() { 47 | DoubleLinkNode next = this.next(); 48 | if(this.next() != null) { 49 | this.next().prev(this.prev()); 50 | } else if(this.prev() != null) { 51 | this.prev().next(null); 52 | } 53 | } 54 | 55 | public void insertNext(DoubleLinkNode next) { 56 | DoubleLinkNode oldNext = this.next(); 57 | oldNext.prev(null); 58 | this.next(next); 59 | oldNext.prev(next); 60 | } 61 | 62 | public void insertPrev(DoubleLinkNode prev) { 63 | DoubleLinkNode oldPrev = this.prev(); 64 | oldPrev.next(null); 65 | this.prev(prev); 66 | oldPrev.next(prev); 67 | } 68 | 69 | public T value() { 70 | return this.value; 71 | } 72 | 73 | @Override 74 | public String toString() { 75 | StringBuilder sb = new StringBuilder(); 76 | DoubleLinkNode node = this; 77 | while(node != null) { 78 | if(node.value == null) { 79 | sb.append(""); 80 | } else { 81 | sb.append(node.value.toString()); 82 | } 83 | 84 | if(node.next != null) { 85 | sb.append("->"); 86 | } 87 | 88 | node = node.next; 89 | } 90 | 91 | return sb.toString(); 92 | } 93 | 94 | public int hashCode() { 95 | int hashCode = 0; 96 | if(this.value != null) { 97 | hashCode += 31 * this.value.hashCode(); 98 | } 99 | 100 | if(this.next() != null) { 101 | hashCode += 17 * this.next.hashCode(); 102 | } 103 | 104 | if(this.prev() != null) { 105 | hashCode += 12 * this.prev.hashCode(); 106 | } 107 | return hashCode; 108 | } 109 | 110 | @Override 111 | public boolean equals(Object other) { 112 | if(this == other) { 113 | return true; 114 | } 115 | 116 | if(!(other instanceof DoubleLinkNode)) { 117 | return false; 118 | } 119 | 120 | DoubleLinkNode otherNode = (DoubleLinkNode)other; 121 | 122 | return equal(this.value, otherNode.value) 123 | && equal(this.next, otherNode.next) 124 | && equal(this.prev, otherNode.prev); 125 | } 126 | 127 | private static boolean equal(Object a, Object b) { 128 | if(a == null || b == null) { return a == b; } 129 | 130 | return a.equals(b); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/LinkedList.java: -------------------------------------------------------------------------------- 1 | package structures; 2 | 3 | public class LinkedList { 4 | private SingleLinkNode head; 5 | private SingleLinkNode tail; 6 | private int size = 0; 7 | 8 | public void add(T value) { 9 | if(this.isEmpty()) { 10 | this.head = this.tail = new SingleLinkNode(value); 11 | } else { 12 | SingleLinkNode node = new SingleLinkNode(value); 13 | tail.next(node); 14 | tail = node; 15 | } 16 | 17 | this.size++; 18 | } 19 | 20 | public void appendAll(LinkedList other) { 21 | this.tail.next(other.head); 22 | this.tail = other.tail; 23 | this.size += other.size; 24 | } 25 | 26 | public T removeFirst() { 27 | if(this.isEmpty()) { throw new RuntimeException("List is empty"); } 28 | T value = this.head.value(); 29 | if(this.tail == this.head) { 30 | this.tail = this.head = null; 31 | } else { 32 | this.head = this.head.next(); 33 | } 34 | 35 | this.size--; 36 | 37 | return value; 38 | } 39 | 40 | public T get(int index) { 41 | if(index < 0 || index >= size()) { 42 | throw new RuntimeException("Index out of bounds: " + index + ", size: " + size() + "."); 43 | } 44 | 45 | if(this.isEmpty()) { throw new RuntimeException("List is empty"); } 46 | 47 | SingleLinkNode node = this.head; 48 | while(index-- > 0) { node = node.next(); } 49 | 50 | return node.value(); 51 | } 52 | 53 | public T first() { 54 | if(this.isEmpty()) { throw new RuntimeException("List is empty"); } 55 | 56 | return this.head.value(); 57 | } 58 | 59 | public T last() { 60 | if(this.tail == null) { throw new RuntimeException("List is empty"); } 61 | 62 | return this.tail.value(); 63 | } 64 | 65 | public boolean contains(T value) { 66 | SingleLinkNode node = head; 67 | while(node != null) { 68 | if(equals(node.value(), value)) { 69 | return true; 70 | } 71 | 72 | node = node.next(); 73 | } 74 | 75 | return false; 76 | } 77 | 78 | public int size() { return this.size; } 79 | public boolean isEmpty() { return this.size() == 0; } 80 | 81 | public K[] toArray(Class itemClass) { 82 | K[] array = (K[]) java.lang.reflect.Array.newInstance(itemClass, size()); 83 | SingleLinkNode node = head; 84 | int i = 0; 85 | while(head != null) { 86 | array[i] = (K) head.value(); 87 | head = head.next(); 88 | i++; 89 | } 90 | 91 | return array; 92 | } 93 | 94 | public String toString() { 95 | if(isEmpty()) { return ""; } 96 | StringBuilder sb = new StringBuilder(); 97 | sb.append(" {"); 98 | sb.append(head); 99 | sb.append("} "); 100 | 101 | return sb.toString(); 102 | } 103 | 104 | public boolean equals(Object otherObj) { 105 | if(!(otherObj instanceof LinkedList)) { 106 | return false; 107 | } 108 | 109 | LinkedList other = (LinkedList) otherObj; 110 | 111 | return this.size == other.size && equals(this.head, other.head); 112 | } 113 | 114 | private boolean equals(Object value, Object other) { 115 | if(value == null || other == null) { return value == other; } 116 | 117 | return value.equals(other); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Debugger.java: -------------------------------------------------------------------------------- 1 | package debug; 2 | 3 | public final class Debugger { 4 | public static final Debugger DEBUG = new Debugger(); 5 | 6 | private volatile boolean enabled = false; 7 | 8 | private Debugger() { } 9 | 10 | public T println(T obj) { 11 | if(enabled) { 12 | System.out.println(obj + ""); 13 | } 14 | 15 | return obj; 16 | } 17 | 18 | public void println(String format, Object arg) { 19 | if(!enabled) { return; } 20 | 21 | println(format, new Object[] { arg }); 22 | } 23 | 24 | public void println(String format, Object arg1, Object arg2) { 25 | if(!enabled) { return; } 26 | 27 | println(format, new Object[] { arg1, arg2 }); 28 | } 29 | 30 | public void println(String format, Object arg1, Object arg2, Object arg3) { 31 | if(!enabled) { return; } 32 | 33 | println(format, new Object[] { arg1, arg2, arg3 }); 34 | } 35 | 36 | public void println(String format, Object arg1, Object arg2, Object arg3, Object arg4) { 37 | if(!enabled) { return; } 38 | 39 | println(format, new Object[] { arg1, arg2, arg3, arg4 }); 40 | } 41 | 42 | public void println(String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) { 43 | if(!enabled) { return; } 44 | 45 | println(format, new Object[] { arg1, arg2, arg3, arg4, arg5 }); 46 | } 47 | 48 | public void println(String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6) { 49 | if(!enabled) { return; } 50 | 51 | println(format, new Object[] { arg1, arg2, arg3, arg4, arg5, arg6 }); 52 | } 53 | 54 | public void println(String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7) { 55 | if(!enabled) { return; } 56 | 57 | println(format, new Object[] { arg1, arg2, arg3, arg4, arg5, arg6, arg7 }); 58 | } 59 | 60 | 61 | public void println(String format, Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, Object ... args) { 62 | if(!enabled) { return; } 63 | 64 | if(args == null) { 65 | args = new Object[] { null }; 66 | } 67 | 68 | Object[] allArgs = new Object[7 + args.length]; 69 | allArgs[0] = arg1; 70 | allArgs[1] = arg2; 71 | allArgs[2] = arg3; 72 | allArgs[3] = arg4; 73 | allArgs[4] = arg5; 74 | allArgs[5] = arg6; 75 | allArgs[6] = arg7; 76 | 77 | if(args.length > 0) { 78 | System.arraycopy(args, 0, allArgs, 7, args.length); 79 | } 80 | 81 | println(format, allArgs); 82 | } 83 | 84 | private void println(String format, Object ... args) { 85 | if(enabled) { 86 | System.out.println(String.format(format, args)); 87 | } 88 | } 89 | 90 | public T print(T obj) { 91 | if(enabled) { 92 | System.out.print(obj + ""); 93 | } 94 | 95 | return obj; 96 | } 97 | 98 | public void print(String format, Object ... args) { 99 | if(enabled) { 100 | System.out.print(String.format(format, args)); 101 | } 102 | } 103 | 104 | public String println(String msg) { 105 | if(enabled) { 106 | System.out.println(msg); 107 | } 108 | 109 | return msg; 110 | } 111 | 112 | public String print(String msg) { 113 | if(enabled) { 114 | System.out.print(msg); 115 | } 116 | 117 | return msg; 118 | } 119 | 120 | public void enable() { this.enabled = true; } 121 | public void disable() { this.enabled = false; } 122 | } 123 | -------------------------------------------------------------------------------- /test/Chapter18Test.java: -------------------------------------------------------------------------------- 1 | package chapter18; 2 | 3 | import static debug.Debugger.DEBUG; 4 | import static org.hamcrest.Matcher.*; 5 | import static org.hamcrest.core.IsCollectionContaining.*; 6 | import static org.hamcrest.MatcherAssert.*; 7 | import static org.hamcrest.core.Is.*; 8 | import static org.hamcrest.core.IsEqual.*; 9 | import static org.hamcrest.core.IsNull.*; 10 | 11 | 12 | import java.lang.*; 13 | import java.util.*; 14 | import java.util.function.*; 15 | import java.util.stream.*; 16 | 17 | import org.hamcrest.core.*; 18 | import org.junit.*; 19 | import org.junit.rules.*; 20 | import structures.*; 21 | import test.*; 22 | 23 | public class Chapter18Test extends ChapterTestBase { 24 | @Test 25 | public void testCardShuffler() { 26 | // given 27 | String[] cards = { "A", "B", "C", "D" }; 28 | Shuffler shuffler = new Shuffler(); 29 | //DEBUG.enable(); 30 | Stream.of(cards).forEach(DEBUG::print); 31 | DEBUG.println(""); 32 | // when 33 | String[] shuffledCards = shuffler.shuffle(cards); 34 | Stream.of(shuffledCards).forEach(DEBUG::print); 35 | // then 36 | Assert.assertEquals(cards.length, shuffledCards.length); 37 | Assert.assertThat(Arrays.asList(shuffledCards), hasItems(shuffledCards)); 38 | } 39 | 40 | @Test 41 | public void testTwosCounter() { 42 | // given 43 | int n = 123; 44 | int expected = (new int[] { 2, 12, 22, 22, 32, 42, 52, 62, 72, 82, 92, 102, 112, 120, 122, 122 }).length; 45 | TwosCounter counter = new TwosCounter(); 46 | // when 47 | int actual = counter.countTwos(n); 48 | // then 49 | Assert.assertEquals(expected, actual); 50 | } 51 | 52 | @Test 53 | public void testGeneralWordDistanceFinder() { 54 | // given 55 | final String[] words = { "A", "B", "C", "D", "B", "E", "A", "D", "F", "C", "A", "G", "C" }; 56 | final WordDistanceFinder target = new WordDistanceFinder(); 57 | checkWordDistanceFinder((a, b) -> target.generalFind(words, a, b)); 58 | } 59 | 60 | @Test 61 | public void testOptimizedWordDistanceFinder() { 62 | // given 63 | final String[] words = { "A", "B", "C", "D", "B", "E", "A", "D", "F", "C", "A", "G", "C" }; 64 | final WordDistanceFinder target = new WordDistanceFinder(); 65 | checkWordDistanceFinder(target.optimizedFind(words)); 66 | } 67 | 68 | private void checkWordDistanceFinder(BiFunction finder) { 69 | Assert.assertEquals("AD", 1, finder.apply("A", "D").intValue()); 70 | Assert.assertEquals("DA", 1, finder.apply("D", "A").intValue()); 71 | 72 | Assert.assertEquals("BD", 1, finder.apply("B", "D").intValue()); 73 | Assert.assertEquals("DB", 1, finder.apply("D", "B").intValue()); 74 | 75 | Assert.assertEquals("EB", 1, finder.apply("E", "B").intValue()); 76 | Assert.assertEquals("BE", 1, finder.apply("B", "E").intValue()); 77 | 78 | Assert.assertEquals("ED", 2, finder.apply("E", "D").intValue()); 79 | Assert.assertEquals("DE", 2, finder.apply("D", "E").intValue()); 80 | 81 | Assert.assertEquals("BG", 7, finder.apply("B", "G").intValue()); 82 | Assert.assertEquals("GB", 7, finder.apply("G", "B").intValue()); 83 | 84 | Assert.assertEquals("BZ", -1, finder.apply("B", "Z").intValue()); 85 | Assert.assertEquals("ZB", -1, finder.apply("Z", "B").intValue()); 86 | 87 | Assert.assertEquals(-1, finder.apply("Z", "T").intValue()); 88 | } 89 | 90 | @Test 91 | public void testSentenceChecker() { 92 | checkSentenceChecker("ilikeicecreamz", true); 93 | checkSentenceChecker("ilikeicecream", true); 94 | checkSentenceChecker("ilikeicecreamzz", false); 95 | checkSentenceChecker("b", false); 96 | checkSentenceChecker("", false); 97 | } 98 | 99 | public void checkSentenceChecker(String sentence, boolean expected) { 100 | HashSet dictionary = new HashSet<>(); 101 | dictionary.add("i"); 102 | dictionary.add("like"); 103 | dictionary.add("ice"); 104 | dictionary.add("cream"); 105 | dictionary.add("icecream"); 106 | dictionary.add("creamz"); 107 | 108 | SentenceChecker target = new SentenceChecker(); 109 | // When 110 | boolean actual = target.isValidSentence(sentence, dictionary); 111 | // Then 112 | Assert.assertEquals(expected, actual); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Chapter18.java: -------------------------------------------------------------------------------- 1 | package chapter18; 2 | 3 | import static debug.Debugger.DEBUG; 4 | 5 | import java.lang.*; 6 | import java.lang.reflect.*; 7 | import java.util.*; 8 | import java.util.function.*; 9 | import java.util.stream.*; 10 | 11 | import structures.*; 12 | 13 | class Shuffler { 14 | private final Random random = new Random(System.nanoTime()); 15 | 16 | public T[] shuffle(T ... elements) { 17 | for(int i = 0; i <= elements.length - 2; i++) { 18 | int swapIndex = random.nextInt(elements.length - i); 19 | T temp = elements[i]; 20 | elements[i] = elements[swapIndex]; 21 | elements[swapIndex] = temp; 22 | } 23 | 24 | return elements; 25 | } 26 | 27 | public T[] shuffleNaive(T ... elements) { 28 | //1 Build an array of indices of the same size as elements 29 | T[] shuffled = (T[]) Array.newInstance(elements[0].getClass(), elements.length); 30 | List indices = new ArrayList<>(); 31 | for(int i = 0; i < elements.length; i++) { 32 | indices.add(i); 33 | } 34 | 35 | int shuffledElementsCounter = 0; 36 | while(indices.size() > 0) { 37 | int randomIndex = random.nextInt(indices.size()); 38 | int shuffledPosition = indices.get(randomIndex); 39 | 40 | shuffled[shuffledPosition] = elements[shuffledElementsCounter]; 41 | 42 | shuffledElementsCounter++; 43 | indices.remove(randomIndex); 44 | } 45 | 46 | return shuffled; 47 | } 48 | } 49 | 50 | class RandomSetGenerator { 51 | // Assumption: source length must be greater than or equal to output size 52 | // Assumption: No repeated numbers are desired 53 | public Set generate(int[] source, int outputSize) { 54 | Random random = new Random(System.nanoTime()); 55 | List indices = new ArrayList<>(); 56 | // con: requires O(N) memory 57 | for(int i = 0; i < source.length; i++) { 58 | indices.add(i); 59 | } 60 | 61 | Set result = new HashSet<>(); 62 | for(int i = 0; i < outputSize; i++) { 63 | int indicesIndex = random.nextInt(indices.size()); 64 | int sourceIndex = indices.get(indicesIndex); 65 | result.add(source[sourceIndex]); 66 | indices.remove(indicesIndex); 67 | } 68 | 69 | return result; 70 | } 71 | } 72 | 73 | class TwosCounter { 74 | public int countTwos(int n) { 75 | if(n < 2) { 76 | return 0; 77 | } 78 | 79 | if(n < 10) { 80 | return 1; 81 | } 82 | 83 | int count = 1; // number 2 84 | double divisionResult = n / 10.0; 85 | while(divisionResult > 0) { 86 | count += divisionResult; 87 | double nextDivisionResult = divisionResult / 10.0; 88 | count += (int) nextDivisionResult; 89 | double remainder = nextDivisionResult - ((int) nextDivisionResult); 90 | if(remainder >= 0.2) { 91 | count++; 92 | } 93 | 94 | divisionResult = nextDivisionResult; 95 | } 96 | 97 | return count; 98 | } 99 | } 100 | 101 | // given a large file with a lot of words, find the shortest distance between 102 | // any two words. If the operation will be repeated many times for the same 103 | // file with other pairs of words, can you optimize your solution? 104 | 105 | class WordDistanceFinder { 106 | 107 | public int generalFind(String[] document, String wordA, String wordB) { 108 | int lastAIdx = -1; 109 | int lastBIdx = -1; 110 | int minDistance = Integer.MAX_VALUE; 111 | 112 | for(int i = 0; i < document.length; i++) { 113 | // A C B D E A 114 | if(wordA.equals(document[i])) { 115 | lastAIdx = i; 116 | } 117 | 118 | if(wordB.equals(document[i])) { 119 | lastBIdx = i; 120 | } 121 | 122 | if(lastAIdx >= 0 && lastBIdx >= 0) { 123 | int distance = Math.abs(lastAIdx - lastBIdx); 124 | if(distance < minDistance) { 125 | minDistance = distance; 126 | } 127 | } 128 | } 129 | 130 | if(minDistance == Integer.MAX_VALUE) { 131 | return -1; 132 | } 133 | 134 | return minDistance; 135 | } 136 | 137 | public BiFunction optimizedFind(String[] document) { 138 | final Map> wordsIndices = new HashMap<>(); 139 | for(int i = 0; i < document.length; i++) { 140 | String word = document[i]; 141 | List wordIndices = wordsIndices.get(word); 142 | if(wordIndices == null) { 143 | wordIndices = new ArrayList<>(); 144 | wordsIndices.put(word, wordIndices); 145 | } 146 | 147 | wordIndices.add(i); 148 | } 149 | 150 | return (String wordA, String wordB) -> { 151 | List wordAIndices = wordsIndices.get(wordA); 152 | List wordBIndices = wordsIndices.get(wordB); 153 | 154 | if(wordAIndices == null || wordBIndices == null) { 155 | return -1; 156 | } 157 | 158 | int minDistance = Integer.MAX_VALUE; 159 | int aCounter = 0; 160 | int bCounter = 0; 161 | while(aCounter < wordAIndices.size() && bCounter < wordBIndices.size()) { 162 | int aIndex = wordAIndices.get(aCounter); 163 | int bIndex = wordBIndices.get(bCounter); 164 | int distance = Math.abs(aIndex - bIndex); 165 | if(distance < minDistance) { 166 | minDistance = distance; 167 | } 168 | 169 | if(aIndex < bIndex) { 170 | aCounter++; 171 | } else { 172 | bCounter++; 173 | } 174 | } 175 | 176 | return minDistance; 177 | }; 178 | } 179 | } 180 | 181 | // Tell me if the sentence is valid according to words available in a dictionary 182 | class SentenceChecker { 183 | public SentenceChecker() { 184 | } 185 | 186 | public boolean isValidSentence(String sentence, HashSet dictionary) { 187 | return isValidSentence(new HashMap<>(), sentence, dictionary, 0); 188 | } 189 | 190 | private boolean isValidSentence( 191 | Map memo, 192 | String sentence, 193 | HashSet dictionary, 194 | int index 195 | ) { 196 | 197 | if(sentence.length() == 0) { 198 | return false; 199 | } 200 | 201 | if(index == sentence.length()) { 202 | return true; 203 | } 204 | 205 | if(memo.containsKey(index)) { 206 | return memo.get(index); 207 | } 208 | 209 | // I can iterate over the dictionary or iterate over the sentence 210 | for(int currentIndex = index + 1; currentIndex <= sentence.length(); currentIndex++) { 211 | String word = sentence.substring(index, currentIndex); 212 | if(dictionary.contains(word)) { 213 | if(isValidSentence(memo, sentence, dictionary, currentIndex)) { 214 | memo.put(index, true); 215 | return true; 216 | } 217 | } 218 | } 219 | 220 | memo.put(index, false); 221 | return false; 222 | } 223 | } -------------------------------------------------------------------------------- /test/Chapter11Test.java: -------------------------------------------------------------------------------- 1 | package chapter11; 2 | 3 | import static debug.Debugger.DEBUG; 4 | import static org.hamcrest.Matcher.*; 5 | import static org.hamcrest.MatcherAssert.*; 6 | import static org.hamcrest.core.Is.*; 7 | import static org.hamcrest.core.IsEqual.*; 8 | import static org.hamcrest.core.IsNull.*; 9 | 10 | 11 | import java.lang.*; 12 | import java.util.*; 13 | import java.util.stream.*; 14 | 15 | import org.hamcrest.core.*; 16 | import org.junit.*; 17 | import org.junit.rules.*; 18 | import structures.*; 19 | import test.*; 20 | 21 | public class Chapter11Test extends ChapterTestBase { 22 | 23 | @Test 24 | public void testSortedArrayMerger() { 25 | // given 26 | int[] a = { 1, 3, 5, 0, 0, 0 }; 27 | int[] b = { 2, 4, 6 }; 28 | int[] expected = { 1, 2, 3, 4, 5, 6 }; 29 | SortedArrayMerger merger = new SortedArrayMerger(); 30 | // when 31 | merger.merge(a, b); 32 | // then 33 | areEqual(a, expected); 34 | } 35 | 36 | @Test 37 | public void testRotatedArraySearcher() { 38 | // given 39 | int[] input = { 2, 3, 4, 5, 1 }; 40 | int[] otherInput = { 9, 10, 6, 6, 6 }; 41 | 42 | for(int term : input) { 43 | checkRotatedArraySearcher_Present(input, term); 44 | checkRotatedArraySearcher_NotPresent(otherInput, term); 45 | } 46 | 47 | for(int term : otherInput) { 48 | checkRotatedArraySearcher_Present(otherInput, term); 49 | checkRotatedArraySearcher_NotPresent(input, term); 50 | } 51 | } 52 | 53 | private void checkRotatedArraySearcher_Present(int[] input, int term) { 54 | RotatedArraySearcher searcher = new RotatedArraySearcher(); 55 | // when 56 | boolean actual = searcher.contains(input, term); 57 | // then 58 | isTrue(actual); 59 | } 60 | 61 | private void checkRotatedArraySearcher_NotPresent(int[] input, int term) { 62 | RotatedArraySearcher searcher = new RotatedArraySearcher(); 63 | // when 64 | boolean actual = searcher.contains(input, term); 65 | // then 66 | isFalse(actual); 67 | } 68 | 69 | @Test 70 | public void testSortPalindromesFirst() { 71 | // given 72 | String[] input = { "not anagram", "aba", "other", "bab", "otto" }; 73 | String[] expected = { "aba", "bab", "otto", "other", "not anagram" }; 74 | SortPalindromesFirst target = new SortPalindromesFirst(); 75 | // when 76 | DEBUG.enable(); 77 | String[] actual = target.sortPalindromes(input); 78 | DEBUG.disable(); 79 | // then 80 | areEqual(actual, expected); 81 | } 82 | 83 | @Test 84 | public void testWhitespacesStringArraySearch() { 85 | // given 86 | String[] inputs = 87 | { "a", "", "", "b", "", "d", "", "e", "f", "n", "r", "", "", "", "z" }; 88 | String[] other = 89 | { "j", "", "", "", "l", "m", "o", "w", "x", "", "", "y" }; 90 | WhitespacesStringArraySearch target = new WhitespacesStringArraySearch(); 91 | // when 92 | for(int i = 0; i < inputs.length; i++) { 93 | // then 94 | if(!inputs[i].equals("")) { 95 | areEqual(format("inputs(%s)", inputs[i]), target.indexOf(inputs, inputs[i]), i); 96 | areEqual(target.indexOf(other, inputs[i]), -1); 97 | } 98 | } 99 | 100 | for(int j = 0; j < other.length; j++) { 101 | if(!other[j].equals("")) { 102 | areEqual(format("other(%s)", other[j]), target.indexOf(other, other[j]), j); 103 | areEqual(target.indexOf(inputs, other[j]), -1); 104 | } 105 | } 106 | } 107 | 108 | @Test 109 | public void testBadMatrixSearch_PairRows() { 110 | testMatrixSearch_PairRows(new BadMatrixFinder()); 111 | } 112 | 113 | @Test 114 | public void testCorrectMatrixSearch_PairRows() { 115 | testMatrixSearch_PairRows(new CorrectMatrixFinder()); 116 | } 117 | 118 | public void testMatrixSearch_PairRows(MatrixFinder finder) { 119 | // given 120 | int[][] matrix = { 121 | {1, 3, 6, 7, 17}, 122 | {2, 5, 8, 11, 18}, 123 | {4, 9, 10, 12, 19}, 124 | {13, 16, 20, 21, 29}, 125 | {14, 24, 25, 26, 30}, 126 | {28, 31, 32, 33, 34} 127 | }; 128 | // when 129 | testMatrixSearch(matrix, finder); 130 | } 131 | 132 | @Test 133 | public void testBadMatrixSearch_OddRows() { 134 | testMatrixSearch_OddRows(new BadMatrixFinder()); 135 | } 136 | 137 | @Test 138 | public void testCorrectMatrixSearch_OddRows() { 139 | testMatrixSearch_OddRows(new CorrectMatrixFinder()); 140 | } 141 | 142 | public void testMatrixSearch_OddRows(MatrixFinder finder) { 143 | // given 144 | int[][] matrix = { 145 | {1, 3, 6, 7, 17}, 146 | {2, 5, 8, 11, 18}, 147 | {13, 16, 20, 21, 29}, 148 | {14, 24, 25, 26, 30}, 149 | {28, 31, 32, 33, 34} 150 | }; 151 | // when 152 | testMatrixSearch(matrix, finder); 153 | } 154 | 155 | public void testMatrixSearch(int[][] matrix, MatrixFinder finder) { 156 | for(int row = 0; row < matrix.length; row++) { 157 | for(int col = 0; col < matrix[0].length; col++) { 158 | DEBUG.println("------------------------------"); 159 | DEBUG.println("Searching: %s", matrix[row][col]); 160 | // then 161 | MatrixIndex index = finder.indexOf(matrix, matrix[row][col]); 162 | areEqual( 163 | format("Row for matrix[%s][%s](%s)", row, col, matrix[row][col]), 164 | index, 165 | MatrixIndex.index(row, col) 166 | ); 167 | } 168 | } 169 | } 170 | 171 | @Test 172 | public void testPeopleLongestSorter() { 173 | // given 174 | Person[] people = people( 175 | person(1, 1), person(3, 3), person(1, 2), 176 | person(2, 1), person(3, 1), person(1, 3), 177 | person(3, 2), person(4, 4), person(2, 3), 178 | person(2, 1), person(4, 3), person(3, 4), 179 | person(2, 2), person(4, 2), person(2, 4) 180 | ); 181 | Person[] expected = people( 182 | person(4, 4), person(3, 3), person(2, 2), person(1, 1) 183 | ); 184 | PeopleLongestSorter sorter = new PeopleLongestSorter(); 185 | // when 186 | structures.LinkedList actual = sorter.longestSort(people); 187 | // then 188 | Person[] actualArr = actual.toArray(Person.class); 189 | areEqual(actualArr, expected); 190 | } 191 | 192 | private static Person person(int weight, int height) { 193 | return new Person(weight, height); 194 | } 195 | 196 | private static Person[] people(Person ... people) { 197 | return people; 198 | } 199 | 200 | @Test 201 | public void testIntegerStreamer() { 202 | // given 203 | IntegerStreamer target = new IntegerStreamer(); 204 | // when 205 | areEqual(target.rankOf(1), null); 206 | target.stream(1); 207 | areEqual(target.rankOf(1), 0); 208 | target.stream(2); 209 | areEqual(target.rankOf(1), 0); 210 | areEqual(target.rankOf(2), 1); 211 | target.stream(9); 212 | areEqual(target.rankOf(9), 2); 213 | target.stream(3); 214 | areEqual(target.rankOf(3), 2); 215 | target.stream(2); 216 | areEqual(target.rankOf(2), 2); 217 | areEqual(target.rankOf(3), 3); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /test/RunJunit.java: -------------------------------------------------------------------------------- 1 | import org.junit.*; 2 | import org.junit.runners.*; 3 | import org.junit.runner.*; 4 | import org.junit.runner.notification.*; 5 | import java.lang.*; 6 | import java.net.*; 7 | import java.io.*; 8 | import java.util.*; 9 | import java.util.stream.*; 10 | import java.util.concurrent.Callable; 11 | import java.util.concurrent.ExecutorService; 12 | import java.util.concurrent.Executors; 13 | import java.util.concurrent.Future; 14 | import java.util.concurrent.TimeUnit; 15 | import java.util.concurrent.TimeoutException; 16 | 17 | public class RunJunit { 18 | private static final int ASSERTION_ERROR_TRACE_TRUNCATE = 20; 19 | private static final int USER_EXCEPTION_TRACE_TRUNCATE = 10; 20 | 21 | private static final int TIMEOUT_SEC = 10; 22 | private static final String RUN_ALL = "all"; 23 | private static Class classToTest = null; 24 | // Arg 1: directory where classes live 25 | // Arg 2: "ClassName" = class to execute 26 | // "all" = run all tests 27 | // = run self 28 | public static void main(String[] args) throws Exception { 29 | if(args.length <= 0) { 30 | throw new Exception("Usage: java RunJunit []"); 31 | } 32 | 33 | final String testClassName; 34 | if(args.length < 2) { 35 | testClassName = "RunJunit"; 36 | } else { 37 | testClassName = args[1]; 38 | } 39 | 40 | if(!testClassName.equals(RUN_ALL)) { 41 | exit(executeTest(testClassName)); 42 | } else { 43 | executeAllTests(); 44 | } 45 | } 46 | 47 | private static void executeAllTests() { 48 | boolean testsPassed = true; 49 | String[] tests = getAllClassNames().stream() 50 | .filter(name -> name.endsWith("Test")) 51 | .toArray(String[]::new); 52 | for(String test : tests) { 53 | try { 54 | testsPassed = testsPassed && executeTest(test).wasSuccessful(); 55 | } catch(Exception e) { 56 | testsPassed = false; 57 | System.out.println(e); 58 | } 59 | } 60 | 61 | System.exit(testsPassed ? 0 : 1); 62 | } 63 | 64 | private static Result executeTest(String testClassName) throws Exception { 65 | Result result = null; 66 | try { 67 | System.out.println("Testing class: " + testClassName); 68 | result = executeTestClass(findClass(testClassName)); 69 | printFailures(result.getFailures()); 70 | printSummary(result); 71 | } catch(ClassNotFoundException e) { 72 | System.out.println("Test class " + testClassName + " does not exist."); 73 | System.exit(1); 74 | } catch (TimeoutException e) { 75 | e.printStackTrace(); 76 | System.exit(1); 77 | } catch(TestNotFoundException e) { 78 | executeAllTests(); 79 | } 80 | 81 | return result; 82 | } 83 | 84 | private static Class findClass(String simpleClassName) throws Exception { 85 | Optional qClassName = getAllClassNames().stream() 86 | .filter(className -> className.endsWith(simpleClassName)) 87 | .findFirst(); 88 | if(qClassName.isPresent()) { 89 | return Class.forName(qClassName.get()); 90 | } else { 91 | System.out.println("WARNING: No unit test " + simpleClassName); 92 | throw new TestNotFoundException("No class for test name: " + simpleClassName); 93 | } 94 | } 95 | 96 | private static List getAllClassNames() { 97 | try { 98 | ClassLoader classLoader = RunJunit.class.getClassLoader(); 99 | Enumeration roots = classLoader.getResources(""); 100 | List classFiles = new ArrayList<>(); 101 | while(roots.hasMoreElements()) { 102 | URL url = roots.nextElement(); 103 | File file = new File(url.getPath()); 104 | classFiles.addAll(getAllClassFiles(file, file)); 105 | } 106 | 107 | return classFiles; 108 | } catch(IOException e) { 109 | throw new RuntimeException(e); 110 | } 111 | } 112 | 113 | private static List getAllClassFiles(File file, File rootDir) { 114 | List classFiles = new ArrayList<>(); 115 | if(file.getName().endsWith(".class")) { 116 | String relativePath = getRelativePath(file, rootDir); 117 | String qClassName = relativePath.replace(".class", "").replace("/", "."); 118 | classFiles.add(qClassName); 119 | return classFiles; 120 | } 121 | 122 | if(file.isDirectory()) { 123 | for(File content : file.listFiles()) { 124 | classFiles.addAll(getAllClassFiles(content, rootDir)); 125 | } 126 | } 127 | 128 | return classFiles; 129 | } 130 | 131 | private static String getRelativePath(File file, File rootDir) { 132 | return rootDir.toURI().relativize(file.toURI()).getPath(); 133 | } 134 | 135 | private static Result executeTestClass(Class testClass) throws Exception { 136 | final ExecutorService executor = Executors.newSingleThreadExecutor(); 137 | try { 138 | final JUnitCore jUnitCore = new JUnitCore(); 139 | final Future future = 140 | executor.submit(() -> jUnitCore.run(testClass)); 141 | 142 | return future.get(TIMEOUT_SEC, TimeUnit.SECONDS); 143 | } finally { 144 | executor.shutdownNow(); 145 | } 146 | } 147 | 148 | private static void exit(Result result) { 149 | if(result.wasSuccessful()) { 150 | System.exit(0); 151 | } else { 152 | System.exit(1); 153 | } 154 | } 155 | 156 | public static void printFailures(List failures) { 157 | for(Failure failure : failures) { 158 | System.out.println(failure.getDescription()); 159 | if(!(failure.getException() instanceof AssertionError)) { 160 | 161 | System.out.println(toShortTrace(failure.getTrace(), ASSERTION_ERROR_TRACE_TRUNCATE)); 162 | } else { 163 | System.out.println(toShortTrace(failure.getTrace(), USER_EXCEPTION_TRACE_TRUNCATE)); 164 | } 165 | } 166 | } 167 | 168 | private static String toShortTrace(String fullTrace, int maxTraceLines) { 169 | String[] shortTrace = Stream 170 | .of(fullTrace.split("\n")) 171 | .limit(maxTraceLines) 172 | .toArray(String[]::new); 173 | return String.join("\n", shortTrace); 174 | } 175 | 176 | public static void printSummary(Result result) { 177 | String summary = ""; 178 | summary += "Tests run: " + result.getRunCount() + " "; 179 | summary += "ignored: " + result.getIgnoreCount() + " "; 180 | summary += "failed: " + result.getFailureCount() + " "; 181 | summary += "time: " + (result.getRunTime() / 100) + "ms"; 182 | System.out.println(summary); 183 | } 184 | 185 | @Test 186 | @Ignore 187 | public void failingTest() { 188 | Assert.assertEquals("This should always fail.", true, false); 189 | System.out.println("Executed test for RunJunit"); 190 | } 191 | 192 | @Test 193 | public void passingTest() { 194 | Assert.assertEquals("This should always pass.", true, true); 195 | } 196 | 197 | @Test 198 | @Ignore 199 | public void testWithException() { 200 | throw new RuntimeException("How does this look?"); 201 | } 202 | 203 | private static class TestNotFoundException extends RuntimeException { 204 | public TestNotFoundException(String msg) { 205 | super(msg); 206 | } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /test/Chapter2Test.java: -------------------------------------------------------------------------------- 1 | package chapter2; 2 | 3 | import static org.hamcrest.Matcher.*; 4 | import static org.hamcrest.MatcherAssert.*; 5 | import static org.hamcrest.core.Is.*; 6 | import static org.hamcrest.core.IsEqual.*; 7 | 8 | import java.lang.*; 9 | import java.util.*; 10 | import java.util.stream.*; 11 | 12 | import org.hamcrest.core.*; 13 | import org.junit.*; 14 | import org.junit.rules.*; 15 | import structures.*; 16 | 17 | public class Chapter2Test { 18 | @Rule 19 | public ErrorCollector errors = new ErrorCollector(); 20 | 21 | private DuplicatesRemover duplicatesRemover = new DuplicatesRemover(); 22 | private KthElementGetter kthGetter = new KthElementGetter(); 23 | private MiddleRemover middleRemover = new MiddleRemover(); 24 | private PivotArranger pivotArranger = new PivotArranger(); 25 | private LinkListNumberCalculator linkListNumberCalculator = new LinkListNumberCalculator(); 26 | private CircularListHeadFinder headFinder = new CircularListHeadFinder(); 27 | private PalindromeIdentifier palindromeIdentifier = new PalindromeIdentifier(); 28 | 29 | @Test 30 | public void testRemoveDuplicatesFromSingleLinkedList() { 31 | checkRemoveDuplicates( 32 | buildList("F"), 33 | buildList("F") 34 | ); 35 | 36 | checkRemoveDuplicates( 37 | buildList("F", "F", "F"), 38 | buildList("F") 39 | ); 40 | 41 | checkRemoveDuplicates( 42 | buildList("F", "O"), 43 | buildList("F", "O") 44 | ); 45 | 46 | checkRemoveDuplicates( 47 | buildList("F", "O", "F"), 48 | buildList("F", "O") 49 | ); 50 | 51 | checkRemoveDuplicates( 52 | buildList("F", "F", "O"), 53 | buildList("F", "O") 54 | ); 55 | 56 | checkRemoveDuplicates( 57 | buildList("F", "O", "O"), 58 | buildList("F", "O") 59 | ); 60 | 61 | checkRemoveDuplicates( 62 | buildList("F", "O", "L", "L", "O", "W", " ", "U", "P"), 63 | buildList("F", "O", "L", "W", " ", "U", "P") 64 | ); 65 | } 66 | 67 | private void checkRemoveDuplicates(SingleLinkNode input, SingleLinkNode expected) { 68 | // when 69 | duplicatesRemover.removeDuplicates(input); 70 | // then 71 | errors.checkThat(input, equalTo(expected)); 72 | } 73 | 74 | @Test 75 | public void testGetKthElement() { 76 | // given 77 | SingleLinkNode head = buildList("0", "1", "2", "3"); 78 | checkKthElementGetter(head, 0, "3"); 79 | checkKthElementGetter(head, 1, "2"); 80 | checkKthElementGetter(head, 2, "1"); 81 | checkKthElementGetter(head, 3, "0"); 82 | } 83 | 84 | private void checkKthElementGetter(SingleLinkNode head, int index, T expected) { 85 | // when 86 | T value = kthGetter.get(head, index).value(); 87 | // then 88 | errors.checkThat(value, equalTo(expected)); 89 | } 90 | 91 | @Test 92 | public void testRemoveMiddleElement() { 93 | // given 94 | SingleLinkNode head = buildList("A", "B", "C", "D", "E"); 95 | SingleLinkNode middle = head.next().next(); 96 | // turn it into a circular single link list 97 | SingleLinkNode tail = middle.next().next(); 98 | tail.next(head); 99 | 100 | // when 101 | middleRemover.remove(middle); 102 | 103 | // then 104 | errors.checkThat(head.value(), equalTo("A")); 105 | errors.checkThat(head.next().value(), equalTo("B")); 106 | errors.checkThat(head.next().next().value(), equalTo("D")); 107 | errors.checkThat(head.next().next().next().value(), equalTo("E")); 108 | 109 | } 110 | 111 | @Test 112 | public void testArrangeLinkedList() { 113 | // given 114 | SingleLinkNode list = buildList(1, 9, 2, 8, 3, 7, 4, 6, 5); 115 | SingleLinkNode expected = buildList(1, 2, 3, 4, 5, 9, 8, 7, 6); 116 | // when 117 | pivotArranger.partition(list, 5); 118 | // then 119 | errors.checkThat(list, equalTo(expected)); 120 | } 121 | 122 | 123 | @Test 124 | public void testAddLinkListNumbers() { 125 | checkAddLinkListNumbers(buildList(1), buildList(2), buildList(3)); 126 | 127 | checkAddLinkListNumbers( 128 | buildList(5), 129 | buildList(7), 130 | buildList(2, 1)); //12 131 | 132 | checkAddLinkListNumbers( 133 | buildList(5), 134 | buildList(7, 1), // 17 135 | buildList(2, 2)); //22 136 | 137 | checkAddLinkListNumbers( 138 | buildList(5), 139 | buildList(7, 9), // 97 140 | buildList(2, 0, 1)); // 102 141 | 142 | checkAddLinkListNumbers( 143 | buildList(0, 5), //50 144 | buildList(2, 7), //72 145 | buildList(2, 2, 1)); //122 146 | 147 | checkAddLinkListNumbers( 148 | buildList(7, 1, 6), // 617 149 | buildList(5, 9, 2), // 295 150 | buildList(2, 1, 9)); // 912 151 | } 152 | 153 | public void checkAddLinkListNumbers( 154 | SingleLinkNode inputA, 155 | SingleLinkNode inputB, 156 | SingleLinkNode expected 157 | ) { 158 | // when 159 | SingleLinkNode actual = linkListNumberCalculator.sum(inputA, inputB); 160 | 161 | // then 162 | errors.checkThat(actual, equalTo(expected)); 163 | } 164 | 165 | @Test 166 | public void testAddLinkListNumbersReversed() { 167 | checkAddLinkListNumbers(buildList(1), buildList(2), buildList(3)); 168 | 169 | checkAddLinkListNumbersReversed( 170 | buildList(5), 171 | buildList(7), 172 | buildList(1, 2)); //12 173 | 174 | checkAddLinkListNumbersReversed( 175 | buildList(5), 176 | buildList(1, 7), // 17 177 | buildList(2, 2)); //22 178 | 179 | checkAddLinkListNumbersReversed( 180 | buildList(5), 181 | buildList(9, 7), // 97 182 | buildList(1, 0, 2)); // 102 183 | 184 | checkAddLinkListNumbersReversed( 185 | buildList(5, 0), //50 186 | buildList(7, 2), //72 187 | buildList(1, 2, 2)); //122 188 | 189 | checkAddLinkListNumbersReversed( 190 | buildList(6, 1, 7), // 617 191 | buildList(2, 9, 5), // 295 192 | buildList(9, 1, 2)); // 912 193 | } 194 | 195 | public void checkAddLinkListNumbersReversed( 196 | SingleLinkNode inputA, 197 | SingleLinkNode inputB, 198 | SingleLinkNode expected 199 | ) { 200 | // when 201 | SingleLinkNode actual = linkListNumberCalculator.sumReverse(inputA, inputB); 202 | 203 | // then 204 | errors.checkThat(actual, equalTo(expected)); 205 | } 206 | 207 | @Test 208 | public void testFindMiddleOfCircularList() { 209 | // given 210 | SingleLinkNode list = buildList(1, 2, 3); 211 | list.next().next().next(list); // make it circular 212 | // when 213 | SingleLinkNode head = headFinder.findHead(list); 214 | // then 215 | errors.checkThat(head.value(), equalTo(1)); 216 | } 217 | 218 | @Test 219 | public void testFindMiddleOfCircularListCorrupted() { 220 | // given 221 | SingleLinkNode list = buildList(1, 2, 3, 4, 5, 6); 222 | list.next().next().next().next().next().next(list.next().next()); // make it circular 223 | // when 224 | SingleLinkNode head = headFinder.findHead(list); 225 | // then 226 | errors.checkThat(head.value(), equalTo(3)); 227 | } 228 | 229 | @Test 230 | public void testIdentifyPalindrome() { 231 | checkIdentifyPalindrome(buildList("A"), true); 232 | checkIdentifyPalindrome(buildList("A", "A"), true); 233 | checkIdentifyPalindrome(buildList("A", "B", "A"), true); 234 | checkIdentifyPalindrome(buildList("A", "B", "A", "B", "A"), true); 235 | 236 | checkIdentifyPalindrome(buildList("A", "B", "A", "B"), false); 237 | checkIdentifyPalindrome(buildList("A", "B"), false); 238 | } 239 | 240 | private void checkIdentifyPalindrome(SingleLinkNode head, boolean expected) { 241 | errors.checkThat(palindromeIdentifier.isPalindrome(head), is(expected)); 242 | } 243 | 244 | private SingleLinkNode buildList(T ... values) { 245 | final SingleLinkNode[] head = new SingleLinkNode[1]; 246 | Stream.of(values) 247 | .map(value -> new SingleLinkNode(value)) 248 | .reduce( 249 | null, 250 | (list, node) -> { 251 | if(list == null) { 252 | head[0] = node; 253 | } else { 254 | list.next(node); 255 | } 256 | 257 | return node; 258 | } 259 | ); 260 | return head[0]; 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /test/Chapter1Test.java: -------------------------------------------------------------------------------- 1 | package chapter1; 2 | 3 | import static org.hamcrest.Matcher.*; 4 | import static org.hamcrest.MatcherAssert.*; 5 | import static org.hamcrest.core.Is.*; 6 | import static org.hamcrest.core.IsEqual.*; 7 | 8 | import org.hamcrest.core.*; 9 | import org.junit.*; 10 | import org.junit.rules.*; 11 | import java.lang.*; 12 | 13 | public class Chapter1Test { 14 | @Rule 15 | public ErrorCollector errors = new ErrorCollector(); 16 | 17 | UniqueCharsIdentifier uniqueIdentifier = new UniqueCharsIdentifier(); 18 | StringReverser reverser = new StringReverser(); 19 | StringPermutationChecker permutationChecker = new StringPermutationChecker(); 20 | SpaceReplacer spaceReplacer = new SpaceReplacer(); 21 | StringCompressor stringCompressor = new StringCompressor(); 22 | Matrix90DegreeRotator matrixRotator = new Matrix90DegreeRotator(); 23 | MatrixClearer matrixClearer = new MatrixClearer(); 24 | StringRotationChecker rotationChecker = new StringRotationChecker(); 25 | 26 | 27 | 28 | @Test 29 | public void testReverseString() { 30 | errors.checkThat(reverser.reverse(null), equalTo(null)); 31 | errors.checkThat(reverser.reverse(""), equalTo("")); 32 | errors.checkThat(reverser.reverse("A"), equalTo("A")); 33 | errors.checkThat(reverser.reverse("AB"), equalTo("BA")); 34 | errors.checkThat(reverser.reverse("ABC"), equalTo("CBA")); 35 | errors.checkThat(reverser.reverse("ABCD"), equalTo("DCBA")); 36 | } 37 | 38 | @Test 39 | public void testStringHasAllUniqueCharacters() { 40 | checkUniqueChars(""); 41 | checkUniqueChars("A"); 42 | checkUniqueChars("ABC"); 43 | checkUniqueChars("Aa"); 44 | 45 | checkNonUniqueChars("ABCA"); 46 | checkNonUniqueChars("AA"); 47 | } 48 | 49 | private void checkUniqueChars(String str) { 50 | errors.checkThat(uniqueIdentifier.hasUniqueChars(str), is(true)); 51 | } 52 | 53 | private void checkNonUniqueChars(String str) { 54 | errors.checkThat(uniqueIdentifier.hasUniqueChars(str), is(false)); 55 | } 56 | 57 | @Test 58 | public void testStringPermutationChecker() { 59 | errors.checkThat(permutationChecker.check("", ""), is(true)); 60 | errors.checkThat(permutationChecker.check("A", "A"), is(true)); 61 | errors.checkThat(permutationChecker.check("AB", "BA"), is(true)); 62 | errors.checkThat(permutationChecker.check("AB", "AB"), is(true)); 63 | errors.checkThat(permutationChecker.check("ABC", "BCA"), is(true)); 64 | errors.checkThat(permutationChecker.check("ABB", "BBA"), is(true)); 65 | 66 | errors.checkThat(permutationChecker.check("ABB", "CBA"), is(false)); 67 | errors.checkThat(permutationChecker.check("A", ""), is(false)); 68 | errors.checkThat(permutationChecker.check("A", "B"), is(false)); 69 | errors.checkThat(permutationChecker.check("A", "BA"), is(false)); 70 | errors.checkThat(permutationChecker.check("AD", "BA"), is(false)); 71 | errors.checkThat(permutationChecker.check("ADB", "CBA"), is(false)); 72 | } 73 | 74 | @Test 75 | public void testReplaceSpaceWithEscape() { 76 | checkReplaceSpaceWithEscape("Mr John Smith ", 13, "Mr%20John%20Smith"); 77 | checkReplaceSpaceWithEscape(" ", 1, "%20"); 78 | checkReplaceSpaceWithEscape("Mr", 2, "Mr"); 79 | checkReplaceSpaceWithEscape("", 0, ""); 80 | checkReplaceSpaceWithEscape("A ", 2, "A%20"); 81 | checkReplaceSpaceWithEscape(" A ", 2, "%20A"); 82 | checkReplaceSpaceWithEscape(" A ", 3, "%20A%20"); 83 | checkReplaceSpaceWithEscape(" A B ", 4, "%20A%20B"); 84 | checkReplaceSpaceWithEscape(" A B ", 5, "%20A%20B%20"); 85 | } 86 | 87 | private void checkReplaceSpaceWithEscape(String inputStr, int length, String expectedStr) { 88 | char[] input = inputStr.toCharArray(); 89 | char[] expected = expectedStr.toCharArray(); 90 | 91 | spaceReplacer.replace(input, length); 92 | errors.checkThat(input, equalTo(expected)); 93 | } 94 | 95 | @Test 96 | public void testStringCompressor() { 97 | errors.checkThat(stringCompressor.compress(""), equalTo("")); 98 | errors.checkThat(stringCompressor.compress("a"), equalTo("a")); 99 | errors.checkThat(stringCompressor.compress("aa"), equalTo("aa")); 100 | errors.checkThat(stringCompressor.compress("aaa"), equalTo("a3")); 101 | 102 | errors.checkThat(stringCompressor.compress("aabcccccaaa"), equalTo("a2b1c5a3")); 103 | errors.checkThat(stringCompressor.compress("abc"), equalTo("abc")); 104 | errors.checkThat(stringCompressor.compress("aabbcc"), equalTo("aabbcc")); 105 | } 106 | 107 | @Test 108 | public void testMatrixRotator3x3() { 109 | byte[] pixelA = new byte[] { 1, 2, 3, 4 }; 110 | byte[] pixelB = new byte[] { 5, 6, 7, 8 }; 111 | byte[] pixelC = new byte[] { 9, 0, 1, 2 }; 112 | byte[] pixelD = new byte[] { 3, 4, 5, 6 }; 113 | byte[] pixelE = new byte[] { 7, 8, 9, 0 }; 114 | byte[] pixelF = new byte[] { 10, 11, 12, 13 }; 115 | byte[] pixelG = new byte[] { 14, 15, 16, 17 }; 116 | byte[] pixelH = new byte[] { 18, 19, 20, 21 }; 117 | byte[] pixelI = new byte[] { 22, 23, 24, 25 }; 118 | 119 | // A B C G D A 120 | // D E F --> H E B 121 | // G H I I F C 122 | 123 | byte[][][] input = new byte[][][] { 124 | { pixelA, pixelB, pixelC }, 125 | { pixelD, pixelE, pixelF }, 126 | { pixelG, pixelH, pixelI } 127 | }; 128 | 129 | byte[][][] expected = new byte[][][] { 130 | { copyPixel(pixelG), copyPixel(pixelD), copyPixel(pixelA) }, 131 | { copyPixel(pixelH), copyPixel(pixelE), copyPixel(pixelB) }, 132 | { copyPixel(pixelI), copyPixel(pixelF), copyPixel(pixelC) } 133 | }; 134 | 135 | matrixRotator.rotate(input); 136 | 137 | errors.checkThat(input, equalTo(expected)); 138 | } 139 | 140 | @Test 141 | public void testMatrixRotator2x2() { 142 | byte[] pixelA = new byte[] { 1, 2, 3, 4 }; 143 | byte[] pixelB = new byte[] { 5, 6, 7, 8 }; 144 | byte[] pixelD = new byte[] { 3, 4, 5, 6 }; 145 | byte[] pixelE = new byte[] { 7, 8, 9, 0 }; 146 | 147 | // A B D A 148 | // D E --> E B 149 | 150 | byte[][][] input = new byte[][][] { 151 | { pixelA, pixelB }, 152 | { pixelD, pixelE } 153 | }; 154 | 155 | byte[][][] expected = new byte[][][] { 156 | { copyPixel(pixelD), copyPixel(pixelA) }, 157 | { copyPixel(pixelE), copyPixel(pixelB) } 158 | }; 159 | 160 | matrixRotator.rotate(input); 161 | 162 | errors.checkThat(input, equalTo(expected)); 163 | } 164 | 165 | private static byte[] copyPixel(byte[] pixel) { 166 | return new byte[] { pixel[0], pixel[1], pixel[2], pixel[3] }; 167 | } 168 | 169 | @Test 170 | public void testZeroRowAndColumn3x3() { 171 | // given 172 | byte[][] matrix = new byte[][] { 173 | { 1, 0, 2 }, 174 | { 3, 4, 0 }, 175 | { 5, 6, 7 } 176 | }; 177 | 178 | byte[][] expected = new byte[][] { 179 | { 0, 0, 0 }, 180 | { 0, 0, 0 }, 181 | { 5, 0, 0 } 182 | }; 183 | //when 184 | matrixClearer.clearZeros(matrix); 185 | //then 186 | errors.checkThat(matrix, equalTo(expected)); 187 | } 188 | 189 | @Test 190 | public void testZeroRowAndColumn2x2() { 191 | // given 192 | byte[][] matrix = new byte[][] { 193 | { 1, 0 }, 194 | { 3, 4 } 195 | }; 196 | 197 | byte[][] expected = new byte[][] { 198 | { 0, 0 }, 199 | { 3, 0 } 200 | }; 201 | //when 202 | matrixClearer.clearZeros(matrix); 203 | //then 204 | errors.checkThat(matrix, equalTo(expected)); 205 | } 206 | 207 | @Test 208 | public void testStringRotationChecker() { 209 | errors.checkThat(rotationChecker.isRotation("", ""), is(true)); 210 | errors.checkThat(rotationChecker.isRotation("a", "a"), is(true)); 211 | errors.checkThat(rotationChecker.isRotation("aa", "aa"), is(true)); 212 | errors.checkThat(rotationChecker.isRotation("ab", "ba"), is(true)); 213 | errors.checkThat(rotationChecker.isRotation("abc", "cab"), is(true)); 214 | 215 | errors.checkThat(rotationChecker.isRotation("", "b"), is(false)); 216 | errors.checkThat(rotationChecker.isRotation("a", ""), is(false)); 217 | errors.checkThat(rotationChecker.isRotation("a", "b"), is(false)); 218 | errors.checkThat(rotationChecker.isRotation("a", "ab"), is(false)); 219 | errors.checkThat(rotationChecker.isRotation("aa", "a"), is(false)); 220 | errors.checkThat(rotationChecker.isRotation("ab", "ac"), is(false)); 221 | errors.checkThat(rotationChecker.isRotation("abc", "acb"), is(false)); 222 | errors.checkThat(rotationChecker.isRotation("cab", "bac"), is(false)); 223 | errors.checkThat(rotationChecker.isRotation("abc", "cba"), is(false)); 224 | 225 | 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /test/Chapter17Test.java: -------------------------------------------------------------------------------- 1 | package chapter17; 2 | 3 | import static debug.Debugger.DEBUG; 4 | import static org.hamcrest.Matcher.*; 5 | import static org.hamcrest.MatcherAssert.*; 6 | import static org.hamcrest.core.Is.*; 7 | import static org.hamcrest.core.IsEqual.*; 8 | import static org.hamcrest.core.IsNull.*; 9 | 10 | 11 | import java.lang.*; 12 | import java.util.*; 13 | import java.util.stream.*; 14 | 15 | import org.hamcrest.core.*; 16 | import org.junit.*; 17 | import org.junit.rules.*; 18 | import structures.*; 19 | import test.*; 20 | 21 | public class Chapter17Test extends ChapterTestBase { 22 | 23 | @Test 24 | public void testInlineNumberSwap() { 25 | // given 26 | int[] a = { 1, 2, 5, 8, 17, 32, 65, 128, 255, 512, 1023 }; 27 | int[] b = { 1, 3, 4*3, 9*3, 16*3, 31*3, 64*3, 127*3, 256*3, 511*3, 1022*3 }; 28 | 29 | for(int i = 0; i < a.length; i++) { 30 | PairOfNumbers numbers = new PairOfNumbers(a[i], b[i]); 31 | InlineNumberSwapper swapper = new InlineNumberSwapper(); 32 | // when 33 | swapper.swap(numbers); 34 | // then 35 | areEqual(numbers.first, b[i]); 36 | areEqual(numbers.second, a[i]); 37 | } 38 | } 39 | 40 | @Test 41 | public void testTicTacToeResultDetector() { 42 | checkBoard(new byte[][] { 43 | { 1, 1, 1 }, 44 | { 0, 2, 0 }, 45 | { 2, 0, 2 } 46 | }, 47 | true 48 | ); 49 | 50 | checkBoard(new byte[][] { 51 | { 0, 1, 1 }, 52 | { 2, 1, 0 }, 53 | { 2, 0, 2 } 54 | }, 55 | false 56 | ); 57 | 58 | checkBoard(new byte[][] { 59 | { 0, 1, 2 }, 60 | { 2, 1, 0 }, 61 | { 2, 1, 2 } 62 | }, 63 | true 64 | ); 65 | 66 | checkBoard(new byte[][] { 67 | { 1, 0, 0 }, 68 | { 2, 1, 0 }, 69 | { 2, 2, 1 } 70 | }, 71 | true 72 | ); 73 | 74 | checkBoard(new byte[][] { 75 | { 0, 2, 0 }, 76 | { 2, 1, 0 }, 77 | { 1, 2, 1 } 78 | }, 79 | false 80 | ); 81 | 82 | checkBoard(new byte[][] { 83 | { 0, 2, 1 }, 84 | { 2, 1, 0 }, 85 | { 1, 2, 0 } 86 | }, 87 | true 88 | ); 89 | } 90 | 91 | private void checkBoard(byte[][] board, boolean expected) { 92 | TicTacToeResultDetector detector = new TicTacToeResultDetector(); 93 | 94 | boolean actual = detector.detectWinner(board); 95 | 96 | areEqual(toString(board), actual, expected); 97 | } 98 | 99 | private String toString(byte[][] board) { 100 | StringBuilder sb = new StringBuilder(12); 101 | sb.append("\n"); 102 | 103 | for(int i = 0; i < 3; i++) { 104 | for(int j = 0; j < 3; j++) { 105 | if(board[i][j] == 0) { 106 | sb.append(" "); 107 | } else { 108 | if(board[i][j] == 1) { sb.append("X"); } 109 | if(board[i][j] == 2) { sb.append("O"); } 110 | } 111 | if(j < 2) { sb.append("|"); } 112 | } 113 | 114 | sb.append("\n"); 115 | if(i < 2) { sb.append("-----\n"); } 116 | } 117 | 118 | return sb.toString(); 119 | } 120 | 121 | @Test 122 | public void testTrailingZerosCounter() { 123 | NFactorialTrailingZeros target = new NFactorialTrailingZeros(); 124 | DEBUG.enable(); 125 | for(int n : new int[] 126 | { 1, 4, 5, 6, 9, 10, 11, 14, 15, 16, 19, 20 }) { 127 | long factorial = factorial(n); 128 | areEqual( 129 | format("For number %s (%s)", n, factorial), 130 | target.calculate(n), 131 | countTrailingZeros(factorial)); 132 | } 133 | } 134 | 135 | private int countTrailingZeros(long number) { 136 | String str = number + ""; 137 | int counter = 0; 138 | for(int i = str.length() - 1; i >= 0; i--) { 139 | if(str.charAt(i) == '0') { 140 | counter++; 141 | } else { 142 | break; 143 | } 144 | } 145 | 146 | return counter; 147 | } 148 | 149 | private long factorial(int n) { 150 | long result = 1; 151 | for(long i = 1; i <= n; i++) { 152 | result *= i; 153 | } 154 | 155 | return result; 156 | } 157 | 158 | @Test 159 | public void testLargestSumSequenceFinder() { 160 | checkLargestSumSequenceFinder(new int[] {3, -4, 9, 0, -8, 5, 2 }, 9); 161 | checkLargestSumSequenceFinder(new int[] { -1 }, -1); 162 | checkLargestSumSequenceFinder(new int[] { -1, -2, 0 }, 0); 163 | checkLargestSumSequenceFinder(new int[] { -4, -2, -1, -3 }, -1); 164 | checkLargestSumSequenceFinder(new int[] { 1, 2, 3}, 6); 165 | checkLargestSumSequenceFinder(new int[] { 4, -2, 3}, 5); 166 | checkLargestSumSequenceFinder(new int[] { 1, -5, 4, -2, 3, -2 }, 5); 167 | } 168 | 169 | private void checkLargestSumSequenceFinder(int[] numbers, int expectedMaxSum) { 170 | LargestSumSequenceFinder target = new LargestSumSequenceFinder(); 171 | // when 172 | int actualMaxSum = target.find(numbers); 173 | // then 174 | areEqual(actualMaxSum, expectedMaxSum); 175 | } 176 | 177 | @Test 178 | public void testWordCounter() { 179 | // given 180 | String text = "My mother lives with her mother at her mother's house, with a " + 181 | "motherload of stuff my mother's room, at Mothers City"; 182 | String word = "Mother"; 183 | int expectedCount = 4; 184 | WordCounter target = new WordCounter(); 185 | // when 186 | int actualCount = target.count(text, word); 187 | // then 188 | areEqual(actualCount, expectedCount); 189 | } 190 | 191 | @Test 192 | public void testPairFinder() { 193 | // given 194 | int[] numbers = { 0, 1, 2, 3, 4, 5, 6, 1, 13 }; 195 | PairFinder target = new PairFinder(); 196 | // when 197 | ArrayList pairs = target.findPairs(numbers, 6); 198 | // then 199 | // DEBUG.enable(); 200 | DEBUG.println(pairs); 201 | areEqual(pairs.size(), 4); 202 | 203 | // when 204 | pairs = target.findPairs(numbers, 2); 205 | // then 206 | DEBUG.println(pairs); 207 | areEqual(pairs.size(), 2); 208 | 209 | // when 210 | pairs = target.findPairs(numbers, 3); 211 | // then 212 | DEBUG.println(pairs); 213 | areEqual(pairs.size(), 3); 214 | } 215 | 216 | @Test 217 | public void testBinaryTreeToSortedList() { 218 | // given 219 | BiNode five = binode(5); 220 | BiNode three = binode(3); 221 | BiNode four = binode(4); 222 | BiNode two = binode(2); 223 | BiNode one = binode(1); 224 | BiNode six = binode(6); 225 | BiNode seven = binode(7); 226 | BiNode eight = binode(8); 227 | BiNode nine = binode(9); 228 | 229 | five.first(three); five.second(seven); 230 | 231 | three.first(two); three.second(four); 232 | seven.first(six); seven.second(eight); 233 | 234 | two.first(one); eight.second(nine); 235 | 236 | BinaryTreeToSortedList target = new BinaryTreeToSortedList(); 237 | // when 238 | target.toSortedList(five); 239 | 240 | // then 241 | areEqual(one.second(), two); areEqual(one.first(), null); 242 | areEqual(two.second(), three); areEqual(two.first(), one); 243 | areEqual(three.second(), four); areEqual(three.first(), two); 244 | areEqual(four.second(), five); areEqual(four.first(), three); 245 | areEqual(five.second(), six); areEqual(five.first(), four); 246 | areEqual(six.second(), seven); areEqual(six.first(), five); 247 | areEqual(seven.second(), eight); areEqual(seven.first(), six); 248 | areEqual(eight.second(), nine); areEqual(eight.first(), seven); 249 | areEqual(nine.second(), null); areEqual(nine.first(), eight); 250 | } 251 | 252 | BiNode binode(int value) { 253 | return new BiNode(value); 254 | } 255 | 256 | @Test 257 | public void testSentenceFinder() { 258 | Set words = new HashSet<>(); 259 | words.add("i"); 260 | words.add("am"); 261 | words.add("the"); 262 | words.add("greatest"); 263 | words.add("player"); 264 | words.add("in"); 265 | words.add("world"); 266 | 267 | SentenceFinder finder = new SentenceFinder(words); 268 | DEBUG.disable(); 269 | SingleLinkNode sentence = finder.sentencify("iammarcelthegreatestdotaplayerintheworld"); 270 | 271 | areEqual( 272 | sentence, 273 | link("i", "am", "MARCEL", "the", "greatest", "DOTA", "player", "in", "the", "world") 274 | ); 275 | } 276 | 277 | private static SingleLinkNode link(String ... words) { 278 | SingleLinkNode head = new SingleLinkNode<>(words[0]); 279 | SingleLinkNode tail = head; 280 | 281 | for(int i = 1; i < words.length; i++) { 282 | tail.next(new SingleLinkNode<>(words[i])); 283 | tail = tail.next(); 284 | } 285 | 286 | return head; 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/Chapter2.java: -------------------------------------------------------------------------------- 1 | package chapter2; 2 | 3 | import java.lang.*; 4 | import java.util.*; 5 | 6 | import structures.*; 7 | 8 | class DuplicatesRemover { 9 | public void removeDuplicates(SingleLinkNode head) { 10 | if(head == null) { 11 | return; 12 | } 13 | 14 | if(head.next() == null) { 15 | return; 16 | } 17 | /* 18 | * By using semantically meaningful names, I was able to find the bug 19 | * in this algorithm, by renaming current to lastUnique, I figured out 20 | * that it was being assigned a non-unique node. 21 | */ 22 | SingleLinkNode lastUnique = head; 23 | while(lastUnique != null) { 24 | SingleLinkNode next = lastUnique.next(); 25 | if(lastUnique.next() != null && isLastDuplicate(head, next)) { 26 | lastUnique.next(next.next()); 27 | } else { 28 | lastUnique = next; 29 | } 30 | } 31 | } 32 | 33 | public boolean isLastDuplicate(SingleLinkNode start, SingleLinkNode last) { 34 | SingleLinkNode iterator = start; 35 | do { 36 | if(iterator.value() == null) { 37 | if(last.value() == null) { 38 | return true; 39 | } 40 | } else if(iterator.value().equals(last.value())) { 41 | return true; 42 | } 43 | 44 | iterator = iterator.next(); 45 | } while(iterator != last); 46 | 47 | return false; 48 | } 49 | } 50 | 51 | 52 | class KthElementGetter { 53 | 54 | public SingleLinkNode get(SingleLinkNode head, int index) { 55 | return get(head, index, new Index(-1)); 56 | } 57 | 58 | public SingleLinkNode get(SingleLinkNode current, int index, Index movingIndex) { 59 | if(current != null) { 60 | SingleLinkNode found = get(current.next(), index, movingIndex); 61 | if(index != movingIndex.get()) { 62 | current = found; 63 | } 64 | } 65 | 66 | movingIndex.inc(); 67 | return current; 68 | } 69 | 70 | private static class Index { 71 | private int index; 72 | public Index(int initial) { index = initial; } 73 | public int get() { return index; } 74 | public void inc() { index++; } 75 | } 76 | } 77 | 78 | class MiddleRemover { 79 | public void remove(SingleLinkNode middle) { 80 | SingleLinkNode iterator = middle.next(); 81 | while(iterator.next() != middle) { 82 | iterator = iterator.next(); 83 | } 84 | 85 | iterator.next(middle.next()); 86 | } 87 | } 88 | 89 | class PivotArranger { 90 | public void partition(SingleLinkNode node, Integer pivot) { 91 | if(node == null || pivot == null || node.next() == null) { 92 | return; 93 | } 94 | 95 | SingleLinkNode lessTail = null; 96 | SingleLinkNode greaterHead = null; 97 | SingleLinkNode greaterTail = null; 98 | SingleLinkNode prev = null; 99 | while(node != null) { 100 | SingleLinkNode next = node.next(); 101 | if(node.value().intValue() < pivot.intValue()) { 102 | if(lessTail == null) { 103 | lessTail = node; 104 | prev = node; 105 | } else { 106 | lessTail.next(node); 107 | node.next(null); 108 | if(prev != lessTail) { 109 | prev.next(next); 110 | } else { 111 | prev = node; 112 | } 113 | 114 | lessTail = node; 115 | } 116 | } else if(node.value().intValue() > pivot.intValue()) { 117 | if(greaterHead == null) { 118 | greaterHead = node; 119 | greaterTail = node; 120 | prev = node; 121 | } else { 122 | greaterTail.next(node); 123 | node.next(null); 124 | if(prev != greaterTail) { 125 | prev.next(next); 126 | } else { 127 | prev = node; 128 | } 129 | 130 | greaterTail = node; 131 | } 132 | } else { 133 | prev.next(next); 134 | node.next(greaterHead); 135 | greaterHead = node; 136 | } 137 | 138 | node = next; 139 | } 140 | 141 | if(lessTail != null) { 142 | lessTail.next(greaterHead); 143 | } 144 | } 145 | } 146 | 147 | class LinkListNumberCalculator { 148 | public SingleLinkNode sum( 149 | SingleLinkNode first, 150 | SingleLinkNode second) { 151 | if(first == null && second == null) { return new SingleLinkNode(0); } 152 | if(first == null && second != null) { return first; } 153 | if(first == null && second != null) { return second; } 154 | 155 | SingleLinkNode head = null; 156 | SingleLinkNode tail = null; 157 | int add = 0; 158 | while(first != null || second != null) { 159 | int firstInt = first != null ? first.value() : 0; 160 | int secondInt = second != null ? second.value() : 0; 161 | 162 | int resultInt = firstInt + secondInt + add; 163 | if(resultInt >= 10) { 164 | add = 1; 165 | resultInt = resultInt - 10; 166 | } else { 167 | add = 0; 168 | } 169 | 170 | SingleLinkNode result = new SingleLinkNode<>(resultInt); 171 | if(head == null) { 172 | tail = head = result; 173 | } else { 174 | tail.next(result); 175 | tail = result; 176 | } 177 | 178 | first = first != null ? first.next() : null; 179 | second = second != null ? second.next() : null; 180 | } 181 | 182 | if(add > 0) { 183 | tail.next(new SingleLinkNode<>(1)); 184 | } 185 | 186 | return head; 187 | } 188 | 189 | public SingleLinkNode sumReverse( 190 | SingleLinkNode first, 191 | SingleLinkNode second) { 192 | if(first == null && second == null) { return new SingleLinkNode(0); } 193 | if(first == null && second != null) { return first; } 194 | if(first == null && second != null) { return second; } 195 | 196 | return reverse(sum(reverse(first), reverse(second))); 197 | } 198 | 199 | private SingleLinkNode reverse(SingleLinkNode head) { 200 | // 1 -> 2 -> 3 -> N 201 | // 2 -> 1 -> 3 -> N 202 | // 3 -> 2 -> 1 -> N 203 | SingleLinkNode iter = head; 204 | while(iter.next() != null) { 205 | SingleLinkNode next = iter.next(); 206 | iter.next(next.next()); 207 | next.next(head); 208 | head = next; 209 | } 210 | 211 | return head; 212 | } 213 | } 214 | 215 | class CircularListHeadFinder { 216 | // I solved this one without really understanding WHY we needed 217 | // a fast runner and a slow runner 218 | public SingleLinkNode findHead(SingleLinkNode node) { 219 | if(node == null || node.next() == null || node.next() == node) { 220 | return node; 221 | } 222 | 223 | SingleLinkNode head = node; 224 | SingleLinkNode runner = node; 225 | do { 226 | if(node.next() == null || runner.next() == null || runner.next().next() == null) { 227 | throw new RuntimeException("No nulls allowed"); 228 | } 229 | node = node.next(); 230 | runner = runner.next().next(); 231 | } while(runner != node); 232 | 233 | while(head != runner) { 234 | head = head.next(); 235 | runner = runner.next(); 236 | } 237 | 238 | return runner; 239 | } 240 | } 241 | 242 | class PalindromeIdentifier { 243 | // Assume it is not circular and is single link 244 | // Asssume spaces are important, meaning they are treated like 245 | // any other character, it must match no matter what. 246 | // null: true 247 | // A : true 248 | // A -> A : true 249 | // A -> B : false 250 | // A -> B -> A : true 251 | // A -> B -> A -> B : false 252 | // A -> B -> A -> B -> A : true 253 | 254 | public boolean isPalindrome(SingleLinkNode head) { 255 | if(head == null || head.next() == null) { return true; } 256 | return isPalindrome(head, head).matched; 257 | } 258 | 259 | private RecursionResult isPalindrome( 260 | SingleLinkNode head, SingleLinkNode current) { 261 | if(current.next() == null) { 262 | return new RecursionResult( 263 | equals(current.value(), head.value()), 264 | head.next() 265 | ); 266 | } else { 267 | RecursionResult recursion = isPalindrome(head, current.next()); 268 | recursion.matched = recursion.matched && equals(current.value(), recursion.node.value()); 269 | recursion.node = recursion.node.next(); 270 | return recursion; 271 | } 272 | } 273 | 274 | private static boolean equals(Object a, Object b) { 275 | if(a == null || b == null) { 276 | return a == b; 277 | } 278 | 279 | return a.equals(b); 280 | } 281 | 282 | private static class RecursionResult { 283 | private boolean matched; 284 | private SingleLinkNode node; 285 | public RecursionResult(boolean matched, SingleLinkNode node) { 286 | this.matched = matched; this.node = node; 287 | } 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /test/Chapter3Test.java: -------------------------------------------------------------------------------- 1 | package chapter3; 2 | 3 | import static debug.Debugger.DEBUG; 4 | import static org.hamcrest.Matcher.*; 5 | import static org.hamcrest.MatcherAssert.*; 6 | import static org.hamcrest.core.Is.*; 7 | import static org.hamcrest.core.IsEqual.*; 8 | import static org.hamcrest.core.IsNull.*; 9 | 10 | 11 | import java.lang.*; 12 | import java.util.*; 13 | import java.util.stream.*; 14 | 15 | import org.hamcrest.core.*; 16 | import org.junit.*; 17 | import org.junit.rules.*; 18 | import structures.*; 19 | import test.*; 20 | 21 | public class Chapter3Test extends ChapterTestBase { 22 | 23 | private CoordinatedStackMaker coordinatedStackMaker = new CoordinatedStackMaker(); 24 | 25 | @Test 26 | public void testThreeStacksWithSingleArray() { 27 | // given an array that can have 3 stacks of 10 elements in it. 28 | Object[] data = new Object[3*10]; 29 | // when 30 | CoordinatedArrayStack[] stacks = coordinatedStackMaker.makeStacks(data, 3); 31 | // then 32 | isTrueThat(stacks.length, equalTo(3)); 33 | isFalseThat(stacks[0], nullValue()); 34 | isFalseThat(stacks[1], nullValue()); 35 | isFalseThat(stacks[2], nullValue()); 36 | 37 | checkCanUse(stacks, 10); 38 | } 39 | 40 | private void checkCanUse(CoordinatedArrayStack[] stacks, int times) { 41 | for(int i = 0; i < times; i++) { 42 | for(int j = 0; j < stacks.length; j++) { 43 | stacks[j].push(i + "" + j); 44 | isTrueThat(stacks[j].peek(), equalTo(i + "" + j)); 45 | } 46 | } 47 | 48 | for(int i = times - 1; i >= 0; i--) { 49 | for(int j = stacks.length - 1; j >= 0; j--) { 50 | int stackIndex = stacks.length - j - 1; 51 | isTrueThat(stacks[stackIndex].peek(), equalTo(i + "" + stackIndex)); 52 | isTrueThat(stacks[stackIndex].pop(), equalTo(i + "" + stackIndex)); 53 | } 54 | } 55 | 56 | for(int i = 0; i < stacks.length; i++) { 57 | isTrueThat(stacks[i].size(), equalTo(0)); 58 | } 59 | } 60 | 61 | @Test 62 | public void testTrackStackMin() { 63 | // given 64 | MinTrackingStack stack = new MinTrackingStack(); 65 | // when 66 | stack.push(5); 67 | isTrueThat(stack.min(), equalTo(5)); 68 | stack.push(4); 69 | isTrueThat(stack.min(), equalTo(4)); 70 | stack.push(4); 71 | isTrueThat(stack.min(), equalTo(4)); 72 | stack.push(7); 73 | isTrueThat(stack.min(), equalTo(4)); 74 | stack.push(4); 75 | isTrueThat(stack.min(), equalTo(4)); 76 | stack.push(8); 77 | isTrueThat(stack.min(), equalTo(4)); 78 | stack.push(6); 79 | isTrueThat(stack.min(), equalTo(4)); 80 | stack.push(2); 81 | isTrueThat(stack.min(), equalTo(2)); 82 | stack.push(3); 83 | isTrueThat(stack.min(), equalTo(2)); 84 | stack.push(1); 85 | isTrueThat(stack.min(), equalTo(1)); 86 | 87 | isTrueThat(stack.pop(), equalTo(1)); 88 | isTrueThat(stack.min(), equalTo(2)); 89 | 90 | isTrueThat(stack.pop(), equalTo(3)); 91 | isTrueThat(stack.min(), equalTo(2)); 92 | 93 | isTrueThat(stack.pop(), equalTo(2)); 94 | isTrueThat(stack.min(), equalTo(4)); 95 | 96 | isTrueThat(stack.pop(), equalTo(6)); 97 | isTrueThat(stack.min(), equalTo(4)); 98 | 99 | isTrueThat(stack.pop(), equalTo(8)); 100 | isTrueThat(stack.min(), equalTo(4)); 101 | 102 | isTrueThat(stack.pop(), equalTo(4)); 103 | isTrueThat(stack.min(), equalTo(4)); 104 | 105 | isTrueThat(stack.pop(), equalTo(7)); 106 | isTrueThat(stack.min(), equalTo(4)); 107 | 108 | isTrueThat(stack.pop(), equalTo(4)); 109 | isTrueThat(stack.min(), equalTo(4)); 110 | 111 | isTrueThat(stack.pop(), equalTo(4)); 112 | isTrueThat(stack.min(), equalTo(5)); 113 | 114 | isTrueThat(stack.pop(), equalTo(5)); 115 | } 116 | 117 | @Test 118 | public void testSetOfStacks() { 119 | // given 120 | SetOfStacks stack = new SetOfStacks<>(3); 121 | // when [1] 122 | stack.push(1); 123 | areEqual(stack.stackCount(), 1); 124 | areEqual(stack.size(), 1); 125 | areEqual(stack.get(0), 1); 126 | 127 | // when [1 2] 128 | stack.push(2); 129 | // then 130 | areEqual(stack.get(1), 2); 131 | 132 | // when [1 2 3] 133 | stack.push(3); 134 | // then 135 | areEqual(stack.stackCount(), 1); 136 | areEqual(stack.size(), 3); 137 | areEqual(stack.get(1), 2); 138 | areEqual(stack.get(2), 3); 139 | 140 | // when [1 2 3][4] 141 | stack.push(4); 142 | // then 143 | areEqual(stack.stackCount(), 2); 144 | areEqual(stack.size(), 4); 145 | areEqual(stack.get(1), 2); 146 | areEqual(stack.get(2), 3); 147 | areEqual(stack.get(3), 4); 148 | 149 | // when [1 2 3] -> 4 150 | areEqual(stack.pop(), 4); 151 | // then 152 | areEqual(stack.stackCount(), 1); 153 | areEqual(stack.size(), 3); 154 | areEqual(stack.get(1), 2); 155 | areEqual(stack.get(2), 3); 156 | 157 | // when [1 2 3][4 5 6][7] 158 | stack.push(4); stack.push(5); stack.push(6); stack.push(7); 159 | // then 160 | areEqual(stack.get(1), 2); 161 | areEqual(stack.get(2), 3); 162 | areEqual(stack.get(3), 4); 163 | areEqual(stack.get(4), 5); 164 | areEqual(stack.get(5), 6); 165 | areEqual(stack.get(6), 7); 166 | 167 | // when [] -> 1 2 3 4 5 6 7 168 | stack.pop();stack.pop();stack.pop();stack.pop();stack.pop(); 169 | stack.pop();stack.pop(); 170 | areEqual(stack.size(), 0); 171 | areEqual(stack.stackCount(), 0); 172 | } 173 | 174 | @Test 175 | public void testTowersOfHanoiSolver() { 176 | // given 177 | int[][] expectedMoves = { 178 | { 0, 2 }, // [2,3] [] [1] 179 | { 0, 1 }, // [3] [2] [1] 180 | { 2, 1 }, // [3] [1 2] [] 181 | { 0, 2 }, // [] [1 2] [3] 182 | { 1, 0 }, // [1] [2] [3] 183 | { 1, 2 }, // [1] [] [2 3] 184 | { 0, 2 } // [] [] [1 2 3] 185 | }; 186 | TowersOfHanoiListener verifier = new TowersOfHanoiListener() { 187 | public void onMove(int moveIndex, LinkedListStack[] poles, int from, int to) { 188 | areEqual(expectedMoves[moveIndex][0], from); 189 | areEqual(expectedMoves[moveIndex][1], to); 190 | } 191 | }; 192 | TowersOfHanoiSolver target = new TowersOfHanoiSolverStub(verifier); 193 | // when 194 | target.solve(3); 195 | // then 196 | } 197 | 198 | private static class TowersOfHanoiSolverStub extends TowersOfHanoiSolver { 199 | private static int moveIndex = 0; 200 | 201 | private TowersOfHanoiListener listener; 202 | 203 | public TowersOfHanoiSolverStub(TowersOfHanoiListener listener) { 204 | this.listener = listener; 205 | } 206 | 207 | @Override 208 | int moveDisc(LinkedListStack[] poles, int from, int to) { 209 | listener.onMove(moveIndex++, poles, from, to); 210 | return super.moveDisc(poles, from, to); 211 | } 212 | } 213 | 214 | private static interface TowersOfHanoiListener { 215 | void onMove(int moveIndex, LinkedListStack[] poles, int from, int to); 216 | } 217 | 218 | @Test 219 | public void testQueueMadeOfStacks() { 220 | // given 221 | QueueMadeOfStacks queue = new QueueMadeOfStacks(); 222 | // when 223 | queue.queue(1); 224 | queue.queue(2); 225 | queue.queue(3); 226 | 227 | // then 228 | areEqual(queue.size(), 3); 229 | areEqual(queue.peek(), 1); 230 | areEqual(queue.dequeue(), 1); 231 | 232 | // when 233 | queue.queue(4); 234 | // then 235 | areEqual(queue.size(), 3); 236 | areEqual(queue.peek(), 2); 237 | areEqual(queue.dequeue(), 2); 238 | areEqual(queue.size(), 2); 239 | areEqual(queue.peek(), 3); 240 | areEqual(queue.dequeue(), 3); 241 | areEqual(queue.size(), 1); 242 | 243 | } 244 | 245 | @Test 246 | public void testStackSorter() { 247 | // given 248 | StackSorter sorter = new StackSorter(); 249 | LinkedListStack stack = new LinkedListStack<>(); 250 | int[] input = { 3, 5, 2, 1, 8, 9, 4 }; 251 | pushAll(stack, input); 252 | int[] expected = { 9, 8, 5, 4, 3, 2, 1 }; 253 | 254 | // when 255 | sorter.sort(stack); 256 | // then 257 | int[] actual = popAll(stack); 258 | areEqual(actual, expected); 259 | } 260 | 261 | @Test 262 | public void testPivot() { 263 | // given 264 | LinkedListStack lo = new LinkedListStack<>(); 265 | pushAll(lo, 9, 1, 2, 8, 7, 3, 4, 6); 266 | LinkedListStack hi = new LinkedListStack<>(); 267 | pushAll(hi, 19, 18, 11, 12, 17, 16, 13, 14); 268 | 269 | StackSorter sorter = new StackSorter(); 270 | // when 271 | int[] sizes = sorter.partition(5, lo, hi, -1, "ASC"); 272 | // then 273 | int[] expectedLo = { 4, 3, 2, 1 }; 274 | int[] expectedHi = { 9, 8, 7, 6, 14, 13, 16, 17, 12, 11, 18, 19 }; 275 | areEqual(popAll(lo), expectedLo); 276 | areEqual(popAll(hi), expectedHi); 277 | areEqual(sizes[0], 4); 278 | areEqual(sizes[1], 4); 279 | } 280 | 281 | @Test 282 | public void testPivotSubset() { 283 | // given 284 | LinkedListStack lo = new LinkedListStack<>(); 285 | pushAll(lo, 9, 1, 2, 8, 7, 3, 4, 6); 286 | LinkedListStack hi = new LinkedListStack<>(); 287 | 288 | StackSorter sorter = new StackSorter(); 289 | // when 290 | int[] sizes = sorter.partition(5, lo, hi, 5, "ASC"); 291 | // then 292 | int[] expectedLo = { 4, 3, 2, 1, 9 }; 293 | int[] expectedHi = { 8, 7, 6 }; 294 | areEqual(popAll(lo), expectedLo); 295 | areEqual(popAll(hi), expectedHi); 296 | areEqual(sizes[0], 2); 297 | areEqual(sizes[1], 3); 298 | } 299 | 300 | @Test 301 | public void testAnimalShelter() { 302 | // given 303 | AnimalShelter shelter = new AnimalShelter(); 304 | // when 305 | shelter.enqueue(cat()); 306 | shelter.enqueue(cat()); 307 | shelter.enqueue(dog()); 308 | shelter.enqueue(cat()); 309 | shelter.enqueue(dog()); 310 | shelter.enqueue(dog()); 311 | 312 | // then 313 | AnimalShelter.Animal animal = shelter.dequeue().get(); 314 | areEqual(animal.age(), 1); 315 | areEqual(animal.type(), "cat"); 316 | 317 | animal = shelter.dequeueDog().get(); 318 | areEqual(animal.age(), 3); 319 | areEqual(animal.type(), "dog"); 320 | 321 | animal = shelter.dequeue().get(); 322 | areEqual(animal.age(), 2); 323 | areEqual(animal.type(), "cat"); 324 | 325 | // when 326 | shelter.enqueue(cat()); 327 | 328 | // then 329 | animal = shelter.dequeueCat().get(); 330 | areEqual(animal.age(), 4); 331 | areEqual(animal.type(), "cat"); 332 | 333 | animal = shelter.dequeueCat().get(); 334 | areEqual(animal.age(), 7); 335 | areEqual(animal.type(), "cat"); 336 | 337 | animal = shelter.dequeue().get(); 338 | areEqual(animal.age(), 5); 339 | areEqual(animal.type(), "dog"); 340 | 341 | animal = shelter.dequeue().get(); 342 | areEqual(animal.age(), 6); 343 | areEqual(animal.type(), "dog"); 344 | } 345 | 346 | private static AnimalShelter.Animal cat() { 347 | return new AnimalShelter.Animal("cat"); 348 | } 349 | 350 | private static AnimalShelter.Animal dog() { 351 | return new AnimalShelter.Animal("dog"); 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /src/Chapter4.java: -------------------------------------------------------------------------------- 1 | package chapter4; 2 | 3 | import static debug.Debugger.DEBUG; 4 | 5 | import java.lang.*; 6 | import java.util.*; 7 | 8 | import structures.*; 9 | 10 | class BinaryTreeBalanceChecker { 11 | 12 | public boolean check(BinaryTreeNode root) { 13 | return checkBalance(root, 0).isBalanced(); 14 | } 15 | 16 | private Balance checkBalance(BinaryTreeNode node, int parentHeight) { 17 | if(node == null) { return new Balance(parentHeight, true); } 18 | 19 | Balance leftBalance = checkBalance(node.left(), parentHeight + 1); 20 | if(!leftBalance.isBalanced()) { 21 | return leftBalance; 22 | } 23 | 24 | Balance rightBalance = checkBalance(node.right(), parentHeight + 1); 25 | if(!rightBalance.isBalanced()) { 26 | return rightBalance; 27 | } 28 | 29 | boolean isBalanced = Math.abs(rightBalance.height() - leftBalance.height()) <= 1; 30 | int height = Math.max(rightBalance.height(), leftBalance.height()); 31 | 32 | return new Balance(height, isBalanced); 33 | } 34 | 35 | public static class Balance { 36 | private final int height; 37 | private final boolean isBalanced; 38 | 39 | public Balance(int height, boolean isBalanced) { 40 | this.height = height; 41 | this.isBalanced = isBalanced; 42 | } 43 | 44 | public int height() { return this.height; } 45 | public boolean isBalanced() { return this.isBalanced; } 46 | } 47 | } 48 | 49 | 50 | class PathChecker { 51 | 52 | // Time Complexity: O(N), Space Complexity: O(N) 53 | public boolean pathExists(DirectedNode nodeA, DirectedNode nodeB) { 54 | if(nodeA == nodeB) { return true; } 55 | if(nodeA == null || nodeB == null) { return false; } 56 | 57 | LinkedListStack visitingA = new LinkedListStack<>(); 58 | HashSet visitedA = new HashSet<>(); 59 | LinkedListStack visitingB = new LinkedListStack<>(); 60 | HashSet visitedB = new HashSet<>(); 61 | 62 | visitingA.push(nodeA); 63 | visitedA.add(nodeA); 64 | visitingB.push(nodeB); 65 | visitedB.add(nodeB); 66 | 67 | while(visitingA.size() != 0 && visitingB.size() != 0) { 68 | if(visitLinks(visitingA, visitedA, visitedB)) { 69 | return true; 70 | } 71 | 72 | if(visitLinks(visitingB, visitedB, visitedA)) { 73 | return true; 74 | } 75 | } 76 | 77 | return false; 78 | } 79 | 80 | @SuppressWarnings({"unchecked"}) 81 | public boolean visitLinks( 82 | LinkedListStack visiting, 83 | HashSet visited, 84 | HashSet destinations) { 85 | 86 | DirectedNode node = visiting.pop(); 87 | for(DirectedNode link : (Iterable) node.links()) { 88 | if(destinations.contains(link)) { 89 | return true; 90 | } 91 | 92 | if(visited.contains(link)) { 93 | continue; 94 | } 95 | 96 | visiting.push(link); 97 | } 98 | 99 | return false; 100 | } 101 | } 102 | 103 | 104 | // 1,2,3,4,5,6,7,8,9, length = 9 105 | // Basically conver this into it binary search array. 106 | // Should create: 107 | // 5 index = lenght/2 = 9/2 = 4 108 | // 3 7 { 3 = 9 - 1 = 8 / 2 = 2 109 | //2 4 6 8 110 | // 9 111 | class TreeFromSortedNumbersCreator { 112 | public BinaryTreeNode create(int ... numbers) { 113 | if(numbers == null || numbers.length == 0) { 114 | return null; 115 | } 116 | 117 | return create(numbers, 0, numbers.length); 118 | } 119 | 120 | public BinaryTreeNode create(int[] numbers, int start, int length) { 121 | if(length == 0) { return null; } 122 | 123 | int rootIndex = start + (length / 2); 124 | int remainingLength = length - 1; 125 | BinaryTreeNode root = new BinaryTreeNode(numbers[rootIndex]); 126 | 127 | int leftStart = start; 128 | int leftLength = rootIndex - start; 129 | root.left(create(numbers, leftStart, leftLength)); 130 | 131 | int rightStart = rootIndex + 1; 132 | int rightLength = remainingLength - leftLength; 133 | root.right(create(numbers, rightStart, rightLength)); 134 | 135 | return root; 136 | } 137 | } 138 | 139 | class ListsFromTreeLevelsCreator { 140 | public structures.LinkedList create(BinaryTreeNode tree) { 141 | structures.LinkedList lists = new structures.LinkedList<>(); 142 | LinkedListStack currentLevel = new LinkedListStack<>(); 143 | LinkedListStack nextLevel = new LinkedListStack<>(); 144 | 145 | nextLevel.push(tree); 146 | 147 | do { 148 | LinkedListStack temp = currentLevel; 149 | currentLevel = nextLevel; 150 | nextLevel = temp; 151 | 152 | lists.add(new structures.LinkedList()); 153 | while(!currentLevel.isEmpty()) { 154 | BinaryTreeNode node = currentLevel.pop(); 155 | lists.last().add(node.value()); 156 | if(node.left() != null) { nextLevel.push(node.left()); } 157 | if(node.right() != null) { nextLevel.push(node.right()); } 158 | } 159 | } while(!nextLevel.isEmpty()); 160 | 161 | return lists; 162 | } 163 | } 164 | 165 | class BinarySearchTreeChecker { 166 | public boolean check(BinaryTreeNode tree) { 167 | if(tree == null) { throw new RuntimeException("tree cannot be null"); } 168 | return check(tree, Integer.MIN_VALUE, Integer.MAX_VALUE); 169 | } 170 | 171 | private boolean check(BinaryTreeNode tree, int min, int max) { 172 | if(tree == null) { return true; } 173 | 174 | if(tree.value() <= min || tree.value() >= max) { return false; } 175 | 176 | return check(tree.left(), min, tree.value()) && 177 | check(tree.right(), tree.value(), max); 178 | } 179 | } 180 | 181 | class NextInOrderNodeFinder { 182 | public BinaryTreeNode find(BinaryTreeNode node) { 183 | if(node == null) { throw new RuntimeException("node can't be null"); } 184 | 185 | if(node.right() == null) { 186 | return firstLeftAncestor(node); 187 | } 188 | 189 | return leftMostLeaf(node.right()); 190 | } 191 | 192 | private BinaryTreeNode firstLeftAncestor(BinaryTreeNode node) { 193 | if(node.parent() == null) { return null; } 194 | 195 | if(node.parent().left() == node) { return node.parent(); } 196 | 197 | return firstLeftAncestor(node.parent()); 198 | } 199 | 200 | private BinaryTreeNode leftMostLeaf(BinaryTreeNode node) { 201 | if(node.left() == null) { 202 | if(node.right() == null) { 203 | return node; 204 | } else { 205 | return leftMostLeaf(node.right()); 206 | } 207 | } 208 | 209 | return leftMostLeaf(node.left()); 210 | } 211 | } 212 | 213 | class HashedCommonAncestorFinder implements CommonAncestorFinder { 214 | public BinaryTreeNode find(BinaryTreeNode node, BinaryTreeNode other) { 215 | if(node == null || other == null) { 216 | throw new RuntimeException("node and other cannot be null."); 217 | } 218 | 219 | if(node == other) { return node; } 220 | 221 | HashSet nodeAncestors = new HashSet(); 222 | nodeAncestors.add(node); 223 | HashSet otherAncestors = new HashSet(); 224 | otherAncestors.add(other); 225 | 226 | while(node != null || other != null) { 227 | if(node != null) { 228 | if(node.parent() != null) { 229 | if(otherAncestors.contains(node.parent())) { 230 | return node.parent(); 231 | } 232 | 233 | nodeAncestors.add(node.parent()); 234 | } 235 | 236 | node = node.parent(); 237 | } 238 | 239 | if(other != null) { 240 | if(other.parent() != null) { 241 | if(nodeAncestors.contains(other.parent())) { 242 | return other.parent(); 243 | } 244 | 245 | otherAncestors.add(other.parent()); 246 | } 247 | 248 | other = other.parent(); 249 | } 250 | } 251 | 252 | // no common ancestor found 253 | return null; 254 | } 255 | } 256 | 257 | class TraversedCommonAncestorFinder implements CommonAncestorFinder { 258 | public BinaryTreeNode find(BinaryTreeNode node, BinaryTreeNode other) { 259 | while(node != null) { 260 | BinaryTreeNode current = other; 261 | while(current != null) { 262 | if(node == current) { 263 | return node; 264 | } 265 | current = current.parent(); 266 | } 267 | 268 | node = node.parent(); 269 | } 270 | 271 | return null; 272 | } 273 | } 274 | 275 | interface CommonAncestorFinder { 276 | public BinaryTreeNode find(BinaryTreeNode node, BinaryTreeNode other); 277 | } 278 | 279 | class SubtreeChecker { 280 | public boolean check(BinaryTreeNode tree, BinaryTreeNode subtree) { 281 | return check(tree, subtree, height(subtree)) == -1; 282 | } 283 | 284 | private int height(BinaryTreeNode tree) { 285 | if(tree == null) { return 0; } 286 | 287 | return Math.max(height(tree.left()), height(tree.right())) + 1; 288 | } 289 | 290 | private int check(BinaryTreeNode tree, BinaryTreeNode subtree, int subtreeHeight) { 291 | if(tree == null) { return 0; } 292 | 293 | int leftHeight = check(tree.left(), subtree, subtreeHeight); 294 | int rightHeight = check(tree.right(), subtree, subtreeHeight); 295 | if(leftHeight == -1 || rightHeight == -1) { 296 | return -1; 297 | } 298 | 299 | int height = Math.max(leftHeight, rightHeight) + 1; 300 | if(height == subtreeHeight && equals(tree, subtree)) { 301 | return -1; 302 | } 303 | 304 | return height; 305 | } 306 | 307 | private boolean isLeaf(BinaryTreeNode tree) { 308 | return tree.left() == null && tree.right() == null; 309 | } 310 | 311 | // O(n) -> number of nodes in tree 312 | private boolean equals(BinaryTreeNode tree, BinaryTreeNode other) { 313 | if(tree == null || other == null) { return other == null; } 314 | return tree.equals(other); 315 | } 316 | } 317 | 318 | 319 | class BinaryTreeSumFinder { 320 | 321 | public List find(BinaryTreeNode tree, int sum) { 322 | List results = new ArrayList<>(); 323 | find(results, null, tree, sum); 324 | 325 | return results; 326 | } 327 | 328 | private void find( 329 | List results, 330 | SingleLinkNode path, 331 | BinaryTreeNode tree, 332 | int sum) { 333 | if(tree == null) { return; } 334 | 335 | SingleLinkNode newPath = new SingleLinkNode<>(tree.value()); 336 | newPath.next(path); 337 | 338 | SingleLinkNode pathNode = newPath; 339 | SingleLinkNode sumPath = new SingleLinkNode(pathNode.value()); 340 | SingleLinkNode sumPathHead = sumPath; 341 | 342 | int pathSum = 0; 343 | while(pathNode != null) { 344 | pathSum += pathNode.value(); 345 | if(pathSum == sum) { 346 | results.add(sumPathHead.toString()); 347 | } 348 | pathNode = pathNode.next(); 349 | if(pathNode != null) { 350 | sumPath.next(new SingleLinkNode(pathNode.value())); 351 | sumPath = sumPath.next(); 352 | } 353 | } 354 | 355 | find(results, newPath, tree.left(), sum); 356 | find(results, newPath, tree.right(), sum); 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /src/Chapter1.java: -------------------------------------------------------------------------------- 1 | package chapter1; 2 | 3 | import java.lang.*; 4 | import java.util.*; 5 | 6 | class UniqueCharsIdentifier { 7 | // Cometi el error de no preguntarme sobre el dominio del input 8 | // hay un numero limitado de caracteres, por lo tanto, pude haber 9 | // mantenido un simple array donde si ya me encontre un dado caracter 10 | // le pongo true, y si me encuentro otro con true, cancelo el algoritmo 11 | // y regreso false 12 | public boolean hasUniqueChars(String str) { 13 | if(str == null) { 14 | throw new RuntimeException("str is null"); 15 | } 16 | 17 | if(str.length() == 0) { 18 | return true; 19 | } 20 | 21 | if(str.length() > 256) { // Assuming we only allow for the 256 ASCII characters 22 | return false; 23 | } 24 | 25 | for(int i = 0; i < str.length() - 1; i++) { 26 | char current = str.charAt(i); 27 | for(int j = i+1; j < str.length(); j++) { 28 | char compared = str.charAt(j); 29 | if(current == compared) { 30 | return false; 31 | } 32 | } 33 | } 34 | 35 | return true; 36 | } 37 | } 38 | 39 | 40 | class StringReverser { 41 | public String reverse(String input) { 42 | if(input == null || input.length() == 0) { return input; } 43 | char[] reversedChars = new char[input.length()]; 44 | int forwards = 0; 45 | int backwards = input.length() - 1; 46 | do { 47 | reversedChars[forwards] = input.charAt(backwards); 48 | reversedChars[backwards] = input.charAt(forwards); 49 | } while (++forwards <= --backwards); 50 | 51 | return new String(reversedChars); 52 | } 53 | } 54 | 55 | class StringPermutationChecker { 56 | // runtime: O(a.length() + b.length() + b.length()) 57 | // memory: O(a.length() + b.length()) 58 | public boolean check(String a, String b) { 59 | if(a == null || b == null) { 60 | return a == b; 61 | } 62 | 63 | if(a.length() != b.length()) { 64 | return false; 65 | } 66 | 67 | if(a.length() == 0) { 68 | return b.length() == 0; 69 | } 70 | 71 | // runtime: O(a.length()) 72 | // memory: O(a.length()) 73 | HashMap charMapA = buildCharMap(a); 74 | // runtime: O(b.length()) 75 | // memory: O(a.length()) 76 | HashMap charMapB = buildCharMap(b); 77 | // runtime: O(a.length()) 78 | for(Map.Entry entry : charMapA.entrySet()) { 79 | if(!entry.getValue().equals(charMapB.get(entry.getKey()))) { 80 | return false; 81 | } 82 | } 83 | 84 | return true; 85 | } 86 | 87 | // runtime: O(str.length()) 88 | // memory: O(str.length()) 89 | public HashMap buildCharMap(String str) { 90 | HashMap charMap = new HashMap(); 91 | for(int i = 0; i < str.length(); i++) { 92 | Integer count = charMap.get(str.charAt(i)); 93 | if(count == null) { 94 | count = 1; 95 | } else { 96 | count = count + 1; 97 | } 98 | 99 | charMap.put(str.charAt(i), count); 100 | } 101 | 102 | return charMap; 103 | } 104 | } 105 | 106 | class SpaceReplacer { 107 | 108 | private static final int SHIFT_BY = 2; 109 | 110 | public void replace(char[] input, int length) { 111 | if(length > input.length) { 112 | throw new RuntimeException("length is greater than input.length"); 113 | } 114 | 115 | if(length == 0) { 116 | return; 117 | } 118 | 119 | // this guy should create the new indices based on found spaces 120 | int[] shiftedIndices = buildShiftedIndices(input, length); 121 | // move characters from right to left 122 | shiftCharacters(input, length, shiftedIndices); 123 | } 124 | 125 | private int[] buildShiftedIndices(char[] input, int length) { 126 | int[] shiftedIndices = new int[length]; 127 | int spaceCount = 0; 128 | for(int i = 0; i < length; i++) { 129 | shiftedIndices[i] = i + (spaceCount * SHIFT_BY); 130 | if(input[i] == ' ') { 131 | spaceCount++; 132 | } 133 | } 134 | 135 | return shiftedIndices; 136 | } 137 | 138 | private void shiftCharacters(char[] input, int length, int[] shiftedIndices) { 139 | for(int i = length - 1; i >= 0; i--) { 140 | if(input[i] == ' ') { 141 | input[shiftedIndices[i]] = '%'; 142 | input[shiftedIndices[i]+1] = '2'; 143 | input[shiftedIndices[i]+2] = '0'; 144 | } else { 145 | input[shiftedIndices[i]] = input[i]; 146 | } 147 | } 148 | } 149 | } 150 | 151 | class StringCompressor { 152 | public String compress(String input) { 153 | if(input == null) { 154 | throw new RuntimeException("input is null."); 155 | } 156 | 157 | if(input.length() < 3) { 158 | return input; 159 | } 160 | 161 | // Assuming that sb implements growing table algorithm 162 | StringBuilder sb = new StringBuilder(input.length() / 2); 163 | int currentCount = 1; 164 | char current = input.charAt(0); 165 | sb.append(current); 166 | for(int i = 1; i < input.length(); i++) { 167 | if (input.charAt(i) == current) { 168 | currentCount++; 169 | } else { 170 | // makes sure that compression is always better than raw string 171 | sb.append(currentCount); 172 | currentCount = 1; 173 | current = input.charAt(i); 174 | sb.append(current); 175 | } 176 | } 177 | 178 | sb.append(currentCount); 179 | 180 | String compressed = sb.toString(); 181 | if(compressed.length() >= input.length()) { 182 | return input; 183 | } else { 184 | return compressed; 185 | } 186 | } 187 | } 188 | 189 | class Matrix90DegreeRotator { 190 | public void rotate(byte[][][] pixels) { 191 | int rowsToRotate = (int) Math.ceil(pixels.length / 2.0); 192 | boolean isOdd = (pixels.length & 0x1) == 0x1; 193 | int colsToRotate = isOdd ? rowsToRotate - 1 : rowsToRotate; 194 | 195 | for(int i = 0; i < rowsToRotate; i++) { 196 | for(int j = 0; j < colsToRotate; j++) { 197 | rotatePixel(pixels, i, j); 198 | } 199 | } 200 | } 201 | 202 | private void rotatePixel(byte[][][] pixels, int x, int y) { 203 | byte[] pixel1 = pixels[x][y]; 204 | Point pixel1Pos = rotate(x, y, pixels.length); 205 | 206 | byte[] pixel2 = pixels[pixel1Pos.x][pixel1Pos.y]; 207 | Point pixel2Pos = rotate(pixel1Pos, pixels.length); 208 | 209 | byte[] pixel3 = pixels[pixel2Pos.x][pixel2Pos.y]; 210 | Point pixel3Pos = rotate(pixel2Pos, pixels.length); 211 | 212 | byte[] pixel4 = pixels[pixel3Pos.x][pixel3Pos.y]; 213 | 214 | setPixel(pixels, pixel1, pixel1Pos); 215 | setPixel(pixels, pixel2, pixel2Pos); 216 | setPixel(pixels, pixel3, pixel3Pos); 217 | //System.out.println("(" + pixel3Pos.x + "," + pixel3Pos.y + ")->(" + x + "," + y + ")"); 218 | setPixel(pixels, pixel4, x, y); 219 | //System.out.println("DONE WITH ("+x+","+y+")"); 220 | } 221 | 222 | private void setPixel(byte[][][] pixels, byte[] pixel, Point point) { 223 | setPixel(pixels, pixel, point.x, point.y); 224 | } 225 | 226 | private void setPixel(byte[][][] pixels, byte[] pixel, int x, int y) { 227 | pixels[x][y] = pixel; 228 | } 229 | 230 | private Point rotate(Point point, int side) { 231 | return rotate(point.x, point.y, side); 232 | } 233 | 234 | private Point rotate(int x, int y, int side) { 235 | //System.out.println("(" + x + "," + y + ")" + "->" + "(" + y + "," + (side -x-1) + ")"); 236 | return new Point(y, side - x - 1); 237 | } 238 | 239 | private static class Point { 240 | final int x; 241 | final int y; 242 | public Point(int x, int y) { 243 | this.x = x; 244 | this.y = y; 245 | } 246 | } 247 | } 248 | 249 | /** 250 | * Mi solución no fue de máxima eficiencia en memoria, porque no necesitaba guardar 251 | * todas las posiciones donde había 0's, solamente necesitaba saber que columnas 252 | * y renglones serían 0's. 253 | */ 254 | class MatrixClearer { 255 | public void clearZeros(byte[][] matrix) { 256 | if(matrix == null || matrix.length == 0 || matrix[0] == null || matrix[0].length == 0) { 257 | throw new RuntimeException("Invalid matrix"); 258 | } 259 | 260 | // run: O(MxN) space: O(M+N) 261 | ZeroMarked zeros = markZeros(matrix); 262 | // run: O(MxN) 263 | clearZeros(matrix, zeros); 264 | } 265 | 266 | private static void clearZeros(byte[][] matrix, ZeroMarked zeros) { 267 | for(int row = 0; row < matrix.length; row++) { 268 | for(int col = 0; col < matrix[0].length; col++) { 269 | if(zeros.rows[row] || zeros.cols[col]) { 270 | matrix[row][col] = 0; 271 | } 272 | } 273 | } 274 | } 275 | 276 | // O(MxN) 277 | private ZeroMarked markZeros(byte[][] matrix) { 278 | ZeroMarked zeros = new ZeroMarked(matrix.length, matrix[0].length); 279 | 280 | for(int row = 0; row < matrix.length; row++) { 281 | if(matrix[row] == null || matrix[row].length == 0) { 282 | throw new RuntimeException("Invalid matrix"); 283 | } 284 | 285 | for(int col = 0; col < matrix[0].length; col++) { 286 | zeros.cols[col] |= zeros.rows[row] |= matrix[row][col] == 0; 287 | } 288 | } 289 | 290 | return zeros; 291 | } 292 | 293 | private static class ZeroMarked { 294 | final boolean[] rows; 295 | final boolean[] cols; 296 | 297 | public ZeroMarked(int m, int n) { 298 | rows = new boolean[m]; 299 | cols = new boolean[n]; 300 | } 301 | } 302 | 303 | private void clearColumnAndRow(byte[][] matrix, int row, int column) { 304 | // clear row 305 | for(int i = 0; i < matrix.length; i++) { 306 | matrix[row][i] = 0; 307 | } 308 | 309 | for(int i = 0; i < matrix[row].length; i++) { 310 | matrix[i][column] = 0; 311 | } 312 | } 313 | } 314 | 315 | class StringRotationChecker { 316 | public boolean isRotation(String input, String rotation) { 317 | if(input == null || rotation == null) { 318 | return false; 319 | } 320 | 321 | if(input.length() != rotation.length()) { 322 | return false; 323 | } 324 | 325 | if(input.length() <= 1) { 326 | return input.equals(rotation); 327 | } 328 | 329 | int shift = findShift(input, rotation); 330 | if(shift == -1) { 331 | return false; 332 | } 333 | 334 | String shifted = shift(input, shift); 335 | return rotation.equals(shifted); 336 | } 337 | 338 | private int findShift(String input, String rotation) { 339 | // first find the index of the pivot on the rotation 340 | char pivotChar = input.charAt(0); 341 | int shift = -1; 342 | for(int i = 0; i < rotation.length(); i++) { 343 | if(pivotChar == rotation.charAt(i)) { 344 | boolean matched = true; 345 | for( 346 | int j = 1, k = i+1; 347 | matched && j < input.length() && k < rotation.length(); 348 | j++, k++) { 349 | matched = input.charAt(j) == rotation.charAt(k); 350 | } 351 | 352 | if(matched) { 353 | // calculate by how much we have to shift 354 | shift = i; 355 | break; 356 | } 357 | } 358 | } 359 | 360 | return shift; 361 | } 362 | 363 | private String shift(String input, int shift) { 364 | if(shift == 0) { 365 | return input; 366 | } 367 | 368 | char[] shifted = new char[input.length()]; 369 | for(int i = 0; i < shifted.length; i++) { 370 | int source = i + shift; 371 | if(source >= shifted.length) { 372 | source = source - shifted.length; 373 | } 374 | 375 | shifted[source] = input.charAt(i); 376 | } 377 | 378 | return new String(shifted); 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /src/Chapter17.java: -------------------------------------------------------------------------------- 1 | package chapter17; 2 | 3 | import static debug.Debugger.DEBUG; 4 | 5 | import java.lang.*; 6 | import java.util.*; 7 | import java.util.stream.*; 8 | 9 | import structures.*; 10 | 11 | class InlineNumberSwapper { 12 | 13 | public void swap(PairOfNumbers pair) { 14 | pair.first = pair.first ^ pair.second; 15 | pair.second = pair.first ^ pair.second; 16 | pair.first = pair.first ^ pair.second; 17 | } 18 | } 19 | 20 | class PairOfNumbers { 21 | public int first; 22 | public int second; 23 | public PairOfNumbers(int first, int second) { 24 | this.first = first; 25 | this.second = second; 26 | } 27 | 28 | public PairOfNumbers pair(int a, int b) { 29 | return new PairOfNumbers(a, b); 30 | } 31 | } 32 | 33 | class TicTacToeResultDetector { 34 | 35 | // Assumptions all we want to know is if someone won not WHO won. 36 | // the input is 1 for player 1, 2 for player 2, 0 for not used 37 | public boolean detectWinner(byte[][] board) { 38 | if(board == null || board.length != 3 || board[0].length != 3 ) { 39 | throw new RuntimeException("Board can only by 3 by 3"); 40 | } 41 | 42 | boolean diagonalA = true, diagonalB = true; 43 | for(int i = 0; i <= 2; i++) { 44 | if(board[i][0] != 0 && board[0][i] != 0) { 45 | if ((board[i][0] == board[i][1] && board[i][1] == board[i][2]) 46 | || (board[0][i] == board[1][i] && board[1][i] == board[2][i])) { 47 | return true; 48 | } 49 | } 50 | 51 | if(i > 0) { 52 | diagonalA &= board[i - 1][i - 1] == board[i][i]; 53 | diagonalB &= board[i - 1][3 - i] == board[i][2 - i]; 54 | } 55 | } 56 | 57 | return diagonalB || diagonalA; 58 | } 59 | } 60 | 61 | class NFactorialTrailingZeros { 62 | public int calculate(int n) { 63 | if(n < 5) { return 0; } 64 | if(n == 5) { return 1; } 65 | if(n < 10) { return 1; } 66 | 67 | int tens = n / 10; 68 | int total = tens * 2; 69 | if(n % 10 >= 5) { 70 | total++; 71 | } 72 | 73 | return total; 74 | } 75 | } 76 | 77 | class LargestSumSequenceFinder { 78 | // Assumption: There is at least 1 positive number 79 | public int find(int[] numbers) { 80 | if(numbers == null || numbers.length == 0) { 81 | throw new RuntimeException("Numbers cannot be null or empty"); 82 | } 83 | 84 | if(numbers.length == 1) { 85 | return numbers[0]; 86 | } 87 | 88 | int maxSum = Integer.MIN_VALUE; 89 | int currentSum = 0; 90 | 91 | for(int number : numbers) { 92 | int nextSum = currentSum + number; 93 | // always check if adding the next number results in a max 94 | // sequence sum, note that there is no need to check if the 95 | // next number is negative before comparing against the max sum 96 | if(nextSum > maxSum) { 97 | maxSum = nextSum; 98 | } 99 | 100 | if(nextSum < 0) { 101 | // restart the sequence sum if adding the next negative number 102 | // results in a negative sum 103 | currentSum = 0; 104 | } else { 105 | // so long as adding the next number does not result in a negative 106 | // sum, it is worth considering it in the sequence sum 107 | currentSum = nextSum; 108 | } 109 | } 110 | 111 | return maxSum; 112 | } 113 | } 114 | 115 | class WordCounter { 116 | private static final int UPPER_LOWER_DELTA = 'a' - 'A'; 117 | 118 | public WordCounter() {} 119 | 120 | public int count(String text, String word) { 121 | String lowerWord = word.toLowerCase(); 122 | 123 | int comparedWordIndex = 0; 124 | int count = 0; 125 | boolean skipCurrentWord = false; 126 | for(int i = 0; i < text.length(); i++) { 127 | if(isNonLetter(text.charAt(i))) { 128 | skipCurrentWord = false; 129 | continue; 130 | } 131 | 132 | if(skipCurrentWord) { 133 | continue; 134 | } 135 | 136 | if(isSameLetter(lowerWord.charAt(comparedWordIndex), text.charAt(i))) { 137 | comparedWordIndex++; 138 | if(comparedWordIndex == lowerWord.length()) { 139 | if(i+1 == text.length() || isNonLetter(text.charAt(i+1))) { 140 | // words matched! 141 | count++; 142 | } 143 | // restart the word index 144 | comparedWordIndex = 0; 145 | } 146 | } else { 147 | comparedWordIndex = 0; 148 | skipCurrentWord = true; 149 | } 150 | } 151 | 152 | return count; 153 | } 154 | 155 | private boolean isSameLetter(char lowerCaseLetter, char otherLetter) { 156 | return lowerCaseLetter == otherLetter || 157 | lowerCaseLetter == (otherLetter - UPPER_LOWER_DELTA); 158 | } 159 | 160 | private boolean isNonLetter(char symbol) { 161 | return !(symbol >= 'a' && symbol <= 'z') && 162 | !(symbol >= 'A' && symbol <= 'Z'); 163 | } 164 | } 165 | 166 | class Pair { 167 | private final int a; 168 | private final int b; 169 | public Pair(int a, int b) { this.a = a; this.b = b; } 170 | public String toString() { 171 | return String.format("(%s, %s)", a, b); 172 | } 173 | } 174 | 175 | class PairFinder { 176 | public ArrayList findPairs(int[] numbers, int sum) { 177 | if(numbers == null || numbers.length < 2) { 178 | throw new RuntimeException("Numbers must be at least of length 2"); 179 | } 180 | 181 | ArrayList results = new ArrayList<>(); 182 | HashMap numbersHash = new HashMap<>(); 183 | 184 | for(int number : numbers) { 185 | if(numbersHash.containsKey(sum - number)) { 186 | for(int i = 0; i < numbersHash.get(sum - number); i++) { 187 | results.add(pair(number, sum - number)); 188 | } 189 | } 190 | 191 | if(numbersHash.containsKey(number)) { 192 | numbersHash.put(number, numbersHash.get(number) + 1); 193 | } else { 194 | numbersHash.put(number, 1); 195 | } 196 | } 197 | 198 | return results; 199 | } 200 | 201 | Pair pair(int a, int b) { return new Pair(a, b); } 202 | } 203 | 204 | class BinaryTreeToSortedList { 205 | public void toSortedList(BiNode tree) { 206 | ArrayList sortedNodes = new ArrayList<>(); 207 | sortedNodes.add(null); 208 | appendToSortedList(tree, sortedNodes); 209 | sortedNodes.add(null); 210 | 211 | for(int i = 1; i < sortedNodes.size() - 1; i++) { 212 | BiNode node = sortedNodes.get(i); 213 | node.first(sortedNodes.get(i - 1)); 214 | node.second(sortedNodes.get(i + 1)); 215 | } 216 | } 217 | 218 | public void appendToSortedList(BiNode tree, List sortedNodes) { 219 | if(tree == null) { return; } 220 | appendToSortedList(tree.first(), sortedNodes); 221 | sortedNodes.add(tree); 222 | appendToSortedList(tree.second(), sortedNodes); 223 | } 224 | } 225 | 226 | class BiNode { 227 | private final int value; 228 | private BiNode first; 229 | private BiNode second; 230 | 231 | public BiNode(int value) { 232 | this.value = value; 233 | } 234 | 235 | public void first(BiNode first) { this.first = first; } 236 | public BiNode first() { return this.first; } 237 | 238 | public void second(BiNode second) { this.second = second; } 239 | public BiNode second() { return this.second; } 240 | 241 | } 242 | 243 | /* 244 | * The input is a set of words all clumped up together, such as: 245 | * iammarcelthegreatestdotaplayerintheworld 246 | * 247 | * The expected output is the strinmg with the least unknown characters 248 | * in it, such that you minimize the letters that dont belong to a word: 249 | * i am MARCEL the greatest DOTA player in the world 250 | * 251 | * For this to work we assume: 252 | * - We have access to dictionary of words 253 | * - We assume that there are words not in that dictionary, 254 | * such as name and proper nouns. 255 | */ 256 | 257 | class SentenceFinder { 258 | private int iterations = 0; 259 | 260 | private static class Dictionary { 261 | private final Set words; 262 | private final int longestWordSize; 263 | 264 | public Dictionary(Set words) { 265 | this.words = words; 266 | int longestWordSize = 0; 267 | for(String word : words) { 268 | if(word.length() > longestWordSize) { 269 | longestWordSize = word.length(); 270 | } 271 | } 272 | this.longestWordSize = longestWordSize; 273 | } 274 | 275 | public boolean contains(String word) { 276 | return this.words.contains(word); 277 | } 278 | 279 | public int longestWordSize() { 280 | return longestWordSize; 281 | } 282 | } 283 | 284 | private final Dictionary words; 285 | public SentenceFinder(Set words) { 286 | this.words = new Dictionary(words); 287 | } 288 | 289 | public SingleLinkNode sentencify(String letters) { 290 | Recursion result = sentencify(new Memo(), letters, 0, ""); 291 | DEBUG.println("Input length: %s", letters.length()); 292 | DEBUG.println("Iterations: %s", iterations); 293 | DEBUG.println("Complexity: N^%2.1f", Math.log(iterations) / Math.log(letters.length())); 294 | return result.words; 295 | } 296 | 297 | private Recursion sentencify( 298 | Memo memo, String letters, int index, String currentWord) { 299 | iterations++; 300 | if(memo.has(index, currentWord)) { 301 | return memo.get(index, currentWord); 302 | } 303 | 304 | Recursion result = null; 305 | if(index == letters.length()) { 306 | if(exists(currentWord)) { 307 | result = results(node(currentWord), 0); 308 | } else { 309 | if(currentWord.equals("")) { 310 | result = results(null, 0); 311 | } else { 312 | result = results(node(currentWord.toUpperCase()), currentWord.length()); 313 | } 314 | } 315 | } else { 316 | String nextWord = currentWord + letters.charAt(index); 317 | Recursion cutoff = sentencify(memo, letters, index + 1, ""); 318 | Recursion extend = sentencify(memo, letters, index + 1, nextWord); 319 | SingleLinkNode cutoffRecursionNode = cutoff.words; 320 | 321 | result = extend; 322 | if(exists(nextWord)) { 323 | if(cutoff.skipCount <= extend.skipCount) { 324 | SingleLinkNode cutoffNode = node(nextWord); 325 | cutoffNode.next(cutoffRecursionNode); 326 | result = results(cutoffNode, cutoff.skipCount); 327 | } 328 | } else if(cutoff.skipCount + nextWord.length() < extend.skipCount) { 329 | SingleLinkNode cutoffNode = node(nextWord.toUpperCase()); 330 | cutoffNode.next(cutoffRecursionNode); 331 | result = results(cutoffNode, nextWord.length() + cutoff.skipCount); 332 | } 333 | } 334 | 335 | memo.memo(index, currentWord, result); 336 | return result; 337 | } 338 | 339 | private SingleLinkNode node(String value) { 340 | return new SingleLinkNode(value); 341 | } 342 | 343 | private boolean exists(String word) { 344 | return words.contains(word); 345 | } 346 | 347 | private static Recursion results(SingleLinkNode results, int skipCount) { 348 | return new Recursion(results, skipCount); 349 | } 350 | 351 | private static class Recursion { 352 | final SingleLinkNode words; 353 | final int skipCount; 354 | public Recursion(SingleLinkNode words, int skipCount) { 355 | this.skipCount = skipCount; 356 | this.words = words; 357 | } 358 | } 359 | 360 | private static class Memo { 361 | private Map entries = new HashMap<>(); 362 | public void memo(int index, String currentWord, Recursion result) { 363 | String key = currentWord + index; 364 | entries.put(key, result); 365 | } 366 | 367 | public boolean has(int index, String currentWord) { 368 | String key = currentWord + index; 369 | return entries.containsKey(key); 370 | } 371 | 372 | public Recursion get(int index, String currentWord) { 373 | String key = currentWord + index; 374 | return entries.get(key); 375 | } 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /test/Chapter9Test.java: -------------------------------------------------------------------------------- 1 | package chapter9; 2 | 3 | import static debug.Debugger.DEBUG; 4 | import static org.hamcrest.Matcher.*; 5 | import static org.hamcrest.MatcherAssert.*; 6 | import static org.hamcrest.core.Is.*; 7 | import static org.hamcrest.core.IsEqual.*; 8 | import static org.hamcrest.core.IsNull.*; 9 | 10 | 11 | import java.lang.*; 12 | import java.util.*; 13 | import java.util.stream.*; 14 | 15 | import org.hamcrest.core.*; 16 | import org.junit.*; 17 | import org.junit.rules.*; 18 | import structures.*; 19 | import test.*; 20 | 21 | public class Chapter9Test extends ChapterTestBase { 22 | 23 | private StepsPermutationCounter stepsCounter = new StepsPermutationCounter(); 24 | private PathPermutationCounter pathsCounter = new PathPermutationCounter(); 25 | 26 | @After 27 | public void disableDebug() { 28 | DEBUG.disable(); 29 | } 30 | 31 | @Test 32 | public void testStepsPermutationCounter() { 33 | checkStepsPermutationCounter(0, 0); 34 | checkStepsPermutationCounter(1, 1); 35 | checkStepsPermutationCounter(2, 2); 36 | checkStepsPermutationCounter(3, 6); 37 | 38 | checkStepsPermutationCounter(4, 12); 39 | checkStepsPermutationCounter(5, 23); 40 | checkStepsPermutationCounter(6, 44); 41 | } 42 | 43 | private void checkStepsPermutationCounter(int steps, int expected) { 44 | areEqual(stepsCounter.count(steps), expected); 45 | } 46 | 47 | @Test 48 | public void testPathPermutationCounter() { 49 | checkPathPermutationCounter(point(0, 0), point(1, 0), 1); 50 | checkPathPermutationCounter(point(0, 0), point(0, 1), 1); 51 | 52 | checkPathPermutationCounter(point(0, 0), point(1, 1), 2); 53 | 54 | checkPathPermutationCounter(point(0, 0), point(1, 2), 3); 55 | checkPathPermutationCounter(point(0, 0), point(2, 1), 3); 56 | 57 | checkPathPermutationCounter(point(0, 0), point(2, 2), 6); 58 | checkPathPermutationCounter(point(0, 0), point(3, 3), 20); 59 | } 60 | 61 | private void checkPathPermutationCounter(Point src, Point dest, int expected) { 62 | areEqual(pathsCounter.count(src.x, src.y, dest.x, dest.y), expected); 63 | } 64 | 65 | @Test 66 | public void testBlockedPathPermutationCounter_middle() { 67 | // given 68 | Integer[][] road = new Integer[3][3]; 69 | road[1][1] = Integer.MIN_VALUE; 70 | BlockedPathPermutationCounter target = new BlockedPathPermutationCounter(); 71 | // when 72 | int actual = target.countPaths(road, 0, 0, 2, 2); 73 | // then 74 | areEqual(actual, 2); 75 | } 76 | 77 | @Test 78 | public void testBlockedPathPermutationCounter_upperRightCorner() { 79 | // given 80 | Integer[][] road = new Integer[3][3]; 81 | road[2][0] = Integer.MIN_VALUE; 82 | BlockedPathPermutationCounter target = new BlockedPathPermutationCounter(); 83 | // when 84 | int actual = target.countPaths(road, 0, 0, 2, 2); 85 | // then 86 | areEqual(actual, 5); 87 | } 88 | 89 | private Point point(int x, int y) { return new Point(x, y); } 90 | 91 | private static class Point { 92 | int x; int y; 93 | public Point(int x, int y) { this.x = x; this.y = y; } 94 | } 95 | 96 | @Test 97 | public void testMagicIndexFinder() { 98 | checkMagicIndexFinder(new int[] { -2, -1, 0, 2, 4, 9, 10 }, 4); 99 | checkMagicIndexFinder(new int[] { -2, -1, 4, 4, 4, 9, 10 }, 4); 100 | checkMagicIndexFinder(new int[] { -2, -1, 0, 2, 5, 9, 10 }, null); 101 | checkMagicIndexFinder(new int[] { -2 }, null); 102 | checkMagicIndexFinder(new int[] { -1, 1 }, 1); 103 | checkMagicIndexFinder(new int[] { 0, 2 }, 0); 104 | checkMagicIndexFinder(new int[] { -3, -4, 2 }, 2); 105 | checkMagicIndexFinder(new int[] { -3, 1, 4 }, 1); 106 | checkMagicIndexFinder(new int[] { -3, 1, 4 }, 1); 107 | checkMagicIndexFinder(new int[] { 2, 2, 2 }, 2); 108 | } 109 | 110 | private void checkMagicIndexFinder(int[] numbers, Integer expected) { 111 | // given 112 | MagicIndexFinder finder = new MagicIndexFinder(); 113 | // when 114 | Integer magic = finder.findMagicIndex(numbers); 115 | // then 116 | areEqual(magic, expected); 117 | } 118 | 119 | @Test 120 | public void testSubsetsBuilder() { 121 | // given 122 | Integer[] set = { 1, 2, 3 }; 123 | SetSubsetsCreator subsetCreator = new SetSubsetsCreator(); 124 | // when 125 | Set> subsets = subsetCreator.createSubsets(set); 126 | // then 127 | isTrue(subsets.contains(nodes(1))); 128 | isTrue(subsets.contains(nodes(1, 2))); 129 | isTrue(subsets.contains(nodes(1, 2, 3))); 130 | 131 | isTrue(subsets.contains(nodes(2))); 132 | isTrue(subsets.contains(nodes(2, 3))); 133 | 134 | isTrue(subsets.contains(nodes(3))); 135 | } 136 | 137 | private static SingleLinkNode nodes(Integer ... values) { 138 | SingleLinkNode tail = null; 139 | for(Integer value : values) { 140 | SingleLinkNode newTail = new SingleLinkNode<>(value); 141 | newTail.next(tail); 142 | tail = newTail; 143 | } 144 | 145 | return tail; 146 | } 147 | 148 | @Test 149 | public void testStringPermutations() { 150 | checkStringPermutations(""); 151 | checkStringPermutations("a"); 152 | checkStringPermutations("ab", "ba"); 153 | checkStringPermutations("abc", "bac", "bca", "cba", "cab", "acb"); 154 | checkStringPermutations( 155 | "dabc", "dbac", "dbca", "dcba", "dcab", "dacb", 156 | "adbc", "bdac", "bdca", "cdba", "cdab", "adcb", 157 | "abdc", "badc", "bcda", "cbda", "cadb", "acdb", 158 | "abcd", "bacd", "bcad", "cbad", "cabd", "acbd" 159 | ); 160 | } 161 | 162 | private void checkStringPermutations(String ... permutations) { 163 | StringPermutations target = new StringPermutations(); 164 | // when 165 | List actual = target.permutations(permutations[0]); 166 | // then 167 | areEqual(actual.size(), permutations.length); 168 | checkList(actual, permutations); 169 | } 170 | 171 | @Test 172 | public void testParenthesisPrinter() { 173 | ParenthesisPrinter printer = new ParenthesisPrinter(); 174 | checkList(printer.permutations(1), "()"); 175 | checkList(printer.permutations(2), "(())", "()()"); 176 | checkList(printer.permutations(3), "(())()", "((()))", "()(())", "(()())","()()()"); 177 | } 178 | 179 | private void checkList(List container, T ... items) { 180 | areEqual(container.size(), items.length); 181 | for(T item : items) { 182 | isTrue("Element not found: " + item, container.contains(item)); 183 | } 184 | } 185 | 186 | @Test 187 | public void testPaintFill() { 188 | // given 189 | byte[][] image = { 190 | { 0, 1, 1, 0 }, 191 | { 0, 0, 2, 1 }, 192 | { 1, 1, 0, 1 }, 193 | { 0, 0, 1, 0 } 194 | }; 195 | 196 | byte[][] expected = { 197 | { 2, 1, 1, 0 }, 198 | { 2, 2, 2, 1 }, 199 | { 1, 1, 2, 1 }, 200 | { 2, 2, 1, 2 } 201 | }; 202 | 203 | PaintFill fill = new PaintFill(); 204 | 205 | // when 206 | int pixelsPainted = fill.fill(image, (byte) 2, new PaintFill.Point(0, 0)); 207 | 208 | // then 209 | areEqual(image, expected); 210 | // then one fill for each pixel painted 211 | areEqual(pixelsPainted, 7); 212 | } 213 | 214 | @Test 215 | public void testChangePermutations() { 216 | // given 217 | // 10 pennies 218 | // 2 nickels 219 | // 1 nickel + 5 cents 220 | // 1 dime 221 | checkChangePermutations(4, 1); 222 | checkChangePermutations(9, 2); 223 | checkChangePermutations(10, 4); 224 | checkChangePermutations(26, 13); 225 | 226 | } 227 | 228 | private void checkChangePermutations(int cents, int expectedCount) { 229 | 230 | ChangePermutations permutations = new ChangePermutations(); 231 | // when 232 | LinkedListStack> change = 233 | permutations.changePermutations(cents); 234 | // then 235 | areEqual(change.size(), expectedCount); 236 | SingleLinkNode permutation; 237 | while(!change.isEmpty()) { 238 | permutation = change.pop(); 239 | areEqual(sum(permutation), cents); 240 | } 241 | } 242 | 243 | private int sum(SingleLinkNode coins) { 244 | int value = 0; 245 | DEBUG.println(coins); 246 | while(coins != null) { 247 | value += coins.value().value(); 248 | coins = coins.next(); 249 | } 250 | return value; 251 | } 252 | 253 | @Test @Ignore 254 | public void testQueensPosition() { 255 | QueensBoardSolver.QueensPosition position = new QueensBoardSolver.QueensPosition(); 256 | 257 | areEqual(position.place(0,0).positions(), 1L); 258 | areEqual(position.place(7,7).positions(), (1L << 63)); 259 | 260 | isTrue("Same position", position.place(0,0).isOccupied(0,0)); 261 | isTrue("Vertical", position.place(0,0).isOccupied(0,1)); 262 | isTrue("Horizontal", position.place(0,0).isOccupied(1,0)); 263 | isTrue("Diagonal", position.place(0,0).isOccupied(1,1)); 264 | 265 | QueensBoardSolver.QueensPosition test = new QueensBoardSolver.QueensPosition( 266 | position.place(4,0).touchedSquares(), 267 | 0 268 | ); 269 | DEBUG.enable(); 270 | printBitboard(test.asBitboard()); 271 | DEBUG.disable(); 272 | } 273 | 274 | @Test @Ignore 275 | public void testQueensBoardSolver() { 276 | // given 277 | QueensBoardSolver solver = new QueensBoardSolver(); 278 | // when 279 | if(true) { return; } 280 | DEBUG.enable(); 281 | List result = solver.solve(); 282 | // then 283 | for(boolean[][] position : result) { 284 | printBitboard(position); 285 | DEBUG.println("-------------"); 286 | break; 287 | } 288 | areEqual(result.size(), 92); 289 | DEBUG.disable(); 290 | } 291 | 292 | private static void printBitboard(boolean[][] position) { 293 | DEBUG.println(" 0 1 2 3 4 5 6 7"); 294 | for(int y = 0; y < position.length; y++) { 295 | DEBUG.print(y + " "); 296 | for(int x = 0; x < position.length; x++) { 297 | DEBUG.print(position[x][y] ? "X " : "- "); 298 | } 299 | DEBUG.println(""); 300 | } 301 | } 302 | 303 | @Test 304 | public void testBoxStacker() { 305 | // given 306 | Box smallest = box(1, 1, 1); 307 | Box secondSmallest = box(2, 2, 2); 308 | Box middle = box(3, 3, 3); 309 | Box largest = box(4, 4, 4); 310 | 311 | 312 | List boxes = boxes( 313 | smallest, 314 | secondSmallest, 315 | box(2, 5, 2), 316 | box(1, 2, 6), 317 | largest, 318 | middle, 319 | box(3, 2, 3) 320 | ); 321 | 322 | SingleLinkNode largestNode = new SingleLinkNode<>(largest); 323 | SingleLinkNode middleNode = new SingleLinkNode<>(middle); 324 | middleNode.next(largestNode); 325 | SingleLinkNode secondSmallestNode = new SingleLinkNode<>(secondSmallest); 326 | secondSmallestNode.next(middleNode); 327 | SingleLinkNode smallestNode = new SingleLinkNode<>(smallest); 328 | smallestNode.next(secondSmallestNode); 329 | 330 | BoxStacker stacker = new BoxStacker(); 331 | 332 | // when 333 | SingleLinkNode stack = stacker.stack(boxes); 334 | // then 335 | DEBUG.println(stack); 336 | areEqual(stack, smallestNode); 337 | } 338 | 339 | private List boxes(Box ... boxes) { 340 | List result = new ArrayList<>(); 341 | for(Box box : boxes) { 342 | result.add(box); 343 | } 344 | 345 | return result; 346 | } 347 | 348 | private static Box box(int h, int w, int d) { 349 | return Box.box(h, w, d); 350 | } 351 | 352 | @Test 353 | public void testExpressionPermutator() { 354 | // given 355 | String expression = "1^0|0|1"; 356 | ExpressionPermutator permutator = new ExpressionPermutator(); 357 | boolean desired = false; 358 | // when 359 | List permutations = permutator.calculatePermutations(expression, desired); 360 | // then 361 | DEBUG.println(permutations); 362 | areEqual(permutations.size(), 2); 363 | for(Expression permutation : permutations) { 364 | areEqual(permutation.eval(), desired); 365 | } 366 | } 367 | } 368 | -------------------------------------------------------------------------------- /src/Chapter10.java: -------------------------------------------------------------------------------- 1 | package chapter10; 2 | 3 | import static debug.Debugger.DEBUG; 4 | 5 | import java.io.*; 6 | import java.lang.*; 7 | import java.util.*; 8 | import java.util.stream.*; 9 | 10 | import structures.*; 11 | 12 | class WordFinder { 13 | private final Document[] documents; 14 | 15 | public WordFinder(Document ... documents) { 16 | this.documents = documents; 17 | } 18 | 19 | // Solution: 20 | // a) Create a trie with all the words in all the documents and 21 | // on each 'leaf' node put the id of the document that contains 22 | // that leaf node. Then it is just a matter of running through 23 | // the trie to figure out if the document is in the trie. 24 | // Advantage; Search is O(key) <-- key is small, might as well be O(K) 25 | // Problem: Memory usage is O(#docs * avg_len(docs)) 26 | // 27 | // b) Instead of using a Trie you can use a HashMap and 28 | // and simply put the doucment key on each word in all the documents 29 | // Advantage: Search is O(1), however there is a high likelyhood that 30 | // there will be colision since there will be a huge number of entries 31 | // 1 word for each document. 32 | // Problem: Memory usage is O(#docs * avg_len(docs)) 33 | // 34 | // Mitigation: For storage in both cases we can store a system that is 35 | // MADE to store and retrieve data efficiently, for example: 36 | // + RDBMS where PK is the word and a column has the associated document 37 | // IDs. 38 | // + Use an in-memory key-value data base, which should have very efficient 39 | // retrieval, and a memory could be shared amongst multiple machines using 40 | // technology such as memcached (distributed object-caching mechanism). 41 | // 42 | // How can we improve the memory usage of this solution? 43 | } 44 | 45 | class Document { 46 | private final String[] content; 47 | private final Object id; 48 | 49 | public Document(Object id, String ... content) { 50 | this.id = id; 51 | this.content = content; 52 | } 53 | 54 | public String word(int index) { 55 | return content[index]; 56 | } 57 | 58 | public int wordCount() { 59 | return this.content.length; 60 | } 61 | 62 | public Object id() { 63 | return this.id; 64 | } 65 | } 66 | 67 | 68 | class PathFinder { 69 | 70 | public SingleLinkNode findPath(Person a, Person b) { 71 | Map> discoverySetB = new HashMap<>(); 72 | Stack> searchStackB = new Stack<>(); 73 | searchStackB.add(node(b)); 74 | 75 | Map> discoverySetA = new HashMap<>(); 76 | Stack> searchStackA = new Stack<>(); 77 | searchStackA.add(node(a)); 78 | 79 | int maxSearch = 3; 80 | int searchIterations = 0; 81 | 82 | while(searchIterations <= maxSearch) { 83 | searchIterations++; 84 | SingleLinkNode pathAtoB = 85 | iterateGraph(discoverySetB, discoverySetA, searchStackA); 86 | if(pathAtoB != null) { 87 | return pathAtoB; 88 | } 89 | 90 | SingleLinkNode pathBtoA = 91 | iterateGraph(discoverySetA, discoverySetB, searchStackB); 92 | if(pathBtoA != null) { 93 | return pathBtoA; 94 | } 95 | } 96 | 97 | return null; 98 | } 99 | 100 | private SingleLinkNode iterateGraph( 101 | Map> destConnections, 102 | Map> srcConnections, 103 | Stack> searchStack) { 104 | 105 | while(!searchStack.isEmpty()) { 106 | SingleLinkNode connection = searchStack.pop(); 107 | if(destConnections.containsKey(connection.value().username())) { 108 | // find the path that contains the connection to the node 109 | SingleLinkNode bConnection = 110 | destConnections.get(connection.value().username()); 111 | // join connection paths, iterate one in reverse order 112 | return joinPaths(connection, bConnection); 113 | } 114 | 115 | for(Person nextConnection : connection.value().connections()) { 116 | if(!srcConnections.containsKey(nextConnection.username())) { 117 | SingleLinkNode nextConnectionNode = node(nextConnection); 118 | nextConnectionNode.next(connection); 119 | srcConnections.put(connection.value().username(), nextConnectionNode); 120 | // now we have a stack of paths 121 | searchStack.push(nextConnectionNode); 122 | } 123 | } 124 | } 125 | 126 | return null; 127 | } 128 | 129 | private SingleLinkNode joinPaths( 130 | SingleLinkNode source, SingleLinkNode destination) { 131 | SingleLinkNode head = destination.next(); 132 | SingleLinkNode next = source; 133 | while(head != null) { 134 | SingleLinkNode nextDestination = head.next(); 135 | head.next(next); 136 | next = head; 137 | head = nextDestination; 138 | } 139 | return next; 140 | } 141 | 142 | SingleLinkNode node(Person person) { 143 | return new SingleLinkNode(person); 144 | } 145 | } 146 | 147 | class Person { 148 | private List connections = new ArrayList<>(); 149 | 150 | private final String username; 151 | public Person(String username) { 152 | this.username = username; 153 | } 154 | 155 | public void add(Person connection) { 156 | this.connections.add(connection); 157 | } 158 | 159 | public Person[] connections() { 160 | return connections.toArray(new Person[connections.size()]); 161 | } 162 | 163 | public String username() { return username; } 164 | 165 | public int hashCode() { 166 | return username.hashCode(); 167 | } 168 | 169 | public final boolean equals(Object other) { 170 | if(!(other instanceof Person)) { 171 | return false; 172 | } 173 | 174 | Person person = (Person) other; 175 | if(person.username == null || username == null) { 176 | return username == person.username; 177 | } 178 | 179 | return username.equals(person.username); 180 | } 181 | } 182 | 183 | // 1 Gb available RAM 184 | // 4,000,000,000 non-negative numbers 185 | // -assuming the file is binary and numbers are stored as 32-bit sequential bits, 186 | // so we the file would be read 32-bits at a time 187 | // 188 | // File size: 4x10^9 * 4 bytes = 16x10^9 bytes = 16x10^6 kb = 16*10^3 Mb = 16 Gb 189 | // 190 | // No matter what, we need to read all of the numbers, in order to ensure that 191 | // whatever number we generate, it is not one of the 4 billion numbers 192 | // 193 | // Problem: If I process 1Gb worth of numbers, that doesn't help me if I cannot somehow 194 | // store the results of having processed all those numbers. 195 | // Potential #1: Generate ranges with the ranges of numbers, but if the numbers are 196 | // all skipped by 1, then this wouldn't work. 197 | // Questions: Is there a way to store multiple numbers in less memory? 198 | // A trie could be used to store all the numbers where each digit is a character 199 | // for example, to store the first 100 numbers: 200 | // it would require 100-character nodes (1-byte each) + 1 boolean flag to say if 201 | // it was actually picked + 100 memory pointers 64-bit each :( 202 | 203 | 204 | /* 205 | class Node { 206 | boolean found = false; 207 | Node[] nodes = new Node[10]; // 64-bit for Node[] + 64-bit * 10 for each Node[] 208 | } 209 | */ 210 | 211 | class FindNewNumber { 212 | final int bytesTotalSize = 16 * 1000000000; 213 | final int bytes250Mb = 250 * 1000000; 214 | final int bytes500Mb = bytes250Mb * 2; 215 | 216 | public int findNumber(String filename) { 217 | // big file name is 16 Gb 218 | // requires 500Mb memory 219 | sort(filename, filename + "sorted", 0, bytesTotalSize); 220 | IntegerStream numbers = createStream(filename + "sorted", 0, bytesTotalSize); 221 | 222 | int prevNum = numbers.next(); 223 | while(numbers.hasNext()) { 224 | int number = numbers.next(); 225 | if(number - prevNum > 1) { 226 | return number - 1; 227 | } 228 | prevNum = number; 229 | } 230 | 231 | throw new RuntimeException("Impossible!"); 232 | } 233 | 234 | private void sort(String srcFile, String dstFile, int start, int size) { 235 | String part0 = "tmp" + start + "-" + size + "0"; 236 | String part1 = "tmp" + start + "-" + size + "1"; 237 | if(size > 1000000000) { 238 | sort(srcFile, part0, start, size / 2); 239 | sort(srcFile, part1, start, size / 2); 240 | mergeTo( 241 | createStream(part0, 0, size / 2), 242 | createStream(part1, 0, size / 2), 243 | dstFile 244 | ); 245 | } else { 246 | sort500MbTo(srcFile, start, part0); 247 | sort500MbTo(srcFile, start + bytes500Mb, part1); 248 | 249 | // requires no memory 250 | IntegerStream part0Stream = createStream(part0, 0, bytes500Mb); 251 | IntegerStream part1Stream = createStream(part1, 0, bytes500Mb); 252 | mergeTo(part0Stream, part1Stream, dstFile); 253 | } 254 | 255 | deleteFile(part0); 256 | deleteFile(part1); 257 | } 258 | 259 | private void deleteFile(String filename) {} 260 | private void mergeTo(IntegerStream a, IntegerStream b, String outputFile) { 261 | FileStream fileStream = openFile(outputFile); 262 | int aNum = a.next(); 263 | int bNum = b.next(); 264 | while(a.hasNext() || b.hasNext()) { 265 | if(bNum < 0) { 266 | writeInt(fileStream, aNum); 267 | aNum = a.next(); 268 | continue; 269 | } 270 | 271 | if(aNum < 0) { 272 | writeInt(fileStream, bNum); 273 | bNum = b.next(); 274 | continue; 275 | } 276 | 277 | if(aNum < bNum) { 278 | writeInt(fileStream, aNum); 279 | aNum = a.next(); 280 | } else { 281 | writeInt(fileStream, bNum); 282 | bNum = b.next(); 283 | } 284 | } 285 | } 286 | 287 | private FileStream openFile(String filename) {return null;} 288 | private void writeInt(FileStream file, int num) {} 289 | 290 | private void sort500MbTo(String srcFilename, int startPoint, String dstFilename) { 291 | int[] numbers = loadAndSort(srcFilename, 0, bytes500Mb); 292 | writeToFile(numbers, dstFilename); 293 | numbers = null; 294 | System.gc(); 295 | } 296 | 297 | private int[] loadAndSort(String filename, int startPoint, int size) { 298 | int[] numbers = load(filename, startPoint, size); 299 | mergesort(numbers); 300 | return numbers; 301 | } 302 | 303 | private int[] load(String filename, int startPoint, int size) { 304 | IntegerStream stream = 305 | createStream(filename, size, startPoint); 306 | return stream.toArray(); 307 | } 308 | 309 | public IntegerStream createStream(String filename, int readSize, int startPoint) { 310 | return new IntegerStream(filename, readSize, startPoint); 311 | } 312 | 313 | // performs an in-line mergesort 314 | public void mergesort(int[] numbers) {} 315 | private void writeToFile(int[] numbers, String filename) {} 316 | 317 | class IntegerStream { 318 | private final String filename; 319 | // the amount of data to read from the filename, 320 | // after that stop reading and claim there are no more numbers. 321 | private final int readSize; 322 | // point at which to start reading from the file 323 | private final int startPoint; 324 | 325 | public IntegerStream(String filename, int readSize, int startPoint) { 326 | this.filename = filename; 327 | this.readSize = readSize; 328 | this.startPoint = startPoint; 329 | } 330 | 331 | // returns Integer.MIN_VALUE if no more numbers available 332 | public int next() { 333 | // loads the next number from the stream 334 | return 0; 335 | } 336 | 337 | public boolean hasNext() { 338 | return false; 339 | } 340 | 341 | // reads all the remaining numbers and builds an array of size readSize - read 342 | public int[] toArray() { 343 | return new int[0]; 344 | } 345 | } 346 | } 347 | 348 | class FileStream { 349 | public void writeInt(int num) {} 350 | } 351 | 352 | // If you are writing a web crawler how do you avoid going into infinite recursion? 353 | // By keeping a Set of all visited paths as you visit them. 354 | class WebCrawler { 355 | 356 | public List crawl(Url url) { 357 | List data = new ArrayList<>(); 358 | Set visitedUrls = new HashSet<>(); 359 | Deque toVisitUrls = new ArrayDeque<>(); 360 | toVisitUrls.push(url); 361 | while(!toVisitUrls.isEmpty()) { 362 | Url urlToVisit = toVisitUrls.pop(); 363 | crawlPage(urlToVisit, data, visitedUrls); 364 | crawlUrls(urlToVisit, visitedUrls, toVisitUrls); 365 | } 366 | 367 | return data; 368 | } 369 | 370 | private Object extract(Url page) { 371 | return new Object(); 372 | } 373 | 374 | private void crawlPage(Url url, List data, Set visitedUrls) { 375 | data.add(extract(url)); 376 | visitedUrls.add(url); 377 | } 378 | 379 | private void crawlUrls(Url url, Set visitedUrls, Deque toVisitUrls) { 380 | for(Url foundUrl : crawlUrls(url)) { 381 | if(!visitedUrls.contains(foundUrl)) { 382 | toVisitUrls.push(foundUrl); 383 | } 384 | } 385 | } 386 | 387 | private Url[] crawlUrls(Url url) { 388 | return new Url[0]; 389 | } 390 | } 391 | 392 | class Url { 393 | } 394 | 395 | // There *are* 10 billion urls in the internet you are interested 396 | // in crawling, you have a way of reading them all, but now you 397 | // want to detect duplicates within those 10 billion urls. 398 | // How would you do this? 399 | // Problem: Big amount of data, how much? 400 | // 10*10^9 = 10^10 URLS 401 | // 100 chars each = 100 * 2 byte = 200 bytes each (assuming each char is 2 bytes) 402 | // 20^12 bytes = 20^9 Kbytes = 20^6 Mbytes = 20^3 Gb = 2 Tb 403 | -------------------------------------------------------------------------------- /test/Chapter4Test.java: -------------------------------------------------------------------------------- 1 | package chapter4; 2 | 3 | import static debug.Debugger.DEBUG; 4 | import static org.hamcrest.Matcher.*; 5 | import static org.hamcrest.MatcherAssert.*; 6 | import static org.hamcrest.core.Is.*; 7 | import static org.hamcrest.core.IsEqual.*; 8 | import static org.hamcrest.core.IsNull.*; 9 | 10 | 11 | import java.lang.*; 12 | import java.util.*; 13 | import java.util.stream.*; 14 | 15 | import org.hamcrest.core.*; 16 | import org.junit.*; 17 | import org.junit.rules.*; 18 | import structures.*; 19 | import test.*; 20 | 21 | public class Chapter4Test extends ChapterTestBase { 22 | 23 | BinaryTreeBalanceChecker balanceChecker = new BinaryTreeBalanceChecker(); 24 | TreeFromSortedNumbersCreator treeCreator = new TreeFromSortedNumbersCreator(); 25 | ListsFromTreeLevelsCreator listsFromLevels = new ListsFromTreeLevelsCreator(); 26 | 27 | 28 | @Test 29 | public void testBinaryTreeBalanceChecker() { 30 | // given 31 | BinaryTreeNode node = node("A"); 32 | node.left(node("B")); 33 | node.right(node("C")); 34 | node.left().left(node("D")); 35 | node.left().left().left(node("H")); 36 | node.left().left().right(node("I")); 37 | node.left().right(node("E")); 38 | node.right().left(node("F")); 39 | node.right().right(node("G")); 40 | 41 | 42 | // when 43 | boolean actual = balanceChecker.check(node); 44 | 45 | // then 46 | areEqual(actual, true); 47 | 48 | // when I make the tree imbalanced 49 | node.left().left().right().right(node("J")); 50 | // then 51 | areEqual(balanceChecker.check(node), false); 52 | } 53 | 54 | private static BinaryTreeNode node(T value) { 55 | return new BinaryTreeNode(value); 56 | } 57 | 58 | @Test 59 | public void testPathChecker() { 60 | // given 61 | DirectedNode x = dirNode("X"); 62 | DirectedNode y = dirNode("Y"); 63 | DirectedNode z = dirNode("Z"); 64 | 65 | DirectedNode a = dirNode("A"); 66 | DirectedNode b = dirNode("B"); 67 | DirectedNode c = dirNode("C"); 68 | DirectedNode d = dirNode("D"); 69 | DirectedNode e = dirNode("E"); 70 | DirectedNode f = dirNode("F"); 71 | DirectedNode g = dirNode("G"); 72 | DirectedNode h = dirNode("H"); 73 | DirectedNode i = dirNode("I"); 74 | DirectedNode j = dirNode("J"); 75 | DirectedNode k = dirNode("K"); 76 | DirectedNode l = dirNode("L"); 77 | 78 | linkTo(x, b, a); 79 | linkTo(y, i, h, g); 80 | linkTo(z, k, l); 81 | 82 | linkTo(a, b, c); 83 | linkTo(b, c, d); 84 | linkTo(c, e, d); 85 | linkTo(d, e, x); 86 | linkTo(e, h, i); 87 | linkTo(f, d, e); 88 | linkTo(g, f, e); 89 | linkTo(j, d, f, k, l); 90 | linkTo(l, k); 91 | 92 | // then when 93 | PathChecker checker = new PathChecker(); 94 | areEqual(checker.pathExists(x, y), true); 95 | areEqual(checker.pathExists(y, x), true); 96 | 97 | areEqual(checker.pathExists(y, z), false); 98 | areEqual(checker.pathExists(z, y), false); 99 | 100 | areEqual(checker.pathExists(z, x), false); 101 | areEqual(checker.pathExists(x, z), false); 102 | } 103 | 104 | private static DirectedNode dirNode(T value) { 105 | return new DirectedNode(value); 106 | } 107 | 108 | private static void linkTo(DirectedNode source, DirectedNode ... destinations) { 109 | for(DirectedNode destination : destinations) { 110 | source.linkTo(destination); 111 | } 112 | } 113 | 114 | @Test 115 | public void testTreeFromSortedNumbersCreator() { 116 | // given 117 | BinaryTreeNode five = node(5); 118 | BinaryTreeNode three = node(3); 119 | BinaryTreeNode seven = node(7); 120 | BinaryTreeNode two = node(2); 121 | BinaryTreeNode four = node(4); 122 | BinaryTreeNode six = node(6); 123 | BinaryTreeNode eight = node(8); 124 | BinaryTreeNode one = node(1); 125 | BinaryTreeNode nine = node(9); 126 | 127 | // level 0 128 | five.left(three);five.right(eight); 129 | // level 1 130 | three.left(two);three.right(four); 131 | eight.left(seven);eight.right(nine); 132 | // level 2 133 | two.left(one); 134 | seven.left(six); 135 | 136 | int[] numbers = { 1,2,3,4,5,6,7,8,9 }; 137 | 138 | // when 139 | BinaryTreeNode tree = treeCreator.create(numbers); 140 | // then 141 | areEqual(tree, five); 142 | areEqual(balanceChecker.check(tree), true); 143 | } 144 | 145 | @Test 146 | public void testTreeFromSortedNumbersCreator_cases() { 147 | checkTreeFromSortedNumbersCreator(1); 148 | checkTreeFromSortedNumbersCreator(1, 2); 149 | checkTreeFromSortedNumbersCreator(1, 2, 3); 150 | checkTreeFromSortedNumbersCreator(1, 2, 3, 4); 151 | checkTreeFromSortedNumbersCreator(1, 2, 3, 4, 5); 152 | checkTreeFromSortedNumbersCreator(1, 2, 3, 4, 5, 6); 153 | } 154 | 155 | private void checkTreeFromSortedNumbersCreator(int ... numbers) { 156 | // when 157 | BinaryTreeNode tree = treeCreator.create(numbers); 158 | // then 159 | areEqual(balanceChecker.check(tree), true); 160 | } 161 | 162 | @Test 163 | public void testCreateListsFromTreeLevels() { 164 | // given 165 | BinaryTreeNode five = node(5); 166 | BinaryTreeNode three = node(3); 167 | BinaryTreeNode seven = node(7); 168 | BinaryTreeNode two = node(2); 169 | BinaryTreeNode four = node(4); 170 | BinaryTreeNode six = node(6); 171 | BinaryTreeNode eight = node(8); 172 | BinaryTreeNode one = node(1); 173 | BinaryTreeNode nine = node(9); 174 | 175 | // level 0 176 | five.left(three);five.right(eight); 177 | // level 1 178 | three.left(two);three.right(four); 179 | eight.left(seven);eight.right(nine); 180 | // level 2 181 | two.left(one); 182 | seven.left(six); 183 | 184 | // when 185 | structures.LinkedList result = listsFromLevels.create(five); 186 | 187 | // then 188 | DEBUG.println(result.toString()); 189 | areEqual(result.get(0).contains(five.value()), true); 190 | areEqual(result.get(0).size(), 1); 191 | 192 | areEqual(result.get(1).contains(eight.value()), true); 193 | areEqual(result.get(1).contains(three.value()), true); 194 | areEqual(result.get(1).size(), 2); 195 | 196 | 197 | areEqual(result.get(2).contains(four.value()), true); 198 | areEqual(result.get(2).contains(two.value()), true); 199 | areEqual(result.get(2).contains(nine.value()), true); 200 | areEqual(result.get(2).contains(seven.value()), true); 201 | areEqual(result.get(2).size(), 4); 202 | 203 | areEqual(result.get(3).contains(six.value()), true); 204 | areEqual(result.get(3).contains(one.value()), true); 205 | areEqual(result.get(3).size(), 2); 206 | } 207 | 208 | @Test 209 | public void testBinarySearchTreeChecker() { 210 | // given 211 | BinaryTreeNode one = node(1); 212 | BinaryTreeNode two = node(2); 213 | BinaryTreeNode three = node(3); 214 | BinaryTreeNode four = node(4); 215 | BinaryTreeNode five = node(5); 216 | BinaryTreeNode six = node(6); 217 | BinaryTreeNode seven = node(7); 218 | BinaryTreeNode eight = node(8); 219 | BinaryTreeNode nine = node(9); 220 | 221 | five.left(three);five.right(seven); 222 | 223 | three.left(two);three.right(four); 224 | seven.left(six);seven.right(nine); 225 | 226 | two.left(one);nine.left(eight); 227 | BinarySearchTreeChecker checker = new BinarySearchTreeChecker(); 228 | 229 | // when given a valid search tree 230 | boolean actual = checker.check(five); 231 | 232 | // then 233 | areEqual(actual, true); 234 | 235 | // when it becomes a non-search tree 236 | three.left(null); three.right(two); 237 | // then 238 | areEqual(checker.check(five), false); 239 | } 240 | 241 | @Test 242 | public void testNextInOrderNodeFinder() { 243 | // given 244 | BinaryTreeNode one = node(1); 245 | BinaryTreeNode two = node(2); 246 | BinaryTreeNode three = node(3); 247 | BinaryTreeNode four = node(4); 248 | BinaryTreeNode five = node(5); 249 | BinaryTreeNode six = node(6); 250 | BinaryTreeNode seven = node(7); 251 | BinaryTreeNode eight = node(8); 252 | BinaryTreeNode nine = node(9); 253 | 254 | five.left(three);five.right(seven); 255 | 256 | three.left(two);three.right(four); 257 | seven.left(six);seven.right(nine); 258 | 259 | two.left(one);nine.left(eight); 260 | 261 | NextInOrderNodeFinder finder = new NextInOrderNodeFinder(); 262 | 263 | // when given the root then find left-most leaf on right sub-tree 264 | areEqual(finder.find(five), six); 265 | // when the left-most child get its parent 266 | areEqual(finder.find(one), two); 267 | // when given a middle node, find left-most leaf on right sub-tree 268 | areEqual(finder.find(seven), eight); 269 | // when given the last node, return null 270 | areEqual(finder.find(nine), null); 271 | // when given the last node, return null 272 | areEqual(finder.find(four), five); 273 | } 274 | 275 | 276 | @Test 277 | public void testHashedCommonAncestorFinder() { 278 | checkCommonAncestorFinder(new HashedCommonAncestorFinder()); 279 | } 280 | 281 | @Test 282 | public void testTraversedCommonAncestorFinder() { 283 | checkCommonAncestorFinder(new TraversedCommonAncestorFinder()); 284 | } 285 | 286 | public void checkCommonAncestorFinder(CommonAncestorFinder finder) { 287 | // given 288 | BinaryTreeNode one = node(1); 289 | BinaryTreeNode two = node(2); 290 | BinaryTreeNode three = node(3); 291 | BinaryTreeNode four = node(4); 292 | BinaryTreeNode five = node(5); 293 | BinaryTreeNode six = node(6); 294 | BinaryTreeNode seven = node(7); 295 | BinaryTreeNode eight = node(8); 296 | BinaryTreeNode nine = node(9); 297 | 298 | five.left(three);five.right(seven); 299 | 300 | three.left(two);three.right(four); 301 | seven.left(six);seven.right(nine); 302 | 303 | two.left(one);nine.left(eight); 304 | // when 305 | areEqual(finder.find(one, one).value(), one.value()); 306 | areEqual(finder.find(one, two).value(), two.value()); 307 | areEqual(finder.find(two, one).value(), two.value()); 308 | areEqual(finder.find(one, three).value(), three.value()); 309 | areEqual(finder.find(three, one).value(), three.value()); 310 | areEqual(finder.find(nine, one).value(), five.value()); 311 | areEqual(finder.find(one, nine).value(), five.value()); 312 | } 313 | 314 | @Test 315 | public void testSubTreeChecker() { 316 | // given 317 | BinaryTreeNode one = node(1); 318 | BinaryTreeNode two = node(2); 319 | BinaryTreeNode three = node(3); 320 | BinaryTreeNode four = node(4); 321 | BinaryTreeNode five = node(5); 322 | BinaryTreeNode six = node(6); 323 | BinaryTreeNode seven = node(7); 324 | BinaryTreeNode eight = node(8); 325 | BinaryTreeNode nine = node(9); 326 | 327 | five.left(three);five.right(seven); 328 | 329 | three.left(two);three.right(four); 330 | seven.left(six);seven.right(nine); 331 | 332 | two.left(one);nine.left(eight); 333 | 334 | BinaryTreeNode eleven = node(11); 335 | eleven.left(node(10));eleven.right(node(12)); 336 | BinaryTreeNode otherThree = node(3); 337 | BinaryTreeNode otherSeven = node(7); 338 | otherSeven.left(node(4));otherSeven.right(node(9)); 339 | 340 | 341 | SubtreeChecker checker = new SubtreeChecker(); 342 | // when 343 | isTrue(checker.check(five, one)); 344 | isTrue(checker.check(five, two)); 345 | isTrue(checker.check(five, three)); 346 | isTrue(checker.check(five, four)); 347 | isTrue(checker.check(five, five)); 348 | isTrue(checker.check(five, six)); 349 | isTrue(checker.check(five, seven)); 350 | isTrue(checker.check(five, eight)); 351 | isTrue(checker.check(five, nine)); 352 | isTrue(checker.check(five, five)); 353 | 354 | isFalse(checker.check(five, eleven)); 355 | isFalse(checker.check(five, otherThree)); 356 | isFalse(checker.check(five, otherSeven)); 357 | 358 | } 359 | 360 | @Test 361 | public void testBinaryTreeSumFinder() { 362 | // given 363 | BinaryTreeNode one = node(1); 364 | BinaryTreeNode two = node(2); 365 | BinaryTreeNode three = node(3); 366 | BinaryTreeNode four = node(4); 367 | BinaryTreeNode five = node(5); 368 | BinaryTreeNode six = node(6); 369 | BinaryTreeNode seven = node(7); 370 | BinaryTreeNode eight = node(8); 371 | BinaryTreeNode nine = node(9); 372 | 373 | five.left(three);five.right(seven); 374 | 375 | three.left(two);three.right(four); 376 | seven.left(six);seven.right(nine); 377 | 378 | two.left(one);nine.left(eight); 379 | 380 | BinaryTreeNode eleven = node(11); 381 | eleven.left(node(10));eleven.right(node(12)); 382 | BinaryTreeNode otherThree = node(3); 383 | BinaryTreeNode otherSeven = node(7); 384 | otherSeven.left(node(4));otherSeven.right(node(9)); 385 | 386 | BinaryTreeSumFinder finder = new BinaryTreeSumFinder(); 387 | 388 | // when 389 | List actual = finder.find(five, 8); 390 | // then 391 | areEqual(actual.size(), 2); 392 | areEqual(actual.get(0), "3->5"); 393 | areEqual(actual.get(1), "8"); 394 | 395 | actual = finder.find(five, 6); 396 | areEqual(actual.size(), 2); 397 | areEqual(actual.get(0), "1->2->3"); 398 | areEqual(actual.get(1), "6"); 399 | 400 | actual = finder.find(five, 19); 401 | areEqual(actual.size(), 0); 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /src/Chapter3.java: -------------------------------------------------------------------------------- 1 | package chapter3; 2 | 3 | import static debug.Debugger.DEBUG; 4 | 5 | import java.lang.*; 6 | import java.util.*; 7 | 8 | import structures.*; 9 | 10 | 11 | class CoordinatedStackMaker { 12 | 13 | public CoordinatedArrayStack[] makeStacks(Object[] data, int numberOfStacks) { 14 | CoordinatedArrayStack[] stacks = new CoordinatedArrayStack[numberOfStacks]; 15 | for(int i = 0; i < numberOfStacks; i++) { 16 | stacks[i] = new CoordinatedArrayStack(data, i, numberOfStacks); 17 | } 18 | 19 | return stacks; 20 | } 21 | } 22 | 23 | class CoordinatedArrayStack { 24 | private final Object[] storage; 25 | private final int base; 26 | private final int multiplier; 27 | private int size; 28 | 29 | public CoordinatedArrayStack(Object[] storage, int base, int multiplier) { 30 | this.storage = storage; 31 | this.base = base; 32 | this.multiplier = multiplier; 33 | this.size = 0; 34 | } 35 | public void push(Object item) { 36 | int index = base + (size * multiplier); 37 | storage[index] = item; 38 | size++; 39 | } 40 | 41 | public Object pop() { 42 | if(size == 0) { return null; } 43 | Object data = peek(); 44 | 45 | int index = base + ((size-1) * multiplier); 46 | storage[index] = null; 47 | size--; 48 | 49 | return data; 50 | } 51 | 52 | public Object peek() { 53 | if(size == 0) { return null; } 54 | return storage[base + ((size -1) * multiplier)]; 55 | } 56 | 57 | public int size() { 58 | return size; 59 | } 60 | 61 | private static class Item { 62 | final int index; 63 | final int prevIndex; 64 | final Object data; 65 | public Item(int prevIndex, Object data, int index) { 66 | this.prevIndex = prevIndex; 67 | this.data = data; 68 | this.index = index; 69 | } 70 | } 71 | } 72 | 73 | // push:4 - min=4 | 4 | 4 74 | // pusn:6 - min=4 | 4->6 | 4->6 75 | // push:5 - min=4 | 4->5->6 | 4->5->6 // THIS could cost O(N) 76 | // push:3 - min=3 | 4->5->6->3 | 3->4->5->6 77 | // push:2 - min=2 | 4->5->6->3->2 | 2->3->4->5->6 78 | 79 | // What can I do to push() the elements in O(1) in such 80 | // a way that I can keep track of min() when pop() in O(1) ? 81 | 82 | // pop(2) - min=3 | 4->5->6->3 83 | // pop(3) - min=4 | 4->5->6 84 | // pop(6) - min=4 | 4->5 85 | // pop(5) - min=4 | 4 86 | 87 | @SuppressWarnings({"unchecked"}) 88 | class MinTrackingStack { 89 | private SingleLinkNode head; 90 | private SingleLinkNode> min; 91 | 92 | public void push(int value) { 93 | SingleLinkNode node = new SingleLinkNode<>(value); 94 | if(head == null) { 95 | this.head = node; 96 | this.min = new SingleLinkNode<>(head); 97 | } else { 98 | if (value < min.value().value().intValue()) { 99 | SingleLinkNode> newMin = new SingleLinkNode<>(node); 100 | newMin.next(min); 101 | min = newMin; 102 | } 103 | 104 | node.next(head); 105 | head = node; 106 | } 107 | } 108 | 109 | public int pop() { 110 | if(head == null) { throw new RuntimeException("No more values!"); } 111 | 112 | if(min.value() == head) { 113 | min = min.next(); 114 | } 115 | 116 | SingleLinkNode popped = head; 117 | head = head.next(); 118 | return popped.value().intValue(); 119 | } 120 | 121 | public int min() { 122 | if(min == null) { throw new RuntimeException("No more values!"); } 123 | 124 | return min.value().value().intValue(); 125 | } 126 | } 127 | 128 | @SuppressWarnings({"unchecked"}) 129 | class SetOfStacks { 130 | private volatile int size; 131 | private volatile int stackCount; 132 | private final int threshold; 133 | private final GrowingArray> stacks; 134 | 135 | public SetOfStacks(int threshold) { 136 | this.threshold = threshold; 137 | this.stacks = (GrowingArray>) (GrowingArray) new GrowingArray(LinkedListStack.class, 10); 138 | size = 0; 139 | } 140 | 141 | public void push(T item) { 142 | if(stackCount() == 0) { 143 | this.stacks.add(new LinkedListStack()); 144 | } 145 | 146 | LinkedListStack stack = this.stacks.getLast(); 147 | if(stack.size() == threshold) { 148 | stack = new LinkedListStack(); 149 | this.stacks.add(stack); 150 | } 151 | 152 | stack.push(item); 153 | this.size++; 154 | } 155 | 156 | public T peek() { 157 | if(this.size() == 0) { 158 | throw new IllegalStateException("No elements in stack"); 159 | } 160 | 161 | return this.stacks.getLast().peek(); 162 | } 163 | 164 | public T pop() { 165 | if(this.size() == 0) { 166 | throw new IllegalStateException("No elements in stack"); 167 | } 168 | 169 | T item = this.stacks.getLast().pop(); 170 | this.size--; 171 | 172 | if(this.stacks.getLast().size() == 0) { 173 | this.stacks.removeLast(); 174 | } 175 | 176 | return item; 177 | } 178 | 179 | public T get(int index) { 180 | if(this.size() <= index) { 181 | throw new IllegalStateException("Index out of bounds"); 182 | } 183 | 184 | int stackIndex = index / threshold; 185 | LinkedListStack stack = this.stacks.get(stackIndex); 186 | 187 | int itemIndex = index != 0 ? index % threshold : 0; 188 | return getItemAtIndex(stack, itemIndex); 189 | } 190 | 191 | private T getItemAtIndex(LinkedListStack stack, int index) { 192 | int itemsToPop = stack.size() - index - 1; 193 | LinkedListStack temp = new LinkedListStack(); 194 | for(int i = 0; i < itemsToPop; i++) { 195 | temp.push(stack.pop()); 196 | } 197 | 198 | T item = stack.peek(); 199 | 200 | for(int i = 0; i < itemsToPop; i++) { 201 | stack.push(temp.pop()); 202 | } 203 | 204 | return item; 205 | } 206 | 207 | public int stackCount() { 208 | return this.stacks.count(); 209 | } 210 | 211 | public int size() { 212 | return this.size; 213 | } 214 | } 215 | 216 | @SuppressWarnings({"unchecked"}) 217 | class TowersOfHanoiSolver { 218 | 219 | public int solve(int numberOfDisks) { 220 | LinkedListStack[] poles = new LinkedListStack[] { 221 | new LinkedListStack(), 222 | new LinkedListStack(), 223 | new LinkedListStack() 224 | }; 225 | 226 | for(int i = 0; i < numberOfDisks; i++) { 227 | poles[0].push(numberOfDisks - i); 228 | } 229 | 230 | return moveDiscs(poles, numberOfDisks, 0, 2); 231 | } 232 | 233 | private int moveDiscs( 234 | LinkedListStack[] poles, int numberOfDisks, int from, int to) { 235 | if(numberOfDisks == 0) { 236 | return 0; 237 | } 238 | 239 | int intermediate = getIntermediatePoleIndex(from, to); 240 | 241 | return moveDiscs(poles, numberOfDisks - 1, from, intermediate) 242 | + moveDisc(poles, from, to) 243 | + moveDiscs(poles, numberOfDisks - 1, intermediate, to); 244 | } 245 | 246 | private static int getIntermediatePoleIndex(int from, int to) { 247 | return 3 - from - to; 248 | } 249 | 250 | int moveDisc(LinkedListStack[] poles, int from, int to) { 251 | Integer movingDisc = poles[from].pop(); 252 | 253 | if(poles[to].size() > 0 && movingDisc > poles[to].peek()) { 254 | String msg = String.format( 255 | "Cannot move a bigger disc (%s) on top of a smaller one (%s).", 256 | movingDisc, 257 | poles[to].peek() 258 | ); 259 | throw new IllegalStateException(msg); 260 | } 261 | 262 | poles[to].push(movingDisc); 263 | 264 | return 1; 265 | } 266 | } 267 | 268 | /* 269 | * TRUTH: I was distracted, because it was getting late and I wanted to finish! 270 | * 271 | * The error I made here was to rush to an answer, and determine that it was the 272 | * best, without really asking myself, how can I avoid the expensive operation? 273 | * + What I have to do is: identify the expensive operation (popping and pushing everytime) 274 | * and ask myself: 275 | * - Do I really need to do it in this expensive manner? 276 | * - Why do I need to do it in this expensive manner? (Answer: XXX) 277 | * - Why do I need XXX? (Answer: YYY) 278 | * - Why do I need YYY? (Answer: ZZZ) Result --> AHA! 279 | */ 280 | class QueueMadeOfStacks { 281 | LinkedListStack pushStack; 282 | LinkedListStack popStack; 283 | 284 | public QueueMadeOfStacks() { 285 | this.pushStack = new LinkedListStack<>(); 286 | this.popStack = new LinkedListStack<>(); 287 | } 288 | 289 | public void queue(T item) { 290 | this.pushStack.push(item); 291 | } 292 | 293 | public T dequeue() { 294 | if(pushStack.size() == 0 && popStack.size() == 0) { 295 | throw new IllegalStateException("Queue is empty, cannot pop"); 296 | } 297 | 298 | T item = peek(); 299 | popStack.pop(); 300 | 301 | return item; 302 | } 303 | 304 | public T peek() { 305 | flushPushStack(); 306 | return popStack.peek(); 307 | } 308 | 309 | public int size() { 310 | return this.pushStack.size() + this.popStack.size(); 311 | } 312 | 313 | private void flushPushStack() { 314 | if(popStack.size() == 0) { 315 | while(pushStack.size() > 0) { popStack.push(pushStack.pop()); } 316 | } 317 | } 318 | } 319 | 320 | 321 | class StackSorter { 322 | private static int LEFT = 0; 323 | private static int RIGHT = 1; 324 | 325 | public void sort(LinkedListStack stack) { 326 | if(stack == null) { throw new RuntimeException("Null stack!"); } 327 | if(stack.size() == 0) { return; } 328 | 329 | LinkedListStack aux = new LinkedListStack<>(); 330 | sort(stack, aux, new int[] { -1, -1 }, "ASC"); 331 | } 332 | 333 | public int[] partition( 334 | Integer pivot, 335 | LinkedListStack left, 336 | LinkedListStack right, 337 | int leftSize, 338 | String order) { 339 | 340 | if(isEmpty(left) || leftSize == 0) { 341 | return new int[] { 0, 0 }; 342 | } 343 | 344 | boolean leftSizeUndetermined = leftSize < 0; 345 | int[] foundSizes = new int[2]; 346 | int rightSize = 0; 347 | int i = 0; 348 | DEBUG.println("\npartition pivot: " + pivot + ", left: " + left + ", leftSize: " + leftSize); 349 | DEBUG.println("lo: " + left + ", hi: " + right); 350 | 351 | while( 352 | !isEmpty(left) && 353 | comparePivot(pivot, left.peek(), order) && 354 | (leftSize > 0 || leftSizeUndetermined) 355 | ) { 356 | right.push(left.pop()); 357 | rightSize++; 358 | leftSize--; 359 | } 360 | if(isEmpty(left) || leftSize == 0) { 361 | // everything was larger than the pivot 362 | // eventually we will hit this 363 | foundSizes[LEFT] = 0; 364 | foundSizes[RIGHT] = rightSize; 365 | } else { 366 | DEBUG.println("Stopped at: " + left.peek() + " with leftSize: " + leftSize); 367 | DEBUG.println("lo: " + left + ", hi: " + right); 368 | 369 | Integer lessThanPivot = left.pop(); 370 | int[] partSizes = partition(pivot, left, right, leftSize - 1, order); 371 | left.push(lessThanPivot); 372 | foundSizes[LEFT] = partSizes[LEFT] + 1; 373 | foundSizes[RIGHT] = partSizes[RIGHT] + rightSize; 374 | } 375 | 376 | return foundSizes; 377 | } 378 | 379 | private void sort(LinkedListStack left, LinkedListStack right, int[] sizes, String order) { 380 | DEBUG.println("--- sort sizes[LEFT]: "+ sizes[LEFT] + ", sizes[RIGHT]: " + sizes[RIGHT] + " -------"); 381 | if(sizes[LEFT] == 1 || sizes[LEFT] == 0 || isEmpty(left)) { 382 | return; 383 | } 384 | 385 | if(sizes[LEFT] == 2) { 386 | Integer first = left.pop(); 387 | Integer second = left.pop(); 388 | 389 | if(areSorted(first, second, order)) { 390 | left.push(second); // if they are already sorted, then 391 | left.push(first); // put them back the way they were 392 | } else { 393 | left.push(first); 394 | left.push(second); 395 | } 396 | 397 | return; 398 | } 399 | 400 | Integer pivot = left.pop(); 401 | int[] partitionSizes = partition(pivot, left, right, sizes[LEFT] - 1, order); 402 | DEBUG.println("--- pivot: " + pivot + ", left: " + left + ", right: " + right); 403 | // sort left numbers 404 | sort(left, right, partitionSizes, order); 405 | // sort right numbers in opposite order so that we can push them back in correct order 406 | String rightOrder = oppositeOrder(order); 407 | sort(right, left, new int[] { partitionSizes[RIGHT], partitionSizes[LEFT] }, rightOrder); 408 | 409 | DEBUG.println("merge: " + left + " =>" + pivot + "<= " + right); 410 | mergePartitions(left, pivot, right, partitionSizes[RIGHT]); 411 | } 412 | 413 | private void mergePartitions( 414 | LinkedListStack left, 415 | Integer pivot, 416 | LinkedListStack right, 417 | int rightSize) { 418 | // merged results end up in the left stack 419 | left.push(pivot); 420 | for(int i = 0; i < rightSize; i++) { 421 | left.push(right.pop()); 422 | } 423 | } 424 | 425 | private boolean comparePivot(int pivot, int b, String order) { 426 | if(order.equals("ASC")) { return b >= pivot; } 427 | if(order.equals("DESC")) { return b <= pivot; } 428 | throw new IllegalStateException("Invalid order: " + order); 429 | } 430 | 431 | private boolean areSorted(int first, int second, String order) { 432 | boolean ascending = order.equals("ASC"); 433 | boolean firstIsGreater = first > second; 434 | boolean descending = !ascending; 435 | boolean firstIsSmaller = !firstIsGreater; 436 | return (firstIsGreater && ascending) || (firstIsSmaller && descending); 437 | } 438 | 439 | private static String oppositeOrder(String order) { 440 | return order.equals("ASC") ? "DESC" : "ASC"; 441 | } 442 | private boolean isEmpty(LinkedListStack stack) { 443 | return stack.size() == 0; 444 | } 445 | } 446 | 447 | 448 | class AnimalShelter { 449 | 450 | private AnimalLink oldest; 451 | private AnimalLink youngest; 452 | 453 | private AnimalLink oldestCat; 454 | private AnimalLink oldestDog; 455 | private AnimalLink youngestCat; 456 | private AnimalLink youngestDog; 457 | 458 | public void enqueue(Animal animal) { 459 | AnimalLink animalLink = new AnimalLink(animal); 460 | if(oldest == null) { youngest = oldest = animalLink; } 461 | else { 462 | animalLink.prevAnimal(youngest); 463 | youngest.nextAnimal(animalLink); 464 | youngest = animalLink; 465 | } 466 | 467 | if(animal.type().equals("cat")) { 468 | if(oldestCat == null) { youngestCat = oldestCat = animalLink; } 469 | else { 470 | animalLink.prevOfType(youngestCat); 471 | youngestCat.nextOfType(animalLink); 472 | youngestCat = animalLink; 473 | } 474 | } else { // assume it is a dog 475 | if(oldestDog == null) { youngestDog = oldestDog = animalLink; } 476 | else { 477 | animalLink.prevOfType(youngestDog); 478 | youngestDog.nextOfType(animalLink); 479 | youngestDog = animalLink; 480 | } 481 | } 482 | } 483 | 484 | public Optional dequeue() { 485 | if(oldest == null) { return Optional.empty(); } 486 | 487 | if(oldest == oldestDog) { 488 | return dequeueDog(); 489 | } else if (oldest == oldestCat) { 490 | return dequeueCat(); 491 | } else { 492 | throw new RuntimeException( 493 | "Oldest was neither oldestCat nor oldestDog. \n" + 494 | "oldest: " + oldest + "\n" + 495 | "oldestDog: " + oldestDog + "\n" + 496 | "oldestCat: " + oldestCat + "\n"); 497 | } 498 | } 499 | 500 | public Optional dequeueDog() { 501 | DEBUG.println("Removing dog!"); 502 | if(oldestDog == null) { return Optional.empty(); } 503 | 504 | Optional result = Optional.of(oldestDog.getAnimal()); 505 | 506 | dequeueAnimal(oldestDog); 507 | 508 | if(oldestDog == youngestDog) { youngestDog = null; } 509 | oldestDog = oldestDog.nextOfType(); 510 | 511 | return result; 512 | } 513 | 514 | public Optional dequeueCat() { 515 | DEBUG.println("Removing cat!"); 516 | if(oldestCat == null) { return Optional.empty(); } 517 | 518 | Optional result = Optional.of(oldestCat.getAnimal()); 519 | dequeueAnimal(oldestCat); 520 | 521 | if(oldestCat == youngestCat) { youngestCat = null; } 522 | oldestCat = oldestCat.nextOfType(); 523 | 524 | return result; 525 | } 526 | 527 | private void dequeueAnimal(AnimalLink animalLink) { 528 | if(animalLink.prevAnimal() != null) { 529 | animalLink.prevAnimal().nextAnimal(animalLink.nextAnimal()); 530 | } 531 | 532 | if(animalLink.nextAnimal() != null) { 533 | animalLink.nextAnimal().prevAnimal(animalLink.prevAnimal()); 534 | } 535 | 536 | if(animalLink.nextOfType() != null) { 537 | animalLink.nextOfType().prevOfType(null); 538 | } 539 | 540 | if(animalLink == oldest) { 541 | oldest = animalLink.nextAnimal(); 542 | } 543 | 544 | if(animalLink == youngest) { 545 | youngest = animalLink.prevAnimal(); 546 | } 547 | } 548 | 549 | static class AnimalLink { 550 | private final Animal animal; 551 | private AnimalLink nextOfType; 552 | private AnimalLink nextAnimal; 553 | 554 | private AnimalLink prevOfType; 555 | private AnimalLink prevAnimal; 556 | 557 | public AnimalLink(Animal animal) { 558 | this.animal = animal; 559 | } 560 | 561 | public Animal getAnimal() { return this.animal; } 562 | 563 | private void nextOfType(AnimalLink next) { 564 | this.nextOfType = next; 565 | } 566 | 567 | public AnimalLink nextOfType() { 568 | return this.nextOfType; 569 | } 570 | 571 | public void nextAnimal(AnimalLink next) { 572 | this.nextAnimal = next; 573 | } 574 | 575 | public AnimalLink nextAnimal() { 576 | return this.nextAnimal; 577 | } 578 | 579 | private void prevOfType(AnimalLink prev) { 580 | this.prevOfType = prev; 581 | } 582 | 583 | public AnimalLink prevOfType() { 584 | return this.prevOfType; 585 | } 586 | 587 | public void prevAnimal(AnimalLink prev) { 588 | this.prevAnimal = prev; 589 | } 590 | 591 | public AnimalLink prevAnimal() { 592 | return this.prevAnimal; 593 | } 594 | 595 | public String toString() { return this.getAnimal().toString(); } 596 | } 597 | 598 | static class Animal { 599 | private static volatile int COUNT = 0; 600 | 601 | private final int age; 602 | private final String type; 603 | 604 | public Animal(String type) { 605 | this.type = type; 606 | this.age = ++COUNT; 607 | } 608 | 609 | public int age() { return this.age; } 610 | public String type() { return this.type; } 611 | 612 | public String toString() { return "{ age: " + age + ", type: " + type + " }"; } 613 | } 614 | } 615 | -------------------------------------------------------------------------------- /src/Chapter11.java: -------------------------------------------------------------------------------- 1 | package chapter11; 2 | 3 | import static debug.Debugger.DEBUG; 4 | 5 | import java.lang.*; 6 | import java.util.*; 7 | import java.util.stream.*; 8 | 9 | import structures.*; 10 | 11 | class SortedArrayMerger { 12 | 13 | public void merge(int[] a, int[] b) { 14 | if(a == null) { throw new RuntimeException("a should not be null."); } 15 | if(b == null) { throw new RuntimeException("b should not be null."); } 16 | if(a.length <= b.length) { 17 | throw new RuntimeException( 18 | "a's length (" + a.length + ") must be greater than b's length (" + b.length + ")." 19 | ); 20 | } 21 | 22 | int ai = a.length - b.length - 1, bi = b.length - 1; 23 | for(int ti = a.length - 1; bi >= 0; ti--) { 24 | if(ai >= 0 && a[ai] >= b[bi]) 25 | a[ti] = a[ai--]; 26 | else 27 | a[ti] = b[bi--]; 28 | } 29 | } 30 | } 31 | 32 | class RotatedArraySearcher { 33 | // sample: 34 | // array=[2, 3, 4, 5, 1] number=2 35 | // how to quickly find this number 3 36 | // I can go to the start 37 | // check 2 then the middle 4 and the end 1 38 | // check on which side the -valid- range is 39 | // in this case 2 - 4 40 | // if our number is in that range, look for it there 41 | // if our number is not there, then do the same but now 42 | // with the numbers on the other side 43 | public boolean contains(int[] rotatedArray, int number) { 44 | return contains(rotatedArray, number, 0, rotatedArray.length); 45 | } 46 | 47 | private boolean contains(int[] rotatedArray, int number, int startIndex, int length) { 48 | if(length == 0) { 49 | return false; 50 | } 51 | 52 | if(length == 1) { 53 | return rotatedArray[startIndex] == number; 54 | } 55 | 56 | if(length == 2) { 57 | return rotatedArray[startIndex] == number || rotatedArray[startIndex + 1] == number; 58 | } 59 | 60 | int middleIndex = startIndex + (length / 2); 61 | 62 | int leftRangeStart = rotatedArray[startIndex]; 63 | int leftRangeEnd = rotatedArray[middleIndex]; 64 | 65 | 66 | int rightRangeStart = rotatedArray[middleIndex + 1]; 67 | int rightRangeEnd = rotatedArray[startIndex + length - 1]; 68 | 69 | if(leftRangeStart <= leftRangeEnd) { // left is ordered 70 | if(number >= leftRangeStart && number <= leftRangeEnd) { // our number is in the left range 71 | return contains(rotatedArray, number, startIndex, middleIndex - startIndex + 1); 72 | } else { // our number is in the right range 73 | return contains( 74 | rotatedArray, 75 | number, 76 | middleIndex + 1, 77 | length - middleIndex - 1 78 | ); 79 | } 80 | } else { // right is ordered 81 | if(number >= rightRangeStart && number <= rightRangeEnd) { // our number is in the right range 82 | return contains( 83 | rotatedArray, 84 | number, 85 | middleIndex + 1, 86 | length - middleIndex - 1 87 | ); 88 | } else { // our number is in the left range 89 | return contains(rotatedArray, number, startIndex, middleIndex - startIndex + 1); 90 | } 91 | } 92 | } 93 | } 94 | 95 | 96 | // Write a method to sort an array of strings so that all the anagrams are next to each other 97 | // Questions: 98 | // 1. This means simply to put all words that are considered palindromes next to each other. 99 | class SortPalindromesFirst { 100 | 101 | public String[] sortPalindromes(String[] input) { 102 | String[] output = new String[input.length]; 103 | groupPalindromes(input, output); 104 | return output; 105 | } 106 | 107 | private void groupPalindromes(String[] inputs, String[] outputs) { 108 | int topDown = outputs.length - 1; 109 | int bottomUp = 0; 110 | for(String input : inputs) { 111 | if(isPalindrome(input)) { 112 | outputs[bottomUp] = input; 113 | bottomUp++; 114 | } else { 115 | outputs[topDown] = input; 116 | topDown--; 117 | } 118 | } 119 | } 120 | 121 | private boolean isPalindrome(String input) { 122 | int forwards = 0; 123 | int backwards = input.length() - 1; 124 | while(forwards < backwards) { 125 | if(input.charAt(forwards) != input.charAt(backwards)) { 126 | return false; 127 | } 128 | 129 | forwards++; 130 | backwards--; 131 | } 132 | 133 | return true; 134 | } 135 | } 136 | 137 | // If you had a 20 GB file with a string on each line, and were asked to 138 | // sort it, how would you do it? 139 | // 0. Assuming I can use about 1 GB of memory. 140 | // 1. Divide the 20 GB into 40 different sections of 500 Mb each 141 | // 2. Load each section of 500 Mb and sort it, then write it back to the file in their 142 | // corresponding section. 143 | // 3. Do a multiway mergesort, where we find the largest strings of each 144 | // section, and whatever we find, we put it in a new sorted file, so we just 145 | // keep appending to the new sorted file using the largest string of each 146 | // section, never keeping in memory more than 40 strings. 147 | 148 | 149 | class WhitespacesStringArraySearch { 150 | public int indexOf(String[] array, String key) { 151 | int lo = 0; 152 | int hi = array.length - 1; 153 | int length = hi - lo + 1; 154 | while(length >= 1) { 155 | DEBUG.println("hi: %s, lo: %s, length: %s", hi, lo, length); 156 | if(length == 1) { 157 | if(!array[hi].equals(key)) { 158 | return -1; 159 | } else { 160 | return hi; 161 | } 162 | } 163 | 164 | int middle = findMiddle(array, lo, lo + (length / 2)); 165 | DEBUG.println("middle: %s", middle); 166 | if(middle == -1) { 167 | if(length <= 2) { 168 | return -1; 169 | } 170 | // all whitespaces to the left 171 | lo = lo + (length / 2) + 1; 172 | } else { 173 | String middleKey = array[middle]; 174 | int comparison = key.compareTo(middleKey); 175 | DEBUG.println("middleKey: %s comparison: %s ", middleKey, comparison); 176 | if(comparison == 0) { 177 | return middle; 178 | } else if(comparison < 0) { 179 | hi = lo + (length / 2) - 1; 180 | } else { 181 | lo = middle + 1; 182 | } 183 | } 184 | 185 | length = hi - lo + 1; 186 | } 187 | 188 | return -1; 189 | } 190 | 191 | private int findMiddle(String[] array, int startIndex, int halfIndex) { 192 | for(int i = halfIndex; i >= startIndex; i--) { 193 | if(!array[i].equals("")) { 194 | return i; 195 | } 196 | } 197 | 198 | return -1; 199 | } 200 | } 201 | 202 | interface MatrixFinder { 203 | MatrixIndex indexOf(int[][] matrix, int key); 204 | } 205 | 206 | // [ 1, 3, 4, 9 ] 207 | // [ 2, 6, 7, 10 ] 208 | // [ 5, 8, 11, 12 ] 209 | class BadMatrixFinder implements MatrixFinder { 210 | private static final Object INC = new Object(); 211 | private static final Object DEC = new Object(); 212 | // According to my implementation, this should also have a binary search 213 | // over the columns once the range of rows is obtained, this should create 214 | // the minimal search space for a binary search of the elements to check. 215 | public MatrixIndex indexOf(int[][] matrix, int key) { 216 | if(matrix == null) { throw new RuntimeException("matrix cannot be null."); } 217 | if(matrix.length == 0 || matrix[0].length == 0) { 218 | throw new RuntimeException("matrix cannot be of size 0"); 219 | } 220 | 221 | if(matrix.length == 1 && matrix[0].length == 1) { 222 | return matrix[0][0] == key ? index(0, 0) : MatrixIndex.NOT_FOUND; 223 | } 224 | 225 | int startingEndIndexRow = findLastEndIndexRow( 226 | matrix, key, 0, matrix.length - 1, 0); 227 | if(startingEndIndexRow == -1) { 228 | return MatrixIndex.NOT_FOUND; 229 | } 230 | DEBUG.println("------------------"); 231 | int startingStartIndexRow = findLastStartIndexRow(matrix, key, startingEndIndexRow, 232 | startingEndIndexRow, 233 | matrix.length - 1, 234 | matrix.length - 1); 235 | 236 | if(startingStartIndexRow == -1) { 237 | return MatrixIndex.NOT_FOUND; 238 | } 239 | 240 | // do binary search on the range of valid rows 241 | for(int i = startingEndIndexRow; i <= startingStartIndexRow; i++) { 242 | int col = Arrays.binarySearch(matrix[i], key); 243 | if(col >= 0) { 244 | return index(i, col); 245 | } 246 | } 247 | 248 | return MatrixIndex.NOT_FOUND; 249 | } 250 | 251 | private static int findLastEndIndexRow( 252 | int[][] matrix, int key, int half, int prev, int prevValid) { 253 | 254 | DEBUG.println("findLastEndIndexRow(half: %s, prev: %s prevValid: %s)", half, prev, prevValid); 255 | int found = -1; 256 | if(half >= 0 && half < matrix.length && half != prev) { 257 | int delta = -1; 258 | if(matrix[half][matrix[half].length - 1] >= key) { 259 | prevValid = half; 260 | // then we want to increase the range by half 261 | DEBUG.println("INCREASE RANGE"); 262 | delta = half - ((Math.abs(half - prev) + 1) / 2); 263 | } else { 264 | // then we want to decrease the range by half 265 | DEBUG.println("DECREASE RANGE"); 266 | delta = half + ((Math.abs(half - prev) + 1) / 2); 267 | } 268 | 269 | if(delta != prevValid && delta != prev && delta != half) { 270 | found = findLastEndIndexRow(matrix, key, delta, half, prevValid); 271 | } 272 | } 273 | 274 | if(found == -1) { 275 | if(half >= 0 && half < matrix.length && matrix[half][matrix[half].length - 1] >= key) { 276 | found = half; 277 | } else if(matrix[prevValid][matrix[prevValid].length - 1] >= key) { 278 | found = prevValid; 279 | } 280 | } 281 | 282 | DEBUG.println("findLastEndIndexRow(half: %s, prev: %s prevValid: %s)=%s", half, prev, prevValid, found); 283 | return found; 284 | } 285 | 286 | private static int findLastStartIndexRow( 287 | int[][] matrix, int key, int min, int half, int prev, int prevValid) { 288 | DEBUG.println("findLastStartIndexRow(half:%s, prev:%s, prevValid:%s)", half, prev, prevValid); 289 | int found = -1; 290 | if(half >= min && half < matrix.length && half != prev) { 291 | int delta = -1; 292 | if(matrix[half][0] <= key) { 293 | prevValid = half; 294 | // then increase the range by going to smaller numbers 295 | DEBUG.println("INCREASE RANGE"); 296 | delta = half + ((Math.abs(half - prev) + 1) / 2); 297 | } else { 298 | // then decrease the range by going to bigger numbers 299 | DEBUG.println("DECREASE RANGE"); 300 | delta = half - ((Math.abs(half - prev) + 1) / 2); 301 | } 302 | 303 | if(delta != prevValid && delta != prev && delta != half) { 304 | found = findLastStartIndexRow(matrix, key, min, delta, half, prevValid); 305 | } 306 | } 307 | 308 | if(found == -1) { 309 | if(half >= min && half < matrix.length && matrix[half][0] <= key) { 310 | found = half; 311 | } else if(matrix[prevValid][0] <= key) { 312 | found = prevValid; 313 | } 314 | } 315 | 316 | DEBUG.println("findLastStartIndexRow(half:%s, prev:%s, prevValid:%s)=%s", half, prev, prevValid, found); 317 | return found; 318 | } 319 | 320 | private MatrixIndex index(int row, int col) { 321 | return MatrixIndex.index(row, col); 322 | } 323 | } 324 | 325 | class CorrectMatrixFinder implements MatrixFinder { 326 | // find row or column where the item is 327 | 328 | // [ 0, 1, 3, 8 ] 329 | // [ 2, 4, 6, 13 ] 330 | // [ 5, 7, 9, 14 ] 331 | // [ 10, 11, 15, 16 ] 332 | // Find: 5 333 | // Start: 0,0 (r,c) 334 | 335 | // 1. Look for first element int he diagonal that is greater than key (2,2) 336 | // [ X, O, O, O, O ] 337 | // [ O, X, O, O, O ] 338 | // [ O, O, #, O, O ] 339 | // [ O, O, O, O, O ] 340 | // [ O, O, O, O, O ] 341 | // 2. Limit search space to the 2 rectangles formed by the A's and the B's 342 | // [ X, X, A, A, A ] 343 | // [ X, X, A, A, A ] 344 | // [ B, B, #, X, X ] 345 | // [ B, B, X, X, X ] 346 | // [ B, B, X, X, X ] 347 | // 3. Call 2 recursive functions: 348 | // A) Set the boundaries to rectangle A 349 | // B) Set the boundaries to rectangle B 350 | // 351 | // Edge Case: 352 | // 2.B We did not find a number in the diagonal greater than the key, but 353 | // there are more rows below the diagonal. 354 | // [ X, O, O ] 355 | // [ O, X, O ] 356 | // [ O, O, X ] 357 | // [ O, O, O ] # 358 | // [ O, O, O ] 359 | // 3.B Then automatically exclude the recangle above from the search 360 | // space, and only search on the left rectangle. 361 | // [ X, X, X ] 362 | // [ X, X, X ] 363 | // [ X, X, X ] 364 | // [ B, B, B ] # 365 | // [ B, B, B ] 366 | public MatrixIndex indexOf(int[][] matrix, int key) { 367 | if(get(matrix, 0, 0) > key) { 368 | return MatrixIndex.NOT_FOUND; 369 | } 370 | 371 | if(get(matrix, matrix.length - 1, matrix[0].length - 1) < key) { 372 | return MatrixIndex.NOT_FOUND; 373 | } 374 | 375 | return indexOf(matrix, key, 0, 0, matrix.length, matrix[0].length); 376 | } 377 | 378 | 379 | // it is guaranteed that the key is 'within' the range of numbers of the matrix 380 | private MatrixIndex indexOf( 381 | int[][] matrix, int key, int startRow, int startCol, int boundaryRow, int boundaryCol) { 382 | DEBUG.println("indexOf(key:%s, start:(%s,%s), boundaries: (%s,%s))", 383 | key, startRow, startCol, boundaryRow, boundaryCol); 384 | // look for first element in the diagonal that is greater than key 385 | if(boundaryRow - startRow <= 0 || boundaryCol - startCol <= 0) { 386 | return MatrixIndex.NOT_FOUND; 387 | } 388 | 389 | if(boundaryRow - startRow == 1) { 390 | DEBUG.println("single row search"); 391 | int col = Arrays.binarySearch(matrix[startRow], key); 392 | if(col >= 0) { 393 | return index(startRow, col); 394 | } else { 395 | return MatrixIndex.NOT_FOUND; 396 | } 397 | } 398 | 399 | if(boundaryCol - startCol == 1) { 400 | DEBUG.println("single column search"); 401 | // TODO: Use binary search 402 | int row = -1; 403 | for(int i = 0; i < boundaryRow; i++) { 404 | if(matrix[i][startCol] == key) { 405 | row = i; 406 | break; 407 | } 408 | 409 | if(matrix[i][startCol] > key) { 410 | break; 411 | } 412 | } 413 | 414 | if(row >= 0 && row < boundaryRow) { 415 | return index(row, startCol); 416 | } else { 417 | return MatrixIndex.NOT_FOUND; 418 | } 419 | } 420 | 421 | int diagonalRow = startRow; 422 | int diagonalCol = startCol; 423 | // TODO: use binary search to search on the diagonal 424 | for(; diagonalRow < boundaryRow && diagonalCol < boundaryCol; diagonalRow++, diagonalCol++) { 425 | if(get(matrix, diagonalRow, diagonalCol) >= key) { 426 | break; 427 | } 428 | } 429 | 430 | MatrixIndex index = MatrixIndex.NOT_FOUND; 431 | DEBUG.println("diagonal: (%s, %s)", diagonalRow, diagonalCol); 432 | if(diagonalCol != boundaryCol) { 433 | if(get(matrix, diagonalRow, diagonalCol) == key) { 434 | return index(diagonalRow, diagonalCol); 435 | } 436 | 437 | // split 438 | // search on rectangle above diagonal spot 439 | int aboveStartRow = startRow; // should start first item of search 440 | int aboveStartCol = diagonalCol; // should start at diagonal col 441 | int aboveBoundaryRow = diagonalRow; // decreasse boundary row 442 | int aboveBoundaryCol = boundaryCol; // should not exceed current boundary 443 | DEBUG.println("Search above"); 444 | index = indexOf(matrix, key, aboveStartRow, aboveStartCol, aboveBoundaryRow, aboveBoundaryCol); 445 | } 446 | 447 | if(diagonalRow != boundaryRow && index == MatrixIndex.NOT_FOUND) { 448 | // search on rectangle to the left of diagonal spot 449 | int leftStartRow = diagonalRow; 450 | int leftStartCol = startCol; 451 | int leftBoundaryRow = boundaryRow; 452 | int leftBoundaryCol = diagonalCol; 453 | DEBUG.println("Search to the left"); 454 | return indexOf(matrix, key, leftStartRow, leftStartCol, leftBoundaryRow, leftBoundaryCol); 455 | } else { 456 | return index; 457 | } 458 | } 459 | 460 | private int get(int[][] matrix, int row, int col) { 461 | return matrix[row][col]; 462 | } 463 | 464 | private MatrixIndex index(int row, int col) { 465 | return MatrixIndex.index(row, col); 466 | } 467 | } 468 | 469 | class MatrixIndex { 470 | public static final MatrixIndex NOT_FOUND = index(-1, -1); 471 | int col; 472 | int row; 473 | private MatrixIndex(int row, int col) { this.row = row; this.col = col; } 474 | 475 | public static MatrixIndex index(int row, int col) { 476 | return new MatrixIndex(row, col); 477 | } 478 | 479 | public boolean equals(Object other) { 480 | if(!(other instanceof MatrixIndex)) { 481 | return false; 482 | } 483 | 484 | MatrixIndex index = (MatrixIndex) other; 485 | return index.row == row && index.col == col; 486 | } 487 | 488 | public String toString() { 489 | return "(" + row + ", " + col + ")"; 490 | } 491 | } 492 | 493 | 494 | /* 495 | * Sorts Person objects by both their weight and height 496 | * and gets the longes possible sort. 497 | **/ 498 | class PeopleLongestSorter { 499 | public static int comparisonCount = 0; 500 | // O(n^2) 501 | public structures.LinkedList longestSort(Person[] people) { 502 | comparisonCount = 0; 503 | structures.LinkedList maxList = null; 504 | Map> memo = new HashMap<>(); 505 | int currentMax = 0; 506 | for(Person person : people) { 507 | if(!memo.containsKey(person)) { 508 | structures.LinkedList longestForPerson = 509 | longestSort(currentMax, filterBy(person, people), memo, person); 510 | if(maxList == null || maxList.size() < longestForPerson.size()) { 511 | maxList = longestForPerson; 512 | currentMax = maxList.size(); 513 | } 514 | } 515 | } 516 | 517 | DEBUG.println("Total Comparisons: %s", comparisonCount); 518 | DEBUG.println("Input size: %s", people.length); 519 | DEBUG.println("Complexity: N^%2.1f", Math.log(comparisonCount) / Math.log(people.length)); 520 | return maxList; 521 | } 522 | 523 | // O(size(people) + size(people - 1) + size(people - 2) ... + size(people - N)) 524 | // O(n^2) --> for the FIRST time it is called. 525 | private structures.LinkedList longestSort( 526 | int currentMax, 527 | List people, 528 | Map> memo, 529 | Person base) { 530 | 531 | if(memo.containsKey(base)) { return memo.get(base); } 532 | 533 | structures.LinkedList result = new structures.LinkedList<>(); 534 | result.add(base); 535 | 536 | // there are no more people to add 537 | if(people.size() == 0) { return result; } 538 | // even if all people could be lined up, we could not beat the current max 539 | if(currentMax > people.size()) { return result; } // but don't memo the result 540 | 541 | 542 | structures.LinkedList longest = null; 543 | for(Person other : people) { 544 | comparisonCount++; 545 | if(other.compare(base) == -1) { 546 | structures.LinkedList otherLongest = null; 547 | if(memo.containsKey(other)) { 548 | otherLongest = memo.get(other); 549 | } else { 550 | otherLongest = longestSort(currentMax - 1, filterBy(other, people), memo, other); 551 | } 552 | 553 | if(longest == null || longest.size() < otherLongest.size()) { 554 | longest = otherLongest; 555 | } 556 | } 557 | } 558 | 559 | if(longest != null) { result.appendAll(longest); } 560 | 561 | memo.put(base, result); 562 | 563 | return result; 564 | } 565 | 566 | // O(size(people)) 567 | private List filterBy(Person person, List people) { 568 | List filtered = new ArrayList<>(); 569 | for(Person other : people) { 570 | comparisonCount++; 571 | if(other.compare(person) == -1) { 572 | filtered.add(other); 573 | } 574 | } 575 | 576 | return filtered; 577 | } 578 | 579 | // O(size(people)) 580 | private List filterBy(Person person, Person[] people) { 581 | List filtered = new ArrayList<>(); 582 | for(Person other : people) { 583 | comparisonCount++; 584 | if(other.compare(person) == -1) { 585 | filtered.add(other); 586 | } 587 | } 588 | 589 | return filtered; 590 | } 591 | } 592 | 593 | class Person { 594 | private final int weight; 595 | private final int height; 596 | 597 | public Person(int weight, int height) { 598 | this.weight = weight; 599 | this.height = height; 600 | } 601 | 602 | /* 603 | * @returns 0: equal 604 | * 1: this is greater 605 | * -1: this is smaller 606 | * Integer.MIN_VALUE: neither 607 | **/ 608 | public int compare(Person other) { 609 | if(this.weight > other.weight && this.height > other.height) { 610 | return 1; 611 | } 612 | 613 | if(this.height < other.height && this.weight < other.weight) { 614 | return -1; 615 | } 616 | 617 | if(this.height == other.height && this.weight == other.height) { 618 | return 0; 619 | } 620 | 621 | return Integer.MIN_VALUE; 622 | } 623 | 624 | public boolean equals(Object otherObj) { 625 | if(!(otherObj instanceof Person)) { 626 | return false; 627 | } 628 | 629 | return compare((Person) otherObj) == 0; 630 | } 631 | 632 | public int hashCode() { 633 | return (31 * weight) + (17 * height); 634 | } 635 | } 636 | 637 | // TODO: Implement using a balanced binary search 638 | // tree, instead of SingleLinkNode, where we 639 | // know that if we traverse the balanced search tree 640 | // in-order, you can tell a node's rank, however 641 | // this would require for each node to know it's size 642 | // which may or may not be a O(1) operation in 643 | // a balanced search tree, since each node must know its 644 | // size via a variable. 645 | class IntegerStreamer { 646 | private SingleLinkNode head; 647 | private Map ranks = new HashMap<>(); 648 | 649 | 650 | // O(N) [number of streamed elements] 651 | public void stream(Integer number) { 652 | SingleLinkNode newNode = new SingleLinkNode<>(number); 653 | if(head == null) { 654 | this.head = newNode; 655 | ranks.put(number, 0); 656 | } else { 657 | SingleLinkNode node = head; 658 | SingleLinkNode prev = null; 659 | int rank = 0; 660 | while(node != null && node.value() <= number) { 661 | prev = node; 662 | node = node.next(); 663 | rank++; 664 | } 665 | 666 | if(prev != null) { prev.next(newNode); } 667 | newNode.next(node); 668 | ranks.put(number, rank); 669 | 670 | while(node != null) { 671 | ranks.put(node.value(), rank + 1); 672 | rank++; 673 | node = node.next(); 674 | } 675 | } 676 | } 677 | 678 | // O(1) 679 | public Integer rankOf(Integer number) { 680 | return ranks.get(number); 681 | } 682 | } 683 | --------------------------------------------------------------------------------