├── .scalafmt.conf ├── project └── build.properties ├── README.md ├── src ├── test │ └── scala │ │ └── com │ │ └── github │ │ └── myyk │ │ └── cracking │ │ ├── chapter7 │ │ └── CircularArrayTest.scala │ │ ├── Chapter3SolutionsTest.scala │ │ ├── Chapter2SolutionsTest.scala │ │ ├── Chapter17SolutionsTest.scala │ │ ├── Chapter5SolutionsTest.scala │ │ ├── Chapter1SolutionsTest.scala │ │ ├── Chapter7SolutionsTest.scala │ │ ├── Chapter16SolutionsTest.scala │ │ └── Chapter4SolutionsTest.scala └── main │ └── java │ └── com │ └── github │ └── myyk │ └── cracking │ ├── chapter7 │ ├── OnlineBookReaderSystem.java │ ├── CircularArray.java │ ├── ChatServer.java │ ├── DeckOfCards.java │ ├── ParkingLot.java │ ├── JukeBox.java │ ├── CallCenter.java │ └── JigsawPuzzle.java │ ├── Chapter6Solutions.java │ ├── Chapter5Solutions.java │ ├── Chapter2Solutions.java │ ├── Chapter1Solutions.java │ ├── Chapter3Solutions.java │ ├── Chapter17Solutions.java │ └── Chapter7Solutions.java ├── .gitignore └── LICENSE /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = "3.0.0-RC4" 2 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.10.1 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cracking-the-coding-interview-6th 2 | Solutions for "Cracking the Coding Interview (6th Edition)" by McDowell 3 | -------------------------------------------------------------------------------- /src/test/scala/com/github/myyk/cracking/chapter7/CircularArrayTest.scala: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking.chapter7 2 | 3 | import org.scalatest._ 4 | import flatspec._ 5 | import matchers._ 6 | 7 | class CircularArrayTest extends AnyFlatSpec with should.Matchers { 8 | 9 | "iterator" should "should iterate through the collection" in { 10 | val array = new CircularArray[Int] 11 | for (i <- 1 until 10) { 12 | array.add(i) 13 | } 14 | var it = array.iterator 15 | for (i <- 1 until 10) { 16 | it.next() shouldBe i 17 | } 18 | 19 | array.rotateRight(3) 20 | it = array.iterator 21 | for (i <- 7 until 10) { 22 | it.next() shouldBe i 23 | } 24 | for (i <- 1 until 7) { 25 | it.next() shouldBe i 26 | } 27 | 28 | array.rotateLeft(3) 29 | it = array.iterator 30 | for (i <- 1 until 10) { 31 | it.next() shouldBe i 32 | } 33 | 34 | array.rotateLeft(3) 35 | array.add(10) 36 | array.add(11) 37 | it = array.iterator 38 | for (i <- 4 until 10) { 39 | it.next() shouldBe i 40 | } 41 | for (i <- 1 until 4) { 42 | it.next() shouldBe i 43 | } 44 | it.next() shouldBe 10 45 | it.next() shouldBe 11 46 | } 47 | } -------------------------------------------------------------------------------- /src/main/java/com/github/myyk/cracking/chapter7/OnlineBookReaderSystem.java: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking.chapter7; 2 | 3 | import java.util.Date; 4 | import java.util.Set; 5 | 6 | /** 7 | * Design an online book reader system. 8 | * 9 | * The major problem I have with this question is, what the fuck is an online book 10 | * reader system. Without being able to chat with an interviewer, it's not obvious 11 | * what I'm supposed to build because this isn't like a common concept like a jukebox 12 | * or a parking lot. 13 | * 14 | * This problem is dumb, I'm leaving it pretty incomplete because it's dumb. 15 | */ 16 | public class OnlineBookReaderSystem { 17 | public class Book { 18 | String title; 19 | long assetId; 20 | Author author; 21 | Publisher publisher; 22 | } 23 | public class Note { 24 | Book book; 25 | long position; 26 | String text; 27 | } 28 | public class Reader { 29 | String name; 30 | String language; 31 | Date birthday; 32 | } 33 | public class Library { 34 | Reader reader; 35 | Set books; 36 | Set notes; 37 | } 38 | public class Author { 39 | String name; 40 | } 41 | public class Publisher {} 42 | /* BookStore? */ 43 | 44 | public class BookFinder {} 45 | /* 46 | * Would ask interviewer about asset delivery, search, store, language 47 | */ 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/github/myyk/cracking/chapter7/CircularArray.java: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking.chapter7; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Iterator; 5 | 6 | public class CircularArray implements Iterable { 7 | public final ArrayList list = new ArrayList(); 8 | private int leftRotations = 0; 9 | 10 | public CircularArray() { 11 | } 12 | 13 | @Override 14 | public Iterator iterator() { 15 | return new Iterator() { 16 | //TODO: add concurrent modification protection 17 | int i = getFirstIndex(); 18 | 19 | @Override 20 | public boolean hasNext() { 21 | return i != getLastIndex(); 22 | } 23 | 24 | @Override 25 | public T next() { 26 | T result = list.get(i); 27 | i = (i + 1) % list.size(); 28 | return result; 29 | } 30 | }; 31 | } 32 | 33 | private int getFirstIndex() { 34 | if (list.isEmpty()) { 35 | return 0; 36 | } 37 | return leftRotations % list.size(); 38 | } 39 | 40 | private int getLastIndex() { 41 | if (list.isEmpty()) { 42 | return 0; 43 | } 44 | return (getFirstIndex() + list.size() - 1) % list.size(); 45 | } 46 | 47 | public T add(T e) { 48 | if (list.isEmpty() || leftRotations % list.size() == 0) { 49 | list.add(e); 50 | } else { 51 | list.add(getFirstIndex(), e); 52 | leftRotations += 2; 53 | } 54 | return e; 55 | } 56 | 57 | public void rotateRight(int n) { 58 | leftRotations += list.size() - n; 59 | } 60 | 61 | public void rotateLeft(int n) { 62 | leftRotations += n; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/github/myyk/cracking/chapter7/ChatServer.java: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking.chapter7; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Set; 7 | 8 | /** 9 | * Explain how you'd design a chat server. 10 | * 11 | * This could go on forever. I could add user friending features. I could add login 12 | * and log out. I think I answered private and group messaging adequately. 13 | */ 14 | public class ChatServer { 15 | public Map,Chat> chats; 16 | public ConnectionManager connectionManager; 17 | 18 | synchronized public Chat findOrCreateChat(Set users) { 19 | if (!chats.containsKey(users)) { 20 | return chats.put(users, new Chat(users)); 21 | } else { 22 | return chats.get(users); 23 | } 24 | } 25 | 26 | synchronized public Chat updateChatUsers(Set oldUsers, Set newUsers) { 27 | Chat chat = chats.remove(oldUsers); 28 | chat.users = newUsers; 29 | chats.put(newUsers, chat); 30 | return chat; 31 | } 32 | 33 | public class User { 34 | String name; 35 | Set friends; 36 | 37 | public void sendMessage(Message message, Chat chat) { 38 | chat.addMessage(message); 39 | } 40 | public void deliver(Message message) {} 41 | } 42 | 43 | public class Message { 44 | int id; 45 | String text; 46 | } 47 | 48 | public class Chat { 49 | Set users; 50 | List messages; 51 | 52 | public Chat(Set users) { 53 | this.users = users; 54 | this.messages = new LinkedList(); 55 | } 56 | 57 | public void addMessage(Message message) { 58 | messages.add(message); 59 | for (User user : users) { 60 | user.deliver(message); 61 | } 62 | } 63 | } 64 | 65 | public class ConnectionManager { 66 | Map connections; 67 | } 68 | 69 | public interface Connection { 70 | public void send(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/github/myyk/cracking/chapter7/DeckOfCards.java: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking.chapter7; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Set; 5 | 6 | /** 7 | * Deck of Cards: Design a generic deck of cards. Explain how to subclass the data 8 | * structures to implement BlackJack 9 | * 10 | * BlackJack: 11 | * BlackJack could be implemented by keeping track of players hands. It would also 12 | * need to be able to calculate the value of the had from the cards in it. It would 13 | * need the concept of hidden versus revealed cards as well. I'm not sure enough how 14 | * split and other more advanced blackjack concepts work. 15 | * 16 | * Differences with the book's answer: 17 | * - I forgot about Enums in Java because of the lack of their usage in Scala. I think 18 | * using those for Suits is quite appropriate and better than what I did. 19 | * - I don't like that they added dealing of hands to the deck, this seems more of 20 | * a responsibility of the game as some games might require them to be dealt a certain 21 | * way because stacking the deck can be part of the game. I also don't really think a 22 | * Deck should know what a Hand is even. 23 | * - I'm not a huge fan of setDeckOfCards, I like setting the cards on the deck which 24 | * can be used to restore the deck to be full again when shuffling the hands into it. 25 | * Alternatively, a new deck could be created. 26 | * - Adding a discard could arguable be part of the Game instead, but they are usually 27 | * kind of the same across most games, where some don't care about an ordering and some 28 | * do. I think it's addition is not bad. 29 | * 30 | */ 31 | // I'm being a bit lazy here by not creating other classes in other files and using 32 | // comments as if they were 33 | public class DeckOfCards { 34 | private ArrayList deck; 35 | private ArrayList discard; 36 | private Set cards; 37 | 38 | public DeckOfCards(Set cards) { 39 | super(); 40 | this.cards = cards; 41 | } 42 | 43 | public void shuffle() {} 44 | public void shuffleDiscardWithDeck() {} 45 | 46 | public PokerCard dealNextCard() { 47 | return null; 48 | } 49 | 50 | public int cardsLeftInDeck() { return deck.size(); } 51 | public int cardsLeftInDiscard() { return discard.size(); } 52 | 53 | public void putOnTop(Card card) {} 54 | 55 | public void putOnBottom(Card card) {} 56 | 57 | public void takeFromDiscard(Card card) {} 58 | 59 | public static interface Card { 60 | } 61 | 62 | public static class PokerCard implements Card { 63 | int num; 64 | Suit suit; 65 | 66 | public boolean isJack() { return num == 11; } 67 | public boolean isQueen() { return num == 12; } 68 | public boolean isKing() { return num == 13; } 69 | public boolean isAce() { return num == 1; } 70 | 71 | public static interface Suit{} 72 | public static class Hearts implements Suit {} 73 | public static class Spades implements Suit {} 74 | public static class Clubs implements Suit {} 75 | public static class Diamonds implements Suit {} 76 | } 77 | 78 | public static DeckOfCards getDeckOfPokerCards() { 79 | // create DeckOfCards with 13 PokerCards of each suit 80 | return null; 81 | } 82 | } -------------------------------------------------------------------------------- /src/main/java/com/github/myyk/cracking/chapter7/ParkingLot.java: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking.chapter7; 2 | 3 | import java.util.List; 4 | import java.util.Set; 5 | 6 | /** 7 | * Design a parking lot. 8 | * 9 | * Designed a lot with a multi-floors, gates, pass holders, one-time users, 10 | * special designated spots, payment before leaving, credit card and cash 11 | * 12 | * Differences with the book's answer: 13 | * Hrm... I have gotten this question before in an interview and the payments 14 | * are always kind of complicated to implement. Maybe I always go there because 15 | * I'm practical and see a parking lot as a business, but I like how they just dealt 16 | * with the issue of parking vehicles and if they would fit or not. That's way easier 17 | * to design in an interview. 18 | */ 19 | public class ParkingLot { 20 | public class LitSign { 21 | boolean isOn; 22 | } 23 | public class LotFullSign extends LitSign{ 24 | } 25 | public class FloorFullSign extends LitSign{ 26 | int floor; 27 | } 28 | public class Pass { 29 | long id; 30 | long accountHolderId; 31 | PassType type; 32 | Set qualitites; 33 | Spot assignedSpot; // nullable 34 | long /*DateTime*/ startDate; 35 | long /*DateTime*/ expireDate; 36 | } 37 | public enum PassType { 38 | Weekly, Monthly, Yearly, WeekendsOnly, WeekdaysOnly 39 | } 40 | public class Car { 41 | String licensePlate; 42 | String state; 43 | Pass pass; 44 | } 45 | public class Spot { 46 | int id; 47 | int floor; 48 | Set qualitites; 49 | boolean isReserved; 50 | } 51 | public enum SpotQualities { 52 | Electric, Compact, Oversized, Motorcycle 53 | } 54 | // TODO: Use JodaMoney 55 | public class Money { 56 | long amount; //smallest denomination 57 | String currancy; //ISO 4217 58 | } 59 | public interface PaymentMethod { 60 | public void pay(Money money); 61 | public void refund(Money money); 62 | } 63 | public class Cash implements PaymentMethod { 64 | @Override public void pay(Money money) {} 65 | @Override public void refund(Money money) {} 66 | } 67 | public class CreditCard implements PaymentMethod { 68 | @Override public void pay(Money money) {} 69 | @Override public void refund(Money money) {} 70 | } 71 | public class ParkingRate { 72 | Money rate; 73 | int minimumMinutes; 74 | int maximumMinutes; 75 | // Restrictions restrictions; 76 | // between certain times 77 | // special event 78 | // SpotQualities 79 | } 80 | public class PaymentCalculator { 81 | List parkingRates; 82 | 83 | private Money calculatePaymentAmount(Ticket ticket) { 84 | return null; 85 | } 86 | } 87 | public class Paybooth { 88 | PaymentCalculator paymentCalculator; 89 | Ticket currentTicket; 90 | public void insertTicket(Ticket ticket) { currentTicket = ticket; } 91 | public Ticket payToExit(PaymentMethod paymentMethod) { 92 | paymentMethod.pay(paymentCalculator.calculatePaymentAmount(currentTicket)); 93 | return ejectTicket(); 94 | } 95 | public Ticket ejectTicket() { 96 | Ticket temp = currentTicket; 97 | currentTicket = null; 98 | return temp; 99 | } 100 | 101 | } 102 | public class GateConsole { 103 | Gate gate; 104 | public Ticket getTicket() { return new Ticket(); } 105 | public void readPass(Pass pass) {} 106 | public void exitWithTicket(Ticket ticket) {} 107 | } 108 | public class Gate { 109 | public void openGate() { } 110 | public void closeGate() { } 111 | } 112 | public class Ticket { 113 | public long /*TODO: use Joda Time*/ entryTime; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/github/myyk/cracking/chapter7/JukeBox.java: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking.chapter7; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Queue; 7 | 8 | /** 9 | * Design a JukeBox 10 | * 11 | * Differences with the book's answer: 12 | * - The major thing I forgot was a player of some sort. I guess it's 2016 and I 13 | * don't really think of the challenges of looking up CDs and kind of just assumed 14 | * that a Song could be .play()'d. 15 | * - I also didn't explicitly put in the display. Maybe because I'm a backend dude, 16 | * I just figured I was building the backend for someone to use when building the 17 | * frontend, which might not be entirely wrong. 18 | * 19 | * In Scala: I would have made various components into Traits which I could mix in. 20 | * For example, I'd have these traits: Catalog, PaymentMachine, AdminInterface 21 | */ 22 | public class JukeBox { 23 | // assuming this is like an old jukebox with pages and stuff 24 | private final Catalog catalog; 25 | private final Map creditPrices; 26 | private int earnedMoney; // can't be returned 27 | private int pendingMoney; // in pennies, assuming USD is fine for this 28 | private int songCredits; // user buys credits, then selects songs 29 | private Queue playlist = new LinkedList(); 30 | private int maxInQueue = Integer.MAX_VALUE; 31 | 32 | // could maybe make edits to catalog through an admin interface 33 | public JukeBox(List> catalog, Map creditPrices) { 34 | this.catalog = new Catalog(catalog); 35 | this.creditPrices = creditPrices; 36 | } 37 | 38 | public void selectSong(Song song) { 39 | if (getSongCredits() <= 0) { 40 | throw new IllegalStateException("Not enough credits to queue a song."); 41 | } else { 42 | setSongCredits(getSongCredits() - 1); 43 | playlist.add(song); 44 | } 45 | } 46 | 47 | public int buySongCredits(int numCredits) { 48 | if (playlist.size() >= maxInQueue) { 49 | throw new IllegalStateException("Too many songs in queue to add credits."); 50 | } 51 | if (!creditPrices.containsKey(numCredits)) { 52 | throw new RuntimeException("Can't buy [" + numCredits 53 | + "], programmer error."); 54 | } 55 | int price = creditPrices.get(numCredits); 56 | if (pendingMoney < price) { 57 | throw new IllegalStateException("Insufficient funds."); 58 | } 59 | pendingMoney -= price; 60 | earnedMoney += price; 61 | return returnMoney(); 62 | } 63 | 64 | public void addMoney(int pennies) { 65 | pendingMoney += pennies; 66 | } 67 | 68 | public int returnMoney() { 69 | int temp = pendingMoney; 70 | pendingMoney = 0; 71 | return temp; 72 | } 73 | 74 | public int getSongCredits() { 75 | return songCredits; 76 | } 77 | 78 | public void setSongCredits(int songCredits) { 79 | this.songCredits = songCredits; 80 | } 81 | 82 | public static class Catalog { 83 | private final List> catalog; 84 | private int currentPage = 0; 85 | 86 | public Catalog(List> catalog) { 87 | super(); 88 | this.catalog = catalog; 89 | } 90 | 91 | public void nextPage() { 92 | if (currentPage + 1 < catalog.size()) { 93 | currentPage++; 94 | } 95 | } 96 | 97 | public void prevPage() { 98 | if (currentPage - 1 >= 0) { 99 | currentPage--; 100 | } 101 | } 102 | 103 | public List getSongsOnPage() { 104 | return catalog.get(currentPage); 105 | } 106 | 107 | public List> getCatalog() { 108 | return catalog; 109 | } 110 | } 111 | 112 | public static class Song { 113 | Artist artist; 114 | String name; 115 | } 116 | 117 | // Band or otherwise 118 | public static class Artist { 119 | String name; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/github/myyk/cracking/chapter7/CallCenter.java: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking.chapter7; 2 | 3 | import java.util.Collection; 4 | import java.util.HashSet; 5 | import java.util.PriorityQueue; 6 | import java.util.Set; 7 | 8 | /** 9 | * Implement a Call Center as specified in the book. 10 | * 11 | * Differences with the book's answer: 12 | * - I kind of like that they had the 'currentCall' on the Employee, that way you 13 | * can see who is handling a call at a given time 14 | * 15 | * Improvements I'd do in real life: 16 | * - This actually fits an Actor model very well. I think it would be appropriate to 17 | * use Akka Actors do do this job. 18 | */ 19 | public class CallCenter { 20 | public static abstract class Employee implements Comparable { 21 | Manager manager; 22 | 23 | private boolean available = true; 24 | public boolean isAvailable() { return available; } 25 | public void makeBusy() { available = false; } 26 | public void makeFree() { available = true; } 27 | 28 | public Priority priority; 29 | 30 | @Override 31 | public int compareTo(Employee o) { 32 | if (o == null) { return -1; } 33 | 34 | return this.priority.compareTo(o.priority); 35 | } 36 | 37 | public boolean canHandleCall(Call call) { 38 | return isAvailable() && isAbleToHandle(call); 39 | } 40 | 41 | abstract public boolean isAbleToHandle(Call call); 42 | 43 | abstract public void handleCall(Call call); 44 | 45 | } 46 | 47 | /** 48 | * This way if we add new employee types we don't have to touch each class, 49 | * just order them here. Also we can view priorities in one place. 50 | */ 51 | public static enum Priority { 52 | Respondent, Manager, Director 53 | } 54 | 55 | public static class Respondent extends Employee { 56 | public Priority priority() { return Priority.Respondent; } 57 | 58 | @Override 59 | public boolean isAbleToHandle(Call call) { 60 | return call.hashCode() % 2 == 0; 61 | } 62 | 63 | @Override 64 | public void handleCall(Call call) { 65 | // do stuff 66 | } 67 | } 68 | public static class Manager extends Employee { 69 | Manager manager; 70 | Employee[] directReports; 71 | 72 | public Priority priority() { return Priority.Manager; } 73 | 74 | @Override 75 | public boolean isAbleToHandle(Call call) { 76 | return call.hashCode() % 100 != 99; 77 | } 78 | 79 | @Override 80 | public void handleCall(Call call) { 81 | // do stuff 82 | } 83 | } 84 | public static class Director extends Manager { 85 | public Priority priority() { return Priority.Director; } 86 | } 87 | 88 | public static class Call {} 89 | 90 | /* LIFO to do a crude distribution of work over employees */ 91 | final PriorityQueue awaitingWork = new PriorityQueue(); 92 | 93 | final Set employees = new HashSet(); 94 | 95 | public CallCenter(Collection employees) { 96 | this.employees.addAll(employees); 97 | this.awaitingWork.addAll(employees); 98 | } 99 | 100 | public Employee dispatchCall(Call call) { 101 | if (!awaitingWork.isEmpty()) { 102 | Employee nextEmployee = awaitingWork.poll(); 103 | while (nextEmployee != null) { 104 | if (nextEmployee.canHandleCall(call)) { 105 | nextEmployee.handleCall(call); 106 | nextEmployee.makeBusy(); 107 | awaitingWork.remove(nextEmployee); 108 | return nextEmployee; 109 | } else { 110 | if (nextEmployee.isAvailable()) { 111 | awaitingWork.add(nextEmployee); 112 | } 113 | nextEmployee = nextEmployee.manager; 114 | } 115 | } 116 | } 117 | 118 | throw new RuntimeException("We're experiencing high volumes of calls right now, leave a " 119 | + "number and we'll call you back once we implement that feature."); 120 | } 121 | 122 | public void finishedCall(Employee employee) { 123 | employee.makeFree(); 124 | awaitingWork.add(employee); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/test/scala/com/github/myyk/cracking/Chapter3SolutionsTest.scala: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking 2 | 3 | import com.github.myyk.cracking.Chapter3Solutions.FullStackException 4 | 5 | import java.util 6 | import java.util.EmptyStackException 7 | import scala.jdk.CollectionConverters._ 8 | import scala.util.Random 9 | 10 | import org.scalatest._ 11 | import flatspec._ 12 | import matchers._ 13 | 14 | class Chapter3SolutionsTest extends AnyFlatSpec with should.Matchers { 15 | 16 | // should be empty to start with 17 | def testStandardStackBehavior(stack: util.Stack[Integer]): Unit = { 18 | stack.isEmpty shouldBe true 19 | intercept[EmptyStackException] { 20 | stack.peek() 21 | } 22 | for (i <- 1 to 10) { 23 | stack.push(i) 24 | } 25 | for (i <- 10 to 1 by -1) { 26 | stack.pop() shouldBe i 27 | } 28 | stack.isEmpty shouldBe true 29 | } 30 | 31 | // should be empty to start with 32 | def testStandardQueueBehavior(queue: util.Queue[Integer]): Unit = { 33 | queue.isEmpty shouldBe true 34 | queue.poll() shouldBe null 35 | intercept[NoSuchElementException] { 36 | queue.remove() 37 | } 38 | val randomNumbers = for (_ <- 1 to 10) yield { 39 | Random.nextInt() 40 | } 41 | for (i <- randomNumbers) { 42 | queue.add(i) 43 | } 44 | for (i <- randomNumbers.take(5)) { 45 | queue.poll() shouldBe i 46 | } 47 | for (i <- randomNumbers) { 48 | queue.add(i) 49 | } 50 | for (i <- randomNumbers.takeRight(5)) { 51 | queue.poll() shouldBe i 52 | } 53 | for (i <- randomNumbers) { 54 | queue.poll() shouldBe i 55 | } 56 | queue.isEmpty shouldBe true 57 | for (i <- randomNumbers) { 58 | queue.add(i) 59 | } 60 | val (oddIndex, evenIndex) = 61 | randomNumbers.zipWithIndex.partition(_._2 % 2 == 1) 62 | queue.removeAll(oddIndex.map(_._1).asJava) 63 | for (i <- evenIndex.map(_._1)) { 64 | queue.poll() shouldBe i 65 | } 66 | queue.isEmpty shouldBe true 67 | } 68 | 69 | "testStandardStackBehavior" should "work with the standard library" in { 70 | testStandardStackBehavior(new util.Stack[Integer]()) 71 | } 72 | 73 | "testStandardQueueBehavior" should "work with the standard library" in { 74 | testStandardQueueBehavior(new util.LinkedList[Integer]()) 75 | } 76 | 77 | "arrayStack" should "be a single array implementing 3 stacks" in { 78 | val stack = new Chapter3Solutions.FixedMultiStack[Int](30) 79 | intercept[IllegalArgumentException] { 80 | stack.peek(-1) 81 | } 82 | intercept[IllegalArgumentException] { 83 | stack.peek(4) 84 | } 85 | intercept[EmptyStackException] { 86 | stack.peek(0) 87 | } 88 | stack.push(10, 1) 89 | stack.peek(1) shouldBe 10 90 | stack.pop(1) shouldBe 10 91 | 92 | stack.push(11, 1) 93 | for (i <- 1 to 10) { 94 | stack.push(i, 0) 95 | } 96 | intercept[FullStackException] { 97 | stack.push(99, 0) 98 | } 99 | stack.pop(1) shouldBe 11 100 | stack.pop(0) shouldBe 10 101 | 102 | for (i <- 1 to 10) { 103 | stack.push(i, 2) 104 | } 105 | intercept[FullStackException] { 106 | stack.push(99, 2) 107 | } 108 | for (i <- 10 to 1 by -1) { 109 | stack.pop(2) shouldBe i 110 | } 111 | intercept[EmptyStackException] { 112 | stack.pop(2) 113 | } 114 | } 115 | 116 | "StackWithMin" should "be a stack that can always provide the min" in { 117 | val stack = new Chapter3Solutions.StackWithMin[Integer]() 118 | testStandardStackBehavior(stack) 119 | 120 | for (i <- 10 to 1 by -1) { 121 | stack.push(i) 122 | stack.min shouldBe i 123 | } 124 | for (i <- 1 to 10) { 125 | stack.min shouldBe i 126 | stack.pop() shouldBe i 127 | } 128 | stack.isEmpty shouldBe true 129 | } 130 | 131 | "SetOfStacks" should "be a stack that can popAt a sub-stack" in { 132 | val stack = new Chapter3Solutions.SetOfStacks[Integer](3) 133 | testStandardStackBehavior(stack) 134 | 135 | for (i <- 1 to 10) { 136 | stack.push(i) 137 | } 138 | stack.popAt(3) shouldBe 10 139 | intercept[IndexOutOfBoundsException] { 140 | stack.popAt(3) 141 | } 142 | stack.popAt(0) shouldBe 3 143 | stack.popAt(1) shouldBe 6 144 | stack.popAt(2) shouldBe 9 145 | stack.popAt(0) shouldBe 2 146 | stack.popAt(1) shouldBe 5 147 | stack.popAt(2) shouldBe 8 148 | stack.popAt(0) shouldBe 1 149 | intercept[IndexOutOfBoundsException] { 150 | stack.popAt(2) 151 | } 152 | stack.popAt(0) shouldBe 4 153 | stack.isEmpty shouldBe false 154 | stack.popAt(0) shouldBe 7 155 | stack.isEmpty shouldBe true 156 | } 157 | 158 | "QueueFromStacks" should "be a Queue" in { 159 | val queue = new Chapter3Solutions.QueueFromStacks[Integer]() 160 | testStandardQueueBehavior(queue) 161 | } 162 | 163 | "sortStack" should "sort the stack" in { 164 | val stack = new util.Stack[Integer]() 165 | val randomNumbers = for (_ <- 1 to 10) yield { 166 | Random.nextInt() 167 | } 168 | for (i <- randomNumbers) { 169 | stack.push(i) 170 | } 171 | Chapter3Solutions.sortStack(stack) 172 | stack.asScala.toList shouldBe randomNumbers.toList.sorted 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/test/scala/com/github/myyk/cracking/Chapter2SolutionsTest.scala: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking 2 | 3 | import Chapter2Solutions.Node 4 | 5 | import org.scalatest._ 6 | import flatspec._ 7 | import matchers._ 8 | 9 | class Chapter2SolutionsTest extends AnyFlatSpec with should.Matchers { 10 | def toNodes(iterable: Seq[Int]): Node = { 11 | iterable.reverse.foldLeft(null: Node)((next, i) => new Node(i, next)) 12 | } 13 | 14 | def toSeq(n: Node): Seq[Int] = { 15 | val builder = Seq.newBuilder[Int] 16 | var prev = n 17 | while (prev != null) { 18 | builder += prev.value 19 | prev = prev.next 20 | } 21 | builder.result() 22 | } 23 | 24 | def test(input: Seq[Int], expected: Seq[Int], testOp: Node => Node): Unit = { 25 | toSeq(testOp(toNodes(input))) shouldBe expected 26 | } 27 | 28 | "removeDups" should "return whether or not a string has all unique characters" in { 29 | test(Nil, Nil, Chapter2Solutions.removeDups) 30 | test(List(1, 2, 3, 4), List(1, 2, 3, 4), Chapter2Solutions.removeDups) 31 | test( 32 | List(1, 1, 2, 1, 1, 3, 4, 1, 1), 33 | List(1, 2, 3, 4), 34 | Chapter2Solutions.removeDups 35 | ) 36 | test( 37 | List(1, 2, 3, 2, 4, 2, 2), 38 | List(1, 2, 3, 4), 39 | Chapter2Solutions.removeDups 40 | ) 41 | } 42 | 43 | def testKToLast(k: Int, input: Seq[Int], expected: Seq[Int]): Unit = { 44 | toSeq(Chapter2Solutions.kToLast(k, toNodes(input))) shouldBe expected 45 | } 46 | 47 | "kToLast" should "return the k-th node" in { 48 | testKToLast(1, Nil, Nil) 49 | testKToLast(-1, List(1, 2, 3), Nil) 50 | testKToLast(0, List(1, 2, 3), List(3)) 51 | testKToLast(1, List(1, 2, 3), List(2, 3)) 52 | testKToLast(2, List(1, 2, 3), List(1, 2, 3)) 53 | testKToLast(3, List(1, 2, 3), Nil) 54 | } 55 | 56 | "deleteMiddleNode" should "delete the provided middle node" in { 57 | val list1 = toNodes(List(1, 2, 3, 4, 5)) 58 | Chapter2Solutions.deleteMiddleNode(list1.next.next) 59 | toSeq(list1) shouldBe List(1, 2, 4, 5) 60 | 61 | val list2 = toNodes(List(1, 2, 3, 4, 5)) 62 | Chapter2Solutions.deleteMiddleNode(list2) 63 | toSeq(list2) shouldBe List(2, 3, 4, 5) 64 | } 65 | 66 | def testPartition(x: Int, list: List[Int]): Unit = { 67 | val result = toSeq(Chapter2Solutions.partition(x, toNodes(list))) 68 | // partitioning again shouldn't change order 69 | val (front, back) = result.partition(_ < x) 70 | result shouldBe front ++ back 71 | } 72 | 73 | "partition" should "partition the list around x" in { 74 | testPartition(5, List(3, 5, 8, 5, 10, 2, 1)) 75 | testPartition(5, List(6, 3, 5, 8, 5, 10, 2, 1)) 76 | testPartition(5, List()) 77 | testPartition(5, List(5)) 78 | testPartition(5, List(1)) 79 | } 80 | 81 | def testSumLists1(a: Int, b: Int): Unit = { 82 | def toIntSeq(n: Int): Seq[Int] = { 83 | n.toString.reverse.map(_.asDigit).toList 84 | } 85 | def toNode(n: Int): Node = { 86 | toNodes(toIntSeq(n)) 87 | } 88 | toSeq(Chapter2Solutions.sumLists1(toNode(a), toNode(b))) shouldBe toIntSeq( 89 | a + b 90 | ) 91 | } 92 | 93 | def testSumLists2(a: Int, b: Int): Unit = { 94 | def toIntSeq(n: Int): Seq[Int] = { 95 | n.toString.map(_.asDigit).toList 96 | } 97 | def toNode(n: Int): Node = { 98 | toNodes(toIntSeq(n)) 99 | } 100 | toSeq(Chapter2Solutions.sumLists2(toNode(a), toNode(b))) shouldBe toIntSeq( 101 | a + b 102 | ) 103 | } 104 | 105 | "sumList1" should "sum lists of digits with 1s magnitude first" in { 106 | testSumLists1(1, 1) 107 | testSumLists1(131234, 9723464) 108 | testSumLists1(99999, 1) 109 | testSumLists1(1, 99999) 110 | } 111 | 112 | "sumLists2" should "sum lists of digits with 1s magnitude last" in { 113 | testSumLists2(1, 1) 114 | testSumLists2(131234, 9723464) 115 | testSumLists2(99999, 1) 116 | testSumLists2(1, 99999) 117 | } 118 | 119 | def testIsPalindrome(list: Seq[Int], expected: Boolean): Unit = { 120 | Chapter2Solutions.isPalindrome(toNodes(list)) shouldBe expected 121 | 122 | } 123 | 124 | "isPalindrome" should "determine if a link list contains a palindrome" in { 125 | testIsPalindrome(List(), expected = true) 126 | testIsPalindrome(List(1), expected = true) 127 | testIsPalindrome(List(8, 9), expected = false) 128 | testIsPalindrome(List(1, 2, 3), expected = false) 129 | testIsPalindrome(List(2, 1, 3, 3, 1, 2), expected = true) 130 | testIsPalindrome(List(2, 1, 3, 1, 2), expected = true) 131 | } 132 | 133 | def testFindIntersection(intersection: Seq[Int]): Unit = { 134 | val n = toNodes(intersection) 135 | val list1 = new Node(1, n) 136 | val list2 = new Node(2, n) 137 | Chapter2Solutions.findIntersection(list1, list2) shouldBe n 138 | Chapter2Solutions.findIntersection(list1, n) shouldBe n 139 | Chapter2Solutions.findIntersection(n, list2) shouldBe n 140 | Chapter2Solutions.findIntersection(n, n) shouldBe n 141 | } 142 | 143 | "findIntersection" should "find the intersection of two linked lists" in { 144 | testFindIntersection(List(1, 2, 3, 4)) 145 | testFindIntersection(List(5)) 146 | } 147 | 148 | "findLoopStart" should "find the start of a loop if there is one in the linked list" in { 149 | Chapter2Solutions.findLoopStart(new Node(1, null)) shouldBe null 150 | 151 | val n1 = toNodes(List(1, 2, 3, 4, 5)) 152 | n1.next.next.next.next.next = n1.next.next //create the loop 5->3 153 | Chapter2Solutions.findLoopStart(n1).value shouldBe n1.next.next.value 154 | 155 | val n2 = toNodes(List(1, 2, 3)) 156 | n2.next.next.next = n2.next //create the loop 3->2 157 | Chapter2Solutions.findLoopStart(n2).value shouldBe n2.next.value 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/test/scala/com/github/myyk/cracking/Chapter17SolutionsTest.scala: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking 2 | 3 | import com.github.myyk.cracking.Chapter17Solutions._ 4 | 5 | import java.lang.{Integer => JInt} 6 | import scala.jdk.CollectionConverters._ 7 | import scala.util.Random 8 | 9 | import org.scalatest._ 10 | import flatspec._ 11 | import matchers._ 12 | 13 | class Chapter17SolutionsTest extends AnyFlatSpec with should.Matchers { 14 | "addWithoutPlus" should "add two integers" in { 15 | def testAddWithoutPlus(a: Int, b: Int): Unit = { 16 | addWithoutPlus(a, b) shouldBe (a + b) 17 | addWithoutPlus(b, a) shouldBe (a + b) 18 | } 19 | 20 | testAddWithoutPlus(0, 0) 21 | testAddWithoutPlus(1, 0) 22 | testAddWithoutPlus(-1, 0) 23 | testAddWithoutPlus(-1, -1) 24 | testAddWithoutPlus(-1, 1) 25 | testAddWithoutPlus(1, 1) 26 | 27 | for (_ <- 1 to 100) { 28 | testAddWithoutPlus(Random.nextInt(), Random.nextInt()) 29 | } 30 | } 31 | 32 | "shuffle" should "shuffle a deck with equal probabilities" in { 33 | val deck: Array[Integer] = (0 to 51).map(Integer.valueOf).toArray 34 | shuffle(deck) 35 | // println(deck.mkString(",")) 36 | } 37 | 38 | "randomSet" should "create a random set from an array of values" in { 39 | randomSet((0 to 51).toArray, 10) should have size 10 40 | } 41 | 42 | "missingNumber" should "find the missing number in the array" in { 43 | missingNumber(Array.emptyIntArray) shouldBe 0 44 | missingNumber(Array(0)) shouldBe 1 45 | missingNumber(Array(1)) shouldBe 0 46 | for (i <- 0 to 3) { 47 | missingNumber((0 to 3).filter(_ != i).toArray) shouldBe i 48 | } 49 | for (i <- 0 to 4) { 50 | missingNumber((0 to 4).filter(_ != i).toArray) shouldBe i 51 | } 52 | 53 | for (i <- 12 to 100) { 54 | missingNumber((0 to i).filter(_ != 10).toArray) shouldBe 10 55 | } 56 | } 57 | 58 | "findLongestSubArrayWithEqualLettersAndNumbers" should "find longest subarray with equal letters and numbers" in { 59 | findLongestSubArrayWithEqualLettersAndNumbers( 60 | Array.emptyCharArray 61 | ) shouldBe Array.emptyCharArray 62 | findLongestSubArrayWithEqualLettersAndNumbers( 63 | Array('a') 64 | ) shouldBe Array.emptyCharArray 65 | findLongestSubArrayWithEqualLettersAndNumbers( 66 | Array('1') 67 | ) shouldBe Array.emptyCharArray 68 | findLongestSubArrayWithEqualLettersAndNumbers( 69 | Array('1', 'a') 70 | ) shouldBe Array('1', 'a') 71 | Set(Array('a', '1'), Array('1', 'b')) should contain( 72 | findLongestSubArrayWithEqualLettersAndNumbers(Array('a', '1', 'b', 'c')) 73 | ) 74 | Set(Array('1', 'a'), Array('2', '3')) should contain( 75 | findLongestSubArrayWithEqualLettersAndNumbers(Array('1', 'a', '2', '3')) 76 | ) 77 | Set( 78 | Array('z', '1', 'a', '2', '3', 'b'), 79 | Array('1', 'a', '2', '3', 'b', 'c') 80 | ) should contain( 81 | findLongestSubArrayWithEqualLettersAndNumbers( 82 | Array('z', '1', 'a', '2', '3', 'b', 'c', 'z') 83 | ) 84 | ) 85 | } 86 | 87 | def countsOfTwoBruteForce(n: Int): Int = { 88 | if (n <= 0) { 89 | 0 90 | } else { 91 | (0 to n).map(_.toString).view.flatten.count(_ == '2') 92 | } 93 | } 94 | 95 | def testCountsOfTwo(n: Int): Unit = { 96 | countsOfTwo(n) shouldBe countsOfTwoBruteForce(n) 97 | } 98 | 99 | "countsOfTwo" should "find the number of 2s in all the numbers 0 to n" in { 100 | countsOfTwoBruteForce(25) shouldBe 9 101 | testCountsOfTwo(25) 102 | testCountsOfTwo(125) 103 | testCountsOfTwo(325) 104 | testCountsOfTwo(1125) 105 | testCountsOfTwo(12314) 106 | testCountsOfTwo(22314) 107 | testCountsOfTwo(52314) 108 | } 109 | 110 | def babyNameFrequencyReductionScala( 111 | freqs: Map[String, Int], 112 | syn: List[(String, String)] 113 | ): Map[String, Int] = { 114 | babyNameFrequencyReduction( 115 | freqs.map { case (name, freq) => name -> JInt.valueOf(freq) }.asJava, 116 | syn.asJava 117 | ).asScala.map { case (name, freq) => name -> Int.unbox(freq) }.toMap 118 | } 119 | 120 | "babyNameFrequencyReduction" should "reduce baby name frequencies to one of the synonyms to the total of the synonym frequencies" in { 121 | babyNameFrequencyReductionScala( 122 | Map( 123 | "Myyk" -> 1, 124 | "John" -> 15, 125 | "Jon" -> 12, 126 | "Chris" -> 13, 127 | "Kris" -> 4, 128 | "Christopher" -> 19 129 | ), 130 | List( 131 | "Jon" -> "John", 132 | "John" -> "Johnny", 133 | "Chris" -> "Kris", 134 | "Chris" -> "Christopher", 135 | "Poop" -> "Poo" 136 | ) 137 | ) shouldBe Map("John" -> 27, "Chris" -> 36, "Myyk" -> 1) 138 | } 139 | 140 | "circusTowerHeight" should "compute the largest human tower height that can be constructed" in { 141 | circusTowerHeight( 142 | Set( 143 | new Person(65, 100), 144 | new Person(70, 150), 145 | new Person(56, 90), 146 | new Person(75, 190), 147 | new Person(60, 95), 148 | new Person(68, 110) 149 | ).asJava 150 | ) shouldBe 6 151 | } 152 | 153 | def testKthNumber(k: Int, expected: Int): Unit = { 154 | kthNumber(k) shouldBe expected 155 | kthNumber2(k) shouldBe expected 156 | } 157 | 158 | "kthNumber" should "be the Kth number such that it's only prime factors are 3, 5, and 7" in { 159 | for ( 160 | (expected, kMinus1) <- Seq(1, 3, 5, 7, 9, 15, 21, 25, 27, 35, 45, 161 | 49).zipWithIndex 162 | ) { 163 | testKthNumber(kMinus1 + 1, expected) 164 | } 165 | testKthNumber(100, 33075) 166 | } 167 | 168 | "findMajority" should "find the majority element in the array if it exists" in { 169 | findMajority(Array(1)) shouldBe 1 170 | findMajority(Array(1, 2)) shouldBe -1 171 | findMajority(Array(1, 2, 1)) shouldBe 1 172 | findMajority(Array(1, 2, 5, 9, 5, 9, 5, 5, 5, 2)) shouldBe -1 173 | findMajority(Array(1, 2, 5, 9, 5, 9, 5, 5, 5)) shouldBe 5 174 | findMajority(Array(5, 9, 5, 9, 5, 5, 5, 1, 2)) shouldBe 5 175 | findMajority(Array(5, 9, 2, 9, 5, 5, 5, 1, 5)) shouldBe 5 176 | } 177 | 178 | "wordDistance" should "find the minimum word distance between two words" in {} 179 | } 180 | -------------------------------------------------------------------------------- /src/main/java/com/github/myyk/cracking/chapter7/JigsawPuzzle.java: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking.chapter7; 2 | 3 | import java.util.List; 4 | import java.util.Set; 5 | import java.util.HashSet; 6 | 7 | /** 8 | * Implement a NxN jigsaw puzzle and solver. 9 | * 10 | * This implementation is pretty incomplete because I feel like enough is there. 11 | * I realize that I maybe should have avoided going into the display, but that 12 | * is kind of how I might lay out the simple Jigsaw app, though maybe this should 13 | * be a JigsawPuzzleApp that has a Jigsaw in it along with the other things. I could 14 | * have done more about the edge and inner and outer, but figured, I wasn't like 15 | * actually drawing these and it doesn't matter that much. It depends how close to 16 | * simulating the logic to solve it I was going to do, but didn't do it so, didn't 17 | * matter much in the end. 18 | */ 19 | public class JigsawPuzzle { 20 | 21 | private Piece[][] board; 22 | private Display display; 23 | private PieceGenerator generator = new PieceGenerator(); 24 | private Solver solver = new Solver(); 25 | private Set knownConnections = new HashSet(); 26 | 27 | public JigsawPuzzle(int n, byte[] image) { 28 | board = new Piece[n][n]; 29 | display = new Display(board, generator.generatePieces(image)); 30 | } 31 | 32 | public class PieceGenerator { 33 | public List generatePieces(byte[] image) { 34 | // generates Pieces from an image 35 | List pieces = null; 36 | return shuffle(pieces); 37 | } 38 | 39 | public List shuffle(List pieces) { 40 | return null; 41 | } 42 | } 43 | 44 | public List generatePieces(byte[] image) { 45 | // generates Pieces from an image 46 | List pieces = null; 47 | return shuffle(pieces); 48 | } 49 | 50 | public List shuffle(List pieces) { 51 | return null; 52 | } 53 | 54 | public boolean fitsWith(PieceEdge a, PieceEdge b) { 55 | if (oppositeSide(a.side, b.side, true)) { 56 | if (a.side == Side.Top) { 57 | return a.piece.x == b.piece.x && a.piece.y == b.piece.y-1; 58 | } else if (a.side == Side.Bottom) { 59 | return a.piece.x == b.piece.x && a.piece.y-1 == b.piece.y; 60 | } else if (a.side == Side.Right) { 61 | return a.piece.x+1 == b.piece.x && a.piece.y == b.piece.y; 62 | } else { 63 | return a.piece.x == b.piece.x+1 && a.piece.y == b.piece.y; 64 | } 65 | } else { 66 | return false; 67 | } 68 | } 69 | 70 | private boolean oppositeSide(Side a, Side b, boolean andSwap) { 71 | return (a == Side.Top && b == Side.Bottom) || 72 | (a == Side.Right && b == Side.Left) || 73 | (andSwap ? oppositeSide(b, a, false) : false); 74 | } 75 | 76 | public void addConnection(PieceEdge a, PieceEdge b) { 77 | // add connection to knownConnections 78 | } 79 | 80 | public Display getDisplay() { 81 | return display; 82 | } 83 | 84 | public void setDisplay(Display display) { 85 | this.display = display; 86 | } 87 | 88 | public class Connection { 89 | Piece a; 90 | Piece b; 91 | Side aSide; 92 | } 93 | 94 | public class PieceEdge { 95 | final Piece piece; 96 | final Side side; 97 | 98 | public PieceEdge(Piece piece, Side side) { 99 | super(); 100 | this.piece = piece; 101 | this.side = side; 102 | } 103 | } 104 | 105 | public enum Side { 106 | Top, Bottom, Right, Left; 107 | } 108 | 109 | public class Piece { 110 | final Picture picture; 111 | /* Position of where it belongs in final image*/ 112 | final int x; 113 | final int y; 114 | 115 | public PieceEdge top() { 116 | return new PieceEdge(this, Side.Top); 117 | } 118 | public PieceEdge bottom() { 119 | return new PieceEdge(this, Side.Bottom); 120 | } 121 | public PieceEdge right() { 122 | return new PieceEdge(this, Side.Right); 123 | } 124 | public PieceEdge left() { 125 | return new PieceEdge(this, Side.Left); 126 | } 127 | 128 | public Piece(Picture picture, int x, int y) { 129 | super(); 130 | this.picture = picture; 131 | this.x = x; 132 | this.y = y; 133 | } 134 | } 135 | 136 | public class PieceView { 137 | final Piece piece; 138 | private int rotationDegrees; 139 | private int x; 140 | private int y; 141 | 142 | public PieceView(Piece piece, int rotationDegrees) { 143 | super(); 144 | this.piece = piece; 145 | this.rotationDegrees = rotationDegrees; 146 | } 147 | public int getRotationDegrees() { 148 | return rotationDegrees; 149 | } 150 | public void setRotationDegrees(int rotationDegrees) { 151 | this.rotationDegrees = rotationDegrees; 152 | } 153 | public int getX() { 154 | return x; 155 | } 156 | public void setX(int x) { 157 | this.x = x; 158 | } 159 | public int getY() { 160 | return y; 161 | } 162 | public void setY(int y) { 163 | this.y = y; 164 | } 165 | } 166 | 167 | public class Picture { 168 | final byte[] image; 169 | 170 | public Picture(byte[] image) { 171 | super(); 172 | this.image = image; 173 | } 174 | } 175 | 176 | public class Display { 177 | Piece[][] board; 178 | Set pieces; 179 | 180 | public Display(Piece[][] board, List pieces) { 181 | this.board = board; 182 | this.pieces = createPieceViews(pieces); 183 | } 184 | 185 | private Set createPieceViews(List pieces) { 186 | return null; 187 | } 188 | 189 | public void update() { 190 | // redraw the view 191 | } 192 | 193 | public void update(JigsawPuzzle puzzle) { 194 | // updates using data from the puzzle, moving pieces onto the board and out of pieces 195 | // if they have already been placed 196 | } 197 | } 198 | 199 | public class Solver { 200 | JigsawPuzzle puzzle; 201 | Set unconnectedPieces; 202 | 203 | public boolean isSolved() { 204 | return unconnectedPieces.isEmpty(); 205 | } 206 | 207 | public void solve() { 208 | // start with a piece 209 | 210 | while (!isSolved()) { 211 | // get an unconnected edge from the connected graph. 212 | // find the connection for that edge. 213 | 214 | //TODO remove break 215 | break; 216 | } 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/test/scala/com/github/myyk/cracking/Chapter5SolutionsTest.scala: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking 2 | 3 | import com.github.myyk.cracking.Chapter5Solutions.NextNumberResult 4 | 5 | import scala.language.implicitConversions 6 | 7 | import org.scalatest._ 8 | import flatspec._ 9 | import matchers._ 10 | 11 | class Chapter5SolutionsTest extends AnyFlatSpec with should.Matchers { 12 | implicit def enrichStringContext(sc: StringContext): RichStringContext = 13 | new RichStringContext(sc) 14 | 15 | class RichStringContext(sc: StringContext) { 16 | def b(): Int = { 17 | def parseBinary(s: String): Int = { 18 | var i = s.length - 1 19 | var sum = 0 20 | var mult = 1 21 | while (i >= 0) { 22 | s.charAt(i) match { 23 | case '1' => sum += mult 24 | case '0' => 25 | } 26 | mult *= 2 27 | i -= 1 28 | } 29 | sum 30 | } 31 | val s = sc.parts.mkString 32 | parseBinary(s) 33 | } 34 | } 35 | 36 | "insert" should "insert the integer into the other" in { 37 | Chapter5Solutions.insert(1, 1, 0, 0) shouldBe 1 38 | Chapter5Solutions.insert(1, 1, 0, 0) shouldBe 1 39 | Chapter5Solutions.insert(2, 1, 0, 0) shouldBe 3 40 | Chapter5Solutions.insert(1, 1, 1, 1) shouldBe 3 41 | Chapter5Solutions.insert(1, b"111", 8, 6) shouldBe b"111000001" 42 | Chapter5Solutions.insert( 43 | b"101000000000", 44 | b"1101", 45 | 4, 46 | 2 47 | ) shouldBe b"101000110100" 48 | Chapter5Solutions.insert( 49 | ~0, 50 | b"1101", 51 | 4, 52 | 2 53 | ) shouldBe ~0 ^ (b"1111" << 2) | (b"1101" << 2) 54 | Chapter5Solutions.insert(b"111111", b"1010", 4, 1) shouldBe b"110101" 55 | } 56 | 57 | "floatToBinaryString" should "convert a double to a string" in { 58 | Chapter5Solutions.floatToBinaryString(1) shouldBe "ERROR" 59 | Chapter5Solutions.floatToBinaryString(0.5) shouldBe ".1" 60 | Chapter5Solutions.floatToBinaryString(0.75) shouldBe ".11" 61 | Chapter5Solutions.floatToBinaryString(0.25) shouldBe ".01" 62 | Chapter5Solutions.floatToBinaryString(0.72) shouldBe "ERROR" 63 | Chapter5Solutions.floatToBinaryString( 64 | b"10101010".toDouble / Math.pow(2, 8) 65 | ) shouldBe ".1010101" 66 | } 67 | 68 | "flipBitToWin" should "find the longest sequence possible with one flip" in { 69 | Chapter5Solutions.flipBitToWin(b"0") shouldBe 1 70 | Chapter5Solutions.flipBitToWin(b"1") shouldBe 2 71 | Chapter5Solutions.flipBitToWin(b"10") shouldBe 2 72 | Chapter5Solutions.flipBitToWin(b"1010") shouldBe 3 73 | Chapter5Solutions.flipBitToWin(b"101") shouldBe 3 74 | Chapter5Solutions.flipBitToWin(b"10010") shouldBe 2 75 | Chapter5Solutions.flipBitToWin(b"10000110100") shouldBe 4 76 | Chapter5Solutions.flipBitToWin(b"100001101100001101") shouldBe 5 77 | Chapter5Solutions.flipBitToWin( 78 | b"10100000000000000000000000000001" 79 | ) shouldBe 3 80 | Chapter5Solutions.flipBitToWin(-1) shouldBe 32 81 | Chapter5Solutions.flipBitToWin(b"11011101111") shouldBe 8 // book example 82 | } 83 | 84 | "nextNumber" should "find the next smaller and next larger integers with the same number of 1s in the binary representation" in { 85 | Chapter5Solutions.nextNumber(b"10") shouldBe new NextNumberResult( 86 | b"1", 87 | b"100" 88 | ) 89 | Chapter5Solutions.nextNumber(b"110") shouldBe new NextNumberResult( 90 | b"101", 91 | b"1001" 92 | ) 93 | Chapter5Solutions.nextNumber( 94 | b"01010000000000000000000000000000" 95 | ) shouldBe new NextNumberResult( 96 | b"01001000000000000000000000000000", 97 | b"01100000000000000000000000000000" 98 | ) 99 | 100 | Chapter5Solutions.nextNumber(-1) shouldBe new NextNumberResult(-1, -1) 101 | Chapter5Solutions.nextNumber(0) shouldBe new NextNumberResult(-1, -1) 102 | Chapter5Solutions.nextNumber(1) shouldBe new NextNumberResult(-1, b"10") 103 | Chapter5Solutions.nextNumber(b"11") shouldBe new NextNumberResult( 104 | -1, 105 | b"101" 106 | ) 107 | } 108 | 109 | "bitsToConvert" should "find the number of bits to convert one int to the other" in { 110 | Chapter5Solutions.bitsToConvert(9873453, 9873453) shouldBe 0 111 | Chapter5Solutions.bitsToConvert(0, 1) shouldBe 1 112 | Chapter5Solutions.bitsToConvert(0, b"1010") shouldBe 2 113 | Chapter5Solutions.bitsToConvert(0, b"101011") shouldBe 4 114 | Chapter5Solutions.bitsToConvert(0, -1) shouldBe 32 115 | Chapter5Solutions.bitsToConvert(b"11101", b"01111") shouldBe 2 116 | } 117 | 118 | "pairwiseSwap" should "swap all the bits in an integer with the neighboring bits" in { 119 | Chapter5Solutions.pairwiseSwap(0) shouldBe 0 120 | Chapter5Solutions.pairwiseSwap(b"01") shouldBe b"10" 121 | Chapter5Solutions.pairwiseSwap(b"10") shouldBe b"01" 122 | Chapter5Solutions.pairwiseSwap(b"1011") shouldBe b"0111" 123 | Chapter5Solutions.pairwiseSwap( 124 | b"1010101010101010101010101010101010101010101010101010101010101010" 125 | ) shouldBe b"0101010101010101010101010101010101010101010101010101010101010101" 126 | Chapter5Solutions.pairwiseSwap( 127 | b"0101010101010101010101010101010101010101010101010101010101010101" 128 | ) shouldBe b"1010101010101010101010101010101010101010101010101010101010101010" 129 | Chapter5Solutions.pairwiseSwap( 130 | b"1111111111111111111111111111111100000000000000000000000000000000" 131 | ) shouldBe b"1111111111111111111111111111111100000000000000000000000000000000" 132 | Chapter5Solutions.pairwiseSwap( 133 | b"0000000000000000000000000000000011111111111111111111111111111111" 134 | ) shouldBe b"0000000000000000000000000000000011111111111111111111111111111111" 135 | } 136 | 137 | "drawLine" should "draw a line in the screen" in { 138 | Chapter5Solutions.drawLine(new Array(2), 8, 0, 8, 0) shouldBe Array[Byte]( 139 | 0xff.toByte, 140 | 0 141 | ) 142 | Chapter5Solutions.drawLine(new Array(2), 8, 0, 8, 1) shouldBe Array[Byte]( 143 | 0, 144 | 0xff.toByte 145 | ) 146 | Chapter5Solutions.drawLine(new Array(2), 8, 2, 6, 1) shouldBe Array[Byte]( 147 | 0, 148 | b"00111100".toByte 149 | ) 150 | Chapter5Solutions.drawLine(new Array(4), 16, 1, 6, 1) shouldBe Array[Byte]( 151 | 0, 152 | 0, 153 | b"01111100".toByte, 154 | 0 155 | ) 156 | Chapter5Solutions.drawLine(new Array(8), 32, 1, 31, 1) shouldBe Array[Byte]( 157 | 0, 158 | 0, 159 | 0, 160 | 0, 161 | b"01111111".toByte, 162 | 0xff.toByte, 163 | 0xff.toByte, 164 | b"11111110".toByte 165 | ) 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/com/github/myyk/cracking/Chapter6Solutions.java: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking; 2 | 3 | /** 4 | * Math and Logic Puzzles 5 | */ 6 | public class Chapter6Solutions { 7 | 8 | /** 9 | * The Heavy Pill: Given 20 pill bottles, 19 with 1.0 gram pills and 1 with 1.1 10 | * gram pills, how would you find which bottle using the scale once? 11 | * 12 | * Answer: Put 1 pill from bottle one, 2 from bottle 2, ... n from bottle n. 13 | * You would expect (1 + 2 + ... + n) grams if they all were 1.0, but they aren't. 14 | * There will be an extra (n * 0.1) grams and that will give you the bottle. 15 | */ 16 | 17 | /** 18 | * Basketball: Can play 2 different games with a basketball hoop. 19 | * 1. Get a shot to make the hoop 20 | * 2. Get 3 shots to make 2 hoops 21 | * If you have a probability of p of making a hoop, for what values of p should you 22 | * pick the first game vs the second. 23 | * 24 | * Answer: 25 | * chance to win 1 = p 26 | * chance to win 2 = 3*p*p - 2*p*p*p 27 | * When (3*p*p - 2*p*p*p) > p, then play game 2, otherwise game 1. 28 | * If p = 0.5, then it's even odds on both. For all p, the 3*p*p < 2*p*p*p 29 | * So we can see that it will be a monotonic function of p. And by graphing it 30 | * we can see that as p increases past 0.5 then you should play game 2 as the odds 31 | * get better. 32 | * 33 | * Here's how the math works, since I found p > 0.5 by accident. 34 | * (3*p*p - 2*p*p*p) > p 35 | * 3*p - 2*p*p > 1 36 | * 0 > 1 - 3*p + 2*p*p 37 | * 0 > (1 - 2p)(1 - p) 38 | * we know 0 <= p <= 1 39 | * 0 > (1 - 2p) 40 | * 2p > 1 41 | * p > 0.5 is when we should play game 2. 42 | */ 43 | 44 | /** 45 | * Dominos: You have a 8x8 chessboard with the diagonal corners cut off. You have 31 46 | * dominos that cover 2 squares. Can you cover the entire board? 47 | * 48 | * Answer: 49 | * Dominos cover a black and white square always. There are 8x8 squares on a full 50 | * chessboard, half are black and half are white. That gives us 32 of each, but our 51 | * board has no corners, so either we have 2 less black or 2 less white. Let's say 52 | * white. So we have 32 black, 30 white. Our dominos can only cover 31 black and 53 | * 31 white tiles, so it will not work. 54 | */ 55 | 56 | /** 57 | * Ants on a Triangle: An ant is on each corner. They have equal chance of choosing to 58 | * walk in either direction towards the other corners at the same speed. What are the 59 | * chances that one or more ants collide? If there were on an n-cornered polygon and 60 | * n-ants what does the answer look like then? 61 | * 62 | * Answer: 63 | * For the ants to not collide, all need to choose the same direction. The chance they 64 | * all go right is 0.5^n. Same for left, so the chance they go the same direction is 65 | * 2*0.5^n = 0.5^(n-1). We were looking for the opposite though, so it's: 66 | * 1 - 0.5^(n-1) 67 | */ 68 | 69 | /** 70 | * Jugs of Water: You have 5qt, 3qt, and all the water. How would you find exactly 4 qts. 71 | * The jobs are misshapen so filling them to half would not be possible. 72 | * 73 | * Answer: 74 | * 1. Fill 5 75 | * 2. 5 => 3, still have 2qt in 5 76 | * 3. Dump 3 77 | * 4. 5 => 3, have 2qt in 3 78 | * 5. Fill 5 79 | * 6. 5 => 3, still have 4qt in 5 80 | */ 81 | 82 | /** 83 | * Blue-Eyed Island: People on an island. A visitor comes and says all blue-eyed people 84 | * must leave ASAP. Flights out at 8pm every day. Nobody knows or can know their own 85 | * eye color, but can see others. Also, nobody knows how many people have blue eyes, 86 | * but they do know there is at least one. How many days will it take for the blue- 87 | * eyed people to leave. 88 | * 89 | * Answer: 90 | * There's not a lot of ways to save state here, but we can keep track of number of days. 91 | * The people can signal to each other by leaving or not leaving. 92 | * There are N blue-eyed people. N > 1. 93 | * If N == 1: 94 | * The blue-eyed person wouldn't see other blue-eyed people, so they should leave 95 | * on day 1. 96 | * If N == 2: 97 | * The blue-eyed people would see 1 person, so they don't leave day since they don't 98 | * know they are blue-eyed. On day 2, they know no one left yesterday, so they 99 | * should leave since everyone else saw 2 people. 100 | * If N > 2: 101 | * We just keep going the same way. If people haven't seen at least as many blue- 102 | * eyed people as the day number since the visitor arrived, then they should leave 103 | * because they are a blue-eyed person. Everyone else would have seen that many. 104 | */ 105 | 106 | /** 107 | * The Apocalypse: Post-apocalyptic world queen is concerned about birth-rates. Every 108 | * family must have 1 girl or face massive fines. If all families have children until 109 | * they have a girl and then they stop with probability of having a girl and a boy being 110 | * the same, what would their expected gender ratios be? 111 | * 112 | * Answer: The gender ratio is equal. The ratio of females is: 113 | * for(i <- 1 until infinity) { 1 / i * (Math.pow(0.5, i))} which is 114 | * 0.5 + 0.25 + 0.125 + ... = 1 115 | */ 116 | 117 | /** 118 | * The Egg Drop Problem: 100 story building. If the egg drops from the Nth floor or 119 | * above it will break. You have two eggs. Minimize the number of drops in the 120 | * worst case. What floor is N? 121 | * 122 | * Answer: 14 drops 123 | * If we start dropping at a floor with the first egg, it will take at worst case 124 | * that floor's amount of drops to find the floor. This is because if it breaks, 125 | * it's below, maybe 1 below. 126 | * If we go up 1 less floor than we started at, we still have a worst case of 127 | * as we did for the first floor. We can continue up this way. 128 | * If use this strategy starting at floor 14, we can get an answer in 14 drops. 129 | * If we try it starting at floor 13, we can't continue keeping the worst case 130 | * at 13 for the upper floors. 131 | */ 132 | 133 | /** 134 | * 100 Lockers: There are 100 closed lockers. For i from 1 to 100, toggle the every 135 | * i lockers starting at locker 1. To toggle a locker, change it state from closed 136 | * to open or open to closed. What lockers are open in the end? 137 | * 138 | * Answer: 10, The only open lockers are those where their number is a square. So 139 | * 1, 4, 9, 16, 25, 36, 49, 64, 81, 10. The mathematical reasoning behind this is 140 | * that the lockers will be toggled once for each distinct factor they have. 141 | * Thus only lockers with an odd number of factors will be left in open. 142 | * 143 | * If a number is prime, it obviously can be factored by itself and 1, because all 144 | * numbers can. 145 | * Non-squares by definition, would have factors that could be paired to give that 146 | * product when multiplied by each other, otherwise it would be a square. 147 | * A square will similarly have pairs of factors that could be multiplied to get that 148 | * product, but it will also have one pair that the values are not distinct since 149 | * they are the square root of the number. This give an odd number of distinct 150 | * factors to the numbers. 151 | */ 152 | 153 | /** 154 | * Poison: You have 1000 bottles of soda, 1 has been poisoned. You have 10 poison 155 | * strips that can be used multiple times with a single drop of soda to detect 156 | * the poison. They can be reused multiple times as long as they don't come 157 | * up positive for poison, which permanently changes them. The poison strips take 158 | * 7 days to give a result and you can only test once a day. How would you find 159 | * the poisoned bottle in the least amount of time. 160 | * 161 | * Answer: 7 days. 162 | * This answer wasn't obvious to me and had to look it up. I got to 26 days, but 163 | * didn't figure out any of the better solutions on my own. The 7 day solution is 164 | * quite simple though. If we number each bottle 0 to 999 and represent those as 165 | * binary numbers, then we can number the test strips from 0 to 9. Since each 166 | * bottle can be represented by a binary number with 10 digits. So for each bottle 167 | * if we put a drop on each test strip which corresponds to it's 1s in it's binary 168 | * form, then after 7 days, we can just read the binary number for the strips 169 | * and it will be the bottle's number. 170 | */ 171 | } 172 | -------------------------------------------------------------------------------- /src/test/scala/com/github/myyk/cracking/Chapter1SolutionsTest.scala: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking 2 | 3 | import org.scalatest._ 4 | import flatspec._ 5 | import matchers._ 6 | 7 | class Chapter1SolutionsTest extends AnyFlatSpec with should.Matchers { 8 | 9 | "stringHasAllUniqueCharacters" should "return whether or not a string has all unique characters" in { 10 | Chapter1Solutions.stringHasAllUniqueCharacters("") shouldBe true 11 | Chapter1Solutions.stringHasAllUniqueCharacters(" ") shouldBe false 12 | Chapter1Solutions.stringHasAllUniqueCharacters("a") shouldBe true 13 | Chapter1Solutions.stringHasAllUniqueCharacters("abc") shouldBe true 14 | Chapter1Solutions.stringHasAllUniqueCharacters("aA") shouldBe true 15 | Chapter1Solutions.stringHasAllUniqueCharacters("aaa") shouldBe false 16 | Chapter1Solutions.stringHasAllUniqueCharacters("abbc") shouldBe false 17 | } 18 | 19 | "arePermutations" should "return whether two strings are permutations of each other" in { 20 | Chapter1Solutions.arePermutations("", "") shouldBe true 21 | Chapter1Solutions.arePermutations("a", "") shouldBe false 22 | Chapter1Solutions.arePermutations("", "a") shouldBe false 23 | Chapter1Solutions.arePermutations("a", "a") shouldBe true 24 | Chapter1Solutions.arePermutations("a", "A") shouldBe false 25 | Chapter1Solutions.arePermutations("aA", "Aa") shouldBe true 26 | Chapter1Solutions.arePermutations("abc", "bac") shouldBe true 27 | Chapter1Solutions.arePermutations("aabc", "bac") shouldBe false 28 | Chapter1Solutions.arePermutations("abc", "bbac") shouldBe false 29 | Chapter1Solutions.arePermutations("abc", "xyz") shouldBe false 30 | Chapter1Solutions.arePermutations("abc", "a") shouldBe false 31 | Chapter1Solutions.arePermutations("a", "bca") shouldBe false 32 | } 33 | 34 | "urlify" should "replace spaces with '%20' in place" in { 35 | val chars1 = Array('a', 'b', 'c', '\u0000') 36 | Chapter1Solutions.urlify(chars1, 3) 37 | new String(chars1) shouldBe "abc\u0000" 38 | 39 | val chars2 = Array('a', ' ', 'c', '\u0000', ' ', ' ') 40 | Chapter1Solutions.urlify(chars2, 3) 41 | new String(chars2) shouldBe "a%20c\u0000" 42 | 43 | // string is "a c d e " 44 | val chars3 = Array('a', ' ', 'c', ' ', 'd', ' ', ' ', 'e', ' ', '\u0000', 45 | ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ') 46 | Chapter1Solutions.urlify(chars3, 9) 47 | new String(chars3) shouldBe "a%20c%20d%20%20e%20\u0000" 48 | } 49 | 50 | "canHavePalindromePermutation" should "return whether a string can form a palindrome" in { 51 | Chapter1Solutions.canHavePalindromePermutation("") shouldBe true 52 | Chapter1Solutions.canHavePalindromePermutation(" ") shouldBe true 53 | Chapter1Solutions.canHavePalindromePermutation("a") shouldBe true 54 | Chapter1Solutions.canHavePalindromePermutation("ab") shouldBe false 55 | Chapter1Solutions.canHavePalindromePermutation("aA abB") shouldBe true 56 | Chapter1Solutions.canHavePalindromePermutation("Tact Coa") shouldBe true 57 | Chapter1Solutions.canHavePalindromePermutation("Myyk") shouldBe false 58 | } 59 | 60 | "isOneAway" should "return whether a string is one edit from another string" in { 61 | Chapter1Solutions.isOneAway("pale", "pale") shouldBe true 62 | Chapter1Solutions.isOneAway("pale", "ple") shouldBe true 63 | Chapter1Solutions.isOneAway("pales", "pale") shouldBe true 64 | Chapter1Solutions.isOneAway("pale", "pales") shouldBe true 65 | Chapter1Solutions.isOneAway("pale", "bale") shouldBe true 66 | Chapter1Solutions.isOneAway("pale", "bake") shouldBe false 67 | Chapter1Solutions.isOneAway("ple", "pate") shouldBe false 68 | Chapter1Solutions.isOneAway("pate", "ple") shouldBe false 69 | Chapter1Solutions.isOneAway("paste", "pate") shouldBe true 70 | } 71 | 72 | "compress" should "return a compressed string if it can compress it" in { 73 | Chapter1Solutions.compress("aabcccccaaa") shouldBe "a2b1c5a3" 74 | Chapter1Solutions.compress("abc") shouldBe "abc" 75 | Chapter1Solutions.compress("a") shouldBe "a" 76 | Chapter1Solutions.compress("aa") shouldBe "aa" 77 | Chapter1Solutions.compress("") shouldBe "" 78 | } 79 | 80 | "rotateMatrix90" should "rotate the input matrix" in { 81 | Chapter1Solutions.rotateMatrix90(Array()) shouldBe Array.empty[Array[Int]] 82 | Chapter1Solutions.rotateMatrix90(Array(Array(1))) shouldBe Array(Array(1)) 83 | Chapter1Solutions.rotateMatrix90( 84 | Array(Array(1, 2), Array(3, 4)) 85 | ) shouldBe Array(Array(3, 1), Array(4, 2)) 86 | //4 rotations and back to beginning 87 | Chapter1Solutions.rotateMatrix90( 88 | Chapter1Solutions.rotateMatrix90( 89 | Chapter1Solutions.rotateMatrix90( 90 | Chapter1Solutions.rotateMatrix90( 91 | Array( 92 | Array(1, 2), 93 | Array(3, 4) 94 | ) 95 | ) 96 | ) 97 | ) 98 | ) shouldBe Array( 99 | Array(1, 2), 100 | Array(3, 4) 101 | ) 102 | Chapter1Solutions.rotateMatrix90( 103 | Array( 104 | Array(1, 2, 3), 105 | Array(4, 5, 6), 106 | Array(7, 8, 9) 107 | ) 108 | ) shouldBe Array( 109 | Array(7, 4, 1), 110 | Array(8, 5, 2), 111 | Array(9, 6, 3) 112 | ) 113 | 114 | Chapter1Solutions.rotateMatrix90( 115 | Array( 116 | Array(1, 2, 3, 4), 117 | Array(5, 6, 7, 8), 118 | Array(9, 10, 11, 12), 119 | Array(13, 14, 15, 16) 120 | ) 121 | ) shouldBe 122 | Array( 123 | Array(13, 9, 5, 1), 124 | Array(14, 10, 6, 2), 125 | Array(15, 11, 7, 3), 126 | Array(16, 12, 8, 4) 127 | ) 128 | Chapter1Solutions.rotateMatrix90( 129 | Array( 130 | Array(1, 2, 3, 4, 5), 131 | Array(6, 7, 8, 9, 10), 132 | Array(11, 12, 13, 14, 15), 133 | Array(16, 17, 18, 19, 20), 134 | Array(21, 22, 23, 24, 25) 135 | ) 136 | ) shouldBe 137 | Array( 138 | Array(21, 16, 11, 6, 1), 139 | Array(22, 17, 12, 7, 2), 140 | Array(23, 18, 13, 8, 3), 141 | Array(24, 19, 14, 9, 4), 142 | Array(25, 20, 15, 10, 5) 143 | ) 144 | } 145 | 146 | "zeroMatrix" should "zeros rows and columns with zeroes in the original matrix" in { 147 | Chapter1Solutions.zeroMatrix(Array(Array(1))) shouldBe Array(Array(1)) 148 | Chapter1Solutions.zeroMatrix( 149 | Array( 150 | Array(1, 2), 151 | Array(3, 4) 152 | ) 153 | ) shouldBe Array( 154 | Array(1, 2), 155 | Array(3, 4) 156 | ) 157 | 158 | Chapter1Solutions.zeroMatrix( 159 | Array( 160 | Array(1, 0), 161 | Array(3, 4) 162 | ) 163 | ) shouldBe Array( 164 | Array(0, 0), 165 | Array(3, 0) 166 | ) 167 | Chapter1Solutions.zeroMatrix( 168 | Array( 169 | Array(1, 0, 3), 170 | Array(3, 4, 5) 171 | ) 172 | ) shouldBe Array( 173 | Array(0, 0, 0), 174 | Array(3, 0, 5) 175 | ) 176 | Chapter1Solutions.zeroMatrix( 177 | Array( 178 | Array(1, 2, 3, 4, 5), 179 | Array(6, 7, 8, 0, 10), 180 | Array(11, 0, 13, 14, 15), 181 | Array(16, 17, 18, 19, 20), 182 | Array(21, 22, 23, 24, 0) 183 | ) 184 | ) shouldBe 185 | Array( 186 | Array(1, 0, 3, 0, 0), 187 | Array(0, 0, 0, 0, 0), 188 | Array(0, 0, 0, 0, 0), 189 | Array(16, 0, 18, 0, 0), 190 | Array(0, 0, 0, 0, 0) 191 | ) 192 | Chapter1Solutions.zeroMatrix( 193 | Array( 194 | Array(0, 2, 3, 4, 5), 195 | Array(6, 7, 8, 0, 10), 196 | Array(11, 12, 13, 14, 15), 197 | Array(16, 17, 18, 19, 20), 198 | Array(21, 22, 23, 24, 0) 199 | ) 200 | ) shouldBe 201 | Array( 202 | Array(0, 0, 0, 0, 0), 203 | Array(0, 0, 0, 0, 0), 204 | Array(0, 12, 13, 0, 0), 205 | Array(0, 17, 18, 0, 0), 206 | Array(0, 0, 0, 0, 0) 207 | ) 208 | } 209 | 210 | "isStringRotation" should "determine if there the strings are rotations of each other" in { 211 | Chapter1Solutions.isStringRotation("", "") shouldBe true 212 | Chapter1Solutions.isStringRotation("a", "a") shouldBe true 213 | Chapter1Solutions.isStringRotation("ab", "ab") shouldBe true 214 | Chapter1Solutions.isStringRotation("ba", "ab") shouldBe true 215 | Chapter1Solutions.isStringRotation( 216 | "waterbottle", 217 | "erbottlewat" 218 | ) shouldBe true 219 | Chapter1Solutions.isStringRotation( 220 | "waterbottle", 221 | "waterbottl" 222 | ) shouldBe false 223 | Chapter1Solutions.isStringRotation( 224 | "waterbottl", 225 | "waterbottle" 226 | ) shouldBe false 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/main/java/com/github/myyk/cracking/Chapter5Solutions.java: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking; 2 | 3 | /** 4 | * Bit Manipulation 5 | */ 6 | public class Chapter5Solutions { 7 | 8 | /** 9 | * Insertion: Given 2 32-bit numbers N and M and two indexes i and j. Insert M into M such that it starts 10 | * at bit j and ends at bit i. 11 | * 12 | * Assumptions: 13 | * i and j have enough bits between them to fit M. 14 | * if there are already bits set in N between i and j, they will be overwritten 15 | * 16 | * Time complexity: O(1) 17 | * Space complexity: O(1) 18 | */ 19 | public static int insert(int n, int m, int j, int i) { 20 | n &= (-1 << (j + 1)) | ((1 << i) - 1); 21 | n |= (m << i); 22 | return n; 23 | } 24 | 25 | /** 26 | * Binary To String: Given a real number between 0 and 1. Return the binary representation as a string. 27 | * 28 | * Assumptions: 29 | * If it doesn't fit in 32 bits, return "ERROR" 30 | * 31 | * Time complexity: O(1) 32 | * Space complexity: O(1) 33 | */ 34 | public static String floatToBinaryString(double num) { 35 | if (num >= 1 || num <= 0) { 36 | return "ERROR"; 37 | } 38 | 39 | StringBuilder buf = new StringBuilder(32); 40 | buf.append('.'); 41 | 42 | while (num > 0) { 43 | if (buf.length() >= 32) { 44 | return "ERROR"; 45 | } 46 | 47 | num = num * 2; 48 | if (num >= 1) { 49 | buf.append("1"); 50 | num -= 1; 51 | } else { 52 | buf.append("0"); 53 | } 54 | } 55 | return buf.toString(); 56 | } 57 | 58 | /** 59 | * Flip Bit to Win: Given a 32-bit integer and the ability to flip one bit from a 0 to a 1, find the 60 | * longest length of sequences of 1s. 61 | * 62 | * Assumptions: 63 | * 64 | * Time complexity: O(n) where n is the number of bits, which means O(1) for 32-bit, but this could be 65 | * general enough to use for longer inputs 66 | * Space complexity: O(1) 67 | * 68 | * Note: Unless I've done something wrong that I don't understand at all. I think my algorithm is much 69 | * better/simpler than the book's optimal answer. 70 | */ 71 | public static int flipBitToWin(int num) { 72 | if (num == -1) { 73 | return 32; 74 | } 75 | 76 | int longest = 1; 77 | int last = 0; 78 | int current = 0; 79 | 80 | for (int i = 0; i < 32; i++) { 81 | int mask = 1 << i; 82 | int bitSet = num & mask; 83 | if (bitSet != 0) { 84 | current++; 85 | longest = Math.max(longest, last + current + 1); 86 | } else { 87 | last = current; 88 | current = 0; 89 | } 90 | } 91 | 92 | return longest; 93 | } 94 | 95 | public static class NextNumberResult { 96 | final int smaller; 97 | final int larger; 98 | 99 | @Override 100 | public String toString() { 101 | return "NextNumberResult [smaller=" + smaller + ", larger=" + larger + "]"; 102 | } 103 | public NextNumberResult(int smaller, int larger) { 104 | super(); 105 | this.smaller = smaller; 106 | this.larger = larger; 107 | } 108 | @Override 109 | public int hashCode() { 110 | final int prime = 31; 111 | int result = 1; 112 | result = prime * result + larger; 113 | result = prime * result + smaller; 114 | return result; 115 | } 116 | @Override 117 | public boolean equals(Object obj) { 118 | if (this == obj) 119 | return true; 120 | if (obj == null) 121 | return false; 122 | if (getClass() != obj.getClass()) 123 | return false; 124 | NextNumberResult other = (NextNumberResult) obj; 125 | if (larger != other.larger) 126 | return false; 127 | return smaller == other.smaller; 128 | } 129 | } 130 | 131 | /** 132 | * Next Number: Given a positive integer, return the next smaller and next larger integer that have the 133 | * same number of 1s as the given integer. 134 | * 135 | * Assumptions: 136 | * if there are no numbers smaller or larger with the same number of 1s, use -1 in that spot 137 | * positive number 138 | * 139 | * Time complexity: O(n) where n is the number of bits in the integer, so O(1) for fixed size 140 | * Space complexity: O(1) 141 | */ 142 | public static NextNumberResult nextNumber(int num) { 143 | if (num <= 0) { 144 | return new NextNumberResult(-1, -1); 145 | } 146 | 147 | int smaller = nextSmallerNumber(num); 148 | int larger = nextLargerNumber(num); 149 | 150 | // handle edge cases 151 | if (larger <= num) { 152 | larger = -1; 153 | } 154 | if (smaller >= num || smaller <= 0) { 155 | smaller = -1; 156 | } 157 | 158 | return new NextNumberResult(smaller, larger); 159 | } 160 | 161 | // counts first two blocks of 0s and 1s from right to left where it starts with startsWith 162 | // array[0] is the first block 163 | private static int[] countBlocks(int num, int startsWith) { 164 | if (startsWith != 0 && startsWith != 1) { throw new IllegalArgumentException("startsWith = [" + startsWith + "] but should be 0 or 1."); } 165 | 166 | int c0 = 0; 167 | int c1 = 0; 168 | for (int i = 0; i < 32; i++) { 169 | int maskedBit = num>>i & 1; 170 | boolean isFirstType = maskedBit == startsWith; 171 | if (isFirstType) { 172 | if (c1 == 0) { 173 | c0++; 174 | } else { 175 | break; 176 | } 177 | } else { 178 | c1++; 179 | } 180 | } 181 | return new int[] { c0, c1 }; 182 | } 183 | 184 | private static int nextLargerNumber(int num) { 185 | int[] c = countBlocks(num, 0); 186 | return num + (1 << c[0]) + ((1 << (c[1] - 1)) - 1); 187 | } 188 | 189 | private static int nextSmallerNumber(int num) { 190 | int[] c = countBlocks(num, 1); 191 | return num - (1 << c[0]) - ((1 << (c[1] - 1)) - 1); 192 | } 193 | 194 | /** 195 | * Debugger: Explain what this code does. 196 | */ 197 | public static boolean doSomethingMysterious(int n) { 198 | // this function checks to see if a number is a power of 2. 199 | // if it is a power of 2 then (n-1) will be all 1s that don't overlap, so it will AND to 0. 200 | // otherwise, it would have at least a 1 still and return false. 201 | return ((n & (n-1)) == 0); 202 | } 203 | 204 | /** 205 | * Conversion: Given two integers, determine the number of bits you would have to flip to convert 206 | * one to the other. 207 | * 208 | * Assumptions: 209 | * 210 | * Time complexity: O(n) where n is the number of bits in the integer, so O(1) for fixed size 211 | * Space complexity: O(1) 212 | * 213 | * Time to code: ~3 minutes 214 | */ 215 | public static int bitsToConvert(int a, int b) { 216 | int differences = 0; 217 | // diff &= (diff-1) // clears the least significant 1 218 | for (int diff = a ^ b; diff != 0; diff &= (diff-1)) { 219 | differences++; 220 | } 221 | return differences; 222 | } 223 | 224 | /** 225 | * Pairwise Swap: Given an integer, swap all the even bits with the odd bits. e.g. Bits 1 and 0 swap, 226 | * bits 2 and 1 swap, etc. 227 | * 228 | * Assumptions: 229 | * 230 | * Time complexity: O(1) 231 | * Space complexity: O(1) 232 | * 233 | * Number of operations: 5 234 | */ 235 | public static int pairwiseSwap(int num) { 236 | return ((num & 0xAAAAAAAA) >>> 1) | ((num & 0x55555555) << 1); 237 | } 238 | 239 | /** 240 | * Draw Line: You have a monochrome screen represented by an array. The screen has a width which is 241 | * divisible by 8. A byte will hold 8 consecutive pixels. Draw a horizontal line from (x1, y) 242 | * to (x2, y) 243 | * 244 | * Assumptions: 245 | * assuming the line is draws with 1s. 246 | * x1 is inclusive 247 | * x2 is exclusive 248 | * x1 is less than x2 249 | * x2 is less than width 250 | * 251 | * Time complexity: O(n) where n is the number of bytes 252 | * Space complexity: O(1) 253 | */ 254 | public static byte[] drawLine(byte[] screen, int width, int x1, int x2, int y) { 255 | if (y < 0 || x1 > x2 || x2 > width ) { throw new IllegalArgumentException(); } 256 | 257 | int yOffset = (width * y) / 8; 258 | byte firstMask = (byte)(0xFF >>> (x1 % 8)); 259 | byte lastMask = (byte)(0xFF << (8 - (x2 % 8))); 260 | for (int xByteOffset = x1/8; xByteOffset < x2/8 + (x2%8==0 ? 0 : 1); xByteOffset++) { 261 | int mask = 0xFF; 262 | if (xByteOffset == x1/8) { 263 | mask &= firstMask; 264 | } 265 | if (xByteOffset == x2/8) { 266 | mask &= lastMask; 267 | } 268 | screen[yOffset + xByteOffset] |= mask; 269 | } 270 | return screen; 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /src/main/java/com/github/myyk/cracking/Chapter2Solutions.java: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | import java.util.Stack; 6 | 7 | /** 8 | * Linked Lists 9 | */ 10 | public class Chapter2Solutions { 11 | public static class Node { 12 | int value; 13 | Node next; 14 | 15 | public Node(int value, Node next) { 16 | super(); 17 | this.value = value; 18 | this.next = next; 19 | } 20 | } 21 | 22 | /** 23 | * Remove Dups: Remove duplicates from the unsorted linked list. 24 | * 25 | * Assumptions: 26 | * singly linked list 27 | * temporary buffer disallowed 28 | * 29 | * Time complexity: O(n^2) 30 | * Space complexity: O(1) 31 | */ 32 | public static Node removeDups(Node n) { 33 | Node current = n; 34 | while (current != null) { 35 | Node runner = current; 36 | while (runner.next != null) { 37 | if (current.value == runner.next.value) { 38 | runner.next = runner.next.next; 39 | } else { 40 | runner = runner.next; 41 | } 42 | } 43 | current = current.next; 44 | } 45 | return n; 46 | } 47 | 48 | /** 49 | * Return Kth to Last: Return k-th to last element in singly linked list. 50 | * 51 | * Assumptions: 52 | * return null if there aren't k+1 elements 53 | * 54 | * Time complexity: O(n) 55 | * Space complexity: O(1) 56 | */ 57 | public static Node kToLast(int k, Node n) { 58 | if (k<0 || n==null) { 59 | return null; 60 | } 61 | 62 | Node current = n; 63 | Node runner = n.next; 64 | 65 | for (int i = 0; i 0); 158 | 159 | if (sum == 0 && a == null && b == null) { 160 | return null; 161 | } else if (a==null && !nextCarry) { 162 | return new Node(sum, nextB); 163 | } else if (b==null && !nextCarry) { 164 | return new Node(sum, nextA); 165 | } else { 166 | return new Node(sum%10, sumLists1Helper(nextA, nextB, nextCarry)); 167 | } 168 | } 169 | 170 | /** 171 | * Sum Lists (1s first): single digit stored in each element, 1s last. Add the numbers. 172 | * 173 | * Assumptions: 174 | * the interviewer wants me to not just reverse the things and write something convoluted instead 175 | * 176 | * Time complexity: O(max(n,m)) 177 | * Space complexity: O(max(n,m)) 178 | */ 179 | public static Node sumLists2(Node n1, Node n2) { 180 | return sumLists2Helper(n1, n2); 181 | } 182 | 183 | private static class SumResult { 184 | Node sum; 185 | boolean carry; 186 | 187 | public SumResult(Node sum, boolean carry) { 188 | this.sum = sum; 189 | this.carry = carry; 190 | } 191 | } 192 | 193 | private static Node sumLists2Helper(Node a, Node b) { 194 | int lenA = findLength(a); 195 | int lenB = findLength(b); 196 | 197 | //pad shorter 198 | if (lenA < lenB) { 199 | a = padZeros(a, lenB - lenA); 200 | } else if (lenA > lenB) { 201 | b = padZeros(b, lenA - lenB); 202 | } 203 | 204 | SumResult result = sumHelperHelper(a, b); 205 | if (result.carry) { 206 | return new Node(1, result.sum); 207 | } else { 208 | return result.sum; 209 | } 210 | } 211 | 212 | private static SumResult sumHelperHelper(Node a, Node b) { 213 | if (a == null) { 214 | return new SumResult(null, false); 215 | } else { 216 | SumResult sumRight = sumHelperHelper(a.next, b.next); 217 | int sum = a.value + b.value + (sumRight.carry ? 1 : 0); 218 | return new SumResult(new Node(sum%10, sumRight.sum), (sum/10 == 1)); 219 | } 220 | } 221 | 222 | private static int findLength(Node n) { 223 | int length = 0; 224 | while (n != null) { 225 | length++; 226 | n = n.next; 227 | } 228 | return length; 229 | } 230 | 231 | private static Node padZeros(Node n, int zeros) { 232 | if (zeros <= 0) { 233 | return n; 234 | } else { 235 | return new Node(0, padZeros(n, zeros-1)); 236 | } 237 | } 238 | 239 | /** 240 | * Palindrome: Are the contents of a singly linked list palindromic. 241 | * 242 | * Assumptions: 243 | * length is not given 244 | * 245 | * Time complexity: O(n) 246 | * Space complexity: O(n) 247 | */ 248 | public static boolean isPalindrome(Node n) { 249 | if (n == null || n.next == null) { 250 | return true; 251 | } 252 | 253 | Node current = n; 254 | Node runner = n; 255 | Stack stack = new Stack<>(); 256 | 257 | while (runner != null && runner.next != null) { 258 | stack.push(current.value); 259 | current = current.next; 260 | runner = runner.next.next; 261 | } 262 | 263 | // odd number of elements, skip the middle element 264 | if (runner != null) { 265 | current = current.next; 266 | } 267 | 268 | while (current != null) { 269 | if (current.value != stack.pop()) { 270 | return false; 271 | } 272 | current = current.next; 273 | } 274 | 275 | return true; 276 | } 277 | 278 | /** 279 | * Intersection: Find where two singly linked lists intersect if they do. 280 | * 281 | * Assumptions: 282 | * intersection is done by reference equality 283 | * the lists aren't too different in length 284 | * 285 | * Time complexity: O(n+m) 286 | * Space complexity: O(n) when n is the length of the first list 287 | */ 288 | public static Node findIntersection(Node a, Node b) { 289 | Node current = a; 290 | Set nodes = new HashSet<>(); 291 | while (current != null) { 292 | nodes.add(current); 293 | current = current.next; 294 | } 295 | 296 | current = b; 297 | while (current != null) { 298 | if (nodes.contains(current)) { 299 | return current; 300 | } 301 | current = current.next; 302 | } 303 | 304 | return null; 305 | } 306 | 307 | /** 308 | * Loop Detection: Find where a loop started if there is one in the singly linked list. 309 | * 310 | * Assumptions: 311 | * 312 | * Time complexity: O(n) 313 | * Space complexity: O(1) 314 | */ 315 | public static Node findLoopStart(Node n) { 316 | Node slow = n; 317 | Node fast = n; 318 | while (fast != null && fast.next != null) { 319 | slow = slow.next; 320 | fast = fast.next.next; 321 | if (slow == fast) // tricky, do this here instead of in while or else breaks on start 322 | break; 323 | } 324 | 325 | if (fast == null || fast.next == null) { 326 | return null; 327 | } 328 | 329 | slow = n; 330 | while (slow != fast) { 331 | slow = slow.next; 332 | fast = fast.next; 333 | } 334 | 335 | return slow; 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /src/main/java/com/github/myyk/cracking/Chapter1Solutions.java: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking; 2 | 3 | /** 4 | * Arrays and Strings 5 | */ 6 | public class Chapter1Solutions { 7 | /** 8 | * Is Unique? Are all characters unique in the string. 9 | * 10 | * Assumptions: Characters are ASCII characters, capitalization matters 11 | * 12 | * Time complexity: O(min(c,n)) where c is number of characters in alphabet (128) 13 | * and n is the length of the string. Or O(1) technically. 14 | * Space complexity: O(c) where c is number of characters in alphabet (128). Or O(1) technically. 15 | */ 16 | public static boolean stringHasAllUniqueCharacters(String str) { 17 | // If there's more characters than there are in the alphabet, then there are duplicates. 18 | if (str.length() > 128) { 19 | return false; 20 | } else { 21 | boolean[] seen = new boolean[128]; 22 | for (int i=0; i=0 && numSpaces>0; i--) { 88 | char c = chars[i]; 89 | if (c == ' ') { 90 | numSpaces--; 91 | chars[i+2*numSpaces] = '%'; 92 | chars[i+2*numSpaces+1] = '2'; 93 | chars[i+2*numSpaces+2] = '0'; 94 | } else { 95 | chars[i+2*numSpaces] = c; 96 | } 97 | } 98 | } 99 | 100 | /** 101 | * Palindrome Permutation: Given a string check if a permutation can be made from it. 102 | * 103 | * Highly optimized solution using a single int for space. 104 | * 105 | * Assumptions: 106 | * case insensitive 107 | * alphabet is A-Z only 108 | * 109 | * Time complexity: O(n) 110 | * Space complexity: O(1) 111 | */ 112 | public static boolean canHavePalindromePermutation(String str) { 113 | int bitMap = 0; 114 | for (int i=0; i= 0 ) { 117 | bitMap ^= 1<= 'a' && c <= 'z') { 126 | return c-'a'; 127 | } else if(c >= 'A' && c <= 'Z') { 128 | return c-'A'; 129 | } else { 130 | return -1; 131 | } 132 | } 133 | 134 | private static boolean containsOnlyOneOdd(int bitMap) { 135 | return (bitMap & (bitMap-1)) == 0; 136 | } 137 | 138 | /** 139 | * One Away: If there are only insert, remove, update edit operations can 2 strings be one (or less) 140 | * edit away from each other. 141 | * 142 | * Assumptions: 143 | * 144 | * Time complexity: O(n) 145 | * Space complexity: O(1) 146 | */ 147 | public static boolean isOneAway(String str1, String str2) { 148 | //optimization 149 | if(Math.abs(str1.length() - str2.length()) > 1) { 150 | return false; 151 | } else { 152 | String shorter = str1, longer = str2; 153 | if (str1.length() > str2.length()) { 154 | shorter = str2; 155 | longer = str1; 156 | } 157 | 158 | boolean differenceFound = false; 159 | for (int i = 0, j = 0; i < shorter.length() && j < longer.length(); i++, j++) { 160 | if (shorter.charAt(i) != longer.charAt(j)) { 161 | if (differenceFound) { 162 | return false; 163 | } 164 | if (shorter.length() != longer.length()) { 165 | i--; // we insert a character at i, so we need to check this character against the next j, so we manually rewind 166 | } 167 | differenceFound = true; 168 | } 169 | } 170 | return true; 171 | } 172 | } 173 | 174 | /** 175 | * String Compression: Using basic compression of letter followed by frequency. Returns the original 176 | * string if compressed version is not smaller. 177 | * 178 | * Assumptions: 179 | * it's ok to allocate a string buffer of size equal to the input string 180 | * it's better to have simpler code and not figure out if compressed result is smaller before creating it 181 | * 182 | * Time complexity: O(n) 183 | * Space complexity: O(n) 184 | */ 185 | public static String compress(String str) { 186 | if (str.isEmpty()) { 187 | return ""; 188 | } 189 | 190 | int count = 1; 191 | char last = str.charAt(0); 192 | StringBuilder buf = new StringBuilder(str.length()); 193 | for (int i=1; i JInt} 6 | import scala.jdk.CollectionConverters._ 7 | import scala.util.Random 8 | import org.scalatest._ 9 | import flatspec._ 10 | import matchers._ 11 | import java.util.Optional 12 | import java.lang 13 | 14 | class Chapter7SolutionsTest extends AnyFlatSpec with should.Matchers { 15 | def testTripleStep(tripleStep: Int => BigInt): Unit = { 16 | tripleStep(0) shouldBe 1 17 | tripleStep(1) shouldBe 1 18 | tripleStep(2) shouldBe 2 19 | tripleStep(3) shouldBe 4 20 | tripleStep(4) shouldBe 7 21 | tripleStep(5) shouldBe 13 22 | } 23 | 24 | "tripleStepRecursive" should "count the ways the child can go up the stairs with 1, 2, or 3 steps at a time" in { 25 | testTripleStep(Chapter7Solutions.tripleStepRecursive) 26 | } 27 | "tripleStepIterative" should "count the ways the child can go up the stairs with 1, 2, or 3 steps at a time" in { 28 | testTripleStep(Chapter7Solutions.tripleStepIterative) 29 | } 30 | 31 | def testFindPath(grid: Array[Array[Boolean]]): Unit = { 32 | val path = Chapter7Solutions.findPath(grid) 33 | val robot = new Robot(0, 0) 34 | path should not be null 35 | for (next <- path) { 36 | if (next) { 37 | robot.moveRight() 38 | } else { 39 | robot.moveDown() 40 | } 41 | 42 | // check collision 43 | grid(robot.getY)(robot.getX) shouldBe false 44 | } 45 | robot.getX shouldBe grid(0).length - 1 46 | robot.getY shouldBe grid.length - 1 47 | } 48 | 49 | def testFindPathNegative(grid: Array[Array[Boolean]]): Unit = { 50 | Chapter7Solutions.findPath(grid) shouldBe null 51 | } 52 | 53 | "findPath" should "find a valid path in the grid" in { 54 | testFindPathNegative(Array(Array(false, true))) 55 | testFindPathNegative(Array(Array(false, true), Array(true, false))) 56 | testFindPathNegative(Array(Array(false, false), Array(false, true))) 57 | 58 | testFindPath(Array(Array(x = true))) 59 | testFindPath(Array(Array(x = false))) 60 | testFindPath(Array(Array(false, false), Array(false, false))) 61 | // testFindPath(Array(Array(false, true), Array(false, false))) 62 | // testFindPath(Array(Array(false, false), Array(true, false))) 63 | // testFindPath(Array(Array(false, true, false), Array(false, false, true), Array(true, false, false))) 64 | } 65 | 66 | "movesToSolution" should "turn some moves into a solution array" in { 67 | Chapter7Solutions.movesToSolution( 68 | List( 69 | List[Optional[lang.Boolean]]( 70 | Optional.empty, 71 | Optional.of(true), 72 | Optional.of(true) 73 | ).asJava 74 | ).asJava 75 | ) shouldBe Array(true, true) 76 | Chapter7Solutions.movesToSolution( 77 | List( 78 | List[Optional[lang.Boolean]]( 79 | Optional.empty, 80 | Optional.of(true), 81 | Optional.empty 82 | ).asJava, 83 | List[Optional[lang.Boolean]]( 84 | Optional.empty, 85 | Optional.of(false), 86 | Optional.of(true) 87 | ).asJava 88 | ).asJava 89 | ) shouldBe Array(true, false, true) 90 | } 91 | 92 | def testFindMagicIndex(a: Array[Int], op: Array[Int] => Int): Unit = { 93 | val i = op(a) 94 | i should be >= 0 95 | a(i) shouldBe i 96 | } 97 | 98 | def testFindMagicIndexNotFound(a: Array[Int], op: Array[Int] => Int): Unit = { 99 | op(a) shouldBe -1 100 | } 101 | 102 | "findMagicIndexDistinct" should "find a magic index if there's one and elements are distinct" in { 103 | testFindMagicIndexNotFound( 104 | Array(2), 105 | Chapter7Solutions.findMagicIndexDistinct 106 | ) 107 | 108 | testFindMagicIndex(Array(0), Chapter7Solutions.findMagicIndexDistinct) 109 | testFindMagicIndex( 110 | Array(-1, 0, 1, 2, 3, 5), 111 | Chapter7Solutions.findMagicIndexDistinct 112 | ) 113 | testFindMagicIndex( 114 | Array(-1, 0, 1, 2, 4, 6), 115 | Chapter7Solutions.findMagicIndexDistinct 116 | ) 117 | testFindMagicIndex( 118 | Array(0, 2, 4, 6), 119 | Chapter7Solutions.findMagicIndexDistinct 120 | ) 121 | } 122 | 123 | "findMagicIndexNonDistinct" should "find a magic index if there's one and elements are distinct" in { 124 | testFindMagicIndexNotFound( 125 | Array(2), 126 | Chapter7Solutions.findMagicIndexNonDistinct 127 | ) 128 | 129 | testFindMagicIndex(Array(0), Chapter7Solutions.findMagicIndexNonDistinct) 130 | testFindMagicIndex( 131 | Array(-1, 0, 1, 2, 3, 5), 132 | Chapter7Solutions.findMagicIndexNonDistinct 133 | ) 134 | testFindMagicIndex( 135 | Array(-1, 0, 1, 2, 4, 6), 136 | Chapter7Solutions.findMagicIndexNonDistinct 137 | ) 138 | testFindMagicIndex( 139 | Array(0, 2, 4, 6), 140 | Chapter7Solutions.findMagicIndexNonDistinct 141 | ) 142 | testFindMagicIndex( 143 | Array(1, 1, 2, 4, 6), 144 | Chapter7Solutions.findMagicIndexNonDistinct 145 | ) 146 | testFindMagicIndex( 147 | Array(-1, -1, 3, 4, 6, 6, 6), 148 | Chapter7Solutions.findMagicIndexNonDistinct 149 | ) 150 | testFindMagicIndex( 151 | Array(-100, -100, -100, -100, -100, -100, 6), 152 | Chapter7Solutions.findMagicIndexNonDistinct 153 | ) 154 | } 155 | 156 | def testPowerSet[T](input: Set[T], expected: Set[Set[T]]): Unit = { 157 | Chapter7Solutions 158 | .powerSet(input.asJava) 159 | .asScala 160 | .map(_.asScala.toSet) 161 | .toSet shouldBe expected 162 | } 163 | 164 | "powerSet" should "get the power set of a set" in { 165 | testPowerSet(Set.empty[Int], Set(Set.empty[Int])) 166 | testPowerSet(Set(1), Set(Set.empty[Int], Set(1))) 167 | testPowerSet(Set(1, 2), Set(Set.empty[Int], Set(1), Set(2), Set(1, 2))) 168 | testPowerSet( 169 | Set(1, 2, 3), 170 | Set( 171 | Set.empty[Int], 172 | Set(1), 173 | Set(2), 174 | Set(3), 175 | Set(1, 2), 176 | Set(2, 3), 177 | Set(1, 3), 178 | Set(1, 2, 3) 179 | ) 180 | ) 181 | } 182 | 183 | def testMultiply(a: Int, b: Int): Unit = { 184 | Chapter7Solutions.multiply(a, b) shouldBe (a * b) 185 | } 186 | 187 | "multiply" should "work like * multiply" in { 188 | testMultiply(0, 0) 189 | testMultiply(0, 1) 190 | testMultiply(1, 0) 191 | testMultiply(1, 1) 192 | testMultiply(123, 312) 193 | testMultiply(123, -1) 194 | testMultiply(-1, 123) 195 | testMultiply(-123, -1) 196 | for (_ <- 1 until 100) { 197 | testMultiply(Random.nextInt(), Random.nextInt()) 198 | } 199 | } 200 | 201 | "permutationsUnique" should "get all permutations of the string" in { 202 | Chapter7Solutions 203 | .permutationsUnique("abc") 204 | .asScala 205 | .toSet shouldBe "abc".permutations.toSet 206 | Chapter7Solutions 207 | .permutationsUnique("myyk") 208 | .asScala 209 | .toSet shouldBe "myyk".permutations.toSet 210 | Chapter7Solutions.permutationsUnique("aaaaa").size shouldBe 1 211 | } 212 | 213 | "parens" should "get all legal combinations of n open and close parens" in { 214 | Chapter7Solutions.parens(0).asScala.toSet shouldBe Set() 215 | Chapter7Solutions.parens(1).asScala.toSet shouldBe Set("()") 216 | Chapter7Solutions.parens(2).asScala.toSet shouldBe Set("()()", "(())") 217 | Chapter7Solutions.parens(3).asScala.toSet shouldBe Set( 218 | "()()()", 219 | "(())()", 220 | "()(())", 221 | "((()))", 222 | "(()())" 223 | ) 224 | Chapter7Solutions.parens(4).asScala.toSet shouldBe Set( 225 | "()()()()", 226 | "()(())()", 227 | "()()(())", 228 | "()((()))", 229 | "()(()())", 230 | "(()()())", 231 | "((())())", 232 | "(()(()))", 233 | "(((())))", 234 | "((()()))", 235 | "(())()()", 236 | "((()))()", 237 | "(()())()" 238 | ) 239 | } 240 | 241 | "paintFill" should "fill the all the connected old color with the new color" in { 242 | val newColor = new Color(255.toByte, 255.toByte, 255.toByte) 243 | val color1 = new Color(1.toByte, 1.toByte, 1.toByte) 244 | val color2 = new Color(2.toByte, 2.toByte, 2.toByte) 245 | val image = Array( 246 | Array(color1, color1, color1, color1, color1), 247 | Array(color2, color2, color1, color2, color2), 248 | Array(color1, color1, color1, color1, color1), 249 | Array(color2, color2, color1, color2, color1), 250 | Array(color1, color2, color1, color2, color1) 251 | ) 252 | val expectedImage = Array( 253 | Array(newColor, newColor, newColor, newColor, newColor), 254 | Array(color2, color2, newColor, color2, color2), 255 | Array(newColor, newColor, newColor, newColor, newColor), 256 | Array(color2, color2, newColor, color2, newColor), 257 | Array(color1, color2, newColor, color2, newColor) 258 | ) 259 | Chapter7Solutions.paintFill(image, 2, 2, newColor) shouldBe expectedImage 260 | } 261 | 262 | def coinsCount(set: Set[Int], total: Int): Int = { 263 | Chapter7Solutions.coinsCount(set.map(JInt.valueOf).asJava, total) 264 | } 265 | 266 | "coinsCount" should "get the number of ways to count to the total with the provided coins" in { 267 | coinsCount(Set(1, 5, 10, 25), 0) shouldBe 1 268 | coinsCount(Set(1, 5, 10, 25), 1) shouldBe 1 269 | coinsCount(Set(1, 5, 10, 25), 5) shouldBe 2 270 | coinsCount(Set(1, 5, 10, 25), 10) shouldBe 4 271 | coinsCount(Set(1, 5, 10, 25), 11) shouldBe 4 272 | coinsCount(Set(5, 10, 25), 25) shouldBe Array( 273 | 25, 274 | 10 + 10 + 5, 275 | 10 + 5 + 5 + 5, 276 | 5 * 5 277 | ).length 278 | coinsCount(Set(5, 10, 25), 26) shouldBe 0 279 | 280 | } 281 | 282 | "placeQueens" should "give all valid combinations of placing n queens on an nxn chess board" in { 283 | // val n = 4 284 | // for { 285 | // board <- Chapter7Solutions.placeQueens(n) 286 | // _ <- Some(println("------------")) 287 | // queen <- board 288 | // } { 289 | // println( "o"*queen + "x" + "o"*(n - 1 - queen)) 290 | // } 291 | 292 | Chapter7Solutions.placeQueens(4).size shouldBe 2 293 | Chapter7Solutions.placeQueens(6).size shouldBe 4 294 | Chapter7Solutions.placeQueens(8).size shouldBe 92 295 | Chapter7Solutions.placeQueens(10).size shouldBe 724 296 | } 297 | 298 | "stackHeight" should "give the maximal box stack height where boxes given the constraints" in { 299 | Chapter7Solutions.stackHeight( 300 | List(new Box(1, 1, 1), new Box(2, 2, 2), new Box(3, 3, 3)).asJava 301 | ) shouldBe 6 302 | Chapter7Solutions.stackHeight( 303 | List(new Box(1, 1, 1), new Box(1, 1, 1), new Box(3, 3, 3)).asJava 304 | ) shouldBe 4 305 | Chapter7Solutions.stackHeight( 306 | List(new Box(3, 1, 1), new Box(2, 1, 1), new Box(3, 3, 3)).asJava 307 | ) shouldBe 5 308 | // could use more and better test cases 309 | } 310 | 311 | "countEval" should "work for fundamental operations" in { 312 | Chapter7Solutions.countEval("1", false) shouldBe 0 313 | Chapter7Solutions.countEval("1", true) shouldBe 1 314 | Chapter7Solutions.countEval("0", false) shouldBe 1 315 | Chapter7Solutions.countEval("0", true) shouldBe 0 316 | 317 | Chapter7Solutions.countEval("1^1", false) shouldBe 1 318 | Chapter7Solutions.countEval("0^0", false) shouldBe 1 319 | Chapter7Solutions.countEval("1^0", false) shouldBe 0 320 | Chapter7Solutions.countEval("0^1", false) shouldBe 0 321 | Chapter7Solutions.countEval("1^1", true) shouldBe 0 322 | Chapter7Solutions.countEval("0^0", true) shouldBe 0 323 | Chapter7Solutions.countEval("1^0", true) shouldBe 1 324 | Chapter7Solutions.countEval("0^1", true) shouldBe 1 325 | 326 | Chapter7Solutions.countEval("1|1", false) shouldBe 0 327 | Chapter7Solutions.countEval("0|0", false) shouldBe 1 328 | Chapter7Solutions.countEval("1|0", false) shouldBe 0 329 | Chapter7Solutions.countEval("0|1", false) shouldBe 0 330 | Chapter7Solutions.countEval("1|1", true) shouldBe 1 331 | Chapter7Solutions.countEval("0|0", true) shouldBe 0 332 | Chapter7Solutions.countEval("1|0", true) shouldBe 1 333 | Chapter7Solutions.countEval("0|1", true) shouldBe 1 334 | 335 | Chapter7Solutions.countEval("1&1", false) shouldBe 0 336 | Chapter7Solutions.countEval("0&0", false) shouldBe 1 337 | Chapter7Solutions.countEval("1&0", false) shouldBe 1 338 | Chapter7Solutions.countEval("0&1", false) shouldBe 1 339 | Chapter7Solutions.countEval("1&1", true) shouldBe 1 340 | Chapter7Solutions.countEval("0&0", true) shouldBe 0 341 | Chapter7Solutions.countEval("1&0", true) shouldBe 0 342 | Chapter7Solutions.countEval("0&1", true) shouldBe 0 343 | } 344 | 345 | "countEval" should "get the number of ways parens can be added to the expression to get the result" in { 346 | Chapter7Solutions.countEval("1^0|0|1", false) shouldBe 2 347 | Chapter7Solutions.countEval("0&0&0&1^1|0", true) shouldBe 10 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /src/main/java/com/github/myyk/cracking/Chapter3Solutions.java: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking; 2 | 3 | import java.util.Collection; 4 | import java.util.EmptyStackException; 5 | import java.util.Iterator; 6 | import java.util.LinkedList; 7 | import java.util.ListIterator; 8 | import java.util.NoSuchElementException; 9 | import java.util.Queue; 10 | import java.util.Stack; 11 | 12 | import com.google.common.collect.Lists; 13 | 14 | /** 15 | * Stacks and Queues 16 | */ 17 | public class Chapter3Solutions { 18 | 19 | public static class FullStackException extends RuntimeException { 20 | private static final long serialVersionUID = -8002601578576477109L; 21 | } 22 | 23 | /** 24 | * Three In One: Implement 3 stacks with 1 array. 25 | * 26 | * Assumptions: 27 | * fixed size array and divisions is ok to reduce complexity in an interview setting. 28 | * stackNum is zero-indexed 29 | * 30 | * push: 31 | * Time complexity: O(1) 32 | * pop: 33 | * Time complexity: O(1) 34 | * peek: 35 | * Time complexity: O(1) 36 | * isEmpty: 37 | * Time complexity: O(1) 38 | */ 39 | @SuppressWarnings("unchecked") 40 | public static class FixedMultiStack { 41 | final int numberOfStacks = 3; 42 | final int stackCapacity; 43 | final int[] stackStartIndexes; 44 | final Object[] elements; 45 | 46 | public FixedMultiStack(final int stackCapacity) { 47 | if (stackCapacity < 0) { 48 | throw new IllegalArgumentException("stackCapacity should greater than 0"); 49 | } 50 | this.stackCapacity = stackCapacity; 51 | this.stackStartIndexes = new int[numberOfStacks]; 52 | this.elements = new Object[stackCapacity]; 53 | for (int i = 0; i= stackCapacity/numberOfStacks*(stackNum+1)) { 81 | throw new FullStackException(); 82 | } 83 | elements[start] = elem; 84 | stackStartIndexes[stackNum] = start+1; 85 | } 86 | 87 | private void validateStackNumber(int stackNum) { 88 | if (stackNum > numberOfStacks || stackNum < 0) { 89 | throw new IllegalArgumentException("stackNum should be less than " + numberOfStacks); 90 | } 91 | } 92 | } 93 | 94 | /** 95 | * Stack Min: Stack with a min() function that gets the minimum value in O(1) time with other 96 | * stack functions still being O(1). 97 | * 98 | * Assumptions: 99 | * 100 | * Design: 101 | * Use a stack for storing the mins, pop min when popping if it equals the top of the min stack. 102 | */ 103 | public static class StackWithMin> extends Stack { 104 | private static final long serialVersionUID = -1006279127078761653L; 105 | 106 | final private Stack minStack = new Stack<>(); 107 | 108 | public T min() { 109 | return minStack.peek(); 110 | } 111 | 112 | @Override 113 | public T push(T i) { 114 | if (isEmpty() || i.compareTo(min()) <= 0) { 115 | minStack.push(i); 116 | } 117 | 118 | return super.push(i); 119 | } 120 | 121 | @Override 122 | public T pop() { 123 | T result = super.pop(); 124 | if (result.compareTo(min()) <= 0) { 125 | minStack.pop(); 126 | } 127 | return result; 128 | } 129 | } 130 | 131 | /** 132 | * Stacks of Plates: A stack that when it gets larger than a threshold it creates another stack. 133 | * There's a new method popAt(int index) which pops from the specified sub-stack. 134 | * 135 | * Assumptions: 136 | * I should remove empty sub-stacks 137 | * sub-stacks are indexed like stacks, next to pop is at size() - 1, first pushed at 0. 138 | * it's okay to have sub-indexes in the middle at less than full capacity 139 | * 140 | * Design: 141 | * Extending Stack doesn't really make sense, but there's no Java interface for Stack, so... 142 | */ 143 | public static class SetOfStacks extends Stack { 144 | private static final long serialVersionUID = -2895833133191086808L; 145 | 146 | private final int threshold; 147 | private final Stack> stacks; 148 | 149 | public SetOfStacks(final int threshold) { 150 | super(); 151 | if (threshold < 0) { 152 | throw new IllegalArgumentException(); 153 | } 154 | this.threshold = threshold; 155 | this.stacks = new Stack<>(); 156 | } 157 | 158 | private void checkConsistency() { 159 | for (Stack stack : stacks) { 160 | if (stack.isEmpty()) { 161 | throw new IllegalStateException("sub-stacks should never be empty"); 162 | } 163 | } 164 | } 165 | 166 | @Override 167 | public T push(T item) { 168 | T result; 169 | if (stacks.isEmpty() || stacks.peek().size() >= threshold) { 170 | result = pushOnNewStack(item); 171 | } else { 172 | result = pushOnCurrentStack(item); 173 | } 174 | checkConsistency(); 175 | return result; 176 | } 177 | 178 | private T pushOnCurrentStack(T item) { 179 | return stacks.peek().push(item); 180 | } 181 | 182 | private T pushOnNewStack(T item) { 183 | Stack newStack = new Stack<>(); 184 | newStack.push(item); 185 | stacks.push(newStack); 186 | return item; 187 | } 188 | 189 | @Override 190 | public T pop() { 191 | //delegate exception handling to Stack implementation. 192 | T result = stacks.peek().pop(); 193 | if (stacks.peek().isEmpty()) { 194 | removeStack(stacks.size() - 1); 195 | } 196 | checkConsistency(); 197 | return result; 198 | } 199 | 200 | private Stack removeStack(int i) { 201 | return stacks.remove(i); 202 | } 203 | 204 | public T popAt(int index) { 205 | Stack stack = stacks.get(index); 206 | T result = stack.pop(); 207 | if (stack.empty()) { 208 | removeStack(index); 209 | } 210 | return result; 211 | } 212 | 213 | @Override 214 | public boolean isEmpty() { 215 | return stacks.isEmpty(); 216 | } 217 | } 218 | 219 | /** 220 | * Queue via Stacks: Implement a queue using two stacks. 221 | * 222 | * Assumptions: 223 | * all the irrelevant to the parts of the interface will throw exceptions 224 | * 225 | * Design: 226 | */ 227 | public static class QueueFromStacks implements Queue { 228 | final Stack front = new Stack<>(); 229 | final Stack back = new Stack<>(); 230 | 231 | /** 232 | * Moves front to back. 233 | */ 234 | private void popAllFront() { 235 | if (!back.isEmpty()) { 236 | throw new IllegalStateException("Can't pop all from front if there's still elements in the back."); 237 | } 238 | while (!front.isEmpty()) { 239 | back.push(front.pop()); 240 | } 241 | } 242 | 243 | @Override 244 | public int size() { 245 | return front.size() + back.size(); 246 | } 247 | 248 | @Override 249 | public boolean isEmpty() { 250 | return size() == 0; 251 | } 252 | 253 | @Override 254 | public boolean contains(Object o) { 255 | return front.contains(o) || back.contains(o); 256 | } 257 | 258 | @Override 259 | public Iterator iterator() { 260 | throw new UnsupportedOperationException(); 261 | } 262 | 263 | @Override 264 | public Object[] toArray() { 265 | throw new UnsupportedOperationException(); 266 | } 267 | 268 | @Override 269 | public E[] toArray(E[] a) { 270 | throw new UnsupportedOperationException(); 271 | } 272 | 273 | @Override 274 | public boolean remove(Object o) { 275 | if (back.remove(o)) { 276 | return true; 277 | } else { 278 | ListIterator it = front.listIterator(front.size()); 279 | while (it.hasPrevious()) { 280 | if (it.previous().equals(o)) { 281 | it.remove(); 282 | return true; 283 | } 284 | } 285 | } 286 | return false; 287 | } 288 | 289 | @Override 290 | public boolean containsAll(Collection c) { 291 | throw new UnsupportedOperationException(); 292 | } 293 | 294 | @Override 295 | public boolean addAll(Collection c) { 296 | for (T next: c) { 297 | front.push(next); 298 | } 299 | return true; 300 | } 301 | 302 | @Override 303 | public boolean removeAll(Collection c) { 304 | Iterator it = c.iterator(); 305 | boolean result = true; 306 | while (it.hasNext()) { 307 | result &= remove(it.next()); 308 | } 309 | return result; 310 | } 311 | 312 | @Override 313 | public boolean retainAll(Collection c) { 314 | throw new UnsupportedOperationException(); 315 | } 316 | 317 | @Override 318 | public void clear() { 319 | front.clear(); 320 | back.clear(); 321 | } 322 | 323 | @Override 324 | public boolean add(T e) { 325 | front.push(e); 326 | return true; 327 | } 328 | 329 | @Override 330 | public boolean offer(T e) { 331 | front.push(e); 332 | return true; 333 | } 334 | 335 | @Override 336 | public T remove() { 337 | if (isEmpty()) { 338 | throw new NoSuchElementException("Cannot remove when queue is empty."); 339 | } 340 | return poll(); 341 | } 342 | 343 | @Override 344 | public T poll() { 345 | if (isEmpty()) { 346 | return null; 347 | } else if (back.isEmpty()) { 348 | popAllFront(); 349 | } 350 | 351 | return back.pop(); 352 | } 353 | 354 | @Override 355 | public T element() { 356 | if (isEmpty()) { 357 | throw new NoSuchElementException("Cannot see element when queue is empty."); 358 | } 359 | return peek(); 360 | } 361 | 362 | @Override 363 | public T peek() { 364 | if (isEmpty()) { 365 | return null; 366 | } else if (back.isEmpty()) { 367 | popAllFront(); 368 | } 369 | 370 | return back.peek(); 371 | } 372 | } 373 | 374 | /** 375 | * Sort Stack: Sort a stack only using another stack. 376 | * 377 | * Assumptions: 378 | * sort ascending 379 | * in-place sorting 380 | * 381 | * Time complexity: O(n^2) 382 | * Space complexity: O(n) 383 | */ 384 | public static > void sortStack(Stack stack) { 385 | Stack temp = new Stack<>(); 386 | while (!stack.isEmpty()) { 387 | T next = stack.pop(); 388 | while (!temp.isEmpty() && next.compareTo(temp.peek()) >= 0) { 389 | stack.push(temp.pop()); 390 | } 391 | temp.push(next); 392 | } 393 | 394 | while (!temp.isEmpty()) { 395 | stack.push(temp.pop()); 396 | } 397 | } 398 | 399 | /** 400 | * Animal Shelter: Animals are given out in a LIFO basis. Adopters can choose between 401 | * cat or dog. 402 | * 403 | * Assumptions: 404 | * can use java.util.LinkedList 405 | * 406 | * Time complexity and Space complexity: 407 | * Enqueues are O(1) because it's just the end linked list. 408 | * Dequeues are O(1) since we just check which of the two queues to look at. 409 | * 410 | * If I were to expand on this, I'm probably have a pet type to queue map. So I could look 411 | * up the different animals more easily. Maybe also the dequeue method could take a collection 412 | * of possible animals that the adopter would like. 413 | */ 414 | public static class AnimalShelter { 415 | public static abstract class Pet { 416 | private int id; 417 | public Pet(int id) { this.id = id; } 418 | 419 | public int getId() { return id; } 420 | public void setId(int id) { this.id = id; } 421 | } 422 | public static class Dog extends Pet { 423 | public Dog(int id) { super(id); } 424 | } 425 | public static class Cat extends Pet { 426 | public Cat(int id) { super(id); } 427 | } 428 | 429 | public int nextId = 0; // this give ordering of when pets were added 430 | public LinkedList dogs = Lists.newLinkedList(); 431 | public LinkedList cats = Lists.newLinkedList(); 432 | 433 | public Pet enqueue(Pet pet) { 434 | pet.setId(nextId++); 435 | if (pet instanceof Dog) { 436 | dogs.addLast((Dog) pet); 437 | } else if (pet instanceof Cat) { 438 | cats.addLast((Cat) pet); 439 | } 440 | return pet; 441 | } 442 | 443 | public Cat dequeueCat() { 444 | if (cats.isEmpty()) { 445 | throw new NoSuchElementException("There are no more cats to adopt."); 446 | } 447 | return cats.removeFirst(); 448 | } 449 | 450 | public Dog dequeueDog() { 451 | if (dogs.isEmpty()) { 452 | throw new NoSuchElementException("There are no more dogs to adopt."); 453 | } 454 | return dogs.removeFirst(); 455 | } 456 | 457 | public Pet dequeueAny() { 458 | if (dogs.isEmpty() && cats.isEmpty()) { 459 | throw new NoSuchElementException("There are no more pets to adopt."); 460 | } else if (dogs.isEmpty()) { 461 | return dequeueCat(); 462 | } else if (cats.isEmpty()) { 463 | return dequeueDog(); 464 | } else { 465 | if (cats.peek().getId() < dogs.peek().getId()) { 466 | return dequeueCat(); 467 | } else { 468 | return dequeueDog(); 469 | } 470 | } 471 | } 472 | } 473 | } 474 | -------------------------------------------------------------------------------- /src/test/scala/com/github/myyk/cracking/Chapter16SolutionsTest.scala: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking 2 | 3 | import com.github.myyk.cracking.Chapter16Solutions.AntGrid.{ 4 | AntGridResult, 5 | Direction 6 | } 7 | import com.github.myyk.cracking.Chapter16Solutions.{ 8 | EnglishIntMaker, 9 | Line, 10 | MasterMindResult, 11 | MutableInteger, 12 | Person, 13 | Point, 14 | TicTacToe, 15 | WordFrequencies, 16 | bestLine, 17 | countDivingBoardsOfKPieces, 18 | countDivingBoardsOfSize, 19 | divide, 20 | factorialZeroes, 21 | isWonTicTacToe, 22 | livingPeople, 23 | livingPeopleBruteForce, 24 | masterMindScore, 25 | masterMindScore2, 26 | multiply, 27 | numberMax, 28 | smallestDifference, 29 | subtract, 30 | _ 31 | } 32 | 33 | import java.lang.{Integer => JInt} 34 | import scala.jdk.CollectionConverters._ 35 | import scala.util.Random 36 | 37 | import org.scalatest._ 38 | import flatspec._ 39 | import matchers._ 40 | 41 | class Chapter16SolutionsTest extends AnyFlatSpec with should.Matchers { 42 | "swapInPlace" should "swap the two integers without using additional space" in { 43 | val originalA = 123456 44 | val originalB = 67890 45 | val a = new MutableInteger(originalA) 46 | val b = new MutableInteger(originalB) 47 | Chapter16Solutions.swapInPlace(a, b) 48 | a.value shouldBe originalB 49 | b.value shouldBe originalA 50 | } 51 | 52 | "WordFrequencies" should "be able to get a word's frequency from a given text" in { 53 | val wordFreqs = 54 | new WordFrequencies("a: b c d, å åå e f g aaa' a a a -aaa- a") 55 | wordFreqs.getFrequency("apple") shouldBe 0 56 | wordFreqs.getFrequency("a") shouldBe 5 57 | wordFreqs.getFrequency("A") shouldBe 5 58 | wordFreqs.getFrequency("aaa") shouldBe 2 59 | wordFreqs.getFrequency("c") shouldBe 1 60 | wordFreqs.getFrequency("å") shouldBe 1 61 | wordFreqs.getFrequency("åå") shouldBe 1 62 | } 63 | 64 | "isWonTicTacToe" should "figure out if a board is won or not already" in { 65 | // not super proud of these tests, but they will do for now. 66 | var board = TicTacToe.newBoard 67 | isWonTicTacToe(board) shouldBe false 68 | board(0)(0) = TicTacToe.X 69 | isWonTicTacToe(board) shouldBe false 70 | board(0)(1) = TicTacToe.O 71 | isWonTicTacToe(board) shouldBe false 72 | board(0)(2) = TicTacToe.X 73 | isWonTicTacToe(board) shouldBe false 74 | board(0)(1) = TicTacToe.X 75 | isWonTicTacToe(board) shouldBe true 76 | 77 | board = TicTacToe.newBoard 78 | isWonTicTacToe(board) shouldBe false 79 | board(0)(0) = TicTacToe.X 80 | board(1)(1) = TicTacToe.X 81 | board(2)(2) = TicTacToe.X 82 | isWonTicTacToe(board) shouldBe true 83 | 84 | board = TicTacToe.newBoard 85 | isWonTicTacToe(board) shouldBe false 86 | board(0)(2) = TicTacToe.X 87 | board(1)(2) = TicTacToe.X 88 | board(2)(2) = TicTacToe.X 89 | isWonTicTacToe(board) shouldBe true 90 | 91 | board = TicTacToe.newBoard 92 | isWonTicTacToe(board) shouldBe false 93 | board(0)(0) = TicTacToe.X 94 | board(1)(1) = TicTacToe.X 95 | board(2)(2) = TicTacToe.X 96 | isWonTicTacToe(board) shouldBe true 97 | 98 | board = TicTacToe.newBoard 99 | isWonTicTacToe(board) shouldBe false 100 | board(0)(2) = TicTacToe.X 101 | board(1)(1) = TicTacToe.X 102 | board(2)(0) = TicTacToe.X 103 | isWonTicTacToe(board) shouldBe true 104 | } 105 | 106 | "factorialZeroes" should "get the number of trailing 0s of n!" in { 107 | for (i <- 0 to 4) { 108 | factorialZeroes(i) shouldBe 0 109 | } 110 | for (i <- 5 to 9) { 111 | factorialZeroes(i) shouldBe 1 112 | } 113 | for (i <- 10 to 14) { 114 | factorialZeroes(i) shouldBe 2 115 | } 116 | factorialZeroes(25) shouldBe 6 117 | factorialZeroes(50) shouldBe 12 118 | } 119 | 120 | "smallestDifference" should "find the smallest difference between any two numbers in the arrays" in { 121 | smallestDifference( 122 | Array(1, 3, 15, 11, 2), 123 | Array(23, 127, 235, 19, 8) 124 | ) shouldBe 3 125 | smallestDifference( 126 | Array(1, 3, 15, 2), 127 | Array(23, 127, 235, 19, 8) 128 | ) shouldBe 4 129 | smallestDifference(Array(1, 3, 15, 2), Array(23, 127, 235, 3, 8)) shouldBe 0 130 | smallestDifference(Array(1), Array(23, 127, 235, 312, 8)) shouldBe 7 131 | } 132 | 133 | def testNumberMax(a: Int, b: Int): Unit = { 134 | numberMax(a, b) shouldBe (a max b) 135 | numberMax(b, a) shouldBe (a max b) 136 | } 137 | 138 | "numberMax" should "find the max between two numbers" in { 139 | testNumberMax(0, 0) 140 | testNumberMax(1, 0) 141 | testNumberMax(-1, 0) 142 | testNumberMax(-1, -2) 143 | testNumberMax(123, 321) 144 | testNumberMax(Int.MaxValue - 1, Int.MaxValue) 145 | testNumberMax(Int.MinValue + 1, Int.MaxValue) 146 | } 147 | 148 | "englishInt" should "get a word representation of an integer" in { 149 | val maker = new EnglishIntMaker() 150 | maker.englishInt(0) shouldBe "Zero" 151 | maker.englishInt(1000) shouldBe "One Thousand" 152 | maker.englishInt(100) shouldBe "One Hundred" 153 | maker.englishInt(101) shouldBe "One Hundred One" 154 | maker.englishInt(1234) shouldBe "One Thousand, Two Hundred Thirty Four" 155 | maker.englishInt( 156 | -1234 157 | ) shouldBe "Negative One Thousand, Two Hundred Thirty Four" 158 | maker.englishInt( 159 | 9341234 160 | ) shouldBe "Nine Million, Three Hundred Forty One Thousand, Two Hundred Thirty Four" 161 | maker.englishInt( 162 | Int.MaxValue 163 | ) shouldBe "Two Billion, One Hundred Forty Seven Million, Four Hundred Eighty Three Thousand, Six Hundred Forty Seven" 164 | maker.englishInt( 165 | Int.MinValue 166 | ) shouldBe "Negative Two Billion, One Hundred Forty Seven Million, Four Hundred Eighty Three Thousand, Six Hundred Forty Eight" 167 | maker.englishInt( 168 | Int.MinValue + 1 169 | ) shouldBe "Negative Two Billion, One Hundred Forty Seven Million, Four Hundred Eighty Three Thousand, Six Hundred Forty Seven" 170 | } 171 | 172 | def testOperations(a: Int, b: Int): Unit = { 173 | testOperationsHelper(a, b) 174 | testOperationsHelper(b, a) 175 | } 176 | 177 | def testOperationsHelper(a: Int, b: Int): Unit = { 178 | subtract(a, b) shouldBe (a - b) 179 | if (a.abs < 10000 && b.abs < 10000) { 180 | multiply(a, b) shouldBe (a * b) 181 | } 182 | if (b != 0 && a.abs < 10000) { 183 | divide(a, b) shouldBe (a / b) 184 | } 185 | } 186 | 187 | "operations" should "do subtraction, multiplication and division properly" in { 188 | testOperations(0, 0) 189 | testOperations(1, 0) 190 | testOperations(-1, 1) 191 | testOperations(-1, 0) 192 | testOperations(Integer.MAX_VALUE, 2) 193 | testOperations(Integer.MIN_VALUE + 1, 2) 194 | testOperations(123, 32) 195 | testOperations(123, -32) 196 | testOperations(Integer.MAX_VALUE, Integer.MAX_VALUE) 197 | testOperations(Integer.MIN_VALUE, Integer.MIN_VALUE) 198 | for (_ <- 1 to 100) { 199 | testOperations(Random.nextInt(), Random.nextInt()) 200 | } 201 | } 202 | 203 | def testLivingPeople(people: Set[Person]): Unit = { 204 | livingPeople(people.asJava) shouldBe livingPeopleBruteForce(people.asJava) 205 | } 206 | 207 | "livingPeople" should "return the year where the most people were living" in { 208 | livingPeople( 209 | Set(new Person(1900, 2000), new Person(1910, 1910)).asJava 210 | ) shouldBe 1910 211 | 212 | val people = for (_: Int <- (1 to 10000).toSet) yield { 213 | val birth = 1900 + Random.nextInt(100) 214 | new Person(birth, birth + Random.nextInt(100)) 215 | } 216 | testLivingPeople(people) 217 | } 218 | 219 | "countDivingBoardsOfKPieces" should "return the number of ways to build a diving board of with k boards" in { 220 | countDivingBoardsOfKPieces(5, 10, 4).asScala.toSet shouldBe Set( 221 | 20, 222 | 25, 223 | 30, 224 | 35, 225 | 40 226 | ) 227 | countDivingBoardsOfKPieces(3, 7, 4).asScala.toSet shouldBe Set( 228 | 12, 229 | 16, 230 | 20, 231 | 24, 232 | 28 233 | ) 234 | countDivingBoardsOfKPieces(10, 10, 4).asScala.toSet shouldBe Set(40) 235 | } 236 | 237 | "countDivingBoardsOfSize" should "return the number of ways to build a diving board of size k" in { 238 | countDivingBoardsOfSize(5, 10, 200) shouldBe Chapter7Solutions.coinsCount( 239 | Set(5, 10).map(JInt.valueOf).asJava, 240 | 200 241 | ) 242 | } 243 | 244 | "bestLine" should "find a line that goes through the most points" in { 245 | bestLine(Set(new Point(1, 1), new Point(2, 2)).asJava) shouldBe new Line( 246 | new Point(1, 1), 247 | new Point(2, 2) 248 | ) 249 | bestLine(Set(new Point(1, 1), new Point(3, 3)).asJava) shouldBe new Line( 250 | new Point(1, 1), 251 | new Point(2, 2) 252 | ) 253 | bestLine(Set(new Point(0, 1), new Point(0, 3)).asJava) shouldBe new Line( 254 | new Point(0, 3), 255 | new Point(0, 3) 256 | ) 257 | bestLine( 258 | Set( 259 | new Point(1, 1), 260 | new Point(2, 2), 261 | new Point(3, 3), 262 | new Point(0, 1), 263 | new Point(0, 3) 264 | ).asJava 265 | ) shouldBe new Line(new Point(0, 0), new Point(1, 1)) 266 | } 267 | 268 | "masterMindScore" should "compute the game score" in { 269 | masterMindScore("RGBY", "GGRR") shouldBe new MasterMindResult(1, 1) 270 | masterMindScore2("RGBY", "GGRR") shouldBe new MasterMindResult(1, 1) 271 | } 272 | 273 | "subSortIndexes" should "give the indexes of the minimum subarray to get a sorted array" in { 274 | subSortIndexes( 275 | Array(1, 2, 4, 7, 10, 11, 7, 12, 6, 7, 16, 18, 19) 276 | ) shouldBe (3, 9) 277 | subSortIndexes( 278 | Array(1, 2, 4, 7, 10, 11, 7, 12, 7, 7, 16, 18, 19) 279 | ) shouldBe (4, 9) 280 | subSortIndexes( 281 | Array(1, 2, 4, 7, 10, 11, 7, 12, 7, 7, 16, 18, 5) 282 | ) shouldBe (3, 12) 283 | subSortIndexes( 284 | Array(3, 2, 4, 7, 10, 11, 7, 12, 6, 7, 16, 18, 19) 285 | ) shouldBe (0, 9) 286 | } 287 | 288 | def testMaxContiguousSequenceSum(array: Array[Int], expected: Int): Unit = { 289 | maxContiguousSequenceSum(array) shouldBe expected 290 | maxContiguousSequenceSum2(array) shouldBe expected 291 | } 292 | 293 | "maxContiguousSequenceSum" should "get the maximum sum of a contiguous subarray" in { 294 | testMaxContiguousSequenceSum(Array(2, -8, 3, -2, 4, -10), 5) 295 | testMaxContiguousSequenceSum(Array(-10), -10) 296 | testMaxContiguousSequenceSum(Array(-10, -2), -2) 297 | testMaxContiguousSequenceSum(Array(-2, -10), -2) 298 | } 299 | 300 | "doesPatternMatch" should "determine if the pattern matches the value" in { 301 | doesPatternMatch("", "catcatgocatgo") shouldBe false 302 | doesPatternMatch("a", "catcatgocatgo") shouldBe true 303 | doesPatternMatch("b", "catcatgocatgo") shouldBe true 304 | doesPatternMatch("ab", "catcatgocatgo") shouldBe true 305 | doesPatternMatch("aabab", "catcatgocatgo") shouldBe true 306 | doesPatternMatch("aabac", "catcatgocatgo") shouldBe false 307 | } 308 | 309 | "findPondSizes" should "find the sizes of the various ponds in the topography" in { 310 | findPondSizes(Array(Array(1))).asScala.toSet shouldBe Set() 311 | findPondSizes(Array(Array(0))).asScala.toSet shouldBe Set(1) 312 | findPondSizes(Array(Array(0, 0), Array(0, 0))).asScala.toSet shouldBe Set(4) 313 | 314 | findPondSizes( 315 | Array( 316 | Array(0, 2, 1, 0), 317 | Array(0, 1, 0, 1), 318 | Array(1, 1, 0, 1), 319 | Array(0, 1, 0, 1) 320 | ) 321 | ).asScala.toSet shouldBe Set(1, 2, 4) 322 | } 323 | 324 | def testSumSwap(a: Array[Int], b: Array[Int]): Unit = { 325 | val result = sumSwap(a, b) 326 | val swappedA = -result(0) :: result(1) :: a.toList 327 | val swappedB = result(0) :: -result(1) :: b.toList 328 | 329 | swappedA.sum shouldBe swappedB.sum 330 | } 331 | 332 | "sumSwap" should "try to find integers to swap to get arrays of the same sum" in { 333 | testSumSwap(Array(4, 1, 2, 1, 1, 2), Array(3, 6, 3, 3)) 334 | } 335 | 336 | def printAntWalk(grid: AntGridResult): Unit = { 337 | println( 338 | s"ant = (${grid.ant._1}, ${grid.ant._2}), direction = ${grid.direction}" 339 | ) 340 | for { 341 | col <- grid.isBlack.indices 342 | row <- grid.isBlack(0).indices 343 | } { 344 | if (grid.ant._1 == col && grid.ant._2 == row) { 345 | if (grid.isBlack(col)(row)) { 346 | print('X') 347 | } else { 348 | print('O') 349 | } 350 | } else if (grid.isBlack(col)(row)) { 351 | print('x') 352 | } else { 353 | print('o') 354 | } 355 | 356 | if (row == grid.isBlack(0).length - 1) { 357 | println() 358 | } 359 | } 360 | } 361 | 362 | "antWalk" should "walk the ant according to it's rules and return the result" in { 363 | // Useful to manually validate, but too verbose otherwise. 364 | // val grid = new AntGrid() 365 | // for (i <- 0 to 20) { 366 | // println(s"------ k = ${i} ${"-"*25}") 367 | // printAntWalk(grid.getResult) 368 | // grid.moveAnt() 369 | // } 370 | 371 | val expectedArray = Array( 372 | Array(false, false, true), 373 | Array(true, true, true), 374 | Array(true, true, false) 375 | ) 376 | antWalk(10) shouldBe new AntGridResult( 377 | (0, 0), 378 | expectedArray, 379 | Direction.Left 380 | ) 381 | } 382 | 383 | "rand7" should "return random numbers 0 until 7" in { 384 | val values = for { 385 | _ <- 0 until 1000 386 | } yield { 387 | rand7() 388 | } 389 | // println(values.groupBy { a => a }.map{case(a, b) => (a, b.size)}.toList.sortBy{case (a, b) => a}) 390 | 391 | values.toSet shouldBe (0 until 7).toSet 392 | } 393 | 394 | "findPairsWithSum" should "find all the pairs with the sum in the array" in { 395 | findPairsWithSum( 396 | Array(2, -3, 5, -7, 8, -1, 0, 1), 397 | 1 398 | ).asScala.toMap shouldBe Map(-1 -> 2, -7 -> 8, 0 -> 1) 399 | } 400 | 401 | "LRUCache" should "work like a LRU cache with a max size" in { 402 | val cache = new LRUCache[Integer, Integer](5) 403 | // test basic fill 404 | for (i <- 1 to 5) { 405 | cache.put(i, i) shouldBe null 406 | cache.containsKey(i) shouldBe true 407 | cache.get(i) shouldBe i 408 | } 409 | 410 | // test evict all 411 | for (i <- 6 to 10) { 412 | cache.put(i, i) shouldBe null 413 | cache.containsKey(i) shouldBe true 414 | cache.get(i) shouldBe i 415 | } 416 | for (i <- 1 to 5) { 417 | cache.containsKey(i) shouldBe false 418 | } 419 | 420 | // test update beginning 421 | cache.put(6, 8) 422 | cache.get(6) shouldBe 8 423 | for (i <- 7 to 10) { 424 | cache.containsKey(i) shouldBe true 425 | cache.get(i) shouldBe i 426 | } 427 | cache.get(6) shouldBe 8 428 | cache.put(6, 6) 429 | 430 | // test update end 431 | cache.put(10, 11) 432 | cache.get(10) shouldBe 11 433 | for (i <- 6 to 9) { 434 | cache.containsKey(i) shouldBe true 435 | cache.get(i) shouldBe i 436 | } 437 | cache.get(10) shouldBe 11 438 | } 439 | 440 | def testCalculate(expression: String, result: Double): Unit = { 441 | calculate(expression) shouldBe result 442 | calculate2(expression) shouldBe result 443 | } 444 | 445 | "calculate" should "compute the value of an expression" in { 446 | testCalculate("2", 2) 447 | testCalculate("2*3", 6) 448 | testCalculate("3/2", 1.5) 449 | testCalculate("3+2", 5) 450 | testCalculate("2-3", -1) 451 | testCalculate("2*3+5/6*3+15", 23.5) 452 | testCalculate("1/0", Double.PositiveInfinity) 453 | testCalculate("2*2-5/0", Double.NegativeInfinity) 454 | testCalculate("2*2-5/0+20", Double.NegativeInfinity) 455 | } 456 | } 457 | -------------------------------------------------------------------------------- /src/test/scala/com/github/myyk/cracking/Chapter4SolutionsTest.scala: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking 2 | 3 | import com.github.myyk.cracking.Chapter4Solutions._ 4 | import com.google.common.collect.Lists 5 | 6 | import java.util 7 | import scala.annotation.tailrec 8 | import scala.jdk.CollectionConverters._ 9 | import scala.util.Random 10 | 11 | import org.scalatest._ 12 | import flatspec._ 13 | import matchers._ 14 | 15 | class Chapter4SolutionsTest extends AnyFlatSpec with should.Matchers { 16 | def createMediumDirectionalTestGraph: (IntGraph, Seq[IntNode]) = { 17 | val graph = new IntGraph() 18 | 19 | val nodes = for (i <- 0 to 7) yield { 20 | val node = new IntNode(i) 21 | graph.addNode(node) 22 | node 23 | } 24 | 25 | nodes(0) addAdjacent nodes(1) 26 | nodes(0) addAdjacent nodes(2) 27 | nodes(1) addAdjacent nodes(3) 28 | nodes(1) addAdjacent nodes(4) 29 | nodes(2) addAdjacent nodes(5) 30 | nodes(3) addAdjacent nodes(4) 31 | nodes(4) addAdjacent nodes(1) 32 | nodes(4) addAdjacent nodes(6) 33 | nodes(5) addAdjacent nodes(6) 34 | nodes(6) addAdjacent nodes(4) 35 | 36 | (graph, nodes) 37 | } 38 | 39 | def createMediumBidirectionalTestGraph: (IntGraph, Seq[IntNode]) = { 40 | val graph = new IntGraph() 41 | 42 | val nodes = for (i <- 0 to 7) yield { 43 | val node = new IntNode(i) 44 | graph.addNode(node) 45 | node 46 | } 47 | def addEdge(a: Int, b: Int): Unit = { 48 | nodes(a) addAdjacent nodes(b) 49 | nodes(b) addAdjacent nodes(a) 50 | } 51 | addEdge(0, 1) 52 | addEdge(0, 2) 53 | addEdge(1, 3) 54 | addEdge(1, 4) 55 | addEdge(2, 5) 56 | addEdge(3, 4) 57 | addEdge(4, 6) 58 | addEdge(4, 6) 59 | 60 | (graph, nodes) 61 | } 62 | 63 | "pathExists" should "find if there is a path between a and b" in { 64 | val (_, nodes) = createMediumDirectionalTestGraph 65 | Chapter4Solutions.pathExistsDirectional( 66 | nodes.head, 67 | nodes.head 68 | ) shouldBe true 69 | Chapter4Solutions.pathExistsDirectional(nodes.head, nodes(1)) shouldBe true 70 | Chapter4Solutions.pathExistsDirectional(nodes(1), nodes.head) shouldBe false 71 | Chapter4Solutions.pathExistsDirectional(nodes.head, nodes(6)) shouldBe true 72 | Chapter4Solutions.pathExistsDirectional(nodes(6), nodes.head) shouldBe false 73 | Chapter4Solutions.pathExistsDirectional(nodes.head, nodes(7)) shouldBe false 74 | Chapter4Solutions.pathExistsDirectional(nodes(6), nodes(1)) shouldBe true 75 | Chapter4Solutions.pathExistsDirectional(nodes(1), nodes(6)) shouldBe true 76 | } 77 | 78 | "pathExistsBidirectional" should "find if there is a path between a and b" in { 79 | val (_, nodes) = createMediumBidirectionalTestGraph 80 | Chapter4Solutions.pathExistsBidirectional( 81 | nodes.head, 82 | nodes.head 83 | ) shouldBe true 84 | Chapter4Solutions.pathExistsBidirectional( 85 | nodes.head, 86 | nodes(1) 87 | ) shouldBe true 88 | Chapter4Solutions.pathExistsBidirectional( 89 | nodes(1), 90 | nodes.head 91 | ) shouldBe true 92 | Chapter4Solutions.pathExistsBidirectional( 93 | nodes.head, 94 | nodes(6) 95 | ) shouldBe true 96 | Chapter4Solutions.pathExistsBidirectional( 97 | nodes(6), 98 | nodes.head 99 | ) shouldBe true 100 | Chapter4Solutions.pathExistsBidirectional(nodes(6), nodes(1)) shouldBe true 101 | Chapter4Solutions.pathExistsBidirectional(nodes(1), nodes(6)) shouldBe true 102 | Chapter4Solutions.pathExistsBidirectional( 103 | nodes.head, 104 | nodes(7) 105 | ) shouldBe false 106 | Chapter4Solutions.pathExistsBidirectional( 107 | nodes(7), 108 | nodes.head 109 | ) shouldBe false 110 | } 111 | 112 | def getHeight(tree: Tree[_]): Int = { 113 | if (tree == null) { 114 | 0 115 | } else { 116 | 1 + Math.max(getHeight(tree.getLeft), getHeight(tree.getRight)) 117 | } 118 | } 119 | 120 | @tailrec 121 | private def binarySearch(value: Int, tree: Tree[Integer]): Boolean = { 122 | if (tree == null) { 123 | false 124 | } else if (value == tree.getData) { 125 | true 126 | } else if (value < tree.getData) { 127 | binarySearch(value, tree.getLeft) 128 | } else { 129 | binarySearch(value, tree.getRight) 130 | } 131 | } 132 | 133 | def log2(num: Int): Int = { 134 | if (num == 0) { 135 | 0 136 | } else { 137 | 31 - Integer.numberOfLeadingZeros(num) 138 | } 139 | } 140 | 141 | def testMinBinaryTree(array: Array[Int]): Unit = { 142 | val bst = Chapter4Solutions.minBinaryTree(array) 143 | if (array.isEmpty) { 144 | getHeight(bst) shouldBe 0 145 | } else { 146 | getHeight(bst) shouldBe (log2(array.length) + 1) 147 | } 148 | for (i <- array) { 149 | binarySearch(i, bst) shouldBe true 150 | } 151 | } 152 | 153 | "minBinaryTree" should "produce a minimal height BST" in { 154 | for (i <- 0 to 128) { 155 | val numbers = for (_ <- 0 until i) yield { 156 | Random.nextInt() 157 | } 158 | testMinBinaryTree(numbers.sorted.toArray) 159 | } 160 | } 161 | 162 | def javaListsOfListsToScala[T]( 163 | lists: java.util.List[java.util.List[T]] 164 | ): List[List[T]] = { 165 | lists.asScala.map(_.asScala.toList).toList 166 | } 167 | 168 | def listsOfDepths[T](tree: Tree[T]): List[List[T]] = { 169 | javaListsOfListsToScala(Chapter4Solutions.listsOfDepths(tree)) 170 | } 171 | 172 | "listsOfDepths" should "give lists of the elements at each depth" in { 173 | /* 174 | * 1 175 | * 2 4 176 | * 3 * * 5 177 | * * * * * 178 | */ 179 | val tree = new Tree( 180 | 1, 181 | new Tree(2, new Tree(3), null), 182 | new Tree(4, null, new Tree(5)) 183 | ) 184 | listsOfDepths(tree) shouldBe List( 185 | List(1), 186 | List(2, 4), 187 | List(3, 5) 188 | ) 189 | listsOfDepths(null) shouldBe List() 190 | listsOfDepths(new Tree(1)) shouldBe List(List(1)) 191 | } 192 | 193 | "isBalanced" should "check if a tree is balanced" in { 194 | Chapter4Solutions.isBalanced(null) shouldBe true 195 | Chapter4Solutions.isBalanced(new Tree(1)) shouldBe true 196 | Chapter4Solutions.isBalanced( 197 | new Tree(1, new Tree(2), new Tree(3)) 198 | ) shouldBe true 199 | Chapter4Solutions.isBalanced( 200 | new Tree(1, new Tree(2, new Tree(4), null), null) 201 | ) shouldBe true 202 | Chapter4Solutions.isBalanced( 203 | new Tree(1, new Tree(2, null, new Tree(4)), null) 204 | ) shouldBe true 205 | Chapter4Solutions.isBalanced( 206 | new Tree(1, null, new Tree(2, null, new Tree(4))) 207 | ) shouldBe true 208 | Chapter4Solutions.isBalanced( 209 | new Tree(1, null, new Tree(2, new Tree(4), null)) 210 | ) shouldBe true 211 | } 212 | 213 | "isValidBST" should "check if a tree is balanced" in { 214 | // null, single, and simple 215 | Chapter4Solutions.isValidBST(null) shouldBe true 216 | Chapter4Solutions.isValidBST(new Tree[Integer](1)) shouldBe true 217 | Chapter4Solutions.isValidBST( 218 | new Tree[Integer](1, new Tree(2), null) 219 | ) shouldBe false 220 | Chapter4Solutions.isValidBST( 221 | new Tree[Integer](1, new Tree(0), null) 222 | ) shouldBe true 223 | Chapter4Solutions.isValidBST( 224 | new Tree[Integer](1, null, new Tree(2)) 225 | ) shouldBe true 226 | Chapter4Solutions.isValidBST( 227 | new Tree[Integer](1, null, new Tree(0)) 228 | ) shouldBe false 229 | Chapter4Solutions.isValidBST( 230 | new Tree[Integer](1, new Tree(0), new Tree(2)) 231 | ) shouldBe true 232 | 233 | // duplicates 234 | Chapter4Solutions.isValidBST( 235 | new Tree[Integer](1, new Tree(1), null) 236 | ) shouldBe true 237 | Chapter4Solutions.isValidBST( 238 | new Tree[Integer](1, null, new Tree(1)) 239 | ) shouldBe false 240 | Chapter4Solutions.isValidBST( 241 | new Tree[Integer](1, new Tree(1), new Tree(1)) 242 | ) shouldBe false 243 | 244 | Chapter4Solutions.isValidBST( 245 | Chapter4Solutions.minBinaryTree( 246 | (0 until 1000).map(_ => Random.nextInt()).sorted.toArray 247 | ) 248 | ) shouldBe true 249 | Chapter4Solutions.isValidBST( 250 | Chapter4Solutions.minBinaryTree( 251 | (0 until 1000).map(_ => Random.nextInt()).toArray 252 | ) 253 | ) shouldBe false 254 | 255 | // right grandchild greater than node 256 | Chapter4Solutions.isValidBST( 257 | new Tree[Integer]( 258 | 20, 259 | new Tree[Integer](10, null, new Tree(25)), 260 | new Tree(30) 261 | ) 262 | ) shouldBe false 263 | } 264 | 265 | "findSuccessor" should "find the in-order successor to the node" in { 266 | Chapter4Solutions.findSuccessor( 267 | null: BinarySearchTree[Integer] 268 | ) shouldBe null 269 | Chapter4Solutions.findSuccessor( 270 | new BinarySearchTree[Integer](1) 271 | ) shouldBe null 272 | Chapter4Solutions.findSuccessor( 273 | new BinarySearchTree[Integer](2).setLeft(1) 274 | ) shouldBe null 275 | val bst1 = new BinarySearchTree[Integer](2).setRight(3) 276 | bst1.getRight.setRight(4) 277 | Chapter4Solutions.findSuccessor(bst1) shouldBe bst1.getRight 278 | Chapter4Solutions.findSuccessor( 279 | bst1.getRight 280 | ) shouldBe bst1.getRight.getRight 281 | Chapter4Solutions.findSuccessor(bst1.getRight.getRight) shouldBe null 282 | val bst2 = new BinarySearchTree[Integer](20).setLeft(10).setRight(30) 283 | bst2.getLeft.setRight(15) 284 | Chapter4Solutions.findSuccessor(bst2.getLeft) shouldBe bst2.getLeft.getRight 285 | Chapter4Solutions.findSuccessor(bst2.getLeft.getRight) shouldBe bst2 286 | } 287 | 288 | "findBuildOrder" should "find a build order for the provided projects" in { 289 | val projects = new Graph[Char]() 290 | val a = new Node('a') 291 | val b = new Node('b') 292 | val c = new Node('c') 293 | val d = new Node('d') 294 | val e = new Node('e') 295 | val f = new Node('f') 296 | a addAdjacent d 297 | f addAdjacent b 298 | b addAdjacent d 299 | f addAdjacent a 300 | d addAdjacent c 301 | projects.addNode(a) 302 | projects.addNode(b) 303 | projects.addNode(c) 304 | projects.addNode(d) 305 | projects.addNode(e) 306 | projects.addNode(f) 307 | 308 | Chapter4Solutions.findBuildOrder(projects).size shouldBe 6 309 | // This can be multiple different correct answers. Would need a checkBuildOrder function to test this. 310 | // Chapter4Solutions.findBuildOrder(projects).map(_.getData) shouldBe List(e, f, b, a, d, c).map(_.getData) 311 | } 312 | 313 | "findBuildOrder" should "not find a build order for the provided projects" in { 314 | val projects = new Graph[Char]() 315 | val a = new Node('a') 316 | a addAdjacent a 317 | projects.addNode(a) 318 | 319 | intercept[IllegalArgumentException] { 320 | Chapter4Solutions.findBuildOrder(projects) 321 | } 322 | 323 | val b = new Node('b') 324 | projects.addNode(b) 325 | 326 | intercept[IllegalArgumentException] { 327 | Chapter4Solutions.findBuildOrder(projects) 328 | } 329 | } 330 | 331 | "findFirstCommonAncestor" should "find the first common ancestor" in { 332 | /* 333 | * 1 334 | * 2 3 335 | * 4 6 7 336 | */ 337 | val tree = new Tree( 338 | 1, 339 | new Tree(2, new Tree(4), null), 340 | new Tree(3, new Tree(6), new Tree(7)) 341 | ) 342 | Chapter4Solutions.findFirstCommonAncestor(tree, tree, tree) shouldBe tree 343 | Chapter4Solutions.findFirstCommonAncestor( 344 | tree, 345 | tree, 346 | new Tree(-999) 347 | ) shouldBe null 348 | Chapter4Solutions.findFirstCommonAncestor( 349 | tree, 350 | new Tree(-999), 351 | tree 352 | ) shouldBe null 353 | Chapter4Solutions.findFirstCommonAncestor( 354 | tree, 355 | tree.getRight.getRight, 356 | tree 357 | ) shouldBe tree 358 | Chapter4Solutions.findFirstCommonAncestor( 359 | tree, 360 | tree, 361 | tree.getRight.getRight 362 | ) shouldBe tree 363 | Chapter4Solutions.findFirstCommonAncestor( 364 | tree, 365 | tree.getLeft, 366 | tree 367 | ) shouldBe tree 368 | Chapter4Solutions.findFirstCommonAncestor( 369 | tree, 370 | tree, 371 | tree.getLeft 372 | ) shouldBe tree 373 | Chapter4Solutions.findFirstCommonAncestor( 374 | tree, 375 | tree.getRight.getRight, 376 | tree.getRight.getLeft 377 | ) shouldBe tree.getRight 378 | Chapter4Solutions.findFirstCommonAncestor( 379 | tree, 380 | tree.getLeft.getLeft, 381 | tree.getRight.getRight 382 | ) shouldBe tree 383 | } 384 | 385 | "bstSequences" should "give all the sequences that could have created it" in { 386 | Chapter4Solutions.bstSequences[Int](null) shouldBe null 387 | Chapter4Solutions 388 | .bstSequences(new Tree(1, new Tree(2), new Tree(3))) 389 | .asScala 390 | .map(_.asScala.toList) 391 | .toSet shouldBe Set(List(1, 2, 3), List(1, 3, 2)) 392 | } 393 | 394 | "weaveSequences" should "weave the two sequences together" in { 395 | val list1 = Lists.newLinkedList[Int] 396 | list1.add(1) 397 | val list2 = Lists.newLinkedList[Int] 398 | list2.add(2) 399 | val prefix = Lists.newLinkedList[Int] 400 | prefix.add(3) 401 | Chapter4Solutions 402 | .weaveSequences( 403 | list1, 404 | list2, 405 | Lists.newLinkedList[util.LinkedList[Int]](), 406 | prefix 407 | ) 408 | .asScala 409 | .map(_.asScala.toList) 410 | .toSet shouldBe Set(List(3, 1, 2), List(3, 2, 1)) 411 | } 412 | 413 | def testIsSubtree(isSubtreeOp: (Tree[Int], Tree[Int]) => Boolean): Unit = { 414 | val t1 = new Tree( 415 | 1, 416 | new Tree(2, new Tree(4), null), 417 | new Tree(3, new Tree(6), new Tree(7)) 418 | ) 419 | isSubtreeOp(null, t1) shouldBe false 420 | isSubtreeOp(t1, null) shouldBe true 421 | isSubtreeOp(t1, t1.getRight) shouldBe true 422 | isSubtreeOp(t1, t1.getLeft) shouldBe true 423 | isSubtreeOp(t1, t1) shouldBe true 424 | isSubtreeOp(t1, t1.getLeft.getLeft) shouldBe true 425 | isSubtreeOp(t1, t1.getRight.getLeft) shouldBe true 426 | isSubtreeOp(t1, t1.getRight.getRight) shouldBe true 427 | } 428 | 429 | "isSubtree" should "check if t2 is a subtree of t1" in { 430 | testIsSubtree(Chapter4Solutions.isSubtree) 431 | } 432 | 433 | "isSubtree2" should "check if t2 is a subtree of t1" in { 434 | testIsSubtree(Chapter4Solutions.isSubtree2) 435 | } 436 | 437 | "RandomTree" should "be able to return a random number" in { 438 | val tree = new RandomTree( 439 | 1, 440 | new RandomTree(2), 441 | new RandomTree(3, new RandomTree(4), new RandomTree(5)) 442 | ) 443 | val results = for (_ <- 0 until 10000) yield { 444 | tree.getRandomNode.getData 445 | } 446 | val occurrences = results 447 | .groupBy(a => a) 448 | .map { case (i, occurrences) => (i, occurrences.size) } 449 | .toList 450 | .sortBy { case (a, _) => a } 451 | .map { case (_, a) => a } 452 | // this should pass most of the time 453 | // max freq shouldn't be more than 20% of the samples more than the min 454 | (occurrences.max - occurrences.min) should be < occurrences.sum / occurrences.size / 5 455 | } 456 | 457 | "countPathsWithSum" should "count the number of paths with the provided sum going downwards in the tree" in { 458 | /* 459 | * 10 460 | * / \ 461 | * 5 -3 462 | * / \ \ 463 | * 3 2 11 464 | * / \ \ 465 | * 3 -2 1 466 | */ 467 | val left = new Tree[Integer]( 468 | 5, 469 | new Tree[Integer](3, new Tree(3), new Tree(-2)), 470 | new Tree[Integer](2, null, new Tree(1)) 471 | ) 472 | val right = new Tree[Integer](-3, null, new Tree(11)) 473 | val tree = new Tree[Integer](10, left, right) 474 | Chapter4Solutions.countPathsWithSum(tree, 10000) shouldBe 0 475 | Chapter4Solutions.countPathsWithSum(tree, 10) shouldBe 1 476 | Chapter4Solutions.countPathsWithSum(tree, 1) shouldBe 2 477 | Chapter4Solutions.countPathsWithSum(tree, 18) shouldBe 3 478 | Chapter4Solutions.countPathsWithSum(tree, 6) shouldBe 2 479 | } 480 | } 481 | -------------------------------------------------------------------------------- /src/main/java/com/github/myyk/cracking/Chapter17Solutions.java: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking; 2 | 3 | import java.util.Arrays; 4 | import java.util.Iterator; 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.PriorityQueue; 9 | import java.util.Random; 10 | import java.util.Set; 11 | 12 | import com.google.common.collect.Lists; 13 | import com.google.common.collect.Maps; 14 | import com.google.common.collect.Sets; 15 | import scala.Tuple2; 16 | 17 | /** 18 | * Hard 19 | */ 20 | public class Chapter17Solutions { 21 | 22 | /** 23 | * Add Without Plus: Do addition without the '+' operator. 24 | * 25 | * Assumptions: 26 | * 27 | * Time complexity: O(b) where b is the number of bits in the numbers 28 | * Space complexity: O(1) 29 | * 30 | * Note: this is ugly and can be improved 31 | */ 32 | public static int addWithoutPlus(int a, int b) { 33 | int sum = 0; 34 | boolean carry = false; 35 | for (int i = 0; i < 32; i++) { 36 | int mask = 1< void shuffle(T[] cards) { 69 | for (int i = cards.length; i > 0; i--) { 70 | int swapIndex = random.nextInt(i); 71 | T temp = cards[i - 1]; 72 | cards[i - 1] = cards[swapIndex]; 73 | cards[swapIndex] = temp; 74 | } 75 | } 76 | 77 | /** 78 | * Random Set: Given an array of integers, generate a random set of size m with equal probability of 79 | * having each value from the array in the resulting set. 80 | * 81 | * Assumptions: 82 | * no duplicates in the array 83 | * can destroy the array 84 | * 85 | * Time complexity: O(m) where m is the size of the set. 86 | * Space complexity: O(m) 87 | * 88 | * Difference with book: They didn't stick with returning a 'set' so didn't need to worry about uniqueness. 89 | * Though, my assumptions dealt with that. Still, their solution is O(v) where v is the number of values 90 | * in the input array, while mine is simply O(m) which is better especially if m is much smaller than 91 | * v. 92 | */ 93 | public static Set randomSet(final int[] values, final int m) { 94 | if (values.length < m) { 95 | throw new IllegalArgumentException("Can't create set of size m from values."); 96 | } 97 | 98 | final Set result = Sets.newHashSet(); 99 | 100 | int i = values.length; 101 | for (int selected = 0; selected < m; selected++) { 102 | final int swapIndex = random.nextInt(i); 103 | result.add(values[swapIndex]); 104 | values[swapIndex] = values[i - 1]; 105 | i--; 106 | } 107 | return result; 108 | } 109 | 110 | /** 111 | * Missing Number: Given an array of integers with values 0 to N and one missing integer find the 112 | * missing integer. The array is non-standard, you cannot access a full integer in a single operation 113 | * you can only access a bit at a time in constant time. 114 | * 115 | * Assumptions: 116 | * 117 | * Time complexity: O(n) 118 | * Space complexity: O(1) 119 | * 120 | * Difference with book: If you can't read more than a bit, doesn't make much sense you can move the 121 | * full integer at once. They use references to integers, where I'm not. 122 | */ 123 | public static int missingNumber(final int[] array) { 124 | List indices = Lists.newArrayList(); 125 | for (int i = 0; i < array.length; i++) { 126 | indices.add(i); 127 | } 128 | return missingNumber(indices, array, 0); 129 | } 130 | 131 | private static int missingNumber(final List indices, final int[] array, int position) { 132 | if (indices.isEmpty()) { 133 | return 0; 134 | } 135 | 136 | final List evens = Lists.newArrayListWithCapacity(indices.size()/2 + 1); 137 | final List odds = Lists.newArrayListWithCapacity(indices.size()/2 + 1); 138 | for (Integer i : indices) { 139 | if (getBit(array, i, position) == 0) { 140 | evens.add(i); 141 | } else { 142 | odds.add(i); 143 | } 144 | } 145 | 146 | if (evens.size() <= odds.size()) { 147 | int v = missingNumber(evens, array, position + 1); 148 | return (v << 1); 149 | } else { 150 | int v = missingNumber(odds, array, position + 1); 151 | return (v << 1) | 1; 152 | } 153 | } 154 | 155 | private static int getBit(final int[] array, final int i, final int j) { 156 | return (array[i] >> j) & 1; 157 | } 158 | 159 | /** 160 | * Letters and Numbers: Given an array of letters and numbers, find the longest subarray with an equal 161 | * number of letters and numbers. 162 | * 163 | * Assumptions: 164 | * only letters and numbers in array 165 | * 166 | * Time complexity: O(n) 167 | * Space complexity: O(n) 168 | * 169 | * Difference with book: In many cases my algorithm has lower constants, but overall is very similar. 170 | * Mine will perform better if the the average chance of any given letter being a letter or a number 171 | * is about even as there will be full traverses through the dataset. 172 | */ 173 | public static char[] findLongestSubArrayWithEqualLettersAndNumbers(final char[] chars) { 174 | final Map> excessLettersToIndices = computeExcessLetters(chars); 175 | int longestSize = 0; 176 | Tuple2 longestIndices = null; 177 | for (final Map.Entry> next : excessLettersToIndices.entrySet()) { 178 | final Tuple2 indices = next.getValue(); 179 | if (indices._2 - indices._1 > longestSize) { 180 | longestSize = indices._2 - indices._1; 181 | longestIndices = indices; 182 | } 183 | } 184 | 185 | if (longestSize == 0) { 186 | return new char[0]; 187 | } else { 188 | return Arrays.copyOfRange(chars, longestIndices._1, longestIndices._2); 189 | } 190 | } 191 | 192 | /* 193 | * From each character keep track of the number of unpaired letters seen before it. If that number has 194 | * been seen before, then there is a equal subarray from when we first had that number of unpaired 195 | * letters until this index. As we find new subarrays that overlap, we can always take the new end index 196 | * as the new array should include all of the old array to be the longest. 197 | */ 198 | private static Map> computeExcessLetters(char[] chars) { 199 | final Map> excessLettersToIndices = Maps.newHashMap(); 200 | int excessLetters = 0; 201 | for (int i = 0; i <= chars.length; i++) { 202 | final Tuple2 indices = excessLettersToIndices.getOrDefault(excessLetters, new Tuple2<>(i, i)); 203 | excessLettersToIndices.put(excessLetters, new Tuple2<>(indices._1, i)); 204 | if (i < chars.length && Character.isLetter(chars[i])) { 205 | excessLetters += 1; 206 | } else { 207 | excessLetters -= 1; 208 | } 209 | } 210 | return excessLettersToIndices; 211 | } 212 | 213 | /** 214 | * Counts of 2: Find the number of 2s in numbers 0 to n inclusive. 215 | * 216 | * Assumptions: 217 | * 218 | * Time complexity: O(log10(n)) 219 | * Space complexity: O(1) 220 | */ 221 | public static int countsOfTwo(final int n) { 222 | if (n < 0) { 223 | return 0; 224 | } 225 | 226 | int remaining = n; 227 | int magnitude = 1; 228 | int twos = 0; 229 | while (remaining > 0) { 230 | final int digit = remaining % 10; 231 | if (digit >= 3) { 232 | twos += magnitude; 233 | } else if (digit == 2) { 234 | twos += (n % (magnitude * 10)) - (2 * magnitude) + 1; 235 | } 236 | 237 | twos += remaining/10 * magnitude; 238 | remaining /= 10; 239 | magnitude *= 10; 240 | } 241 | return twos; 242 | } 243 | 244 | /** 245 | * Baby Names: Given baby names and their frequency and a list of equivalent name pairs. Reduce the data 246 | * to a synonym to the total of all equivalent synonym to that name. 247 | * 248 | * Assumptions: 249 | * synonym relationships are transitive 250 | * all data is valid 251 | * remove names with no frequency 252 | * 253 | * Time complexity: O(names + synonyms) 254 | * Space complexity: O(names) 255 | */ 256 | public static Map babyNameFrequencyReduction(final Map frequencies, final List> synonyms) { 257 | final Map> nameSets = Maps.newHashMap(); 258 | for (final Tuple2 syn: synonyms) { 259 | mergeSets(nameSets, syn._1, syn._2); 260 | } 261 | // add missing nameSets that have no syn 262 | for (final Map.Entry nameFreq: frequencies.entrySet()) { 263 | if (!nameSets.containsKey(nameFreq.getKey())) { 264 | nameSets.put(nameFreq.getKey(), Sets.newHashSet(nameFreq.getKey())); 265 | } 266 | } 267 | 268 | return babyNameFrequencyReduction(nameSets, frequencies); 269 | } 270 | 271 | private static Map babyNameFrequencyReduction(final Map> nameSets, final Map frequencies) { 272 | final Map result = Maps.newHashMap(); 273 | for (final Set nameSet : Sets.newHashSet(nameSets.values())) { 274 | int total = 0; 275 | String minName = null; 276 | for (final String next : nameSet) { 277 | if (minName == null || next.compareTo(minName) < 0) { 278 | minName = next; 279 | } 280 | total += frequencies.getOrDefault(next, 0); 281 | } 282 | if (total > 0) { 283 | result.put(minName, total); 284 | } 285 | } 286 | return result; 287 | } 288 | 289 | private static void mergeSets(final Map> nameSets, final String a, final String b) { 290 | if (nameSets.containsKey(a) != nameSets.containsKey(b)) { 291 | final Set nameSet = nameSets.getOrDefault(a, nameSets.get(b)); 292 | nameSet.add(a); 293 | nameSet.add(b); 294 | nameSets.put(a, nameSet); 295 | nameSets.put(b, nameSet); 296 | } else if (!nameSets.containsKey(a)) { 297 | final Set nameSet = Sets.newHashSet(a, b); 298 | nameSets.put(a, nameSet); 299 | nameSets.put(b, nameSet); 300 | } else { 301 | // merge 2 sets 302 | final Set nameSetA = nameSets.get(a); 303 | final Set nameSetB = nameSets.get(b); 304 | for (String nextB : nameSetB) { 305 | nameSets.put(nextB, nameSetA); 306 | nameSetA.add(nextB); 307 | } 308 | } 309 | } 310 | 311 | public static class Person { 312 | final int height; 313 | final int weight; 314 | 315 | public static final Person MAX_VALUE = new Person(Integer.MAX_VALUE, Integer.MAX_VALUE); 316 | 317 | public Person(final int height, final int weight) { 318 | this.height = height; 319 | this.weight = weight; 320 | } 321 | 322 | public boolean smallerThan(final Person other) { 323 | return height < other.height && weight < other.weight; 324 | } 325 | } 326 | 327 | /** 328 | * Circus Tower: Given a list of people who have a height and a weight and can stack only where the person on top is 329 | * both shorter and lighter. How many people high can the person tower be? 330 | * 331 | * Assumptions: 332 | * ties can't stack 333 | * 334 | * Time complexity: O(?) 335 | * Space complexity: O(?) at least p^2 336 | * 337 | * Note: This could have a better performance. Probably could sort on both dimensions first. 338 | */ 339 | public static int circusTowerHeight(final Set people) { 340 | return circusTowerHeight(Person.MAX_VALUE, people, Maps.newHashMap()); 341 | } 342 | 343 | private static int circusTowerHeight(final Person base, final Set people, final Map memo) { 344 | if (people.isEmpty()) { 345 | return 0; 346 | } else if (memo.containsKey(base)) { 347 | return memo.get(base); 348 | } else { 349 | final Set smaller = Sets.newHashSet(); 350 | for (final Person person : people) { 351 | if (person.smallerThan(base)) { 352 | smaller.add(person); 353 | } 354 | } 355 | int maxHeight = 0; 356 | for(Person next : smaller) { 357 | final Set smallerCopy = Sets.newHashSet(smaller); 358 | smallerCopy.remove(next); 359 | maxHeight = Math.max(maxHeight, circusTowerHeight(next, smallerCopy, memo)); 360 | } 361 | memo.put(base, maxHeight + 1); 362 | return maxHeight + 1; 363 | } 364 | } 365 | 366 | private static class NoDuplicatesPriorityQueue extends PriorityQueue 367 | { 368 | @Override 369 | public boolean offer(E e) 370 | { 371 | boolean isAdded = false; 372 | if(!super.contains(e)) 373 | { 374 | isAdded = super.offer(e); 375 | } 376 | return isAdded; 377 | } 378 | } 379 | 380 | /** 381 | * Kth Multiple: Find the Kth number such that it's only prime factors are 3, 5, and 7. 382 | * 383 | * Assumptions: 384 | * Just doing this one time, otherwise make this like a Stream, I guess an Iterator in Java. 385 | * 386 | * Time complexity: O(k log k) 387 | * Space complexity: O(?) // something less than k. 388 | * 389 | * Note: This solution is easy to understand but a little slower than the optimal solution. 390 | */ 391 | public static int kthNumber(final int k) { 392 | final PriorityQueue numbers = new NoDuplicatesPriorityQueue<>(); 393 | numbers.offer(1); 394 | 395 | int i = 0; 396 | int number = 1; 397 | while(i < k) { 398 | number = numbers.poll(); 399 | numbers.offer(number * 3); 400 | numbers.offer(number * 5); 401 | numbers.offer(number * 7); 402 | 403 | i++; 404 | } 405 | return number; 406 | } 407 | 408 | /** 409 | * Kth Multiple: Find the Kth number such that it's only prime factors are 3, 5, and 7. 410 | * 411 | * Assumptions: 412 | * Just doing this one time, otherwise make this like a Stream, I guess an Iterator in Java. 413 | * 414 | * Time complexity: O(k) 415 | * Space complexity: O(?) // something less than k. 416 | */ 417 | public static int kthNumber2(final int k) { 418 | final LinkedList q3 = Lists.newLinkedList(); 419 | final LinkedList q5 = Lists.newLinkedList(); 420 | final LinkedList q7 = Lists.newLinkedList(); 421 | q3.offer(3); 422 | q5.offer(5); 423 | q7.offer(7); 424 | 425 | int i = 1; 426 | int number = 1; 427 | while(i < k) { 428 | number = findMin(q3, q5, q7); 429 | updateQueues(number, q3, q5, q7); 430 | 431 | i++; 432 | } 433 | return number; 434 | } 435 | 436 | private static int findMin(final LinkedList q3, final LinkedList q5, final LinkedList q7) { 437 | return Math.min(Math.min(q3.peek(), q5.peek()), q7.peek()); 438 | } 439 | 440 | private static void updateQueues(final int min, final LinkedList q3, final LinkedList q5, final LinkedList q7) { 441 | if (q3.peek().equals(min)) { 442 | q3.pop(); 443 | q3.offer(3*min); 444 | q5.offer(5*min); 445 | q7.offer(7*min); 446 | } else if (q5.peek().equals(min)) { 447 | q5.pop(); 448 | q5.offer(5*min); 449 | q7.offer(7*min); 450 | } else { 451 | q7.pop(); 452 | q7.offer(7*min); 453 | } 454 | } 455 | 456 | /** 457 | * Majority Element: Find an value such that the value represents a majority of the elements in the positive integer 458 | * array. Return -1 if there is no such element. 459 | * 460 | * Assumptions: 461 | * majority means > array.length / 2 462 | * 463 | * Time complexity: O(n) 464 | * Space complexity: O(1) 465 | */ 466 | public static int findMajority(int[] array) { 467 | final int maybeMajority = findPossibleMajority(array); 468 | if (maybeMajority > 0 && isValidMajority(maybeMajority, array)) { 469 | return maybeMajority; 470 | } else { 471 | return -1; 472 | } 473 | } 474 | 475 | private static int findPossibleMajority(final int[] a) { 476 | int maybeMajority = -1; 477 | int majorityCount = 0; 478 | for (int j : a) { 479 | if (majorityCount == 0) { 480 | maybeMajority = j; 481 | } 482 | 483 | if (maybeMajority == j) { 484 | majorityCount++; 485 | } else { 486 | majorityCount--; 487 | } 488 | } 489 | return maybeMajority; 490 | } 491 | 492 | private static boolean isValidMajority(final int maybeMajority, final int[] array) { 493 | int found = 0; 494 | for (int next : array) { 495 | if (next == maybeMajority) { 496 | found++; 497 | if (found > array.length / 2) { 498 | return true; 499 | } 500 | } 501 | } 502 | return false; 503 | } 504 | 505 | /** 506 | * Word Distance: Given a large text file of words and two words. Find the shortest distance between the two words. 507 | * 508 | * Assumptions: 509 | * the word exists 510 | * pre-process dictionary 511 | * 512 | * Pre-processing: 513 | * Time complexity: O(w) where is the number of non-distinct words 514 | * Space complexity: O(w) 515 | * 516 | * Time complexity: O(a + b) where a and b are the number of occurrences of the two words 517 | * Space complexity: O(1) 518 | */ 519 | public static int wordDistance(final String a, final String b, final Map> wordPositions) { 520 | final Iterator occA = wordPositions.get(a).iterator(); 521 | final Iterator occB = wordPositions.get(b).iterator(); 522 | int minDistance = Integer.MAX_VALUE; 523 | int nextA = occA.next(); 524 | int nextB = occB.next(); 525 | while (occA.hasNext() && occB.hasNext()) { 526 | if (nextA > nextB) { 527 | minDistance = Math.min(minDistance, nextA - nextB); 528 | nextB = occB.next(); 529 | } else { 530 | minDistance = Math.min(minDistance, nextB - nextA); 531 | nextA = occA.next(); 532 | } 533 | } 534 | return minDistance; 535 | } 536 | 537 | public static Map> wordPositions(final String[] words) { 538 | final Map> result = Maps.newHashMap(); 539 | for (int i = 0; i < words.length; i++) { 540 | result.getOrDefault(words[i], Lists.newLinkedList()).add(i); 541 | } 542 | return result; 543 | } 544 | } 545 | -------------------------------------------------------------------------------- /src/main/java/com/github/myyk/cracking/Chapter7Solutions.java: -------------------------------------------------------------------------------- 1 | package com.github.myyk.cracking; 2 | 3 | import java.math.BigInteger; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.Collections; 7 | import java.util.Comparator; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Optional; 12 | import java.util.Set; 13 | import java.util.Stack; 14 | 15 | import com.google.common.collect.Lists; 16 | import com.google.common.collect.Maps; 17 | import com.google.common.collect.Sets; 18 | 19 | /** 20 | * Recursion and Dynamic Programming 21 | */ 22 | public class Chapter7Solutions { 23 | /** 24 | * Triple Step: A child is running up steps, they can take 1, 2, or 3 steps at a time. How many ways 25 | * can they make it up N steps? 26 | * 27 | * Assumptions: 28 | * 29 | * Time complexity: O(n) 30 | * Space complexity: O(n) 31 | */ 32 | public static BigInteger tripleStepRecursive(final int steps) { 33 | return tripleStepRecursive(steps, Lists.newArrayList(BigInteger.valueOf(1), BigInteger.valueOf(1), BigInteger.valueOf(2))); 34 | } 35 | 36 | private static BigInteger tripleStepRecursive(final int steps, final List stepsToWays) { 37 | if (steps < 0) { 38 | return BigInteger.ZERO; 39 | } else if (steps < stepsToWays.size()) { 40 | return stepsToWays.get(steps); 41 | } else { 42 | BigInteger ways = tripleStepRecursive(steps - 1, stepsToWays).add(tripleStepRecursive(steps - 2, stepsToWays).add(tripleStepRecursive(steps - 3, stepsToWays))); 43 | stepsToWays.add(ways); 44 | return ways; 45 | } 46 | } 47 | 48 | /** 49 | * Triple Step: Same as above but using less space and coded iteratively. 50 | * 51 | * Assumptions: 52 | * 53 | * Time complexity: O(n) 54 | * Space complexity: O(1) 55 | */ 56 | public static BigInteger tripleStepIterative(int steps) { 57 | if (steps < 0) { 58 | return BigInteger.ZERO; 59 | } else if (steps == 0 || steps == 1) { 60 | return BigInteger.ONE; 61 | } else if (steps == 2) { 62 | return BigInteger.valueOf(2); 63 | } else { 64 | BigInteger nMinus3 = BigInteger.ONE; 65 | BigInteger nMinus2 = BigInteger.ONE; 66 | BigInteger nMinus1 = BigInteger.valueOf(2); 67 | 68 | for (int i = 3; i < steps; i++) { 69 | BigInteger temp = nMinus1.add(nMinus2.add(nMinus3)); 70 | nMinus3 = nMinus2; 71 | nMinus2 = nMinus1; 72 | nMinus1 = temp; 73 | } 74 | return nMinus1.add(nMinus2.add(nMinus3)); 75 | } 76 | } 77 | 78 | public static class Robot { 79 | private int x; 80 | private int y; 81 | 82 | public Robot(int x, int y) { 83 | this.x = x; 84 | this.y = y; 85 | } 86 | 87 | public int getX() { 88 | return x; 89 | } 90 | 91 | public int getY() { 92 | return y; 93 | } 94 | 95 | public void moveRight() { 96 | x++; 97 | } 98 | 99 | public void moveDown() { 100 | y++; 101 | } 102 | } 103 | 104 | /** 105 | * Robot in a Grid: There's a robot and a grid with r rows and c columns. The robot is 106 | * in the upper left corner. It can move right or down on each move. There are some 107 | * grid locations that are off limits. Design an algorithm to find a path from the 108 | * top to the bottom right corner. 109 | * 110 | * Assumptions: 111 | * grid is NxM 112 | * 113 | * Time complexity: O(nm) 114 | * Space complexity: O(n+m) 115 | */ 116 | // return sequence of moves n, Some(true) means right, Some(false) means down. None means no solution. 117 | // in the grid, true means off limits 118 | public static boolean[] findPath(boolean[][] grid) { 119 | if (grid.length == 1 && grid[0].length == 1) { 120 | return new boolean[0]; 121 | } 122 | 123 | List>> moves = new ArrayList<>(); 124 | for (int i = 0; i < grid.length; i++) { 125 | List> next = new ArrayList<>(); 126 | moves.add(next); 127 | for (int j = 0; j < grid[0].length; j++) { 128 | next.add(Optional.empty()); 129 | } 130 | } 131 | 132 | boolean[] solution = findPath(true, 1, 0, grid, moves); 133 | if (solution != null) { 134 | return solution; 135 | } 136 | 137 | solution = findPath(false, 0, 1, grid, moves); 138 | return solution; 139 | } 140 | 141 | // moves stores the move to get to the given position 142 | private static boolean[] findPath(boolean movedRight, int robotX, int robotY, boolean[][] grid, List>> moves) { 143 | if (robotX >= grid[0].length || robotY >= grid.length) { 144 | return null; 145 | } else if (moves.get(robotY).get(robotX).isPresent()) { 146 | // we've already been here 147 | return null; 148 | } else if (grid[robotY][robotX]) { 149 | return null; 150 | } else { 151 | moves.get(robotY).set(robotX, Optional.of(movedRight)); 152 | if (robotX == grid[0].length - 1 && robotY == grid.length - 1) { 153 | return movesToSolution(moves); 154 | } else{ 155 | moves.get(robotY).set(robotX, Optional.of(movedRight)); 156 | // try right 157 | boolean[] solution = findPath(true, robotX + 1, robotY, grid, moves); 158 | if (solution != null) { 159 | return solution; 160 | } 161 | 162 | // try left 163 | solution = findPath(false, robotX, robotY + 1, grid, moves); 164 | return solution; 165 | } 166 | } 167 | } 168 | 169 | protected static boolean[] movesToSolution(List>> moves) { 170 | int x = moves.get(0).size() - 1; 171 | int y = moves.size() - 1; 172 | boolean[] answer = new boolean[x + y]; 173 | int i = answer.length; 174 | while (i > 0) { 175 | i--; 176 | answer[i] = moves.get(y).get(x).get(); 177 | if (answer[i]) { 178 | x--; 179 | } else { 180 | y--; 181 | } 182 | } 183 | 184 | return answer; 185 | } 186 | 187 | /** 188 | * Magic Index: Given an sorted array A[1, 2, ... , n, n-1] find if it has an 189 | * index such that A[i] = i. 190 | * 191 | * Assumptions: 192 | * sorted 193 | * distinct 194 | * 195 | * Time complexity: O(log n) 196 | * Space complexity: O(log n) 197 | */ 198 | // -1 means not found 199 | public static int findMagicIndexDistinct(final int[] a) { 200 | if (a == null) { 201 | return -1; 202 | } 203 | return findMagicIndexDistinct(a, 0, a.length - 1); 204 | } 205 | 206 | private static int findMagicIndexDistinct(final int[] a, int start, int end) { 207 | int mid = (start + end) / 2; 208 | if (start > end) { 209 | return -1; 210 | } else if (a[mid] == mid) { 211 | return mid; 212 | } else if (a[mid] > mid) { 213 | return findMagicIndexDistinct(a, start, mid - 1); 214 | } else { 215 | return findMagicIndexDistinct(a, mid + 1, end); 216 | } 217 | } 218 | 219 | /** 220 | * Magic Index: Given an sorted array A[1, 2, ... , n, n-1] find if it has an 221 | * index such that A[i] = i. 222 | * 223 | * Assumptions: 224 | * sorted 225 | * 226 | * Time complexity: O(n) 227 | * Space complexity: O(log n) 228 | */ 229 | // -1 means not found 230 | public static int findMagicIndexNonDistinct(final int[] a) { 231 | if (a == null) { 232 | return -1; 233 | } 234 | return findMagicIndexNonDistinct(a, 0, a.length - 1); 235 | } 236 | 237 | private static int findMagicIndexNonDistinct(final int[] a, int start, int end) { 238 | int mid = (start + end) / 2; 239 | if (start > end) { 240 | return -1; 241 | } else if (a[mid] == mid) { 242 | return mid; 243 | } else if (a[mid] < mid) { 244 | if (a[mid] > 0 && a[mid] < a.length) { 245 | int answer = findMagicIndexNonDistinct(a, start, a[mid]); 246 | if (answer != -1) { 247 | return answer; 248 | } 249 | } 250 | return findMagicIndexNonDistinct(a, mid + 1, end); 251 | } else { 252 | if (a[mid] > 0 && a[mid] < a.length) { 253 | int answer = findMagicIndexNonDistinct(a, a[mid], end); 254 | if (answer != -1) { 255 | return answer; 256 | } 257 | } 258 | return findMagicIndexNonDistinct(a, start, mid -1); 259 | } 260 | } 261 | 262 | /** 263 | * Power Set: Write a function to return all subsets of a set. 264 | * 265 | * Assumptions: 266 | * 267 | * Time complexity: O(2^n) 268 | * Space complexity: O(2^n) 269 | */ 270 | public static Set> powerSet(Set set) { 271 | Set> result = Sets.newHashSet(); 272 | return powerSet(set, result); 273 | } 274 | 275 | private static Set> powerSet(Set set, Set> result) { 276 | if (!result.contains(set)) { 277 | result.add(set); 278 | for (T next : set) { 279 | Set newSet = Sets.newHashSet(set); 280 | newSet.remove(next); 281 | powerSet(newSet, result); 282 | } 283 | } 284 | return result; 285 | } 286 | 287 | /** 288 | * Recursive Multiply: Write a function to multiply two numbers recursively without 289 | * using the '*' operator. 290 | * 291 | * Assumptions: 292 | * all numbers 293 | * overflow works the same 294 | * 295 | * Time complexity: O(b) where b is the number of bits in the number 296 | * Space complexity: O(b) from the stacks, though this could be tail call optimized in Scala 297 | * 298 | * Difference with book: If you think of b being composed of bits b31 to b0. Then you 299 | * can think of product = a*b31*1<<31 + ... + a*b0*1<<0. This is pretty straight 300 | * forward to implement and I think less complicated than the book's answer. 301 | */ 302 | public static int multiply(int a, int b) { 303 | // optimization by reducing bits on second, maybe, would need to perf test. 304 | // this could be adding more instructions for all I know. 305 | if (Integer.highestOneBit(a) > Integer.highestOneBit(b)) { 306 | return multiplyHelper(a, b, 0, 0); 307 | } else { 308 | return multiplyHelper(b, a, 0, 0); 309 | } 310 | } 311 | 312 | private static int multiplyHelper(final int a, final int b, final int i, final int product) { 313 | if (b == 0) { 314 | return product; 315 | } else if ((b & 1) == 1) { 316 | return multiplyHelper(a, b>>>1, i+1, product + (a<>>1, i+1, product); 319 | } 320 | } 321 | 322 | /** 323 | * Towers of Hanoi: Given 3 stacks set up as towers of hanoi, move the rings from 324 | * one the starting stack to another stack by following the tower of hanoi rules. 325 | * 326 | * Assumptions: 327 | * 328 | * Time complexity: O(2^n) 329 | * Space complexity: O(1) 330 | * 331 | * Note: I'm just going to do it as written in the book as this is hard for me to 332 | * conceptualize since I'm not actually doing any real work. 333 | * *** Come back to this one. 334 | */ 335 | public static void moveDisks(int n, Stack origin, Stack destination, Stack buffer) { 336 | if (n <= 0) return; 337 | moveDisks(n-1, origin, buffer, destination); 338 | moveTop(origin, destination); 339 | moveDisks(n-1, buffer, destination, origin); 340 | } 341 | 342 | private static void moveTop(Stack origin, Stack destination) { 343 | destination.push(origin.pop()); 344 | } 345 | 346 | /** 347 | * Permutations without Dups: Compute all permutations of a string of unique chars. 348 | * 349 | * Assumptions: 350 | * 351 | * Time complexity: O(k!) 352 | * Space complexity: O(k!) 353 | * 354 | * Note: This actually works if there are dups. That was sort of a clever accident. 355 | * The issue with the code below though is that StringBuffers apparently don't 356 | * hash to the same value if they are equal. This makes sense though since they 357 | * are a mutable data structure. Very interesting bug, I'm leaving it so I can see 358 | * this again. 359 | */ 360 | public static Set permutationsUnique(final String str) { 361 | final Set intermediate = permutationsUniqueHelper(str); 362 | final Set results = Sets.newHashSet(); 363 | for (StringBuffer sb : intermediate) { 364 | results.add(sb.toString()); 365 | } 366 | return results; 367 | } 368 | 369 | private static Set permutationsUniqueHelper(final String str) { 370 | if (str.isEmpty()) { 371 | return Sets.newHashSet(); 372 | } 373 | 374 | Set result = Sets.newHashSet(); 375 | final StringBuffer first = new StringBuffer(); 376 | first.append(str.charAt(0)); 377 | result.add(first); 378 | 379 | for (int i = 1; i < str.length(); i++) { 380 | final char c = str.charAt(i); 381 | final Set newResult = Sets.newHashSet(); 382 | // This can't handle very large strings cause these Sets aren't doing what you'd 383 | // normal guess for a String, because StringBuffers don't hash to the same values 384 | // even when they are equal. 385 | for (StringBuffer perm : result) { 386 | final Set temp = Sets.newHashSet(); 387 | for (int j = 0; j <= perm.length(); j++) { 388 | StringBuffer copy = new StringBuffer(perm); 389 | copy.insert(j, c); 390 | temp.add(copy); 391 | } 392 | newResult.addAll(temp); 393 | } 394 | result = newResult; 395 | } 396 | return result; 397 | } 398 | 399 | /** 400 | * Parens: Final all legal pairings of N parenthesis. 401 | * 402 | * Assumptions: 403 | * 404 | * Time complexity: O(?) // both of these grow quite fast 405 | * Space complexity: O(?) 406 | * 407 | * Difference with book: Their answer requires less memory as it doesn't create a lot 408 | * of strings that end up being duplicates and eliminated when adding to the set. 409 | * Given that this algorithm hits it's limits at pretty low values of n, I don't 410 | * think their optimization is necessary at all. Even for their less optimized 411 | * answer and explanation it is more confusing than below in my opinion. 412 | * 413 | * My answer uses 'String.format' to interpolate the strings. It's understandable 414 | * what's happening and it's more performant than just '+'-ing the strings. 415 | * 416 | * I also looked at the solution differently, rather than putting parentheses into 417 | * the n-1 values, you can think of them as next to or encapsulating the n-1 values. 418 | * I find this way of looking at it easier to code correctly. 419 | */ 420 | public static Set parens(final int n) { 421 | final Set result = Sets.newHashSet(); 422 | if (n <= 0) { 423 | // do nothing 424 | } else if (n == 1) { 425 | result.add("()"); 426 | } else { 427 | final Set nMinusOne = parens(n - 1); 428 | // the one duplicate will be eliminated by the set. 429 | for (String next : nMinusOne) { 430 | result.add(String.format("()%s", next)); 431 | result.add(String.format("(%s)", next)); 432 | result.add(String.format("%s()", next)); 433 | } 434 | } 435 | return result; 436 | } 437 | 438 | public static class Color { 439 | final byte r; 440 | final byte g; 441 | final byte b; 442 | 443 | public Color(final byte r, final byte g, final byte b) { 444 | this.r = r; 445 | this.g = g; 446 | this.b = b; 447 | } 448 | 449 | @Override 450 | public int hashCode() { 451 | final int prime = 31; 452 | int result = 1; 453 | result = prime * result + b; 454 | result = prime * result + g; 455 | result = prime * result + r; 456 | return result; 457 | } 458 | 459 | @Override 460 | public boolean equals(Object obj) { 461 | if (this == obj) 462 | return true; 463 | if (obj == null) 464 | return false; 465 | if (getClass() != obj.getClass()) 466 | return false; 467 | Color other = (Color) obj; 468 | if (b != other.b) 469 | return false; 470 | if (g != other.g) 471 | return false; 472 | return r == other.r; 473 | } 474 | } 475 | 476 | /** 477 | * Paint Fill: Given an image, color and x,y coordinate fill the color at the 478 | * coordinates with the new color and touching pixels with the same old color 479 | * and all the pixels touching them and so on. 480 | * 481 | * Assumptions: 482 | * 483 | * Time complexity: O(n*m) where n and m are the dimensions of the image array 484 | * Space complexity: O(1) 485 | */ 486 | public static Color[][] paintFill(final Color[][] image, final int x, final int y, final Color newColor) { 487 | if (outsideImage(image, x, y)) { 488 | throw new IllegalArgumentException("x or y is out of range of the image"); 489 | } 490 | 491 | final Color oldColor = image[y][x]; 492 | paintFill(image, x, y, newColor, oldColor); 493 | return image; 494 | } 495 | 496 | private static void paintFill(final Color[][] image, final int x, final int y, final Color newColor, final Color oldColor) { 497 | if (!outsideImage(image, x, y) && image[y][x].equals(oldColor)) { 498 | image[y][x] = newColor; 499 | 500 | paintFill(image, x+1, y, newColor, oldColor); 501 | paintFill(image, x-1, y, newColor, oldColor); 502 | paintFill(image, x, y+1, newColor, oldColor); 503 | paintFill(image, x, y-1, newColor, oldColor); 504 | } 505 | } 506 | 507 | private static boolean outsideImage(final Color[][] image, final int x, final int y) { 508 | return x < 0 || x >= image[0].length || y < 0 || y >= image.length; 509 | } 510 | 511 | /** 512 | * Coins: Given an infinite number of coins of various denominations and a total. 513 | * Find the number of different ways to get that total with coins. 514 | * 515 | * Assumptions: 516 | * positive value coins 517 | * 518 | * Time complexity: O(?) 519 | * Space complexity: O(?) 520 | * 521 | * Note: This is a little trickier than doing it in Scala since if you use a List in 522 | * the helper function you have to be careful to not mutate the list fucking up 523 | * the one of the nested calls since they share the same list. 524 | * 525 | * Also if this needs to be done with large coins to smaller coins. 526 | */ 527 | public static int coinsCount(final Set coins, final int total) { 528 | Map cache = Maps.newHashMap(); 529 | cache.put(0, 1); // base case 530 | Integer[] coinsArr = new Integer[coins.size()]; 531 | coins.toArray(coinsArr); 532 | Arrays.sort(coinsArr, Comparator.reverseOrder()); 533 | return coinsCount(coinsArr, 0, total, cache); 534 | } 535 | 536 | private static int coinsCount(final Integer[] coins, final int index, final int remaining, final Map cache) { 537 | if (remaining < 0) { 538 | return 0; 539 | } else if (cache.containsKey(remaining)) { 540 | return cache.get(remaining); 541 | } else if (index == coins.length) { 542 | return 0; 543 | } else { 544 | int ways = 0; 545 | int coin = coins[index]; 546 | for (int i = 0; i <= remaining; i += coin) { 547 | ways += coinsCount(coins, index + 1, remaining - i, cache); 548 | } 549 | cache.put(remaining, ways); 550 | return ways; 551 | } 552 | } 553 | 554 | /** 555 | * Eight Queens: Find every combination of boards where you can place 8 queens on 556 | * an 8x8 board such that they can't attack each other with standard chess 557 | * moves. A result can be expressed as a single array of the row position of the 558 | * queen on the i-th column where i is the array index. 559 | * 560 | * Assumptions: 561 | * 562 | * Time complexity: O(n!) where n is the number of moves, but it's pruned, so maybe 563 | * the bound can be expressed tighter than that. 564 | * Space complexity: O(n!) 565 | * 566 | * Notes: Performance could be improved by keeping track of the occupied diagonals and 567 | * rows on the way down so that the isBoardValid would be much faster. 568 | */ 569 | public static ArrayList placeQueens(final int numberOfQueens /* = 8, if we could have defaults*/) { 570 | return placeQueens(numberOfQueens, 0, new int[numberOfQueens], new ArrayList<>()); 571 | } 572 | 573 | private static ArrayList placeQueens(final int numberOfQueens, int queenIndex, int[] placedQueens, ArrayList result) { 574 | if (!isBoardValid(placedQueens, queenIndex, numberOfQueens)) { 575 | // do nothing 576 | } else if (queenIndex == numberOfQueens) { 577 | result.add(placedQueens.clone()); 578 | } else { 579 | for (int i = 0; i < numberOfQueens; i++) { 580 | placedQueens[queenIndex] = i; 581 | placeQueens(numberOfQueens, queenIndex + 1, placedQueens, result); 582 | } 583 | } 584 | return result; 585 | } 586 | 587 | private static boolean isBoardValid(int[] placedQueens, int numPlacedQueens, int boardSize) { 588 | boolean[][] board = new boolean[numPlacedQueens][boardSize]; 589 | for (int i = 0; i < numPlacedQueens; i++) { 590 | board[i][placedQueens[i]] = true; 591 | for (int j = i-1; j >= 0; j--) { 592 | int offset = i - j; 593 | if (board[j][placedQueens[i]]) { 594 | return false; 595 | } else if (placedQueens[i] + offset < boardSize && board[j][placedQueens[i] + offset]) { 596 | // right diagonal 597 | return false; 598 | } else if (placedQueens[i] - offset >= 0 && board[j][placedQueens[i] - offset]) { 599 | return false; 600 | } 601 | } 602 | } 603 | return true; 604 | } 605 | 606 | public static class Box { 607 | public static Box MAX_VALUE = new Box(Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE); 608 | 609 | public final int height, width, depth; 610 | 611 | public Box(final int height, final int width, final int depth) { 612 | this.height = height; 613 | this.width = width; 614 | this.depth = depth; 615 | } 616 | 617 | public boolean smallerThan(Box other) { 618 | return height < other.height && width < other.width && depth < other.depth; 619 | } 620 | } 621 | 622 | /** 623 | * Stack of Boxes: Given a stack of N boxes. Find the maximum height that you can 624 | * stack the boxes given the constraints that you cannot rotate a box and a box 625 | * can only be stacked if the upper box has smaller width, height, and depth than 626 | * the box below it. 627 | * 628 | * Assumptions: 629 | * boxes have positive values for width, height and depth 630 | * 631 | * Time complexity: O() 632 | * Space complexity: O() 633 | * 634 | * Note: I thought of another way you could do this. Though, maybe not any better in 635 | * performance would be to construct a directed graph from smaller to larger box. 636 | * Then traverse it from each root (smallest) storing at each node the previous 637 | * Max(height + previous height, previous max). You could also keep track of the 638 | * overall max, so you don't have to search for it later. 639 | */ 640 | public static int stackHeight(final List boxes) { 641 | if (boxes.isEmpty()) { 642 | return 0; 643 | } 644 | 645 | return stackHeight(Sets.newHashSet(boxes), Box.MAX_VALUE, new HashMap<>()); 646 | } 647 | 648 | private static int stackHeight(final Set boxes, final Box bottom, final Map, Integer> cache) { 649 | if (cache.containsKey(boxes)) { 650 | return cache.get(boxes); 651 | } else { 652 | int maxHeight = 0; 653 | for (Box box : boxes) { 654 | if (box.smallerThan(bottom)) { 655 | final Set newBoxes = Sets.newHashSet(boxes); 656 | newBoxes.remove(box); 657 | maxHeight = Math.max(maxHeight, box.height + stackHeight(newBoxes, box, cache)); 658 | } 659 | } 660 | cache.put(boxes, maxHeight); 661 | return maxHeight; 662 | } 663 | } 664 | 665 | /** 666 | * Boolean Evaluation: Given a boolean expression and a result. Find how many ways 667 | * parentheses can be added to the expression to get the result. 668 | * 669 | * Assumptions: 670 | * cannot put parentheses around a unary expression such as a value 0 or 1 671 | * each operator has the same precedence 672 | * expressions are valid 673 | * 674 | * Time complexity: O() 675 | * Space complexity: O() 676 | * 677 | * Note: The answer is also equal to the Catalan number where n is the number of 678 | * operators. That is Cn = (2n)!/((n+1)!n!) 679 | * 680 | * Difference with books answer: 681 | * They kind of cleverly concatenated the result with the strings for the cache to 682 | * use just one. This seems less clean than using a tuple key as I would in Scala 683 | * but oh well, this isn't Scala. I got the answer on my own after a while, the 684 | * tests really helped. This is one question I didn't write the test first for and 685 | * I regret that. It took longer to develop that it could have otherwise. 686 | * Calculating the total ways to be able to subtract for the negative case is really 687 | * useful. Maybe what I did before was more efficient, but it's doubtful there was 688 | * much work saved compared to this answer which is a couple functions smaller and 689 | * easier to read. 690 | */ 691 | public static int countEval(String expression, boolean result) { 692 | final Map cacheTrue = Maps.newHashMap(); 693 | cacheTrue.put("0", 0); 694 | cacheTrue.put("1", 1); 695 | final Map cacheFalse = Maps.newHashMap(); 696 | cacheFalse.put("0", 1); 697 | cacheFalse.put("1", 0); 698 | return countEval(expression, result, cacheTrue, cacheFalse); 699 | } 700 | 701 | private static int countEval(String expression, boolean result, Map cacheTrue, Map cacheFalse) { 702 | if (result && cacheTrue.containsKey(expression)) { 703 | return cacheTrue.get(expression); 704 | } else if (!result && cacheFalse.containsKey(expression)) { 705 | return cacheFalse.get(expression); 706 | } else { 707 | int count = 0; 708 | for (int i = 1; i < expression.length(); i+=2) { 709 | final char c = expression.charAt(i); 710 | final String left = expression.substring(0, i); 711 | final String right = expression.substring(i+1); 712 | final int leftFalse = countEval(left, false, cacheTrue, cacheFalse); 713 | final int rightFalse = countEval(right, false, cacheTrue, cacheFalse); 714 | final int leftTrue = countEval(left, true, cacheTrue, cacheFalse); 715 | final int rightTrue = countEval(right, true, cacheTrue, cacheFalse); 716 | final int totalWays = (leftFalse + leftTrue) * (rightFalse + rightTrue); 717 | 718 | int ways = 0; 719 | if (c == '|') { 720 | ways = (leftFalse*rightTrue) + (leftTrue*rightFalse) + (leftTrue*rightTrue); 721 | } else if (c == '&') { 722 | ways = leftTrue * rightTrue; 723 | } else if (c == '^') { 724 | ways = (leftTrue * rightFalse) + (leftFalse * rightTrue); 725 | } 726 | count += (result ? ways : totalWays - ways); 727 | } 728 | if (result) { 729 | cacheTrue.put(expression, count); 730 | } else { 731 | cacheFalse.put(expression, count); 732 | } 733 | return count; 734 | } 735 | } 736 | } 737 | --------------------------------------------------------------------------------