459 41 41MB
English Pages 708 Year 2015
CRACKING ��the � �·
CODING INTERVIEW
189 PROGRAMMING Q!JESTIONS & SOLUTIONS
CRACKING the
CODING INTERVIEW 6TH EDITION
ALso BY GAYLE LAAKMANN McDowELL CRACKING THE PM INTERVIEW How TO LAND A PRODUCT MANAGER Jos IN TECHNOLOGY
CRACKING THE TECH CAREER INSIDER ADVICE ON LANDING A JOB AT GOOGLE, MICROSOFT, APPLE, OR ANY Top TECH (OMPANY
CRACKING the
CODING INTERVIEW 6th Edition 189 Programming Questions and Solutions
GAYLE LAAKMANN MCDOWELL Founder and CEO, CareerCup.com
CareerCup, LLC Pa lo Alto, CA
CRACKING THE CODING INTERVTEW, SIXTH EDITION Copyright© 2015 by CareerCup.
All rights reserved. No part of this book may be reproduced in any form by any electronic or me chanical means, including information storage and retrieval systems, without permission in writing from the author or publisher, except by a reviewer who may quote brief passages in a review. Published by CareerCup, LLC, Palo Alto, CA. Compiled Feb 10, 2016. For more information, contact [email protected].
978-0-9847828-5-7 (ISBN 13)
For Davis and Tobin, and all the things that bring us joy in life.
Introduction .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
The I nterview Process
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.. .
.
.
.
.
.
.
How Questions are Selected
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Introduction . I.
Why?
.
.
.
.
.
.
. · .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
...
.
.
.
. . .
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 2
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
4
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 6
.
4
It's All Relative ....................................................... 7 II.
Frequently Asked Questions
.
.
Behind the Scenes .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.7
.
The Amazon Interview
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
, . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
The Google I nterview
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
.
.
.
.
.
.
.
.
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
The Microsoft I nterview
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
The Apple Interview
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
8
9
11
.
The Facebook Interview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 2 .
.
.
.
.
.
.
.
•
.
.
.
Experienced Cand idates .
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
.
.
The Palantir Interview I l l. Special Situations
.
.
.
.
.
.
.
.
.
.
.
.
.
. .
.
.
.
Product (and Program) Ma nagement
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Startu ps
.
.
.
.
Dev Lead and Managers .
.
.
.
Testers and SD ETs
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
... .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.. . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
•
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.. .
.
.
.
.. .
.
.
.
.
13
.
.
.
.
.
.
15
.
.
.
.
.
16
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
.
.
Acquisitions and Acquihires
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
.
Before the Interview
.
.
.
Getting the Right Experience Writing a Great Resume .
Preparation Ma p .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Know YourTechnical Projects .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Responding to Behavioral Questions So, tell me about you rself.. .. Big 0
.
.
.
.
.
. .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
26 26
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
I nterview Preparation Grid
VI.
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Behavioral Questions
V.
.
.
18 21
For Interviewers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . IV.
15
.
.
.
.
.
.
.
.
.
.
.
.
•
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
32
•
.
.
.
•
.
.
.
.
.
.
.
.
.
.
.
.
32
.
.
.
.
.
.
.
.
.
.
.
.
33
.
.
.
.
.
.
.
.
.
.
.
.
34
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
...
.
.
.
...
.
.
.
....
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
38
.
An Analogy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
38
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
40
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
41
Drop the Non-Dom inant Terms
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
42
Time Complexity
.
Space Com plexity
.
.
.
.
.
.
.
.
.
.
.
Drop the Consta nts
VI
.
.
.. .
.
Cracking the Coding Interview. 6th Edition
..... .
.
.
.
.
. . .. .
.
.
.
Introduction Mu lti-Pa rt Algorithms: Add vs. Multiply . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 Amortized Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 Log N Runtimes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 Recu rsive Ru ntimes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 Exam ples and Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 .
VII. Technical Questions
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
60
How to Prepare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 What You Need To Know . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 Walking Through a Problem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 Optimize& Solve Tech nique # 1 : Look for BU D . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 Optimize& Solve Technique # 2 : D I Y (Do It You rself) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 Optimize& Solve Technique #3: Simpl ify and Generalize
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
71
Optim ize& Solve Technique # 4: Base Case a nd Build . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 1 Optimize& Solve Technique #5: Data Structure Brainstorm
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
72
Best Conceiva ble Runtime (BCR) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2 Handling I n correct Answers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 When You've Heard a Question Before . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 The "Perfect" Language for Interviews . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 What Good Coding Looks Li ke ............................................
77
Don't Give U p! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 1 .
VIII. The Offer and Beyond
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
82
.
Handling Offers and Rejection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 Eval u ating the Offer. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 Negotiation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 On the Job . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85 IX.
l nterview Questions .
Data Structures
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
81
.
•
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
88
Cha pter 1 I Arrays and Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88 .
.
.
.
Array List & Resizable Arrays
.
.
.
.
.
.
.
.
.
Hash Tables
.
String Builder
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
88
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
89
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
89
Chapte r 2 I L i n ked Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 Creating a Linked List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 92 Deleting a Node from a Singly Linked List The "Runner" Technique
.
.
.
.
.
.
.
.
.
.
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
. ..
.
.
....
.
.
. . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
93
. .
.
.
.
.
.
.
.
.
. ..
.
.
.....
93
.
.
.
.
.
Recursive Problems. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
CrackingTheCodinglnterview.com \6th Edition
I
VII
Introduction Chapter 3 I Stacks and Queues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 .
96
Implementing a Queue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
97
.
Implementing a Stack .
.
.
.
.
Chapter 4 I Trees and Graphs . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Binary Tree Traversal.
Tries (Prefix Trees). .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. . 1 00
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 03 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 03
Binary Heaps (Min-Heaps and Max-Heaps)
.
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 00
Types of Trees
Graphs
.
.
.
.
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 05 .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1 05
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . . . . 1 07
Graph Search
.
Concepts and Algorithms
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
•
.
.
.
112
Chapter 5 I Bit Manipu lation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 2 Bit Manipulation By Hand . Bit Facts and Tricks
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 12
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112 .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
113
. .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
�
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
113
Two's Complement and Negative Numbers Arithmetic vs. Logical Right Shift
.
.
.
.
.
.
Common Bit Tasks: Getting and Setting
.
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 14
Chapter 6 I Math and Logic Puzzles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 7 Prime Numbers Probability
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
117
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 19
StartTalking
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 21
Develop Rules and Patterns . Worst Case Shifting
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 22
Algorithm Approaches
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 22
Chapter 7 I Object-Oriented Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 25 How to Approach . Design Patterns .
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 25
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 26
Chapter 8 I Recursion and Dynamic Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 30 How to Approach . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 30
Recursive vs. Iterative Solutions
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
Dynamic Programming & Memoization .
Chapte r 9 I System Design and Scala bility Handling the Questions Design: Step-By-Step
VII I
.
.
.
.
.
.
.
.
.
. .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 1 37
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 37
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 38
Algorithms that Scale: Step-By-Step Key Concepts .
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131
.
.
.
.
.
.
.
.
.
.
.
.
. .
.
.
..
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. . . . . . 1 39
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 40
Cracking the Coding Interview, 6th Edition
Introduction .
Considerations
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 42 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 43
There is no "perfect" system. . [xample Problem .
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 43
Chapter 1 0 I Sorting a nd Searching . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1 46
.
.
•
.
.
.
.
.
.
•
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 46
.
.
.
.
.
.
.
.
.
.
•
.
.
.
. .
What the Interviewer Is Looking For
.
.
Common Sorting Algorithms Searching Algorithms
Cha pter 1 1 I Testing
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. .
.
.
•
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. .
•
.
. .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1 49 .
.
1 52 1 52
Testing a Real World Object
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 53
Testing a Piece of Software
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 54
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. .
.
.
.
.
.
.
.
.
.
.
.
1 55
Troubleshooting Questions
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1 56
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Testing a Function
.
Knowledge Based
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1 58
Chapter 1 2 I C a nd C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 58 Classes and Inheritance.
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 58
Constructors and Destructors Virtual Functions
.
Default Values .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1 59
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 61 .
Poin ters and References .
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 60
Operator Overloading.
Templates
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 59 .
Virtual Destructor
.
.
.
.
.
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 61 .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1 62
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1 63
Chapter 1 3 I Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 65 .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1 65
Overloading vs. Overriding
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1 65
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1 66
How to Approach
.
.
.
.
.
.
Collection Framework .
.
.
.
Chapter 1 4 I Data bases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 69 .
.
.
Denormalized vs. Normalized Databases .
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 69
SOL Syntax and Variations
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1 69
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1 69
Small Database Design
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1 71
Large Database Design
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1 72
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. .
.
SOL Statements
.
.
Chapter 15 \Threads and Locks .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. .
.
.
.
.
.
.
1 74
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. .
.
.
.
.
.
. .
.
.
.
.
.
.
.
.
.
.
.
.
.
..
.
.
.
.
.
.
.
.
.
.
1 74
Synchronization and Locks
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1 76
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
1 79
Th reads in Java
.
.
Deadlocks and Deadlock Prevention
CrackingTheCodinglnterview.com I 6th Edition
I
IX
Introduction Additional Review Problems
.
.
.
.
.
.
.
.
Chapter 1 6 \ Moderate . . . . . . . . . . .
.
.
Solutions
.
.
.
.
.
.
.
.
.
.
.
.
.
Data Structures . . . . . . . . . .
.
.
.
.
.
.
. .
.
.
.
.
.
.
.
.
.
.
.
. ·
. . . .
Chapter 17 \ Hard . . . . . . . . . . . . . . . . . X.
.
.
.
.
. . . . .
.
.
.
.
.
.
. .
.
. . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. . •
.
.
.
. . .
.
.
. . . . . . .
.
. . . . •
.
.
.
•
.
.
. . . . 192
.
. . . . . . .
Advanced Topics
.
.
.
.
.
.
.
.
Useful Math . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
. . . . . . . .
.
.
.
.
.
. . . .
.
.
.
.
.
.
.
.
. . . . . 18 1
.
. •
. .
.
.
.
•
.
.
.
.
•
.
.
.
•
•
1 91
. . . . . . 276
. . . . . . . . . . . . 422
. . . . . . . .
1 81
.
Additional Review Problems . . . . . . . . . . . . . .
. . . . .
.
•
. .
.
.
. . . . . . . . .
.
.
. . . . 1 86
.
. . . . . . . .
.
.
. . . . . .
.
.
. .
. . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
Knowledge Based . . . . . . . . . . . . . .
.
.
•
.
.
.
.
.
.
. . . . . . . . . . . . . .
. . . .
.
.
.
.
.
.
.
. . .
. . . . . . . . . . . .
Concepts a nd Algorithms . . . . . . . . . . . . . . . .
XI.
.
.
.
.
. . . . . . . . . . . . . . . . . . .
.
.
.
. . . . . . 462
.
.
.
.
.
.
.
. . . . . . 629
.
.
.
.
628
Topolog ica l Sort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 632 Dijkstra's Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 633 Hash Table Collision Resolution
.
Rabin-Ka rp Substri ng Search . . . .
AVL Trees .
.
. .
.
.
Red-Black Trees .
.
.
. . . . . . . .
. . . . . . . . . . . . . . . . . .
.
.
.
. . . . .
. .
. . . . . . . . . . . . . .
.
.
.
.
.
. . . . . . . . .
.
. .
.
. . . . 636
. . . . . . . . . .
.
. . . . 636
. . . . . . . . . .
. . . . . . . . .
. . . . . . . . . . . .
.
.
.
MapReduce . . . . . . . . . . . . .
.
. . . . . . .
.
. . .
.
. . . . . . . . . . . . . . .
. . . . .
. .
.
.
. . . . . .
.
. .
.
.
.
. .
.
.
. . . . . .
. . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . .
. .
.
.
.
. 637
. . 639
. . . . . . . . 6 42
Additiona l Studying . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 44 .
.
.
HashMaplist . .
.
. . . . . . . . . . . .
XII. Code Library
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. . . . . . .
.
. . . . . . . . . . . . . . . . . . . . .
.
.
. . . . 6 46
645
Tree Node (Binary Sea rch Tree) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 47 .
.
. . . .
. . . . . . .
.
. . . . . . . .
.
.
LinkedlistNode (Li nked List) .
Trie & TrieNode . XIII. Hints
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6 49 .
.
.
.
.
. . . . . . . . .
.
.
.
.
Hints for Data Structu res . . . . . . . . . . . . . . . . . . . Hints for Concepts and Alg_o rithms . . . . . .
Hints for Knowledge-Based Questions . .
.
.
. . . . .
.
.
.
.
.
.
.
.
. . . .
.
.
.
.
. . . . .
.
.
.
.
.
.
. . . . . . .
.
.
.
.
.
.
.
. . . . 6 49
.
.
.
.
.
.
.
.
. . . . . . . . . . . . . . . . . . . . . . 653
652
.
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 662
.
. . . .
.
. . . . . . . .
.
. . . . . . . . . . . . . . . . . . . . . 676
Hints for Additional Review Problems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 679 XIV. About the Author
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
Join us at www.CrackingTheCodinglnterview.com to download the complete solutions,
contribute or view solutions in other programming languages, discuss problems from this book
with other readers, ask questions, report issues, view this book's errata, and seek additional advice.
X
Cracking the Coding Interview, 6th Edition
.
696
Foreword Dear Reader,
Let's get the introductions out of the way. I am not a recruiter. I a m a software engineer. And a s such , I know what it's like to be asked to whip up bril liant algorithms on the spot and then write flawless code on a whiteboard. I know because I've been asked to do the same thing-in i nterviews at Google, Microsoft, Apple, and Amazon, among other com panies. I also know because I've been on the other :side of the table, asking candidates to do th is. I've com bed through stacks of resumes to find the engineers who I thought might be a ble to actually pass these i nter views. I've eva luated them as they solved-or tried to solve-cha llenging questions. And I've debated in Goog le's H iring Committee whether a ca ndidate did wel l enough to merit an offer. I u ndersta nd the fu l l hiring circle because I've been through i t all, repeatedly.
And you, reader, a re probably preparing for an interview, perhaps tomorrow, next week, or next year. I am here to help you solidify your understanding of com puter science fu ndamentals and then learn how to a pply those fu nda menta ls to crack the coding interview. The 6th edition of Cracking the Coding Interview u pdates the 5th edition with 70% more content: additional questions, revised solutions, new cha pter introductions, more a lgorithm strategies, hi nts for all problems, and other content. Be sure to check out our website, CrackingTheCodinglnterview.com, to con nect with other candidates a nd discover new resou rces. I'm excited for you and for the skills you are going to develop. Thorough preparation will give you a wide range of technical and com munication ski lls. It will be well worth it, no matter where the effort ta kes you ! I encourage you t o read these introductory cha pters ca refu lly. They contai n im portant insight that just might make the difference between a "hire" and a "no h i re:' And remember-interviews are hard! In my yea rs of interviewing at Google, I saw some interviewers ask "easy" questions while others ask harder questions. But you know what? Getting the easy questions doesn't make it any easier to get the offer. Receiving an offer is not a bout solving questions flawlessly (very few ca ndidates do!). Rather, it is about answering questions better than other candidates. So don't stress out when you get a tricky question-everyone else probably thought it was hard too. It's okay to not be flaw less.
Study hard, practice-and good luck! Gayle L. McDowell Fou nder/CEO, CareerCup.com Author of Cracking the PM Interview and Cracking the Tech Career
CrackingTheCodingl nterview.com I 6th Edition
Introduction Something's Wrong
We walked out of the hiring meeting frustrated-again. Of the ten candidates we reviewed that day, none wou ld receive offers. Were we being too harsh, we wondered? I, in particu lar, was disappoi nted. We had rejected one of my candidates. A former student. One I had referred. He had a 3.73 GPA from the U niversity of Washington, one of the best computer science schools in the world, and had done extensive work on open-sou rce projects. He was energetic. He was creative. He was sha rp. He worked hard. He was a true geek in all the best ways. But I had to agree with the rest of the committee: the data wasn't there. Even if my emphatic recommenda tion cou ld sway them to reconsider, he would surely get rejected in the later stages of the hiring process. There were just too many red flags. Although he was quite intelligent, he struggled to solve the interview problems. Most successfu l candi dates cou ld fly through the first question, which was a twist on a well-known problem, but he had trou ble developing an algorithm. When he came up with one, he fai led to consider solutions that optim ized for other scenarios. Finally, when he began coding, he flew through the code with an initial solution, but it was riddled with mistakes that he failed to catch. Though he wasn't the worst candidate we'd seen by any measu re, he was fa r from meeting the "bar:' Rejected. When he asked for feed back over the phone a couple of weeks later, I struggled with what to tel l him. Be smarter? No, I knew he was brilliant. Be a better coder? No, his skills were on par with some of the best I'd seen. Like many motivated candidates, he had prepared extensively. He had read K&R's classic C book, and he'd reviewed CLRS' fa mous a lgorithms textbook. He cou ld describe in detail the myriad of ways of balancing a tree, and he cou ld do things in C that no sane programmer should ever want to do. I had to tell him the unfortunate truth: those books aren't enough. Academic books prepare you for fa ncy resea rch, and they will probably make you a better softwa re engineer, but they're not sufficient for inter views. Why? I'll give you a hint: You r interviewers haven't seen red-black trees since they were in school either. 1o
crack the cod ing interview, you need to prepare with real interview questions. You must practice on problems and learn their patterns. It's about developing a fresh algorithm, not memorizing existing problems.
real
Cracking the Coding Interview is the
result of my first-hand experience interviewing at top companies and later coaching candidates through these interviews. It is the result of hundreds of conversations with candi dates. It is the result of the thousands of questions contributed by cand idates and interviewers. And it's the result of seeing so many interview questions from so many fi rms. Enclosed in this book a re 189 of the best interview questions, selected from thousands of potential problems. My Approach
The focus of Cracking the Coding Interview is algorithm, cod ing, and design questions. Why? Because while you can and will be asked behavioral questions, the answers will be as varied as you r resu me. Like wise, while many firms will ask so-called "trivia" questions (e.g., "What is a virtua l fu nction?"), the skills devel oped through practicing these questions are limited to very specific bits of knowledge. The book will briefly touch on some of these questions to show you what they're like, but I have chosen to al locate space to a reas where there's more to learn.
2
Cracki ng the Coding Interview, 6th Edition
Introduction My Passion
Teaching is my passion. I love helping people understand new concepts and giving them tools to help them excel in their passions. My fi rst official experience teaching was in college at the University of Pennsylvania, when I became a teaching assistant for an undergraduate computer science course during my second year. I went on to TA for several other courses, and I eventually launched my own computer science cou rse there, focused on hands-on skills. As an engi neer at Google, training and mentoring new engineers were some of the thi ngs I enjoyed most. I even used my "20% time" to teach two computer science cou rses at the U niversity of Washington. Now, years later, I conti nue to teach computer science concepts, but this time with the goa l of preparing engineers at startups for their acquisition interviews. I've seen their mistakes and struggles, and I've devel oped tech niques and strategies to help them combat those very issues. Cracking the Coding Interview, Cracking the PM Interview, Cracking the Tech Career,
and CareerCup reflect my passion for teaching. Even now, you ca n often find me "hanging out" at Ca reerCup.com, helping users who stop by for assistance. Join us. Gayle L. McDowell
CrackingTheCodinglnterview.com [6th Edition
3
I The Interview Process
At most of the top tech companies (and many other companies), a lgorithm and cod i ng problems form the la rgest com ponent of the interview process. Think of these as problem-solving questions. The interviewer is looking to eva luate your ability to solve a lgorithmic problems you haven't seen before. Very often, you might get through only one question in an interview. Forty-five minutes is not a long ti me, and it's difficult to get through severa l different questions in that time fra me. You should do you r best to ta l k out loud throughout the problem and explain you r thought process. You r interviewer might j u m p i n sometimes to help you; let them. It's normal a n d doesn't rea lly mean that you 're doing poorly. (That said, of cou rse not needing hi nts is even better.) At the end of the interview, the interviewer wi ll walk away with a gut feel for how you did. A nu meric score might be assig ned to you r performance, but it's not actually a qua ntitative assessment. There's no chart that says how many poi nts you get for different thi ngs. It just doesn't work like that. Rather, you r interviewer will make an assessment of you r performance, usually based on the following: Ana lytical skills: Did you need much help solving the problem? How opti mal was you r solution? How long did it take you to a rrive at a solution? If you had to desig n/architect a new solution, did you struc ture the problem wel l and think through the tradeoffs of different decisions? Cod ing skil ls: Were you able to successfu lly tra nslate your algorithm to reasonable code? Was it clea n and well-organized? Did you think a bout potential errors? Did you use good style? Technical knowledge I Computer Science fu nda mentals: Do you have a strong fou ndation in computer science and the releva nt tech nologies? Experience: Have you made good technical decisions in the past? Have you built interesting, challenging projects? Have you shown d rive, initiative, and other im porta nt factors? Cu ltu re fit I Communication skills: Do you r personal ity and values fit with the company and tea m? Did you commun icate wel l with you r i nterviewer? The weighting of these a reas will va ry based on the question, interviewer, role, team, and com pany. In a standard a lgorithm question, it might be almost entirely the first three of those. �Why?
This is one of the most common questions cand idates have as they get sta rted with this process. Why do things this way? After a l l, 1 . Lots of g reat cand idates don't do well in these sorts of interviews.
4
Cracking the Coding Interview, 6th Edition
1
I
The Interview Process
2. You could look up the a nswer if it did ever come up. 3. You rarely have to use data structu res such as binary search trees in the rea l world. If you did need to,
you cou ld surely learn it. 4. Whiteboard cod ing is an artificial environment. You would never code on the whiteboard in the rea l world, obviously. These complai nts aren't without merit. In fact, I agree with all of them, at least in part. At the same time, there is reason to do things this way for some-not all-positions. It's not i m portant that you agree with this logic, but it is a good idea to u nderstand why these questions a re being asked. It helps offer a little i nsight into the interviewer's mindset. False negatives are acceptable.
This is sad (and frustrating for candidates), but true. From the compa ny's perspective, it's actually acceptable that some good candidates are rejected. The company is out to build a great set of employees. They can accept that they miss out on some good people. They'd prefer not to, of course, as it ra ises their recruiting costs. It is an acceptable tradeoff, though, provided they can stil l hire enough good people. They're far more concerned with false positives: people who do wel l in an interview but are not in fact very good. Problem-solving skills are valuable.
If you're able to work through several hard problems (with some help, perhaps), you're probably pretty good at developing optimal algorithms. You're smart. Smart people tend to do good things, and that's valuable at a company. It's not the only thing that matters, of course, but it is a rea lly good thi ng. Basic data structure and algorithm knowledge is useful.
Many i nterviewers wou ld a rgue that basic computer science knowledge is, in fact, useful. Understanding trees, graphs, lists, sorting, and other knowledge does come up period ical ly. When it does, it's really valu able. Cou ld you learn it as needed? Sure. But it's very difficu lt to know that you should use a binary search tree if you don't know of its existence. And if you do know of its existence, then you pretty much know the basics. Other interviewers ju stify the reliance on data structu res and a lgorithms by arguing that it's a good "proxy:' Even if the skills would n't be that hard to learn on their own, they say it's reasonably well-correlated with being a good developer. It means that you've either gone through a computer science program (in which case you've learned and retained a reasonably broad set of technical knowledge) or learned this stuff on your own. Either way, it's a good sign. There's a nother reason why data structu re and a lgorithm knowledge comes up: because it's hard to ask pro b lem-solvi ng questions that don't involve them. It turns out that the vast majority of problem-solving questions involve some of these basics. When enough candidates know these basics, it's easy to get into a pattern of asking questions with them.
CrackingTheCodingl nterview.com I 6th Edition
S
1
I
The Interview Process
Whiteboards let you focus on what matters.
It's absolutely true that you'd struggle with writing perfect code on a wh iteboa rd. Fortu nately, you r inter viewer doesn't expect that. Virtu ally everyone has some bugs or m i nor syntactical errors. The nice thing about .a wh iteboard is that in some ways, you can focus on the big pictu re. You don't have a com piler. so you don't need to make you r code compile. You don't need to write the entire class definition and boilerplate code. You get to focus on the interesting, "meaty" pa rts of the code: the fu nction that the question is really all about. That's not to say that you should just write pseudocode or that correctness doesn't matter. Most inter viewers aren't okay with pseudocode, and fewer errors a re better. Whiteboards also tend to encou rage candidates to speak more and explain their thought process. When a candidate is given a computer, their communication d rops su bsta ntial ly. But it's not for everyone or every company or every situation.
The a bove sections are intended to help you u nderstand the thought process of the compa ny. My personal thoughts? For the right situation, when done well, it's a reasonable judge of someone's problem-solving skills, in that people who do wel l tend to be fa irly sma rt. However, it's often not done very well. You have bad interviewers or people who just ask bad questions. It's also not appropriate for all com panies. Some com panies should value someone's prior experience more or need skills with particu lar technologies. These sorts of questions don't put much weight on that. It a lso won't measure someone's work ethic or ability to focus. Then again, almost no interview process can really evaluate this. Th is is not a perfect process by any mea ns, but what is? All interview processes have their downsides. 111 leave you with this: it is what it is, so let's do the best we ca n with it. � How Questions a re Selected
Candidates frequently ask what the "recent" interview questions are at a specific company. Just asking this question reveals a fu ndamental misu ndersta nding of where questions come from. At the vast majority of com panies, there are no lists of what interviewers should ask. Rather, each inter viewer selects their own questions. Si nce it's somewhat of a "free for all" as fa r as questions, there's nothing that makes a qu estion a "recent Google interview question" other than the fact that some interviewer who ha ppens to work at Goog le just so happened to ask that question recently. The questions asked this year at Goog le do not rea lly differ from those asked th ree years ago. In fact, the questions asked at Google generally don't differ from those asked at similar companies (Amazon, Face book, etc.). There a re some broad d ifferences across com pan ies. Some com panies focus on algorithms (often with some system design worked in), and others really like knowledge-based questions. But within a given category of question, there is little that makes it "belong" to one company instead of another. A Google a lgorithm question is essentially the same as a Facebook algorithm question.
6
Cracking the Coding Interview, 6th Edition
1
I
The Interview Process
� I t's All Relative
If there's no grading system, how are you evaluated? How does an interviewer know what to expect of you? Good question. The answer actually makes a lot of sense once you understand it. Interviewers assess you relative to other candidates on that same question by the same interviewer. It's a relative com parison. For example, suppose you came up with some cool new brai nteaser or math problem. You ask your friend Alex the question, and it ta kes him 30 minutes to solve it. You ask Bella and she takes 50 minutes. Chris is never able to solve it. Dexter ta kes 1 5 minutes, but you had to give him some major hints and he probably would have taken fa r longer without them. Ellie ta kes 1 0-and comes up with an alternate approach you weren't even aware of. Fred ta kes 35 minutes. You11 wa lk away saying, "Wow, Ellie did really well. 111 bet she's pretty good at math:' (Of cou rse, she could have just gotten lucky. And maybe Chris got unlucky. You might ask a few more questions just to really make sure that it wasn't good or bad luck.} Interview questions are much the same way. You r interviewer develops a feel for you r performance by comparing you to other people. It's not about the candidates she's interviewing that week. It's about all the candidates that she's ever asked this question to. For this reason, getting a hard question isn't a bad thing. When it's harder for you, it's harder for everyone. It doesn't make it any less likely that you'l l do well. � Frequently Asked Questions I didn't hear back immediately after my interview. Am I rejected?
No. There a re a number of reasons why a company's decision might be delayed. A very simple explanation is that one of you r interviewers hasn't provided their feed back yet. Very, very few companies have a policy of not responding to candidates they reject. If you haven't heard back from a company within 3 - 5 business days after your interview, check in (politely} with you r recruiter. Can I re-apply to a company after getting rejected ?
Almost always, but you typically have to wait a bit (6 months to a 1 year}. You r fi rst bad interview usually won't affect you too much when you re-i nterview. Lots of people get rejected from Goog le or Microsoft and later get offers from them.
CrackingTheCodinglnterview.com J 6th Edition
7
II Behi nd the Scenes
Most com panies conduct their interviews in very similar ways. We will offer an overview of how com panies interview and what they're looking for. Th is information should guide your interview prepa ration and your reactions during and after the interview. Once you a re selected for an interview, you usually go through a screening interview. This is typica lly conducted overthe phone. College candidates who attend top schools may have these interviews in-person. Don't let the name fool you; the "screening" interview often involves coding and a lgorithms questions, and the bar can be just as high as it is for in-person i nterviews. If you're unsure whether or not the interview will be technical, ask your recru iting coordi nator what position you r interviewer holds (or what the interview might cover). An engineer will usually perform a technical interview. Many companies have taken advantage of online synchronized document editors, but others will expect you to write code on paper and read it back over the phone. Some interviewers may even give you "home work" to solve after you hang u p the phone or just ask you to email them the code you wrote. You typica lly do one or two screening interviewers before being brought on-site. In an on-site interview round, you usually have 3 to 6 in-person i nterviews. One of these is often over lu nch. The lunch interview is usually not technical, and the i nterviewer may not even submit feed back. This is a good person to discuss your interests with and to ask a bout the company culture. Your other interviews will be mostly technical and will involve a com bi nation of coding, a lgorithm, design/a rchitecture, and behav iora l/experience questions. The distri bution of questions between the a bove topics va ries between com panies and even teams due to company priorities, size, and just pure ra ndomness. Interviewers are often given a good deal of freedom i n their interview questions. After you r interview, you r interviewers wi l l provide feed back in some form. I n some companies, you r inter viewers meet together to discuss your performance and come to a decision. In other com panies, inter viewers submit a recommendation to a hiring manager or hiring committee to make a final decision. In some companies, interviewers don't even make the decision; their feed back goes to a hiring committee to make a decision. Most companies get back after a bout a week with next steps (offer, rejection, further interviews, or just an update on the process). Some compa nies respond much sooner (sometimes same day!) and others ta ke much longer. If you have waited more than a week, you should follow u p with your recruiter. If your recruiter does not respond, this does not mean that you are rejected (at least not at any major tech company, and almost any
8
Cracking the Coding Interview, 6th Edition
11
I Behind the Scenes
other com pany). Let me repeat that again: not responding indicates nothing about your status. The inten tion is that a ll recru iters should tel l cand idates once a final decision is made. Delays ca n and do happen. Follow up with your recru iter if you expect a delay, but be respectf u l when you do. Recruiters a re just like you. They get busy and forgetfu l too. � The Microsoft Interview
Mic rosoft wa nts smart people. Geeks. People who are passionate a bout tech nology. You probably won't be tested on the ins and outs of C++ APls, but you will be expected to write code on the board. In a typica l i nterview, you'll show up at Microsoft at some time in the morning and fill out initial paper work. You'l l have a short interview with a recruiter who will give you a sample question. You r recruiter is usually there to prep you, not to grill you on tech nical questions. If you get asked some basic tech nica l questions, it may be because your recruiter wa nts to ease you into the i nterview so that you're less nervous when the "rea l" interview starts. Be nice to you r recru iter. You r recru iter ca n be you r biggest advocate, even pushing to re-interview you if you stum b led on you r fi rst interview. They can fight for you to be h i red-or not! During the day, you'll do four or five i nterviews, often with two d ifferent teams. U n like many companies, where you meet you r interviewers in a conference room, you'll meet with your Microsoft i nterviewers in their office. This is a great time to look around and get a feel for the tea m cultu re. Depending on the tea m, interviewers may or may not share their feedback on you with the rest of the interview loop. When you complete you r i nterviews with a team, you might speak with a hiring manager (often called the "as app'; short for "as appropriate"). If so, that's a great sign! It likely means that you passed the interviews with a particular tea m. It's now down to the hiring manager's decision. You might get a decision that day, or it might be a week. After one week of no word from H R, send a friendly email asking for a status update. If you r recruiter isn't very responsive, it's because she's busy, not because you're being si lently rejected. Definitely Prepare:
"Why do you want to work for Microsoft?" In this question, Microsoft wa nts to see that you're passionate a bout technology. A great a nswer might be, "I've been using Microsoft software as long as I can remember, a nd I'm really impressed at how Microsoft manages to create a product that is universa lly excellent. For example, I've been using Visual Studio recently to learn game prog ramming, a nd its APls a re excellent:' Note how this shows a passion for technology! What's U nique:
You'll only reach the hiring ma nager if you've done well, so if you do, that's a great sign! Additionally, Microsoft tends to give teams more individual control, and the product set is diverse. Experi ences ca n vary su bstantially across Microsoft since different teams look for different things.
CrackingTheCodinglnterview.com J 6th Edition
9
11
I Behind the Scenes
� The Amazon I nt erview
Amazon's recru iting process typica lly begins with a phone screen in which a candidate interviews with a specific tea m. A sma ll portion ofthe time, a candidate may have two or more interviews, which can ind icate either that one of their interviewers wasn't convinced or that they are being considered for a different team or profile. In more unusual cases, such as when a candidate is loca l o r has recently interviewed for a d ifferent position, a candidate may only do one phone screen. The engineer who interviews you will usually ask you to write sim ple code via a shared document editor. They will also often ask a broad set of questions to explore what a reas of technology you're fa miliar with. Next, you fly to Seattle (or whichever office you're interviewing for) for four or five interviews with one or two teams that have selected you based on you r resume and phone interviews. You will have to code on a whiteboa rd, and some interviewers will stress other skil ls. Interviewers a re each assigned a specific a rea to probe and may seem very d ifferent from each other. They can not see the other feed back until they have submitted their own, and they a re discou raged from discussing it until the hiring meeting. The "ba r raiser" interviewer is charged with keeping the interview bar high. They attend special training and will interview candidates outside their group in order to balance out the group itself. If one interview seems significantly harder and different, that's most likely the bar ra iser. This person has both significant experi ence with interviews and veto power in the hiring decision. Remem ber, though: just beca use you seem to be struggling more in this interview doesn't mean you're actually doing worse. You r performance is judged relative to other candidates; it's not evaluated on a simple "percent correct" basis. Once you r interviewers have entered their feed back, they will meet to d iscuss it. They will be the people making the hiring decision. While Amazon's recru iters are usually excel lent at following up with candidates, occasionally there are delays. If you haven't heard from Amazon within a week, we recommend a friendly ema il. Definitely Prepare:
Amazon ca res a lot a bout sca le. Make sure you prepare for scalability questions. You don't need a back ground in distributed systems to answer these questions. See our recommendations in the System Design and Scalability cha pter. Add itiona lly, Amazon tends to ask a lot of questions a bout object-oriented design. Check out the Object Oriented Design chapter for sample questions and suggestions. What's Unique:
The Bar Raiser is brought in from a different team to keep the bar high. You need to impress both this person and the hiring manager. Amazon tends to experiment more with its hiring process than other compan ies do. The process descri bed here is the typical experience, but due to Amazon's experimentation, it's not necessa rily universa l. �
The Google Interview
There are many sca ry rumors floating around about Goog le interviews, but they're mostly just that: rumors. The interview is not terribly different from Microsoft's or Amazon's.
t0
Cracking the Coding Interview, 6th Edition
11
I
B e h i n d the Scenes
A Goog le engineer performs the first phone screen, so expect tough technical questions. These questions may involve coding, sometimes via a shared document. Ca ndidates a re typically held to the same sta ndard and are asked similar q uestions on phone screens as i n on-site interviews. On you r on-site interview, you'll interview with four to six people, one of whom will be il lunch i ntervie wer. Interviewer feed back i s kept confidentia l from the other i nterviewers, so you can be assured that you enter each interview with blank slate. Your lunch interviewer doesn't submit feedback, so this is a great opportu nit y to ask honest q uestions. Interviewers a re typica lly not given specific focuses, and there is no "structure" or "system" as to what you're asked when. Each interviewer can conduct the interview however she would like. Written feed back is subm itted to a hiring comm ittee (HC) of engineers and managers to make a hire I no-hire recommendation. Feedback is typically broken down into four categories (Analytical Ability, Coding, Experience, and Communication) and you are given an overall score from 1 .0 to 4.0. The HC usually does not include any of you r interviewers. If it does, it was purely by random chance. To extend an offer, the HC wants to see at least one i nterviewer who is an "enthusiastic endorser:' In other words, a packet with scores of 3.6, 3. 1 , 3.1 and 2 .6 is better than all 3.1 s. You do not necessarily need to excel in every interview, and you r phone screen performance is usually not a strong factor in the final decision. If the hiring comm ittee recommends an offer, your packet will go to a com pensation committee and then to the executive management committee. Returni ng a decision can take several weeks because there a re so many stages and committees. Definitely Prepare:
As a web-based company, Google cares about how to design a scalable system. So, make sure you prepare for q uestions from System Design and Scalability. Goog le puts a strong focus on analytical (algorithm) skills, regard less of experience. You should be very well prepared for these questions, even if you think your prior experience should count for more. What's Different:
Your i nterviewers do not make the hiring decision. Rather, they enter feedback which is passed to a hiring committee. The hiring committee recommends a decision which can be-though rarely is-rejected by Goog le executives. � The Apple I nterview
Much like the company itself, Apple's interview process has minimal bureaucracy. The i nterviewers will be looking for excel lent technical skills, but a passion for the position and the company is a lso very important. While it's not a prerequisite to be a Mac user, you should at least be familiar with the system. The interview process usually begins with a recruiter phone screen to get a basic sense of you r skills, followed up by a series of technical phone screens with team mem bers. Once you're i nvited on campus, you'll typically be greeted by the recru iter who provides an overview of the process. You will then have 6-8 interviews with members of the team with which you're interviewing, as well as key people with whom you r team works.
CrackingTheCodinglnterview.com j 6th Edition
11
11
I Behind the Scenes
You can expect a mix of one-on-one and two-on-one interviews. Be ready to code on a whiteboard and make sure a l l of your thoughts are clearly communicated. Lunch is with you r potential future manager and appea rs more casual, but it is sti ll an interview. Each i nterviewer usually focuses on a different area and is discou raged from sharing feedback with other interviewers un less there's something they want su bse quent interviewers to drill into. lowa rds the end of the day, your interviewers will compare notes. If everyone still feels you're a viable candi date, you will have an interview with the d i rector and the VP of the organization to which you're applying. While this decision is rather informal, it's a very good sign if you make it. This decision also ha ppens behind the scenes, and if you don't pass, you'll simply be escorted out of the building without ever having been the wiser (until now). If you made it to the director and VP interviews, all of you r interviewers will gather in a conference room to give an official thumbs up or thumbs down. The VP typica lly won't be present but can sti ll veto the hire if they weren't impressed. Your recruiter will usually follow up a few days later, but feel free to ping him or her for updates. Definitely Prepare:
If you know what team you're interviewing with, make sure you read up on that product. What do you like a bout it? What would you improve? Offering specific recommendations ca n show you r passion for the job. What's Unique:
Apple does two-on-one interviews often, but don't get stressed out about them-it's the same as a one-on one interview! Also, Apple employees a re huge Apple fa ns. You should show this same passion in your interview. �
The Facebook Interview
Once selected for an interview, ca ndidates will generally do one or two phone screens. Phone screens will be tech nical and will involve cod ing, usually an on line document ed itor. After the phone interview(s), you might be asked to do a homework assignment that will include a mix of coding and algorithms. Pay attention to your cod ing style here. If you've never worked in an environment which had thorough code reviews, it may be a good idea to get someone who has to review your code. During you r on-site interview, you will interview primarily with other software engineers, but hiring managers are also involved whenever they are available. All interviewers have gone through comprehen sive interview training, and who you interview with has no bearing on you r odds of getting an offer. Each interviewer is given a "role" during the on-site interviews, which helps ensure that there are no repeti tive questions and that they get a holistic pictu re of a candidate. These roles are: Behavioral ("Jedi"): Th is interview assesses you r abil ity to be successfu l in Facebook's environment. Would you fit well with the cu ltu re and values? What a re you excited a bout? How do you tackle chal lenges? Be prepa red to ta lk about your interest in Facebook as well. Facebook wants passionate people. You might a lso be asked some cod ing questions in this interview. Coding and Algorithms ("Ninja"): These are your sta ndard cod ing and a lgorithms questions, much like what you'll find i n this book. These questions are designed to be challenging. You can use any program ming language you want.
12
Cracking the Coding Interview, 6th Edition
11
I Behind the Scenes
Design/Architecture ("Pirate"): For a backend software engineer, you might be asked system design questions. Front-end or other specialties will be asked desig n questions related to that discipline. You should openly discuss different solutions and their tradeoffs. You can typica lly expect two "ninja" interviews and one "jedi" interview. Experienced candidates will a lso usually get a "pi rate" interview. After your interview, interviewers submit written feedback, prior to discussing your performa nce with each other. This ensures that your performance in one i nterview will not bias a nother interviewer's feed back. Once everyone's feedback is submitted, your interviewing team and a hiring manager get together to colla borate on a final decision. They come to a consensus decision and submit a final h i re recommendation to the hiring comm ittee. Definitely Prepare:
The you ngest of the "elite" tech companies, Facebook wa nts developers with an entrepreneurial spi rit. I n your interviews, you should show that you love t o build stuff fast. They want to know you can hack together a n elegant and sca lable solution using a ny language of choice. Knowi ng PHP is not especially important. particula rly given that Facebook a lso does a lot of backend work in C++, Python, Erla ng, and other languages. What's Unique:
Facebook i nterviews developers for the com pany "i n genera l;' not for a specific team. If you a re h i red, you will go through a six-week "bootcam p" which will help ram p you up in the massive code base. You'll get mentorship from senior devs, learn best practices, a nd, ultimately, get a greater flexibility in choosing a project than if you were assigned to a project in your interview. � The Palantir I n terview
Unlike some companies which do "pooled" interviews (where you interview with the company a s a whole, not with a specific team), Palantir interviews for a specific team. Occasionally, your appl ication might be re-routed to another team where there is a better fit. The Pa la nti r i nterview process typica lly starts with two phone interviews. These i nterviews a re a bout 30 to 45 m i n utes and will be primarily technical. Expect to cover a bit a bout you r prior experience, with a heavy focus on algorithm questions. You might also be sent a HackerRa nk cod ing assessment. which will eva luate your ability to write optimal algorithms a nd correct code. Less experienced candidates, such as those in college, a re particularly likely to get such a test. After this, successful candidates are invited to campus and will i nterview with up to five people. Onsite interviews cover you r prior experience, relevant domain knowledge, data structures and algorithms, and design. You may also l i kely get a demo of Pa lantir's products. Ask good questions and demonstrate you r passion for the company. After the interview, the i nterviewers meet to d iscuss your feedback with the hiring manager.
CrackingTheCodingl nterview.com I 6th Edition
13
II
I Behind the Scenes
Definitely Prepare:
Pa lantir values hiring brilliant engineers. Many candidates report that Palantir's questions were harder than those they saw at Google and other top compan ies. This doesn't necessa rily mean it's harder to get an offer (although it certai n ly can); it just means interviewers prefer more challenging questions. If you're inter viewing with Palantir, you should lea rn your core data structu res and algorithms inside and out. Then, focus on prepa ring with the hardest algorithm questions. Brush up on system design too if you're interviewing for a backend role. This is a n im porta nt part of the process. What's Unique:
A cod ing chal lenge is a common part of Pa lantir's process. Although you'l l be at your computer and can look up material as needed, don't wa l k into this unprepared. The questions can be extremely challenging and the efficiency of your algorithm will be evaluated. Thorough interview preparation will help you here. You can also practice cod ing challenges online at HackerRa n k.com.
14
Cracking the Coding I nterview, 6th Edition
Ill Special S ituations
There are many paths that lead someone to this book. Perhaps you have more experience but have never done this sort of interview. Perhaps you're a tester or a PM. Or perhaps you're actually using this book to teach yourself how to interview better. Here's a little something for all these "special situations." � Experienced Candidates
Some people assume that the a lgorithm-style questions you see in this book are only for recent g rads. That's not enti rely true. More experienced engi neers might see slightly less focus on algorithm questions-but only slightly If a
company asks algorithm questions to inexperienced candidates, they tend to ask them to experienced candidates too. Rightly or wrong ly, they feel that the skills demonstrated in these q uestions are important for a l l developers. Some i nterviewers might hold experience candidates to a somewhat lower standard. After a l l, it's been years si nce these ca ndidates took an a lgorithms class. They're out of practice. Others though hold experienced candidates to a hig her standard, reasoning that the more years of experi ence al low a candidate to have seen many more types of problems. On average, it balances out. The exception to this rule is system design and a rchitecture questions, as well as questions a bout your resume. Typically, students don't study much system architecture, so experience with such chal lenges would only come professionally. Your performance in such interview questions would be evaluated with respect to your experience level. However, students and recent g raduates are sti l l asked these questions and should be prepared to solve them as well as they can. Additional ly, experienced candidates will be expected to g ive a more in-depth, impressive response to questions l i ke, "What was the hardest bug you've faced?"You have more experience, and you r response to these questions should show it. � Testers and SOETs
SDETs (software design engi neers in test) write code, but to test features instead of build features. As such, they have to be great coders and great testers. Double the prep work! If you're a pplying for an SDET role, take the fol lowi ng approach:
CrackingTheCodinglnterview.com I 6th Edition
1S
111
I
Special Situations
Prepare the Core Testing Problems: For exa mple,
how wou ld you test a l ight bulb? A pen? A cash register? Microsoft Word? The Testi ng cha pter wi l l give you more background on these problems.
Practice the Coding Questions: The
number one thing that SDETs get rejected for is cod ing skil ls. Although cod ing sta ndards a re typically lower for an SDET tha n for a traditional developer, SDETs are sti l l expected to be very strong in cod ing and algorithms. Make sure that you practice solving all the same cod ing and algorithm questions that a regular developer wou ld get.
•
Practice Testing the Coding Questions: A very popu lar format for SDET questions is "Write code to do X," followed up by, "Okay, now test it:' Even when the question doesn't specifica lly require this, you should ask you rself, "How wou ld I test this?" Remember: any problem can be an SDET problem!
Strong communication skills can also be very im porta nt for testers, since you r job requ ires you to work with so many d ifferent people. Do not neglect the Behaviora l Questions section. Career Advice
Finally, a word of career advice: If, like many candidates, you are hoping to a pply to an SDET position as the "easy" way into a compa ny, be aware that many candidates find it very d ifficult to move from an SDET posi tion to a dev position. Make sure to keep your coding and a lgorithms skills very sharp if you hope to make this move, and try to switch within one to two yea rs. Otherwise, you m ight find it very d ifficult to be taken seriously in a dev interview. Never let you r codi ng skills atrophy. � Prod uct (and Progra m) Management
These "PM" roles va ry wildly across companies and even with in a compa ny. At Microsoft, for instance, some PMs may be essentially customer evangelists, working in a customer-fa cing role that borders on ma rketing. Across campus though, other PMs may spend much of their day coding. The latter type of PMs wou ld likely be tested on codi ng, since this is an im porta nt part of their job fu nction. Generally spea ki ng, interviewers for PM positions are looking for ca ndidates to demonstrate skills in the fol lowing a reas: Handling Ambig uity: This is typica lly not the most critical area for an interview, but you should be awa re that interviewers do look for skill here. Interviewers want to see that, when faced with an ambiguous situation, you don't get overwhelmed and sta ll. They wa nt to see you tackle the problem head on: seeking new information, prioritizing the most importa nt pa rts, and solving the problem in a structu red way. This typically will not be tested directly (though it can be), but it may be one of many thi ngs the interviewer is looking for in a problem. Customer Focus (Attitude): I nterviewers want to see that your attitude is customer-focused. Do you assume that everyone will use the product just like you do? Or are you the type of person who puts himself in the customer's shoes and tries to understand how they want to use the prod uct? Questions like "Design an alarm clock for the blind" are ripe for examining this aspect. When you hear a question like this, be sure to ask a lot of questions to understa nd who the customer is and how they are using the prod uct. The skills covered in the Testing section are closely related to this. •
Customer Focus (Technical Skills): Some teams with
more complex products need to ensu re that their PMs wa lk in with a strong understa nding of the prod uct, as it wou ld be d ifficult to acqu ire this knowledge on the job. Deep tech nical knowledge of mobile phones is probably not necessa ry to work on the Android or Windows Phone teams (although it might still be n ice to have), whereas an understa nding of security might be necessa ry to work on Windows Secu rity. Hopefu l ly, you wou ldn't interview with a tea m that
16
Cracking the Coding I nterview, 6th Edition
111
l Special Situations
req uired specific technical skills unless you a t least claim t o possess t h e req uisite ski lls. •
Mu lti Level Communication:
•
Passion for Technology: Happy employees a re productive employees, so a company wants to make sure that you'll enjoy the job and be excited about your work. A passion for technology-and, ideally, the com pany or team-should come across in you r a nswers. You may be asked a question d i rectly like, "Why a re you interested in Microsoft?" Additionally, your interviewers will look for enthusiasm in how you discuss your prior experience and how you discuss the team's challenges. They want to see that you will be eager to face the job's challenges.
•
-
PMs need to be a ble to communicate with people at all levels in the company, across many positions and ra nges of technical skills. Your interviewer will want to see that you possess this flexibility in you r com munication. This is often exami ned di rectly, through a question such as, "Explain TCP/I P to your grand mother:' You r commun ication skills may a lso be assessed by how you d iscuss your prior projects.
Teamwork I Leadership:
This may be the most im porta nt aspect of the interview, and-not surpris ingly-the job itself. All interviewers will be looking for you r ability to work well with other people. Most common ly, this is assessed with questions like, "Tell me about a time when a teammate wasn't pulling his I her own weig ht:' Your interviewer is looking to see that you handle conflicts well, that you take in itiative, that you understa nd people, and that people like working with you. Your work preparing for behavioral questions will be extremely im porta nt here.
All of the a bove a reas are im porta nt skills for PMs to master and are therefore key focus areas of the inter view. The weighting of each of these a reas will roughly match the im portance that the area holds in the actual job. � Dev Lead and Managers
Strong coding skills a re a lmost a lways required for dev lead positions and often for management positions as well. If you'll be cod ing on the job, make sure to be very strong with coding and algorithms-just like a dev would be. Goog le, in particular, holds ma nagers to high sta ndards when it comes to cod ing. I n add ition, prepare to be examined for skills in the following a reas: Teamwork I Leadership: Anyone in a ma nagement-like role needs to be able to both lead and work with people. You will be examined implicitly and explicitly in these areas. Explicit eva luation will come in the form of asking you how you hand led prior situations, such as when you disagreed with a manager. The implicit eva luation comes in the form of you r interviewers watching how you interact with them. If you come off as too arroga nt or too passive, your interviewer may feel you a ren't great as a manager. Prioritization: Managers a re often faced with tricky issues, such as how to make sure a team meets a tough deadline. Your interviewers will want to see that you can prioritize a project appropriately, cutti ng the less im porta nt aspects. Prioritization means asking the right q uestions to understa nd what is critical and what you can reasonably expect to accomplish. •
Communication: Managers need to commun icate with people both a bove and below them, and poten tially with customers and other much less techn ical people. Interviewers will look to see that you can communicate at many levels and that you can do so in a way that is friendly and engaging. This is, in some ways, a n eva l uation of your personality. "Getting 1hings Done": Perhaps the most importa nt thing that a manager can do is be a person who"gets things done:'This means stri king the right ba lance between preparing for a project and actually im ple menting it. You need to understa nd how to structure a project and how to motivate people so you can accomplish the team's goals.
CrackingTheCodingl nterview.com I 6th Edition
17
111
\ Special Situations
Ultimately, most of these areas come back to you r prior experience and you r personality. Be sure to prepa re ve ry, very thoroughly using the interview prepa ration grid. � S tartups
The appl ication and interview process for sta rtu ps is highly va riable. We ca n't go through every sta rtup, but we can offer some genera l poi nters. Understand, however, that the process at a specific sta rtu p mig ht deviate from this. The Application Process
Many sta rtups might post job listings, but for the hottest sta rtu ps, often the best way in is through a personal referral. This reference doesn't necessarily need to be a close friend or a coworker. Often just by reaching out and expressing your interest, you can get someone to pick up you r resume to see if you're a good fit. Visas and Work Authorization
U nfortunately, many smaller sta rtups in the U.S. are not able to sponsor work visas. They hate the system as much you do, but you won't be able to convince them to hire you a nyway. If you require a visa and wish to work at a startup, you r best bet is to reach out to a professional recruiter who works with many sta rtups (and may have a better idea of which sta rtups will work with visa issues), o r to focus your search on bigger sta rtups. Resume Selection Factors
Sta rtups tend to want engineers who are not only smart and who ca n code, but a lso people who would work wel l in an entrepreneurial environ ment. Your resume should idea l ly show initiative. What sort of proj ects have you started? Being able to "hit the ground ru nning" is a lso very i m porta nt; they want people who already know the la nguage of the compa ny. The Interview Process
In contrast to big companies, which tend to look mostly at you r general a ptitude with respect to software development, startups often look closely at you r personality fit, ski ll set, and prior experience. Personality Fit: Personality fit is typica lly assessed by how you interact with you r interviewer. Establishing a friend ly, engaging conversation with your interviewers is you r ticket to many job offers.
Beca use sta rtups need people who can hit the ground ru nning, they are likely to assess your skills with specific programming languages. If you know a language that the startup works with, make sure to brush up on the details.
Skill Set:
Exp erience: Startu ps are likely to ask you a lot of questions about you r experience. Pay special attention to the Behavioral Questions section.
In add ition to the above areas, the cod ing and a lgorithms questions that you see in this book are also very common.
18
Cracking the Coding I nterview, 6th Edition
111
I Special Situations
� Acqu isitions and Acquihires
During the techn ical due diligence process for many acqu isitions, the acquirer will often interview most or all of a startu p's employees. Goog le, Yahoo, Facebook, and many other compan ies have this as a standard part of many acqu isitions. Which startups go through this? And why?
Part of the reasoning for this is that their employees had to go through this process to get hired. They don 't want acquisitions to be an "easy way" into the compa ny. And, since the team is a core motivator for the acquisition, they figure it makes sense to assess the skills of the tea m. Not all acquisitions a re like th is, of cou rse. The fa mous multi-billion dollar acqu isitions generally d id not have to go through this process. Those acquisitions, after all, are usually a bout the user base and commu nity, less so a bout the employees or even the technology. Assessing the team's skills is less essentia l. However, it is not as simple as "acquihires get interviewed, trad itional acquisitions do not."There is a big gray a rea between acquihires (i.e., talent acquisitions) and product acquisitions. Many sta rtups a re acquired for the team and ideas behind the tech nology. The acq ui rer might d iscontinue the product, but have the team work on something very similar. If your sta rtu p is going through this process, you can typica lly expect you r team to have interviews very similar to what a normal candidate wou ld experience (and, therefore, very similar to what you'll see in this book). How important are these interviews?
These interviews can ca rry enormous im porta nce. They have three d ifferent roles: • •
•
They can make or break acqu isitions. They a re often the reason a company does not get acquired. They determine which employees receive offers to join the acqu irer. They ca n affect the acqu isition price (in part as a consequence of the number of employees who join).
These interviews a re much more than a mere "screen:' Which employees go through the interviews?
For tech sta rtups, usually all of the engineers go through the interview process, as they are one of the core motivators for the acqu isition. I n add ition, sales, customer support, product managers, and essentially any other role might have to go through it. The CEO is often slotted into a product manager interview or a dev manager interview, as this is often the closest match for the CEO's cu rrent responsibilities. Th is is not an absolute rule, though. It depends on what the CEO's role presently is and what the CEO is interested in. With some of my clients, the CEO has even opted to not interview and to leave the company u pon the acqu isition. What happens to employees who don't perform well in the interview?
Employees who underperform will often not receive offers to join the acquirer. (If many employees don't perform well, then the acqu isition will likely not go through.)
CrackingTheCodinglnterview.com I 6th Edition
19
111
I Special Situations
In some cases, employees who performed poorly in interviews will get contract positions for the pu rpose of "knowledge transfer:' These a re tern pora ry positions with the expectation that the employee leaves at the term ination of the contract (often six months), although sometimes the employee ends up being reta ined. In other cases, the poor performance was a result of the employee being m is-slotted. This occurs i n two common situations: Sometimes a sta rtu p labels someone who is not a "trad itional" software engineer as a software engineer. This often ha ppens with data scientists or database engineers. These people may underperform during the software engineer interviews, as their actual role involves other skills. In other cases, a CEO "sells" a junior software engineer as more senior than he actually is. He underper forms for the senior bar because he's being held to an unfa i rly high standard. I n either case, sometimes the employee will be re-interviewed for a more appropriate position. (Other times though, the employee is just out of luck.) In rare cases, a CEO is able to override the decision for a pa rticu larly strong employee whose interview performance did n't reflect this. Your "best" (and worst} employees might surprise you.
The problem-solving/algorithm interviews conducted at the top tech com pa nies eva luate particular skills, which might not perfectly match what their manager eva luates in their employees. I've worked with many compan ies that a re su rprised at who their strongest and weakest performers a re in interviews. That junior engineer who sti ll has a lot to lea rn a bout profess ional development might tu rn out to be a great problem-solver in these interviews. Don't count anyone out-or in-until you've eva luated them the same way their interviewers will. Are employees held to the same standards as typical candidates?
Essentially yes, although there is a bit more leeway. The big com panies tend to ta ke a risk-averse approach to hiring. If someone is on the fence, they often lean towa rds a no-h ire. I n the case of an acqu isition, the "on the fence" employees can be pulled through by strong performance from the rest of the team. How do employees tend to react to the news of an acquisition/acquihire?
This is a big concern for many sta rtu p CEOs and founders. Will the employees be upset a bout this process? O r, what if we get their hopes up but it doesn't ha ppen? What I've seen with my clients is that the leadership is worried a bout this more than is necessa ry. Certainly, some employees are upset about the process. They might not be excited a bout joining one of the big com panies for any number of reasons. Most employees, though, a re cautiously optimistic a bout the process. They hope it goes through, but they
know that the existence of these interviews means that it might not.
20
Cracking the Coding Interview, 6th Edition
111
I Special Situations
What happens t o t h e team after an acquisition?
Every situation is different. However, most of my clients have been kept together as a tea m, or possibly integrated into an existing team. How should you p repare your team for acquisition interviews?
Interview prep for acqu isition interviews is fa irly similar to typica l interviews at the acquirer. The difference is that you r company is doing this as a team and that each employee wasn't individ ually selected for the interview on their own merits. You're oil in this together.
Some sta rtu ps I've worked with put their "rea l" work on hold and have their teams spend the next two or three weeks on interview prep. Obviously, that's not a choice all companies can ma ke, but-from the perspective of wanting the acquisi tion to go throug h-that does increase your resu lts substantially. Your tea m should study individual ly, in tea ms of two or three, or by doing mock interviews with each other. If possible, use all three of these approaches. Some people may be less prepared than others.
Many developers at sta rtups might have only vaguely heard of big 0 time, binary search tree, breadth-first sea rch, and other im porta nt concepts. They'll need some extra time to prepare. People without computer science degrees (or who ea rned their degrees a long time ago) should focus first on lea rning the core concepts discussed in this book, especia lly big 0 time (which is one of the most important). A good fi rst exercise is to implement a l l the core data structures and a lgorithms from scratch. If the acquisition is im portant to your compa ny, give these people the time they need to prepa re. They'll need it. Don't wait until the lost minute.
As a sta rtup, you might be used to taking things a s they come without a ton of planning. Startups that do this with acquisition interviews tend not to fa re well. Acquisition interviews often come up very suddenly. A company's CEO is chatting with an acquirer (or severa l acquirers) and conversations get increasingly serious. The acquirer mentions the possi bility of inter views at some point i n the future. Then, all of a sudden, there's a "come i n at the end of this week" message. If you wait until there's a firm date set for the interviews, you probably won't get much more than a couple of days to prepare. That might not be enough time for you r engineers to learn core computer science concepts and p ractice interview questions. �
For Interviewers
Since writing the last edition, I've learned that a lot of interviewers a re using Crocking the Coding Interview to learn how to interview. That wasn't rea lly the book's intention, but I might as well offer some guidance for interviews.
CrackingTheCodinglnterview.com I 6th Edition
21
111
I Special Situations
Don't actually ask the exact questions in here. First, these questions were selected beca use t h ey're good for interview preparation. Some questions that are good for interview preparation are not always good for interviewing. For example, there are some brainteasers in this book because sometimes interviewers ask these sorts of questions. It's worthwh ile for candidates to practice those if they're interviewing at a company that l i kes them, even though I personally fi nd them to be bad q uestions.
Second, your candidates are reading this book, too. You don't want to ask questions that your candidates have already solved. You can ask questions similar to these, but don't just pluck questions out of here. Your goal is to test their problem-solving skil ls, not their memorization skil ls. Ask Medium and Hard Problems
The goa l of these questions is to eval uate someone's problem-solving skil ls. When you ask questions that are too easy, performance gets clustered together. Minor issues can su bstantially d rop someone's perfor mance. It's not a reliable indicator. Look for questions with multiple hurdles.
Some questions have "Aha!" moments. They rest on a particu lar insight. If the candidate doesn't get that one bit, then they do poorly. If they get it, then suddenly they've outperformed many candidates. Even if that insight is an indicator of skills, it's stil l only one indicator. Ideally, you want a question that has a series of hurd les, insig hts, or optim izations. Multiple data points beat a single data poi nt. Here's a test: if you can give a hint or piece of guidance that makes a substantial difference in a candidate's performance, then it's probably not a good interview q uestion. Use hard questions, not hard knowledge.
Some interviewers, in an attempt to make a question hard, inadvertently make the knowledge hard. Sure enough, fewer candidates do wel l so the statistics look rig ht, but it's not for reasons that ind icate much a bout the candidates' skil ls. The knowledge you are expecting candidates to have should be fairly straightforward data structu re and a lgorithm knowledge. It's reasonable to expect a com puter science graduate to understand the basics of big 0 and trees. Most won't remember Dij kstra's algorithm or the specifics of how AVL trees works. If your interview question expects obscure knowledge, ask you rself: is this tru ly an i mporta nt ski l l ? Is it so i mportant that I would like to either reduce the number of candidates I hire or reduce the amount to which I focus on problem-solving or other skills? Every new skill or attribute you eval uate shrinks the number of offers extended, un less you counter-bala nce this by relaxing the requirements for a different skill. Sure, a l l else being equal, you might prefer someone who could recite the finer points of a two-inch thick algorithms textbook. But all else isn't equal. Avoid "scary" questions.
Some questions intimidate candidates because it seems like they involve some specialized knowledge, even if they rea lly don't. This often includes questions that i nvolve: Math or pro ba b i lity.
22
Cracking the Cod ing I nterview, 6th Edition
111
I Special Situations
Low-level knowledge (memory allocation, etc.). •
System design or scalability. Proprietary systems (Google Maps, etc.).
For example, one question I sometimes ask is to find all positive integer solutions under 1 ,000 to c3 + d3 (page 68).
a3
+
b3
=
Many candidates will at first think they have to do some sort of fancy factorization of this or semi-advanced math. They don't. They need to understa nd the concept of exponents, sums, and equal ity, and that's it. When I ask this q uestion, I explicitly say, "I know this sounds like a math problem. Don't worry. It's not. It's an a lgorithm question:' If they start going down the path of factorization, I stop them and remind them that it's not a math question. Other questions might involve a bit of proba bility. It might be stuff that a cand idate wou ld surely know (e.g., to pick between five options, pick a ra ndom number between 1 and 5). But simply the fact that it i nvolves probability wil l intimidate candidates. Be ca refu l asking questions that sound intimidating. Remember that this is already a rea lly intimidating situation for candidates. Adding on a "scary" question might just fluster a candidate and cause him to u nderperform. If you're going to ask a question that sounds "sca ry;• make sure you rea lly reassu re candidates that it doesn't require the knowledge that they think it does. Offer positive reinforcement.
Some interviewers put so much focus on the "rig ht" question that they forget to think a bout their own behavior. Many candidates are intimidated by interviewing and try to read into the interviewer's every word. They can cling to each thing that might possibly sound positive or negative. They interpret that little comment of "good luck" to mean somethi ng, even though you say it to everyone regard less of performance. You wa nt candidates to feel good about the experience, a bout you, and about their performa nce. You want them to feel comfortable. A candidate who is nervous will perform poorly, and it doesn't mean that they aren't good. Moreover, a good candidate who has a negative reaction to you or to the company is less likely to accept an offer-and they might dissuade their friends from interviewi ng/accepti ng as well. Try to be warm and friendly to candidates. This is easier for some people than others, but do your best. Even if being wa rm and friendly doesn't come naturally to you, you can sti ll make a concerted effort to sprinkle in positive rema rks throughout the interview: •
"Rig ht, exactly:'
•
"Great point:'
•
"Good work:'
•
"Okay, that's a rea lly interesting approach:' "Perfect:'
No matter how poorly a candidate is doing, there is always something they got rig ht. Find a way to infuse some positivity i nto the interview.
CrackingTheCodinglnterview.com I 6th Edition
23
Ill
\
Special Situati ons
Probe deeper on behavioral questions
.
Many ca ndidates are poor at articu lating their specific accom plishments. You ask them a question about a challenging situation, and they tell you about a difficult situation their team faced. As far as you ca n tell, the candidate did n't really do much. Not so fast, though. A candidate might not focus on themselves because they've been trai ned to celebrate their team's accomplishments and not boast about themselves. This is especially common for people i n leadership roles a n d female candidates. Don't assume that a candidate did n't do much in a situation just because you have trouble understanding what they did. Call out the situation (nicely!). Ask them specifically if they ca n tel l you what their role was. If it did n't rea lly sound like resolving the situation was d ifficu lt, then, again, probe deeper. Ask them to go into more detai Is a bout how they thought a bout the issue and the d ifferent steps they took. Ask them why they took certain actions. Not descri bing the deta ils of the actions they took makes them a flawed candi date, but not necessarily a flawed employee. Being a good interview candidate is its own skill (after a l l, that's part of why this book exists), a nd it's prob ably not one you want to eva luate. Coach your candidates.
Read through the sections on how candidates can develop good algorithms. Many of these tips are ones you can offer to candidates who are strugg l i ng. You're not "teaching to the test" when you do this; you're separating interview skills from job skil ls. Many candidates don't use an example to solve an interview question (or they don't use a good exa mple). This makes it su bsta ntially more difficult to develop a solution, but it does n't necessa rily mea n that they're not very good problem solvers. If candidates don't write a n exam ple themselves, or if they inadvertently write a special case, guide them. Some candidates ta ke a long time to find the bug beca use they use an enormous example. This does n't make them a bad tester or developer. It just means that they did n't rea lize that it would be more efficient to ana lyze their code conceptually first, or that a sma ll exam ple would work nea rly as well. Guide them. If they d ive into code befo re they have a n optimal solution, pull them back and fo cus them on the algo rithm (if that's what you want to see). It's u nfair to say that a candidate never fo und or im plemented the optimal solution if they did n't really have the time to do so. If they get nervous and stuck a nd aren't sure where to go, suggest to them that they wa lk through the brute fo rce solution and look for areas to optim ize. If they haven't said anything and there is a fairly obvious brute force, rem i nd them that they ca n sta rt off with a brute force. Their fi rst solution doesn't have to be perfect. Even if you think that a candidate's ability i n one of these areas is an i mporta nt factor, it's not the only factor. You can always mark someone down for "failing" this h u rdle while helping to guide them past it. While this book is here to coach candidates through i nterviews, one of your goals as an i nterviewer is to remove the effect of not preparing. After all, some ca nd idates have studied for interviews and some candi dates haven't, and this probably doesn't revea l much a bout their skills as an engi neer. Guide ca ndidates using the tips in this book (within reason, of cou rse-you don't want to coach candidates throug h t h e problems so much that you ' re not eva luating their problem-solving skills a nymore).
24
Cracking the Coding Interview, 6th Edition
111
I Special Situations
B e carefu l here, though. I f you're someone who comes off as intimidating t o candidates, this coaching could make things worse. It can come off as your telling candidates that they're constantly messing up by creating bad examples, not prioritizing testing the right way, and so on. If they want silence, give them silence.
One of the most common questions that candidates ask me is how to deal with an interviewer who insists on talking when they just need a moment to think i n silence. If you r candidate needs this, give you r candidate this time to think. Lea rn to d isti nguish between 'Tm stuck and have no idea what to do; and 'Tm thinking in silence:' It might help you to guide your candidate, and it might help many candidates, but it doesn't necessa rily help all candidates. Some need a moment to think. Give them that time, and take into account when you're evaluating them that they got a bit less g u ida nce than others. Know your mode: sanity check, quality, specialist, and proxy.
At a very, very high level, there are four modes of questions: ·
·
Sanity Check: These a re often easy problem-solving or design questions. They assess a minimum degree of com petence in problem-solving. They won't tell distinguish between "okay" versus "great'; so don't evaluate them as such. You can use them early in the process (to filter out the worst candidates), or when you only need a minimum degree of competency. Quality Check: These a re the more challenging questions, often in problem-solving or design. They
are designed to be rigorous and rea lly make a candidate think. Use these when algorithmic/problem solving skills a re of high importance. The biggest mistake people make here is asking questions that are, in fact, bad problem-solving questions. ·
Specialist Questions: These questions test knowledge of specific topics, such as Java or machine
learning. They should be used when for skills a good engineer could n't quickly learn on the job. These questions need to be appropriate for true specialists. Unfortunately. I've seen situations where a company asks a candidate who just completed a 1 0-week cod ing bootcam p deta iled questions a bout Java. What does this show? If she has this knowledge, then she only lea rned it recently and, therefore, it's likely to be easily acquirable. If it's easily acquira ble, then there's no reason to hire for it. Proxy Knowledge: This is knowledge that is not qu ite at the specialist level (in fact, you might not even
need it), but that you would expect a cand idate at their level to know. For example, it might not be very importa nt to you if a candidate knows CSS or HTML. But if a candidate has worked in depth with these tech nologies and ca n't tal k a bout why tables are or aren't good, that suggests an issue. They're not a bsorbing information core to their job. When compan ies get into trouble is when they mix and match these: •
They ask special ist questions to people who aren't specialists. They hire for special ist roles when they don't need specialists.
•
•
They need special ists but are only assessing pretty basic skills. They a re asking san ity check (easy) questions, but think they're asking qual ity check questions. They therefore interpret a strong difference between "okay" and "great" performance, even though a very minor deta il might have separated these.
In fact, having worked with a number of small and large tech com pa n ies on their hiring process, I have found that most companies a re doing one of these things wrong.
CrackingTheCodinglnterview.com I 6th Edition
25
IV Before the I nterview
Acing an interview starts well before the interview itself-years before, in fact. The following timeline outli nes what you should be thinking about when. If you're sta rting late into this process, don't worry. Do as much "catching up" as you can, and then focus on preparation. Good luck! � Getting the Right Experience
Without a great resume, there's no interview. And without great experience, there's no great resume. There fore, the first step in landing an interview is getting great experience. The fu rther in adva nce you ca n think a bout this the better. For current students, this may mean the following: Take the Big Project Classes: Seek out the classes with big coding projects. This is a great way to get some what practical experience before you have a ny formal work experience. The more relevant the project is to the real world, the better. Get an Internship: Do everything you can to land a n internship ea rly in school. It will pave the way for even better internsh i ps before you graduate. Many of the top tech companies have internship prog ra ms designed especially for freshman a nd sophomores. You can also look at sta rtups, which might be more flexi ble. •
Start Something: Build
a project on you r own time, participate in hackathons, or contribute to an open sou rce project. It doesn't matter too much what it is. The im porta nt thing is that you're coding. Not only will this develop you r technical skills a nd practical experience, your initiative will impress compa nies.
Professiona ls, on the other hand, may already have the right experience to switch to their dream company. For instance, a Google dev proba bly already has suffi cient experience to switch to Facebook. However, if you're trying to move from a lesser-known company to one of the "biggies," or from testing/IT into a dev role, the following advice will be usefu l: •
Shift Work Responsibilities More Towards Coding: Without revea ling to your manager that you
a re thi nking of leaving, you ca n discuss you r eagerness to take on bigger coding challenges. As much as possible, try to ensure that these projects a re "meaty," use relevant technolog ies, and lend themselves well to a resume bullet or two. It is these cod ing projects that will, ideally, form the bulk of your resume. Use Your Nights and Weekends: If you have some free time, use it to build a mobile app, a web a pp, or a piece of desktop software. Doing such projects is also a great way to get experience with new technolo gies, making you more releva nt to today's companies. This project work should defin itely be listed on you r resume; few things a re as impressive to an i nterviewer as a candidate who built something "just
26
I
Cracking the Coding I nterview, 6th Edition
IV
I Before the Interview
for fun:' All of these boil down to the two big things that companies want to see: that you're smart and that you can code. If you can prove that, you can land your i nterview. In addition, you should think in adva nce a bout where you want your career to go. If you want to move into management down the road, even though you're currently looking for a dev position, you should find ways now of developing leadership experience. •
Wri ting a G reat Resume
Resume screeners look for the same things that i nterviewers do. They want to know that you're smart and that you can code. That means you should prepare your resume to hig hlight those two things. Your love of tennis, traveling, or magic cards won't do m uch to show that. Th ink twice before cutti ng more tech nical l i nes in order to al low space for your non-tech nical hobbies. Appropriate Resume Length
In the US, it is strongly advised to keep a resume to one page if you have less than ten years of experience. More experienced candidates ca n often justify 1 .5 - 2 pages otherwise. Th ink twice a bout a long resume. Shorter resumes are often more impressive. Recruiters only spend a fixed amount of time (a bout 1 0 seconds) looking at your resume. If you limit the content to the most impressive items, the recruiter is sure to see them. Adding additional items just distracts the recruiter from what you'd really like them to see. Some people just flat-out refuse to read long resumes. Do you really want to risk having your resume tossed for this reason? If you a re thinki ng right now that you have too m uch experience and ca n't fit it a l l on one or two pages, trust me, you can. Long resumes a re not a reflection of having tons of experience; they're a reflection of not understa nding how to prioritize content. Employment History
Your resume does not-a nd should not-include a full history of every role you've ever had. I nclude only the relevant positions-the ones that make you a more impressive candidate. Writing Strong Bullets
For each role, try to discuss your accomplishments with the fol lowing approach: "Accomplished X by imple menting Y which led to z:· Here's an exam ple: •
"Reduced object rendering time by 75% by implementing distributed cachi ng, leading to a 1 0% reduc tion in log-in time:'
Here's a nother exa mple with a n alternate wording: •
"Increased average match accuracy from 1 .2 to 1 .5 by im plementing a new comparison algorithm based on windiff:'
Not everything you did will fit into this approach, but the principle is the same: show what you did, how you did it, and what the results were. Idea l ly, you should try to make the results"measura ble" somehow.
CrackingTheCodinglnterview.com I 6th Edition
27
IV I
Before the I nterview
Projects
Developing the projects section on you r resume is often the best way to present you rself as more experi enced. Th is is especially true for college students or recent grads. The projects should include you r 2 4 most sign ificant projects. State what the project was and which languages or tech nologies it employed. You may also want to consider including details such as whether the project was an individual or a team p roject, and whether it was com pleted for a cou rse or indepen dently. These details a re not required, so only include them if they make you look better. Independent projects a re generally preferred over cou rse projects, as it shows initiative. -
Do not add too many projects. Many candidates make the mistake of adding all cl uttering their resume with small, non-impressive projects.
13
of their prior projects,
So what should you build? Honestly, it doesn't matter that much. Some employers real ly like open sou rce projects (it offers experience contributing to a large code base), while others prefer independent projects (it's easier to understand you r personal contributions). You could build a mobile app, a web app, or almost anything. The most important thing is that you're building someth ing. Programming Languages and Software Software
Be conservative a bout what software you list, and understand what's appropriate for the company. Soft wa re l i ke Microsoft Office can almost always be cut. Technical software l i ke Visual Studio and Eclipse is somewhat more releva nt, but many of the top tech companies won't even ca re about that. After all, is it rea lly that hard to learn Visual Studio? Of cou rse, it won't h u rt you to list all this software. It just takes up va luable space. You need to eva luate the trade-off of that. Languages
Should you list everything you've ever worked with, or shorten the list to just the ones that you're most comforta ble with? Listing everything you've ever worked with is dangerous. Many interviewers consider anyth ing on your resume to be "fa ir game" as far as the interview. One alternative is to list most of the languages you've u sed, but add your experience level. This approach is shown below: La nguages: Java (expe rt), C ++ (proficient), JavaScript (prior experience). Use whatever wording ("expert'; "fluent'; etc.) effectively com mun icates you r skillset. Some people list the number of years of experience they have with a particu lar language, but this can be really confusing. If you first learned Java 1 0 years ago, and have used it occasionally throughout that time, how many yea rs of experience is this? For this reason, the number of yea rs of experience is a poor metric for resumes. It's better to just describe what you mean in plain English. Advice for Non-Native English Speakers and I nternationals
Some com panies will throw out your resume just because of a typo. Please get at least one native English spea ker to proofread you r resume.
28
Cracking the Codi ng I nterview, 6th Edition
IV
I
Before the Interview
Additional ly, for US positions, do not i nclude age, ma rita I status, or nationa I ity. Th is sort of persona I i nforma tion is not appreciated by companies, as it creates a legal liability for them. Beware of (Potential) Stigma
Certain languages have stigmas associated with them. Sometimes this is because of the language them selves, but often it's because of the places where this language is used. I'm not defending the stig ma; I'm just letting you know of it. A few stigmas you should be awa re of: Enterprise Languages: Certa in languages have a stigma associated with them, and those a re often the ones that are used for enterprise development. Visual Basic is a good exa mple of this. If you show you r self to be a n expert with VB, it can cause people to assume that you're less skil led. Many of these same people wi ll admit that, yes, VB.NET is actually perfectly capable of building sophisticated applications. But sti ll, the kinds of appl ications that people tend to build with it a re not very sophisticated. You wou ld be unlikely to see a big name Silicon Va lley using VB.
In fa ct, the same argument (although less strong) applies to the whole .NET platform. If your primary focus is .NET and you're not a pplying for .NET roles, you'll have to do more to show that you're strong technicallythan if you were com i ng in with a different backgrou nd. Being Too Language Focused: When recru iters at some of the top tech companies see resumes that list every flavor of Java on their resume, they make negative assumptions about the cal i ber of candi date. There is a belief in many circles that the best software engineers don't define themselves around a particular language. Thus, when they see a candidate seems to flaunt which specific versions of a language they know, recru iters will often bucket the candidate as "not our kind of person:'
Note that this does not mean that you should necessarily take this "language flau nting" off your resume. You need to understa nd what that company va lues. Some companies do va lue this. Certifications: Certifications for software eng ineers can be anyth ing from a positive, to a neutra l, to a negative. Th is goes ha nd-in-hand with being too la nguage focused; the compa nies that a re biased agai nst candidates with a very lengthy list of technologies tend to a lso be biased against certifications. This means that in some cases, you should actua l ly remove this sort of experience from your resume. Knowing Only One or Two Languages: The more time you've spent cod i ng, the more thi ngs you've
built, the more languages you will have tended to work with. The assu mption then, when they see a resu me with only one language, is that you haven't experienced very many problems. They also often worry that candidates with only one or two languages will have trouble learning new tech nologies (why hasn't the candidate learned more thi ngs?) or will just feel too tied with a specific tech nology (poten tially not using the best language for the task). This advice is here not just to help you work on your resume, but also to help you develop the right experi ence. If you r expertise is in C#.N ET, try developing some projects in Python and JavaScript. If you only know one or two languages, build some applications in a d ifferent la nguage. Where possible, try to tru ly diversify. The languages in the cluster of {Python, Ru by, and JavaScri pt} a re somewhat similar to each other. It's better if you can learn la nguages that a re more different, like Python, C++, and Java.
CrackingTheCodingl nterview.com I 6th Edition
29
IV
I
Before the I nterview
� Preparation Map
The fo llowing map should give you an idea of how to tackle the interview prepa ration process. One of the key ta keaways here is that it's not just a bout interview questions. Do projects and write code, too! Lea rn multiple progra m ming Ian ua es.
Build projects outside of school/work.
+ Build website I port fOlio showca sing you r experience.
Students: find intern ship and ta ke cla sses with large projects.
Expa nd Network.
+ Continue to work on projects. Try to add on one more project.
Professionals: focus work on "meaty" projects.
+ Read intro sections of CtCI (Cracking the Coding Interview).
Ma ke ta rget list of preferred companies.
�
Create draft of resume and send it out for a resume review.
I mplement data structu res and a lgorithms from scratch.
-----+
Form mock i nterview group with friends to interview each other.
+ Lea rn and master Big O.
-----+
+ Do min i-projects to solidify understa nding of ke conce ts.
Do severa l mock inter views.
+ Continue to practice i nterview questions.
-----+
Create list to track mista kes you've made solving problems.
-----+
+ Begin a pplying to companies.
Review I u pdate resume.
Create interview prep grid (pg 32).
t
30
Cracki ng the Cod ing I nterview, 6th Edition
IV
Re-read intro to CtCi, especially Tech & Behavioral section.
_____.
Do a nother mock interview.
_____.
I Before the I nterview Continue to practice q uestio n s, writing code on paper.
t Do a final mock interview.
+-
Phone I nterview: Locate headset and /or video camera.
..-
t Rehearse stories from the interview prep grid (pg 32).
_____.
Re-read Algorithm Approaches (pg 67).
_____.
Re-read Big 0 section (pg 38).
t Rehea rse each story from interview p rep grid once.
..-
Day Bef,Qre
..-
_____.
Review Powers of 2 ta ble (pg 6 1 ). Print for a phone screen.
_____.
Continue to practice i nterview questions.
t Continue to practice questions& review you r list of mistakes.
t Remember to ta lk out loud. Show how you think.
..,.___
Be Confident (Not Cocky!).
..-
Wake u p i n plenty of time to eat a good breakfast& be on time.
_____.
Write Tha nk You note to recruiter.
t Don't forget: Stumbling and struggling is normal!
_____.
t Get an offer? Celebrate! You r hard work paid off!
..,.___
If no offer, ask when you can re-apply. Don't give u p hope!
..,.__
If you haven't heard from recruiter, check in after one week.
CrackingTheCodinglnterview.com I 6th Edition
31
v Behaviora l Questions
Behavioral questions are asked to get to know you r personal ity, to u nderstand you r resume more deeply, and just to ease you into a n interview. They are im porta nt questions and ca n be prepared for. � I nterview Prepa ration Grid
Go through each of the projects or components of you r resume and ensure that you can ta l k about them in deta il. Filling out a grid like this may help:
Challenges Mistakes/Fai l u res Enjoyed Leadership Conflicts What You'd Do Differently Along the top, as colum ns, you should list all the major aspects of you r resume, including each project, job, or activity. Along the side, as rows, you should list the common behavioral questions. Study this grid before you r i nterview. Reducing each story to just a couple of keywords may make the grid easier to study and reca ll. You can also more easily have this grid in front of you d u ring an interview without it being a distraction. In add ition, ensure that you have one to th ree projects that you ca n ta l k about in detail. You should be able to discuss the technical components in depth. These should be projects where you played a central role. What are you r weaknesses?
When asked about you r wea knesses, give a rea l wea kness! Answers like "My greatest wea kness is that I work too hard" tell you r i nterviewer that you're a rrogant a nd/or won't admit to your fau lts. A good a nswer conveys a rea l, legitimate wea kness but emphasizes how you work to overcome it. For exam ple: " Sometimes, I don't have a very good attention to detai l. While that's good because i t lets me execute quickly, it also means that I sometimes make careless mistakes. Because of that, I make sure to always have someone else double check my work:'
I 32
Cracking the Coding I nterview, 6th Edition
V
I
Behaviora l Questio n s
What questions should you a s k the interviewer?
Most interviewers will give you a chance to ask them questions. The quality of your questions wi l l be a factor, whether su bconsciously or consciously, in their decisions. Walk into the interview with some ques tions in mind. You can think about three genera l types of questions. Genuine Questions
These a re the questions you actually want to know the answers to. Here a re a few ideas of questions that are valuable to many candidates: 1 . "What is the ratio of testers to developers to program managers? What is the interaction like? How does project planning happen on the team?" 2. "What brought you to this company? What has been most challenging for you?"
These questions will give you a good feel for what the day-to-day l ife is like at the com pany. lnsigh tfuf Questions
These questions demonstrate your knowledge or understanding of technology. 1 . "I noticed that you use technology X. How do you handle problem Y?"
2 . "Why did the prod uct choose to use the X protocol over t h e Y protocol? I know it has benefits like A, B, C, but many compan ies choose not to use it because of issue o:· Asking such questions will typically require advance resea rch about the company. Passion Questions
These questions a re designed to demonstrate your passion for technology. They show that you're inter ested in learning and will be a strong contributor to the company. 1 . "I'm very interested in scalability, and I'd love to learn more a bout it. What opportunities are there at this
com pany to learn about this?" 2. "I'm not familiar with tech nology X, but it sou nds like a very interesting solution. Could you tel l me a bit
more about how it works?"
� Know You r Tech n ical Projects
As part of you r preparation, you should focus on two or three technical projects that you should deeply master. Select projects that ideally fit the following criteria: The project had chal lenging components (beyond just "learning a lot"). •
•
You played a central role (idea lly on the challenging components). You can ta lk at technical depth.
For those projects, and all you r projects, be able to ta lk a bout the cha l lenges, mista kes, technical decisions, choices of technologies (and tradeoffs of these), and the things you would do d ifferently. You can also think about fol low-up questions, like how you would sca le the appl ication.
CrackingTheCodinglnterview.com I 6th Edition
33
V
I Behavioral Qu estions
� Responding to Behaviora l Questions
Behavioral questions al low your interviewer to get to know you and you r prior experience better. Remember the following advice when responding to questions. Be Specific, Not Arrogant A rro ga n c e is
a red flag, but you stil l want to make you rself sound impressive. So how do you make you rself sound good without being a rroga nt? By being specific!
Specificity means giving just the facts and letting the interviewer derive an interpretation. For example, rather than saying that you "d id all the hard parts;' you can instead describe the specific bits you did that were chal lenging. Limit Details
When a candidate blabbers on a bout a problem, it's hard for an interviewer who isn't wel l versed in the subject or project to understa nd it. Stay light on details and just state the key poi nts. When possible, try to translate it or at least explain the im pact. You can always offer the interviewer the opportunity to drill in further.
I
"By examining the most common user behavior and a pplying the Rabin-Karp a lgorithm, desig ned a new algorithm to reduce search from 0( n ) to 0 ( log n ) in 90% of cases. I can go into more details if you'd li ke:'
This demonstrates the key points while letting your interviewer ask for more details if he wants to. Focus on Yourself, Not Your Team
I nterviews a re fundamenta l ly a n ind ivid ual assessment. Unfortu nately, when you listen to many candidates (especia lly those in leadership roles), their answers are about "we'; "us'; a nd "the team." The interviewer wa lks away having little idea what the candidate's actua l impact was and might conclude that the candi date d id little. Pay attention to your answers. Listen for how much you say "we" versus "I:' Assume that every question is a bout your role, and speak to that. Give Structured Answers
There are two common ways to think about structuring responses to a behavioral question: nugget first and S.A.R. These techniques can be used sepa rately or together. Nugget First
Nugget Fi rst means sta rting your response with a "nugget" that succi nctly descri bes what your response will be a bout. For example: I nterviewer: "Tell me a bout a time you had to persuade a group of people to make a big change:' Candidate: "Sure, let me tel l you a bout the time when I convinced my school to let u n derg ra d u ate s teach their own courses. Initial ly, my school had a ru le where..:· 34
Cracking the Coding Interview, 6th Edition
V
I
Behaviora l Questions
This technique gra bs your interviewer's attention and makes it very clear what you r story will be a bout. It also helps you be more fo cused in your communication, since you've made it very clear to you rself what the gist of your response is. S.A.R. (Situation, Action, Result)
The S.A.R. approach means that you sta rt off outlin ing the situation, then explaining the actions you took, and lastly, describing the resu lt. Example; "Tell me about a challenging interaction with a teammate:'
Situation: On my operati ng systems project, I was assigned to work with three other people. While two were great, the third team member didn't contribute much. He stayed quiet during meeti ngs, ra rely chipped in during email d iscussions, and struggled to complete his components. This was an issue not only because it shifted more work onto us, but also because we didn't know if we could count on him. •
Action: I did n't want to write him off completely yet. so I tried to resolve the situation. I did three things.
Fi rst, I wanted to understa nd why he was acti ng like this. Was it laziness? Was he busy with something else? I struck u p a conversation with him and then asked him open-ended questions a bout how he felt it was going. Interesting ly, basically out of nowhere, he sa id that he wanted to ta ke on the writeu p, which is one of the most time intensive pa rts. This showed me that it wasn't laziness; it was that he didn't feel like he was good enough to write code. Second, now that I understa nd the cause, I tried to make it clear that he should n't fea r messi ng up. I told him a bout some of the bigger mistakes that I made and admitted that I wasn't clear a bout a lot of pa rts of the project either. Third and finally, I asked him to help me with brea king out some of the components of the project. We sat down together and designed a thoroug h spec fo r one of the big component, in much more detail than we had before. Once he could see all the pieces, it hel ped show him that the project wasn't as scary as he'd assumed. Result: With his confidence raised, he now offered to ta ke on a bunch of the smaller coding work. and then eventually some of the biggest pa rts. He finished a l l his work on time, and he contributed more in discussions. We were happy to work with him o n a futu re project.
The situation and the result should be succinct. You r interviewer generally does not need many details to understa nd what happened and, in fact. may be confused by them. By using the S.A.R. model with clear situations, a ctions and results, the interviewer will be able to easily identify how you made an impact and why it mattered. Consider putting you r stories into the following g rid: Nugget
Story 1
Situation
Action(s)
Result
What It Says
1 . .. .
.
2 . .. .
3 . ..
Story 2 _ Explore the Action
In almost all cases, the "action" is the most im porta nt part of the story. U nfortunately, far too many people ta l k on and on about the situation, but then just breeze through the action.
CrackingTheCodingl nterview.com I 6th Edition
35
V
I Behaviora l Questions
Instead, dive into the action. Where possi ble, brea k down the action into multiple parts. For example: "I did three things. Fi rst, l..."Th is will encourage sufficient depth. Think About What It Says
Re-read the story on page 35. What personality attri butes has the cand idate demonstrated? Initiative/Leadership: The cand idate tried to resolve the situation by addressing it head-on. Empathy: The cand idate tried to understa nd what was happening to the person. The cand idate a lso showed em pathy in knowing what would resolve the teammate's insecu rity. -
Compassion: Although the teammate was harm ing the team, the cand idate wasn't angry at the team mate. His em pathy led him to compassion. Humility: The cand idate was able to admit to his own flaws (not only to the teammate, but also to the
interviewer). •
Teamwork/Helpfulness: The cand idate worked with the teammate to break down the project into
manageable chunks. You should think about you r stories from this perspective. Analyze the actions you took and how you reacted. What personality attri butes does you r reaction demonstrate? In many cases, the answer is "none:'That usually means you need to rework how you commun icate the story to make the attri bute clearer. You don't want to expl icitly say, "I did X because I have em pathy;' but you can go one step away from that. For exam ple: Less Clear Attribute: "I ca l led u p the client and told him what happened:' •
More Clear Attribute (Empathy and Courage): "I made sure to ca ll the client myself, because I knew that he would appreciate hearing it directly from me:·
If you stil l can't make the persona lity attri butes clear, then you might need to come up with a new story entirely. � So, tell me about you rself...
Many interviewers kick off the session by asking you to tel l them a bit a bout you rself, or asking you to wa lk through you r resume. This is essentia lly a "pitch''. It's you r interviewer's fi rst impression of you, so you want to be sure to nail this. Structure
A typica l structu re that works well fo r many people is essentially chronological, with the opening sentence describing their cu rrent job and the conclusion discussing their relevant and interesting hobbies outside of work (if any). 1 . Current Role [Headline Only]: 'Tm a software engineer at M icroworks, where I've been lead ing the And roid team fo r the last five yea rs:· 2.
1 did my undergrad at Berkeley and spent a few summers working at startu ps, including one where I attempted to launch my own business.
College: My background is in computer science.
3. Post Colleg e & Onwards: After c o l l e g e, I w a n te d
to get some exposu re to larger corporations so I joined Amazon as a developer. It was a great experience. I learned a ton a bout large system design and I got to really drive the launch of a key part of AWS. That actually showed me that I rea lly wanted to be in a more
36
I
Cracking the Coding Interview, 6th Edition
V f Behaviora l Questions
entrepreneurial environment. One of my old managers from Amazon recru ited me out to join her startup, which was what brought me to Microworks. Here, I did the initial system a rchitecture, which has scaled pretty well with our rapid growth. I then took an opportun ity to lead the Android team. I do manage a team of three, but my role is primarily with tech nical leadership: a rch itectu re, cod ing, etc.
4. Current Role [Details]:
5.
Outside of Work: Outside of work, I've been participating in some hackathons-mostly doing iOS development there as a way to learn it more deeply. I'm a lso active as a moderator on online forums around Android development.
6. Wrap Up: I'm looki ng now for something new, and your company caught my eye. I've always loved the connection with the user, and I really want to get back to a smaller environment too. This structure works well for about 95% of candidates. For candidate with more experience, you might condense part of it. Ten years from now, the candidate's initial statements might become just: "After my CS degree from Berkeley, I spent a few years at Amazon and then joined a startup where I led the Android team:' Hobbies
Think carefu lly a bout your hobbies. You may or may not want to discuss them. Often they're just fluff. If your hobby is just generic activities l i ke skiing or playing with you r dog, you can probably skip it. Sometimes though, hobbies can be useful. This often ha ppens when: •
The hobby is extremely unique (e.g., fire breathing). It may strike u p a bit of a conversation and kick off the interview on a more amiable note. The hobby is tech nical. This not only boosts you r actual skillset, but it a lso shows passion for technology.
•
The hobby demonstrates a positive personality attri bute. A hobby like "remodeling you r house yourself" shows a d rive to learn new things, take some risks, and get you r hands di rty (literally and figuratively).
It would rarely h u rt to mention hobbies, so when in doubt, you might as well. Th ink a bout how to best frame you r hobby though. Do you have any successes or specific work to show from it (e.g., landing a part in a play)? Is there a personality attribute this hobby demonstrates? Sprinkle in Shows of Successes
In the a bove pitch, the candidate has casually d ropped in some highlights of his background. He specifica lly mentioned that he was recruited out of Microworks by his old manager, which shows that he was successful at Amazon. He a lso mentions wanting to be in a smaller environment, which shows some element of culture fit (assuming this is a startup he's a pplying for). •
He mentions some successes he's had, such as launching a key part of AWS and a rchitecting a scalable system. He mentions his hobbies, both of which show a d rive to learn.
When you think about your pitch, think a bout what different aspects of your background say about you. Can you can d rop in shows of successes (awards, promotions, being recruited out by someone you worked with, launches, etc.)? What do you want to communicate a bout yourself?
CrackingTheCodingl nterview.com j 6th Edition
37
VI Big O
This is such an im portant concept that we are dedicating an entire (long!) chapter to it. Big 0 time is the language and metric we use to describe the efficiency of algorithms. Not u ndersta nding it thoroughly can really hurt you in developing an algorithm. Not only might you be judged harshly for not real ly u nderstanding big 0, but you wi ll also struggle to judge when you r algorithm is getting faster or slower. Master th is concept. •
An Analogy
Imagine the following scena rio: You've got a file on a hard drive and you need to send it to you r friend who lives across the cou ntry. You need to get the file to you r friend as fast as possible. How should you send it? Most people's fi rst thought wou ld be email, FTP, or some other means of electronic transfer. That thought is reasonable, but only half correct. If it's a sma l l file, you're certainly rig ht. It wou ld ta ke 5 1 0 hours to get to an airport, hop on a flig ht, and then deliver it to you r friend. -
But what if the file were real ly, really large? Is it possible that it's faster to physica lly deliver it via plane? Yes, actually it is. A one-tera byte (1 TB) file could ta ke more tha n a day to tra nsfer electronical ly. It wou ld be much faster to just fly it across the country. If you r file is that u rgent (and cost isn't an issue), you might just want to do that. What if there were no flights, and instead you had to d rive across the cou ntry? Even then, for a really huge file, it would be faster to d rive. •
Ti me Complexity
This is what the concept of asym ptotic ru ntime, or big "a lgorithm" ru ntime as:
0
time, means. We cou ld describe the data tra nsfer
Electronic Transfer: O ( s ) , where s is the size of the fi le. This means that the time to transfer the file increases li nearly with the size of the fi le. (Yes, this is a bit of a simplification, but that's okay for these pu rposes.) Airplane Transfer: O( 1 ) with respect to the size of the fi le. As the size of the file increases, it won't ta ke any longer to get the file to you r friend. The time is constant.
38
Cracking the Coding Interview, 6th Edition
VI
I Big 0
No matter how big the consta nt is and how slow the linear increase is, linear will at some point su rpass consta nt.
0(1)
.
.
..
.·
· .···
- - - - - - - - -. . .... - - . ···
. . . . ··
.. ·
.....
There are many more runtimes than this. Some of the most com mon ones a re O ( log N ) , O ( N log N ) , O ( N ) , O ( W ) and O ( 2N ) . There's n o fixed list of possible runtimes, though.
You can a lso have multiple variables in you r runtime. For exam ple, the time to paint a fence that's w meters wide and h meters high could be descri bed as 0 ( wh ) . If you needed p layers of paint, then you could say that the time is O ( wh p ) . Big 0, Big Theta, and Big Omega
If you've never covered big 0 in an academic setti ng, you ca n probably skip this su bsection. It might confuse you more than it hel ps. This "FYI" is mostly here to clear up am biguity in wording fo r people who have learned big 0 before, so that they don't say, "But I thought big 0 mea nt..:' Academ ics use big
0,
big
8
(theta), and big
0
(omega) to descri be ru nti mes.
0 (big 0): In academia, big 0 describes an upper bound on the time. An a lgorithm that prints a l l the values in an a rray could be descri bed as 0 ( N ) , but it cou ld a lso be described as 0 ( N2 ) , 0 ( N3 ) , or O ( 2N) (or many other big 0 times). The algorithm is at least as fast as each of these; therefore they a re upper bounds on the runtime. This is similar to a less-tha n-or-equa l-to relationship. If Bob is X yea rs old (I'll assume no one l ives past age 1 30), then you could say X s. 130. It wou ld also be correct to say that X S. 1 , 000 or X S. 1 , 000, 000. It's technically true (althoug h not terribly usefu l). Likewise, a simple algorithm to print the va lues i n an array is O( N ) as wel l as O( N3) or any ru ntime bigger than O( N ) . 0
(big omega): I n academia, 0 i s the equivalent concept but for lower bound. Printing the values in an a rray is 0 ( N) as wel l as 0 ( l o g N) and 0 ( 1 ) . After a l l, you know that it won't be faster than those runtimes.
•
0
(big theta): I n academ ia, 8 means both 0 and O( N ) . 8 gives a tight bound on runtime.
0. That
is, a n algorithm is E> ( N ) if it is both O ( N ) and
In industry (and therefo re in interviews), people seem to have merged 0 and 0 together. Industry's meaning of big 0 is closer to what academics mean by 0 , in that it would be seen as incorrect to describe printing an a rray as 0 ( N2 ) . Industry wou ld just say this is O ( N ) . For this book, we will use big 0 i n the way that industry tends to use it: By always trying to offer the tig htest description of the runtime. Best Case, Worst Case, and Expected Case
We can actually descri be our runtime fo r an a lgorithm in three different ways.
CrackingTheCodingl nterview.com I 6th Edition
19
VI
I Big 0
Let's look at this from the perspective of q u ick sort. Quick sort picks a random element as a "pivot" and then swaps va lues in the array such that the elements less than pivot appear before elements g reater than pivot. Th is gives a "partial sort:'Then it recu rsively sorts the left and rig ht sides using a similar process. Best Case: If all elements a re equal, then qu ick sort will, on average, just traverse through the a rray once.
This is 0 ( N ) . (Th is actually depends slightly on the im plementation of quick sort. There are implementa tions. though, that will run very quickly on a sorted array.) •
Worst Case: What if we get really unlucky and the pivot is repeated ly the biggest element i n the array?
(Actually, this can easily happen. If the pivot is chosen to be the fi rst element in the subarray and the a rray is sorted in reverse order, we'll have this situation.) In this case, our recursion doesn't d ivide the array in half and recurse on each half. It just sh rinks the suba rray by one element. This will degenerate to a n 0 ( N 2 ) runtime. Expected Case: Usual ly, though, these wonde rfu l or terri ble situations won't happen. Su re, sometimes
the pivot wil l be very low or very high, but it won't ha ppen over and over again. We can expect a runtime of O ( N log N ) . We ra rely ever discuss best case time com plexity, because it's not a very useful concept. After a l l, we cou ld take essentially any a lgorithm, special case some input, and then get a n 0 ( 1) time in the best case. For many-probably most-a lgorithms, the worst case and the expected case are the same. Sometimes they're different, though, and we need to describe both of the runtimes. What is the relationship between best/worst/expected case and big O/theta/omega?
It's easy for candidates to muddle these concepts (probably because both have some concepts of"higher'; "lower" and "exactly right"), but there is no particu lar relationship between the concepts. Best, worst, and expected cases describe the big 0 (or big theta) time for particu lar in puts or scena rios. Big 0, big omega, and big theta describe the upper, lower, and tight bou nds for the runtime. ., Space Com plexity
Time is not the only thing that matters in an algorithm. We might a lso care a bout the amount of memory or space-required by an a lgorithm. Space com plexity is a parallel concept to time com plexity. If we need to create an a rray of size n, this wil l requ i re 0 ( n ) space. I f we need a two-dimensional array of size n x n, this will requ i re O ( n 2 ) space. Stack space in recu rsive calls cou nts, too. For example, code like this wou ld take 0 ( n ) time and 0( n ) space. int sum( int n ) { / * E x 1 . */ 1 2
3
4 5
6
}
if ( n < = 0 ) { return 0 ; } return n + s um ( n - 1 ) ;
Each ca ll adds a level to the stack.
1
s um ( 4) - > sum ( 3 ) - > s um ( 2 ) - > s um ( l ) - > s um ( 0 )
2 3 4 5
Each o f these calls i s added to the call stack a n d ta kes u p actual memory. 40
Cracking the Coding Interview, 6th Edition
Vf ! Big O However, just because you have n calls tota l doesn't mean it takes 0 ( n ) space. Consider the below fu nc tion, which adds adjacent elements between 0 and n : 1 i nt pai rSumSequence ( int n ) { /* Ex 2 . * / 2 3
=
int sum 0; ( i nt i = 0 ; i < n ; i++) { s um += pairSum( i , i + l ) ;
for
4 5
6
7 8 9
}
ret u r n s um; }
int pairSum( i nt a , i nt b ) { 10 ret u rn a + b ; 11 }
There will be roughly 0 ( n ) calls to pa i rSum. However, those calls do not exist simu lta neously on the call stack, so you only need 0 ( 1 ) space. �
Drop the Constants
It is very possi ble for 0( N ) code to run faster than 0( 1 ) code for specific inputs. Big rate of increase.
0 just
describes the
For this reason, we d rop the constants in ru ntime. An algorithm that one might have described as 0 ( 2N ) is actually 0 ( N ) . Many people resist doing this. They will see code that has two {non-nested) for loops and continue this 0 ( 2N ) . They think they're being more "precise:'They're not. Consider the below code: Min and Max 1
1
2
3
4
5
6
i nt m i n = I ntege r . MAX_VALUE ; m ax = Intege r . MIN_VALUE ; for ( int x : a r ray) { i f ( x < min) min x; if ( x > m a x ) m a x = x ; } int
Min and Max 2 1
2 3
4 5 6
7
8
int m i n = I ntege r . MAX_VALU E ; i nt m ax = I ntege r . MIN_VALU E ; fo r (int x : array) { if ( x < m i n ) m i n x; =
}
fo r
( i nt x
if
}
(x
>
: a rray) { m a x ) max = x ;
Which one is faster? The first one does one for loop and the other one does two for loops. But then, the fi rst solution has two lines of code per for loop rather than one. If you're going to cou nt the number of instructions, then you'd have to go to the assembly level and ta ke into account that m u ltiplication requ i res more instructions than addition, how the compiler would opti mize something, and all sorts of other detai ls. Th is would be horrendously complicated, so don't even sta rt going down this road. Big 0 a l lows us to express how the runtime sca les. We just need to accept that it doesn't mean that 0 ( N ) is a lways bette r than
O ( N2 ) .
CrackingTheCodinglnterview.com \ 6th Edition
41
VI
I Big 0
� Drop the Non-Dom inant Terms
What do you do about an expression such as O ( N2 not especially importa nt.
+
N ) ? That second N isn't exactly a constant. But it's
We already said that we drop constants. Therefore, O ( N2 + N2 ) would be D ( N2 ) . If we don't care about that latter N1 term, why would we care about N? We don't. You should drop the non-dominant terms. •
O ( N2 + N ) becomes O ( N2 ) .
•
O ( N + log N ) becomes O ( N ) .
•
0 ( 5 * 2N
+
1000N100 ) becomes 0 ( 2N ) .
We might still have a sum in a ru ntime. For exam ple, the expression O ( B 2 + A ) cannot be reduced (without some special knowledge of A and B). The following graph depicts the rate of increase for some of the common big 0 times.
O{log x J
A s you can see, 0 ( x2 ) i s much worse than 0 ( x ) , but it's not nea rly as bad a s 0 ( 2 ' ) o r O ( x l ) . There are lots of ru ntimes worse than O ( x l ) too, such as O ( x' ) or 0 ( 2 x * x l ) . � Mu lti-Pa rt Algorithms: Add vs. Multiply
Suppose you have a n algorithm that has two steps. When do you multiply the ru ntimes and when do you add them? This is a common sou rce of confusion for candidates.
42
Cracking the Coding Interview, 6th Edition
VI Add 1
2 3
4 5
6
7
the Runtimes: 0 (A + B )
Multiply the Runtimes: 0 (A * B )
for ( int a : a r rA) { print ( a ) ; }
2
1
3 4 5
for ( int b : arrB) { print ( b ) ; }
I Big 0
for ( int a : a r rA) { for ( i nt b : a r r B ) { p rint ( a + , + b ) ; } } "
"
In the exa mple on the left, we do A chu nks of work then B chunks of work. Therefore, the total a mount of work is O ( A + B ) . I n the exa m ple on the right, we do B chu nks of work fo r each element i n A. Therefore, the tota l amount of work is O ( A * B ) . I n other words: If you r algorithm is in the form "do this, then, when you're all done, d o that" then you add the ru ntimes. If your algorithm is in the form "do this for each time you do that" then you mu ltiply the runtimes. It's ve ry easy to mess this up in an interview, so be carefu l. � Amortized Ti me
An Array l i st, or a dyna mically resizing array, a llows you to have the benefits of an a rray while offering flexibil ity in size. You won't ru n out of space in the Array L i st since its ca pacity will grow as you insert elements. An Arraylist is im plemented with an array. When the a rray hits capacity, the Arrayl i s t class wi l l create a new array with double the ca pacity a nd copy all the elements over to the new a rray. How do you describe the runtime of insertion? This is a tricky question. The array cou ld be fu ll. If the a rray conta ins N elements, then inserting a new element will ta ke O ( N ) time. You will have to create a new array of size 2N a nd then copy N elements over. This insertion will ta ke 0 ( N ) ti me. However, we also know that this doesn't ha ppen very often. The vast majority of the time insertion will be in O ( l ) time. We need a conceptthat ta kes both into accou nt. This is what amortized time does. It allows us to describe that, yes, this worst case ha ppens every once in a whi le. But once it happens, it won't happen again for so long that the cost is "amortized." I n this case, what is the amortized time? As we insert elements, we double the ca pacity when the size of the array is a power of 2. So after X elements, we double the capacity at a rray sizes 1 , 2, 4, 8, 1 6, ... , X. That doubling ta kes, respectively, 1 , 2, 4, 8, 1 6, 32, 64, ... , X copies. What is the sum of 1 + 2 + 4 + 8 + 16 + . .. + X? If you read this sum left to right, it sta rts with 1 a nd dou bles u ntil it gets to X. If you read right to left, it starts w ith X and ha lves until it gets to 1 .
What then is the sum of X + X
+
X
+
Ys
+
. . . + 1 ? This is roughly 2X.
Therefore, X insertions take 0 ( 2X ) time. The amortized time for each insertion is 0 ( 1 )
.
CrackingTheCodingl nterview.com j 6th Edition
43
VI
I Big 0
� Log N Ru ntimes
We commonly see 0( log N) in runtimes. Where does this come from? Let's look at binary search as an exa mple. In binary sea rch, we a re looking for an example x in an N-element sorted array. We first com pare x to the mid point of the a rray. If x == middle, then we retu rn. If x < midd le, then we search on the left side of the a rray. If x > middle, then we search on the right side of the array. search 9 wit hin { 1 , 5 , 8, 9, 1 1 , 1 3 , 15, 1 9 , 21 } compare 9 to 11 - > sm a l l e r . sea rch 9 with i n { 1 , 5 , 8 , 9 , 1 1 } compare 9 to 8 - > bigger s ea rch 9 within { 9 , 1 1 } compare 9 to 9 r etur n
We sta rt off with an N-element array to search. Then, after a single step, we're down to Yz elements. One more step, and we're down to % elements. We stop when we either find the va lue or we're down to just one element. The tota l runtime is then a matter of how many steps (d ividing N by 2 each time) we can take until N becomes 1 . N N N N = N = = =
16
/* I* I* I*
8
4
2 1
di v id e di v id e di v id e di v id e
by by by by
2 2 2 2
*I *I *I */
We could look at this in reverse (going from 1 to 1 6 instead of 1 6 to 1 ). How many times we can multiply 1 by 2 u ntil we get N? N N N
=
=
= =
1
2 4
/* /* I* /*
N 8 N = 16
multiply mult iply multiply multiply
What is k i n the expression 2k
2• 16 > log2 l6 = 4 log2 N k > 2k N =
-
=
-
=
by by by by
2 2 2 2
*/ */ *I */
N? This is exactly what l og expresses.
=
This is a good ta keaway for you to have. When you see a problem where the number of elements in the problem space gets halved each time, that wi ll likely be a O ( log N) runtime. This is the same reason why fi nding an element in a bala nced binary sea rch tree is 0( log N ) . With each comparison, we go either left or right. Half the nodes are on each side, so we cut the problem space in half each time.
I
What's the base of the log? That's an excellent question! The short answer is that it doesn't matter for the pu rposes of big 0. The longer explanation can be found at "Bases of Logs" on page 630.
• Recu rsive Runtimes
Here's a tricky one. What's the runtime of this code? 1
i nt f ( int n ) { 44
Cracking the Coding Interview, 6th Edition
VI if
2 3
4 5
6
}
I Big 0
( n a 2 - > - > a 0 - >b 1 - >b 2 - > - > b 0 and you wa nted to rea rra nge it into a 1 - > b 1 - > a 7 - > b 7 - > - > a 0 - > b 0 • You do not know the length of the l i n ked list (but you do know that the length is an even number). •
•
•
You could have one poi nter pl (the fast poi nter) move every two elements for every one move that p 2 makes. When pl h its t h e e n d o ft h e linked l i st, p2 wi l l b e at the midpoint. Then, move pl b a c k to t h e front and begin "weaving" the elements. On each iteration, p2 selects an element and inserts it after p l . •
Recu rsive Problems
A number of linked list problems rely on recursion. If you're having trouble solving a linked list problem, you should explore if a recursive approach will work. We won't go i nto depth on recu rsion here, since a later chapter is devoted to it.
CrackingTheCodinglnterview.com j 6th E dition
93
Chapter 2 I Linked Lists However, you should remember that recu rsive algorithms take at least O ( n ) space, where n is the depth of the recu rsive call. All recu rsive algorithms can be implemented iteratively, although they may be much more complex. I nterview Questions 2.1
R�mov� Dups: Write code to remove duplicates from an u nsorted linked list.
FOLLOW UP How would you solve this problem if a temporary buffer is not a llowed? Hints: #9, #40
pg 208
2.2
Return Kth to Last: Implement an a lgorithm to find the kth to last element of a singly linked list. Hints: #8, #25, #4 1, #67, # 1 26
pg 209
2.3
Delete Middle Node: Im plement an a lgorithm to delete a node in the middle (i.e., any node but the first and last node, not necessarily the exact middle) of a singly linked list, given only access to that node.
EXAMPLE Input: the node c from the linked list a - > b - > c - > d - > e - >f Resu lt: nothing is retu rned, but the new linked list looks like a - > b - > d - > e - >f Hints: #72
2.4
Partition: Write code to partition a linked list a round a value x, such that all nodes less than x come before a l l nodes greater than or equal to x . If x is contained within the list, the values of x only need to be after the elements less than x (see below). The partition element x can appear anywhere in the "right partition"; it does not need to appear between the left and right partitions.
EXAMPLE I nput:
3 - > 5 - > 8 - > 5 - > 10 - > 2 - > 1 [partition = 5]
Output:
3 - > 1 - > 2 - > 10 - > 5 - > 5 - > 8
Hints: #3, #24
pg 2 1 2
94
Cracking the Coding Interview, 6th Edition
Chapter 2 2.5
I Linked Lists
Sum Lists: You have two numbers represented by a linked list, where each node contains a single d igit. The digits are stored in reverse order, such that the 1 's digit is at the head of the list. Write a
fu nction that adds the two num bers and returns the sum as a linked list. EXAMPLE In put: ( 7 - > 1 - > 6) + ( 5 - > 9 - > 2 ) . That is, 617 + 295. Output: 2 -> 1 -> 9. That is, 912. FOLLOW UP Su ppose the d igits are stored i n forward order. Repeat the above problem. EXAMPLE Input: ( 6 - > 1 - > 7 ) + ( 2 - > 9 -> 5 ) . That is, 617
+
295 .
Output: 9 - > 1 -> 2. That is, 912. Hints: #7, #30, # 7 1 , #95, # 1 09 ______
2.6
pg 2 14
Palindrome: Im plement a fu nction to check if a linked list is a palindrome. Hints: #5, # 1 3, #29, #6 1, # 1 0 1 � �
2.7
pg 2 1 6
Intersection: Given two (singly) linked lists, determ ine if the two lists intersect. Retu rn the inter secting node. Note that the intersection is defined based on reference, not va lue. That is, if the kth node of the first linked list is the exact sa me node (by reference) as the jth node of the second linked list, then they a re intersecting. Hints: #20, #45, #55, #65, #76, #93, # 1 1 1, # 1 20, # 129
pg 2 2 1
2.8
Loop Detection: Given a circu lar linked list, implement an a lgorithm that returns the node at the
beginning of the loop. DEFINITION Circu lar linked list: A (corru pt) linked list in which a node's next pointer points to an earlier node, so as to make a loop in the linked list. EXAMPLE Input:
A - > B - > C - > D -> E -> C [the same C as earlier]
Output:
C
Hints: #50, #69, #83, #90
pg 2 2 3
Add itional Questions: Trees and Graphs (#4.3), Object-Oriented Design (#7. 1 2), System Design and Scal ability (#9.5), Moderate Problems (# 1 6.25), Hard Problems (#1 7.1 2). Hints start on page 653.
Cracki ngTheCodinglnterview.com I 6th Edition
95
3 Stacks and Queues
uestions on stacks and queues will be much easier to handle if you are comfortable with the ins and outs of the data structu re. The problems can be quite tricky, though. While some problems may be slight mod ifications on the original data structu re, others have much more complex chal lenges.
Q
> I m plementing a Stack
The stack data structu re is precisely what it sounds like: a stack of data . In certain types of problems, it can be favorable to store data in a stack rather than in an a rray. A stack uses LIFO (last-in fi rst-out) orderi ng. That is, as in a stack of dinner plates, the most recent item added to the stack is the first item to be removed. It uses the following operations: pop ( ) : Remove the top item from the stack. •
pu s h ( i tern ) : Add a n item to the top of the stack.
peek ( ) : Retu rn the top of the stack. is Empty ( ) : Return true if and only if the stack is empty. Unlike an a rray, a stack does not offer constant-ti me access to the i th item. However, it does a llow constant time adds and removes, as it doesn't requ i re shifting elements a round. We have provided simple sa mple code to implement a stack. Note that a stack can also be i m plemented using a linked list, if items were added and removed from the sa me side. public class MyStack { 1 2 private static class StackNode { private T data ; 3 4 private StackNode next ; 5
6 7
public StackNode ( T data ) { this . data data ; =
8
}
9
}
11
private Stac kNode top;
16 12 13
public T pop ( ) { i f (top n u l l ) th row new EmptystackException ( ) ; T item top . data ;
14 15
==
=
96
Cra ck i n g the Cod i n g Interview, 6th Edition
Chapter 3 16 17
18 19 20
21
22
23 24 25
26 27
28 29
3�
31
32 33
34
I Stacks and Queues
to p = top . next; ret urn item; }
public void push(T item) { StackNode t = new StackNode < T > ( item) ; t . next = t o p ; top = t ; } p u b l i c T peek ( ) { if ( top = = n u l l ) t h row new EmptySt a c k Exception ( ) ; return top . dat a ; } public boolean i s Empty ( ) { ret urn top null; } ==
} One case where stacks a re often usefu l is in certai n recu rsive a lgorithms. Sometimes you need to push temporary data onto a stack as you recu rse, but then remove them as you backtrack (for example, because the recu rsive check fai led). A stack offers a n intu itive way to do this. A stack can also be used to im plement a recu rsive algorithm iteratively. (This is a good exercise! Take a simple recu rsive algorithm and im plement it iteratively.)
� Implementing a Queue A queue im plements FIFO (first-in fi rst-out) ordering. As in a tine or queue at a ticket stand, items a re removed from the data structu re in the same order that they a re added. It uses the operations: add ( i tern ) : Add an item to the end of the list.
remove ( ) : Remove the fi rst item i n the list. peek ( ) : Return the top of the queue. is Empty ( ) : Retu rn true if and only if the queue is em pty.
A queue can also be im plemented with a linked l i st. I n fact, they are essentially the same thi ng, as long as items a re added and removed from opposite sides.
1 2
3 4 5 6 7 8
9 10 11
12 13
14
public c l a s s MyQueue { private static c l a s s QueueNode { private T data ; private QueueNode next ;
}
public QueueNode ( T dat a ) { t h i s . data = data ; }
private QueueNode first ; private QueueNode last; public void add ( T item) {
Crac kin gTheCo d in gl n te rvi ew co m I 6th Edition .
�
Cha pter 3 15
I Stacks and Queues =
QueueNode t new QueueNode ( item) ; if ( last ! = null) { last . next = t ;
16 17 18 19 20
}
last = t ; if ( first == null) { first = last ;
21 22
23 24
}
25 26 27
}
public T remove ( ) { if ( first == null) t h row new NoSuchE lement Exception ( ) ; T data = first . data ; first = first . next ; if ( first == null) { last = null ;
28 29 30 31
}
32 33 34 35 36 37 38 39 40 41 42
ret urn dat a ; }
public T peek( ) { if (first == n u l l ) throw new NoSuchElement Exception ( ) ; ret urn first . data ; }
public boolean i s Empty( ) { ret urn first == null ;
43
}
}
It is especially easy to mess up the updating of the fi rst and last nodes in a queue. Be sure to dou ble check this. One place where queues a re often used is in breadth-fi rst search or in implementing a cache. I n breadth-fi rst search, for example, we u sed a queue to store a list of the nodes that we need to process. Each time we p rocess a node, we add its adjacent nodes to the back of the queue. This allows us to process nodes in the order in which they a re viewed.
Interview Questions 3.1
Three in One: Describe how you could use a single array to implement three stacks. Hints: #2, # 1 2, #38, #58
3.2
..... .. .
-- . .. .. pg 227
Stack Min: How would you design a stack which, in addition to p u s h and pop, has a fu nction min
which returns the mini m u m element? Pu s h, pop and m i n should all operate in 0 ( 1 ) time. Hints: #27, #59, #78
98
Cracking the Coding I nterview, 6th Edition
Chapter 3 3.3
J Stacks and Queues
Stack of Plates: Imagine a (literal) stack of plates. If the stack gets too high, it might topple. Therefo re, i n real life, we wou ld likely start a new stack when the previous stack exceeds some
be threshold. lmolement a data structure SetOfSta cks that mimics this. s e tO-fS t a c k s should composed of several stacks and should create a new stack once the previous one exceeds capacity. S etOfSta c ks . p u s h ( ) and S etOfSt a c k s . p o p ( ) should behave identically to a single stack (that is, pop ( ) should retu rn the same values as it would if there were just a single stack).
FOLLOW U P I m plement a fu nction popAt ( int i n de x ) which perfOrms a pop operation o n a specific su b-stack. Hints: #64, #8 7
3.4
....... . .
. . .. ..
.
Queue via Stacks: Im plement a MyQueue class which implements a queue using two stacks. Hints: #98, # 1 1 4 -
3.5
. pg 23 3
·-·--··
·····---
-·-�·-- - --·
· · -··
-·--
- _ p g 236
Sort Stack: Write a program to sort a stack such that the smallest items are on the top. You can use an additional temporary stack, but you may not copy the elements into any other data structure (such as a n a rray). The stack supports the fo llowing operations: p u s h, pop, peek, and i s Empty. Hints: # 1 5, #32, #43
3.6
Animal Shelter: An animal shelter, which holds only dogs and cats, operates on a strictly"first in, fi rst out" basis. People must adopt either the "oldest" (based on arrival time) of all animals at the shelter, or they can select whether they would prefer a dog or a cat (and will receive the oldest animal of that type). They cannot select which specific animal they would l i ke . Create the d a t a structures to maintain this system and implement operations such as enqueue, de q ueu e A n y , dequeueDog, and dequeueCat. You may use the built-in L i nked L i s t data structu re. Hints: #22, #56, #63 .. .
_
_ __ _
pg 239
Additional Questions: Lin ked Lists (#2.6), Moderate Problems (#1 6.26), Hard Problems (#1 7.9). Hints start on page 653.
CrackingTheCodinglnterview.com I 6th Edition
99
4 Trees and G raphs
M complicated than searching in a linearly organized data structu re such as an array or linked list. Addi
any interviewees find tree and graph problems to be some of the trickiest. Searching a tree is more
tionally, the worst case and average case time may vary wildly, and we must evaluate both aspects of any algorithm. Fluency in implementing a tree or graph from scratch will prove essential. Because most people a re more fa miliar with trees than graphs (and they're a bit simpler), we'll discuss trees fi rst. This is a bit out of order though, as a tree is actually a type of graph.
I
Note: Some of the terms in this chapter can va ry slightly across different textbooks and other sou rces. If you're used to a different definition, that's fine. Make sure to clear up any a m biguity with your interviewer.
� Types of Trees
A nice way to understa nd a tree is with a recursive explanation. A tree is a data structu re composed of nodes. Each tree has a root node. (Actua lly, this isn't strictly necessa ry in graph theory, but it's usually how we use trees in prog ramming, and especially prog ramming interviews.) •
The root node has zero or more child nodes. Each child node has zero or more child nodes, and so on.
The tree cannot contain cycles. The nodes may or may not be i n a particular order, they cou ld have any data type as values, and they may or may not have links back to their parent nodes. A very simple class definition for Node is: 1 cla s s Node { public St rin g name ; 2 3
4
public Node [ ] c h i l d ren ;
}
You might also have a Tre e class to wrap this node. For the pu rposes of interview questions, we typically do not use a Tree class. You can if you feel it makes you r code simpler or better, but it ra rely does. 1 c l a s s Tree { z public No d e root ; 3
}
1 00
Cracking the Coding Interview, 6th Edition
Chapter 4
I Trees and Graphs
Tree a n d graph questions are rife with am biguous deta ils a n d incorrect assumptions. B e sure t o watch out fo r the following issues and seek cla rification when necessary. Trees vs. Binary Trees
A binary tree is a tree in which each node has up to two children. Not all trees are binary trees. For example, this tree is not a binary tree. You could ca ll it a terna ry tree.
There are occasions when you might have a tree that is not a binary tree. For example, suppose you were using a tree to represent a bunch of phone numbers. I n this case, you might use a 1 0-a ry tree, with each node having up to 1 0 children (one for each digit). A node is called a "leaf" node if it has no children. Binary Tree vs. Binary Search Tree
A binary search tree is a binary tree in which every node fits a specific ordering property: a l l l ef t d e s c endents < = n < a l l right d e s cendents. This must be true for each node n.
I
The definition of a binary search tree can va ry slightly with respect to equal ity. Under some defi nitions, the tree can not have dupl icate va lues. In others, the dupl icate values will be on the right or can be on either side. All are valid defin itions, but you should cla rify this with your interviewer.
Note that this inequal ity must be true for all of a node's descendents, not just its immediate children. The fol lowing tree on the left below is a binary search tree. The tree on the right is not, since 1 2 is to the left of 8. A binary search tree.
Not a binary search tree.
When given a tree question, many candidates assume the interviewer means a binary search tree. Be sure to ask. A binary search tree im poses the condition that, for each node, its left descendents are less than or equal to the cu rrent node, which is less than the right descendents. Balanced vs. Unbalanced
While many trees are ba lanced, not all a re. Ask you r interviewer for clarification here. Note that balancing a tree does not mean the left and right su btrees are exactly the same size (like you see under "perfect binary trees" in the following diagram).
Cracking TheCodinglnterview.com j 6th Edition
101
Chapter 4
I Trees a nd Graphs
One way to think about it is that a "bala nced" tree really means someth ing more like "not terribly imba l anced:' It's balanced enough to ensure 0 ( log n ) times for i n s e rt and find, but it's not necessa rily as ba la nced as it cou ld be. Two common types of bala nced trees a re red-black trees (pg 639) and AVL trees (pg 637). These a re discussed in more deta il in the Adva nced Topics section. Complete Binary Trees
A com plete binary tree is a bi nary tree in which every level of the tree is fully filled, except for perhaps the last level.1o the extent that the last level is filled, it is fi lled left to right. not a com plete binary tree
a complete binary tree
3
Full Binary Trees
A full binary tree is a binary tree in which every node has either zero or two chi ldren. That is, no nodes have only one child. not a fu l l binary tree
a fu ll binary tree
Perfect Binary Trees
A perfect bi nary tree is one that is both ful l and com plete. All leaf nodes will be at the same level, and this level has the maximum number of nodes.
Note that perfect trees are ra re in interviews and in rea l life, as a perfect tree must have exactly 2 k - 1 nodes (where k is the number of levels). In an interview, do not assume a binary tree is perfect.
1 02
Cracking the Coding Interview, 6th Edition
Chapter 4
I Trees and Graphs
� Binary Tree Traversal
Prior to your interview, you should be comfortable implementing in-order, post-order, and pre-order traversa l. The most common of these is in-order traversal. In-Order Traversal
In-order traversal means to "visit" (often, print) the left branch, then the cu rrent node, and finally, the right bra nch. 1
2 3
4 5 6
7
void inOrderTraversa l ( TreeNode node) { if ( node ! = n u l l ) { inOrderTravers a l ( node . left ) ; visit ( node ) ; i nOrderTraversal ( node . right ) ; } }
When performed on a binary sea rch tree, it visits the nodes in ascending order (hence the name "in-order"). Pre-Order Traversal
Pre-order traversal visits the cu rrent node before its child nodes (hence the name "pre-order").
1
2
vo id preOrderTravers a l (TreeNode node) { if ( node ! = n u l l ) {
visit ( node ) ;
3
4
preOrderTravers a l ( node . left ) ; preOrderTravers a l ( node . right ) ;
5
6
7
}
}
In a pre-order traversal, the root is a lways the first node visited. Post-Order Traversal
Post-order traversal visits the cu rrent node after its child nodes (hence the name "post-order"). 1
2 3
4 5
6
7
void postOrderTrave r s a l ( TreeNode nod e ) { if ( node ! = n u l l ) { postOrderTravers a l ( node . left ) ; postOrderTravers a l ( node . r ight ) ; visit ( node ) ; } }
In a post-order traversal, the root is always the last node visited. � Binary Heaps (Mi n-Heaps and Max-Heaps)
We'l l j ust discuss min-heaps here. Max-heaps are essentially equ ivalent, but the elements are in descending order rather than ascending order. A
min-heap is a complete binary tree (that is, totally filled other than the rightmost elements on the last level) where each node is smaller than its children. The root. therefore, is the minimum element in the tree.
Cracki ngTheCodingl nterview.corn I 6th Edition
1 03
Chapter 4
I Trees and Graphs
We have two key operations on a min-heap: i n s e rt a nd ext ra c t_min. Insert
When we insert into a min-heap, we always start by inserting the element at the bottom. We insert at the rightmost spot so as to maintain the complete tree property. Then, we "fix" the tree by swapping the new element with its pa rent, until we fi nd a n appropriate spot for the element. We essentia lly bubble up the minimum element. Step 1 : I nsert 2
Step 2: Swap 2 a nd 7
Step 3: Swap 2 and 4
This ta kes 0 ( log n ) time, where n is the number of nodes in the heap. Extract Minimum Element
Finding the minimum element of a min-heap is easy: it's always at the top. The trickier part is how to remove it. (In fact, this isn't that tricky.) First, we remove the minimum element and swap it with the last element in the heap (the bottommost, rightmost element). Then, we bubble down this element, swapping it with one of its children until the min heap property is restored. Do we swa p it with the left child or the right child? That depends on their va lues. There's no inherent ordering between the left and right element, but you'l l need to take the smaller one in order to mainta i n the min-heap ordering. Step 1 : Replace min with 80
Step 2: Swa p 23 and 80
: 80 : . . .
This a lgorithm will also ta ke O ( log n ) time.
1 04
Cracking the Coding Interview, 6th Edition
Step 3: Swa p 32 and 80
Chapter 4
I Trees and Graphs
,.. Tries (Prefix Trees)
A trie (sometimes called a prefix tree) is a funny data structu re. It comes up a lot i n interview questions, but algorithm textbooks don't spend much time on this data structure. A trie is a variant of an n-a ry tree in which characters a re stored at each node. Each path down the tree may represent a word. The * nodes (sometimes cal led "nu l l nodes") a re often used to indicate complete words. For example, the fact that there is a * node under MANY ind icates that MANY is a complete word. The existence of the MA path indicates there are words that start with MA. The actual implementation of these * nodes might be a special type of child (such as a Termi n a t i n gTrieNode, which inherits from Tri eNode). Or, we could use just a boolean flag terminates within the "parent" node. _
A node in a trie could have a nywhere from 1 through ALPHABET S I Z E ALPHABET S I ZE if a boolean flag is used instead of a * node). _
+
1 children (or, 0 through
Very common ly, a trie is used to store the entire (English) language for quick prefix lookups. While a hash table can quickly look up whether a string is a va lid word, it cannot tel l u s if a string i s a prefix of any valid words. A trie can do this very quickly.
I
How quickly? A trie can check if a string is a valid prefix in 0 ( K ) time, where K is the length of the stri ng. This is actually the same runtime as a hash table wi l l take. Although we often refer to hash table lookups as being 0 ( 1) time, this isn't entirely true. A hash table must read through all the characters in the input, which ta kes 0 ( K ) time in the case of a word lookup.
Many problems involving lists of valid words leverage a trie as an optimization. In situations when we search through the tree on related prefixes repeatedly (e.g., looking up M, then MA, then MAN, then MANY), we might pass around a reference to the cu rrent node in the tree. This wi l l allow us to just check if Y is a child of MAN, rather than starting from the root each time.
� Graphs A tree is actually a type of graph, but not all graphs are trees. Simply put, a tree is a connected graph without cycles. A graph is simply a collection of nodes with edges between (some of) them. •
Graphs can be either directed (like the fol lowing graph) or undirected. While d irected edges a re like a
CrackingTheCodingl nterview.com I 6th Edition
1 OS
Chapter 4
l Trees and Graphs
one-way street, undirected edges are like a two-way street. •
The graph might consist of multiple isolated subgraphs. If there is a path between every pa ir of vertices, it is called a "connected graph:' The graph can also have cycles (or not). An "acyclic graph" is one without cycles.
Visual ly, you could d raw a graph l i ke this:
In terms of programmi ng, there are two common ways to represent a graph. Adjacency List
This is the most common way to represent a graph. Every vertex (or node) stores a list of adjacent vertices. In an undirected graph, an edge like ( a , b ) would be stored twice: once in a's adjacent vertices and once in b's adjacent vertices. A simple class defi nition for a graph node could look essentially the same as a tree node. 1 c l ass Graph { 2 publ ic Node [ ] node s ; 3
4 5
6 7
8
}
c lass Node { public String name ; public Node [ ] chi ld ren; }
The Graph class is used because, unlike in a tree, you ca n't necessa rily reach all the nodes from a single node. You don't necessa rily need any add itional classes to represent a graph. An array (or a hash table) of lists (a rrays, a rraylists, linked lists, etc.) can store the adjacency list. The graph a bove could be represented as: 0: 1 1: 2 2 : 0, 3 3: 2 4: 6 5: 4 6: 5
This is a bit more compact, but it isn't qu ite as clean. We tend to use node classes unless there's a compelling reason not to. Adjacency Matrices
An adjacency matrix is an NxN boolean matrix (where N is the number of nodes), where a true value at mat rix [ i ] [ j ] ind icates an edge from node i to node j. (You can also use an integer matrix with Os and 1 s.) In an u n d i rected g raph, an adjacency matrix will be symmetric. In a d i rected g raph, it will not (necessarily) be.
1 06
Cracking the Coding I nterview, 6th Edition
Chapter 4 I Trees and Graphs
The same graph a lgorithms that are used on adjacency lists (breadth-first search, etc.) can be performed with adjacency matrices, but they may be somewhat less efficient. In the adjacency list representation, you can easily iterate through the neig hbors of a node. In the adjacency matrix representation, you wil l need to iterate through all the nodes to identify a node's neighbors. � Graph Sea rch
The two most common ways to search a graph are depth-fi rst search and breadth-fi rst sea rch. In depth-fi rst sea rch (DFS), we start at the root (or another a rbitrarily selected node) and explore each branch completely before moving on to the next branch. That is, we go deep first (hence the name depth first search) before we go wide. In breadth-fi rst search (BFS), we start at the root (or another a rbitrarily selected node) and explore each neighbor before going on to any of their children. That is, we go wide (hence breadth-first search) before we go deep. See the below depiction of a graph and its depth-fi rst and breadth-fi rst search (assuming neighbors are iterated in numerica l order) . Graph
Depth-First Search
1
2 3
4
5
6
Node 0 Node 1 Node 3 Node 2 Node 4 Node 5
Breadth-First Search
1 2
3
4
5
6
Node Node Node Node Node Node
0 1
4
5 3
2
Breadth-fi rst search and depth-first sea rch tend to be used i n different scena rios. DFS is often preferred if we want to visit every node in the graph. Both will work just fine, but depth-first sea rch is a bit simpler. However, if we want to find the shortest path (or just any path) between two nodes, BFS is genera l ly better. Consider representing all the friendships in the entire world in a graph and trying to find a path of friend ships between Ash and Vane s s a . l n depth-fi rst search, we cou ld ta ke a path like A s h - > B r i a n - > C a rleton - > Davi s - > E r i c - > F a ra h - > G a y l e - > H a r ry - > I s a be l l a - > J o hn - > K a r i ... and then find ou rselves very fa r away. We could go through most of the world without realizing that, in fact, V a ne s s a is A s h 's friend. We will sti l l eventually find the path, but it may ta ke a long time. It also won't find u s the shortest path. In breadth-first search, we would stay close to Ash for as long as possible. We might iterate through many of As h's friends, but we wouldn't go to his more distant connections unti l a bsolutely necessa ry. If Vane s s a i s A s h's friend, o r his friend-of-a-friend, we'll fi n d this out relatively q uickly.
CrackingTheCod ingl nterview.com j 6th Edition
1 07
Cha pter 4
I Trees and Graphs
Depth-First Search (DFS)
{ n DFS, we visit a node a and then iterate through each of a's neighbors. When visiting a node b that is a neighbor of a, we visit a l l of b's neighbors before going on to a 's other neighbors. That is, a exhaustively sea rches b's branch before any of its other neighbors. Note that pre-order and other forms of tree traversal a re a form of DFS. The key difference is that when implementing this alg orithm for a graph, we must check if the node has been visited. If we don't. we risk getti ng stuck in an infinite loop. The pseudocode below im plements DFS. 1
2 3
4 5
6 7
8
9
10
void s e a rc h ( Node root ) { if ( root == null ) ret u r n ; v i s it ( root ) ; root . visited = t rue; for each ( Node n in root . ad j acent ) { if ( n . vi s ited == false) { search ( n ) ; } } }
Breadth-First Search (BFS)
BFS is a bit less intuitive, and many interviewees struggle with the im plementation unless they a re already familiar with it. The main tri pping point is the (false) assumption that BFS is recu rsive. It's not. Instead, it uses a queue. In BFS, node a visits each of a's neighbors before visiti ng any of their neighbors. You can think of this as sea rching level by level out from a. An iterative solution involving a queue usually works best.
1 2
void s e a rc h ( Node root ) { Queue queue = new Queue ( ) ; root . ma rked true; queue . enqueue ( root ) ; / / Add to the end of queue
3
=
4 5 6 7
8
9
10 11
12 13
14 15 16
}
while ( ! queue . is Empty ( ) ) { Node r = queue . dequeue ( ) ; / / Remove from the front of the queue visit ( r ) ; foreach ( Node n in r . a d j acent ) { if ( n . ma rked == fa l s e ) { n . ma rked = t rue ; queue . enqueue ( n ) ; } } }
If you a re asked to im plement BFS, the key thing to remember is the use of the queue. The rest of the algo rithm flows from this fact. Bidirectional Search
Bidirectional search is used to find the shortest path between a source and desti nation node. It operates by essentially ru nning two simultaneous breadth-fi rst searches, one from each node. When their sea rches collide, we have found a path.
1 08
Crack i n g the Cod i n g I nterview, 6th Edition
Chapter 4
I Trees and Graphs
Bidirectional Search
Breadth-First Search
Two sea rches (one from s and one from t ) that collide after fou r levels total (two levels each).
Single search from s to t that collides after four levels.
10 see why this
is fa ster, consider a graph where every node has at most k adjacent nodes and the shortest path from node s to node t has length d . I n traditional breadth-fi rst search, we would sea rch u p to k nodes in the fi rst "level" of the search. In the second level, we would search up to k nodes for each of those fi rst k nodes, so k2 nodes total (thus fa r). We wou ld do this d times, so that's 0( kd) nodes. In bidi rectional search, we have two sea rches that collide after approximately ri levels (the midpoint of the path). The search from s visits approximately kd12 , as does the search from t. That's approximately 2 k a 12 , or 0( ka'2 ) , nodes total.
Th is might seem like a minor d ifference, but it's not. It's huge. Reca l l that ( kd12 ) * ( kd12 ) tional sea rch is actually fa ster by a fa ctor of kd12 .
kd . The bidi rec
=
Put another way: if our system could only support sea rching "friend of friend" paths in breadth-fi rst search, it could now likely support "friend of friend of friend of friend" paths. We can support paths that a re twice as long. Additional Reading: Topological Sort (pg 632), Dijkstra's Algorithm (pg 633), AVL Trees (pg 637), Red Black Trees (pg 639).
I nterview Questions 4.1
Route Between Nodes: Given a directed g ra ph, design an a lgorithm to find out whether there is a route between two nodes. Hints: # 127 ---· - ------- - · · · ·----- ·-
4.2
_pg 24 1
Minimal Tree: Given a sorted (i ncreasing order) a rray with unique i nteger elements, write an algo
rithm to create a binary sea rch tree with minimal height. Hints: # 1 9, #73, # 1 1 6 -----
4.3
··-··-··-··-··-·-·-
-----
--
·-·-·
-
pg 242
list of Depths: Given a binary tree, design a n algorithm which creates a l i n ked list of a l l the nodes at each depth (e.g., if you have a tree with depth D, you'l l have D linked lists). Hints: # 1 07, # 723, # 1 35 ................... .. .
___ _ _
CrackingTheCodingl nterview.com I 6th Edition
pg 243
1 09
Chapter 4 4.4
I Trees a nd Graphs
Check Balanced: Implement a function to check if a binary tree is balanced. For the pu rposes of this question, a bala nced tree is defi ned to be a tree such that the heights of the two subtrees of a ny node never d iffer by more than one. Hints: #2 1, #33, #49, # 1 05, # 124 _ _ _ _ __
4.5
Validate BST: Implement a fu nction to check if a binary tree is a binary search tree. Hints: #35, #57, #86, # 1 13, # 128
4.6
pg 244
.
· · · · ··
pg 245
Successor: Write an a lgorithm to find the "next" node (i.e., in-order successor) of a given node in a
binary search tree. You may assume that each node has a link to its parent. Hints: #79, #9 1
pg 248
4.7
Build Order: You a re given a list of projects and a list of dependencies (which is a list of pairs of projects, where the second project is dependent on the fi rst project). All of a project's dependencies must be built before the project is. Find a build order that will allow the projects to be built. If there is no valid build order, retu rn an error.
EXAMPLE Input: proj e cts : a , b , c , d , e , f dependenc i e s : ( a , d ) , ( f , b ) , ( b , d ) , ( f , a ) , ( d , c )
Output: f , e , a , b , d , c Hints: #26, #47, #60, #85, # 7 25, # 7 33
pg 250
4.8
First Common Ancestor: Design an algorithm and write code to find the fi rst common ancestor of two nodes in a binary tree. Avoid storing add itional nodes in a data structure. N OTE: This is not necessarily a binary search tree. Hints: # 1 0, # 1 6, #28, #36, #46, #70, #80, #96
pg257
4.9
BST Sequences: A binary search tree was created by traversing through an a rray from left to right and inserting each element. Given a binary search tree with distinct elements, print all possible arrays that cou ld have led to this tree.
EXAMPLE In put:
Output: { 2 ,
1,
3 }, { 2 , 3 , 1 }
Hin ts: #39, #48, #66, #82
pg 262
11O
Cracking the
Coding I nterview, 6th Edition
Chapter 4 4.1 0
I Trees and Graphs
Check Subtree: T l and T 2 are two very large binary trees, with T l much bigger than T2. Create a n
algorithm to determine if T 2 i s a subtree o f Tl. A tree T2 is a subtree of Tl ifthere exists a node n in Tl such that the subtree of n is id entica l to T 2 . That is, i f you cut off t h e tree a t node n, t h e two trees would b e identica l. Hints: #4, # 1 1 , # 1 8, #3 7, #37 ___
4.1 1
pg 2 6 5
Random Node: You a re implementing a binary tree class from scratch which, in addition to
i n s e rt, f i nd, and delete, has a method getRa ndomNode ( ) which returns a random node from the tree. All nodes should be equa lly l i kely to be chosen. Design and i m plement a n a lgorithm for getRa ndomNode, and explain how you wou ld i m plement the rest of the methods. Hints: #42, #54, #62, #75, #89, #99, # 1 72, # 1 7 9 .
4.1 2
. . ..
. .
. .
·· · ·
_ pg 268
·· · ·· ·· · ··· · · · ··· -···· ··-·- · ----
Paths with Sum: You are given a binary tree i n which each node conta ins a n integer va lue (which
might be positive or negative). Design an a lgorithm to count the nu mber of paths that sum to a given va lue. The path does not need to sta rt or end at the root or a leaf, but it must go downwa rds (traveling only from parent nodes to child nodes). Hints: #6, # 7 4, #52, #68, #77, #87, #94, # 7 03, # 7 08, # 1 1 5
- pg 272
Additional Questions: Recursion (#8.1 0), System Design and Sca lability (#9.2, #9.3), Sorting and Sea rching (#1 0. 1 0), Hard Problems (#1 7.7, #1 7.1 2, # 1 7 . 1 3, #1 7 . 1 4, # 1 7.1 7, # 1 7.20, # 1 7.22, # 1 7.25). Hi nts sta rt on page 653.
CrackingTheCodingl nterview.com I 6th Edition
111
5 Bit M a n i p u lation
B lation. Other times, it's sim ply a usefu l technique to optimize your code. You should be comforta ble
it manipu lation is used in a variety of problems. Sometimes, the question explicitly calls for bit manipu
doing bit manipu lation by hand, as well as with code. Be ca refu l; it's easy to make little mista kes. � Bit Ma nipulation By Hand
If you're rusty on bit manipu lation, try the following exercises by hand. The items in the third column can be solved manually or with "tricks" (described below). For simplicity, assume that these a re four-bit num bers. If you get confused, work them through as a base 1 0 num ber. You can then apply the same process to a binary num ber. Remember that A indicates a n XOR, and - is a NOT (negation). 0110 + 0010 0011 + 0010 0110 - 0011 1000 - 0110
0011 * 0101 0011 * 0011 1101 » 2 1101 A 0101
0110 + 0110 0100 * 0011 1101 /\ (-1101 ) 1011 & (-0 < < 2 )
Solutions: line 1 (1 000, 1 1 1 1 , 1 1 00); line 2 (01 0 1 , 1 00 1 , 1 1 00); line 3 (001 1 , 001 1 , 1 1 1 1 ) ; line 4 (00 1 0, 1 000, 1 000) .
The tricks in Column 3 are as fol lows: 1 . 0110
+
0 1 1 0 is equivalent to 0 1 1 0 * 2, which is equivalent to shifting 01 1 0 left by 1 .
2. 0100 equals 4, and mu ltiplying by 4 is just left shifting by 2. So we shift 0011 left by 2 to get 1 1 0 0 . 3. Thi n k a bout this operation b i t b y bit. If you XOR a b i t with i t s own negated va lue, you will always get 1 . Therefore, the sol ution to a A ( -a ) will be a seq uence of 1 s. 4. -0 is a sequence of l s, so -0 < < 2 is 1 s fol lowed by two Os. AN Ding that with another va lue will clear
the last two bits of the va lue.
If you didn't see these tricks immediately, think a bout them logica l ly. � Bit Facts and Tricks
The following expressions a re usefu l in bit manipu lation. Don't just memorize them, though; think deeply about why each of these is true. We use "l s" and "Os" to indicate a seq uence of 1 s or Os, respectively. x x
" "
0s = x
l s = -x
x " x = 0
112
x & 0s
x & ls
0 x
x & x = x
Cracking the Coding Interview, 6th Edition
es
I
x
I
x x
I
ls
x
=
x
ls
x
Chapter s
I
Bit M a n ipu lation
To u ndersta nd these expressions, recall that these operations occur bit-by-bit, with what's ha ppening on one bit never im pacting the other bits. This means that if one of the above statements is true for a single bit, then it's true for a sequence of bits . ., Two's Complement and Negative N u m bers
Com puters typically store integers in two's complement representation. A positive number is represented as itself while a negative number is represented as the two's complement of its a bsolute value (with a 1 in its sign bit to indicate that a negative va lue). The two's complement of a n N-bit number (where N is the number of bits used for the num ber, excludin g the sign bit) is the complement of the number with respect to 2N. Let's look at the 4-bit integer - 3 as an example. If it's a 4-bit number, we have one bit for the sign and three bits for the value. We want the complement with respect to 23, which is 8. The complement of 3 (the abso l ute value of - 3 ) with respect to 8 is 5 . 5 in binary is 101. Therefore, -3 in binary as a 4-bit number is 1 1 0 1 , with the fi rst bit being the sign bit. In other words, the binary representation of -K (negative K) as a N-bit number is c on c a t ( 1 , 2N-1 - K ) . Another way to look at this is that we invert the bits in the positive representation and then add 1. 3 is 011 in binary. Flip the bits to get 100, add 1 to get 101, then prepend the sign bit (1) to get 1101. In a fou r-bit integer, this would look l i ke the following.
7
� 111
-1
1 111
6
� 110
-2
1 11 0
5
� 101
-3
1 101
4
� 100
-4
1 100
3
� 011
-5
1 011
2
� 010
-6
1 010
1
� 001
-7
1 001
0
� 000
Observe that the absolute values of the i ntegers on the left and right always sum to 23, and that the binary values on the left and rig ht sides are identica l, other than the sign bit. Why is that? ., Arith metic vs. Logical Right Shift
There a re two types of rig ht shift operators. The arithmetic rig ht shift essentia l ly divides by two. The logical right shift does what we wou ld visually see as shifting the bits. This is best seen on a negative number. I n a logica l rig ht shift, we shift the bits and put a 0 in the most significant bit. It is indicated with a > » operator. On an 8-bit integer (where the sign bit i s the most significant bit), this would look l i ke the image below. The sign bit is indicated with a gray background. 1
= -75
0
= 90
Cracki ngTheCodinglnterview.com I 6th Edition
113
Chapter 5 I Bit Manipulation In a n arithmetic right shift, we shift va lues to the right but fill in the new bits with the value of the sign bit. This has the effect of (roughly) dividing by two. It is ind icated by a > > operator.
What do you think these functions would do on parameters x 1
2 3
4 5
6
7
=
- 9 3 242 and count
=
40?
int repeatedArithmeticSh ift ( i nt x , int count ) { for ( int i = 0 ; i < count ; i++) { x > > = 1 ; / / Arithmet ic shift by 1 } ret urn x ; }
8
int re peatedlogicalShift ( int x, int count ) { for ( i nt i = 0 ; i < count ; i++ ) { 1� x > > > = 1 ; / / Logical s hift b y 1 11 } 12 ret urn x ; 13 }
9
With the logica l shift, we would get 0 because we are shifting a zero into the most significant bit repeated ly.
With the a rith metic shift, we wou ld get - 1 because we are shifti ng a one into the most significant bit repeatedly. A sequence of all ls in a (signed) integer represents - 1 . ., Common Bit Tasks: Getting a n d Setting
The following operations are very im porta nt to know, but do not sim ply memorize them. Memorizing leads to mista kes that are im possible to recover from. Rather, understa nd how to im plement these methods, so that you can implement these, and other, bit prob lems. Get Bit
This method sh ifts 1 over by i bits, creating a value that looks like 00010000. By performing an AND with num, we clear a l l bits other than the bit at bit i. Finally, we com pare that to 0. If that new value is not zero, then bit i must have a 1. Otherwise, bit i is a 0. 1
2 3
boolean getBit ( int num, int i ) { ret urn ( ( num & ( 1 « i ) ) ! = 0) ; }
Set Bit
Set B i t shifts 1 over by i bits, creating a va lue like 00010000. By performing a n OR with num, only the va lue at bit i wi ll cha nge. All other bits of the mask are zero and will not affect num. int setBit ( i nt num, int i ) { 1 2 return num I (1 « i ) ;
3
}
114
Cracking the Cod i n g I nterview, 6th Edition
Chapter 5
I Bit Manipulation
Clear Bit
This method operates in al most the reverse of set Bit. First we create a number l i ke 11101111 by creating the reverse of it (00010000) and negating it. Then, we perform a n AND with num. This will clear the ith bit and leave the remainder unchanged. int clearBit ( int num, int i ) { 1 int mas k = � ( 1 < < i ) ; 2 3 return num & mas k ; }
4
To clear all bits from the most significant bit through i (inclusive), we create a mask with a 1 at the i th bit (1 < < i). Then, we su btract 1 from it. giving us a sequence of 0s followed by i ls. We then AND our number with this mask to leave just the last i bits. 1 int clearBitsMSBth roughI ( int num, int i ) { 2 int mas k = ( 1 < < i ) - 1 ; return num & mas k ; 3 4
10 +
1
2 3
4
}
clear a l l bits from i through 0 (inclusive), we ta ke a sequence of all l s (which is - 1) and shift it left by i 1 bits. This gives us a sequence of 1 s (in the most sign ificant bits) followed by i 0 bits. int c learBit sithrough0 ( int num, int i ) { int mas k = ( - 1 < < ( i + 1 ) ) ; return num & mas k ; }
Update Bit
To set the ith bit to a va lue v, we first clear the bit at position i by using a mask that looks like 11101111. Then, we shift the intended value, v, left by i bits. This wil l create a number with bit i equal to v and a l l other bits equal t o 0 . Finally, w e OR these two numbers, u pdating the i t h b i t i f v is 1 a n d leaving it as 0 otherwise. int updateBit ( int num, int i , boolean bit i s l ) { 1 2 int va lue = bit i s l ? 1 : 0; 3 int mas k = � ( 1 < < i ) ; 4 return (num & mas k ) J ( value < < i ) ; 5
}
I nterview Questions 5.1
Insertion: You a re given two 32-bit numbers, N and M, and two bit positions, i and j. Write a method to insert M into N such that M starts at bit j and ends at bit i. You
can assume that the bits j through i have enough space to fit all of M. That is, if 10011, you can assume that there a re at least 5 bits between j and i. You would not, for exam ple, have j 3 and i 2, because M could not fu lly fit between bit 3 and bit 2 .
M
=
=
=
EXAM PLE
Input:
N
Output: N
10000000000, =
M
=
10011 , i
2, j
6
10001001100
Hints: # 1 3 7, # 1 69, #2 1 5
_ pq 276
CrackingTheCodinglnterview.com J 6th Edition
115
Chapter 5 5.2
I Bit Manipulation
Binary to String: Given a real number between 0 and 1 (e.g., 0.72) that is passed in as a double, print the binary representation. If the number cannot be represented accu rately in binary with at most 32 cha racters, print "ERROR:' Hin ts: # 1 43, # 1 67, # 1 73, #269, #297
'' - -- --- - - - ,,,, 5.3
pg 277
Flip Bit to Win: You have an integer and you can flip exactly one bit from a 0 to a 1. Write code to find the length of the longest sequence of ls you could create.
EXAMPLE I n put:
1775
Output:
8
( o r : 110111011 1 1 )
Hints: # 1 59, #226, #3 1 4, #352
. . ... pg 278 5.4
Next Number: Given a positive integer, print the next smallest and the next la rgest number that have the same number of 1 bits in their binary representation. Hints: # 1 47, # 1 75, #242, #3 12, #339, #358, #3 75, #390
, ,, , , , , ,, ,.,
5.5
...... . -·----· - ···· - --· ·· ··· ·-····· '
' ' ' ,,,,,,,,,,,,._ --------·--
Debugger: Explain what the fol lowing code does: ( ( n & ( n - 1 ) )
==
pg 280
0).
Hints: # 1 5 1, #202, #26 1, #302, #346, #372, #383, #398 _ ________ ___ __
5.6
pg 285
Conversion: Write a fu nction to determ ine the number of bits you would need to flip to convert
integer A to integer B . EXAMPLE I nput:
29 ( o r : 11101 ) , 15 ( o r : 01111 )
Output:
2
Hints: #336, #369
pg 286 5.7
Pairwise Swap: Write a program to swap odd a nd even bits in an integer with as few instructions as
possible (e.g., bit 0 and bit 1 are swa pped, bit 2 and bit 3 a re swa pped, and so on).
Hin ts: # 1 45, #248, #328, #355
pg 2 8 6 5.8
Draw Line: A monochrome screen is stored as a single a rray of bytes, a l lowing eight consecutive
pixels to be stored in one byte. The screen has width w, where w is divisible by 8 (that is, no byte will be split across rows). The height of the screen, of cou rse, can be derived from the length of the array and the width. Im plement a fu nction that draws a horizontal line from ( x l , y ) to ( x 2 , y ) . The method signature should look something like: draw l i ne ( byte [ ] s c reen , int widt h , int x l , int x 2 , int y ) Hints: #366, #38 1, #384, #39 1
'''''''' - - - pg 2 87
Additional Questions: Arrays and Stri ngs (#1 .1 , #1 .4, #1 .8), Math and Logic Puzzles (#6.1 O), Recursion (#8.4, #8. 1 4), Sorting and Searching (#1 0.7, #1 0.8), C++ (#1 2.1 O), Moderate Problems (#l 6.1 , # 1 6.7), Hard Problems (#1 7.1 ) .
Hints sta rt o n page 662.
116
Cracking the Coding Interview, 6th Edition
6 Math a n d Log ic Puzzles
S have policies banning them. U nfortunately, even when these q uestions are banned, you sti l l may find
o-cal led "puzzles" (or bra i n teasers) are some of the most hotly debated q uestions, and many companies
yourself being asked one of them. Why? Because no one can agree on a definition of what a brai nteaser is.
The good news is that if you are asked a puzzle or brainteaser, it's likely to be a reasonably fair one. It prob ably won't rely on a trick of word i ng, and it can a l most a lways be logically deduced. Many have their foun dations in mathematics or computer science, and almost all have solutions that ca n be logically deduced. We'l l go through some common approaches for tackl ing these q uestions, as well as some of the essentia l knowledge. � Prime N u m bers As you
probably know, every positive i nteger can be decom posed into a product of primes. For exa mple: 84 =
*
22
31
*
5°
*
71
*
1 1°
*
13°
*
17°
*
. . .
Note that many of these pri mes have a n exponent of zero. Divisibility
The prime number law stated a bove means that, in order for a number x to divide a number y (written x\y, or mod ( y , x ) = 0), a l l primes in x's prime factorization m ust be in y's prime factorization. Or, more specifically: 2j 0
Let x
*
3 j1
*
lf x \y, then for all i, j i
5 j 2 * 7j3
Algorith m Approaches
If you're stuck, consider applying one of the a pproaches fo r solving a lgorithm questions (sta rti ng on page 67). Brainteasers are often nothing more than algorithm questions with the technical a spects removed. Base Case and Build and Do It You rself (DIY) can be especially useful. Additional Reading: Usefu l Math (pg 629).
Interview Questions 6.1
The Heavy Pill: You have 20 bottles of pills. 1 9 bottles have 1 .0 gram pil ls, but one has pills of weight
1 . 1 grams. Given a scale that provides an exact measu rement. how wou ld you find the heavy bottle? You can only use the scale once.
Hin ts: # 786, #252, #3 7 9, #387
1 22
Cracki ng the Coding Interview, 6th Edition
.. . . .. .. . . p;i .!B9
Chapter 6 6.2
I Math and Logic Puzzles
Basketball: You have a basketball hoop a n d someone says that you can play one of two games.
G ame
1:
You get one shot to make the hoop.
Game 2; You get three shots a nd you have to make two of three shots.
If p is the probability of making a particular shot for wh ich values of p should you pick one game or the other? Hints: # 7 8 7, #239, #284, #323
6.3
pg 290
Dominos: There is a n 8x8 chessboard i n which two diagonally opposite corners have been cut off. You are given 3 1 dominos, and a single domino can cover exactly two squares. Ca n you use the 3 1 dominos to cover the entire board? Prove your answer (by providing an example o r showing why it's impossible). Hints: #367, #397
pg29 1
6.4
Ants on a Triangle: There a re three ants on d ifferent vertices of a triangle. What is the probability of
collision (between any two or all of them) if they start walking on the sides of the triangle? Assume that each a nt randomly picks a direction, with either direction being equally likely to be chosen, and that they wa lk at the same speed. Simila rly, fi nd the proba bility of collision with n ants on an n-vertex polygon. Hints: # 1 57, # 1 95, #296 . . .. ... . . .
6.5
-· · ····· · · · · · · · --· · ··· ·- ··-·· ·
--- · · · · ·
·-··- · ·- ··-··- · ·- ··-··- · ·- ·· - ·····
pg 29 1
Jugs of Water: You have a five-quart j ug, a three-q uart jug, a nd an unlimited supply of water (but
no measuring cups). How would you come u p with exactly four quarts of water? Note that the jugs are oddly shaped, such that fil l i ng up exactly "ha lf" of the jug would be impossible. Hints: # 1 49, #379, #400
pg 2 92
6.6
Blue-Eyed Island: A bunch of people are living on an island, when a visitor comes with a strange
order: all blue-eyed people must leave the island as soon as possible. There will be a flight out at 8:00 pm every evening. Each person ca n see everyone else's eye color, but they do not know their own (nor is a nyone allowed to tell them). Additionally, they do not know how many people have blue eyes, a lthough they do know that at least one person does. How many days will it take the blue-eyed people to leave? Hints: #2 1 8, #282, #34 1, #370
6.7
The Apocalypse: In the new post-apoca lyptic world, the world queen is desperately concerned
about the birth rate. Therefore, she decrees that all families should ensure that they have one girl or else they face massive fines. If all families abide by this policy-that is, they have continue to have children until they have one girl, at which point they immed iately stop-what will the gender ratio of the new generation be? (Assume that the odds of someone having a boy or a girl on any g iven pregna ncy is equa l.) Solve this out logica lly and then write a computer simulation of it. Hints: # 1 54, # 1 60, # 1 71, # 1 88, #20 1 ····· · ·· ·· ·· ····· ·· · ·
· ·· ·· · · ·· ··· ··
· · · · ······
CrackingTheCodinglnterview.com I 6th Edition
· · · · · ·· ·
pg 2 9 3
1 23
Cha pter 6 6.8
I Math and Logic Puzzles
There is a building of 1 00 floors. If an egg drops from the Nth floor or a bove, it will b rea k. If it's dropped from any floor below, it will not break. You' re given two eggs. Find N, while minimizing the number of drops for the worst case.
The Egg Drop Problem:
Hints: # 1 56, #233, #294, #333, #357, #374, #395
pg 296 6.9
1 00 Lockers: There a re 1 00 closed lockers in a hallway. A man beg ins by open ing a l l 1 00 lockers. Next, he closes every second locker. Then, on his third pass, he toggles every third locker (closes it if it is open or opens it if it is closed). Th is process continues for 1 00 passes, such that on each pass i, the man togg les every ith locker. After his 1 OOth pass in the hallway, in which he toggles only locker #1 00, how many lockers are open? Hints: # 1 39, # 1 72, #264, #306
p g297
6.1 0
Poison: You have 1 000 bottles of soda, and exactly one is poisoned. You have 1 0 test strips which can be used to detect poison. A single drop of poison will turn the test strip positive permanently. You can put any number of drops on a test strip at once and you can reuse a test strip as many times as you'd l i ke (as long as the resu lts a re negative). However, you can on ly run tests once per day and it takes seven days to retu rn a resu lt. How wou ld you figure out the poisoned bottle in as few days as possible?
FOLLOW U P Write code t o simulate you r approach. Hints: # 1 46, # 1 63, # 1 83, # 1 9 1, #205, #22 1, #230, #24 1, #249
- pg 2 98
Additional Problems: Moderate Problems (#1 6.5), Hard Problems (#1 7.1 9) Hints sta rt on page 662.
1 24
Cracking the Coding Interview, 6th Edition
7 Object-Oriented Design
O ment technical problems or real-life objects. These problems give-or at least are believed to give bject-oriented design questions require a candidate to sketch out the classes and methods to imple
an interviewer insight into you r coding style.
These questions are not so much a bout reg urg itating design patterns as they are about demonstrating that you u ndersta nd how to create elega nt, maintainable object-oriented code. Poor performance on this type of question may raise serious red flags. � How to Approach
Regard less of whether the object is a physical item or a technical task, object-oriented design questions can be tackled i n similar ways. The following a pproach will work well for many problems. Step 1 : Handle Ambiguity
Object-oriented design (OOD) questions are often intentiona lly vague i n order to test whether you'll make assum ptions or if you'll ask clarifying questions. After all, a developer who just codes something without understa nding what she is expected to create wastes the company's time and money, and may create much more serious issues. When being asked an object-oriented design q uestion, you should inquire who is going to use it and how they are going to use it. Depending on the q uestion, you may even want to go through the "six Ws": who, what, where, when, how, why. For example, suppose you were asked to describe the object-oriented design for a coffee maker. This seems straig htforward enough, right? Not quite. Your coffee maker might be an industria l machine designed to be used in a massive restaura nt servicing hundreds of customers per hour and making ten d ifferent kinds of coffee prod ucts. Or it might be a very simple machi ne, designed to be used by the elderly for just simple black coffee. These use cases will signifi ca ntly im pact your desig n. Step 2: Define the Core Objects
Now that we u nderstand what we're designing, we should consider what the "core objects" in a system are. For example, su ppose we are asked to do the object-oriented design for a restau ra nt. Our core objects might be thi ngs like Ta b l e, G u e s t , P a rty, Orde r, Meal, Employee, S e rver, and Host.
CrackingTheCod ingl nterview.com I 6th Edition
125
Chapter 7
j Object-Oriented Design
Step 3: Analyze Relationships
Having more or less decided on our core objects, we now want to analyze the relationships between the objects. Which objects are mem bers of which other objects? Do a ny objects in herit from any others? Are relationships ma ny-to-many or one-to-many? For example, in the resta urant question, we may come up with the followi ng design: Pa rty should have an array of G u e s t s . S e rver and Host in herit from Employe e .
Each Table has one Pa rty, but each Pa rty may have mu ltiple Tables . There is one Host for the Restau rant. Be very carefu l here-you can often make incorrect assumptions. For exa mple, a single Table may have m u ltiple P a rties (as is common in the trendy "com m u nal tables" at some restaurants). You should ta lk to you r interviewer about how genera l pu rpose you r design should be. Step 4: Investigate Actions
At this point, you should have the basic outline of you r object-oriented design. What remains is to consider the key actions that the objects will take and how they relate to each other. You may find that you have forgotten some objects, and you will need to update you r design. For exa mple, a Pa rty wa lks into the R e s t a u rant, and a Guest requests a T a b l e from the Host. The Host looks up the Reservation and, if it exists, assigns the Pa rty to a Table. Otherwise, the Pa rty is added to the end of the list. When a Pa rty leaves, the Table is freed and assigned to a new Pa rty in the list. � Design Patterns
Because interviewers are trying to test your capabilities and not your knowledge, design patterns are mostly beyond the scope of an interview. However, the Singleton and Factory Method design patterns are widely used in interviews, so we will cover them here. There are fa r more design patterns than this book cou ld possibly discuss. A great way to improve you r soft ware engi neering skills is to pick up a book that focuses on this a rea specifica lly. Be carefu l you don't fa l l into a trap of constantly trying to find the "right" design pattern for a pa rticular problem. You should create the design that works for that problem. I n some cases it might be an estab lished pattern, but in many other cases it is not. Singleton Class
The Singleton pattern ensures that a class has only one instance and ensures access to the instance through the application. It can be useful in cases where you have a "global" object with exactly one instance. For exam ple, we may want to implement R e s t a u ra n t such that it has exactly one instance of R e s t a u rant. 1
2 3
4 5
6 7
public class Resta urant { private stat ic Restaurant _i nstance n ull; protected Restaurant ( ) { . . . } public stati c Restaurant getinstance ( ) { if (_i n stance null) { _inst a n ce = new Restauran t ( ) ; } =
==
1 26
Cracking the Coding Interview, 6th Edition
Chapter 7 8 9
10
I Object-Oriented Desig n
return _instance ; }
}
It should be noted that many people dislike the Singleton design pattern, even ca lling it an "anti-pattern:· One reason for this is that it can interfere with unit testing.
Factory Method
The Factory Method offers an interface for creating an instance of a class, with its su bclasses deciding which class to instantiate. You m ight wa nt to im plement this with the creator class being abstract and not providing an im plementation for the Factory method. Or, you could have the Creator class be a concrete class that provides an implementation for the Factory method. In this case, the Factory method would take a parameter representing which class to instantiate. 1 public class Ca rdGame { 2 public static Ca rdGame createCardGame (GameType type ) { 3 if (type == GameType . Poke r ) { 4 return new PokerGame ( ) ; 5 } else if (type == GameType . Blac kJack) { 6 return new BlackJ ackGame ( ) ; 7 8
9
10 }
}
return nul l ; }
I nterview Questions 7.1
Deck of Cards: Design the data structures for a generic deck of cards. Explain how you would
su bclass the data structu res to implement blackjack. Hints: # 1 53, #275
--- ---- - pg305 7.2
Call Center: Imagine you have a ca ll cente r with three levels of employees: respondent, manager,
and di rector. An incoming telephone cal l m ust be first a llocated to a respondent who is free. If the respondent ca n't handle the call, he or she must esca late the call to a manager. If the ma nager is not free or not able to handle it, then the call should be escalated to a director. Design the classes and data structu res for this problem. Implement a method d i s p a t c h C a l l ( ) which assigns a ca ll to the first ava ilable employee. Hints: #363 ---- ·- ·-·-·- ·-·-·- --
7 .3
----
Jukebox: Design a musical ju kebox using object-oriented principles. Hints: # 1 98
7.4
_______
pg 3 1 0
____
pg 3 1 2
Parking Lot: Design a parking lot using object-oriented principles. Hints: #258
7.5
Online Book Reader: Design the data structu res for an online book reader system. Hints: #344 __
..... .... PQ 3 1 8
CrackingTheCodinglnterview.com I 6th Edition
127
Cha pter 7 .6
7 I Object-Oriented Design
Jigsaw:
Im plement a n NxN jigsaw puzzle. Design the data structu res and explain an algorithm to solve the puzzle. You can assume that you have a fi t sWi th method which, when passed two puzzle edges, retu rns true if the two edges belong together.
Hints: # 1 92, #238, #283
pg 3 1 8 7.7
Chat Server: Explain how you would design a chat server. In particu l a r, provide details about the various backend components, classes, and methods. What wou ld be the hardest problems to solve? Hints: #2 13, #245, #271
pg 326
7.8
Othello: Othello is played as follows: Each Othello piece is white on one side and black on the other. When a piece is su rrou nded by its opponents on both the left and right sides, or both the top and bottom, it is said to be ca ptu red and its color is fl ipped. On you r turn, you must ca ptu re at least one of you r opponent's pieces. The game ends when either user has no more valid moves. The win is assigned to the person with the most pieces. I m plement the object-oriented design for Othello. Hints: # 1 79, #228 _ _ _ _
7.9
_
pg 326
Circu lar Array: Implement a C i r c u l a rAr ray class that supports an a rray-like data structu re which
ca n be efficiently rotated. If possible, the class should use a generic type (also cal led a tem plate). and should support iteration via the sta ndard for ( Obj o : c i r c u l a rA rra y ) notation. Hints: #389 · · · · · · · · · · · · · · · · · · · ··········· ··-·····
128
Cracking the Coding Interview, 6th Edition
pg 32 9
Chapter 7 7.1 0
I Object-Oriented Design
Minesweeper: Design and implement a text-based Minesweeper game. Minesweeper is the classic single-player computer game where an NxN grid has B m i nes (or bombs) hidden across the grid. The remaining cells are either blank or have a number beh ind them. The numbers reflect the number of bombs i n the su rrou nding eight cells. The user then u ncovers a cell. If it is a bomb, the player loses. If it is a number, the number is exposed. If it is a blank cell, this cell and all adjacent blank cells (up to and including the su rrou nding nu meric cells) are exposed. The player wins when a l l non-bomb cells are exposed. The p layer ca n also flag certain places as potential bom bs. This doesn't affect g a m e play, other than to block the user from accidentally clicking a cell that is thought to have a bomb. (Tip for the reader: if you're not familiar with this game, please play a few rou nds on l i ne first.)
This is a fully exposed board with 3 bombs. This is not shown to the user. 1
1
1
2
2
2
1
1
1
1
1
*
*
The player initially sees a board with nothing exposed.
1
I
1
1
1
1
1
*
1
Clicking on cell (row = 1 , col = 0) would expose this:
2
The user wins when everything other than bombs has been exposed.
2
2
Hints: #35 7 , #36 7, #377, #386, #3 99
7.1 1
p g 332
File System: Explain the data structu res and algorithms that you would use to design an in-memory file system. Illustrate with an example in code where possible. Hints: # 1 4 7, #2 7 6 ..................... . . .. ............... ,·'�"
7.1 2
337'
Hash Table: Design and implement a hash table which uses chaining (linked lists) to handle colli
sions. Hints: #287, #307
pg 339
Additional Questions: Threads and Locks (#1 6.3) Hints start on page 662.
CrackingTheCodinglnterview.com j 6th Edition
129
8 Recursion and Dyna m i c Prog ra m m i n g
Wproblem is recursive is that it can be built off of subproblems.
hile there a re a large number o f recursive problems, many follow similar patterns. A good hint that a
When you hear a problem beginning with the followi ng statements, it's often (though not always) a good ca ndidate for recursion: "Design an a lgorith m to compute the nth ..:; "Write code to list the first n ..:; "Imple ment a method to compute all..:; and so on.
I
Tip: In my experience coaching candidates, people typically have a bout 50% accuracy i n their "this sounds like a recursive problem" instinct. Use that instinct, since that 50% is va luable. But don't be afra id to look at the problem in a different way, even if you initially thought it seemed recursive. There's also a 50% chance that you were wrong.
Practice ma kes perfect! The more problems you do, the easier it will be to recognize recursive problems. � How to Ap proach
Recursive solutions, by defin ition, are built off of solutions to subproblems. Many ti mes, this will mean simply to compute f ( n ) by adding something, removing someth ing, or otherwise changing the solution for f ( n - 1 ) . In other cases, you might solve the problem for the first half of the data set, then the second half, and then merge those resu lts. There are many ways you might divide a problem into subproblems. Three of the most common approaches to develop an a lgorithm a re bottom-up, top-down, and half-and-ha lf. Bottom-Up Approach
The bottom-up approach is often the most intu itive. We start with knowing how to solve the problem for a simple case, like a list with only one element. Then we figure out how to solve the problem for two elements, then for th ree elements, and so on. The key here is to think about how you can build the solution for one case off of the previous case (or multiple previous cases). Top-Down Approach
The top-down approach can be more complex since it's less concrete. But someti mes, it's the best way to think a bout the problem. In these problems, we think a bout how we can divide the problem for case N into subproblems. Be ca reful of overlap between the cases. 13O
Cracking the Coding Interview, 6th Edition
Chapter 8
I Recursion and Dynamic Programming
Half-and-Half Approach
In add ition to top-down and bottom-up approaches, it's often effective to divide the data set in half. For example, binary search works with a "half-and-ha lf" approach. When we look for an element in a sorted a rray, we fi rst figure out which half of the array contains the value. Then we recu rse and search for it in that half. Merge sort is also a "half-and-half" approach. We sort each half of the array and th e n me rge togethe r the sorted halves. � Recursive vs. Iterative Solutions
Recu rsive algorithms can be very space inefficient. Each recu rsive ca ll adds a new layer to the stack, which means that if your algorithm recurses to a depth of n , it uses at least O ( n ) memory. For this reason, it's often better to implement a recu rsive a lgorithm iteratively. All recursive a lgorith ms can be im plemented iteratively, a lthough sometimes the code to do so is much more com plex. Before diving into recu rsive code, ask you rself how hard it would be to implement it iteratively, and discuss the tradeoffs with your interviewer. � Dyna mic Prog ramming & Memoization
Although people make a big deal about how scary dynamic prog ramming problems are, there's rea lly no need to be afraid of them. In fact, once you get the hang of them, these can actually be very easy problems. Dyna mic programming is mostly just a matter of taking a recursive algorithm and finding the overlapping subproblems (that is, the repeated calls). You then cache those results for future recu rsive ca lls. Alternatively, you can study the pattern of the recursive ca lls and im plement something iterative. You sti ll "cache" previous work.
I
A note on term inology: Some people call top-down dynamic prog ramming "memoization" and only use "dynamic prog ramming" to refer to bottom-u p work. We do not m a ke such a distinction here. We ca l l both dynamic programming.
One of the simplest examples of dynamic programming is computing the nth Fibonacci number. A good way to approach such a problem is often to implement it as a normal recu rsive solution, and then add the caching pa rt. Fibonacci Numbers
Let's wa l k through a n approach to compute the nth Fibonacci number. Recursive
We will start with a recu rsive implementation. Sounds simple, right? 1 int fibonacc i ( int i ) { 2 if ( i == 0) return 0 ; if ( i == 1 ) return 1 ; 3 4 return fibonacc i ( i - 1 ) + fibonacci ( i - 2 ) ; 5
}
CrackingTheCodingl nterview.com I 6th Edition
131
Chapter 8
I Recursion and Dynamic Programming
What is the runtime of this function? Think for a second before you answer. If you said O ( n ) or O ( n2 ) (as many people do), think again. Study the code path that the code takes. Drawing the code paths as a tree (that is, the recu rsion tree) is useful on this and many recursive problems.
-----
�
fi b ( 42
fi b ( 3 )
./
fib ( 2 )
fib ( 2 )
"'-..
fib ( l )
fib ( 2 )
-----
-.....
fi b ( 3 )
./
fi b ( S )
/ "\...
fi b ( l )
fi b ( 0 )
.......
fib ( l )
/ "\...
fib ( l )
fi b ( 0 )
/ "\...
fi b ( l )
fi b ( 0 )
Observe that the leaves o n the tree are all f i b ( 1 ) and f i b ( 0 ) . Those signify the base cases. The total number of nodes in the tree will represent the runtime, since each call only does 0 ( 1 ) work outside of its recu rsive calls. Therefore, the number of calls is the runtime.
I
Tip: Remember this fo r futu re problems. Drawing the recu rsive calls as a tree is a great way to figure out the runtime of a recu rsive a lgorithm.
How many nodes are in the tree? Until we get down to the base cases (leaves), each node has two children. Each node branches out twice. The root node has two children. Each of those children has two children (so fo u r children tota l in the "grand children" level). Each of those grandchildren has two children, and so on. If we do this n times, we11 have roughly 0 ( 2") nodes. This gives us a runtime of roughly O ( 2" ) .
I
Actually, it's slightly better than O ( 2" ) . If you look at the su btree, you might notice that (excluding the leaf nodes and those immediately above it) the right subtree of any node is always smaller than the left su btree. If they were the same size, we'd have an 0 ( 2 " ) runtime. But since the right and left su btrees a re not the sa me size, the true runtime is closer to O ( 1 . 6" ) . Saying 0 ( 2 " ) is sti ll technically correct though as it describes a n u pper bound on the ru nti me (see "Big 0, Big Theta, and Big Omega" on page 39). Either way, we sti ll have an exponential runtime.
I ndeed, if we im plemented this on a com puter, we'd see the number of seconds increase exponentially. 80
1
60 40 20 0
0
10
We should look fo r a way to optimize this.
132
20
30
40
Se(onds to Generate Nth Fibona(d
Cracking the Coding I nterview, 6th Edition
I Recursion and Dynamic Programming
Chapter 8 Top-Down Dynamic Programming (or Memoization)
Study the recursion tree. Where do you see identical nodes? There a re lots of identica l nodes. For example, fib ( 3) appears twice and fib(2) appea rs three times. Why should we recompute these from scratch each time? In fact, when we call fib ( n ) , we shouldn't have to do much more than 0( n ) cans, since there's only O ( n ) possible values we can throw at fib. Each time we compute fib ( i ) , we should just cache this result and use it later. This is exactly what memoization is. With just a small mod ification, we can twea k this function to ru n in O ( n ) time. We sim ply cache the resu lts of fibona c c i ( i ) between calls. 1 i nt fibonacc i ( i nt n ) { 2 ret u r n fibonacc i ( n , new i n t [ n + 1 ] ) ; 3
4 5
6
7
}
int fibonac c i ( int i , int [ ] mem o ) { if ( i == 0 1 1 i 1 ) ret urn i ; ==
8
if (memo [ i ] == 0) { memo [ i ] = f i b o n acc i ( i - 1 , me mo )
9
10
11
12
}
+
f i b onacc i ( i - 2, me mo ) ;
return memo [ i ] ;
}
While the fi rst recu rsive fu nction may ta ke over a minute to generate the SOth Fibonacci number on a typical computer, the dyna mic prog ramm ing method can generate the 1 O,OOOth Fibonacci number in just fractions of a mill isecond. (Of cou rse, with this exact code, the int wou ld have overflowed very ea rly on.) Now, if we d raw the recursion tree, it looks something like this (the black boxes represent cached ca lls that retu rned immed iately):
------ fi b ( S ) fib(4 �
_.,,, fib( 3 ) / "' fi b ( 2 ) fib( l ) / "' fib ( l ) fib(0)
�
�
How many nodes a re in this tree now? We might notice that the tree now just shoots stra ight down, to a depth of roughly n . Each node of those nodes has one other child, resu lting i n roughly 2 n children i n the tree. This gives us a ru ntime of O ( n ) . Often it can be usefu l to pictu re the recursion tree a s something like this:
�
fib
------ fib( S ) ------
( 4�
fi b ( 3 )
fib ( 2 ) / "' fib( l ) fi b ( 0 )
� �(1)
Th is i s not actually how t h e recursion occu rred. However, by expa nding the fu rther u p nodes rather than the
CrackingTheCodinglnterview.com I 6th Edition
133
Chapter 8
I
Recursion and
D y na mic Programming
lower nodes, y ou have a tree that g rows wide before i t grows deep. (It's like doing this breadth-first rather than depth-fi rst.) Sometimes this makes it easier to compute the number of nodes in the tree. All you're rea l ly doing is changing which nodes you expa nd and which ones retu rn cached val ues. Try this if you're stuck on computing the runtime of a dynamic prog ramm ing problem. Bottom-Up Dynamic Programming
We can a lso take this approach and implement it with bottom-u p dyna mic programming. Think about doing the same things as the recu rsive memoized approach, but in reverse. First, we com pute fib ( 1 ) and fib ( 0 ) , which a re already known from the base cases. Then we use those to com pute f ib ( 2 ) . Then we use the prior answers to com pute f i b ( 3 ) , then f i b ( 4 ) , and so on. l
i nt fi bonacci ( i nt n ) { if ( n 0 ) ret urn 0 ; else if ( n == 1 ) return 1 ;
2
==
3
4 5
=
i nt [ ] memo new int [ n ] ; me mo [ 0 ] = 0 ; me mo [ l ] = 1 ; for ( i nt i = 2 ; i < n ; i++ ) { memo [ i ] = memo [ i - 1 ] + memo [ i - 2 ] ; } ret u r n me mo [ n - 1 ) + me mo [ n - 2 ] ;
6 7 8
9
10 11
12
}
If you really think a bout how this works, you only use memo [ i ] for memo [ i+l ] and memo [ i+2 ] . You don't need it after that. Therefore, we can get rid of the memo table and just store a few variables. 1 int f i b o n acc i ( int n ) {
2
if ( n == 0 ) ret u r n 0 ; int a = 0; int b = 1 ; fo r ( i nt i = 2 ; i < n ; i++) { i nt c = a + b ; b; a b = c;
3
4
5 6 7
8
9
19
11
}
ret u r n a + b;
}
Th is is basica l ly stori ng the resu lts from the last two Fibonacci values into a and b. At each iteration, we compute the next va lue ( c = a + b ) and then move ( b , c a + b ) into ( a , b ) . =
This explanation might seem like overki l l for such a simple prob lem, but tru ly understa nding this process will make more difficult problems much easier. Going through the problems in this chapter, many of which use dynamic prog ramming, wi l l help solid ify your understanding. Additional Reading: Proof by Ind uction (pg 63 1 ).
I nterview Questions 8.1
Triple Step: A child is running up a staircase with n steps and can hop either 1 step, 2 steps, or 3
steps at a time. Implement a method to cou nt how many possible ways the child can run u p the stairs.
Hints: # 1 52, # 1 78, #2 1 7, #237, #262, #359
134
Cracki ng the Coding I nterview, 6th Edition
pg 342
I Recursion and Dynamic Programming
Cha pter 8 8.2
Robot in a G rid: Imagine a robot sitting on the upper left corner of grid with r rows and c columns. The robot can only move in two directions, right and down, but certain cells a re "off limits" such that the robot cannot step on them. Design an algorithm to find a path for the robot from the top left to the bottom rig ht.
Hints: #33 1, #360, #388
pg 344 8.3
Magic Index: A magic index in an array A [ 0 n - 1 ] is defi ned to be an index such that A [ i ] = i. Given a sorted array of distinct integers. write a method to find a magic index, if one exists, i n array A . •
•
•
FOLLOW U P What i f t h e values are not disti nct? Hints: # 1 70, #204, #240, #286, #340
pg346 8.4
Power Set: Write a method to return a l l subsets of a set. Hints: #273, #290, #338, #354, #373
. .. . . - - pg348 8.S
Recursive Multiply: Write a recursive function to multiply two positive integers without using the * operator. You can use add ition, subtraction, and bit shifting, but you should minimize the nu mber of those operations. Hints: # 1 66, #203, #227, #234, #246, #280 _ _
8.6
pg350
Towers of Hanoi: In the classic problem of the Towers of Hanoi, you have 3 towers and N disks of
different sizes which can slide onto any tower. The puzzle starts with disks sorted in ascending order of size from top to bottom (i.e., each disk sits on top of an even larger one). You have the following constrai nts: (1 ) Only one disk can be moved at a time. (2) A disk is slid off the top of one tower onto a nother tower. (3) A
disk cannot be placed on top of a smaller disk.
Write a program to move the disks from the first tower to the last using stacks. Hints: # 1 44, #224, #250, #272, #3 1 8 __
8.7
pg353
Permutations without Dups: Write a method to compute all permutations of a string of unique
characters. Hints: # 1 50, # 1 85, #200, #267, #278, #309, #335, #356
_ pg 3 5 5 8.8
Permutations with Dups: Write a method to compute all permutations of a string whose charac ters a re not necessarily unique. The list of permutations should not have duplicates. Hints: # 1 6 1, # 1 90, #222, #255 · · ···· · · · · · · ··· · · ·· ··
CrackingTheCodinglnterview.com I 6th Edition
pg357
135
Chapter 8 8.9
I
Rec u rsion a nd Dyna mic Progra m m i ng
Parens: Implement an algorith m to print all va lid (e.g., properly opened and closed) com binations of n pairs of parentheses.
EXAMPLE Input: 3
Output: ( ( ( ) ) ) , ( ( ) ( ) ) , ( ( ) ) ( ) , ( ) ( ( ) ) , ( ) ( ) ( ) Hints: # 1 38, # 1 74, # 187, #209, #243, #265, #295
pg 359
8.1 O
Paint Fill: Implement the "paint fill" fu nction that one might see on many i mage editing prog rams. That is, given a screen (represented by a two-d imensional array of colors), a poi nt, and a new color, fi l l in the su rrou nding area u ntil the color changes from the original color. Hints: #364, #382
pg 3 6 1
8.1 1
Coins: Given a n infi n ite number of quarters (25 cents), di mes (1 O cents), nickels (5 cents), and
penn ies ( 1 cent), write code to calcu late the number of ways of representing n cents. Hints: #300, #324, #343, #380, #394 ----- ··
8.1 2
- - --
- - - - .... pg 362
Eight Queens: Write an a lgorithm to print all ways of arranging eight queens on an 8x8 chess boa rd so that none of them share the sa me row, col u m n, or d iagonal. I n this case, "d iagonal" means all d iagona ls, not just the two that bisect the board. Hints: #308, #350, #37 1 ___
8.1 3
Stack of Boxes: You have a stack of n boxes, with widths wi, heig hts h i, and depths di. The boxes ca nnot be rotated and can only be stacked on top of one another if each box in the stack is strictly larger than the box above it in width, height, and depth. Implement a method to compute the height of the tallest possible stack. The height of a stack is the sum of the heig hts of each box. Hints: # 1 55, # 1 94, #2 1 4, #260, #322, #368, #378
8.1 4
pg 3 64
---·· pg 366
Boolean Evaluation: Given a boolean expression consisting of the symbols 0 (fa lse), 1 (true), &
(AN D), I (OR), and A (XOR), and a desi red boolean result va lue resu lt, implement a fu nction to count the number of ways of parenthesizing the expression such that it eva luates to r e s u lt .
EXAMPLE
cou n t E v a l ( " l A0 l 0 l l " , fa l s e ) - > 2 cou ntEva l ( " 0&0&0&1Al l 0 " , t r u e ) - > 1 0 Hints: # 1 48, # 1 68, # 1 97, #305, #327
pg 368
Additional Questions: Linked Lists
Stacks and Queues (#3.3), Trees and Graphs (#4.2, #4.3, Math and Logic Puzzles (#6.6), Sorti ng and Searching (#1 0.5, #1 0.9, Moderate Problems (# 1 6. 1 1 ), Hard Problems (#1 7.4, # 1 7.6, # 1 7.8, # 1 7.1 2, # 1 7. 1 3, (#2.2, #2.5, #2.6),
#4.4, #4.5, #4.8, #4. 1 0, #4. 1 1 , #4. 1 2), # 1 0. 1 0), C++ (#1 2.8),
# 1 7 . 1 5, # 1 7. 1 6, # 1 7.24, # 1 7.25).
H i nts sta rt on page 662.
1 36
Cracking the Coding I nterview, 6th Edition
9 System Desi g n and Sca l a b i l ity
D are no "gotchas;' no tricks, and no fa ncy a lgorithms-at least not usually. What trips u p many people is espite how intimidating they seem, scalability questions can be among the easiest questions. There
that they believe there's something "magic" to these problems-some hidden bit of knowledge.
It's not like that. These questions a re simply designed to see how you wou ld perform in the rea l world. If you were asked by you r manager to design some system, what would you do? That's why you should approach it just like this. Tackle the problem by doing it just like you would at work. Ask questions. Engage the interviewer. Discuss the tradeoffs. We will touch on some key concepts in this cha pter, but recognize it's not rea lly about memorizing these concepts. Yes, u nderstanding some big components of system design can be usefu l, but it's much more about the process you ta ke. There are good solutions and bad solutions. There is no perfect solution. � Handling the Questions •
•
key goa l of sys�em design questions is to eva luate your ability to communicate. Stay engaged with the interviewer. Ask them questions. Be open about the issues of you r system.
Communicate: A
Go broad first: Don't dive straight into the algorithm part or get excessively focused on one part. Use the whiteboard: Using a whiteboa rd helps your interviewer follow you r proposed design. Get up to the whiteboard in the very beginning and use it to draw a picture of what you're proposi ng.
·
•
•
Acknowledge interviewer concerns: You r interviewer wi ll likely jump in with concerns. Don't brush them off; va lidate them. Acknowledge the issues your interviewer poi nts out and make cha nges accord ingly. Be careful about assumptions: An i ncorrect assu m ption can d ra matica lly change the problem. For exa m ple, if your system produces ana lytics I statistics for a dataset, it matters whether those ana lytics must be tota lly up to date. State your assumptions explicitly: When you do make assum ptions, state them. This a llows you r inter
viewer to correct you if you're mista ken, and shows that you at least know what assumptions you're making. •
Estimate when necessary: I n many cases, you might not have the data you need. For exam ple, if you're
designing a web crawler, you might need to estimate how much space it will ta ke to store all the U RLs. You ca n estimate this with other data you know. Drive: As the ca ndidate, you should stay in the d river's seat. This doesn't mean you don't tal k to your
interviewer; in fact. you must tal k to your interviewer. However, you should be d riving through the ques-
CrackingTheCodingl nterview.com I 6th Edition
1 17
Cha pter 9
I System Design and Scalability
tion. Ask questions. Be open about tradeoffs. Continue to go deeper. Continue to make improvements. These questions a re largely about the process rather than the ultimate design. � Desig n: Step-By-Step
If you r manager asked you to design a system such as TinyURL, you probably would n't just say, "Okay'; then lock you rself in your office to design it by you rself. You wou ld probably have a lot more questions befo re you do it. This is the way you should handle it in a n interview. Step 1 : Scope the Problem
You can't design a system if you don't know what you're designing. Scoping the problem is important because you want to ensure that you're building what the i nterviewer wants and because this might be something that interviewer is specifica lly eva luating. If you're asked something such as "Design TinyURL'; you'll want to understand what exactly you need to implement. Will people be able to specify their own short URLs? Or will it all be auto-generated? Will you need to keep track of any stats on the clicks? Should the URLs stay a l ive forever, or do they have a timeout? These are questions that must be answered befo re going fu rther. Make a list here as well of the major features or use cases. For exam ple, for Ti nyURL, it m ight be: •
•
Shortening a URL to a TinyURL. Ana lytics fo r a URL. Retrieving the URL associated with a TinyURL. User accou nts and l i n k management.
Step 2: Make Reasonable Assumptions
It's okay to make some assumptions (when necessary), but they should be reasonable. For exam ple, it would not be reasonable to assume that your system only needs to process 1 00 users per day, or to assume that you have infinite memory available. However, it might be reasonable to design fo r a max of one million new URLs per day. Making this assu mp tion can help you calculate how much data you r system might need to store. Some assumptions might ta ke some "prod uct sense" (which is not a bad thing). For exam ple, is it okay fo r the data to be sta le by a max of ten minutes? That all depends. If it ta kes 1 O minutes for a just-entered URL to work. that's a deal-brea king issue. People usually want these URLs to be active immediately. However, if the statistics are ten minutes out of date, that m i g ht be okay. Talk to you r interviewer a bout these sorts of assumptions. Step 3: Draw the Major Components
Get up out of that chair and go to the whiteboa rd. Draw a d iagram of the major components. You might have something like a frontend server (or set of servers) that pull data from the backend's data store. You might have a nother set of servers that crawl the internet for some data, and another set that process ana lytics. Draw a pictu re of what this system might look like. Wa lk through you r system from end-to-end to provide a flow. A user enters a new URL. Then what?
138
Cracking the Coding Interview, 6th Edition
Chapter 9
I System Design and Scalability
It may help here to ig nore major scalabil ity chal lenges and just pretend that the sim ple, obvious approaches will be okay. You'll handle the big issues in Step 4. Step 4: Identify the Key Issues
Once you have a basic design in m ind, focus on the key issues. What will be the bottlenecks or major chal lenges in the system? For exa m ple, if you were designing TinyURL, one situation you might consider is that while some URLs will be infrequently accessed, others can suddenly peak. This might happen if a URL is posted on Reddit or another popu lar forum. You don't necessarily want to consta ntly hit the database. Your interviewer might provide some guida nce here. If so, take this guidance and u se it. Step 5: Redesign for the Key Issues
Once you have identified the key issues, it's time to adjust you r design for it. You might find that it involves a major redesign or just some minor tweaking (like using a cache). Stay up at the whiteboa rd here and update you r diagram as you r design changes. Be open a bout any limitations in your design. You r i nterviewer will likely be aware of them, so it's important to communicate that you're aware of them, too. � Algorithms that Scale: Step-By-Step
In some cases, you're not being asked to design an entire system. You're just being asked to design a single feature or a lgorithm, but you have to do it in a scalable way. Or, there might be one algorithm part that is the "rea l" focus of a broader design question. In these cases. try the followi ng approach. Step 1 : Ask Questions
As in the earlier approach, ask questions to make sure you really understa nd the question. There might be details the interviewer left out (intentionally or unintentionally). You can't solve a problem if you don't understand exactly what the problem is. Step 2: Make Believe
Pretend that the data can all fit on one machine and there are no memory limitations. How wou ld you solve the problem? The a nswer to this question will provide the general outline for you r solution. Step 3: Get Real
Now go back to the original problem. How much data can you fit on one machine, and what problems will occur when you split u p the data? Common problems include figuring out how to logica lly divide the data up, and how one machine wou ld identify where to look up a different piece of data. Step 4: Solve Problems
Final ly, think about how to solve the issues you identified in Step 2 . Remember that the solution for each issue m ight be to actu a l ly remove the issue entirely, or it m ight be to simply mitigate the issue. Usually, you
CrackingTheCodinglnterview.com j 6th Edition
139
Cha pter 9
I System Design and Sca lability
can continue using (with modifications) the approach you outlined in Step 1 , but occasionally you wil l need to fu ndamenta lly alter the approach. Note that an iterative approach is typica lly useful. That is, once you have solved the problems from Step 3, new problems may have emerged, and you must tackle those as well. You r goal is not to re-a rchitect a com plex system that com panies have spent m i l lions of dollars building, but rather to demonstrate that you can ana lyze and solve problems. Poking holes in you r own solution is a fantastic way to demonstrate this. �
Key Concepts
While system desig n questions a ren't rea lly tests of what you know, certa in concepts can make things a lot easier. We will give a brief overview here. All of these a re deep, complex topics, so we encourage you to use online resources for more research. Horizontal vs. Vertical Scaling
A system can be sea led one of two ways. Vertica l scaling means increasing the resources of a specific node. For exam ple, you might add addi tional memory to a server to improve its ability to handle load cha nges. Horizonta l sca ling means increasing the number of nodes. For exa mple, you might add additional servers, thus decreasing the load on any one server. Vertica l scaling is generally easier than horizontal scali ng, but it's lim ited. You can only add so much memory or disk space. Load Balancer
Typica lly, some frontend pa rts of a scalable website will be thrown behind a load balancer. This a l lows a system to distri bute the load evenly so that one server doesn't crash and ta ke down the whole system. To do so, of course, you have to build out a network of cloned servers that all have essentially the same code and access to the same data. Database Denormalization and NoSQL
Joins in a relational database such as SQL can get very slow as the system grows bigger. For this reason, you would genera l ly avoid them. Denormal ization is one part of this. Denormalization means adding redundant i nformation i nto a database to speed up reads. For exam ple, imagine a database describing projects and tasks (where a project can have multiple tasks). You might need to get the project name and the task information. Rather than doing a join across these ta bles, you can store the project name within the task ta ble (in addition to the project table). Or, you can go with a NoSQL database. A NoSQL data base does not support joins and might structure data in a different way. It is designed to sca le better. Database Partitioning (Sharding)
Sharding means splitting the data across multiple machines while ensuring you have a way of figuring out which data is on which machine. A few common ways of partitioning include:
1 40
Cracking the Coding Interview, 6th Edition
Chapter 9 I System Design and Scalability •
Vertical Partitioning: This is basically partitioning by feature. For example, if you were building a social
network, you might have one partition for tables relating to profiles, another one for messages, and so on. One drawback of this is that if one of these tables gets very large, you might need to repartition th at database (possibly using a different partitioning scheme). Key-Based [or Hash-Based) Partitioning: This u ses some part of the data (for example a n ID) to parti tion it. A very simple way to do this is to al locate N servers and put the data on mod ( key , n ) . One issue with this is that the number of servers you have is effectively fixed. Adding add itional servers means real locati ng a l l the data-a very expensive ta sk. Directory-Based Partitioning: I n this scheme, you mai nta i n a lookup ta ble for where the data can be found. This makes it relatively easy to add add itiona l servers, but it comes with two major d rawbacks. Fi rst, the looku p table can be a single point of failure. Second, constantly accessing this ta ble im pacts performance.
Many a rchitectures actually end up using mu ltiple partitioning schemes. Caching
An in-memory cache can deliver very ra pid resu lts. It is a simple key-va lue pairing and typica lly sits between you r application layer and your data store. When an application requests a piece of information, it fi rst tries the cache. If the cache does not contain the key, it wi ll then look up the data in the data store. (At this poi nt, the data might-or might not-be stored in the data store.) When you cache, you might cache a query and its resu lts d i rectly. Or, a lternatively, you can cache the specific object (for exam ple, a rendered version of a part of the website, or a list of the most recent blog posts). Asynchronous Processing & Queues
Slow operations should ideally be done asynchronously. Otherwise, a user might get stuck waiting and waiting for a process to complete. I n some cases, we ca n do this in advance (i.e., we can pre-process). For example, we might have a queue of jobs to be done that update some part of the website. If we were running a forum, one of these jobs might be to re-render a page that lists the most popular posts and the number of comments. That list might end up being slightly out of date, but that's perhaps okay. It's better than a user stuck waiting on the website to load simply because someone added a new comment and i nvalidated the cached version of this page. In other cases, we might tel l the user to wa it and notify them when the process is done. You've proba bly seen this on websites before. Perha ps you enabled some new part of a website and it says it needs a few minutes to im port you r data, but you'll get a notification when it's done. Networking Metrics
Some of the most im portant metrics around networki ng include: Bandwidth: This is the maxim u m amount of data that can be transferred in a unit of time. It is typically expressed in bits per second (or some similar ways, such as giga bytes per second). Throug hput: Whereas bandwidth is the maximum data that ca n be tra nsferred in a unit of time, throughput is the actual amount of data that is transferred.
Latency: This is how long it ta kes data to go from one end to the other. That is, it is the delay between the
sender sending information (even a very sma ll chunk of data) and the receiver receiving it.
CrackingTheCodingl nterview.com I 6th Edition
1 141
Chapter 9
I System De s i g n a n d Sca l a b i l ity
Imagine you have a conveyor belt that transfers items across a factory. Latency is the time it takes an item to go from one side to another. Th roughput is the number of items that roll off the conveyor belt per second. Bui l ding a fatter conveyor belt will not change latency. It will, however, change throughput and band width. You can get more items on the belt, thus transferring more in a given unit of time. Shorten ing the belt will decrease latency, since items spend less time in transit. It won't change the throughput or bandwidth. The same number of items will roll off the belt per unit of time. Making a faster conveyor belt will change all three. The time it takes an item to travel across the factory decreases. More items will also roll off the conveyor belt per unit of time. Bandwidth is the number of items that can be transferred per unit of time, in the best possible condi tions. Throughput is the time it really ta kes, when the machines perhaps a ren't operati ng smoothly. Latency can be easy to disrega rd, but it can be very important in particular situations. For example, if you're playing certa in online games, latency can be a very big deal. How can you play a typical online sports game (like a two-player football game) if you a ren't notified very qu ickly of your opponent's movement? Addition ally. u n l i ke throughput where at least you have the option of speeding things up through data com pres sion, there is often little you can do about latency. MapReduce
MapReduce is often associated with Google, but it's used much more broadly than that. A MapReduce prog ram is typically used to process large amou nts of data. As its name suggests, a MapReduce progra m requires you to write a Map step and a Reduce step. The rest is hand led by the system. •
Map takes in some data and em its a < key, v a l u e > pair. Red u c e takes a key and a set of associated values and "reduces" them in some way, emitting a new key and va lue. The resu lts of this might be fed back into the Red u c e program for more reducing.
MapReduce al lows us to do a lot of processing in paral lel, which makes processing huge amou nts of data more scala ble. For more information, see "Ma pReduce" on page 642. i. Considerations
In addition to the earlier concepts to lea rn, you should consider the following issues when designing a system. Failures: Essentially any part of a system can fail. You'll need to plan for many or all of these failures. •
Availability and Reliability: Availability is a fu nction of the percentage of time the system is opera tional. Rel iabil ity is a fu nction of the probability that the system is operational for a certa in u n it of time. Read-heavy vs. Write-heavy: Whether an application will do a lot of reads or a lot of writes im pacts the design. If it's write-heavy, you could consider queuing up the writes (but think a bout potenti a l failure here!). If it's read-heavy, you might want to cache. Other design decisions could change as well. Security: Secu rity threats can, of cou rse, be devastating for a system. Th ink about the types of issues a
system might face and design a round those. This is just to get you sta rted with the potential issues for a system. Remember to be open in you r interview a bout the tradeoffs.
1 42
Cracking the Cod ing I nterview, 6th Edition
Chapter 9
I System Design and Scalability
,. There is no "perfect" system.
There is no single design for TinyURL or Google Maps or any other system that works perfectly (althoug h there a re a great number that wou ld work terri b ly). There are always tradeoffs. Two people cou ld have su bstantially different designs for a system, with both being excel lent given d ifferent assumptions. You r goa l in these problems is to be able to understa nd use cases, scope a problem, make reasonable assum ptions, create a solid design based on those assumptions, and be open about the weaknesses of you r design. Do not expect something perfect. � Example Problem Given a list of millions of documen ts, how would you 11nd all documents that contain a fist of words ? The words con appear in any order, but they must be complete words. That is, "book " does not match "bookkeeper."
Before we start solving the problem, we need to u ndersta nd whether this is a one time only operation, or if this fi ndWord s procedure will be called repeated ly. Let's assume that we wi ll be calling fi ndWord s many times for the same set of documents, and, therefore, we ca n accept the burden of pre-processing. Step 1
The first step is to pretend we just have a few dozen documents. How wou ld we im plement findWords in this case? (Tip: stop here and try to solve this yourself before reading on.) One way to do this is to pre-process each document and create a hash table index. This hash table wou ld map from a word to a list of the documents that contain that word. "books" - > {doc2, doc3, doc6, doc8 }
"many" - > { do c ! , doc3, doc7, doc8 , doc9 }
To search
for"many books;' we wou ld simply do a n intersection on the va lues for"books" and "ma ny'; and retu rn { doc 3 , doc 8 } as the resu lt.
Step 2
Now go back to the orig inal problem. What problems a re introd uced with millions of docu ments? For starters, we proba bly need to d ivide up the documents across many machines. Also, depending on a va riety of factors, such as the number of possible words and the repetition of words in a document, we may not be a ble to fit the fu l l hash table on one machine. Let's assume that this is the case. This d ivision introduces the fol lowing key concerns: 1.
How will we divide up our hash table? We cou l d divide it up by keyword, such that a given machine contains the fu l l document list for a given word. Or, we cou ld d ivide by document, such that a machine contains the keyword mapping for only a su bset of the documents.
2. Once we decide how to d ivide up the data, we may need to process a document on one machine and push the resu lts off to other machi nes. What does this process look like? (Note: if we divide the hash table by document, this step may not be necessary.) 3. We wi l l need a way of knowing which machine holds a piece of data. What does this looku p ta ble look
like, and where is it stored? These are just three concerns. There may be many others.
C rackin gTheCodinglnterview.com j 6th Edition
1 43
Chapter 9
I System Design and Sca lability
Step 3
I n Step 3, we find solutions to each of these issues. One solution is to d ivide u p the words a l pha betica lly by keyword, such that each machi ne controls a ra nge of word s (e.g., "after" through "apple"). We can implement a simple a lgorith m in which we iterate through the keywords alpha betically, storing as much d ata as possi ble on one machine. When that machine is fu ll, we can move to the next machi ne. The adva ntage of this approach is that the looku p ta ble is small and simple (since it must only specify a ra nge of va lues), and each machine can store a copy of the looku p table. However, the disadvantage is that if new d ocu ments or words are added, we may need to perform an expensive shift of keywords. To find a l l the documents that match a list of stri ngs, we would first sort the list and then send each machine a looku p req uest for the strings that the machine owns. For example, if our string is "aft e r b u i l d s boat ama z e banana", mach ine 1 wou ld get a looku p request for { "after" , "ama ze" } .
Machine 1 looks up the documents conta i n i ng"aft e r" and "ama ze;' a nd performs a n i ntersection on these d ocument lists. Machine 3 does the same for { " b a n a n a " , "boat" , "bu i l d s" } , and i ntersects their lists. I n the final step, the initial mach ine would do an i ntersection on the results from Machine 1 and Machine 3 . The following d iagra m explains this process.
[
"aft e r b u i l d s boat ama ze bana na"
Ma chine 1 : "after amaze"
Ma chine 3 : "bu i ld s boat banana"
"afte r" - > d oc 1 , doc s , doc7 "ama ze" - > d oc 2 , d oc s , doc7 f d oc S
doc7}
I
I
"bu i l d s" "boat" "ba n a na"
I
->
->
->
d oc 3 , doc4, docs d oc 2 , d oc 3 , d o c s doc 3 , doc4 , d o c s
{doc 3 , d oc s } solut ion
=
docs
I
Interview Questions
These questions a re desig ned to mi rror a real i nterview, so they wi ll not always be well defined. Thi n k a bout what q uestions you wou ld ask you r interviewer and then make reasona ble assu m ptions. You may make different assumptions than us, and that wi l l lead you to a very d ifferent design. That's okay! 9.1
Stock Data: Imagine you are building some sort of service that will be cal led by up to 1 , 000 client applications to get simple end-of-day stock price information (open, close, high, low). You may assu me that you a l ready have the data, and you can store it in any format you wish. How would you design the client-facing service that provides the information to client applications? You are respon sible for the development, rol lout, and ongoing monitori ng and maintenance of the feed. Describe the different methods you considered and why you wou ld recommend you r approach. You r service can use any technologies you wish, and can d istri bute the i nformation to the client applications i n a n y mechanism you choose. Hints: #385, #396 ----- ·-· ·- ··-··-· ·- ··-·· --- ··-
1 44
I
· · · · ·· · ··· · ·· · · ··· ··· ···· · ·
· · ··· · · · · · · · - ·
Cracking the Coding I nterview, 6th Edition
------ _ _ _
_
pg 3 72
Chapter 9
I System Design and Scalability
Social Network: How wou ld you design the data structu res for a very large socia l network like Face
9.2
book or Lin ked In? Descri be how you would design an algorithm to show the shortest path between two people (e.g., Me -> Bob -> Susan -> Jason -> You}. Hints: #270. #285. #304. #32 1
pg 374
Web Crawler: ff you were desig ning a web crawler, how wou ld you avoid getting into infin ite loops?
9.3
Hints: #334, #353, #365
. . . . . pg378
Duplicate URLs: You have 1 0 bil lion URLs. How do you detect the d u p l icate documents? In this
9.4
case, assume "du plicate" means that the URLs are identica l. Hints: #326, #347 · · · · · · · · · · · ····
-
pg 380
Cache: Imagine a web server for a simplified search engine. This system has 1 00 machines to
9.5
respond to search queries, which may then ca ll out using proc e s s S e a rch ( string q u e r y ) to anothercluster of machines to actua l ly get the resu lt. The machine which res ponds to a given qu ery is chosen at random, so you can not guarantee that the same machine will always respond to the same req uest. The method proc e s s S e a rch is very expensive. Desi gn a caching mechanism for the most recent queries. Be sure to explain how you wou ld update the cache when data changes. Hints: #259, #274, #293, #3 1 1 .
9.6
. . . .
pg3 8 1
··· ··-·--·-·-· -·-----
Sales Rank: A large eCommerce company wishes to list the best-selling products, overa ll and by
category. For example, one produ ct m ight be the #1 056th best-selling prod uct overa l l but the # 1 3th best-selling product under "Sports Equipment" and the #24th best-sel ling prod uct under "Safety." Descri be how you would design this system. Hints: # 1 42, # 1 58, # 1 76, # 1 89, #208, #223, #236, #244 -··- ··- ·· -···· · ·- ··- ·· - · ·- ·· -· · ··-··· - · · · ·- · · - ·· · · ·
9.7
_____
pg 385
Personal Financial Manager: Explain how you wou ld design a personal financial ma nager (like Mint.com}. This system wou ld connect to you r bank accou nts, ana lyze you r spend ing habits, and make recommendations. Hints: # 7 62, # 1 80, # 1 99, #2 12, #247, #276
pg 3 8 8
9.8
Pastebin: Design a system like Pastebin, where a user can enter a piece of text and get a randomly generated URL to access it. Hints: # 1 65, # 1 84, #206, #232 ·· ····
· ········-· - ·-··-·--
pg 39 2
Add itiona l Questions: Object-Oriented Design (#7.7) Hints start on page 662.
CrackingTheCodinglnterview.com I 6th Edition
145
10 Sorti ng and Searc h i n g
U and searching problems are twea ks of the well-known algorith ms. A good approach is therefore to run
ndersta nding the common sorting a n d sea rching algorithms i s incred ibly va luable, a s many sorti ng
through the different sorting algorith ms and see if one a pplies particu larly well.
For example, suppose you are asked the fol lowing question: Given a very large a rray of P e r s on objects, sort the people in increasing order of age. We're given two interesting bits of knowledge here: 1.
It's a la rge array, so efficiency is very important.
2. We a re sorting based on ages, so we know the values are in a sma ll range.
By scanning through the various sorting algorith ms, we might notice that bucket sort (or radix sort) wou ld be a perfect candidate for this algorithm. I n fact, we ca n make the buckets small (just 1 year each) and get O ( n ) ru nning time. >
Common Sortin g Algorithms
Learning (or re-learning) the common sorting algorithms is a great way to boost your performance. Of the five a lgorithms explained below, Merge Sort, Quick Sort and Bucket Sort a re the most commonly used i n interviews. Bubble Sort I Runtime: O ( n 2 ) average and worst case. Memory: 0 ( 1 ) .
I n bubble sort, we sta rt at the beginning of the array and swap the first two elements if the first is greater than the second. Then, we go to the next pair, and so on, conti nuously making sweeps of the array u ntil it is sorted. I n doing so, the smaller items slowly "bubble" u p to the beginning of the list. Selection Sort I Runtime: 0 ( n 2 ) average and worst case. Memory: 0 ( 1 ) .
Selection sort is the child's a lgorithm: simple, but i nefficient. Find the smallest element using a linear sca n and move it to the front (swapping it with the front element). Then, find the second smal lest and move it, aga i n doing a linear sca n. Continue doing this u ntil a l l the elements a re in place. Merge Sort I Runtime: 0 ( n log ( n ) ) average and worst case. Memory: Depends .
Merge sort d ivides the array in half, sorts each of th ose halves, and then merges them back together. Each of those halves has the same sorting algorithm applied to it. Eventually, you a re merging just two sing le element a rrays. It is the "merge" pa rt that does al l the heavy lifting. 1 46
Cracking the Coding I nterview, 6th Edition
Chapter 1 0
I Sorting and Searching
The merge method operates b y copying a l l the elements from the ta rget array segment into a helper a rray, keeping track of where the start of the left and right halves should be (he lperleft and he lperRight). We then iterate through helper, copying the smaller element from each half into the a rray. At the end, we copy any remaining elements into the ta rget array. 1 2 3
4 5 6
void mergesort ( i nt [ ] a rray) { int [ ] helper new int [ a r ray . length ] ; mergesort ( a rray, helpe r , 0, a rray . leng�h - l ) ; =
}
void mergesort ( i nt [ ] a rray , int [ ] helper, int low, int high) { if ( low < high ) { int middle = ( low + high) I 2 ; 8 mergesort ( a rray, helper, low, middle) ; / / Sort left half 9 mergesort ( a rray, helper, middle+l , high ) ; / / Sort right half 10 merge ( a rray, helper, low, middle, high ) ; / / Merge t hem 11 12 } 13 }
7
14 15
void merge ( int [ ] a r ray, int [ ] helpe r , int low, int middle, int high) { /* Copy bot h h a lves into a helper a r ray * / for ( int i = low ; i < = high ; i++ ) { 18 helpe r [ i ] = a r ray [ i ] ; 19 }
16 17 20
int helperleft = low; int helperRight = middle + int cu rrent = low;
21 22 23
24 25 26
/* Iterate through helper a r ray . Compa re the left and right half, copying back * the smaller element f rom the two h a lves i n t o t h e origi n a l a r ray . * /
27
while ( helperleft 2 u s i n g namespace st d ;
3 4
#define NAME_S I Z E 5 0 I I Defines a macro
5
6
c la s s Person { int id ; II a l l members are pr ivate by default char name [ NAME_SIZE ] ;
7
8
9
10
11
12 13
14
15
public : void a boutMe ( ) { cout c c "I am a person . " ; } };
16
c l a s s Student : public Person { public : void aboutMe ( ) { 19 cout c c "I am a student . " ; 20 } 21 } ; 17 18
22 23
25
int mai n ( ) { Student * p new Student ( ) ; p - >aboutMe ( ) ; II pr ints "I am a student . " delete p ; I I Importa nt ! Make sure to delete a llocated memory . ret u r n 0 ;
28
}
24
26 27
=
1 58
Cracking the Codi ng Interview, 6th Edition
Chapter 1 2
(
C
and C++
All data members and methods are private by default in C++. One can modify this by i ntroducing the keyword p u b l i c . � Constructors a n d Destructors
The constructor of a class is automatically called upon an object's creation. If no constructor is defi ned, the compiler automatically generates one called the Default Constructor. Alternatively, we can define our own constructor. If you just need to i n itialize primitive types, a simple way to do it is this: Perso n ( i nt a) { 2 id a; 3 } This works for primitive types, but you might instead want to do this: 1 Perso n ( i nt a ) : id ( a ) { 1
=
2
3 } The data member id is assigned before the actual object is created a n d before the remainder of the constructor code is called. This approach is necessary when the fields a re constant or class types.
The destructor cleans up upon object deletion and is a utomatically called when an object is destroyed. It cannot ta ke an a rg ument as we don't explicitly call a destructor. 1
2
3
�Person ( ) { delet e obj ; / / free any memory allocated within c l a s s }
� Virtual Functions
I n a n earlier example, we defi ned p to be of type S t ud e n t : 1 St ud e nt * p = n ew S t ud ent ( ) ; 2 p - > a bo utMe ( ) ;
What wou ld happen if we defi ned p to be a P e r s o n * , like so? 1 Pe r s on * p = n ew St ud e n t ( ) ; 2 p - > a bo u t Me ( ) ; In this case, "I am a person" would be printed instead. This is because the function aboutMe is resolved at compile-time, in a mechanism known as static binding. If we want to ensu re that the Student's implementation of a boutMe is called, we can defi ne a boutMe in the P e r s on class to be v i r t u a l . 1 c l a s s Person { 2
3
4 5
6
7
�
9 1� 11
12
};
v i rt u a l void a boutMe ( ) { c out < < "I am a person . " ; }
c l a s s S t ud e n t : public Person { publi c : void a bou tMe ( ) { cout < < "I am a student . " ; }
CrackingTheCodingl nterview.com I 6th Edition
159
Chapter 1 2
I C and C ++
13 } ; Another usage for virtual fu nctions is when we can't (or don't want to) im plement a method for the parent class. Imagine, for exam ple, that we want St u d e nt and T e a c h e r to inherit from P e r s o n so that we can im plement a common method such as addCou r s e ( s t r i n g s ) . Calling addCou r s e on P e r s on, however, wou ldn't make much sense since the im plementation depends on whether the object is actually a Student or T e a c her. I n this case, we might want addCou r s e to be a virtual function defi ned with in P e rson, with the imple mentation being left to the subclass.
1 2
class Person { int i d ; I I a l l members a re private by default char name [ NAME_SIZE ] ; public : virtual void aboutMe ( ) { cout « "I a m a person . " « endl ;
3
4
5 6
7 8
}
9
10 11 12 13 14
15
};
c l a s s Student : public Person { public : void aboutMe ( ) { cout < < "I am a student . " < < end l ;
}
16
17 18 19
20 21
22
virt u a l bool addCourse ( string s ) = 0 ;
};
bool addCourse ( st ring s ) { cout « "Added cou rse " ret urn t rue; }
«
s
«
" to student . "
«
end l ;
23 24
int ma i n ( ) { Person * p = new Student ( ) ; 25 p - > aboutMe ( ) ; / / prints "I am a student . " 26 p - >addCourse ( "History" ) ; delete p ; 27 28 }
Note that b y defining addCou r s e t o b e a "pure virtual function;' Person i s n o w an abstract class a n d we can not instantiate it. � Virtual Destructor
The virtual function natu ra l ly i ntrod uces the concept of a "virtual destructor:' suppose we wanted to imple ment a destructor method for P e r s on and S t u d e n t . A naive solution might look like this: 1 class Pe rson { 2 public : -Person ( ) { 3 4 cout « " D e l et i ng a person . " « end l ; 5 } 6 };
7 8
9
c l a s s Student public :
1 60
public Person {
Cracking the Coding Interview, 6th Edition
Chapter 1 2 �
10 11 12
st ud e n t ( ) { c o ut « "De l e t i n g a st u d e n t . "
«
I C and C++
e nd l ;
}
13 } ; 14 15 i n t main ( ) { 16 P e rso n * p n ew St ud e n t ( ) ; 17 delete p; // prints "Deleting a person . " 18 } =
As i n the earlier exam ple, since p is a P e r son, the destructor for the P e r s o n class is called. This is problem atic because the memory for Student may not be cleaned up. To fix this, we simply define the destructor for P e r son to be virtual. 1 c l a s s Person { 2 public : 3 virtual � Pe rso n ( ) { 4 cout « "De l e t i n g a p e rso n . " « en d l ; 5 } 6 }; 7 B
c l a s s St ud e nt : p u b l i c Person { p ubl i c ; � st u d e n t ( ) { 10 11 cout « "De l e t i n g a st ud e n t . " 12 } 13 } ;
9
«
e nd l ;
14
i n t main ( ) { P e rso n * p new St ud e n t ( ) ; delete p ; 17 18 } This will output the following: 15
16
Deleti n g a student . Deleting a person . � Default Va lues
Functions can specify default val ues, a s shown below. Note that a l l default parameters must be on the rig ht side of the function declaration, as there would be no other way to specify how the parameters line up. 1 int func ( int a , i n t b 3) { 2 x = a; 3 y = b; 4 return a + b ; 5 } =
6
7 .i
w z
=
=
f u nc ( 4 ) ; f u nc ( 4 , 5 ) ;
� O perator Overloading
Operator overloading enables us to apply operators l i ke + to objects that would otherwise not support these operations. For example, if we wanted to merge two BookShe 1 ves into one, we could overload the + operator as follows.
CrackingTheCodinglnterview.com j 6th Edition
1 61
Cha pter 1 2 1
I C and C ++
BookShelf BookShelf : : operator+( BookS helf &oth e r ) {
.
.
.
}
� Poi nters and References A pointer holds the address of a variable and can be used to perform any operation th at cou ld be directly done on the variable, such as accessing and modifyi ng it.
Two pointers ca n equal each oth er, such that changing one's value a lso cha nges the other's value (since they, i n fact point to the same address). 1
2
3
4
5
new int; i nt * p *p 7; i nt * q p; *p = 8; cout < < *q ; I I prints 8 =
Note that the size of a poi nter varies depending on the a rch itecture: 32 bits on a 32-bit machine and 64 bits on a 64-bit machine. Pay attention to this d ifference, as it's common for i nterviewers to ask exactly how much space a data structu re takes u p. References A reference is a noth er name (an alias) for a pre-existi ng object and it does not have memory of its own. For exa mple: 1
2 3 4
int a 5; i nt & b a; b = 7; cout < < a ; I I prints 7 =
=
I n line 2 a bove, b is a reference to a; modifying b will a lso mod ify a .
You ca nnot create a reference without specifying where i n memory it refers to. However, you c a n create a free-standing reference as shown below: 1 I * allocates memory to store 12 a n d makes b a reference to t h i s 2· * piece of memory . *I 3 can st int & b = 12; U n like pointers, references ca nnot be null and cannot be reassigned to a nother piece of memory. Pointer Arithmetic
One will often see prog rammers perform addition on a pointer, such as what you see below: i nt * p new i nt [ 2 ] ;
1
2 3
4 5
=
p [ 0]
p[l] p++; cout
0; =
ciass SniftedList
3
4 5 6
7
8 9
T* a rray ;
\
int off s e t , s i z e ;
public : S hif t e d L i s t ( i n t s z ) : offset ( 0 ) , s i ze ( s z ) { a r ray = n ew T [ s ize] ; }
�Shifted list ( ) { d e l e t e [ ] a r ray ; }
10 11 12
13
void s h iftBy ( int n ) { offs et = ( offset + n ) % s i ze; }
1415 16
17
T ge tAt ( i n t i) { return a r ray [ c on ve rtl nd ex ( i ) ] ;
18
19
}
20
void se tAt ( T item, int i) { 21 22 a rray [ convert l ndex ( i ) ] = item ; 23 } 24 25 p r iva t e : 26 int conve rt lndex ( int i ) { 27 int i nd e x ( i - offset ) % size; 28 whil e ( index < 0) index += s i z e ; 29 return index ; =
30 31
};
}
Interview Questions 1 2.1
Last K Lines: Write a method to print the last K lines of an input file using C ++. Hints: #449, #459
........... - - - pg4 2 2
1 2.2
Reverse String: Implement a function void rever s e ( c h a r * s t r ) i n C or C++ which reverses a null-termi nated string. Hints: #4 1 0, #452
. . . pg42 3
1 2.3
Hash Table vs. STL Map: Compare and contrast a hash table and a n STL map. How is a hash table implemented? If the number of inputs is small, which data structure options can be used i nstead of a hash table? Hints: #423
pg4 23
Cracki ngTheCodingl nterview.com I 6th Ed ition
1 63
Cha pter 1 2 1 2.4
I C and C++
Virtual Functions: How do virtual functions work i n C++? Hints: #463
pg4 24
1 2.5
Shallow vs. Deep Copy: What is the difference between deep copy and shal low copy? Explain how you would use each. Hints: #445
.. .. ----- -- pg42 5
1 2.6
Volatile: What is the significance of the keyword"volatile" in
C?
Hints: #456
.. P 9426
1 2.7
Virtual Base Class: Why does a destructor in base class need to be declared v i rt u a l ? Hints: #42 7 , #460 _
1 2.8
pg427
Copy Node: Write a method that takes a pointer to a Node structu re as a parameter and retu rns a complete copy of the passed in data structu re. The N od e data structure contains two pointers to other Nod e s . Hints: #427, #462
pg427
1 2.9
Smart Pointer: Write a smart pointer class. A smart pointer is a data type, usually implemented with tem p lates, that simu lates a pointer while also providing automatic garbage col lection. It automati cally counts the number of references to a Sma rtPointer object and frees the object of type T when the reference count hits zero. Hints: #402, #438, #453
pg4 2 8
1 2. 1 0 Malloc: Write an aligned malloc and free function that supports a l locati ng memory such that the memory address retu rned is divisible by a specific power of two.
EXAMPLE
a l ign_malloc ( 1000 , 1 2 8 ) will retu rn a memory address that is a multiple of 1 28 and that points to memory of size 1 000 bytes. a ligned_free ( ) wil l free memory al located by a l ign_ma lloc. Hints: #4 7 3 , #432, #440
pg430
1 2.1 1 20 Alloc: Write a function in C cal led my2DAl loc which allocates a two-dimensional a rray. Mini mize the number of ca lls to ma l loc and make sure that the memory is accessi ble by the notation
a rr [ i ] [ j ] . Hints: #406, #4 78, #426
pg43 1
Additional Questions: Linked Lists (#2.6), Testing (#1 1 .1 ), Java (#1 3.4), Threads and Locks (#1 5.3). Hints start on page 676.
1 64
Cracki ng the Coding Interview, 6th Edition
13 Java
Wthe language and syntax. Such questions are more unu sual at bigger compan ies, which believe more
hile Java-related questions are found throug hout this book, this chapter deals with questions a bout
in testi ng a candidate's aptitude than a ca ndidate's knowledge (and which have the time and resou rces to train a candidate in a particu lar language). However, at other companies, these pesky questions ca n be qu ite common. � How to Approach
As these questions focus so much on knowledge, it may seem silly to ta lk a bout an approach to these prob lems. After a l l, isn't it just a bout knowi ng the right answer? Yes and no. Of cou rse, the best thing you can do to master these questions is to learn Java inside and out. But, if you do get stu mped, you ca n try to tackle it with the fol lowing approach: 1.
Create an example of the scena rio, and ask you rself how things should play out.
2. Ask you rself how other languages wou ld handle this scena rio. 3.
Consider how you wou ld design this situation if you were the language designer. What wou ld the impli cations of each choice be?
Your interviewer may be equally-or more-impressed if you can derive the a nswer than if you automati cally knew it. Don't try to bluff though. Tell the interviewer, "I'm not su re I can recal l the answer, but let me see if I can figure it out. Suppose we have this code . . . "
� Overloading vs. Overriding
Overloading is a term used to describe when two methods have the same name but differ i n the type or number of a rguments. 1
2
public do ubl e c om put eA r ea ( C i r c l e c ) { public double c om pu teA r ea ( Squ a re s ) {
. • • •
•
•
} }
Overridi ng, however, occu rs when a method shares the sa me name and fu nction sig natu re as a nother method in its super class. public abstract c l a s s Shape { 1 public void printMe ( ) { 2 3 Sy s tem out println ( I am a shape . " ) ; 4 } 5 public abst ract double computeArea ( ) ; 6 } .
.
"
CrackingTheCodinglnterview.com I 6th Edition
1 65
Chapter 1 3 7
8
9
10 11
12
13
=
public double computeArea ( ) { return rad * rad * 3 . 15 ; }
15 17
18 19
20
21 22
23
24 25
26 27
28
29
30
31
}
public c l a s s Ambiguous extends Shape { private double area 10; public double computeArea ( ) { return a rea ; =
}
}
public c l a s s Int roductio nOverriding { public static vo id main ( String [ ] args ) { Shape [ ] shapes = new Shape [ 2 ] ; Circle circle new Circle ( ) ; Ambiguous ambiguous = new Ambiguous ( ) ; =
32 33 34
shapes [ 0 ] sha pes [ ! ]
35
37
38
40
circle; ambiguous ;
for ( Shape s : shapes ) { s . printMe ( ) ; System . out . print l n ( s . computeArea ( ) ) ;
36 39
J ava
public class Circle extends Shape { private double rad 5; public void printMe ( ) { System . out . print l n ( "I am a circle . " ) ; }
14 16
I
} }
}
The a bove code will print: 1
2
3
4
I am a circle .
78 . 75
I am a shape . 10.0
Observe that C i rc l e overrode p r i ntMe ( ) , wherea s Amb i guou s just left this method as-is.
� Collection Fra mework
Java's collection fra mework is incred ibly usefu l, and you will see it used throughout this book. Here a re some of the most usefu l items: Array L i s t : An Array l i s t is a dynamically resizing a rray, which grows as you in sert elements. 1 Arraylist myArr = new ArrayList ( ) ; 2 myArr . add( "one" ) ; 3 myArr . add ( "two") ; 4 System . out . print l n (myArr . get ( 0 ) ) ; / * prints */
Vector: A vector is identical as well.
1 66
I
very
similar to an Array l i st. except that it is syn chronized. Its syntax is almost
Cra cking the Coding Interview, 6th Edition
\
Chapter 1 3 1
2
3
4
Java
Vector myVect new Vecto r ( ) ; m y Ve ct . a dd ( "on e " ) ; my Ve c t . a dd ( "two" ) ; sysLem . ouL . prlnLin(myVect . get (0 ) ) ;
L i n ked List: L i n kedList is, of cou rse, Java's built-i n L i n ked L i s t class. Though it rarely comes up in a n interview, it's usefu l to study because it demonstrates some of the syntax for a n iterator. 1 Linkedl ist < Str ing> my L i nk e dLi s t new Link e d list ( ) ; 2 mylinked list . a dd ( " t wo" ) ; 3 m y L i n ke d L ist . a dd F i r s t ( "o ne" ) ; =
4 5 i
7
Iterator iter = my linked l i st . iterator ( ) ; while ( iter . hasNext ( ) ) { System . out . print l n ( iter . next ( ) ) ; }
HashMap: The HashMap collection is widely used, both i n interviews and i n the real world. We've provided a snippet of the syntax below. HashMa p map = new H a s hM a p ( ) ; 1 2
3
4
,
ma p . put ( "one'' , "uno" ) ; map . put ( "two", "dos" ) ; System . out . print l n ( ma p . get ( "one" ) ) ;
Before your interview, make sure you're very comfortable with the above syntax. You11 need it.
I nterview Questions
Please note that because virtually all the solutions in this book a re implemented with Java, we have selected only a small number of questions for this chapter. Moreover, most of these questions dea l with the "trivia" of the languages, since the rest of the book is fi lled with Java programming questions. 1 3.1
Private Constructor: In terms of inheritance, what is the effect of keeping a constructor private? Hints: #404 -------
1 3.2
______ , ......... .. ... ....._ .. --· · ·-·-"
.. . . . _ _
pg433
Return from Finally: In Java, does the f i n a l ly block get executed if we insert a ret u rn state
ment inside the try block of a t r y - catc h - f i n a l ly? Hints: #409
pg43 3
1 3.3
Final, etc.: What is the difference between f i n a l, fina lly, and fina l i z e? Hints: #4 12 .....
1 3.4
·--·--·- ·
- ··········--
········ - -··--· ------ -
pg43 3
Generics vs. Tem plates: Explain the difference between tem plates in C ++ and generics in Java.
Hints: #4 1 6, #425
pg4 3 5
1 3.S
TreeMap, HashMap, LinkedHashMap: Explain the d ifferences between Tr eeMap, Ha s hMa p, and
L i n kedHa s hMap. Provide a n exa mple of when each one would be best. Hints: #420, #424, #430, #454
.. pg4 36
Cracki ngTheCodingl nterview.com I 6th Editi o n
1 1 67
Chapter 1 3 1 3.6
I
Java
Object Reflection: Expl a i n what object reflection is i n Java and why it is useful.
Hin ts: #435
1 3.7
Lambda Expressions: There is a cl ass Cou n t ry that has methods getCont i nent ( ) and g e t Popu l a t ion ( ) . Write a fu nction int get Popu lat ion ( L i s t < C o u n t ry > c o u n t r ie s , S t r i n g cont i n en t ) that com putes the total popu lation of a given continent, given a list of all cou ntries and the name of a continent.
Hints: #448, #46 1, #464
1 3.8
pg4 37
_ pg4 38
Lambda Random: Using Lam bda expressions, write a function L i s t < I n t e g e r > getRa ndomS u b s e t ( L i s t < I n t eger> l i s t ) that retu rns a ra ndom su bset o f a rbitra ry size. A l l su bsets (including t h e em pty set) should b e equally likely t o b e c hosen.
Hints: #443, #450, #457
------
pg4 39
Additional Questions: Arrays and Strings (#1 .3), Object-Oriented Design (#7. 1 2), Threads and Locks (# 1 5.3) H i nts sta rt on page 676.
1 68
Cracki ng the Coding Interview, 6th Edition
14 Databases
I key concepts a nd offer an overview of how to approach these problems. As you read these queries, don't f you profess knowledge of data bases, you might be asked some questions on it. We'll review some of the
be surprised by minor variations in syntax. There are a variety of flavors of SQL, and you might have worked with a slig htly diffe rent one. The examples in this book have been tested against Microsoft SQL Server. � SQL Syntax and Variations
implicit a nd explicit joins are shown below. These two statements are equivalent, and it's a matter of personal preference which one you choose. For consiste ncy, we will sti ck to the explicit join. 1
2 3
SELECT CourseName, TeacherName F ROM Courses INNER JOIN Tea chers ON Courses . TeacherID Teachers . TeacherID =
1
2
3 4
SE LECT CourseName, TeacherName F ROM Courses , Teachers WHERE Courses . TeacherID = Teac hers . TeacherID
� Denormalized vs. Normalized Databases
Normalized databases a re designed to minimize red undan cy, while denormalized data bases a re desig ned to optimize read time. In a traditional normalized database with data like Cou rs es and Tea che rs, Cou r s e s might contain a column called Tea c h e rI D, which is a foreign key to Tea c h e r . One benefit ofthis is that information a bout the teacher (name, add ress, etc.) is only stored once in the database. The d rawback is that many common q ueries wil l require expensive joi ns. I nstead, we can denormalize the database by storing redundant data. For exam ple, if we knew that we would have to repeat this query often, we might store the teacher's name in the Courses table. Denormal ization is commonly used to create highly scalable systems. � SQL Statements
Let's walk through a review of basic SQL syntax, using as an example the database that was mentioned earlier. This data base has the following simple stru cture {" indicates a primary key): Courses : CourseID* , CourseName, Tea cherID Tea c hers : TeacherID* , TeacherName Student s : StudentID* , StudentName Cracki ngTheCodinglnterview.com j 6th Edition
169
Chapter 1 4
I Databases
StudentCou rses : CourseID* , StudentID*
Using the a bove table, implement the fol lowing queries. Query 1 : Student Enrollment
Im plement a Query to get a list of all students a n d how many courses each student is enrol led in. At fi rst, we might try something like this: 1 2
/ * I n c o r rect Code * / S E L ECT Students . StudentName, count ( * ) F ROM Students INNER JOIN Studentcourses ON Student s . StudentID StudentCourses . StudentID GROUP BY Student s . StudentID
3
4 5
=
Th is has three problems: We have excluded students who are not enrol led i n any courses, since s t u d entCou r s e s only includes enrolled students. We need to change this to a L E FT JOIN.
1.
2. Eve n if we changed it to a L E FT JOIN, the query is sti l l not q u ite rig ht. Doing c o u n t ( * ) would return how many items there a re in a given group of Student I Ds. Students enrolled in zero courses would still have one item i n their group. We need to change this to count the number of Cou r s e IDs i n each group: c ount ( Stud entCou r s e s . Co u r s e I D ) . 3 . We've grou ped by students . s t u d ent I D, but there are still m u ltiple stud entN ames i n each group. How will the d atabase know which S t u d entName to return? S u re, they may a l l have the same val ue, but the d atabase doesn't u ndersta nd that. We need to apply an aggregate function to this, such as f i rs t ( Student s . stud entName ) . Fixing these issues gets u s to this query:
/ * Solution 1 : Wra p with a n other q uery * / SEL ECT StudentName , Students . StudentID, Cnt F ROM ( S E LECT Students . StudentID, count ( StudentCourses . CourseID) a s [ Cnt ] FROM Students L E F T JOIN StudentCourses ON Student s . St udent ID Studentcourses . StudentID GROUP BY Student s . StudentID T INNER JOIN Students on T . studentID = Students . StudentID
1
2
3 4
5 6
=
7
8
Looking at this code, one might ask why we don't just select the student name on line 3 to avoid having to wrap lines 3 through 6 with another query. Th is (incorrect) solution is shown below.
1
/* Incorrect Code */ S E L ECT StudentName, Student s . StudentID, count ( StudentCourses . CourseID) as [Cnt ] FROM Students LEFT J O I N StudentCourses ON Student s . Student ID Studentcourses . StudentID GROUP BY Student s . StudentID
I
2 3
=
4
The answer is that we can't do that - at least not exactly as shown. We can only select values that are i n an agg regate fu nction or in the GROUP BY clause. Alternatively, we could resolve the above issues with either of the fol lowing statements: 1
/ * Solution 2 : Add StudentName to GROUP BY clause . */ S E L ECT StudentName , Student s . StudentID, count ( StudentCourses . CourseID) as [ Cnt ] F ROM Student s L E F T JOIN Studentcourses ON Student s . StudentID = Student Courses . StudentID G ROUP BY Student s . StudentID, Student s . StudentName
2 3
4 5
OR 170
C racki ng the Coding Interview, 6th Ed iti on
Chapter 14 1
2 3
4 5
6
I Databases
/ * Solution 3 : Wra p wit h aggregate function . */ S E L ECT ma x ( StudentName) a s [StudentName ] , Student s . Student ID, count ( StudentCourses . CourseID) as [Count ] F ROM Students L E F T JOIN StudentCourses ON Student s . StudentID = StudentCourses . Student ID GROUP
BY
Student s . stude ntID
Query 2: Teacher Class Size
Implement a query to get a list of a l l teachers and how many students they each teach. If a teacher teaches the same student in two courses, you should double count the student. Sort the list in descending order of the number of students a teacher teaches. We can construct this query step by step. First. let's get a list of T e a c h e r IDs and how many students are associated with each T e a c h e rID. This is very similar to the earlier query. 1 SELECT TeacherID, count ( StudentCourse s . CourseID) AS [ Number ] 2 F ROM Courses INNE R JOIN StudentCourses 3 ON Courses . CourseID = StudentCourses . CourseID �· GROUP BY Courses . TeacherID Note that this I N N E R JOIN will not select teachers who aren't teaching classes. We'll handle that i n the below query when we join it with the list of all teachers. 1 SE LECT TeacherName , isnul l ( StudentSize . Numbe r, 0 ) 2 F ROM Teachers L E FT JOIN 3 ( S E L ECT TeacherID, count ( StudentCourses . CourseID) AS [ Number ] 4 F ROM Courses INNE R JOIN StudentCourses 5 ON Courses . Cou rseID = StudentCourses . CourseID 6 GROUP BY Cou rses . TeacherID) StudentSize 7 ON Teachers . TeacherID = StudentSize . TeacherID 8 ORD E R BY StudentSize . Number DESC Note how we handled the NU L L va lues in the S E L ECT statement to convert the NU L L values to zeros.
� Small Database Design
Add itionally, you might be asked to design your own database. We'll wa lk you through an a pproach for this. You might notice the similarities between this approach and the a pproach for object-oriented design. Step 1 : Handle Ambiguity
Database questions often have some a mbiguity, intentionally or unintentional ly. Before you proceed with your design, you must understand exactly what you need to design. Imagine you are asked to design a system to represent an apartment renta l agency. You will need to know whether this agency has multiple locations or just one. You should a lso d iscuss with your interviewer how general you should be. For example, it would be extremely rare for a person to rent two a pa rtments in the same building. But does that mean you shouldn't b e a ble to handle that? Maybe, maybe not. Some ve ry ra re conditions might be best handled throug h a work a round (like duplicating the person's contact information in the database). Step 2: Define the Core Objects
Next. we should look at the core objects of our system. Each of these core objects typically translates into a table. In this case, our core objects might be P ro p e rty, B u i l d i n g, Apa rtment, Ten a n t and Man a ge r.
Cracki ngTheCodingl nterview.com j 6th Edition
1 71
Chapter 1 4
l Databases
Step 3: Analyze Relationships
Outl ining the core objects should give us a good sense of what the tables should be. How do these tables relate to each other? Are they many-to-many? One-to-ma ny? If B u i l d i n g s has a one-to-manyrelationship with Apa rtment s (one B u i ld i n g has many Apartment s), then we might represent this as follows:
Apartment I D
int
BuildingID
int
Apa rtmentAddress
varcha r ( 100)
BuildingName
varcha r ( 100)
BuildingID
int
BuildingAddress
varcha r ( 500)
Note that the Apa rtments table lin ks back to B u i l d i n g s with a Building!D column. If we want to allow for the possibility that one person rents more than one a pa rtment, we might want to implement a ma ny-to-many relationsh ip as follows: TenantApartments
i�
Tenant ID
int
Apa rtment ID
int
Apa rtment ID
int
Apa rtmentAddress Building I D
Tenant ID
int
va rcha r ( 500 )
TenantName
varcha r ( 100)
int
Tena ntAdd ress
varc h a r ( 500)
The TenantApa rtments ta ble stores a relationship between Ten a n t s and Apa rtments. Step 4 : Investigate Actions
Final ly, we fi ll in the deta i ls. Wa lk through the common actions that will be taken and understa nd how to store and retrieve the relevant data. We'll need to handle lease terms, moving out, rent payments, etc. Each of these actions requires new tables and columns . ., Large Database Desig n
When desig ning a large, sca lable database, joins (which a re required in the above examples) a re genera l ly very slow. Thus, you must denormalize your data. Th ink carefu lly a bout how data will be used-you'll prob ably need to dupl icate the data in multiple ta bles. I nterview Questions
Questions 1 through 3 refer to the database schema at the end of the cha pter. Each apartment can have mu ltiple tena nts, and each tenant c a n have multiple a partments. Each apartment belongs to one building, and each building belongs to one complex. 1 4.1
Multiple Apartments: Write a SOL query to get a list of tena nts who are renting more than one
apartment. Hints: #408
1 72
Cracking the Coding I nterview, 6th Edition
Chapter 1 4 1 4.2
I
Data b a ses
Open Requests:
Write a SQL q uery to get a list of all buildings and the number of open requests (Requests in which statu s equals 'Ope n ' ).
Hints: #4 1 1 -- •• • • •
1 4.3
•
mm
pg44 2
Close All Requests: Building # 1 1 is u ndergoing a major renovation. Implement a q uery to close all
requests from apartments in this building. Hints: #43 1
. PQ44 2
1 4.4
Joins: What a re the d ifferent types of joins? P lease explain how they d iffer and why certai n types
a re better in certai n situations. Hints: #45 1
. . ....... PQ44 2
--- · -·-··--· ·---
1 4.5
Denormalization: What is denormalization? Expla in the pros and cons. Hints: #444, #455
. .. pg443
1 4.6
Entity-Relationship Diagram: Draw an entity-relationship diagram for a database with companies,
people, and professiona ls (people who work for compa nies). Hints: #436 ·· ·· · ·· ·· ·- · · - · -· ·- · - ··- · ·- · ·-··· · · · ·· ·- ·--· ·- ·---· - · ·- - · - ··---· ---
1 4.7
pg444
Design Grade Database: I magine a simple database storing i nformation for students' grades.
Design what this data base might look l i ke and provide a SQL q uery to return a list of the honor roll students (top 1 0%), sorted by their grade point average. Hints: #428, #442 · · · ·· ·· · -··-··-··-·-··-····· ·-·
· ---- · .
·-··-·-·-··-· - ·-··-·-··-··-
......
- pg445
· · · · ····--·-·--·
Add itional Questions: Object-Oriented Design (#7.7), System Design and Scalability (#9.6) Hints sta rt on page 676.
UnitNumber Building!D
va r c h a r ( 1 0 )
BuildingID
ComplexName
va rcha r ( 100 )
RequestID
int
Complex I D
int
Status
va rc h a r ( 100 )
BuildingName
va rc h a r ( 100 )
Apt ID
int
Des c r i pt ion
va rcha r ( 500 )
Addres s
Complex ID
int
va r c h a r ( 5 00)
Tenant!D
i nt
Apt ID
int
TenantName
va rc h a r ( 100)
CrackingTheCodingl nterview.com j 6th Edition
1 73
15 Th reads a n d Locks
I rithm with threads (u nless you're working in a team for which this is a particularly im portant skill). It
n a Microsoft, Google or Amazon interview, it's not terribly common to be asked to im plement an algo
is, however, relatively common for interviewers at a ny company to assess your general understanding of threads, particularly your understanding of dead locks. This cha pter wil l provide a n introduction to this topic. ._ Th reads in Java
Every thread in Java is created and controlled by a unique object of the j a va . l a n g . Thread class. When a standalone application is ru n, a user thread is automatica lly created to execute the main ( ) method. This thread is called the main thread. In Java, we can implement threads in one of two ways: By implementing the j ava . la n g . Runnable interface By extending the j a va . l a n g . Thread class We will cover both of these below. Implementing the Runnable Interface
The Runnable interface has the following very simple structu re. p u b l ic i nterfa c e Runnable { 1 2 void r u n ( ) ; } 3
To
create and use a thread using this interface, we do the fol lowing:
1.
Create a class which implements the Runnable interface. An object of this class is a Runnable object.
2. Create an object of type Th read by passing a Runnable object as argu ment to the Th read constructor. The T h r e a d object now has a Runnable object that implements the r u n ( ) method. 3.
The s t a r t ( ) method is i nvoked on the Thread object created in the previous step.
For example: public cla s s RunnableThrea d Example implement s Runnable { 1 2 public int count = 0 ; 3
pu b l i c void
�
run ( ) { System . out . print ln ( "RunnableThread start i ng . " ) ;
5
1 74
Cracking the Cod i n g Interview, 6th Ed ition
Chapter 1 5 6 7
9
10 11
12
13
14 16
17 li
19
20
21 22
}
}
public stat i c vo id ma i n ( St r i ng [ ] a rgs ) { RunnableThread Example insta nce = n ew Runna bleThread Example ( ) ; Thread thread = new Thread ( i n stance ) ; t h read . st a rt ( ) ;
23
24 25
26
27
28
2,
30
31
Threads a n d Locks
t ry { while ( count < 5 ) { Thread . s leep ( 500) ; count++; } } cat c h ( InterruptedException exc ) { System . out . print l n ( "RunnableThread inte rrupted . " ) ; } System . o ut . print l n ( "Runna bleThread t e rm i nating . " ) ;
8
15
I
}
/* wa its until above t h read counts to 5 ( s lowly ) * / while ( i n stance . count ! = s ) { try { Thread . s leep( 250) ; } catc h ( I nte rrupted Exception e x c ) { exc . printSta ckTrace ( ) ; } }
In the a bove code, observe that a l l we really needed to do is have our class implement the r u n ( ) method (line 4). Another method can then pass an instance of the class to new Th read ( obj ) (lines 1 9 20) and call s t a r t ( ) on the thread (line 21 ). -
Extending the Thread Class
Alternatively, we can create a thread by extend ing the Th read class. This will almost a lways mean that we override the run ( ) method, and the subclass may a lso call the thread constructor explicitly in its constructor. The below code provides an example of this. 1
2 3 4
public cla s s ThreadExample extends Th read { int count = 0;
5
6
7 8
9 10 11 12
13
14
15
16 17
18
}
public vo id run ( ) { System . out . print ln ( "Thread sta rt ing . " ) ; t ry { while ( count < 5 ) { Thread . s leep ( S00) ; System . out . p r i ntln ( " I n Thread, count is " + count ) ; count++ ; } } catch ( I nte rrupted Exception exc ) { System . out . pr i nt l n ( "Thread interrupted . " ) ; } System . out . println ( "Thread terminating . " ) ; }
CrackingTheCodingl nterview.com j 6th Edition
!
11s
Chapter 1 5
I Threads and Locks
19
pu b l i c class f x a m p l e B { public static void main ( String args ( ] ) { 21 T h r ea dE xa mpl e i n stance = n ew Th r ea d E xa mpl e ( ) ; 22 i n s t a n ce . st a rt ( ) ; 20
23
24
w h i l e ( i n st a n ce . count ! = 5) { t ry {
25
26
Thread . s leep (250) ; } catch ( I nterruptedExcept ion exc ) {
27
28
exc . pr i nt StackTrace ( ) ;
29
30 31 32
}
}
}
}
This code is very similar to the first approach. The difference is that si nce we a re extending the Th read class, rathe r th a n j ust im plementing an interface, we can ca ll s t a rt ( ) on the instance of the class itself. Extending the Thread Class vs. Implementing the Ru nnable Interface
When creating threads, there a re two reasons why im plementing the Runnable interface may be prefer a ble to extending the Th read class: Java does not support mu ltiple inheritance. Therefore, extending the Th read class means that the subclass cannot extend any other class. A class implementing the Runnable i nterface will be able to extend another class. A class might only be interested in being ru nnable, and therefore, inheriting the fu l l overhead of the Th read class wou ld be excessive . ., Synchron ization and Locks
Threads within a given process share the same memory space, which is both a positive and a negative. It ena bles threads to share data, which can be va lua ble. However, it a lso creates the opportu nity for issues when two threads modify a resou rce at the same time. Java provides synchronization in order to control access to shared resources. The keyword syn c h roni zed and the loc k form the basis for im plementi ng synch ronized execution of code. Synchronized Methods
Most common ly, we restrict access to shared resources through the use of the syn c h roni zed keyword. It can be a pplied to methods and code blocks, and restricts mu ltiple threads from executing the code simul taneously on the same object. lo
1 2 3
4
cla rify the last poi nt, consider the following code: public class MyClass extends Thread private St ring name ; p r i va t e MyOb j ec t my Ob j ;
5
{
p u b l i c M yC la s s ( My Ob j ect o b j , String n ) { n a me = n ; myObj = obj ;
6
7
8
} 1 76
Cracki ng the Codi ng Interview, 6th Edition
Cha pter 9
10 11
12 13
14
}
15 I
Th reads a n d Locks
public voi d run ( ) { myObj . foo ( name ) ; }
15
public class MyObj ect { public synchronized void foo ( St ring name ) { try { 17 Sy stem . out . p rintln ( "Thread " + name + " . foo( ) : starting" ) ; 18 Thread . slee p ( 3000 ) ; 19 Sy stem . out . p rintln ( "Thread " + name + " . foo( ) : ending" ) ; 28 21 } catch ( Inter rupted E xception exc ) { System . out . p ri nt l n ( "Thread " + n ame + " : i nterrupted . " ) ; 22 23 } 24 } 25 }
16
Can two instances of MyC l a s s call foo at the same ti me? It depends. If they have the same insta nce of MyObj ect, then no. But, if they hold different references, then the answer is yes. 1 / * Diffe rence refe rence s - both t h rea d s can call MyObj ect . foo ( ) * / 2 MyObj ect obj l new MyObject ( ) ; MyObject obj 2 new MyObject ( ) ; 3 4 MyC lass t h readl new MyCla s s (obj l , "1" ) ; S MyClass thread2 new MyC l a s s ( o b j 2, "2" ) ; 6 threadl . st a rt ( ) ; 7 t h read2 . st a rt ( ) =
=
8 9
/ * Same refe rence to o bj . Onl y one * and the othe r will be forced to 1 1 MyOb j e c t obj = new MyObj ec t ( ) ; 1 2 MyClass t h readl new MyCl a s s ( obj , 13 MyC l a s s thread2 new MyCla s s ( obj , 14 t h readl . st a rt ( ) 1 5 t h read2 . start ( ) 10
w i l l be a llowe d t o call foo, wait . * I
=
"1" ) ; "2") ;
Static methods synchron ize o n the class lock. The two threads a bove could not simultaneously execute synchron ized static methods on the same class, even ifone is calling foo and the other is cal l ing b a r .
1 2
public class MyClass extends Thread
3
4 5
6
7
8 9
10
11
12
}
{
public voi d r un ( ) { if ( name . eq uals ( " l" ) ) MyObject . foo( name ) ; else i f ( name . eq uals ( "2" ) ) MyObj ect . ba r ( name ) ; }
public c l a s s MyObj ect { public static synchro n i ze d voi d foo ( St ring name ) { /* same a s befo re */ } p u b l i c s t a t i c synchroni ze d voi d ba r ( St ring name ) { /* same a s foo */ } }
If you run this code, you will see the following printed: Thread Thread Thread Thread
1 . foo ( ) : l . foo( ) : 2 . ba r ( ) : 2 . ba r ( ) :
start i ng ending start i ng ending
CrackingTheCodinglnterview.com I 6th Edition
1 77
Chapt@r 1 S I
Th read s a n d Locks
Synchronized Blocks
Similarly, a block of code can be synchronized. This operates very similarly to synchronizing a method. 1
public class MyClass extends Thread
2
3
{
public void run ( ) { myObj . foo ( name ) ;
4
5
}
6
7
8 9
10
} public cla s s MyObj ect { public void foo ( String name) { synch ronized (thi s ) {
11
}
12
}
13
} Like synchronizing a method, only one thread per insta nce of MyObj e ct can execute the code within the s y n c h ron i z ed block. That means that. if t h readl and t h re a d 2 have the same instance of MyOb j e c t, only one will be allowed to execute the code block at a time. Locks
For more granular control, we can utilize a lock. A lock (or mon itor) is used to synchronize access to a sha red resou rce by associati ng the resou rce with the lock. A thread gets access to a shared resource by first acqu i ring the lock associated with the resou rce. At any given time, at most one thread can hold the lock and, therefore, only one thread can access the shared resou rce. A common use case for locks is when a resource is accessed from multiple places, but should be only accessed by one thread at a time. This case is demonstrated in the code below. 1
public cla s s LockedATM { pri va t e Lock lock; pri va t e i nt ba l a n ce = 100;
2
3
4 S
public LockedATM ( ) { lock = n ew Reentrant lo c k ( ) ;
6
7
}
8 9
10 11
12 13
14 15 16 17
18 19
public i nt withdraw ( i nt value) { lock . lo c k ( ) ; i nt temp = bala nce ; t ry { Thread . sleep ( 100 ) ; t e m p = temp - value ; Thread . s leep ( 100 ) ; bala nce t emp; } catch ( I nter rupted E xception e ) { lock . unlo c k ( ) ; return t e mp; =
20
}
22
public
21
23
24
25
26 1 78
int d e p o s i t ( i n t value) { lock . lo c k ( ) ; int temp = bala nce ; t ry { Thread . s leep ( 100) ;
Cracking the Coding Interview, 6th Edition
}
Chapter 1 5 27
Lemp = Lemp + va lue ; Thread . slee p ( 300) ; balance = tem p ;
28
29 30
} catch ( I nterrupted Excepti on e) {
31
lock . u n l o c k ( ) ;
32
I
Th reads and Locks
}
return temp ;
33
}
34 } Of course, we've added code to intentionally slow down the execution of withd raw and depos it, as it
helps to illustrate the potential problems that can occur. You may not write code exactly like this, but the situation it mirrors is very, very real. Using a lock will help protect a shared resource from being modified i n unexpected ways. � Deadl ocks and Deadlock Prevention
A deadlock is a situation where a thread is waiting for an object lock that a nother thread holds, and this second thread is wa iting for an object lock that the first thread holds (or an equiva lent situation with severa l threads). Since each thread is waiting for the other thread to relinquish a lock, they both remain waiting forever. The threads are said to be deadlocked. In order for a deadlock to occur, you must have all four of the fol lowing conditions met: Only one process can access a resou rce at a given time. (Or, more accu rately, there is limited access to a resource. A deadlock could a lso occur if a resource has limited quantity.)
1. Mutual Exclusion:
Processes already holding a resou rce ca n request additional resou rces, without relin quishing their cu rrent resources.
2. Hold and Wait:
3. No Preemption: One 4.
process cannot forcibly remove a nother process' resource.
Circular Wait: Two
or more processes form a circular chain where each process is waiting on another resou rce in the chain.
Deadlock prevention entails removing any of the a bove conditions, but it gets tricky because many of these conditions are difficult to satisfy. For instance, removing #1 is d ifficult because many resou rces can only be used by one process at a time (e.g. printers). Most deadlock prevention algorithms focus on avoiding condition #4: circular wait. I nterview Questions 1 5.1
Thread vs. Process: What's the difference between a thread and a process? Hints: #405
'''' _ pg447
1 5.2
Context Switch: How would you measure the time spent i n a context switch? Hints: #403, #407, #4 1 5, #44 1 ,, , , ..__
'
_
_
CrackingTheCodinglnterview.com I 6th Ed iti on
pg447
1 79
Chapter 1 5 1 5.3
I
Threads and Locks
Dining Philosophers: I n the famous dining philosophers problem, a bunch of philosophers are sitti ng around a circular ta ble with one chopstick between each of them. A philosopher needs both chopsticks to eat, and always picks up the left chopstick before the right one. A deadlock cou ld potentially occur if a l l the phi losophers reached for the left chopstick at the same time. Using threads and locks, implement a simu lation of the dining philosophers problem that prevents dead locks. Hints: #4 1 9, #43 7
pg449
1 5.4
Deadlock-Free Class: Design a class which provides a lock only if there are no possible deadlocks. Hints: #422, #434
pg45 2
1 5.5
Call In Order: Suppose we have the following code:
public c l a s s Foo { public Foo ( ) { } p u b l i c void first ( ) { . . . } public void second ( ) { } public void t h i rd ( ) { } } •
.
•
•
•
.
•
.
.
The same instance of F oo will be passed to three different threads. T h readA will ca ll fi rst, t h reads will ca l l s e c ond, and t h read( will call t h i rd. Design a mecha nism to ensure that first is called before s e c ond and s e c ond is called before t h i rd. Hints: #4 7 7, #433, #446
pg456
1 5.6
Synchronized Methods: You are given a class with synchronized method A and a normal method
B. If you have two threads in one insta nce of a program, ca n they both execute A at the same time? Can they execute A and B at the same time? Hin ts: #429
pg458
1 5.7
FizzBuzz: In the classic problem FizzBuzz, you a re told to print the numbers from 1 to n. However, when the number is divisible by 3, print "Fizz''. When it is divisible by 5, print "Buzz''. When it is divis ible by 3 and 5, print "FizzBuzz''. In this problem, you a re asked to do this in a multithreaded way. Implement a multithreaded version of FizzBuzz with four threads. One thread checks for divisibility of 3 and prints "Fizz''. Another thread is responsible for divisibility of 5 and prints "Buzz''. A third thread is responsible for d ivisibility of 3 and 5 and prints "FizzBuzz''. A fourth thread does the num bers. Hints: #4 7 4, #439, #447, #458
pg458
Hints sta rt on page 676.
1 80
Cracking the Coding Interview, 6th Edition
16 Moderate
1 6.1
Number Swapper: Write a fu nction to swap a number in place (that is, without tempora ry vari
a bles). Hints: #492, #7 1 6, #737
pg462
1 6.2
Word Frequencies: Design a method to find the freq uency of occu rrences of any given word in a
book. What if we were ru nning this algorithm multiple times? Hints: #489, #536 _ _
1 6.3
pg46 3
Intersection: Given two stra ight line segments (represented as a start point and a n end point), compute the point of intersection, if any. Hints: #465, #472, #497, #5 1 7, #527 ,_____
1 6.4
pg464
Tic Tac Win: Design an algorithm to figure out if someone has won a game of tic-tac-toe. Hints: #7 1 0, #732 --· ·- · --·- .. ----- .......---·---·-""''
1 6.5
---........ ,_ __ .....-·--
__
pg4 6 6
Factorial Zeros: Write an algorithm which com putes the number of trailing zeros in n factorial. Hints: #585, # 7 1 1, #729, #733, #745
------
1 6.6
pg473
Smallest Difference: Given two arrays of integers, compute the pair of values (one va lue in each array) with the smallest (non-negative) difference. Retu rn the difference. EXAM PLE
In put: ( 1 , 3, 1 5, 1 1 , 2}, (23, 1 27, 235, 1 9, 8} Output: 3. That is, the pair (1 1 , 8). Hints: #632, #670, #679
_ pg474
1 6.7
Number Max: Write a method that finds the maximum of two numbers. You should not use if-else or any other com parison operator. Hints: #473, #5 1 3, #707, #728 --· -· - ..__
CrackingTheCodinglnterview.com I 6th Edition
pg475
1 81
Chapter 1 6 1 6.8
I
Moderate
English Int: Given any integer, print an English phrase that describes the integer (e.g., "One Thou
sand, Two Hundred Thirty Fou r"). Hints: #502, #588, #688
p g 477
1 6.9
Operations: Write method s to implement the m u ltiply, su btract, and divide operations for integers.
The results of all of these are integers. Use only the add operator. Hints: #572, #600, #6 1 3, #648
p g478
1 6. 1 0 Living People: Given a list of people with their birth and death years, implement a method to
com pute the yea r with the most number of people alive. You may assu me that a l l people were born between 1 900 and 2000 (inclusive). If a person was alive d u ring any portion of that yea r, they should be incl uded in that year's count. For exa mple, Person (birth = 1 908, death = 1 909) is included i n the cou nts for both 1 908 a nd 1 909. Hints: #476, #490, #507, #5 1 4, #523, #532, #54 1, #549, #576 ___
p g 482
1 6.1 1 Diving Board: You are building a diving boa rd by placing a bunch of planks of wood end-to-end. There a re two types of planks, one of length s h o rt e r and one of length longe r. You must use
exactly K planks of wood. Write a method to generate all possible lengths for the diving boa rd. Hints: #690, #700, #7 1 5, #722, #740, #747
p g486
1 6.1 2 XML Encoding: Since XML is very verbose, you a re given a way of encoding it where each tag gets
mapped to a pre-defined integer value. The lang uage/g rammar is as follows: Element Att ribute END Tag Value
--> --> --> --> -->
Tag Att r ibutes END Child ren END Tag Va lue 0 s ome p r e d e fin e d mapping to int string value
For exa m ple, the following XML might be converted into the compressed string below (assum i ng a mapping of family - > 1 , person - > 2 , f i rstName - > 3 , la stName - > 4 , state -> 5 ) . < family la stName= " McDowe l l " state=" CA " > < person firstName= "Gayle " > Some Mes s age< / person> < /family>
Becomes: 1 4 Mc Dowel l 5 CA 0 2 3 Gayle 0 Some Mes sage 0 0
Write code to print the encoded version of an XML element (passed in E lement and Att ribute objects). Hints: #466
.......................... . . p g489
1 6.1 3 Bisect Squares: Given two squares on a two-d i m ensional plane, find a line that would cut these two
squares in half. Assume that the top and the bottom sides of the square run parallel to the x-axis. Hints: #468, #479, #528, #560
p g490
182
Cracking the Coding I nterview, 6th Edition
C hapter 1 6
I Moderate
1 6.1 4 Best Line: Given a two-dimensional graph with points on it, find a line which passes the most
number of points.
Hints: #49 1, #520, #529, #563
. . ...... .......... .. pg49 2
1 6. 1 5 Master Mind: The Game of Master Mind is played as follows:
The computer has fou r slots, and each slot will contain a ball that is red (R). yellow ( Y), green (G) or blue (B). For example, the computer mig ht have RGG B (Slot #1 is red, Slots #2 and #3 are green, Slot #4 is blue). You, the user, a re trying to guess the solution. You might. for exa mple, guess YRGB. When you guess the correct color for the correct slot, you get a "hit:' If you guess a color that exists but is in the wrong slot, you get a "pseudo-hit:• Note that a slot that is a hit can never count as a pseudo-hit. For example, if the actua l solution is RGBY and you guess GGRR, you have one hit and one pseudo-hit. Write a method that, given a guess and a solution, retu rns the number of hits and pseudo-h its. Hints: #639,
#730
___ pg494 1 6. 1 6 Sub Sort: Given an array of integers, write a method to find ind ices m and n such that if you sorted elements m through n, the entire a rray would be sorted. Minimize n - m (that is, find the smal lest
such seq uence). EXAMPLE Input: 1, 2 , 4, 7, 1 0 , 1 1 , 7, 12 , 6, 7, 1 6 , 18 , 19 Output: ( 3 , 9 ) Hints: #482,
#553, #667, #708, #735, #746
.... ................ .. ..... .. .. .. .. .. .. .. .. . ..
pg496
1 6.1 7 Contiguous Sequence: You are given an a rray of integers (both positive and negative). Find the
contiguous sequence with the la rgest sum. Return the sum. EXAMPLE
3, - 2 , 4, - 1 0 Output: 5 ( i . e , { 3 , - 2 , 4} )
ln put: 2 , - 8 ,
•
Hints:
#53 1, #55 1, #567, #594, #6 1 4 ··· ·· ·· --
pg49 8
1 6. 1 8 Pattern Matching: You are given two strings, patte rn and value. The patte rn string consists of just the letters a and b, descri bing a pattern within a string. For exa mple, the string c a t c atgocatgo matches the pattern a a b a b (where c a t i s a and go is b). It also matches patterns like a, ab, and b.
Write a method to determine if va l ue matches patte rn. Hints: #63 1,
#643, #653, #663, #685, #7 1 8, #727 .
· · · -·-
........... ..... .. .. .. . .. .. .. ... pg499
CrackingTheCodinglnterview.com I 6th Edition
1 83
I Moderate
Chapter 1 6
1 6. 1 9 Pond Sizes: You have an integer matrix representing a plot of land, where the value at that loca
tion represents the height above sea level. A value of zero indicates water. A pond is a region of water connected vertically, horizonta lly, or diagonally. The size of the pond is the total number of connected water cells. Write a method to compute the sizes of a l l ponds in the matrix. EXAMPLE Input: 0 0 1 0
2 1 1 1
1 0 0 0
0 1 1 1
Output: 2, 4, 1 (in any order) Hints: #674,
#687, #706, #723
1 6.20 T9: On old cell phones, users typed on a numeric keypad and the phone wou ld provide a list of -
words that matched these num bers. Each digit mapped to a set of 0 4 letters. I m plement an a lgo rithm to retu rn a list of matching words, given a sequence of dig its. You are provided a list of valid words (provided in whatever data structu re you'd like). The mapping is shown in the diagram below: 1
2
3
a bc
def
4
s
6
ghi
jkl
mno
7
8
9
pqrs
tuv
wxyz
0
EXAM PLE Input:
87 3 3
Output:
tree , u s e d
Hints: #47 7,
#487, #654, #703, #726, #744
pg 505
1 6.2 1 Sum Swap: Given two a rrays of integers, find a pair of values (one value from each a rray) that you
can swap to give the two a rrays the same sum. EXAMPLE
Input: {4,
1,
2,
1, 1,
2} and {3, 6,
3, 3}
Output: { 1 , 3 ) Hints: #545, #557,
#564, #57 1, #583, #592, #602, #606, #635
pg 509
1 84
Cracking the Cod i n g Interview, 6th Edition
1 6 I Moderate
Chapter
1 6.22 Langton's Ant: An ant is sitting on an infinite grid of white and black squares. It initially faces right.
At each step, it does the following: ( 1 ) At a white square, fli p the color of the square, turn 90 degrees right (clockwise), and move forwa rd one unit.
(2) At a black square, flip the color of the square, turn 90 degrees left (cou nter-clockwise), and move forward one unit.
Write a program to simu late the first K moves that the ant makes and print the final board as a grid. Note that you a re not provided with the data structure to represent the grid. This is something you must design you rself. The only input to your method is K. You should print the final grid and return nothing. The method signatu re might be something like void p ri ntKMoves ( i nt K ) . Hints: #474,
#48 1, #533, #540, #559, #570, #599, #6 1 6, #627 -··-·· -· ·-··-· · -· ·-··-· -· · --
pg 5 1 2
1 6.23 Rand7 from Rands: Im plement a method rand7 ( ) given rand s ( ) . That is, given a method that generates a random number between 0 and 4 (inclusive), write a method that generates a random
number between 0 and 6 (inclusive). Hints: #505,
#574, #637, #668, #697, #720
···-·- -- - --- pg 5 1 8
1 6.24 Pairs with Sum: Design an algorithm to find a l l pairs of integers within an a rray which sum to a
specified value. Hints: #548,
#597, #644, #673 __ __
pg 5 2 0
1 6.25 LRU Cache: Design and build a "least recently used" cache, which evicts the least recently used
item. The cache should map from keys to values (a llowing you to insert and retrieve a va lue associ ated with a particular key) and be initialized with a max size. When it is fu ll, it should evict the least recently used item. Hints: #524,
#630, #694 -- ..
__ ______ _
pg 5 2 1
1 6.26 Calculator: Given an arithmetic equation consisting of positive integers, + , , * and I ( n o paren -
theses), compute the resu lt. EXAMPLE I nput:
2 * 3+ 5 / 6 * 3+15
Output:
23 5
Hints: #52 1,
#624, #665, #698
•
------
--·- · · · · · ·�----···-
CrackingTheCodingl nterview.com I 6th Edition
pg 5 24
1 85
17 Hard
17.1
Add Without Plus: Write a fu nction that adds two numbers. You should not use + or any arithmetic
operators. Hints: #467, #544, #60 1, #628, #642, #664, #692, #712, #724
pg 530 1 7 .2
Shuffle: Write a method to shuffle a deck of ca rds. It must be a perfect shuffle-in other words, each of the 52! permutations of the deck has to be equally likely. Assu me that you a re given a ra ndom nu mber generator which is perfect. Hints: #483, #579, #634 -----
1 7 .3
· - ·· -··- -·- ·· ··· ·- -·-·· -- --·-··-- ·--·-·--
Random Set: Write a method to ra ndom ly generate a set of m integers from an a rray of size n. Each element must have equal p robability of being chosen. Hints: #494, #596
........... .. pg 5 3 2 1 7.4
Missing Number: An array A contains all the i ntegers from 0 to n, except for one number which is m issing. In this problem, we cannot access an entire integer in A with a single operation. The elements of A a re represented in binary, and the only operation we can use to access them is "fetch the j th bit of A [ i ] ," which takes constant time. Write code to find the missing integer. Can you do it in O ( n ) time? Hints: #6 1 0, #659, #683
pg 5 3 3 1 7 .S
Letters and Numbers: Given a n a rray fil led with letters and num bers, find the longest suba rray with
an equal number of letters and num bers. Hints: #485, #5 1 5, #6 7 9, #671, #713
pg 5 3 6 1 7.6
Count of 2s: Write a method to cou nt the number of 2s that appear i n all the numbers between 0 and n (inclusive) . EXAMPLE
In put: 25 Output: 9 (2, 1 2, 20, 2 1 , 22, 23, 24 and 25. Note that 22 cou nts for two 2s.)
Hints: #573, # 6 1 2, #64 1
pg 5 3 8
186
C racking the Coding I nte rview, 6th Edition
Chapter 1 7 1 7.7
I Hard
Baby Names: Each year, the government releases a list of the 1 0000 most common ba by names
and their frequencies (the number of babies with that name). The only problem with this is that some names have multiple spellings. For exam ple, "John" and "Jon" are essentially the same name but would be listed sepa rately in the list. Given two lists, one of names/freq uencies a nd the other of pairs of equ iva lent na mes, write an a lgorithm to print a new list of the true frequency of each name. Note that if John and Jon a re synonyms, and Jon and Joh n ny a re synonyms, then John and Johnny a re synonyms. (It is both tra nsitive and symmetric.) I n the final list, a ny name ca n be used as the "real" name. EXAM PLE
I nput:
Names: John (1 5), Jon (1 2), Chris (1 3), Kris (4), Christopher (1 9) Synonyms: (Jon, John), (John, Johnny), (Chris, Kris), (Chris, Christopher) Output: John (27), Kris (36) Hints: #478, #493, #5 72, #537, #586, #605, #655, #675, #704
p g 54 1
1 7 .8
Circus Tower: A
circus is designing a tower routine consisting of people sta nding atop one a noth er's shoulders. For practical and aesthetic reasons, each person must be both shorter a nd lig hter than the person below him or her. Given the heig hts a nd weig hts of each person in the circus, write a method to com pute the largest possible number of people in such a tower.
EXAMPLE
l nput (ht, wt): ( 6 5 , 100 ) ( 70 , 15 0 ) ( 5 6 , 90 ) ( 7 5 , 1 9 0 ) ( 60, 9 5 ) ( 68 , 1 1 0 )
Output: The longest tower is length 6 a nd includes from top to bottom: ( 56 , 90 ) ( 60 , 9 5 ) ( 65 , 100 ) ( 68 , 1 1 0 ) ( 70 , 1 50 ) ( 7 5 , 190 ) Hints: #638, #657, #666, #682, #699 . ..
1 7.9
·· ·· ·· -
-
-
--
--
. . . . . ··-·-·-·····
···-······-·······--
.. .
.. .. .. .. . .. .. . pg 546 .
.
.
.
.
Kth Multiple: Design an a lgorithm to find the kth number such that the only prime factors a re 3, 5, and 7. Note that 3, 5, and 7 do not have to be factors, but it should not have a ny other prime factors. For example, the first several m u ltiples would be (in order) 1 , 3, 5, 7, 9, 1 5, 2 1 . Hints: #488, #508, #550, #59 1, #622, #660, #686
1 7. 1 0 Majority Element: A majority element is an element that makes u p more tha n half of the items i n
a n a rray. Given a positive integers a rray, find t h e majority element. I f there i s n o majority element, return - 1 . Do this in O ( N ) time and 0 ( 1 ) space.
EXAM PLE
I nput:
Output:
1 2 5 9 5 9 5 5 5 5
Hints: #522, #566, #604, #620, #650
. ............................ pg 554
1 7.1 1 Word Distance: You have a l arge text file containing words. Given any two words, find the shortest
distance (in terms of number of words) between them in the file. If the operation will be repeated many times for the same file (but different pairs of words), can you optim ize your solution?
Hints: #486, #50 1, #538, #558, #633
CrackingTheCodinglnterview.com I 6th Edition
1 87
I
Chapter 1 7 1 7. 1 2
Hard
BiNode: Consider a simple data structure called BiNode, which has pointers to two other nodes.
public c l a s s BiNode { public BiNode nodel, node2 ; public int data ; }
The d ata structu re B i Node could be used to represent both a b i n a ry tree (where nod e l is the left node and nod e 2 is the right node) or a doubly linked list (where nodel is the previous node and nod e 2 is the next node). I m plement a method to convert a b i n a ry sea rch tree (im plemented with B iNode) i nto a doubly linked list. The va lues should be kept in order and the operation should be performed in place (that is, on the original data structu re). Hints: #509,
#608, #646, #680, #70 1, #7 1 9
. ....... ... .................. ............ pg 560
1 7.1 3 Re-Space: Oh, no! You have accidentally removed all spaces, punctuation, and capitalization i n a lengthy document. A sentence l i ke "I reset t h e comput e r . It s t i l l d i dn ' t boot ! " beca m e " i r e s ett h e c omput e r i t s t i l l d i d ntboot''. You'll deal with the punctuation and capi ta lization later; right now you need to re-insert the spaces. Most of the words a re i n a d i ctionary but a few a re not. Given a dictionary (a list of strings) and the document (a stri ng), design an algorithm to unconcatenate the document in a way that minimizes the number of unrecognized cha racters.
EXAMPLE: I nput:
j es s looked j u st l i ket imh e rb rot her
Output: j e s s looked j u st l i ke t im h e r brot h e r ( 7 unrecog n ized characters) Hints: #496,
#623, #656, #677, #739, #749 . . . .. ... .
1 7.1 4
.
. .. .
.
.
. ........... ......
pg 5 6 3
,_,,_,,,,,,_,,_,,,,,,_,,_,,_, ______ ,,_,,_,,_,,_,,_,,_ _ _
Smallest K: Design an algorithm to find the smallest K num bers i n an a rray.
Hints: #470,
#530, #552, #593, #625, #647, #66 1, #678
... ....... ... - - - - - - - - pg567
1 7. 1 5 Longest Word: Given a l ist of words, write a prog ra m to fi nd the longest word made of other words in the list.
EXAMPLE l nput: cat , ba n a n a , do g , n a n a , wa l k , wa l ker , dogwa l ke r Output: dogwa l ker Hints: #475,
#499, #543, #589
pg 572
1 7 .16 The Masseuse: A popular masseuse receives a seq uence of back-to-back appointment requests and is debati ng which ones to accept. She needs a 1 5-minute break between appointments and therefore she cannot accept any adjacent req uests. Given a sequ ence of back-to-back a ppoi nt ment requests (a ll m ultiples of 1 5 m i nutes, none overlap, and none can be moved), find the opti mal (highest tota l booked m i nutes) set the masseuse can honor. Return the number of m i n utes.
EXAMPLE Input: { 30 , 1 5 , 60 , 75 , 45 , 1 5 , 1 5 , 45 } Output: 180 minutes Hints:
( { 30 , 60 , 45 , 45 } ) .
#495, #504, #5 1 6, #526, #542, #554, #562, #568, #578, #587, #607 . . . . .. .. . . . . . .
1 88
Cra ck i ng the Coding I nterview, 6th Edition
. . ..
_
-
- - · ··
_
_
_ _
_
pg5 74
Chapter 1 7
I
Hard
1 7.1 7 Multi Search: Given a string b and an array of sm aller strings T, design a method to search b for
each small stri ng in T. Hints: #480, #582, #6 1 7, #743
pg 57 8
1 7. 1 8 Shortest Supersequence: You are given two arrays, one shorter (with all distinct elements) and one
longer. Find the shortest subarray in the longer array that contains all the elements in the shorter a rray. The items can appear in any order. EXAMPLE ln put: { l , S, 9 } I { 7 , S, 9 , 0, 2, 1, 3 , 5 . 7 . 9 . 1 , 1 , 5 , 8 , 8 , 9 , 7 } O utput: [ 7 , 10 ] (the u nderli ned portion above) Hints: #645, #652, #669, #68 1, #69 1 , #725, #73 1, #74 1
.. ........ pg 5 84
1 7. 1 9 Missing Two: You a re given an array with all the num bers from 1 to N appearing exactly once,
except for one number that is missing. How can you find the missing number in O ( N ) time and 0( 1 ) space? What if there were two numbers missing? Hints: #503, #590, #609, #626, #649, #672, #689, #696, #702, #7 1 7
1 7.20 Continuous Median: Num bers are random ly generated and passed to a method. Write a program
to find and maintain the median value as new values are generated. Hints: #5 1 9, #546, #575, #709
1 7.21 Volume of Histogram: Imagine a histog ram (bar graph). Design an a lgorithm to com pute the
volume of water it could hold if someone poured water across the top. You can assume that each h istogram bar has width 1 . EXAMPLE (Black bars are the histog ram. Gray is water.) In put: { 0 , 0 , 4, 0 , 0 , 6 , 0, 0 , 3 , 0, S , 0 , 1 , 0 , 0 , 0 }
Output: 2 6
0 0 4 0 0 6 0 0 3 0 5 0 1 0 0 0
Hints: #629, #640, #65 1, #658, #662, #676, #693, #734, #742 . . ..
-- -
·
- ---
---
. . ... --- pg 59 6
1 7.22 Word Transformer: Given two words of equal length that are in a dictionary, write a method to
tra nsform one word into a n other word by changing only one letter at a time. The new word you get in each step m ust be in the dictionary. EXAMPLE In put: DAMP, LI KE Output: DAMP -> LAMP -> LIMP -> LIME -> LIKE Hints: #506, #535, #556, #580, #598, #6 1 8, #738
- - .... .. . .. .. pg 602
CrackingTheCodingl nterview.com I 6th Edition
189
I Hard
Chapter 1 7
1 7.23 Max Black Square: Imagine you have a square matrix, where each cell (pixel) is either black or wh ite Design an algorith m to find the maximum subsquare such that a l l fou r borders a re filled with black p ixels. Hints: #684, #695, #705, #7 1 4, #72 1, #736
pg 608
1 7.24 Max Submatrix: Given an NxN matrix of positive and negative integers, write code to find the submatrix with the la rgest possible sum. Hin ts: #469, #5 1 1, #525, #539, #565, #58 1, #595, #6 15, #62 1 . . .. .. .. . .. . .. .. . . .. . ..... .. . ... ...
. . ... .. . ... .. ... . ... .. . ......... .. ........ ... ... .
Pg 6 1 1
1 7 .25 Word Rectangle: Given a l ist of m i l l ions of word s, design a n a lgorithm to create the largest possible rectangle of letters such that every row forms a word (read ing left to rig ht) and every colu m n forms a word (reading top to bottom). The words need not be chosen consecutively from the list, but all rows must be the same length and all columns must be the same heig ht. Hints: #477, #500, #748
pg 6 1 5
1 7.26 Sparse Similarity: The similarity of two documents (each with distinct words) is defined to be the size of the intersection d ivided by the size of the u n ion. For exa m p le, if the documents consist of i ntegers, the sim ila rity of { l , 5 , 3 } and { 1 , 7 , 2 , 3 } is 0 . 4, because the intersection has size 2 and the union has size 5. We have a long list of documents (with d istinct va lues and each with an associated ID) where the simila rity is believed to be "spa rse:'That is, any two arbitrarily selected documents a re very likely to have similarity 0. Design a n algorithm that returns a list of pairs of document IDs and the associated similarity. Print only the pairs with similarity g reater than 0. Em pty documents should not be printed at all. For simplicity, you may assu me each document is represented as an a rray of d istinct integers. EXAM PLE In put: 13 : 16 : 19 : 24 :
{ 14, 1 5 , 100, 9 , 3 } { 32 , 1 , 9 , 3 , 5 } {15, 29, 2 , 6, 8 , 7} { 7 , 10 }
Output: IDl , ID2
SIMI LARITY
1 3 , 19 1 3 , 16 1 9 , 24
0.1 0 . 25 0 . 14285714285 7 14285
Hints: #484, #498, #5 1 0, #5 18, #534, #547, #555, #56 1, #569, #577, #584, #603, #6 1 1, #636 ___
1 90
Cracking the Coding Interview, 6th Edition
pg 620
1 Solutions to Arrays and Stri ngs
Is Unique: Im plement an a lgorith m to determ ine if a string has all u n ique characters. What if you ca nnot use additional data stru ctures?
1 .1
pg 90
SOLUTION
You should fi rst ask your interviewer if the string is an ASCII string or a U n icode string. Asking this q uestion wi l l show an eye for detail and a solid foundation in com puter scien ce. We' l l assume for simplicity the char acter set is ASCII. If this assum ption is not valid, we wou ld need to increase the storage size. One sol ution is to create an array of boolea n values, where the flag at index i ind icates whether character i in the a l phabet is contained in the string. The second time you see this character you can immed iately return fa l s e . We c a n a lso immed iately return fa l s e i f t h e string length exceeds t h e n u m ber of u n ique characters i n the a l pha bet. After a l l, you can't form a string of 280 u n ique characters out of a 1 28-character a l phabet.
I
It's also okay to assume 256 characters. Th is wou ld be the case in extended ASCII. You should clarify you r assumptions with you r interviewer.
The code below implements this algorithm. 1
boolea n isUniq ueCha r s ( St ring s t r ) { if ( st r . lengt h ( ) > 128 ) ret u r n false;
2 3
4
boolea n [ ] char_set = new boolea n [ 128 ] ; for ( i nt i = 0; i < s t r . lengt h ( ) ; i++) { i nt val = st r . c h a rAt ( i ) ; if ( char_set [ val ] ) { / / Already fou nd t h i s char i n str ing ret u r n false; } char_set [val] = t rue ;
S 6 7
8 9
10 11
12 13
}
return true; }
The time com plexity for this code is O ( n ) , where n is the length of the string. The space com plexity is 0 ( 1 ) . (You could also a rgue the time com plexity is 0 ( 1 ) , since the for loop will never iterate t h roug h more than 1 28 cha racters.) If you did n't wa nt to assume the character set is fixed, you could express the complexity as 0 ( c ) space and 0( min ( c, n ) ) or 0 ( c ) time, where c is the size of the character set.
1 92
Cracking the Coding Interview, 6th Edition
Solutions to Cha pter 1
I Arrays and Strings
We c a n reduce our space usage b y a factor o f eight by u s i n g a b i t vector. We will assume, i n the below code, that the string only uses the lowercase letters a through z. This will allow us to use just a single int. boolean is U niq ueC ha rs ( St ring st r ) { i nt checker = 0 ; for ( i nt i = 0 ; i < st r . lengt h ( ) ; i++) { int va l = str . charAt ( i ) - ' a ' ; if ( ( checker & ( 1 < < va l ) ) > 0 ) { retu rn false;
1
2
3
4 S
6
7
}
8
checker I = ( 1 < < va l ) ; 9 } 1� return true; 11 } If we can't use add itional data structures, we can do the following: 1. Compare every character of the stri ng to every other character of the string. Th is will take 0 ( n2) time and 0 ( 1 ) space.
2. If we a re allowed to modify the input string, we cou ld sort the string i n O ( n log ( n ) ) time a n d then l inearly check the string for neig hboring cha racters that are identical. Carefu l, though: many sorting a lgorithms ta ke up extra space. These solutions a re not as optimal i n some respects, but might be better depending on the constrai nts of the p roblem. 1 .2
Check Permutation: Given two strings, write a method to decide if one is a permutation of the other. p g 90
SOLUTION
Like in many questions, we should confirm some d etails with our i nterviewer. We should understand if the permutation comparison is case sensitive. That is: is God a permutation of dog? Additionally, we should ask if wh itespace is sign ificant. We wi l l assu me for this problem that the compa rison i s case sensitive and whitespace is sign ificant. So, "god " is d ifferent from "dog". Observe first that strings of d ifferent lengths cannot be permutations of each other. There a re two easy ways to solve this problem, both of which use this optimization. Solution #1 : Sort the strings.
If two stri ngs a re permutations, then we know they have the same cha racters, but in different orders. There fore, sorting the strings will put the characters from two permutations i n the same order. We just need to compare the sorted versions of the stri ngs. 1 St r i ng sort ( St ring s ) { 2 char [ ] content = s . toCharArray ( ) ; j ava ut i l Ar rays . sort ( content ) ; 3 4 return new St ring( content ) ; s } .
6 7
8
9
10
.
boolean permutat ion ( St ri ng s , St ring t ) { if ( s . lengt h ( ) ! = t . lengt h ( ) ) { return fa l s e ; }
Crr.ickingTheCodingtnterview.cOm I 6th Edition
193
Solutions to Chapter 1 11
12
I Arrays and Stri ngs
ret u r n sort ( s ) . eq ua l s ( sort ( t ) ) ; }
Though this algorithm is not as optimal in some senses, it may be preferable i n one sense: It's clean, simple and easy to understa nd. I n a practical sense, this may very wel l be a su perior way to im plement the problem. However, if efficiency is very importa nt, we can im plement it a different way. Solution #2: Check if the two strings have identical character counts.
We can also use the definition of a permutation-two words with the same character cou nts-to im ple ment this a lgorith m. We simply iterate through this code, cou nting how many times each character a ppea rs. Then, afterwards, we com pare the two a rrays. 1
boolea n pe rmutation ( String s , String t ) { if ( s . lengt h ( ) ! = t . lengt h ( ) ) { return fa lse; }
2
3
4 5
6
int [ ] letters
7
8
=
new int [ 128 ] ; / / Assumpt ion =
9 10 11
12 13
14
15
16
17 18 19
cha r [ ] s_a rray s . toCharArray ( ) ; for ( ch a r c : s_a rray ) { / / count number of e a c h char in s . letters [ c ] ++; }
for ( int i = 0 ; i < t . lengt h ( ) ; i++ ) { int c = ( int ) t . c h a rAt ( i ) ; letters [ c ] ; if ( letters [ c ] < 0 ) { ret urn fa l s e ; - -
} }
20 21
return true;
22 } Note the assumption on line 6. In you r interview, you should a lways check with you r i nterviewer a bout the size of the character set. We assumed that the character set was ASCII. 1 .3
URLify: Write a method to replace a l l spaces i n a stri ng with '%20'. You may assume that the stri ng has sufficient space at the end to hold the add itional characters, and that you a re given the "true" length of the stri ng. (Note: if im plementing in Java, please use a character a rray so that you can perform this operation i n place.)
EXAM PLE
,,
I nput:
"Mr J oh n Smith
Output:
"Mr%20J ohn%20Smith"
J
13 pg 90
SOLUTION ---- ----
------
A com mon
··-··-· ·- ·-····· ·- ··-· ·- . .- ·· -· -·-
approach i n string manipulation problems is to edit the string sta rting from the end and working backwards. This is usefu l because we have a n extra buffer at the end, which a l lows us to change cha racters without worrying about what we're overwriti ng.
1 94
Cracki ng
the Cod ing Interview, 6th Edition
Solutions to Chapter 1
I Arrays and Strings
We will u s e t h i s approach i n this problem. The a lgorithm employs a two-scan approach. I n t h e first scan, we count the number of spaces. By tri pling this number, we can compute how many extra characters we will have in the final string. I n the second pass, which is done in reverse order, we actually edit the string. When we see a space, we replace it with %20. If there is no space, then we copy the original character. The code below implements this algorithm.
1
2
void
3
5
6 7
8
9
10 11
I I E nd
a rray
'
12
13
14 15
=
16 17
18
1, 21
=
=
4
2e
rQplacQSpacQ s ( c ha r ( J s t r , i n t tru e L e ngt h ) { 0; int spaceCount = 0, index, i for ( i 0; i < trueLength ; i++ ) { if ( s t r [ i ] == ' ' ) { s p a ce c o u n t++ ; } } i nd ex = truelengt h + s p aceC o u n t * 2 ; ' \0 ' ; i f ( t r ue length < s t r . le n gt h ) s t r [ t r ue lengt h ] for ( i = t r ue l e n gth - 1 ; i > = 0 ; i - - ) { if ( str [ i J == ' ) { '0'; s t r [ index - 1 ] st r [ i nd ex - 2 ] = ' 2 ' ; st r [ i nd ex - 3 ] = ' % ' ; i n d ex i nd ex - 3 ; } else { s t r [ index - 1 ] = st r [ i ] ; i nd ex - - ; } }
}
We have implemented this p roblem using character a rrays, because Java strings a re immutable. If we used strings d i rectly, the function would have to return a new copy of the string, but it would allow us to im ple ment this in just one pass. 1 .4
Palindrome Permutation: Given a string, write a function to check if it is a permutation of a pa lindrome. A palindrome is a word o r phrase that is the same forwards and backwards. A permutation is a rearrangement of letters. The palindrome does not need to be limited to just d ictionary words.
EXAMPLE I n p ut:
Ta ct Coa
O utput:
True (permutations: "ta co cat'; "at co e t a ·: etc.) pg 9 1
SOLUTION - ··-··-· ··· " ' "' " " " " ' ' " ' " ' "' "' "
· · · - ··-· ··· · ·-·· -·- ·· - -·- · ·-··-··- · · - ··-··- · · - · ·-··- · ·-· ·-··- · ·-··-··- · · -·· -
This is a q uestion where it helps to figure out what it means for a string to be a permutation of a palindrome. Th is is like asking what the "defi n ing features" of such a string would be. A palindrome is a stri ng that is the same forwards and backwards. Therefore, to decide if a string is a permu tation of a palindrome, we need to know if it can be written such that it's the same forwards and backwards. What does it ta ke to be able to write a set of characters the same way forwards and backwards? We need to have an even n u m ber of a l most all cha racters, so that half can be on one side and half can be on the other side. At most one character (the middle character) can have an odd count. For example, we know t a ct c oapapa is a permutation of a pa lindrome because it has two Ts, four As, two
CrackingTheCodingl nterview.com I 6th Edition
1 95
Solutions to Chapter 1
I Arrays and Strings
Cs, two Ps, and one 0. That 0 would be the center of a l l possible pa lindromes. TO
I
be more precise, strings with even length (after removing all non-letter characters) m ust have a l l even counts of characters. Strings of an odd length m ust have exactly one character with an odd count. Of course, an "even" string can't have an odd n u m ber of exa ctly one character, otherwise it woul d n't be an even-length string (an odd n u m ber + many even n u m bers = an odd n u mber). Likewise, a string with odd length can't have a l l characters with even counts (su m of evens is even). It's therefore sufficient to say that, to be a permutation of a palindrome, a string can have no more than one character that is odd . This wil l cover both the odd and the even cases.
This leads us to our first a l gorithm. Solution #1
Implementing this algorithm is fairly straightforward. We use a hash table to count how many times each character appears. Then, we iterate through the hash table and ensure that no more than one character has an odd count. 1 boolean i s Permut ationOfPal i nd rome ( St ring ph rase ) { 2 int [ ] t a b le = buildC h a r F requencyTable ( phrase ) ; ret u r n chec kMaxOneOdd ( t a b le ) ; 3 4 } 5
/*
Check t hat no more than one c h a racter has an odd count . * / boolean checkMaxOneOdd ( i nt [ ] table) { 8 boolean foundOdd = fa lse ; for ( i nt count : table ) { 9 10 i f ( count % 2 == 1) { 11 i f ( foundOdd ) { 12 ret u r n fa lse ; 13 } 14 fou ndOdd = t r ue; 15 } 16 } 17 ret u r n t r ue; 18 } 19 20 / * Map each c h a racter to a numbe r . a -> 0 , b -> 1 , c -> 2 , et c . 21 * T h is i s case i nse ns it ive Non - letter cha racters map to - 1 . * / 2 2 i nt getCha rNumbe r ( C h a racter c ) { 23 int a = Cha racte r . getNumericValue ( ' a ' ) ; 24 int z Cha racte r . getNumericValue ( ' z ' ) ; 25 i nt v a l = Character . getNumericVa l ue ( c ) ; if ( a true
pa l e s , p a l e - > t r u e pa l e ,
ba l e
pa l e ,
bae
->
->
true fa l s e pg 9 1
SOLUTION ----·-• •
«- •·--·-·--
There is a "brute force" a lgorithm to do this. We could check all possible strings that a re one edit away by testing the removal of each character (and comparing), testing the replacement of each character (and comparing), and then testing the inse rtion of each possible character (and comparing). That wou ld be too slow, so let's not bother with i mplementing it. This is one of those problems where it's helpful to think a bout the "mean i ng" of each of these operations. What does it mean for two strings to be one insertion, replacement. or remova l away from each other? •
•
•
Replacement: Consider two strings, such as b a l e and pa l e, that a re one replacement away. Yes, that does mean that you could replace a character i n b a l e to make pa l e. But more precisely, it means that they a re different only in one place. Insertion: The strings a p p l e and a p l e are one inse rtion away. This means that if you compared the strings, they would be identical-except for a shift at some point i n the strings. Removal: The strings a p p l e and a p l e a re a lso one removal away, since remova l is just the inverse of insertion.
We can go ahead and implement th is algorithm now. We'll merge the insertion and remova l check into one step, and check the replacement step sepa rately. Observe that you don't need to check the strings for insertion, removal, a n d replacement ed its. The lengths of the strings will ind icate which of these you need to check. 1
boolean oneEditAway ( St ring first , St r i ng second ) { if ( first . lengt h ( ) s econd . lengt h ( ) ) { 3 return oneEditReplace ( first , second ) ; 4 } e l s e if ( fi rst . lengt h ( ) + 1 == s econd . lengt h ( ) ) { 5 return oneEdit i n s e rt (first, s econd ) ; 1 second . length ( ) ) { 6 } e l s e if (fi rst . length ( ) 7 retu r n oneEditinsert ( second , first ) ; 8 } 9 return fa l s e ; 10 } 2
==
-
==
11
12
boolean oneEdit Replace ( St ring s l , String s 2 ) { boolean foundDifference = fa l s e ; 14 for ( int i = 0 ; i < s l . lengt h ( ) ; i++) { if ( sl , cha rAt ( i ) ! = s 2 . charAt ( i ) ) { 15 if ( foundDifference ) { 1i 17 ret u r n fa lse ; 18 } 13
19
CrackingTheCodingl nterview.com I 6th Edition
1 99
Solutions to Chapter 1
20 21
}
22
fou ndDifference
I Arrays and Strings =
t rue ;
} ret urn t r u e ;
23
24 }
25 26
/* Check if yo u c a n insert a cha ract e r into s 1 to make s 2 . * / 27 boolea n oneEdit insert ( String s l , String s 2 ) { int index! = 0 ; 28 29 i n t index2 0; 30 while ( index2 < s 2 . length ( ) && i ndexl < s l . length ( ) ) { 31 i f ( s l . cha rAt ( i ndexl) ! = s 2 . cha rAt ( i ndex2 ) ) { if ( index! != index2) { 32 return fals e ; 33 34 } 35 i ndex2++ ; 36 } else { 37 index!++; 38 index2++; 39 } 40 } 41 return t rue ; 42 } =
This a lg orithm (and a l most a n y reasona ble a lgorithm) ta kes 0 ( n ) time, where n is the length of the shorter stri ng.
I
Why is the runtime d i ctated by the shorter string instead of the longer stri ng? If the strings a re the same length (plus or m i n us one character), then it does n't matter whether we use the longer string or the shorter stri ng to define the runtime. If the strings a re very d ifferent lengths. then the algorithm wi l l term in ate in 0 ( 1 ) time. One rea l ly, really long string therefore won't significa ntly extend the runtime. ft increases the runtime on ly if both strings a re long.
We might n otice that the code for one Edi t R e p l a c e is very similar to that for oneEdi t l n s e rt . We can merge them i nto one method. To do this, observe that both methods follow sim ilar logic: compare each character a n d ensure that the stri ngs a re o n ly d ifferent by one. The methods va ry in how they handle that d ifferen ce. The method one E d i tReplace does noth i n g other than flag the d ifferen ce, whereas one E d i t l n s e rt increments the poi nter to the longer string. We can handle both of these in the same method. 1 boolean oneEditAway ( St ring firs t , St ring second ) { 2 / * Length checks . */ 3 if (Math . a bs ( fi r s t . le ngth ( ) - second . length ( ) ) > 1 ) { 4 ret urn fa lse; 5
}
6 7 B
9
10
11 12
13
14 15
200
/ * Get shorter and longer string . */ St ring s l = fi rs t . length ( ) < second . lengt h ( ) St ring s 2 = first . length ( ) < second . length ( )
first : second ; second : first ;
int index! = 0; int index2 = 0; boolean foundDifference = fals e ; while ( i ndex2 < s 2 . length ( ) && i ndex! < s l . length ( ) ) { if ( s l . cha rAt ( index l ) ! = s 2 . cha rAt ( i ndex2 ) ) {
I
Crac king the Coding Interview, 6th Edition
Solutions to Chapter 1 16
/* E n s ure that t h i s is the fi rst d ifference found . */ (foundDifference) return false ; foundDifference = t r u e ; if
17
18
19 20
==
,
if ( s 1 . lengt h ( ) s 2 . length ( ) ) { / / On replace move s h orter pointer index1++ ; } } else { i ndex1++ ; // If mat ch ing, move short e r pointer } index2++; / / Always move pointer for longer st ring _
21
22.
23
24 25 26 27
28 29
I Arrays and Strings
} return true;
}
Some people might a rgue the first approach is better, as it is clearer and easier to follow. Others, however, will a rgue that the second approach is better, since it's more compact and doesn't dupl icate code (which ca n facilitate maintainability). You don't necessarily need to "pick a side:'You ca n discuss the tradeoffs with your i nterviewer. String Compression: Implement a method to perform basic string compression using the cou nts of repeated cha racters. For example, the stri ng a a b c c c c c a a a would become a 2 b l c 5 a 3 . If the "compressed" string would not become sma l ler than the orig inal stri ng, you r method should return the original string. You ca n assume the string has only uppercase and lowercase letters (a z).
1 .6
-
pg 9 1
SOLUTION
At fi rst gla nce, implementing this method seems fai rly stra ig htforward, but perhaps a bit tedious. We iterate through the stri ng, copyi ng characters to a new stri ng and counting the repeats. At each iteration, check if the cu rrent cha racter is the same as the next cha racter. If not, add its compressed version to the result. How hard could it be?
1
2 3
4 5
6
String compres sBad ( St r i ng st r ) { String compres s edSt ring = " " ; int countconsecut ive = 0 ; for ( i nt i = 0 ; i < st r . length ( ) ; i++ ) { countConsecut ive++ ; /* If next cha racter i s d ifferent than current, a p pend this char to res u lt . * / if ( i + 1 > = st r . lengt h ( ) I I st r . c h a rAt ( i ) ! = st r . cha rAt ( i + 1 ) ) { compressedSt ring += " " + st r . c h a rAt ( i ) + countCo n s ecutive; countConsecut ive = 0; }
7 8
9
10
11
12
13 14
} return compressedSt ring . lengt h ( ) < str . lengt h ( ) }
?
compres s edSt ring
st r ;
This works. Is it efficient thoug h? Ta ke a look at the runtime of this code. The runtime is O ( p + k2 ) , where p is the size of the orig inal string and k is the number of cha racter sequences. For example, if the string is a a b c c d eeaa, then there a re six characte sequences. It's slow beca use string concatenation operates in O ( n 2 ) time (see S t r i n gB u i l d e r on pg 8 ). We can fix this by using a S t r i n gB u i ld e r.
Cra c ki n gTheCod i n gl nterv iew.c om 6th Edition
201
Solutions to Chapter 1 1
I Arrays and Strings
String compress ( String str) { St ringBuilder compressed = new St ringBuilde r ( ) ; int countConsecutive = 0 ; for ( i nt i 0 ; i < st r . length ( ) ; i++ ) { countConsecutive++;
2
3
4
=
5
6 7
/* If next chara cter is different t h a n current , a ppend this char to result . * / if ( i + 1 >= st r . lengt h ( ) I I st r . cha rAt ( i ) ! = st r . charAt ( i + 1 ) ) { compres sed . a ppend ( st r . charAt ( i ) ) ; compres sed . a ppend ( countConsecutive ) ; countConsecutive = 0 ; }
8 9
10 11 12 13
} ret u r n compressed . length ( )
< st r . length ( ) ? compres sed . toSt ring ( ) : s t r ; } Both of these sol utions c reate the com pressed string fi rst and then return the shorter of the i n p ut st ri ng and the compressed string.
14 15
I nstead, we can check in advance.This wil l be more opti mal i n cases where we don't have a large n umber of re peating characters. It wil l avoid us having to create a stri ng that we never use. The downside of this is that it causes a second loop through the characters and a lso adds nearly duplicated code. 1 String compres s ( String s t r ) { /* Check final length and return input st ring if it would be longer . */ 2 3 i nt finallength = countCompre s s ion ( st r ) ; if ( final length >= st r . length ( ) ) return str; 4 5
6
StringBuilder compressed = new StringBuilde r ( final Lengt h ) ; // initial capacity i nt countConsecutive = 0; for ( int i = 0; i < str . lengt h ( ) ; i++ ) { countConsecutive++ ;
7
8 9
10 11
/ * If next c h a r a cter is d ifferent than c u r rent, append this char to result . * / if (i + 1 >:o st r . length ( ) 1 1 str . charAt ( i ) 1 - st r . c h a rAt ( i + 1 ) ) { compressed . a ppend ( st r . charAt ( i ) ) ; compres sed . a ppend ( countConsecutive ) ; countConsecutive = 0 ; }
12
13 14 15 16 17 18 19 2�
} return compressed . tost ring ( ) ; }
2 1 int countCompre s s ion ( St ring s t r ) { 22 i nt comp res sedlength = 0 ; int countConsecutive = 0; 23 for ( i nt i = 0; i < s t r . length ( ) ; i++ ) { 24 countConsecutive++ ; 25 26
27
28 29 30 31
32
}
/* If next cha racter is diffe rent t h a n current, i n c rease the lengt h . * / if (i + 1 >= st r . lengt h ( ) I I st r . c h a rAt ( i ) ! = st r . charAt ( i + 1 ) ) { compressed length += 1 + String . va l ueOf ( countConsecutive ) . lengt h ( ) ; countConsecutive = 0; }
ret u r n compres sedlengt h ; 33 34 }
202
Cracking the Cod i n g Interview, 6th Edition
Solutions to Chapter 1
I Arrays and Strings
One other benefit o f this a pproach i s that
w e c a n initial ize StringBuilder t o its necessary capacity u p-front. Without this, StringBuilder will (behind the scenes) need to double its capacity every time it hits capacity. The capacity could be double what we u ltimately need . 1 .7
Rotate Matrix: Given a n image represented by an NxN matrix, where each pixel i n the i mage is 4 bytes, write a method to rotate the image by 90 deg rees. Can you do this in place? pg 9 1
SOLUTION
Because we're rotating the matrix by 90 degrees, the easiest way to do this is to implement the rotation in layers. We perform a circular rotation on each layer, moving the top edge to the rig ht edge, the right edge to the bottom edge, the bottom edge to the left edge, and the left edge to the top edge.
How do we perform this four-way edge swap? One option is to copy the top edge to an array, and then move the left to the top, the bottom to the left, and so on. This requires O ( N ) memory, which is actually u n necessa ry. A better way to do this is to implement the swap index by index. I n this case, we d o the followi ng: 1 2 3
4 5
6
=
fo r i 0 to n temp = t o p [ i ] ; t op [ i ] left [ i ] left [ i ] b o tt om [ i ] b ott om [ i ] right [ i ] right [ i ] = temp =
=
=
We perform such a swap on each layer, starti ng from the outermost layer and working our way inwards. (Alternatively, we could start from the in ner layer and work outwards.) The code for this algorithm is below. 1
2 3
4 5 6
7
8
boolean rotat e ( i nt [ ] [ ] matrix) { if ( mat rix . length == 0 I I matrix . length ! = matrix [ 0 ] . lengt h ) ret urn false; int n matrix le n gth ; f o r ( i nt layer 0; layer < n / 2 ; laye r++ ) { int first layer ; int las t = n - 1 laye r ; for ( int i first ; i < last ; i++ ) { int o ffset i firs t ; =
.
=
=
-
=
=
-
CrackingTheCodingl nterview.com I 6th Edition
203
Solutions to Chapter 1 I Arrays a n d Stri n g s 9
i nt top = matrix [ fi rst ] [ i ] ; I I save top
10 11
I I left - > top mat rix [ f i r st ] [ i ]
12 13 15 16
17
I I right - > bottom mat rix [ la st ] [ la s t - offset ]
18 19
I I top - > right mat rix [ i ] [ la s t ]
20 21
22 25
mat rix [ la s t - offs et ] [ f i r s t ] ;
I I bottom - > left mat r ix [ la s t - offset ] [ fi rst ]
14
23 24
=
mat r i x [ la st ] [ la s t - offset ] ;
mat r ix [ i ] [ la st ] ;
top; I I right
next , k, i ) ; i = i + 1; 6 if ( i == k) { 7 8 ret urn head ; 9 } 10 retu rn nd ; 11 } 2
==
=
12 13
14
node* nthToLast ( node* hea d , int k) { 0; int i return nthToLast ( head , k , i ) ;
16
}
15
=
Approach C: Create a Wrapper Class.
We descri bed earlier that the issue was that we couldn't simulta neously return a cou nter a nd an index. If we wra p the counter value with simple class (or even a single element array), we can mimic passing by reference. 1
class I ndex { public int value }
2
3 4 5
0 ,·
LinkedListNode kthTo La s t ( Li nked ListNode hea d , int k ) { Index idx = new Index( ) ; retu rn kthToLast ( head, k, idx ) ; }
6
7
8
9
10
LinkedListNode kthToLast ( LinkedLi stNode head , int k, I ndex idx ) { if ( head nul l ) { return nul l ; 12 13 } 14 LinkedListNode node kthToLast ( head . next , k, idx ) ; idx . value idx . value + 1 ; 15 16 if ( idx . va lue = = k ) { return hea d ; 17 18 } 19 return node; 20 } 11
==
=
Each of these recursive solutions ta kes 0 ( n ) space due to the recu rsive calls. There a re a number of other solutions that we haven't addressed. We could store the cou nter in a static va ri a ble. Or, we could create a class that stores both the node and the cou nter, and retu rn an insta nce of that class. Reg ardless of which solution we pick, we need a way to u pdate both the node and the counter in a way that a l l levels of the recursive stack wi ll see.
210
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 2
I Linked Lists
Solution #3: Iterative
A more optimal, but less straig htforwa rd, sol ution is to implement this iteratively. We can use two poi nters, pl and p2. We place them k nodes a pa rt in the linked l ist by putting p2 at the beginning and moving pl k nodes into the list. Then, when we move them at the same pace, pl will hit the end of the linked list after L ENGTH - k steps. At that point, p2 will be L E NGTH - k nodes into the list, or k nodes from the end. The code below im plements this algorithm. 1 2 3
4
LinkedListNode nthToLast ( Li nked ListNode hea d , int k ) { L inked ListNode p l hea d ; L inked li stNode p2 = hea d ;
5
/ * Move pl k nodes i nto the list . */ fo r ( int i = 0 ; i < k ; i++) { if ( pl == n u l l ) return n u l l ; / / Out of bounds pl = pl . next ;
6
7
8
9
}
10 11
/ * Move t hem at t he same pace . When p l hits t he end, p2 wi l l be at t he right * element . * / while ( p l ! = n u l l ) { pl . next ; pl p2 = p2 . next ;
12
13
14 15 16
}
17
re t u rn p2 ;
18 }
This algorithm ta kes O ( n ) time and 0 ( 1 ) space. 2.3
Delete Middle Node: Im plement an algorithm to delete a node in the middle (i.e., any node but the first and last node, not necessa rily the exact midd le) of a singly linked list. given only access to that node.
EXAMPLE In put: the node c from the linked list a - > b- > c - > d - > e - >f Resu lt: nothing is returned, but the new linked list looks like a - > b - > d - > e - >f pg 94
SOLUTION -·� ·
..
�. ·
�
... � . -
- -
- · - --··-· ·
·--·
___ _ _ _ ...... _
I n this problem, you a re not given access to the head of the linked list. You only have access to that node. The solution is simply to copy the data from the next node over to the cu rrent node, and then to delete the next node. The code below implements this a lgorithm. 1 2 3 4 5
6
7 8
9
boolean deleteNode ( Li nked ListNode n ) { if ( n == n u l l I I n . next == n u l l ) { return false ; / / F a i l u re } Link e d Li stNod e next n . next ; n . data = next . data ; n . next = next . next ; return t r u e ; =
}
CrackingTheCodinglnterview.com I 6th Edition
211
I Linked Lists
Solutions to Chapter 2
Note that this problem ca nnot be solved if the node to be deleted is the last node in the linked list. That's okay-your interviewer wa nts you to point that out, and to discuss how to handle this case. You could, for example, consider ma rking the node as dummy. 2.4
Partition: Write code to partition a li nked list around a value x, such that a l l nodes less than x come before all nodes greater than or equal to x. If x is contained within the list, the values of x only need to be after the elements less than x (see below). The partition element x can appear anywhere in the "right partition"; it does not need to appear between the left a nd right partitions.
EXAMPLE -
> 5 -> 8 -> 5
In put:
3
Output:
3 -> 1 ->
2
-
> 10 - > 2 - > 1 [partition = 5]
- > 10 - > 5 - > 5 - > 8 pg 94
SOLUTION
- ··- .. - .. . · · ·- · · ·· ·· ·· ··- · · - .. . . _ ... , -··
.. .. . .. . . . . . . . . .. . . .. . . ... . .. . ... . . . .. .. ... . ...... ... ....._ ., _ ,,,. ,,_ , . ...... .
If this were an array, we would need to be ca reful about how we sh ifted elements. Array sh ifts a re very expensive. However, in a linked list, the situation is much easier. Rather than sh ifting and swa pping elements, we ca n actually create two different linked lists: one for eleme nts less than x, and one for elements greater than or equal to x. We iterate through the linked list, inserting elements into our be fore list or our aft e r list. Once we reach the end of the lin ked list and have com pleted this splitting, we merge the two lists. This approach is mostly "sta ble" in that elements stay in their original order, other than the necessa ry move ment a round the partition. The code below implements this approach.
1
/ * Pa s s in the head of the linked list and the value t o part ition a round * / Linked L istNode partition ( LinkedListNode node, int x ) { LinkedListNode beforeSta rt = n u l l ; Linked L istNode beforeEnd null; Linked ListNode afterSta rt = n u l l ; LinkedListNode a fterEnd nul l ;
2
3 4
=
5
6
=
7
8
9
10 11
12 13
14
1 ::;"
16
17
1i
19
20 21 n
23 24
25 26
212
/ * Pa rtition list */ while ( node ! = nul l ) { L i n ke d L istNode next = node . next ; node . next = n u l l ; i f ( node . data < x ) { I * I ns e rt node into e n d of before l i st * / if ( beforeSta rt == nul l ) { beforeSt a rt nod e ; beforeEnd = befo reSt a rt ; } else { beforeEnd . next = node; beforeE nd = node; } } el s e { I * I nsert node into end of after l i s t */ if ( afterSt a rt null ) { afterSt a rt node; aft e r E nd = aft e rSt a rt ; } else { =
==
=
Cracking the Cod ing I nte rview, 6th Edition
Solutions to Chapter 2
I Li nked Lists
a ft e r E nd . ne x t = nod e ; a ft e r E n d nod e ;
27 28 29 30
=
}
} node
31
32
}
33
34
a
n e xt ; ==
if ( beforeSt a r t nul l ) { return afterStar t ; }
35
36 37
38
39
/* Merge before list and after l i s t * I beforeEnd . next afterSt a rt ; ret urn beforeStart ; =
40 41 }
If it bugs you to keep around four different variables for tracking two li nked lists, you're not a lone. We can make this code a bit shorter. If we don't care a bout ma king the elements of the list "sta ble" (which there's no obligation to, since the interviewer hasn't specified that), then we can instead rea rra nge the elements by growing the list at the head and tail. I n this approach, we start a "new" list (using the existing nodes). Elements bigger than the pivot element a re put at the tai l a nd elements sma ller are put at the head. Each time we insert a n eleme nt, we update either the head or tail. 1
2
3
4
L i nked ListNode partitio n ( Linked ListNode node, int x ) { nod e ; L i n ked ListNode head L i n ked ListNode tail nod e ; =
5
while ( node ! = null ) { L inked L istNode next node . next ; if ( node . data < x ) { / * I n sert node at head . */ node . next = head ; head nod e ; } e l se { /* I nsert node at tail . */ t a i l . next nod e ; tail = node; } node = next ; } tail . next null ;
6
=
7 8
9
10
=
11
12 13
=
14 15 16
17
18
=
19
20 21
22
}
/ / The head has cha nged , so we need to return it to t he u se r . return head ;
There a re m a ny equally optim a l solutions to this problem. If you came up w it h a d ifferent one, that's okay!
CrackingTheCodingl nterview.com I 6th Edition
213
I Linked Lists
Solutions to Cha pter 2
Sum Lists: You have two num bers represented by a linked list, where each node conta ins a single dig it. The dig its are stored in reverse order, such that the 1 's digit is at the head of the list. Write a
2.5
fu nction that adds the two num bers and retu rns the sum as a linked list.
EXAMPLE
Input: ( 7 - > 1 - > 6 )
+
( 5 - > 9 - > 2 ) . That is, 617
+
295.
Output: 2 - > 1 - > 9. That is, 912 . FOLLOW U P Su ppose the d igits are stored in forwa rd order. Repeat the above problem. Input: (6 - > 1 - > 7)
+
( 2 - > 9 - > 5 ) . That is, 617 + 295.
Output: 9 -> 1 - > 2. That is, 9 1 2 . pg 95
SOLUTION ·· ··· ··· ··· ·· ·
·· ··· · ·· ··· ··· ··· · ··· · ··· ··· ··· ······ -··- ··-··-· ··· ··- ·· -· ·- ··-··-·· - ·-
It's usefu l to remember in this problem how exactly addition works. Imagine the p roblem: 6 1 7 +
2
9
5
First, we add 7 and 5 to get 1 2. The digit 2 becomes the last digit of the num ber, and 1 gets carried over to the next step. Second, we add 1 , 1 , and 9 to get 1 1 . The 1 becomes the second digit, and the other 1 gets ca rried over the final step. Th ird and finally, we add 1 , 6 and 2 to get 9. So, our va lue becomes 9 1 2 . We can mimic this process recursively by adding node by node, ca rrying over any "excess" data to the next node. Let's wa lk th rough this for the below linked list: 7 -> 1 -> 6 + 5 -> 9 -> 2 We do the fol lowing: 1 . We add 7 and 5 first, getting a result of 1 2. 2 becomes the fi rst node in our linked list, and we "carry" the
to the next sum.
1
List : 2 - >
?
We then add 1 and 9, as well as the "ca rry;' getting a result of 1 1 . linked list, and we ca rry the 1 to the next sum.
2.
L i st : 2 - > 1 - > 3. Finally, we add
6, 2
1
becomes the second element of our
?
and our"ca rry;' to get 9. This becomes the fi na l element of our linked list.
L i s t : 2 - > 1 - > 9.
The code below implements this algorithm. 1
L i nked L i s tNode a d d l i s t s ( L i n ked L i stNode 1 1 ,
if ( 11
2
3
4
}
5
i
==
nu l l && 1 2 ret urn n u ll ;
L i nked l i stNode res u lt
7
8 9
10
11
214
==
null && carry
L i n ked L i stNode 1 2 ,
0) {
new L i n ke d l i stNode ( ) ;
i nt va l ue = ca rry; if ( 11 ! = nul l ) { va lue += 11 . d a t a j } i f ( 1 2 ! = null ) {
Cracking
==
the Cod i n g Interview, 6th Edition
int
ca r ry ) {
Solutions to Chapter 2 12
va lue += 12 . dat a ;
13
}
15
result . data
14
16
17
va lue % 10; / * Second d igit of number */
/ * Recurse * / if ( 1 1 ! = null 1 1 12 ! = nul l ) { Linked listNode more addlist s ( l l
18 19
=
20 21
result . setNext (more ) ;
22
23
null ? null : 11 . next , == null ? null : 12 . next , va lue > 10 ? 1 : 0) ; 12
==
=
}
24
25
I Linked Lists
return result ; }
I n implementing this code, we must be careful to handle the condition when one linked list i s shorter than another. We don't want to get a null pointer exception.
Follow Up
Part B is conceptually the same (recurse, ca rry the excess), but has some additional complications when it comes to implementation: 1. One list may be shorter than the other, and we can not handle this "on the flY:' For example, suppose we were adding (1 -> 2 -> 3 -> 4) and (5 -> 6 -> 7). We need to know that the 5 should be "matched" with the 2, not the 1 . We can accomplish this by comparing the lengths of the lists in the beginning and padding
the shorter list with zeros. 2. In the first part, successive results were added to the ta il (i.e., passed forward). This meant that the recur
sive call would be passed the carry, and would return the result (wh ich is then appended to the taill. I n this case, however, results are added t o the head (i.e., passed backwa rd). The recursive c a l l m ust return the result, as before, as well as the carry. This is not terribly challenging to implement, but it is more cum bersome. We can solve this issue by creati ng a wrapper class called Partial Sum. The code below implements this a lgorithm. 1 cla s s Pa rt ialSum { 2 public Linked listNode s um = null; 3 public int ca r ry = 0; 4 5
6
7
8 9
10
11 12
13
14 15 16
17
18
19
20
21
22
}
LinkedListNode addLists ( LinkedListNode int lenl lengt h ( ll ) ; int len2 = lengt h ( l2 ) ;
11,
L inked ListNode 1 2 ) {
/ * Pad the shorter list with zeros - see note ( 1 ) */ if ( lenl < len2) { 11 = pa d l ist ( ll , len2 - lenl ) ; } else { 12 = padList ( l2 , lenl - len2 ) ; }
/ * Add lists * / Part ialSum sum = addlistsHelpe r ( ll, 12 ) ; /* I f there was a carry va lue left over, ins ert this at the front of the list . * Otherwise, j ust return the linked list . * / if ( s um . carry == 0 ) {
Cracki ngTheCodinglnterview.com j 6th Edition
21S
Solutions to Chapter 2 23
I Linked Lists
return s um . s u m ; } else { LinkedlistNode result return result ;
24
25 26
27
i n s e rt Before ( s um . sum, sum . carry ) ;
}
28 } 29 30 PartialSum add ListsHelpe r ( LinkedListNode 1 1 , Linked ListNode 12) { 31 if ( 1 1 == null && 1 2 == nul l ) { 32 P a rtia lSum s um = new PartialSum ( ) ; 33 return sum; 34 } / * Add sma l l e r digits recurs ively * / 35 36 PartialSum sum = add ListsHelpe r ( l l . next , 12 . next ) ; 37
/ * Add ca rry to current data * / int val s um . ca rry + 1 1 . data + 12 . data ;
38
39
=
40
/ * I n s e rt sum of c u rrent digits * /
41
42
LinkedListNode full_result = insert Before ( s um . sum, val
43
44
s um . sum = full_res ult ; sum . ca rry val / 10; return sum;
46
48 49
10) ;
/ * Return s um so far, and the ca rry value * /
45
47
%
}
/ * Pad the list with zeros * / Linked ListNode pad List ( LinkedListNode 1, int padding) { 52 Linked L istNode head = l ; 53 for ( int i = 0 ; i < padd ing; i++ ) { 54 head insert Before ( hea d , 0 ) ; 55 } return head; 56 57 } 50 51
=
58 59
60 61
62
63
64
65
66
/ * Helper funct i o n to insert node i n t he front of a l i n ked l i s t * / Linked listNode insert Before ( Li n kedlistNode list, int data ) { LinkedlistNode node = new Linked l i stNode (data ) ; if ( list ! = n ull ) { node . next = list ; } return node; }
N ote how we have p ulled i n s e rt B e f or e ( ) , pad L i st ( ) , a n d l e n gt h ( ) (not listed) i nto their own methods. This makes the code clea n e r and easier to read-a wise thi ng to d o in your i nterviews! 2.6
Palindrome: Implement a fu n ctio n to check if a linked list is a pa lindrome. pg 95
SOLUTION To a pproach
this problem, we ca n picture a pa lindrome like 0 > 1 - > 2 > 1 > 0. We know that, si nce it's a palindrome, the list must be the same backwards a n d forwa rds. This leads us to our fi rst solution .
21 6
Cracking the Codi n g Interview, 6th Edition
-
-
-
Solution s to Chapter 2
I
Li n ked Lists
Solution #1 : Reverse and Compare
Our first solution is to reverse the linked list a nd com pare the reversed list to the original list. If they're the same, the lists are identica l. Note that when we com pare the l in ked list to the reversed list, we only actually need to com pare the fi rst half of the list. If the first half of the normal list matches the first half of the reversed list, then the second half of the normal list must match the second half of the reversed list. 1
2
3
boolean isPalindrome ( Li nked L istNode head ) { L i n kedlistNode reve rsed reverseAndClone ( head ) ; return i s E q ua l ( head , reve rsed ) ;
4
}
5
=
6
Linked ListNode reve rseAndClone ( Li nked L istNode node ) { L inkedl istNode head = n u l l ; wh ile ( node ! = nul l ) { 8 9 L inked l istNode n = new Linked listNode ( node . data ) ; / / C lone n . next = hea d ; 10 n; head 11 12 node = node . next ; 13 } 14 return hea d ; 15 } 7
16
17
boolean i s E q ua l ( L i n ked ListNode one, L i nked L i stNode two ) { whi le (one ! = null & & two ! = n ul l ) { 19 if ( o n e . data ! = two . data ) { 20 ret urn fa l s e ; 21 } 22 one one . next ; 23 two two . next ; 18
24
25
26 }
}
return one
==
null
&&
two
==
null;
Observe that we've modula rized this code i nto reve r s e a nd i s E q u a 1 fu nctions. We've also created a new class so that we ca n return both the head a nd the tail of this method. We could have also retu rned a two element array, but that approach is less maintai nable. Solution #2: Iterative Approach
We wa nt to detect linked lists where the front half of the list is the reverse of the second half. How would we do that? By reversing the front half of the list. A stack can accom plish this. We need to push the first half of the elements onto a stack. We can do this in two different ways, dependi ng on whether or not we know the size of the linked list. If we know the size of the linked list, we can iterate through the fi rst half of the elements in a sta ndard for loop, pushing each element onto a stack. We must be careful, of course, to handle the case where the length of the li nked list is odd. If we don't know the size of the linked list we can iterate through the linked list using the fast runner I slow runner technique described in the beginning of the cha pter. At each step in the loop, we push the data from the slow runner onto a stack. When the fast runner hits the end of the list, the slow runner will have reached the middle of the linked list. By this point, the stack wil l have all the elements from the front of the linked list but in reverse order.
Cracki ngTh eCod i ngl n terv iew.com I 6th Edition
217
Sol utions to Chapter 2 I Lin ked Li sts Now, we simply iterate throug h the rest of the linked list. At each iteration, we com pare the node to the top of the stack. If we com plete the iteration without finding a difference, then the linked l ist is a palindrome. 1
boolean isPa l i ndrome ( L inked ListNode head ) { Linked L i stNode fast hea d ; Linked ListNode slow = hea d ;
2
=
3
4 5
Sta c k < I nteger> stack = new Stack< I ntege r> ( ) ;
6
7
I*
Push elements from first half of l i n ked list onto stack . When fa s t runner (which is moving at 2x s peed ) reaches t he end of the linked list, then we * know we ' re at the middle * I while (fast ! = null && fast . next ! = nu l l ) { stack . pu s h ( slow . data ) ; slow . next ; slow f a s t = fast . next . next ; }
8
*
9
10
11
12 13
14
15 16
I*
H a s odd number of elements , s o s k i p t h e middle element if (fast ! = null ) { slow = slow . next ; }
17
18
19
20 21 22
while ( s low ! = null) { int top = stack . pop ( ) . i ntVa lue ( ) ;
23 24
I* If values a re different , then it ' s not a palind rome i f ( top ! = slow . data ) { ret urn false; } slow = slow . next ;
25
26 27
28
29 30 31
*I
}
*I
} return true;
Solution #3: Recursive Approach
First, a word on notation: in this solution, when we use the notation node Kx, the variable K indicates the va lue of the node data, and x (which is either f or b) indicates whether we a re referring to the front node with that va lue or the back node. For exam ple, i n the below linked list, node 2b would refer to the second (back) node with value 2 . Now, like many linked list problems, y o u c a n approach this problem recursively. W e may have some intui tive idea that we want to com pare element 0 and element n - 1, element 1 and element n 2, element 2 and element n - 3, and so on, until the m iddle element(s). For exam ple: -
0 ( 1 ( 2 (
3
) 2 ) 1 ) 0
In order to apply this approach, we first need to know when we've reached the middle element, as this will form our base case. We can do this by passing in lengt h - 2 for the length each time. When the length equals 0 o r 1, we're at the center of the l i n ked list. This is beca use the length is reduced by 2 each time. Once we've recursed Yi times, length will be down to 0. 1
2
3
4 5
recurs e ( Node n , int lengt h ) { if ( lengt h 0 I I lengt h == 1 ) { ret urn [ someth i ng ] ; I I At middle } recurse ( n . next , length 2); ==
-
218
Cracking t h e Coding Interview, 6th Edition
Solutions to Chapter 2
I Linked Lists
6
7
}
Th is method wil l form the outline of the is P a l i n d rome method. The "meat" of the a lgorithm though is comparing node i to node n - i to check if the l i n ked list is a palind rome. How do we do that? Let's examine what the cal l stack looks like: 1
2 3
4
5
6 7
8
vl
=
=
is Palindrome : list 0 ( 1 ( 2 ( 3 = isPalindrome : list = 1 ( 2 ( 3 ) v3 = isPalindrome : list = 2 ( 3 ) 2 v4 = is Palindrome : list = 3 ) 2 ) ret urns v3 returns v2 returns vl returns ?
v2
) 2 2 ) ) 1 1 )
7
=
) 1 ) 0. l e ngth 1 ) 0 . l e ngt h = 5 ) 0 . l e ngt h = 3 0 . l e ngt h = 1
In the above ca l l stack, each ca l l wants to check if the list is a pa lind rome by comparing its head node with the correspond i ng node from the back of the list. That is: •
•
Line 1 need s to compare node 0f with node 0b Line 2 need s to compare node 1 f with node l b Line 3 need s t o compare n o d e 2f with n o d e 2b
•
Line 4 need s to compare node 3f with node 3b.
If we rewind the stack, passing nodes back as d escribed below, we ca n do just that: •
Line 4 sees that it is the mid d l e node (since l engt h equals n ode 3, so head . next is node 2b.
=
1), and passes back head . next. The value head
Line 3 compares its head, n o d e 2f, to retu r ned_n o d e (the va lue from the previous recursive call), which is node 2b. If the va lues m atch, it passes a referen ce to node lb (ret u r ned_nod e . next) u p t o line 2. Line 2 compares its head (nod e 1 f) to retu r ned_nod e (nod e l b) . If the values match, it passes a reference to node 0b (or, ret u rned_nod e . next) u p to line 1 . Line 1 compares its head, node 0f, to ret u r ned_n ode, which i s node 0b. If the values match, it retu rns true. To genera lize,
each cal l compares its head to ret u r ned_nod e, and then passes ret u r ned_nod e . next up the stack. I n this way, every node i gets compared to node n - i. If at any point the values do not match, we return f a l s e, and every cal l up the stack checks for that value. But wait, you m ight ask, sometimes we said we'll return a bool e a n value, and sometimes we're returning a node. Which is it? It's both. We create a simple class with two mem bers, a bool e a n and a node, a n d retu rn an insta nce of that class. 1 2
3
4
cla s s Res ult { public L i n ked ListNode node ; public boolean res u l t ; }
The exam ple below illustrates the parameters and return values from this sample list. 1
2
3
is Palindrome : list = 0 ( 1 ( 2 ( 3 ( 4 ) 3 ) 2 ) 1 ) 0 . l e n i s Pa lindrooe : list = 1 ( 2 ( 3 ( 4 ) 3 ) 2 ) 1 ) 0 . l e n = i s Pa l i nd rome : list = 2 ( 3 ( 4 ) 3 ) 2 ) 1 ) 0 . l e n = 5
=
7
9
CrackingTheCodinglnterview.com I 6th Edition
219
Sol utions to Chapter 2
I Linked Lists
is Palind rome : list 3 ( 4 ) 3 ) 2 ) 1 ) 0 . len = i s Palind rome : list 4 ) 3 ) 2 ) 1 ) 0 . len = 1 returns node 3 b , true 6 returns node 2b, true 7 returns node l b , true 8 9 returns node 0b, t rue 10 returns n u l l , true
4
=
5
3
=
Implementing t h i s code is now just a matter of filling in the details.
1
boolean isPalind rome ( L i nked L istNode head ) { int length = lengthOfList ( head ) ; Res ult p = isPalind romeRecurse ( head , lengt h ) ; ret urn p . result ; }
2
3
4 5 6 7
Result i s Palind romeRecurse ( L i nked ListNode head , int lengt h ) { if ( head == null I I length < = 0 ) { II Even number of nodes return new Result ( head , true) ; 9 10 } e l se if ( lengt h == 1) { II Odd number of nodes 11 return new Result ( head . next , true) ; } 12 13 I* Recurse on s u b l i st . *I 14 15 Result res = i s Palind romeRec u r se ( head . next , length 2); 16 17 I* If child calls are not a palind rome , pass back u p * a failure . * I 18 19 i f ( ! res . result I I res . node == n u l l ) { 20 return res ; } 21 22 I* Check if matches corresponding node on other s ide . *I 23 24 res . result = ( head . d ata == res . node . d ata ) ; 8
-
25
26 27
I * Return corres ponding node . *I res . node = res . node . next ;
28 29
30
return re s ; }
31 3 2 int lengthOflist ( Li n kedlistNode n ) { 33 i n t size = 0 ; 34 while ( n ! = n u l l ) { 35 s ize++ ; n = n . next ; 36 37 } 38 return s i z e ; 39 }
Some of you might be wonderi ng why we went through a l l this effort to create a special Res u lt class. Isn't there a better way? Not rea lly-at least not i n Java. However, if we were implementing this in
C
or C++, we could have passed in a do uble pointer.
1
bool isPalind romeRecurse ( Node hea d , int lengt h , Node** next ) {
3
}
2
It's ugly, but it works. 220
Cracking the Coding I nterview, 6th Edition
Solutions to Chapter 2 2.7
I Linked Lists
Intersection: Given two (sing ly) linked lists, determine if the two lists intersect. Return the intersecting node. Note that the intersection is defined based on reference, not value. That is, if the kth node of the first linked list is the exact same node {by reference) as the j th node of the second linked list, then they are intersecting. pg 95
SOLUTION
Let's draw a picture of intersecting linked lists to get a better feel for what is going on. Here is a picture of intersecting linked lists:
�
7
And here is a pictu re of non-intersecting linked lists:
We should be carefu l here to not inadvertently d raw a special case by making the linked lists the same length. Let's first ask how we wou ld determine if two linked lists intersect. Determining if there's an intersection.
How would we detect if two linked lists intersect? One approach would be to use a hash ta ble and just throw all the linked lists nodes into there. We wou ld need to be ca refu l to reference the linked lists by their memory location, not by their val ue. There's an easier way though. Observe that two intersecting linked lists will a lways have the same last node. Therefore, we can just traverse to the end of each linked list and compare the last nodes. How do we find where the i ntersection is, though ? Finding the intersecting node.
One thought is that we could traverse backwards through each linked list. When the linked lists "split'; that's the intersection. Of course, you can't rea lly traverse backwards through a singly linked list. If the linked lists were the same length, you could just traverse through them at the same time. When they coll ide, that's you r intersection.
CrackingTheCodinglnterview.com j 6th Edition
221
Solutions to Cha pter 2
I
L i n ked L i sts
�
When they're not the same length, we'd like to just "chop off"-or ignore-those excess (gray) nodes. How can we do this? Well, if we know the lengths of the two linked lists, then the difference between those two l i n ked lists will tell us how much to chop off. We can get the lengths at the same time as we get the tails of the linked lists (which we used i n the fi rst step to determine if there's an i ntersection). Putting it all together.
We now have a mu ltistep process. 1 . Run through each linked list to get the lengths and the tai ls. 2.
Compare the tails. If they a re d ifferent (by reference, not by value), return im med iate ly. There is no inter section.
3 . Set two pointers to the sta rt of each linked list
4. On the longer linked list, advance its pointer by the d ifference i n lengths. 5.
Now, trave rse on each l i n ked list until the pointers a re the same.
The im plementation for this is below. 1
L i nked listNode findintersection ( Li nked listNode listl, Linked l istNode list2) { if ( l i s t l null I I list2 == n u l l ) ret u r n n u l l ; ==
2
3 4
/ * Get tail and s i zes . */ Result resultl getTa i lAndSize ( l istl ) ; Result result2 getT a i lAndSize ( list2 ) ;
5 6
=
7 8
/ * If d iffe rent tail nodes, then there ' s no i ntersection . * / if ( resultl . ta i l ! = result2 . ta i l ) { ret urn null; }
9
10 11 12 13
/* Set pointers to the start of each l inked list . */ Linked ListNode shorter = resultl . s ize < result2 . s ize ? listl : list 2 ; Li nked ListNode longer result l . size < result 2 . size ? list2 : listl;
14 15
=
16
17
/ * Adva nce the pointer for the longer l i nked list by d iffe rence i n lengths . */ longer get KthNode ( longe r , Mat h . a bs ( resultl . s ize - result2 . s ize ) ) ;
18
=
19
20
/ * Move both pointers until you have a coll ision . * / while ( s horter ! = longe r ) { shorter shorter . next ; longer longe r . next ; }
21
22 23
=
=
24 25
26 27
2�
29
/ * Ret u r n either one . * / ret u r n longe r ; }
222
I
Cracking the Cod i ng Interview, 6th Edition
Solutions to Chapter 2
I Li nked Lists
30
class Res ult { public L i n kedL istNode t a i l ; p u b l i c i nt size; 32 p u b l i c Res ult ( L inked L i s t Node t a i l , i nt size ) { 33 this . ta i l tail; 34 this . size = size; 35 36 } 37 } 31
=
38 3'
40
Result ge tT a i lA nd S i z e ( L inke d L i s tNo d e
if ( list
41
42
43 44 45 46
47 48 49
50 51
52 53
54 55 58
null ) ret urn null;
i nt size = 1 ; L i nked ListNode c u r rent = list ; while ( c urrent . next ! = null ) { s i ze++ ; current cu rrent . next ; } ret urn new Res ult ( current , size ) ;
}
L inkedListNode getKt hNode ( L i nked l istNode head , int k ) { LinkedListNode cu rrent = head ; wh ile ( k > 0 && c u r rent ! = null ) { c u r rent c u r rent . next ; k- - ;
=
} ret urn current ; }
Th is algorithm takes O ( A add itional space. 2.8
list ) {
=
56 57
==
+
B ) time, where A and B a re the lengths of the two linked lists. It ta kes 0 ( 1 )
Loop Detection: Given a circu lar linked list, implement a n algorithm that returns the node at the beginning of the loop.
DEFIN ITION Circular linked list: A (corru pt) linked list in which a node's next pointer points to an earlier node, so as to make a loop in the l i n ked list. EXAMPLE In put: Output:
A - > B - > C - > D - > E - > C [the same C as earlier] c
pg 95 SOLUTION
This is a mod ification of a classic interview problem: detect if a linked list has a loop. Let's apply the Pattern Matching approach. Part 1 : Detect If Linked Ust Has A Loop
An easy way to detect if a linked list has a loop is through the F a s t R u n n e r I SlowR u n n e r approach. F a s t R u n n e r moves two steps at a time, while S l owRunner moves one step. Much like two cars racing a round a track at d ifferent steps, they must eventually meet.
Cra cki n gTheCod i n gl nte rview.co m I 6t h Edition
223
Solutions to Chapter 2
I Lin ked Lists
An astute reader may wonder if F a s t R u n n e r might "hop over" S l owRu n n e r completely, without ever colliding. That's not possible. Suppose that F a s t R u n n e r did hop over S l owRu n n er, such that S l owR u n n e r is at spot i and F a s t R u n n e r is at spot i + 1. In the previous step, S l owR u n n e r wou ld be at spot i - l and F a s t R u n n e r wou ld at spot ( ( i + 1 ) - 2 ) , or spot i - 1 . That is, they wou ld have col lided. Part 2: When Do They Collide?
Let's assume that the linked list has a "non-looped " part of size k. l f we apply our a lgorithm from part
1,
when will F a s t R u n n e r and S lowR u n n e r co llide?
We know that for every p steps that S l owR u n n e r ta kes, F a s t R u n n e r has taken 2 p steps. Therefore, when S l owRu n n e r enters the looped portion after k steps, F a st R u n n e r has ta ken 2 k steps total and must be 2k - k steps, or k steps, into the looped portion. Since k might be much larger than the loop length, we should actually write this as mod ( k , LOOP _S I Z E ) steps, which we will denote as K. At each subsequent step, F a st R u n n e r and S l owRu n ne r get either one step farther away or one step closer, depending on you r perspective. That is, because we are in a circle, when A moves q steps away from B, it is also moving q steps closer to B. So now we kn ow the fo llowing facts: 1 . S l owRunner is 0 steps i nto the loop. 2.
F a st Ru n n e r is K steps into the loop.
3. S l owR u n n e r is K steps behind F a st R u n ner. 4. F a s t R u n n e r is LOOP_SI Z E
- K steps behind S l owRu n n e r.
5. F a s t R u n n e r catches up to S l owRu n n e r at a rate of 1 step per u n it of time.
So, when do they meet? Well, if F a s t R u n n e r is LOOP _S I Z E K steps behind S lowR u n n e r, and Fast Runner catches u p at a rate of 1 step per unit of time, then they meet after LOOP _S I Z E - K steps. At this point, they wil l be K steps before the head of the loop. Let's ca l l this point Col l i s i o n S po t .
n l a n d n 2 w i l l meet here, th ree nodes from start of loop
Part 3: How Do You Find The Start of the Loop? =
We now kn ow that Col l i s i o n S pot is K n odes before the start of the loop. Because K mod ( k , LOOP_ S I Z E ) (or, in other words, k = K + M * LOOP_S I Z E, for any i n teger M), it is a lso correct to say that it is k nodes from the loop start. For examp le, if node N is 2 n odes into a 5 n ode loop, it is a lso correct to say that it is 7, 1 2, or even 397 nodes into the loop. Therefore, both Col l i si o n S pot and L i n ked l i st H e a d are k nodes from the start of the loop. 224
Cracki ng t h e Coding I nterview, 6th Edition
Solutions to Chapter 2
I
Li n ked Li sts
Now, if we keep one pointer at Co l li s i on S po t and move the other one to L i n ked li stHead, they will each be k nodes from LoopSt a rt. Moving the two pointers at the same speed will cause them to col l ide aga in-this time after k steps, at which point they will both be at LoopSta rt . All we have to do is return this node. Part 4: Putting It All Together To
summarize, we move F a stPointer twice as fast as S l owPointer. When S lowPointer enters the loop, after k nodes, Fa stPointer is k nodes into the loop. This means that F a stPointer and SlowPointer are LOOP_S IZE k nodes away from each other. -
Next, if Fa stPointer moves two nodes for each node that SlowPointer moves, they move one node closer to each other on each turn. Therefore, they will meet after LOOP S I ZE - k tu rns. Both will be k nodes from the front of the loop. _
The head of the linked list is also k nodes from the front of the loop. So, if we keep one pointer where it is, and move the other poi nter to the head of the l i n ked list, then they will meet at the front of the loop. Our a lgorithm is derived directly from parts 1 , 2 and 3. 1 . Create two pointers, F a stPointer and S l owPointer.
2.
Move Fa stPointer at a rate of 2 steps and S l owPointer at a rate of 1 step.
3. When they coll ide, move S lowPointer to L i n ked L i stHead. Keep Fa stPointer where it is.
4. Move S l owPointer and FastPointer at a rate of one step. Return the new collision poi nt. The code below im plements this algorithm. 1
2
3
4
LinkedListNode FindBegin ning ( Li nked ListNode hea d ) { LinkedListNode s low hea d ; LinkedListNode fast = hea d ; =
5
I * Find meet ing point . This will be LOOP_SIZE - k steps into the l i n ked list . * I while (fast ! = n u l l && fast . next ! = n u l l ) { s low = slow . next ; fast = fast . next . next ; if ( s low == fa s t ) { I I Coll i s ion brea k ; } }
6 7 8 9
10 11
12
13 14
I * E rror check - no meet ing poi nt, and therefore no loop * I if (fast == n u l l I I fast . next == n u l l ) { return n u l l ; }
15
16 17 18
19
I * Move s low to Head . Keep fast at Meet ing Point . Each a re k steps from the * Loop Sta rt . If t hey move at the same pace , they must meet at Loop Sta rt . * I s low hea d ; while ( s low ! = fa s t ) { s low slow . next ; fast = fast . next ; }
20
21
=
22 23
=
24 25 26
27 28
29
I * Bot h now point to the start of t he loop . */ return fa st ; }
CrackingTheCodingl nterview.com I 6th Edition
22S
Solutions to Chapter 2
226
I Linked Lists
Cracki ng the Coding Interview, 6th Edition
3 Solutions to Stacks and Queues
3.1
Three in One: Describe how you could use a s i ngle a rray to im plement three sta cks. pg 98
SOLUTION
Li ke many problems, this one somewhat depends on how well we'd l i ke to support these stacks. If we're okay with simply allocating a fixed amount of space for each stack, we can do that. This may mean though that one sta ck run s out of space, while the others are nea rly empty. Alternatively, we can be flexible in our space allocation, but this sign ifica ntly increa ses the complexity of the problem. Approach
1:
Fixed Division
We can d ivide the array in three equal parts and a llow the ind ividual sta c k to grow in that lim ited space. Note: We will use the notation [ to mean inclusive of an end point and "(" to mean exclusive of an end point. "
"
For stack 1 , we will use [ 0, X ) . For stack 2, we will use [ X
,
1
X).
For stack 3, we will use [ 1X , n ) . The code for t h i s solution is below. 1 c l a s s FixedMult iStack { 2 priva te int numberOfSt acks private int s t a c kCapacity ; 3 private int [ ] values ; 4 5 private int [ ] s izes ;
6
7
� 9
10 11
12 13
14
15 16 17
3;
public FixedMultiStac k ( int s t a c kSi z e ) { s t a c kCapacity s t a c kSiz e ; values = new int [ st a ckSize * numberOfSta cks ] ; s izes = new int [ numberOfStacks ] ; =
}
/* Pus h value onto stack . * / p u blic void p u s h ( int stackNum, int value) throws F ullStackE xception { / * Check tha t we have space fo r the next element */ i f ( i s Full ( sta ckNum) ) { throw new FullStackException ( ) ;
CrackingTheCodinglnterview.com I 6th Edition
227
Sol utions to Chapter 3 I 18
}
19
20
I* I ncrement stack pointer and then update top value . * / s i zes [ stackNum]++; va l ue s [ indexOfTo p ( stackNum ) ] = va lue;
21
22
23 24
}
25
I * Pop item f rom top stack . *I public int pop ( int stackNum) { if ( is Empt y ( s t a c kN um ) ) { t h row n ew EmptySt ac kE xcept ion ( ) ; }
26
27 28
29
30
31
=
int topi ndex i ndexOfTo p ( s t a c kNum ) ; int value = values [ topi ndex ] ; I I Get top values [ topindex] 0; I I C l e a r s i zes [ stackNum] - - ; I I Shri n k return value;
32
33 34
=
35 36
}
37
38
I* Return top element . * I public i nt pee k ( i nt stackNum) { if ( i s Empt y ( stackNum ) ) { t h row new EmptySt a c k E xception ( ) ; } ret urn values [ i ndexOflo p ( stackNum ) ] ; }
39
40 41
42 43
44 45 46
I * Return if stack is empty . *I publ i c boolean is Empty ( i nt stackNum) { ret u rn s izes [ sta ckNum ] 0; }
47
48 49
==
50
51 52 53
I * Return if stack is full . *I public boolean i s F u l l ( int stackNum) { ret u rn s izes [ sta ckNum ] stackCapacity; } ==
54 55
56
I * Ret urns i ndex of the top of the sta c k . * I p rivate i nt indexOflo p ( int s t a c kNum) { i nt offset stackNum * stackCapacity; int size sizes [ stackNum ] ; retu r n offset + size 1;
57
58 59
=
=
60 61 62
Stacks and Queues
-
}
}
I f we had addition a l i nformation about the expected usages o f the stacks, then we could modify this algo rithm a ccord i n g ly. For example, if we expected Stack 1 to have many more elements than Stack 2, we cou ld a l l ocate more space to Stack 1 and less s pa ce to Stack 2. Approach 2: Flexible Divisions
A secon d a pproach is to allow the stack b l ocks to be flexible i n size. When o n e stack exceeds its initial capacity, we grow the a l lowa ble capa city and shift elements as necessary. We will also design o u r array to be ci rcu lar, such that the fi n a l stack may start at the end of the array and wra p a ro u n d to the begi n n i ng.
228
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 3
I
Stacks a n d Queues
Please note that the code for this solution is far more complex than would be appropriate for a n i nterview. You could be res ponsible for pseu docode, or perhaps the code of individual compone nts, but the entire implementation wou l d be far too much work. 1
2 3
4 5
6
7
8
9
10 11 12
public c l a s s MultiSt a c k { / * St ackinfo is a s imple c l a s s that holds a set of data a bout each stack . It * does not hold t he actual items in the stack . We could have done this wit h * j ust a bunch of individual varia bles , but that ' s messy a nd doe s n ' t ga in us * much . * / private c l a s s Stacki nfo { public int s t a rt , s i z e , capacity; public St ackinfo ( int start , i nt capacity) { this . start = start ; this . capacity = capacity; }
13
/* Check if a n index on the full array i s wit hin t he stack bou ndaries . The * stack c a n wrap around to t he start of the a rray . */ p u b l i c boolean isWit h i nStackCapacity ( i nt i ndex ) { /* If outs ide of bounds of a rray, return false . */ if ( index < 0 I I index > = values . lengt h ) { ret u r n false ; }
14 15
16
17 18
19
20 21 22 23
24 25
}
26
27
p u b l i c int lastCapac ityindex ( ) { return adj usti ndex ( start + c a p a c ity }
28
29 30
31
public i n t lastE leme ntindex ( ) { return a d j u s t i ndex ( start + s i ze }
32
33
34
35 36
37
38 3'
40 41
42 43
44
45 46
47
48
49
50 51 52
53
/ * If index wraps around, a d j u s t it . * / i nt cont iguou s index = index < start ? index + values . length i nt end = start + capacity; return start
3
=
1
5
2
8
12
10
3
->
1
7
tmp
Step
=
5
12
s
10
3
1
7
tmp
3
=
Note that 8 and 1 2 a re still in s l-and that's okay! We just repeat the same steps for those two numbers as we did for 5, each time popping off the top of sl and putting it into the "rig ht place" on s 2 . (Of cou rse, si nce 8 and 1 2 were moved from s2 to sl precisely because they were larger than 5, the "rig ht place" for these elements will be rig ht on top of 5. We won't need to muck a round with s 2's other elements, and the inside of the below w h i l e loop will not be run when tmp is 8 or 1 2.) 1
2
3
4 5
6
7
�
9
le
11 12
void sort ( Stack s ) { Sta c k < I ntege r > r = new Stac k < I nteger > ( ) ; while ( ! s . is Empty ( ) ) { / * I nsert each element in s i n sorted o rder into r . */ int tmp = s . pop( ) ; while ( ! r . is Empty ( ) && r . peek ( ) > tmp) { s . pu s h ( r . po p ( ) ) ; } r . pus h ( tmp ) ; }
13 14
15
16 }
/ * Copy t he elements from r back into s . */ while ( ! r . is Empty ( ) ) { s . pus h ( r . pop( ) ) ; }
This algorithm is 0 ( N2 ) time and 0 ( N ) space. If we were allowed to use unlim ited stacks, we cou ld i m plement a modified quicksort or mergesort. With the mergesort solution, we would create two extra stacks and divide the stack into two parts. We would recu rsively sort each stack, and then merge them back together in sorted order into the orig inal stack. Note that this would require the creation of two additional stacks per level of recursion. With the quicksort solution, we wou ld create two additional stacks and divide the stack into the two stacks based on a pivot element. The two stacks would be recu rsively sorted, and then merged back together into the orig inal stack. Like the earlier solution, this one involves creating two add itional stacks per level of recursion.
138
Cracking the Coding I nterview, 6th Edition
Solutions to Chapter 3
I
Stac ks and Queues
Animal Shelter: An animal shelter, which holds only dogs and cats, operates on a strictly "first in, first out " basis. Peop l e m ust adopt either the "oldest" (based on arrival time) of all a n imals at the shelter, or they can select whether they wou l d prefer a dog or a cat (and will receive the old est animal of that type). They ca n not select w h ic h s p e cific a n imal t he y wou ld like. Create the data structu res to maintain this system and implement operations such as enq u e u e, d e q u e u eAny, d e q u e u eDog,
3.6
and dequeueCat. You may use the built-in Linked l i st data structure.
SOLUTION
pg 99
We cou ld ex pl ore a va riety of solutions to this pro bl e m For i nsta nce, we could mainta i n a single queue. This would make d e q u e u eAny easy, but d e q u e u eDog and d e q u e u e C a t wo u ld req uire iteration through the queue to find the fi rst d og or cat. This would increase the complexity of the solution and decrease the effi c ie ncy. .
An alternative approach that is simple, clean and efficient is to simply use separate q u e u es for dogs and cats, a n d to p lac e them wit h i n a w ra pper c l a ss ca lled A n i m a l Q u e u e . We the n store some s ort of t im estam p to mark when each animal was enqueued. When we call d e q u e u eAny, we peek at the heads of both the dog a n d c a t q u e u e a n d re tu rn t he o ldest . 1
2 3
4 5
6 7
abst ract c l a s s Animal { private int orde r ; protected String name ; public Anima l ( String n ) { name = n ; } public void s etOrder ( int ord ) { order ord ; } public int getOrder ( ) { ret urn o rd e r ; }
8
9
10
11 12 13
}
/* Compa re orders of animals to return the older item . * / public boolean isOlderTha n ( Animal a ) { ret urn t h i s . order < a . getOrder ( ) ; }
14 cla s s Anima lQueue { 15 Linked list dogs new Linked list ( ) ; 16 Linked list cat s new Linked list ( ) ; 17 private int order = 0; // acts a s t imestamp
18
19
2e
21 22
23
24 25 26 27
28
29
30
31
32 33
34 35
36
public void enqueue ( Animal a) { /* Order is used as a sort of t imestamp, so that we can compare the insertion * order of a dog to a cat . */ a . setOrde r ( order ) ; order++;
}
if (a instanceof Dog ) dogs . addlast ( ( Dog ) a ) ; else if ( a instanceof Cat ) cat s . addlast ( (Cat ) a ) ;
public Animal dequeueAny ( ) { / * Look at tops of dog and cat queue s , and pop the queue with the oldest * value . */ if (dogs . s i ze ( ) 0) { return dequeuecats ( ) ; 0) { } e ls e if ( cats . s i ze ( ) ret urn dequeueDogs ( ) ; } ==
CrackingTheCodinglnterview.com I 6th Edition
239
Solutions to Cha pter 3 37 38 39
I Stacks a n d Queues
Dog dog ; dogs . peek ( ) ; Cat c at c at s . p ee k ( ) ; if (dog . i sOlderThan ( c at ) ) { return dequeueDogs ( ) ; } else { return dequeueCat s ( ) ; } =
4�
�1
42
43
44 45
}
4�
public Dog dequeueDogs ( ) { return dogs . po ll ( ) ; }
46 47 49
5 t'l 51 52
53
54 }
55
56
p u bli c Cat dequeueCat s ( ) { ret u r n cats . poll ( ) ; }
5�
public c l a s s Dog extends Animal { public Dog ( St r i ng n ) { super ( n ) ; } }
6� 61
public cla s s Cat extends Animal { public Cat ( String n ) { super ( n ) ; }
57
5,
62
}
It is importa nt that Dog and Cat both inherit from an An ima l class si nce d e q u e u e A ny ( ) needs to be able to support returning both Dog a nd Cat objects. If we wa nted, ord e r could be a true timestamp with the actual date and ti me. The adva ntage of this is that we would n't have to set and maintain the nu merical order. If we somehow wound up with two animals with the same timestam p, then (by defi nition) we don't have an older animal a nd we could return either one.
240
Cracking the Coding I nterview, 6th Edition ..
4 Solutions to Trees and Graphs
4.1
Route Between Nodes: Given a d irected g raph, design an algorithm to find out whether there is a
route between two nodes. pg 1 0 9
SOLUTION
This problem can be solved by just simple graph traversal, such as depth-first search or breadth-first sea rch. We start with one of the two nodes and, d u ring traversal, check if the other node is found. We should mark any node found in the course of the algorithm as "a lready visited" to avoid cycles and repetition of the nodes. The code below provides an iterative implementation of breadth-first search. 1 enum State { U nv isit e d , Visit ed , Visiting; } 2 3
4 5
6
7
8
9
10 11
12
13
14
15
16 17 18 19 20 21
22
23
24 25
25 27
28 29
boolean s e a rc h ( Graph g, Node start , No d e e n d ) { if ( st a rt
==
end ) return true;
// operates as Qu e u e L i nked List q = new Linkedlist ( ) ; for ( N od e u : g . ge tNo d e s ( ) ) { u . state State . Unvis ited ; } start . state = State . Visiting; q . add ( st a rt ) ; No d e u ; while ( ! q . i s Empty( ) ) { u = q . removeF irst ( ) ; // i . e . , dequeue ( ) if ( u ! = n u l l ) { for ( Node v : u . getA d j a c e nt ( ) ) { if ( v . state = = State . Unvis ited ) { if ( v == end ) { return t rue; } else { v . state State . Vi s it i ng ; q . ad d ( v ) ; } } } u . state State . Vi s ited ; } =
=
CrackingTheCodinglnterview.com I 6th E d i tio n
241
Solution s to Chapter 4 30 31
32
I Trees and Graphs
}
return fa l s e ; }
It may be worth discussing with your interviewer the tradeoffs between breadth-first sea rch and depth-first search for th is and other problems. For example, depth-first sea rch is a bit simpler to im plement since it can be done with simple recursion. Breadth-first search can also be usefu l to find the shortest path, whereas depth-first sea rch may traverse one adjacent node very deeply before ever going onto the immediate neigh bors. Minimal Tree: Given a sorted (increasing order) array with unique integer elements, write an
4.2
algorith m to create a binary search tree with minimal height. pg SOLUTION
--
--
---
---
--
--
-
--
1 09
-----
10 create a tree of minimal height, we need to match the number of nodes in the left subtree to the number of nodes in the right su btree as much as possible. This means that we want the root to be the middle of the array, since this wou ld mean that half the elements would be less than the root and half wou ld be greater than it.
We proceed with constructing our tree in a similar fash ion. The middle of each subsection of the array becomes the root of the node. The left half of the array will become our left subtree, and the right half of the array will become the right su btree. .
One way to implement this is to use a simple roo t i n s e rtNod e ( int v ) method which inserts the va lue v through a recursive process that sta rts with the root node. This will indeed construct a tree with minimal height but it will not do so very efficiently. Each insertion will require traversing the tree, giving a tota l cost of O ( N log N ) to the tree. Alternatively, we can cut out the extra traversals by recursively using the c reateMin ima l B S T method. This method is passed just a su bsection of the array and returns the root of a minimal tree for that array. The algorithm is as follows: 1 . Insert into the tree the middle element of the array. 2. Insert (into the left su btree) the left subarray elements. 3.
Insert (into the right su btree) the right suba rray elements.
4.
Recu rse.
The code below implements this algorith m. 1
TreeNode createMinima lBST ( int a r ray [ ] ) { ret u r n createMinima lBST ( a r ray, 0, a r ray . length }
2
3
4 5
6 7
8 9
10
11 12
13
-
1);
T reeNode createMinima lBST ( int a r r [ ] , int start , int end) { if ( end < start ) { ret u r n null ; } int mid = ( sta rt + end) / 2 ; TreeNode n = new TreeNode ( a r r [ m id ] ) ; n . left c reateMinimalBST ( a r r , start , mid 1) ; n . right = cre ateMinimalBST ( a r r , m id + 1 , end ) ; ret urn n ;
242
=
C racking the Coding Interview, 6th Edition
-
Solutions to Chapter 4 14
I Trees and Graphs
}
Although this cod e does not seem especially complex, it can be very easy to make little off-by- o ne errors.
Be sure to test these pa rts of the code very thorough ly.
List of Depths: Given a binary tree, design an a lgorithm which creates a linked list of all the nodes
4.3
at each depth (e.g., if you have a tree with depth D, you'll have D l i n ked lists). pg 1 09
SOLUTION
Thoug h we might think at first gla nce that this problem req uires a level-by-level traversal, this isn't actua lly necessa ry. We can traverse the g raph any way that we'd like, provided we know which level we're on as we do so. We ca n i mplement a simple mod ification of the p re-order traversal algorithm, where we pass in l e v e l + 1 to the next recursive call. The code below provides a n implementation using de pth-fi rst search. void create LevelLinked List (TreeNode root , ArrayList< Linked List > list s , 1 2 int leve l ) { if ( root == null ) return; I I base case 3 4 5
Linked List list = null; if ( li st s . s i ze ( ) = = level) { I I Level not contained in list list new LinkedList ( ) ; / * Levels a re a lways t raversed in orde r . So, if this is the first time we ' ve * vis ited level i , we must have seen levels 0 through i - 1 . We ca n * the refore safely add t he level at the end . */ lists . add ( l ist ) ; } else { list = lists . get ( level) ;
6
7
=
8
9
10 11
12
13 14
}
15
16 17 18
19
list . add ( root ) ; create LevelLinked List ( root . left , l i s t s , level + l ) ; create LevelLinked List ( root . right , l i st s , level + 1 ) ; }
20
ArrayList < Linked List > create levelL inked List (TreeNode root ) { ArrayList < L inked list> lists = new Arraylist< Linked List > ( ) ; c reate Level linked list ( root , list s , 0) ; return list s ; 23 21 22
24
}
Alternatively, we can a lso implement a mod ification of breadth-fi rst sea rch. With this implementation, we want to iterate through the root first, then level 2. then level 3, and so on. With each level i, we will have already fully visited a l l nodes on level i. 1. This means that to get which nodes a re on level i, we ca n simply look at all children of the nodes of level i 1. -
-
The code below im plements this a lgorithm. 1 Array List< Linked List> create Level LinkedL ist ( TreeNode root ) { 2 ArrayList< Linked list> result = new Arraylist < L inked list > ( ) ; / * "Vis it" the root */ 3 4 Linked List< TreeNode> cu r rent = new L inked List ( ) ; S if ( root ! = null ) { current . add ( root ) ; 6 7
}
CrackingTheCodinglnterview.com I 6th Edition
243
Sol utions to Chapter 4 8
9
18 11 12
13
14 15 16 17 18 19 20 21
22 23
24 }
I Trees and Graphs
while (current . s ize ( ) > 0 ) { result . add (current ) ; I I Add p revious level L i n ked L i s t pa rents = current; II Go to next level cu rrent = new L i n ked L i s t ( ) ; for (TreeNode pa rent : parents ) { I * Visit t he chi ldren * I if ( pa rent . left ! = null ) { current . ad d ( pa rent . left ) ; } if ( pa rent . right ! = null ) { current . ad d ( pa rent . right ) ; } } } ret urn res u l t ;
One might ask which of these solutions is more efficient. Both run in 0 ( N) time, but what a bout the space efficiency? At first, we might want to claim that the second solution is more space efficient. In a sense, that's correct. The first solution uses 0 ( log N ) recursive calls (in a bala nced tree), each of which adds a new level to the stack. The second solution, which is iterative, does not req uire this extra space. However, both solutions require returning 0 ( N ) data. The extra 0 ( log N ) space usage from the recursive implementation is dwarfed by the 0( N) data that must be returned. So while the first solution may actua l ly use m ore data, they a re equally efficient when it comes to "big O." Check Balanced: Im plement a fu nction to check if a binary tree is bala nced. For the purposes of this question, a bala nced tree is defined to be a tree such that the heig hts of the two su btrees of any node never differ by more than one.
4.4
pg 7 7 0 SOLUTION
In this question, we've been fortunate enough to be told exactly what bala nced means: that for each node, the two su btrees differ in height by no more than one. We can implement a solution based on this defini tion. We can simply recurse through the entire tree, and for each node, compute the heig hts of each subtree. 1
i nt getHeight ( T reeNode root ) { if ( root == n ul l ) ret urn 1 ; I I Base ca se ret urn Mat h . max ( getHeight ( root . left ) , getHeight ( root . r ight ) ) }
2
-
3
4
5
6
+
boolea n i s Ba lanced (TreeNode root ) { if ( root =� n ul l ) ret urn t r u e ; I I Base case
7 8 9
10 11
12
13
14
15
}
244
int heightDiff = getHeight ( root . left ) - getHeight ( root . right ) ; if (Math . abs ( heightDiff ) > 1) { return fa lse; } else { I I Recurse ret urn i s B a lanced ( root . left ) & & i s B a l a nced ( root . right ) ; }
Cracki ng the Coding I nterview, 6th Edition
1;
Solutions to Chapter 4
l Trees and Graphs
Although this works, it's not very efficient. On each node, we recurse through its entire subtree. This means that getHeight is called repeatedly on the same nodes. The algorithm is O ( N log N ) since each node is "touched" once per node a bove it. We need to cut out some of the calls to getHeight. If we inspect this method, we may notice that getHeight could actually check if the tree is bala nced at the same time as it's checking heig hts. What d o we do when we discover that the su btree isn't ba lanced? Just return an error code. This improved algorithm works by checking the height of each su btree as we recurse down from the root. On each node, we recursively get the heig hts of the left and right su btrees through the c he c kHe ight method. If the subtree is balanced, then c h e c kHeight will retu rn the actual height of the subtree. If the su btree is n ot balanced, then c h e c kHeight will retu rn an error code. We wil l immediately brea k and retu rn an error code from the cu rrent call.
I
What do we use for a n error code? The height of a null tree is generally defined to be 1 so that's not a great idea for an error code. Instead, we'll use I ntege r . MIN_VA L U E . -
,
The code below i mplements this algorithm. 1 int c h e c k H e ight ( T r ee N o d e root ) { 2 if ( root n u l l ) r et u r n 1 ; ==
3
4
int leftHeight if ( leftHeight
5
6
7
-
=
int rightHeight if ( rightHeight
8 ' 10
che ckHeight ( root . left ) ; I ntege r . MIN_VALUE ) return I nteger . MIN_VALUE ; / / P a s s e rror u p ==
checkHeight ( root . right ) ; I nteger . MIN_VALU E ) ret urn Intege r . MIN_VALU E ; / / Pass error u p
=
11 12
i n t height Diff leftHeight - rightHeight ; i f ( Math . a b s ( heightDiff ) > 1 ) { ret urn Intege r . MIN_VALUE; / / F o u n d error - > p a s s it ba c k
14 15
}
13
16
} e l se {
}
return Mat h . max( leftHeight , rightHeight )
+
1;
17
boolea n isBala nced ( TreeNode root ) { 19 return c h ec k H e ight ( r oot ) ! = I ntege r . MIN_VALU E ; 20 } 18
This code ru ns in O ( N ) time and O ( H ) space, where H is the height of the tree. 4.5
Validate BST: I mplement a fu nction to check if a binary tree is a binary sea rch tree. pg
7 70
SOLUTION -··· · ··· ··· ·· · ·-·· ··- · · -----
We can implement this solution in two d ifferent ways. The first leverages the in-order traversal, and the second builds off the property that left < c u r rent < right. ==
CrackingTheCodinglnterview.com I 6th Ed iti on
245
Solutions to Cha pter 4
I Trees and Graphs
Solution #1 : In-Order Traversal
Our first thought might be to do an in-order traversal, copy the elements to an a rray, and then check to see if the a rray is sorted. This solution ta kes up a bit of extra memory, but it works-mostly. The only problem is that it can't handle duplicate va lues in the tree properly. For exam ple, the a lgorithm can not distinguish between the two trees below (one of which is invalid) since they have the same in-order traversal . Valid BST
Invalid BST
� @ However, if we assume that the tree cannot have duplicate va lues, then this approa ch works. The pseudo code for this method looks something like: 1
i nt index = 0; voi d copyBST ( TreeNode root , i nt [ ] a rr ay ) { if ( root == null) return; copyBST ( root . left , a rray ) ; array [ index] = root . d ata ; index++ ; copyBST ( root . right , a rray ) ;
2 3
4 5
6 7
}
8 9
10 11
12 13
14 15
boolean checkBST ( TreeNode root ) { i nt [ ] a rray = new int [ root . s ize ] ; copyBST ( root , a rray) ; for ( int i = 1 ; i < a rray . lengt h ; i++ ) { if ( a rray [ i ] < = a rray [ i - 1 ] ) ret urn false;
}
16 17
}
return t r u e ;
Note that it is necessa ry to keep tra ck of the logica l "end" of the a rray, since it would be allocated to hold all the elements. When we examine this solution, we find that the a rray is not a ctually necessa ry. We never use it other than to compare a n element to the previous element. So why notjust track the last element we saw and com pare it as we go? T he code below implements this algorithm. 1
I nteger la st_printed = null; boolean checkBST ( TreeNode n ) { if ( n == n u l l ) ret urn true;
2
3
4 5
II Check I recurse left if ( ! checkBST ( n . left ) ) ret urn false;
6
7
8 9
10 11 12
13
14
246
II Check current if ( l ast_printed ! = null && n . data < = l a st_printed ) { ret urn fa l s e ; } la st_printed = n . data ; II Check I recurse right
Cracking the Coding Interview, 6th Edition
Solutions to Cha pter 4 15
if
16 17
18
}
I Trees a nd Graphs
( ! c heckBST ( n . right ) ) return false ;
ret urn t r u e ; // All
good !
We've used an Integer instead of int so that we can know when la st_pri nted has been set to a value. If you don't like the use of static varia bles, then you can tweak this code to use a wrapper class for the integer, as shown below. 1 cla s s W r a p i nt { 2 p u b l i c int va lue; 3
}
Or, if you're implementing this i n C ++ or another language that supports passing integers by reference, then you ca n simply do that. Solution #2: The Min I Max Solution In the second solution, we leverage the definition of the binary search tree.
What does it mean for a tree to be a binary search tree? We know that it must, of cou rse, satisfy the condition left . data c u rrent - > right). If n were to the right of q , then we have fu lly traversed q 's su btree as wel l. We need to traverse upwards from q until we fi nd a node x that we have not fully traversed. How do we know that we have not fu lly traversed a node x? We know we have hit this case when we move from a left node to its parent. The left node is fu lly traversed, but its parent is not.
248
Cracking the Coding I nterview, 6th Edition
Solutions to Chapter 4
I Trees and Graphs
The pseudocode looks like this: 1 2
3
4 5
6
7
8
9
10
Node i norderSuc c ( Node n ) { if ( n has a right s u bt ree ) { ret urn leftmost child of right s ubtree } else { while ( n i s a right child of n . pa rent ) { n = n . pa rent ; / / Go u p } return n . pa rent ; / / Pa rent has not been t raversed }
}
But wa it-what if we traverse all the way up the tree before finding a left child? This will happen only when we hit the very end of the in-order traversal. That is, if we're already on the fa r right of the tree, then there is no in-order successor. We should return nu l l. The code below implements this algorithm (and p roperly hand les the null case). 1
2 3
TreeNode i norderSucc ( TreeNode n ) { if ( n == nul l ) ret urn null ;
4
/ * Found right childre n - > return leftmost node of right s u btree . * / if ( n . right ! = null ) { return leftMostChild ( n . right ) ; } else { TreeNode q = n ; TreeNode x = q . pa rent ; // Go up until we ' re on left i n stead of right while ( x l = null && x . left ! = q) { q Xj x = x . pa rent ; } return x ; }
5 6 7
8
9
10
11
12
13
14 15
16
17
18
}
TreeNode leftMostChild ( TreeNode n) { if (n == n u l l ) { 21 ret u r n n u l l ; 22 } 23 while ( n . left ! = nul l ) { 24 n n . left ; 25 } 26 return n ; 19
20
=
27
}
This is not the most algorithmically complex problem in the world, but it can be tri cky to code perfectly. I n a problem like th is, it's usefu l t o sketch out pseudocode t o carefully outline the different cases.
CrackingTheCodinglnterview.com I 6th Edition
249
Sol ution s to Chapter 4
I Trees and Graphs
Build Order: You a re given a list of projects and a list of dependencies (which is a list of pairs of
4.7
projects, where the second project is dependent on the fi rst project). A l l of a project's dependencies must be built before the project is. Find a build order that will a l low the projects to be built. If there is no va lid build order, return a n error. EXAMPLE I nput: proj ect s : a , b, c , d , e , f dependenc ies : ( a , d ) , ( f , b ) , ( b , d ) , ( f , a ) , ( d , c ) Output: f , e , a , b , d , c pg 1 1 0
SOLUTION
Visualizing the information as a graph proba bly works best. Be careful with the direction of the arrows. I n the graph below, a n a rrow from d to g mea ns that d must b e compiled before g . You c a n also d raw them in the opposite direction, but you need to consistent and clear about what you mea n. Let's d raw a fresh exa m p le.
I n d rawing this example (which is not the exa mple from the problem descri ption), I looked for a few things. I wa nted the nodes la beled somewhat randomly. If I had instead put a at the top, with b and c as chil d ren, then d and e, it cou ld be misleading. The alpha betical order wou ld match the compile order. I wa nted a graph with mu ltiple pa rts/components, since a con nected graph is a bit of a specia l case. I wanted a graph where a node l in ks to a node that cannot immediately fo llow it. For example, f l i n ks to a but a cannot im med iately fol low it (since b and c must come before a and after f). I wa nted a larger graph since I need to figure out the pattern. I wa nted nodes with multiple dependencies. Now that we have a good exa mple, let's get started with an algorithm. Solution # l
Where do we sta rt? Are there any nodes that we can definitely compile immed iately? Yes. Nodes with no i ncoming edges can be built immediately since they don't depend on anything. Let's add all such nodes to the build order. I n the earlier exa mple, this means we have an order off, d (or d, f). Once we've done that it's irrelevant that some nodes a re dependent on d and f since d and f have already been built. We can reflect this new state by removi ng d and f's outgoing edges. build order : f, d
250
Cracking the Coding Interview, 6th Edition
Sol utions to Chapter 4
I Trees and Graphs
Next. we know that c , b, and g are free to build since they have no incoming edges. Let's build those and then remove their outgoing edges. build order : f, d,
c,
b, g
G G)
a>
®
cp0
Project a can be built next, so let's do that and remove its outgoing edges. This leaves just e. We build that next, giving us a com plete build order. build orde r : f, d , c , b, g, a , e Did this a lgorithm work, or did we just get lucky? Let's think a bout the logic.
1 . We first added the nodes with no incoming edges. If the set of projects can be built, there must be some "first" project. and that project can't have any dependencies. If a project has no dependencies (incoming edges), then we certainly can't break a nything by building it first. 2. We removed a l l outgoing edges from these roots. This is reasonable. Once those root projects were built,
it doesn't matter if a nother project depends on them. 3. After that, we found the nodes that now have no i ncom i ng edges. Using the same logic from steps 1 and 2, it's okay if we build these. Now we just repeat the same steps: find the nodes with no dependencies,
add them to the build order, remove their outgoing edges, and repeat. 4. What if there are nodes remaining, but all have dependencies (incoming edges)? This means there's no way to build the system. We should return an error. The implementation follows this approach very closely. I nitial ization and setup: 1 . Build a graph where each project is a node and its outgoing edges represent the projects that depend on it. That is, if A has an edge to B (A -> B), it means B has a dependency on A and therefore A must be b u i lt before B. Each node also tracks the n u m ber of incoming edges. 2. Initialize a bui ldOrd e r a rray. Once we determine a project's build order, we add it to the array. We a lso conti nue to iterate through the a rray, using a toBeP roc e s sed pointer to point to the next node to be
fu lly processed. CrackingTheCodinglnterview.com j 6th Edition
I
251
Solution s to Chapter 4 3.
I Trees and Graphs
Find a l l the nodes with z e ro incoming edges a n d add those t o a b u i ldOrd e r array. Set a toBeProc es sed pointer to the beginning of the array.
Repeat until toBeP roc e s s ed is at the end of the b u i ldOrd e r: l . Read node at toBePro c e s s ed. »
If node is nu 1 1. then all remaining nodes have a dependency and we have detected a cycle.
2 . For each child of node:
3.
»
Decrement c h i ld . d e pen d e n c i e s (the n u m ber of incoming edges).
»
If c h i ld . depen d e n c i e s is zero, add c h i l d to end of b u i ldOrd e r.
I ncrement toBePro c e s sed.
The code below im plements this algorithm. / * Find a correct build order . * / 1 2 Proj ect [ ] findBuildOrde r ( St r i ng [ ] proj ect s , St ring [ ] [ ] dependencies ) { 3 Graph graph = buildGraph ( proj ect s , dependencies ) ; 4 return orderProj ects (graph . getNodes ( ) ) ; 5 } 6 7 / * Build the graph, adding the edge ( a , b ) if b i s dependent on a . Assumes a pair * is listed i n "build order" . The pair ( a , b ) in dependencies indicates that b 8 9 * depends on a and a must be built before b . */ 1 0 Graph buildGraph ( String [ ] project s , St ring [ ] [ ] dependencies ) { 11 G r a p h graph = new Graph ( ) ; 12 for ( St ring project : project s ) { 13 graph . createNode ( project ) ; 14 } 15 16 for ( St r i ng [ ] dependency : dependencies ) { 17 St ring first = dependency [ 0 ] ; St ring second = dependency [ l ] ; 18 19 graph . addEdge (first, second ) ; 20 } 21 22 return graph ; 23 } 24 25
26
27 28
29
30 31 32
33
34 35
36 37
38
39
40
41
252
/* Ret u r n a l i s t of the projects a correct build order . */ Project [ ] orderProj ects (ArrayList< Proj ect> project s ) { Project [ ] order = new Project [ proj ects . s i ze ( ) ] ; /* Add "root s" to the build order first . * / int endOfList = addNonDependent ( order , project s , 0 ) ; int toBeProcessed = 0; while ( toBeProcessed < order . lengt h ) { Project current = order [ toBeProcessed ] ; / * We have a circular dependency s i n ce t here are no remai n i ng proj ects with * zero dependencies . * / if ( cu rrent == nul l ) { return n u l l ; }
Cracking the Coding I n te rv iew, 6th Ed ition
Solutions to Chapter 4 42
/* Remove myself a s a dependency . * / Array list< Project> c h i ld ren = current . getChild ren ( ) ; for ( Project c h i l d : children ) { child . decrementoependencies ( ) ; }
43
44 45
46 47 48
/ * Add children that have no one depending on t hem . */ endOflist = addNonDependent ( order, children, endOfList ) ; toBeProces sed++;
49
50 51
52 53 54 55
I Trees and Graphs
}
return orde r ; }
56 / * A helper function to i n s e rt projects w i t h zero dependencies i nto t he order * a rray, start i ng at i ndex offset . */ 5 8 int addNonDependent ( Project [ ] order, Array L i s t < Project> project s , int offset ) { 59 for ( Project project : project s ) { 60 if ( project . getNumberDependencies ( ) == 0) { 61 orde r [ offs et ] = project ; 62 offset++; 63 } 64 } ret u r n offset ; 65 66 } 67 68 public c l a s s Graph { 69 private ArrayList< Project > nodes = new Array List< Proj ect > ( ) ; 70 private HashMap map new H a s hMap ( ) ; 57
=
71 72
public Project getOrCreateNode ( St r i ng name ) { if ( ! map . containsKey ( name ) ) { Project node = new Project ( name ) ; nodes . ad d ( node ) ; map . put ( name , node ) ; }
73
74 75 76 77 78 79
return map . get ( name ) ;
80
}
81
82 83
public void addEdge ( St r i ng startName , St r i ng endName ) { Project start = getOrCreateNode ( startName ) ; Project end = getOrCreateNode ( e ndName ) ; start . addNeighbor ( e nd ) ; }
84 85 86 87
88 89
90
public Arraylist< Proj ect > getNodes ( ) { ret urn nodes ; } }
91
public c l a s s Project { private Array List< Project> child re n = new Arraylist< Proj ect > ( ) ; 93 private HashMap m a p = new Ha s hMap ( ) ; 94 private String n ame ; 95 private i nt dependencies 0; 96 public Project ( String n ) { name n; } 97
92
CrackingTheCodingl nterview.com I 6th Edition
253
Solutions to Cha pter 4 98 99
publ ic vo id addNeighbo r ( Proj ect node ) { if ( ! ma p . conta i n sKey ( node . getName ( ) ) ) { c h i ld ren . add ( node ) ; map . put ( node . getName ( ) , node ) ; node . in crementDependencies ( ) ; } }
1 00 101
102 103
104 105
1 06
107
public vo id inc rement Depende n c ies ( ) { depende n c ies++; } public vo id decrementDependencies ( ) { dependencies - - ; }
1 08
109 110 111
112
113 }
public St ring getName ( ) { return name ; } public Array l i s t < Proj ect > getCh i ld re n ( ) { ret urn child ren; } public int getNumberDependencies ( ) { ret urn dependencies; }
This solution takes 0 ( P pairs.
I
I Trees and Graphs
+
D ) time, where P is the number of projects and D is the number of dependency
Note: You might recog nize this as the topological sort algorithm on page 632. We've rederived this from scratch. Most people won't know this a lgorithm and it's reasonable for an interviewer to expect you to be able to derive it.
Solution #2
Alternatively, we can use depth-first search (DFS) to fi nd the build path.
Suppose we picked an arbitrary node (say b) and performed a depth-first search on it. When we get to the end of a path and can't go any further (which will happen at h and e), we know that those terminati ng nodes can be the last projects to be built. No projects depend on them. DFS ( b ) DFS ( h ) build o rder ..., h DFS ( a ) DFS ( e ) build o rder . . . , e, h
II II II II II II II
Step Step Step Step Step Step Step
1
2 3
4 5 6
7+
Now, consider what happens at node a when we return from the DFS of e. We know a 's children need to appear after a in the build order. So, once we return from searching a's children (and therefore they have been added), we can choose to add a to the front of the build order. 254
Cracking the Coding I nterview, 6th Edition
Solutions to Chapter 4
l Trees and Graphs
Once we return from a, and complete the DFS of b's other children, then everything that must appear after b is in the list. Add b to the front. / / Step 1 DFS( b ) I I Step 2 DF S ( h ) I I Step 3 h build order I I Step 4 DFS ( a ) I I Step S DFS ( e ) build o rd e r , e, h I I St e p 6 I I St e p 7 build order = , a , e, h DFS ( e ) - > return I I step 8 I I Step 9 build o rd e r , b , a, e , h •
.
•
•
. ,
=
=
.
.
•
.
• • .
Let's mark these nodes as having been built too, just in case someone else needs to build them.
Now what? We can start with any old node again, doing a DFS on it and then adding the node to the front of the build queue when the DFS is com pleted. DFS ( d ) DFS ( g ) b u i l d o rd e r build o rd e r =
=
• • .
•
.
.
, g, b , a, e, h , d , g , b, a , e, h
DFS ( f ) DF S ( c ) build o rd e r , c, d , g, b , a , e , h build order = f , c , d , g, b , a, e , h =
• • .
In a n a lgorithm like this, we should think about the issue of cycles. There is no possible build order if there is a cycle. But sti ll, we don't want to get stuck i n a n infinite loop just because there's no possible solution.
A cycle will happen if, while doing a DFS on a node, we run back i nto the same path. What we need there fore is a signal that ind icates"l'm sti ll processing this node, so if you see the node agai n, we have a problem:' What we can do for this is to mark each node as a "p a r t i a l " (or"is v i s it i ng") state just before we start the DFS on it. If we see any node whose state is pa rt i a l, then we know we have a problem. When we're done with this node's DFS, we need to u pdate the state. We a lso need a state to ind icate "I've already processed/built this node" so we don't re-build the node. Our state therefore can have three options: COMP LETED, PART IAL, and B LANK. The code below implements this algorithm. Stack< Project> findBuildOrder ( St r i ng [ ] projects , St r i ng [ ] [ ] dependenc ies ) { 1 Graph graph = buildGraph ( project s , dependencies ) ; 2 return o rd e rPro j ec t s (gr a ph ge tN o d e s ( ) ) ; 3 4 } .
CrackingTheCodinglnterview.com j 6th Edition
255
Solutions to Ch a pter 4 5 5 7
I Trees and Graphs
Stack< Proj ect > orderProj ects (Array L i s t < Project > project s ) { Stack stack new Stack ( ) ; for ( Project project : project s ) { if ( p roject . getState ( ) == Project . State . BLANK) { i f ( ! doDF S ( project, stac k ) ) { ret u r n n u l l ; } =
8
9
10 11
12
13
}
14
15 16
17 18
19
20
21
22
} ret u r n stack; } boolean doDF S ( Project proj ect , Stack< Project > s t a c k ) { if ( p roject . getState ( ) Project . State . PARTIAL ) { ret urn false ; / / Cycle } ==
23
==
i f ( p roject . getState ( ) Project . State . B LAN K ) { project . setState ( Project . State . PARTIAL ) ; ArrayList < Project> children project . getChildren ( ) ; for ( Project child : childre n ) { if ( ! doDF S ( child , stack) ) { return fal s e ; } } project . setState ( Project . State . COMPLETE ) ; stack . pu s h ( project ) ; } ret urn true;
24 25
=
26
27
28 29 30 31
32
33
34
35 36 37 38 39
40
} /* Same a s before */ Graph buildGra p h (String [ ] project s , St ring [ ] [ ] dependencie s ) { . . . } public class Graph { }
41 42
/* E s sentially equ ivalent to earlier solutio n , wit h state i nfo added and * dependency count removed . * / 4 3 public class P r o j e c t { public enum State { COMPLETE , PARTIAL, B LANK} ; 44 45 private State state State . BLAN K ; 46 p u b l i c State getState ( ) { ret urn state ; } 47 st ; } public void setState ( State st ) { state 48 / * Du plicate code removed for brevity * / 49 } =
=
Like the earlier algorithm, this solution is O ( P+D ) time, where P is the number of projects and D is the number of dependency p a irs .
By the way, this problem is called topological sort: linearly ordering the verti ces in a g raph such that for every edge ( a , b ) , a appears before b i n the linear o rder.
256
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 4 4.8
I
Trees and G ra p h s
First Common Ancestor: Design an algorithm and write code to fi nd the first common ancestor of two nodes in a binary tree. Avoid storing additional nodes in a data structure. NOTE: This is not necessarily a binary search tree.
pg 1 1 0
SOLUTION
If this were a binary search tree, we could mod ify the fi nd operation for the two nodes and see where the paths diverge. U nfortunately, this is not a bi nary search tree, so we must try other approaches.
Let's assume we're looking for the common ancestor of nodes p and q. One question to ask here is if each node in our tree has a link to its parents. Solution #1 : With Links to Parents
If each node has a l i n k to its parent, we cou ld trace p and q's paths up until they i nterse ct. Th is is essentially the same problem as question 2.7 wh ich find the intersection of two linked l ists. The "linked list" in this case is the path from each node up to the root. (Review this solution on page 2 2 1 .) 1 TreeNode commonAncestor ( TreeNode p , TreeNode q ) { 2 int delta = depth ( p ) - depth ( q ) ; I I g e t difference in depths 3 TreeNode first = delta > 0 ? q : p ; I I get shallower node TreeNode second = delta > 0 ? p : q ; I I get deeper node 4 5 second = goUpBy ( second, Math . a b s ( delta ) ) ; II move deeper node u p 6 7
.
I* Find where paths int e r sec t * I while ( first ! = s ec o nd && first ! = null && s ec o n d ! = nul l ) { first = first . parent ; seco nd = second . parent ; } return first == null I I s econd f i rst ; null null
8 9
10 11 12
13 } 14 1 5 TreeNode goUpBy (TreeNode node , i n t d e l t a ) { while ( delta > 0 && node ! = null) { 16 node = nod e p a r en t ; 17 18 delta - - ; 19 } 20 return node; 21 } 22 23 int d e pt h (Tr ee Nod e node ) { 24 int depth 0; 25 while ( n ode ! = n u l l ) { node = n od e . p a ren t ; 26 27 depth++ ; 28 } 2' return d e pt h ; .
=
30
}
This approach will take 0 ( d ) time, where d is the depth of the deeper node. Solution #2: With Links to Parents (Better Worst-Case Runtime)
Simi lar to the earlier approach, we cou ld trace p's path upwards and check if any of the nodes cover q . The first node that covers q (we already know that every node o n this path wil l cover p) must b e t h e first common an cestor. CrackingTheCodinglnterview.com I 6th Editio n
257
Solutions to Chapter 4
I Trees and Graphs
Observe that we don't need to re-check the entire subtree. As we move from a node x to its parent y , all the nodes under x have a l ready been checked fo r q . Therefore, we only need to check the new nodes "uncov ered''. which will be the nodes under x's sibling. For example, suppose we're looki ng for the first common ancestor of node p = 7 and node q = 1 7. When we go to p . pa rent (5), we uncover the subtree rooted at 3. We therefore need to search this subtree for q. Next, we go to node 1 0, uncovering the subtree rooted at 1 5. We check this subtree for node voila-there it is.
17
and
3 To
implement this, we can just traverse u pwards from p, storing the pa rent and the s i bling node i n a variable. (The s i b l i n g node is always a c h i l d o f pa rent a n d refers t o t h e newly uncovered subtree.) At each iteration, s i b l i n g gets set to the old pa rent's sibling node and pa rent gets set to pa rent . p a r ent. 1 TreeNode commonAncestor ( TreeNode root , TreeNode p , TreeNode q ) { 2 / * Check i f either node is not in the tree, or if one covers the other . */ 3 if ( ! covers ( root , p ) I I ! covers ( root , q ) ) { 4 ret urn n u l l ; 5 } else if (cover s ( p , q ) ) { ret urn p ; 6 7 } else if ( cove rs ( q , p ) ) { return q ; 8 9 } 10 11 / * Traverse upwards until you find a node that covers q . * / 12 TreeNode s ibling = getSibling ( p ) ; 13 TreeNode pa rent = p . pa rent ; while ( ! covers ( s i b l ing, q ) ) { 14 sibling = getSibling ( parent ) ; 15 16 pa rent = parent . pa rent ; 17 } 18 ret urn pa rent ; 19 } 20
2 1 boolea n cover s ( TreeNode root , TreeNode p ) { 22 if ( root == null) ret urn false; 23 if ( root = = p) ret urn true ; 24 ret urn covers ( root . left , p) I I covers ( root . right , p ) ; 25 } 26 27 TreeNode getS ibling ( TreeNode node) { 28 if ( node == n u l l I I node . pa rent null) { 29 return null ; 30 } 31 32 TreeNode pa rent node . pa rent ; ==
2 58
Cracking t h e Cod i n g
Interview, 6th Edition
Solutions to Chapter 4 33
34
return pa rent . left == node }
?
I Trees and Graphs
pa rent . right : pa rent . left ;
This algorithm takes o ( t ) time, where t is the size of the su btree for the first common ancestor. I n the worst case, this will be O ( n ) where n is the number of nodes in the tree. We can derive this runtime by noticing that each node in that subtree is sea rched once. ,
Solution #3: Without Links to Parents
Alternatively, you could follow a chain in which p and q a re on the same side. That is, if p and q are both on the left of the node, branch left to look for the common ancestor. If they a re both on the right, branch right to look for the common ancestor. When p and q a re no longer on the sa me side, you must have found the first common ancestor. The code below implements this a pproach. 1 TreeNode commonAncestor (TreeNode root , TreeNode p, TreeNode q ) { 2 / * E r ror check - one node i s not in t he t ree . * / if ( ! covers ( root , p ) 1 1 ! cnvers ( root , q ) ) { 3 4 return null ; 5
6 7
8 9
10 11 12 13 14 15
}
return ancestorHelper ( root , p , q ) ;
}
TreeNode a ncestorHelpe r ( TreeNode root , TreeNode p , TreeNode q ) { if ( root == null I I root == p I I root == q ) { return root ; }
boolean pisOnLeft = covers ( root . left , p ) ; boolean qisOnLeft covers ( root . left , q ) ; if ( pisOnleft ! = q isOnLeft ) { / / Nodes a re on different side return root ; =
16
17 18
} 19 TreeNode childSide = pisOnLeft ? root . left retu rn a ncestorHe lpe r ( childSide, p, q ) ; 20 21 } 22 2 3 boolean covers (TreeNode root , TreeNode p ) { 24 if ( root == null) ret urn false ; 25
26 27
root . r ight ;
==
if ( root p) return true; return covers ( root . left , p ) I I covers ( root . right , p ) ;
}
This algorithm runs i n O ( n ) time on a bala nced tree. This i s because c overs is called on 2n nodes in the first call (n nodes for the left side, and n nodes for the right side). After that the algorithm branches left or right, at which point c ov e r s will be called on 2'.Y,' nodes, then 2% , and so on. This resu lts in a runtime of O ( n ) . We know at this point that we cannot d o better than that i n terms of the asymptotic runtime since we need to potentially look at every node in the tree. However, we may be able to improve it by a constant multi ple. Solution #4: Optimized
Although Solution #3 is optimal i n its runti me, we may recognize that there is stil l some inefficiency in how it operates. Specifically, covers sea rches all nodes under root for p and q, including the nodes in each subtree (root . left and root . right). Then, it picks one of those su btrees and searches all of its nodes. Each subtree is sea rched over and over again.
CrackingTheCodinglnterview.com I 6th Edition
259
Solutions to Chapter 4
I Trees and Graphs
We may recognize that we should only need to search the entire tree once to find p and q . We should then be a ble to "bu bble u p" the fi ndings to earlier nodes in the stack. The basic logic is the same as the earlier solution. We recu rse through the entire tree with a function called commonAn c es t o r ( TreeNode TreeNode p , TreeNode q ) . This fu nction returns values as follows:
root ,
Returns p, if root's su btree includes p (and not q). Returns q, if root's su btree inclu des q (and not p). Retu rns n u l l, if neither p nor q a re in root's subtree. Else, returns the common ancestor of p and q . Finding the common a ncestor of p a n d q in the fi nal case is easy. When c ommonAn c e stor ( n . l eft , p , q ) and c ommonAn c e s tor ( n . right , p , q ) both return non-n u l l values (indicating that p and q were found in different su btrees), then n will be the common ancestor.
23
}
return root ; } else { ret u r n x = = null ? y }
The problem with this code occurs in the case where a node is not contained in the tree. For example, look at the following tree:
1
5
8
S u ppose we call commonAn c e s t o r ( node 3 , node 5 , node 7 ) . 0fcou rse, node 7 does not exist and that's where the issue will come in. The calling order looks like:
1
2
commonAn c ( node 3 , node 5 , node 7 ) calls commonAnc ( node 1 , node 5 , node 7 )
260
Cra cking the Coding Interview, 6th Ed ition
II II
- - -
> 5 > null
Solutions to Chapter 4 3
4
ca l l s commonAn c ( node 5 , node 5 , node 7 ) ca lls commonAnc ( node 8 , node 5 , node 7 )
I
Trees and Graphs
II --> 5 II > null - -
I n other words, when we call c ommonAn c e stor on the right subtree, the code will retu rn n ode S, just as it should. The problem is that, in finding the common a ncestor of p and q , the c alling function can't distin guish between the two cases: •
•
Case 1 : p is a child of q (or, q is a child of p)
Case 2: p is in the tree and q is not (or, q is in the tree a nd p is not)
I n either of these cases, c ommonAnc e s t o r will return p. I n the fi rst case, this is the correct return value, but
in the second case, the return value should be n u l l . We somehow need to distinguish between these two cases, a n d this is what the code below does. This code solves the problem by retu rning two values: the node itself and a flag indicating whether this node is actually the common ancestor. 1
2
3
4
5
6 7
8
9
cla s s Result { public TreeNode node ; public boolea n isAncestor; public Result (TreeNode n , boolean isAn c ) { node = n ; isAncestor isAn c ; } } =
10
TreeNode commonAncestor ( T reeNode root , TreeNode p , TreeNode q ) { Result r = commonAncestorHelpe r ( root , p , q ) ; if ( r . isAncesto r ) { 12 return r . node ; 13 14 } 15 return n u l l ; 16 } 11
17 18
19
20 21 22 23
24 25
26
27
28 29
30
31 32 33
34 35 36 37
38
39
40
Result colllllo nAncHelpe r ( TreeNode root , TreeNode p, TreeNode q ) { if ( root n u l l ) return new Result ( nu l l , fals e ) ; if ( r oot p && root == q) { return new Result ( root , true ) ; } Result rx = commonAncHelpe r ( root . left , p , q ) ; if ( rx . isAncestor ) { II Found common ancestor return rx; } Result ry = commonAncHelpe r ( root . right , p , q ) ; if ( ry . isAncesto r ) { II Found common ancestor return ry; } if ( rx . node ! = n u l l && ry . node ! = null) { return new Res ult ( root , true ) ; II This i s the common ancestor } else if ( root == p I I root == q) { I* If we ' re c u r rently at p or q , and we also found one of those nodes in a * s u bt ree , then t h is is truly an a n cestor and the flag s hould be true . *I boolean isAncestor = rx . node ! = null I I ry . node ! = n u l l ;
CrackingTheCodingl nterview.com I 6th Edition
261
Solutions to Chapter 4 41
ret urn new Res ult ( root, isAncestor ) ;
42
} else {
return new Res ult ( r x . node ! =n ull ? rx . node
43 44 45
I Trees and Graphs
}
ry . node, false ) ;
}
Of cou rse, as this issue only comes u p when p or q is not actu a l ly i n the tree, a n alternative solution wou ld be to first search through the enti re tree to make sure that both nodes exist. 4.9
A binary sea rch tree was c reated by traversing through an a rray from left to right and inserting each element. Given a binary sea rch tree with d istinct elements, print a l l possible a rrays that cou ld have led to this tree. BST Sequences:
EXAMPLE
Input:
Output: { 2 , 1 , 3 } , { 2 , 3 , l }
pg 1 1 0
SOLUTION
It's useful to kick off this question with a good example.
5
80
We shou ld also think a bout the ordering of items in a binary sea rch tree. Given a node, all nodes on its left must be less than all nodes on its rig ht. Once we reach a place without a node, we i nsert the new value there. What this means is that the very first element in our a rray must have been a 50 in order to c reate the a bove tree. If it were anything else, then that va lue wou ld have been the root instead. What else can we say? Some people jump to the conclusion that everything on the left must have been inserted before elements on the rig ht, but that's not actually true. In fact, the reverse is true: the order of the left or right items doesn't matter. Once the 50 is inserted, a l l items less than 50 wil l be routed to the left and a l l items g reater than routed to the right. The 60 or the 20 cou ld be inserted fi rst, and it wou ld n't matter.
50
wil l be
Let's think a bout this problem recu rsively. If we had all a rrays that could have created the su btree rooted at 20 (ca l l this a r ray5et20), and all a rrays that cou ld have created the su btree rooted at 60 (cal l this a r ray5 et60), how would that give us the fu l l answer? We cou ldjust"weave"each a rray from a rray5et20 with each array from a r ray5et 60-a nd then prepend each a rray with a 50.
262
Cracki ng the Cod in g I nterview, 6th Ed ition
Solutions to Chapter 4
I Trees and Graphs
Here's what we mean by weaving. We are merging two arrays in a l l possi b le ways, while keeping the elements within each array in the same relative order. arrayl : { 1 , a r ray2 : { 3 , weaved : { l , {3,
2} 4} 2, 3, 4 } , { 1 , 3 , 2, 4} , { 1 , 3, 4 , 2 } , 1 , 2, 4 } , { 3 , 1 , 4 , 2 } , { 3 , 4 , 1 , 2 }
Note that, a s long as there aren't any duplicates in the original array sets, we won 't have t o worry that weaving will create duplicates. The last piece to tal k about here is how the weaving works. Let's think recursively a bout how to weave { 1 , 2 , 3 } and { 4 , 5 , 6 } . What are the subproblems? Pre pend a 1 to all weaves of { 2, 3} and { 4, S, 6 } . Prepend a 4 to all weaves of { l , 2 , 3 } and { 5 , 6 } . To implement this, we'll store each as linked lists. This wi ll make i t easy t o a d d a n d remove elements. When we recurse, we' l l push the prefixed elements down the recursion. When f i r s t or s e c ond are empty, we add the remainder to prefix and store the resu lt.
It works something l i ke this: weave (fi rst , s e c o nd ,
p r ef i x ) : weave ( { l , 2 } , { 3 , 4 } , { } ) weave ( { 2 } , { 3 , 4 } , { 1 } ) weave ( { } , { 3 , 4} , { 1 , 2 } ) { 1 , 2, 3 , 4} weave ( { 2 } , { 4} , { 1 , 3} ) weave ( { } , { 4 } , { 1 , 3 , { 1 , 3 , 2 , 4} weave ( { 2 } , { } , { 1 , 3, { 1 , 3 , 4, 2 } weave ( { l , 2 } , { 4} , { 3 } ) weave ( { 2 } , {4}, { 3 , l } ) w eave ( { } , { 4 } , { 3 , 1 , { 3 , 1 , 2 , 4} weave ( { 2 } , { } , { 3 , 1 , { 3 , 1 , 4, 2 } weave ( { l , 2 } , { } , { 3 , 4} ) { 3, 4, 1 , 2 }
2} ) 4} )
2} ) 4} )
Now, let's t h i n k through t h e implementation of removi ng, say, 1 from { 1 , 2 } a n d recu rsing. We need t o be carefu l about mod ifying this list, si nce a later recursive ca ll (e.g., weave ( { 1 , 2 } , { 4 } , { 3} ) ) might need the l sti l l in { l , 2 } . We could clone the list when w e recurse, s o that w e only modify the recursive calls. Or, we could modify the list, but then "revert"the changes after we're done with recursing. We've chosen to implement it the latter way. Since we're keeping the same reference to first s e c ond, and prefix the enti re way down the recursive call stack, then we'll need to clone prefix just before we store the com plete resu lt. 1
2 3 4 5
6
Array L i s t < Li nked L i s t < Integer>>
a l lSequence s ( TreeNode node ) { ArrayLi s t < L i nked L i st < I ntege r > > result = new ArrayList< Li nked L i s t < I ntege r > > ( ) ; if ( node == n u l l ) { result . add ( new Li nked l i s t < I nteger> ( ) ) ; retu r n result ;
CrackingTheCodinglnterview.com I 6th Edition
263
Solutions to Chapter 4 7
l Trees and Graphs
}
8 9
Li nked list < I nteger> prefix prefix . add ( node . data ) ;
10 11
/ * Recurse on left a nd right s ubtrees . * / ArrayList < L inked L i s t < I ntege r > > leftSeq = a llSequences ( node . left ) ; ArrayList < L inked L i s t < I ntege r > > rightSeq = a llSequences ( node . right ) ;
12 13
14 15
15
/ * Weave together each list from the left and right sides . * / for ( Li n ked List < I nteger> left : leftSeq ) { for ( L i nked list< I nteger> right : rightSeq ) { Array l i s t < L inked l i s t < I ntege r > > weaved = new ArrayList < Li nked L i s t < I ntege r > > ( ) ; weavelist s ( left , right , weaved, prefix ) ; result . addAll (weaved ) ; } } ret urn res ult ;
17 18 19 20 21
22
23
24 25
26
27 28
29
30
31 32
33
34
35 36
37
38 39
40
41 42
}
/ * weave lists together in a l l pos s i ble ways . This a lgorithm works by removing the * head from one list , recursing, a nd t h e n doing the same t h i ng with the other * list . */ void weavelist s ( L i n ked list< Integer> first , Linked l i s t < I nteger> second , ArrayList< Li nked L i s t < I ntege r > > res ult s , L inked List< Intege r> prefi x) { / * One l i s t i s empty . Add rema inder to [ a cloned ] prefix a nd store result . * / if (first . s i ze ( ) = = 0 1 1 second . s i ze ( ) = = 0 ) { Li nked list < I ntege r> result = ( L inked l i st < I nteger> ) prefix . clone ( ) ; result . addAl l ( first ) ; result . addAll ( second ) ; res ult s . add ( result ) ; ret u r n ; } /* Recurse with head of f i r s t added t o the p refi x . Removing the head w i l l damage * first , so we ' l l need t o put it back where we found it afterwa rd s . */ int head f i rst first . remove First ( ) ; prefix . addLast ( headF irst ) ; weavelists ( f i r s t , second, res ult s , prefi x) ; prefix . removela st ( ) ; first . ad d First ( headFirst ) ;
43
44
=
45
46
47
48 49 50 51
/ * Do the same t h i ng wit h second , damaging and then restoring the list . * / int headSecond second . removeFirst ( ) ; prefix . addLast ( headSecond ) ; weaveList s ( first , second , res u lt s , prefix ) ; prefix . remove la st ( ) ; s e cond . ad d F i r s t ( headSecond ) ; =
52
53
54 55 56
new Li nked l i st < I nteger> ( ) ;
}
Some people struggle with this problem because there a re two different recu rsive a lgorithms that must be desig ned and implemented. They get confused with how the algorithms should interact with each other and they try to juggle both in their heads. If this sounds like you, try this: trust and focus. Trust that one method does the right thing when imple menting an independent method, and focus on the one thing that this independent method needs to do. 264
Cracking the Cod i n g I nterview, 6th E d iti o n
Solutions to Chapter 4
I Trees a nd Graphs
Look at weave l i s t s . l t h a s a specific job: to weave two lists together and return a list of a ll possible weaves. The existence of a l lSequences is irreleva nt. Focus on the task that weave Lists has to do and design this a lgorithm. As you're implementing a l l S e q u e n c e s (whether you do this before or after weave L i s t s), trust that weave L i st s will do the right thing. Don't concern yourself with the particulars of how weave L i s t s operates while im plementing something that is essentially independent. Focus o n what you're doing while you're doing it. In fact, this is good advice in genera l when you're confused during whiteboa rd cod ing. H ave a good under sta nding of what a particular function should do (nokay, this function is going to retu rn a list of ). You should verify that it's rea lly doing what you think. But when you're not dealing with that function, focus on the one you are dea ling with and trust that the others do the right thing. It's often too much to keep the implementations of m u ltiple algorith ms straight in your head. __"
4.1 0
Check Subtree: T l and T2 are two very large binary trees, with T l much bigger than T2. Create a n a lgorithm t o determ ine if T2 is a su btree of T l .
A tree T2 is a subtree of Tl i f there exists a node n i n Tl such that t h e subtree of n is identica l t o T2. That is, if you cut off the tree at node n, the two trees would be identica l. pg
117
SOLUTION --· · -- · · · ----- ------
In problems l i ke this, it's useful to attem pt to solve the problem assuming that there is just a small amount of data. This will give us a basic idea of an approach that might work. The Simple Approach
In this smaller, simpler problem, we could consider comparing string representations of traversa ls of each tree. lfT2 is a subtree of Tl, then Tl's traversal should be a su bstri ng ofTl. ls the reverse true? If so, should we use a n in-order traversa l or a pre-order traversal? An in-order traversal will definitely not work. After all, consider a scenario in which we were using binary search trees. A binary sea rch tree's in-order traversa l always prints out the va lues in sorted order. Therefore, two binary search trees with the same va lues will always have the same in-order traversals, even if their structu re is different. What about a pre-order traversal ? This is a bit more promising. At least in this case we know certai n thi ngs, like the first element in the pre-order traversa l is the root node. The left and right elements wi ll follow. Unfortunately, trees with different structu res could sti ll have the sa me pre-order traversal.
if) 0�
There's a simple fix thoug h. We can store N U LL nodes i n the pre-order traversa l string as a special character, like an 'X'. (We'l l assume that the binary trees contain only integers.) The left tree would have the traversa l { 3 , 4 , X} and the right tree will have the traversa l { 3 , X , 4 } . Observe that, a s long as we represent the N U LL nodes, the pre-order traversa l of a tree is unique. That is, if two trees have the same pre-order traversa l, then we know they are identical trees in va lues and structu re.
CrackingTheCodingl nterview.com I 6th Edition
265
Solutions to Chapter 4
I Trees and Graphs
To see this, consider reconstructing a tree from its pre-order traversal (with NULL nodes indicated). For exam ple: 1 , 2, 4, X, X, X, 3, X, X. The root is l , and its left node, 2, fo llows it. 2.left must be 4. 4 must have two NULL nodes (since it is followed by two Xs). 4 is complete, so we move back up to its parent, 2. 2.right is another X (NULL). l 's left su btree is now com plete, so we move to l 's right child. We place a 3 with two NULL children there. The tree is now complete.
Th is whole process was deterministic, as it will be on any other tree. A pre-order traversal always starts at the root and, from there, the path we take is entirely defined by the traversal. Therefo re, two trees a re iden tical if they have the same pre-order traversal. Now consider the su btree problem. If T2's pre-order traversal is a substri ng of Tl 's pre-order traversal, then T2's root element must be found in Tl . If we do a pre-order traversal from this element in Tl , we wil l fo llow an identical path to T2's traversal. Therefo re, T2 is a subtree of Tl . Implementing this is quite straightforward. We just need to construct and compare the pre-order traversals.
1
boolean contai n sTree ( T reeNode t l , TreeNode t 2 ) { St ringBuilder stringl new St ringB uilder ( ) ; St ringBuilder st ring2 = new St ringBuilder ( ) ;
2 3
4
5
getOrderSt ring ( t l , stringl ) ; getOrderSt ring (t2, st ring2 ) ;
6
7
8
return st ringl . i ndexOf ( st ring2 . toSt ring ( ) )
9
10
!=
}
-1;
11 void getOrderString ( TreeNode node, St ringBuilder sb) { if ( node = = nul l ) { 12 13 14
15
16 17 18
19 }
/ / Add null indicator
s b . a p pend ( "X " ) ; return ;
} s b . a p pend ( node . data + " ) ; / / Add root getOrderSt ring ( node . left , sb) ; / / Add left getOrderString( node . right , s b ) ; / / Add right "
This approach ta kes 0 ( n + m ) time and 0 ( n + m ) space, where n and m a re the number of nodes in Tl and T2, respectively. Given millions of nodes, we might want to reduce the space com plexity. The Alternative Approach
An alternative approach is to search through the larger tree, Tl. Each time a node in Tl matches the root of T2, ca l l mat c h T r ee. The mat c h Tree method will compare the two subtrees to see if they are identical. Ana lyzi ng the ru ntime is somewhat com plex. A naive answer would be to say that it is 0( nm) time, where n is the number of nodes in T1 and m is the nu mber of nodes in T2. Wh ile this is tech nica l ly correct, a little more thought can prod uce a tighter bound. 266
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 4
I Trees and Graphs
We d o not actually call mat c h Tree on every node i n T l . Rat her, w e call it k ti mes, where k i s the number of occu rrences of T 2's root in Tl. The runtime is closer to O ( n + km ) . I n fact, even that overstates the runtime. Even if the root were identica l, we exit mat c hTree when we fi nd a difference between Tl and T2. We therefore probably do not actually look at m nodes on each call of match Tree. The code below implements this a lgorithm. 1 2 3
4 5
boolean co ntai nsTree ( TreeNode t l , TreeNode t 2 ) { if ( t 2 null ) return true ; / / The empty t ree is a lways a s ubtree retu rn s ubTree ( t l , t 2 ) ; } ==
6
boolean subTree ( T reeNode r l , TreeNode r 2 ) { if ( rl == null ) { 8 return fa l s e ; / / big tree empty & s u btree still not found . 9 } else if ( rl . data == r2 . data && mat chTree ( rl, r2) ) { 10 ret u r n t r u e ; 11 } 12 ret u r n s u bTree ( r l . left , r2) I I s u bTree ( r l . right , r2 ) ; 13 }
7
14 15
boolean matchTree ( TreeNode r l , TreeNode r 2 ) { if ( rl == n ull && r2 == n u l l ) { ret u r n true; / / not hing left i n t he s u btree } el s e if ( r l == null I I r2 == n u l l ) { ret u r n false; // exactly t ree is empty, t herefore trees don ' t match 20 } else if ( rl . data ! = r2 . data ) { 21 ret u r n false; / / data does n ' t match 22 } else { 23 ret u r n matchTree ( rl . left , r2 . left ) && matchTree ( rl . right , r2 . right ) ; 24 } 25 } 16 17 18 19
When might the simple solution be better, and when might the a lternative approach be better? This is a great conversation to have with your interviewer. Here are a few thoughts on that matter: + m ) memory. The a lternative solution takes O( log ( n ) memory. Remember: memory usage can be a very big deal when it comes to sca lability.
1 . The simple solution ta kes O ( n
+
log ( m ) )
2. The simple solution is O ( n + m ) time and the a lternative solution has a worst case time of O ( nm ) . However, the worst case time ca n be deceivi ng; we need to look deeper than that. slightly tighter bound on the runtime, as explai ned earlier, is O ( n + km ) , where k is the number of occu rrences of T2's root in Tl. Let's suppose the node data for Tl and T2 were ra ndom numbers picked between 0 and p. The va lue of k wou ld be approximately � . Why? Because each of n nodes i n Tl has a � chance of equaling the root, so approximately y;; nodes in Tl should eq ual T2 . root. So, let's say p 1000, n = 1000000 and m = 100. We would do somewhere around 1 , 1 00,000 node checks
3. A
=
( 1 100000
=
1000000
+
100 ·1���0000 ).
4. More complex mathematics and assu m ptions could get us an even tighter bound. We assumed in #3 a bove that if we call mat c h Tree, we would end up traversing all m nodes of T2. It's far more likely, though, that we will find a difference very early on in the tree and wi ll then exit early. In summary, the alternative approach is certainly more optimal i n terms of space and is likely more optimal in terms of time as well. It all depends on what assumptions you make and whether you prioritize reducing
CrackingTheCodinglnterview.com I 6th Edition
267
Solutions to Chapter 4
I Trees and Graphs
the average case runtime at the expense of the worst case runtime. This is an excellent point to make to your interviewer. 4.1 1
Random Node: You are im plementing a binary search tree class from scratch, which, in addition to in s e rt, find, and d e l e t e, has a method getRandomNode ( ) which returns a random node from the tree. All nodes should be equally l i kely to be chosen. Design and implement an a lgorithm for getRandomNode, and explain how you wou ld implement the rest of the methods. pg 1 1 1
SOLUTION
Let's d raw an example.
3
We're going to explore many solutions until we get to a n opti mal one that works. One thing we should realize here is that the question was phrased in a very interesting way. The interviewer did not simply say, "Design an algorithm to retu rn a random node from a binary tree:'We were told that this is a class that we're building from scratch. There is a reason the question was phrased that way. We proba bly need access to some part of the internals of the data structure. Option #1 [Slow & Working]
One solution is to copy all the nodes to an array and return a random element in the array. This sol ution will ta ke O ( N ) time and O ( N ) space, where N is the number of nodes in the tree. We can guess our interviewer is probably looking for something more optimal, since this is a little too straig htforward (and should make us wonder why the interviewer gave us a binary tree, since we don't need that information). We should keep in mind as we develop this solution that we probably need to know something about the internals of the tree. Otherwise, the q uestion proba bly would n't specify that we're developing the tree class from scratc h. Option #2 [Slow & Working]
Returning to our original solution of copyi ng the nodes to an a rray, we can explore a solution where we mainta in an a rray at a l l times that lists all the nodes in the tree. The problem is that we'll need to remove nodes from this array as we delete them from the tree, and that will take 0 ( N ) time. Option #3 [Slow & Working)
We could label all the nodes with an index from 1 to N and label them in binary search tree order (that is, according to its inorder traversal). Then, when we call getRa ndomNode, we generate a random index between 1 and N. If we apply the label correctly, we can use a binary search tree search to find this index.
268
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 4
J Trees and Graphs
However, this leads to a similar issue as earlier solutions. When we insert a node or a delete a node, all of the indices might need to be u pdated.This can ta ke O ( N ) time. Option #4 [Fast & Not Working]
What if we knew the depth of the tree? (Since we're building our own class, we ca n ensure that we know this. It's an easy enough piece of data to track.) We cou ld pick a random depth, and then traverse left/right ra ndomly until we go to that depth. This wouldn't actually ensure that a l l nodes are equally likely to be chosen though. First, the tree doesn't necessa rily have an equal number of nodes at each level. This means that nodes on levels with fewer nodes might be more likely to be chosen than nodes on a level with more nodes. Second, the random path we ta ke might end up terminating before we get to the desired level. Then what? We cou ld just retu rn the last node we find, but that would mean unequal proba bilities at each node. Option #5 [Fast & Not Working]
We could try just a simple approach: traverse randomly down the tree. At each node: •
With X odds, we retu rn the cu rrent node.
•
With X odds, we traverse left. With X odds, we traverse rig ht.
This solution, like some of the others, does not distribute the proba bilities evenly across the nodes. The root has a X proba bility of being selected-the same as a l l the nodes in the left put together. Option #6 [Fast & Working]
Rather than just continuing to brainstorm new solutions, let's see if we ca n fix some of the issues in the previous solutions. To do so, we must diagnose-deeply-the root problem in a solution. Let's look at Option #5. It fa ils beca use the proba bilities a ren't evenly d istri buted across the options. Can we fix that while keeping the basic a lgorithm the same? We can start with the root. With what probability should we retu rn the root? Since we have N nodes, we must retu rn the root node with X probabil ity. (In fact, we must return each node with X proba bility. After all, we have N nodes and each must have equal proba bility. The tota l must be 1 (1 00%), therefore each must have X probability.) We've resolved the issue with the root. Now what a bout the rest of the problem? With what probabil ity should we traverse left versus right? It's not 50/50. Even in a balanced tree, the number of nodes on each side might not be equal. If we have more nodes on the left than the right, then we need to go left more often. One way to think a bout it is that the odds of picking something-anything-from the left must be the sum of each individual probability. Si nce each node must have proba bil ity X , the odds of picking something from the left must have probability L E FT_S I Z E * X . This shou ld therefore be the odds of going left. Likewise, the odds of going right should be RIGHT_SI Z E * X . This means that each node m u st know the size of the nodes on the left and the size of the nodes on the rig ht. Fortunately, our interviewer has told us that we're building this tree class from scratch. It's easy to keep track of this size information on inserts and de letes. We ca n just store a s i z e va riable in each node. I ncrement s i z e on inserts and decrement it on deletes.
CrackingTheCodingl nterview.com I 6th Edition
269
Solutions to Chapter 4 1
I Trees and Graphs
class TreeNode { private int dat a ; public TreeNode left ; public TreeNode right ; private int size = 0 ;
2
3
4
5
6
7
public TreeNod e ( int d ) { data = d ; s i ze = 1 ; }
8
9
10 11 12 13
public TreeNode getRa ndomNode ( ) { int leftSize = left = = n u l l ? 0 left . s i ze ( ) ; Ra ndom random = new Random ( ) ; i n t index = random . next l nt ( s i ze ) ; if ( index < left S i z e ) { ret u r n left . getRa ndomNode ( ) ; } e l s e i f ( i ndex = = left S i z e ) { return t h i s ; } else { return right . get Ra ndomNod e ( ) ; } }
14
15
16
17
18 19
20 21
22
23
24 25
public void insertinOrde r ( int d) { if (d dat a ) { ret urn right ! = null ? right . fi nd ( d ) : null ;
46
47
48 49
50 51
52 53 54
55
}
}
}
ret urn n u l l ;
I n a balanced tree, this algorithm will be 0 ( log N ) , where N is the number of nodes. 270
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 4
I Trees and Graphs
Option # 7 [Fast & Working]
Random number calls can be expensive. If we'd l i ke, we can reduce the number of random number calls su bstantially. Imagine we called getRa ndomNode on the tree below, and then traversed left.
3
We traversed left because we picked a number between 0 and 5 (inclusive). When we traverse left, we aga in pick a ra ndom number between 0 and 5 . Why re-pick? The first number will work just fine. But what if we went right instead? We have a number between 7 and 8 (inclusive) but we would need a number between O and 1 (inclusive). That's easy to fix: just su btract out L E FT_S I Z E + 1 . Another way to think a bout what we're doing is that the initial random number ca ll ind icates which node (i) to return, and then we're locating the ith node in an i n-order traversal. Subtracting L E FT_S IZE + 1 from i reflects that, when we go right, we skip over L E FT_S IZE + 1 nodes i n the i n-order traversal. 1 c l a s s Tree { 2 TreeNode root = n u l l ;
3 4 s 6
public int s i z e ( ) { ret u r n root
8 9
10
11
12
}
13
14
15
16
17
18 19
22 23
24
25
26
27
28
29
30 31
32
root s i z e ( ) ; } .
public TreeNode get R a n d omNo d e ( ) { if ( root == n u l l ) ret u r n n u l l ;
7
20 21
null ? 0
}
Random random = new Random ( ) ; int i = random . next int ( s i ze ( ) ) ; ret u r n root . getithNode ( i ) ;
public void i n sert l nOrde r ( int va lue ) { i f ( root = = n u l l ) { root = new TreeNode (value ) ; } else { root . i n s ertinO r d er ( va lu e ) ; } }
class TreeNode { / * c on s tr u c tor a n d variables are the same . * / public TreeNode get it hNode ( int i ) { int leftSize l e ft = = n u ll ? 0 : left . size ( ) ; if ( i < l ef t S iz e ) { ret u r n left . get ithNode ( i ) ; } else if (i == leftSize ) { ret u r n t h i s ; } e ls e { =
CrackingTheCodinglnterview.com I 6th Edition
271
Solutions to Cha pter 4 33
I Trees and G raphs
+ 1 nod e s , s o s u bt ract them . * / ret urn right . getithNod e ( i - ( l eftSize + 1 ) ) ;
/ * Skipping ove r leftSi ze
34 35
}
36
}
39
public void insertlnOrder (int d) { /* same */ } p u b l i c int s i ze ( ) { return s i z e ; } public TreeNode find ( i nt d ) { / * same * / }
37 38
40
41
}
Like the previous a lgorithm, this a lgorithm takes O ( log N ) time in a balanced tree. We ca n also describe the ru ntime as O ( D ) , where D is the max depth of the tree. Note that O ( D ) is an accu rate descri ption of the runtime whether the tree is bala nced or not. 4.1 2
Paths with Sum: You a re given a binary tree in which each node conta ins a n integer va lue (which
might be positive or negative). Design an algorithm to cou nt the nu mber of paths that sum to a given va lue. The path does not need to sta rt or end at the root or a leaf, but it must go downwards (traveling only from parent nodes to child nodes). pg 1 1 1
SOLUTION
Let's pick a potential sum-say, 8-and then d raw a binary tree based on th is. This tree intentionally has a number of paths with this sum.
3
One option is the brute force approach. Solution #1 : Brute Force
I n the brute force approach, we just look at all possible paths. lo do this, we traverse to each node. At each node, we recu rsively try all paths downwa rds, tracking the sum as we go. As soon as we hit our ta rget sum, we i ncrement the total. 1 2 3
int countPathsWithSum ( TreeNode root, int ta rgetSum ) { if ( root == null ) return 0;
4
/ * Count paths with sum starting from t he root . * / int paths F romRoot = countPath sWithSumF romNod e ( root , ta rgetSum, 0) ;
5 6
7
/ * Try the nodes on the left and right . * / int path sOn Left = countPathsWithSum ( root . left , targetSum ) ; int pathsOnRight = countPathsWithSum ( root . right , targetSum ) ;
8
9 10 11 12
13
14
ret urn pat h s F romRoot }
+
pathsOnleft
+
pathsOnRight ;
/ * Ret urns the number of paths with this sum starting from this node . */
272
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 4 15
16
i n t co u ntPathsWith S u mF r o mNod e ( Tr ee Nod e
if ( node
17 18 19 20 21
n ul l ) ret urn 0 ;
node, int t a r getS um
,
i nt
c urr en t Sum) {
currentSum += node . data ; int totalPaths = 0; if ( c urrentSum == ta rgetSum) { / / Found a path from the root totalPaths++; }
22
23
24
totalPaths += count PathsWit hSumFromNod e ( node . left , ta rgetSum, cu rrentSum ) ; totalPaths += countPat hsWit hSumFromNode ( node . right , ta rgetSum, curre ntSum ) ; ret urn totalPat h s ;
25 26
27 28
==
I Trees and Graphs
}
What is t h e time com plexity o f this a lgorithm? Consider that node at depth d will be "touched" (via countPathsWithSumF romNode) by d nodes a bove it. In a balanced binary tree, d wi ll be no more than approximately log N. Therefo re, we know that with N nodes in the tree, countPathsWith SumF romNode will be called O ( N log N ) times. The runtime i s O(N log N). We can also approach this from the other direction. At the root node, we traverse to all N - 1 nodes beneath 3 it (via countPath s Wi thSumF romNode). At the second level (where there are two nodes), we traverse to N nodes. At the third level (where there are four nodes, plus three above those), we traverse to N 7 nodes. Fol lowing this pattern, the total work is rough ly: -
-
( N - 1) + ( N - 3 ) + (N
-
7)
+
(N
-
15 )
+
( N - 31 )
+
. . .
+
(N - N )
To simplify this, notice that the left side of each term i s always N and the right side i s one less than a power of two. The n u m ber of terms is the depth of the tree, which is O ( log N ) . For the rig ht side, we can ignore the fact that it's one less than a power of two. Therefo re, we really have this: O ( N * [ n umber of t erms ] - [ s um of powers of two from 1 t hrough N ] ) O ( N log N - N ) O ( N log N )
I f the va lue of the sum of powers of two from 1 through N isn't obvious to you, think a bout what the powers of two look like i n binary: 0001 + 0010 + 0100 + 1000 1111 :=
Therefore, the ru ntime is O ( N log N) in a balanced tree. In an unbala nced tree, the runtime could be much worse. Consider a tree that is just a straight line down. At the root. we traverse to N 1 nodes. At the next level (with just a single node), we traverse to N 2 nodes. At the third level, we traverse to N 3 nodes, and so on. This leads us to the sum of numbers between 1 and N, which is O ( N2 ) . -
-
-
Solution #2: Optimized
In ana lyzing the last solution, we may realize that we repeat some work. For a path such as 1 0 - > 5 - > 3 - > - 2, we traverse this path (or parts of it) repeatedly. We do it when we start with node 1 0, then when we go to node 5 (looking at 5, then 3, then -2), then when we go to node 3, and then finally when we go to node -2. Ideally, we'd like to reuse this work.
Cracki ngTheCodinglnterview.com I
6th Edition
2 73
Solutions to Chapter 4
I Trees and Graphs
3
Let's isolate a given path and treat it as just a n array. Consider a (hypothetical, extended) path like: 10 - > 5 - >
1
-> 2 ->
-1
-1
->
- > 7 ->
1
-> 2
What we're rea lly saying then is: How many contiguous subsequences in this a rray sum to a ta rget sum such as 8? In other words, for each y, we're trying to find the x values below. (Or, more accu rately, the number of x values below.) t a rgets um
t
t s
I
A
\
t y
x
If each value knows its ru nning sum (the sum of values from s through itself), then we can find this pretty easi ly. We just need to leverage this simple equation: runn ingSum x = runni ngSumY - t a r getSum. We then look for the values of x where this is true. runn i n gSumy
1•r-•,_•m1\ ru n n i n gSum x t a r getSum
t
A
II"
'
s
t
I
A
x
�
t y
Si nce we're just looki ng for the number of paths, we ca n use a hash table. As we iterate through the a rray, build a hash table that maps from a runn ingSum to the number of times we've seen that sum. Then, for each y, look up ru n n i n gSumy - ta rgetSum in the hash table. The va lue in the hash table will tell you the number of paths with sum t a r getSum that end at y . For exa m ple: 0 5 1 2 6 3 4 8 7 i ndex : va lue : 1 0 - > 5 - > 1 - > 2 - > - 1 - > - 1 - > 7 - > 1 - > 2 s um :
10
15
16
18
17
16
23
24
26
The va lue of r u n n i n gS u m7 is 24. lf t a r getSum is 8, then we'd look up 1 6 in the hash table. This wou ld have a value of 2 (originating from index 2 and index 5). As we can see above, indexes 3 through 7 and indexes 6 through 7 have sums of 8. Now that we've settled the a lgorithm for an a rray, let's review this on a tree. We ta ke a simi lar approach. We traverse through the tree using depth-fi rst sea rch. As we visit each node:
274
Cracking the Coding I nterview, 6th Edition
Solutions to Chapter 4 1.
2. 3.
I Trees a n d Graphs
Track its r u n n i ngSum. We'll take this i n a s a parameter and immediately increment i t by node . value. -
Look u p runni ngSum ta rgetSum in the hash table. The va lue there indicates the total num ber. set tot a l P a t h s to this value. If r u n n i ngSum tot a l P a t h s .
==
ta rgetSum, then there's one additional path that starts at the root. Increment
4 . Add r u n n i ngSum to the hash table (incrementing the value i f it's already there).
5. Recurse left and rig ht, counting the number of paths with sum ta rgets um. 6. After we're done recursing left and right, decrement the value of runn ingSum i n the hash table. This is
essentially backing out of our work; it reverses the changes to the hash table so that other nodes don't use it (since we're now done with node). Despite the com plexity of deriving this algorithm, the code to i mplement this is relatively simple. 1 2
3 4 5
6
7
8
int countPathsWithSum ( T reeNode root , int ta rgetSum) { retu rn countPat hsWit hSum ( root , ta rgetSum, 0 , new HashMap ( ) ) ; }
int count PathsWithSum (TreeNode node, int ta rgetSum, int runningSum, HashMa p < I nt eger, Integer> pathCou nt ) { if ( node == n u l l ) return 0 ; / / Base case
9
/ * Count pat hs with s um ending at the current node . * / runningSum += node . data ; int s um runningSum - ta rgetSum; int totalPaths = pat hCount . getOrDefault ( sum, 0 ) ;
10
11
=
12 13 14 15 1' 17
/* If runningSum equals ta rgetSum, t hen one additional path start s at root . * Add in t h i s path . * / if ( runningSum = = ta rgetSum ) { tota lPat hs++ ;
18
}
19 2e 21
/* I n c rement pat hCou nt, recurse, then dec rement pat hCount . * / i n c rementHas hTa ble ( pat hCount , ru n n i n gsu m, 1 ) ; / / Inc rement pat hCount totalPat hs += count PathsWit hSum ( node . left , ta rgetSum, runningSum, pat hCount ) ; totalPaths + = countPathsWithSum ( node . right , ta rgetSum, runningSum, pathCou nt ) ; inc rementHas hTable ( pat hCount , runningSum, - 1 ) ; // Decrement pat hCount
22
23
24 25
26 ret u rn totalPath s ; 27 } 28 29 void inc rementHa s hTable ( Ha s hMa p < I nteger, Integer> hashTable, int key, int delt a ) { int newCount = hashTable . getOrDefau lt ( key, 0 ) + delt a ; 30 31 i f ( newCount = = 0 ) { // Remove when zero t o reduce s pace usage 32 h a s hTable . remove ( key ) ; 33 } else { 34 h a s hTable . put ( key, newCount ) ; 35 } 36
}
The runtime for this algorithm is O ( N ) , where N is the number of nodes in the tree. We know it is O ( N ) because we travel to each node just once, doing 0 ( 1 ) work each time. I n a bala nced tree, the space com plexity is 0( log N ) due to the hash table. The space com plexity can grow to 0( n ) in an unbalanced tree.
CrackingTheCodinglnterview.com I 6th Ed iti on
275
5 Sol utions to Bit M a n i p u lation
5.1
Insertion: You a re given two 32-bit numbers, N and M, and two bit positions, i and j. Write a method to insert M into N such that M starts at bit j and ends at bit i. You can assume that the bits j through i have enough space to fit all of M. That is, if M 10011, you can assume that there are at least 5 bits between j and i. You wou ld not, for exa m ple, have j 3 and i 2, because M could not fu l ly fit between bit 3 and bit 2. =
=
=
EXAMPLE I nput:
=
N
10000000000 , M
10011 , i
2, j
6
1 0001001100
Output: N
pg 1 15 SOLUTION
This problem can be approached in three key steps: 1 . Cl ea r
the bits j t h ro u g h i in N
2. Sh ift M so that it li nes u p with bits j through i 3. Merge M and N. The trickiest part is Step 1 . How do we clear the bits in N? We can do this with a mask. This mask wi l l have a l l 1 s, except for O s in t h e bits j through i . W e create this mask b y creating the left half of the mask fi rst, and then the right ha lf. 1
int updateBit s ( int n , int m , i nt i , int j ) { I * Create a ma s k to clear bits i through j in n . EXAMPLE : i = 2, j = 4 . Result * should be 11100011 . For s implicity, we ' ll use j ust 8 bits for t he example . * I int allOnes = �0; II will equal sequence of all ls
2
3
4 ,.. ,)
II ls before position j , then 0s . left int left = a llOnes < < ( j + 1 ) ;
6
7 8
=
11100000
9
II l ' s after position i . right int right = ( ( 1 getAlternatingSequences ( int n ) { 11 Arraylist sequences = new Arraylist < I ntege r > ( ) ;
8
12
13
0 '·
int searchingFor int counter = 0;
14 15 16
for ( int i = 0; i < Intege r . BYTES * 8; i++ ) { if ( ( n & 1) ! = searchingFo r ) { sequences . add ( counter ) ; searchingFor = n & 1; // Flip 1 to 0 or 0 to 1 counter = 0 ; } counter++ ; n >>>= 1; } sequences . add ( counte r ) ;
17 18
19 20
21
22 23 24
25 26
27
ret urn sequences ;
28 }
29
30 31 32 33
34
/ * Given the le ngth s of alternating sequences of 0s and ls , find t he longest one * we can build . */ int find longestSequence (Array list < I nteger> seq ) { i n t maxSeq 1;
35
36
37
38
39
for ( int i 0; i < seq . s i ze ( ) ; i += 2) { int zeros Seq = seq . get ( i ) ; int onesSeqRight = i - 1 > = 0 ? seq . get ( i - 1 ) : 0 ; i n t onesSeq left = i + 1 < seq . s i ze ( ) ? seq . get ( i + 1 )
40 41 42
int thisSeq = 0; if ( zerosSeq 1) { / / can merge t hi s Seq onesSeq Left + 1 + onesSeqRight ; } if ( zerosSeq > 1 ) { // J ust add a zero to either s ide t h i s Seq = 1 + Mat h . ma x ( onesSeqRight, onesSeq Left ) ; } else if ( zerosSeq == 0 ) { / / No zero, but take either s ide t h i s Seq = Mat h . ma x ( onesSeqRight , onesSeqleft ) ; } maxSeq = Mat h . max ( t h i s Seq , maxSeq ) ; ==
=
43
44 45
46
47
48
49
50 51
52 }
Th is is
0 '·
}
ret u r n maxSeq; pretty good.
It's
0( b)
tim e a n d
0( b )
m e m o ry,
where b is the le ngth of the sequence.
CrackingTheCodinglnterview.corn j 6th Edition
279
Solutions to Cha pter 5
I
l Bit Manipulation
Be ca reful with how you express the ru ntime. For example, if you say the ru ntime is O ( n ) , what is n ? It is not correct to say that this algorithm is O(va lue of the integer). This algorithm is O(nu m ber of bits) . Forth is reason, when you have potential ambigu ity in what n might mean, it's best just to not use n. Then neither you nor you r interviewer will be confused. Pick a different varia ble name. We used "b'; for the n u m ber of bits. Someth ing logical works well.
Can we do better? Recall the concept of Best Conceiva ble Runtime. The B.C.R. for this algorithm is O( b ) (since we'll always have to read through the sequence), so we know we ca n't optimize the time. We can, however, reduce the memory usage. Optimal Algorithm
To reduce the space usage, note that we don't need to hang on to the length of each sequence the entire time. We only need it long enough to compare each 1 s sequence to the immediately preceding 1 s seq uence. Therefore, we can just wa l k through the i nteger doing this, tracking the cu rrent 1 s sequence length and the previous l s sequence length. When we see a zero, u pdate previou s L e n gt h : If the next bit is a 1 , p re v i ou s Length should be set to c u rrent Length. If the next bit is a 0 , then we can't merge these sequences together. So, set p re viou s Length to 0. Update max Lengt h as we go. int flipBit ( int a ) {
1 2
I * If a l l ls, this i s a l ready the longest seq ue nce . *I if (�a == 0) ret urn Integer . BYTES * 8;
3
4
int c u r rent length = 0 ; i n t p reviou s length = 0 ; i n t maxlength = 1 ; I I We ca n a lways have a seq uence o f at least one 1 while ( a ! = 0) { if ( ( a & 1 ) = = 1 ) { I I Current bit is a 1 current Length++; } else if ( ( a & 1) == 0 ) { II C u r rent bit is a 0 I * Update to 0 ( if next b i t i s 0 ) or cu rrent length ( if next b i t is 1 ) . * I previo us lengt h = ( a & 2 ) == 0 ? 0 : cu rrent length; currentlength = 0 ;
5
6 7 8
9
rn
11 12 13
14
15
}
16 17
maxlength a »>= 1;
18 19
20
=
Math . ma x ( p revio us Length + cu rrent length + 1 , maxlengt h ) ;
} return maxlengt h ;
}
The runtime o f this algorithm is still 0 ( b ) , but we use o n l y 0 ( 1 ) additional memory. 5.4
Next Number: Given a positive integer, print the next smallest and the next largest number that have the same number of 1 bits in their binary representation. pg 1 1 6
SOLUTION
There are a number of ways to approach this problem, including using brute force, using bit manipu lation, and using clever arithmetic. Note that the a rithmetic approach builds on the bit manipu lation approach. You'll want to understa nd the bit manipu lation approach before going on to the arithmetic one. 280
Cracking the Coding
Interview, 6th Edition
Solutions to Chapter 5
I
I Bit Man ipu lation
The term inology can be confusing for this p roblem. We'l l call getNext the bigger number and get P rev the smaller number.
The Brute Force Approach
An easy approach is simply brute force: cou nt the number of ls in n, and then increment (or decrement) until you find a number with the same number of ls. Easy-but not terri bly interesting. Can we do some thing a bit more optimal? Yes! Let's start with the code for getNext, and then move on to getPrev. Bit Manipu lation Approach for Get Next Number
If we think about what the next number should be, we can observe the following. Given the number 1 3948, the binary representation looks like:
We want to make this number bigger (but not too big). We a lso need to keep the same number of ones. Observation: Given a number n and two bit locations i and j , suppose we flip bit i from a 1 to a 0, and bit j from a 0 to a 1. If i > j, then n will have decreased. If i < j, then n will have increased. We know the following: 1 . If we fl ip a zero to a one, we must flip a one to a zero. 2. When we do that, the number wil l be bigger if and only if the zero-to-one bit was to the left of the one to-zero bit. 3. We want to make the number bigger, but not u n necessa rily bigger.Therefore, we need to flip the rig htmost zero which has ones on the right of it. To put this in a different way, we a re flipping the rig htmost non-trailing zero. That is, using the above examp le, the trailing zeros are in the 0th and 1st spot. The rightmost non-trailing zero is at bit 7. Let's ca l l this position p. Step
1 : Flip rightmost non-trailing zero
1
1
0
1
1
0
1
1
1
1
1
1
0
0
With this cha nge, we have increased the size of n. But, we a lso have one too many ones, and one too few zeros. We'll need to shrink the size of our number as much as possible while keeping that in mind. We can shrink the number by rea rranging all the bits to the right of bit p such that the 0s a re on the left and the ls a re on the rig ht. As we do this, we wa nt to replace one of the 1 s with a 0. A relatively easy way of doing this is to count how m a ny ones a re to the right of p, clear all the bits from 0 u ntil p, and then add back in c l - 1 ones. Let c l be the number of ones to the right of p and c0 be the number of zeros to the right of p. Let's wa l k through this with an example.
CrackingTheCodinglnterview.com I 6th Edition
281
Solutions to Chapter 5
I Bit Man ipu lation
Step 2: Clear bits to the right ofp. From before, c 0
=
2. c l
=
S. p
=
7.
To clear these
bits, we need to create a mask that is a sequence of ones, followed by p zeros. We can do this as fol lows: a 1 < < p ; II all zeros except for a 1 at pos itio n p . I I all z e ro s , f ollowed b y p ones . b = a - l; I I all one s , fol l owed b y p zeros . ma s k = -b; n = n & mas k ; I I clea r s rightmost p bits . =
O r, more co ncisely, we do: n &= - ( ( 1 < < p ) - 1 ) . -
Step 3: Add in c 1
1 ones.
1 To
1
0
1
1
0
1
0
0
0
1
1
1
1
1 ones on the right, we do the followi ng: insert c l a l ) ; I I 0s with a 1 a t position c l 1 « (cl 1 b = a - l I I 0 s with ls at pos itions 0 t h rough c l - 1 n = n I b• I I inserts ls at positions 0 t h rough c l 1 -
-
-
• J
-
J
Or, more concisely:
n I = ( 1 >=
!
=
0) ) {
1;
}
while ( ( c & 1 )
cl++; c
>>=
1) {
l;
}
I* E r ro r : if n 11 . . 1100 . . . 00, then there i s no bigger number with the same * number of ls . *I if ( c0 + c l = = 31 I I c0 + cl == 0 ) { ret u r n - 1 ; } ==
int p = c0 + c l ; II posit ion of r ightmost non - t railing zero n I = ( 1 < < p ) ; I I F l i p rightmost non - t railing zero n & - ( ( 1 > > 1 ) I ( ( x & 0x55555555) < < 1 ) ) ; 3
}
Note that we use the logical right sh ift, instead of the arithmetic right shift. This is because we want the sign bit to be filled with a zero. We've implemented the code a bove for 32-bit integers in Java. If you were working with 64-bit integers, you would need to change the mask. The log ic, however, would remain the same. S.8
Draw Line: A monochrome screen is stored as a single array of bytes, allowing eight consecutive
pixels to be stored i n one byte. The screen has width w, where w is d ivisible by B (that is, no byte will be split across rows). The height of the screen, of cou rse, can be derived from the length of the a rray and the width. Implement a fu nction that d raws a horizontal line from (x l , y) to (x2, y).
The method signatu re should look something like: d ra wLin e ( b y t e [ ] screen, int widt h , i nt xl , int x2 , int y ) pg 1 1 6 SOLUTION ----- ·-· -· ·- ··-· ··· ·-.. · ·
.
-··
-· ·-· ·-·-· ·- . .-· ·-
A na ive solution to the problem is stra ightforwa rd: iterate in a for loop from xl to x2, setting each pixel along the way. But that's hardly any fu n, is it? (Nor is it ve ry efficient.) A better solution is to recog nize that if x l and x2 a re far away from each other, several fu ll bytes will be contained between them. These full bytes can be set one at a time by doing sc reen [ byt e_pos ] 0x F F . The residual start and end of the line can be set using masks. 1 void d rawLine ( byte [ ] s creen, int widt h , int xl , int x2 , i nt y ) { i nt sta rt_offset = xl % 8 ; 2 i nt first_ful l_byte = xl I 8 ; 3 4 if ( sta rt_offset ! = 0 ) { 5 6 7
8
9
10
11
12 13 14 15 16 17 18 19
20 21
22 23 24
first_full_byte++ ;
} int end_offset = x2 % 8 ; i nt last_full_byte = x2 I 8 ; if ( end_offset ! = 7) { last_full_byte - - ; } II Set full bytes f o r ( i nt b = first_full_byte ; b > sta rt_offset ) ; b yt e e nd_m a s k = ( byte ) - ( 0x F F > > ( end_offset + 1 ) ) ; II Set start and end of l i ne if ( ( x1 I B ) ( x2 I 8 ) ) { I I x 1 and x2 are i n the s a m e b y t e ==
CrackingTheCodingl nterview.com
I 6th Editio n
287
Solutions to Chapter 5 25
byt e m a s k = ( byt e ) ( st a rt_m a s k & end_ma s k ) ; s c r e e n [ (width I 8 ) * y + ( xl I 8 ) ] I = ma s k ; } else { if ( s ta rt_offset != 0) { int byte_number = (widt h I 8) * y + f i rst_f u l l_byte s c reen [ byte_numbe r ] I = sta rt_ma s k ;
26
27
28 29 30 31
if ( e nd_offset ! = 7 ) { int byte_number = ( width I 8 ) * y + last_ful l_byt e s c reen [ byte_numbe r ] I = e nd_m a s k ;
33
34
35
37
-
1;
}
32
36
I Bit Manipu lation
}
}
+
1;
}
Be ca refu l on this problem; there are a lot of"gotchas" a nd special cases. For exa mple, you need to consider the case where xl and x2 are in the same byte. Only the most carefu l candidates ca n im plement this code bug-free.
288
Cracking the Coding Interview, 6th Edition
6 Solutions to Math a n d Log i c Puzzles
6.1
The Heavy Pill: You have 20 bottles of pills. 1 9 bottles have 1 .0 gram pil ls, but one has pills of weight
1 .1 grams. Given a scale that provides an exact measurement, how would you find the heavy bottle? You can only use the sca le once. pg 122
SOLUTION
Sometimes, tricky constra ints can be a clue. This is the case with the constraint that we can only use the scale once. Beca use we can only use the scale once, we know something interesting: we must weigh m u ltiple pills at the same time. I n fact, we know we must weigh pills from at least 1 9 bottles at the same time. Other wise, if we skipped two or more bottles entirely, how could we distinguish between those missed bottles? Remember that we only have one chance to use the sca le. So how can we weigh pills from more than one bottle and discover which bottle has the heavy pills? Let's suppose there were just two bottles, one of which had heavier pills. If we took one pill from each bottle, we would get a weight of 2.1 grams, but we wouldn't know which bottle contributed the extra 0.1 grams. We know we must treat the bottles differently somehow. If we took one pill from Bottle #1 and two pills from Bottle #2, what wou ld the sca le show? It depends. If Bottle #1 were the heavy bottle, we would get 3.1 grams. If Bottle #2 were the heavy bottle, we would get 3.2 grams. And that is the trick to this problem. We know the "expected" weight of a bunch of pills. The difference between the expected weight and the actua l weight wil l ind icate which bottle contri buted the heavier pills, p rovided we select a diffe rent number of pills from each bottle. We can generalize this to the fu l l solution: take one pill from Bottle #1 , two pills from Bottle #2, three pills from Bottle #3, and so on. Weigh this mix of pills. If all pills were one gram each, the scale would read 2 1 0 2 1 I 2 ::; 2 1 0). Any "overage" must come from the extra 0. 1 grams ( 1 + 2 + + 20 ::; 20 * gram pills. •
•
•
This form ula wil l tell you the bottle number:
we ight - 210 grams 0 . l grams
So, i f the set of pills weighed 2 1 1 .3 g rams, then Bottle # 1 3 would have t h e heavy pills.
CrackingTheCodinglnterview.com I 6th Edition
289
Solutions to Chapter 6
I Math and Logic Puzzles
Basketball: You have a basketbal l hoop and someone says that you can play one of two games.
6.2
Game 1 : You get one shot to make the hoop. Game 2 : You get three shots and you have to make two of three shots. If p is the proba bility of making a particular shot, for which values of p should you pick one game or the other? pg 1 23 SOLUTION
To solve this problem, we can apply straightforwa rd probability laws by com paring the probabilities of winning each game. Probability of winning Game 1 :
The probability of winning Game 1 is p, by definition. Probability of winning Game 2:
Let s ( k , n ) be the probability of making exactly k shots out of n. The probability of winning Game 2 is the proba bility of making exactly two shots out of three OR making a l l three shots. I n other words: P( winning ) = s ( 2 , 3 ) + s ( 3 , 3 ) The proba bility of making all three shots is: s ( 3 , 3 ) = p' The probability of making exactly two shots is: P ( making 1 and 2, a nd mis s i ng 3 ) +
P ( making 1 and 3 , and m i s s ing 2 ) P ( miss ing 1 , and making 2 and 3 ) p * p * (1 - p ) + p * (1 - p ) * p + (1 - p) * p * p 3 ( 1 - p ) p2 +
Adding these together, we get: p3 + 3 ( 1 - p) p2 p3 + 3p' - 3p3 3p 2 - 2 p3
Which game should you play?
You should play Game 1 if P ( Game 1 ) > P ( Game 2 ) : p 1
>
3p' - 2p3 • 3p - 2 p2 2p2 - 3p + 1 > 0 (2p - l ) ( p - 1 ) >
>
0
Both terms must be positive, or both m ust be negative. But we know p both terms must be negative. 2p - 1
2p < 1
p
floors ? - 1 : egg2 ; 29 } If we wa nt to generalize this code for more building sizes, then we can solve for x in: x(x+l
Yi
=
number of f loo rs
This wi ll i nvolve the quadratic formula. 6.9
Lockers: There a re 1 00 closed lockers in a hallway. A man begins by opening all 1 00 lockers. Next, he closes every second locker. Then, on his third pass, he toggles every third locker (closes it if it is open or opens it if it is closed) . This process continues for 1 00 passes, such that on each pass i , the m a n toggles every ith locker. After h i s 1 OOth pass i n t h e hallway, in which h e toggles only locker #1 00, how many lockers a re open?
1 00
pg 124 SOLUTION ·---- ·-
···-··- -··-··- -·-···· · ·- - ·-··· · · --·-· · · · ·-··-
--- -- · -- --·- ---·-· -· ··- ·· -···· ··- ·· -· ·- · · ·-· ·
We c a n ta ck le this p ro b lem by t hi n ki n g through what it means for a door to be togg led. This wi ll help us deduce which doors at the very end will be left opened.
CrackingTheCodinglnterview.com I 6th Edition
297
Solutions to Chapter 6
I Math and Logic Puzzles
Question: For which rounds is a door toggled (open or closed}?
A door n is toggled once for each factor of n, including itself and 1 . That is, door 1 5 is toggled on rounds 1 , 3, 5, and 1 5 . Question: When would a door be left open?
A door is left open if the number of factors (wh ich we w i l l call x) is odd. You can th ink a bout this by pairing factors off as an open and a close. If there's one remaining, the door will be open. Question: When would x be odd?
The value x is odd if n is a perfect sq uare. He re's why: p a i r n's factors by their complements. For example, if n is 36, the factors are (1 , 36), (2, 1 8), (3, 1 2), (4, 9), (6, 6). Note that (6, 6) only contributes one factor, thus giving n an odd number of factors. Question: How many perfect squares are there?
There a re 1 0 perfect sq uares. You cou ld count them ( 1 , 4, 9, 1 6, 25, 36, 49, 64, 8 1 , rea lize that you can take the num bers 1 through 10 and square them:
1 00), or you
could sim ply
1 *1 , 2 * 2 , 3 * 3 , . . . , 10*10 Therefore, there a re 6.1 0
10
lockers open at the end of this process.
Poison: You have 1 000 bottles of soda, and exactly one is poisoned. You have 1 0 test strips which can be used to detect poison. A single drop of poison will turn the test strip positive permanently. You can put any number of drops on a test strip at once and you can reuse a test strip as many times as you'd like (as long as the resu lts are negative). However, you can only ru n tests once per day and it takes seven days to return a resu lt. How wou ld you figure out the poisoned bottle in as few days as possible?
Fol low up: Write code to simulate your approach. pg 124 SOLUTION
Observe the wording of the problem. Why seven days? Why not have the results j ust return im med iately? The fact that there's such a lag between sta rting a test and reading the resu lts likely means that we'l l be doing something else in the meantime (running additional tests). Let's hold on to that thought, but start off with a simple approach just to wrap our heads a round the problem. Naive Approach (28
days)
A simple approach is to divide the bottles across the 1 0 test strips, fi rst in groups of 1 00. Then, we wait seven days. When the results come back, we look for a positive result across the test strips. We select the bottles associated with the positive test strip, "toss" (i.e., ignore) all the other bottles, and repeat the process. We perform this operation until there is only one bottle left i n the test set. 1 . Divide bnttlP� /\cross availa ble test strips, one d rop per test strip.
2 . After seven d ays, check the test strips for 3.
resu lts.
On the positive test strip: select the bottles associated with it into a new set of bottles. If this set size is 1 ,
298
Cracking the Coding I nterview, 6th Edition
Solutions to Chapter 6
I Math a n d Logic Puzzles
we have located the poisoned bottle. If it's greater than one, go to step 1 . To simulate th is. we'll build classes for Bottle a nd TestStrip that mirror the problem's fu nctionality.
1
2 3
4 5
class Bott le { private boolean poisoned private int id ; public public public public
6 7
8
9
10
=
false ;
Bott le ( int i d ) { t h i s . id id ; } int get!d ( ) { ret urn id ; } vo id setAs Poisoned ( ) { poisoned = true ; } boo lean i s Poisoned ( ) { ret urn poisoned ; } =
}
1 1 class TestStrip { 12 p u b l i c static int DAYS_FOR_RESULT = 7 ; 13 private Arraylist d rops ByDay new Arraylist > ( ) ; 14 15 private int id ; 16 17 public TestSt r i p ( int id ) { this . id id; } 18 public int get!d ( ) { ret u r n id; } 19 20
21 22 23
24 25
26 27
2i 29
30 31
32 33
34 35
36 37 32
39
/ * Re size list of days / d rops to be la rge e nough . */ private void sizeDropsForDay ( int day ) { while ( d rops ByDay . s i ze ( ) < = day) { d rops ByDay . add ( new Arraylist ( ) ) ; } }
/ * Add d rop from bott le on specific day . * / public void addDropOnDa y ( i nt day, Bott le bott le ) { s izeDrops ForDay(day ) ; Arraylist< Bott le> d rops = d rops ByDay . get ( d ay ) ; d rops . add ( bott le ) ; }
/ * Checks if any of the bott les in the set are poisoned . */ private boolean hasPoison (Arraylist < Bott l e > bott les ) { for ( Bott le b : bott les ) { if ( b . isPoisoned ( ) ) { ret u rn t rue ; }
40
}
ret urn fa lse ;
41
42
43
44
45 46
47 43
49
50 51
52
53
}
/ * Gets bott les used i n the test DAYS_FOR_RESU LT days ago . * / public Array l i st < Bott l e > get la stWeeks Bott le s ( int day ) { i f (day < DAYS_FOR_RESULT) { ret urn n u ll ; } ret urn d rops ByDay . get (day - DAYS_FOR_RESULT ) ; }
/ * Checks for poisoned bottles s i nce before DAYS_FOR_RESULT */ public boolean is Pos it iveOnDay ( int day) {
CrackingTheCodingl nterview.com I 6th Edition
299
Solution s to Cha pter 6
l Math and Logic Puzzles
int testDay = day - DAYS_FOR_RESULT; if (testDay < 0 I I testDay > = d rops ByDay . s i ze ( ) ) { ret urn fa l s e ;
54 55 56 57 58 59
}
fo r ( i nt d = 0 ; d bottles = drops ByDay . get ( d ) ; if ( ha s Poison ( bottles ) ) { ret u r n true ;
60
61 62
}
63 64
}
return fa l s e ;
65
}
66
}
This i s just one way o f simulating the behavior o f the bottles and test strips, and each h a s its pros and cons. With this i nfrastructure built, we can now implement code to test our approach. 1
int findPoisonedBott le (ArrayLi s t < Bottle> bott les , ArrayList strips ) { int today = 0 ;
2
3 4 5
while ( bottles . s i ze ( ) > 1 && strips . s i ze ( ) > 0) { / * Run test s . */ runTestSet ( bott les , strips , today ) ;
6 7 8
/ * Wa it for results . */ today += TestStrip . DAYS_FOR_RESULT;
9
10 11
/ * Check res u lt s . * / fo r (TestSt rip strip : strips ) { if ( strip . i s Posit iveOnDay (today) ) { bottles = strip . get LastWeeks Bott les (today ) ; strips . remove ( st r ip ) ; break ;
12
13
14 15 16 17
}
18
}
19
}
21
if ( bottles . s i ze ( ) == 1) { ret urn bottles . get ( 0 ) . getid ( ) ;
24
ret urn - 1 ;
20 22 23
25 29
}
}
27
/ * Distri bute bottles a c ross test strips even ly . */ void runTestSet (Array l i s t < Bott le> bott les , Arraylist strips , int day) { int index = 0 ; 30 for ( Bottle bottle : bott les ) { 31 TestSt rip strip = strips . get ( index ) ; 32 strip . addDropOnDay (day, bottle ) ; 33 index = ( index + 1 ) % strips . s i ze ( ) ; 34 } 35 } 28 29
36
37
/* The complete code can be found i n t he downloadable code atta chment . */
Note that this a pproach makes the assumption that there wil l always be multiple test strips at each round. This assumption is valid for 1 000 bottles a n d 10 test stri ps.
300
I
Cracki n g the Cod i n g I nterview, 6th Edition
Solutions to Chapter 6
I Math a nd Logic Puzzles
If we can't assume th is, we can im plement a fa il-safe. If we have just one test strip remaini ng, we start doing one bottle at a time: test a bottle, wait a week, test another bottle. Th is approach will take at most 28 days. Optimized Approach
( 1 0 days)
As noted in the beginning of the solution, it might be more opti mal to run multiple tests at once. If we d ivide the bottles up
into 1 0 groups (with bottles 0 - 99 going to strip 0, bottles 1 00 - 1 99 going to strip 1 , bottles 200 - 299 going to strip 2, and so on), then day 7 wi ll reveal the first digit of the bottle number. A positive result on strip i at day 7 shows that the fi rst digit ( 1 OO's dig it) of the bottle number is i. Dividing the bottles in a different way can reveal the second or third digit. We j ust need to run these tests on different days so that we don't confuse the results. ..
Strip 0
nay·o -> 7 >
Oxx
'
· Oay: l
->
xOx
8
oaJ:-21 ->�9
·
xxO
Strip 1
1 xx
xlx
xxl
Strip 2
2xx
x2x
xx2
Strip 3
3xx
x3x
xx3
Stri p 4
4xx
x4x
xx4
Strip 5
Sxx
xSx
xxS
Strip 6
6xx
x6x
xx6
Stri p 7
lxx
xlx
xxl
Stri p 8
8xx
x8x
xx8
Stri p 9
9xx
x9x
xx9
For exam ple, if day 7 showed a positive resu lt on strip 4, day 8 showed a positive result on stri p 3, and day 9 showed a positive result on strip 8, then this would m a p to bottle #438. This mostly works, except for one edge case: what happens if the poisoned bottle has a duplicate d igit? For exam ple, bottle #882 or bottle #383. In fact, these cases are quite different. If day 8 doesn't have any"new" positive results, then we can conclude that digit 2 equals digit 1 . The bigger issue is what happens if day 9 doesn't have any new positive resu lts. In this case, all we know is that digit 3 equals either digit 1 or digit 2. We cou ld not distinguish between bottle #383 and bottle #388. They will both have the same pattern of test results. We will need to ru n one additional test. We could run this at the end to clear up am biguity, but we can also run it at day 3,just in case there's any ambiguity. All we need to do is shift the final digit so that it winds up in a different place than day 2's results. Strip 0
Oxx
xOx
xxO
xx9
Strip 1
1 xx
xlx
xxl
xxO
Strip 2
2xx
x2x
xx2
xxl
Strip 3
3xx
x3x
xx3
xx2
Strip 4
4xx
x4x
xx4
xx3
Strip 5
Sxx
xSx
xxS
xx4
CrackingTheCodingl nterview.com I 6th Edition
I
301
Solutions to Chapter 6
I Math and Logic Puzzles
6xx
Stri p 6
x6x
xx6
xxs xx6
Stri p 7
?xx
x7x
xx?
Stri p 8
8xx
x8x
xx8
xx?
Strip 9
9xx
x9x
xx9
xx8
Now, bottle #383 will see (Day 7 #3, Day 8 -> #8, Day 9 -> [NON E], Day 1 0 -> #4), while bottle #388 wi l l see (Day 7 #3, Day 8 -> #8, Day 9 -> [NONE], Day 1 0 -> #9). We can disti nguish between these by "reversing" the shifting on day 1 O's resu lts. =
=
What happens, though, if day 1 0 still does n't see any new resu lts? Could this happen? '
=
Actual ly, yes. Bottle #898 wou ld see (Day 7 #8, Day 8 -> #9, Day 9 -> [NONE], Day 1 0 -> [NONE]). That's okay, though. We j u st need to distinguish bottle #898 from #899. Bottle #899 will see (Day 7 #8, Day 8 -> #9, Day 9 -> [NONE], Day 10 -> #0). =
The "am biguous" bottles from day 9 will a lways map to different va lues on day 1 0. The logic is: If Day 3 -> 1 O's test revea ls a new test resu lt, "u nshift"this value to derive the third digit. Otherwise, we know that the third digit equals either the first digit or the second digit and that the third digit, when sh ifted, sti ll equals either the first digit or the second digit. Therefore, we j u st need to figure out whether the fi rst digit "shifts" into the second digit or the other way around. In the former ca se, the third digit equals the fi rst digit. In the latter ca se, the third digit equa l s the second digit.
•
Implementing this requ ires some carefu l work to prevent bugs.
1
int find Pois o nedBottle (Array list bottles , Array L ist strips) { if ( bottles . si ze ( ) > 1 000 I I strips . si ze ( ) < 10) ret urn - 1 ;
2 3
4
=
int t es t s 4; II three digits , plus one ex tra int nTestStrips = strips . size ( ) ;
5
6 7
I * Run t es t s . * I
8
=
'
H'J
11
12
13
14
15
15
17
18 19
20 21 22 23
24
'H_) L
26 27
28
302
for ( i nt day 0; day < test s ; day++ ) { runTestSet ( bottle s , strip s , day ) ; }
I * Get results . * I Has hSet< I nteger> previousResul t s = new HashSet < I nteger> ( ) ; int [ ] d igits = new int [ test s ] ; for ( int day 0 ; d ay < test s ; day++ ) { int resu ltDay = day + TestSt rip . DAYS_FOR_RESULT ; d igits [day] = getPositiveOnDay ( strips , resultDay , previousRes ult s) ; previous Results . ad d ( d igits [ d ay ] ) ; } =
I * If d ay l ' s res ults ma t c hed day 0 ' s , u pdate t he digit . * I if (d igits [ l ] == - 1 ) { d igit s [ l ] = d igits [ 0] ; }
/ * If day 2 ma t c hed day 0 or day 1 , check day 3 . Day 3 is the s ame as day 2, but * inc reme nted b y 1 . */ if ( d igit s [ 2 ] == - 1 ) { Cracking th e Coding Interview, 6t h E d iti o n
Solutions to Chapter 6 29
I Math and Logic Puzzles
if ( digit s [ 3 ] = = - 1 ) { /* Day 3 didn ' t give new result */ /* Digit 2 equals d igit 0 or d igit 1 . But , digit 2, when inc remented also * matches digit 0 or digit 1 . This mea n s that d igit 0 inc remented mat ches * digit 1 , or t h e other way a round . * / digits [ 2 ] ( ( digit s [ 0 ] + 1 ) % nTe stStrips ) d igits [ l ] ? digit s [ 0 ] : d igits [ l ] ; } else { digits [ 2 ] = ( d igit s [ 3 ] - 1 + nTestStrips ) % nTestStrips ; }
30 31 32
33
34
35 36 37
}
38
39 40
return digit s [0] * 100 + digits [ l ] * 10 + d igits [ 2 ] ;
41
}
43
/* Run set of tests for this day . */ void runTestSet (ArrayList bottles , Array List strips , int day) { if ( day > 3 ) retu r n ; / / only works for 3 days ( d igit s ) + one extra
42
44 45 46 47 4' 50
Sl
52
53 54
for ( Bott le bott le : bottles ) { int index getTestStripindexForDay ( bott le, day, strips . s i ze ( ) ) ; TestStrip testStrip = strips . get ( index ) ; testStrip . addDropOnDay ( day, bottle ) ; } =
48
}
/* Get strip that s hould be used on this bott le on t h i s day . */ int getTestStripindexForDay ( Bott le bott le, int day, int nTestStrips ) { 56 i nt id = bott le . getid ( ) ; switc h ( da y ) { 57 58 case 0: return id / 100 ; case 1 : return ( id % 100 ) I 10; 59 60 case 2 : ret u r n id % 10; 61 case 3 : return ( id % 10 + 1) % nTestStrips ; 62 default : return - 1 ; 63 } 64 } 55
65
/* Get results that are pos itive for a particular day, excluding prior results . */ int getPosit iveOnDay (ArrayList testStrips , int day, Has hSet < I ntege r > p reviousResult s ) { 68 69 for ( TestStrip testSt r i p : testStrips ) { 70 int id testStr ip . geti d ( ) ; 71 if (testStrip . is Posit iveOnDa y ( d a y ) && ! previous Results . contains ( id ) ) { return testStrip . getld ( ) ; 72 73 } 66
67
=
74
}
75 76
return - 1 ; }
It will take 1 0 days i n the worst case to get a result with this approach. Optimal Approach (7 days)
We can actually optimize this slightly more, to retu rn minimum number of days po ssi b l e .
a
result i n just seven d ays This is of course the .
CrackingTheCodinglnterview.com j 6th Edition
3 03
Solutions to Chapter 6
I Math and Log i c Puzzles
Notice what each test st rip really means. It's a binary indicator for poisoned or u npoisoned. Is it possible to map 1 000 keys to 1 0 binary values such that each key is mapped to a u nique configuration of values? Yes, of cou rse. This is what a binary n um ber is. We can ta ke each bottle number and look at its binary representation. If there's a 1 in the ith digit then we will add a d rop of this bottle's contents to test strip i. Observe that 210 is 1 024, so 1 O test strips will be enough to handle up to 1 024 bottles. We wait seven days, a nd then read the results. If test strip i is positive, then set bit i of the result value. Readi ng a l l the test strips wi ll give us the I D of the poisoned bottle. int findPoisonedBottle (Array L i s t < Bott le> bott les , ArrayList strips ) { runTests ( bott les , strips ) ; Array l i s t < I nteger> pos itive = getPosit iveOnDay ( strips , 7 ) ; return setBit s ( po s itive ) ; }
1 2 3
4 5 • 7
8
9
10
11
12
13 14 15
16 17
1 1!
/ * Add bottle content s to test strips */ void runTests (ArrayList< Bott le> bott l e s , Array List testStrips ) { for ( Bottle bottle : bottles ) { i n t i d = bott le . getid ( ) ; i n t bitindex 0; while ( id > 0 ) { i f ( ( id & 1 ) == 1 ) { testStrips . get ( bitindex) . addDropOnDa y ( 0, bottle ) ; } bitindex++ ; id >>= 1 ; } } } =
19 20 2.1 2 2 / * Get test strips that are positive on a part i c ular day . * / 2 3 Arraylist get Pos it iveOnDay (Array List testStrips , i n t day) { 24 ArrayLi st pos itive = new Array L i s t < I nteger > ( ) ; 25 for (TestSt r i p testStrip : testStrips ) { int id = testStrip . getid ( ) ; 26 27 if (testStrip . is Pos it iveOnDay ( d ay ) ) { positive . add ( id ) ; 28 29 } 3� } 31 return positive ; 32 } 33
34 / * C reate number by s etting bits wit h i n d ices s pecified in pos itive . * / 35 int setBits (ArrayL i s t < I nteger > positive) { int id = 0; 3• for ( I nteger bitindex : pos itive ) { 37 38 id I = 1 « bitindex;
39
40
-1 1
}
}
return i d ;
This approach will work as long as bottles.
304
21
>=
B, where T is the number of test strips and B is the number of
Cracking the Coding Interview, 6th Edition
7 Solutions to Obj ect-Oriented Desi g n
Deck of Cards: Design the data structures for a generic deck of cards. Expla in how you would subclass the data structures to implement blackjack.
7.1
pg 127
SOLUTION
First, we need to recognize that a "generic" deck of cards can mean many things. Generic could mean a sta ndard deck of cards that can play a poker-like game, or it could even stretch to U no or Baseball cards. It is i mportant to ask your interviewer what she means by generic. Let's assume that your interviewer clarifies that the deck is a sta ndard 52-card set, like you might see used in a blackjack or poker game. If so, the design might look like this: 1
2 3
4 5
6
7
8 9
10 11
12
public enum Suit { Club ( 0 ) , Di a m ond ( 1 ) , Heart ( 2 ) , Spade ( 3 ) ; private int value; private Suit ( int v ) { va l u e = v; } public int getVa l ue ( ) { re t u r n value; } public stat ic Suit getSuitF romVa l u e ( int value) { }
public void s etD ec kOfC a r d s (Arr ay L i s t < T > deckOfCards ) {
14
17
18
23
25
27 28 29
.
•
.
}
public void s huffle ( ) { . . . } public int rema i ningCa rd s ( ) { return ca r d s . s i z e ( ) - dealt lndex ;
15
16
24 25
}
public class Deck < T extends Card> { private Array list ca rd s ; / / all ca rds , dealt o r no t private int dealti ndex = 0; / / marks first undealt card
13
19 20 21 22
. . .
}
}
public T [ ] dealHan d ( int n umb e r) { public T dealCard ( ) { . . . }
public a b s t ract c l a s s C a rd { p rivate boolean available
=
...
}
t rue ;
/* number or face that ' s on ca r d - a n umb e r * Queen, 1 3 for K i n g , o r 1 f o r Ace */ p rotected int faceValue; p r ot e c t e d Suit s u i t ;
2
t h r o u g h 10, or 11 for
J ack,
CrackingTheCodingl nterview.com I 6th Edition
1 2 for
305
Solutions to Chapter 30 31
public Card ( int c , Suit s) { faceValue = c ; s uit = s ; }
32
33
34
35
36
public abstra ct int value ( ) ; public Suit suit ( ) { ret urn s uit ; }
37
38 39 40 41
43 }
45
46
47
/ * Checks if the card i s avai l a ble to be given out to s omeone * / p u b l i c boolean isAva i l a b le ( ) { ret urn available; } public void markUnavailable ( ) { ava i l a ble fa l s e ; } public void markAvailable ( ) { available = t r u e ; } =
42
44
7 J Object-Oriented Design
public c l a s s Hand { protected Array l i st cards = new Arraylist ( ) ;
48 49
50 51
52
53
54 55 56 57
58
public int score ( ) { int score = 0 ; for ( T card : c a rd s ) { score + = card . value ( ) ; } ret u r n score; } public void addCard ( T card ) { c a rds . add ( c a rd ) ; }
59 } I n the a bove code, we have im plemented Dec k with generics but restricted the type of T to Card . We have a lso implemented Ca rd as an abstract class, since methods like v a l ue ( ) don't make much sense without a specific game atta ched to them. (You could make a compelling argument that they should be implemented anyway, by defaulting to sta ndard poker rules.)
Now, let's say we're building a blackjack game, so we need to know the value of the cards. Fa ce cards are 1 0 and a n ace is 1 1 (most of the time, but that's the job of the Hand class, not the fol lowing class). 1 public c l a s s B l a c k J a ckHand extends Hand < Bl a c k J a c kCard> { 2 / * Th e r e a re multiple pos s ible scores for a blackjack hand, s ince aces h ave 3 * multiple values . Ret urn the highest pos s i ble s core t h at ' s under 21, or t h e 4 * lowest score that ' s over . * / 5 public int score ( ) { Array L i s t < l nteger> scores = pos s ibleScores ( ) ; 6 7 int maxUnder = I ntege r . MIN_VALUE ; 8 int minOver = I ntege r . MAX_VALU E ; 9 for ( int score : s cores ) { 10 if ( s core > 21 && score < minOve r ) { 11 minOver = s c o r e ; 12 } else if ( s core < = 2 1 && s c o r e > maxUnder) { 13 maxUnder s c o re ; 14 } 1S } ret urn maxUnder 16 I nteger . MIN_VALUE m inOve r maxUnder; 17 } =
18
306
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 7 I Object-Orie nted Design /* ret urn a list o f all pos s ible scores this hand could have (evaluating each * ace a s both 1 and 11 * / p r ivat e Array List pos s i b l escores ( ) { }
19
20
21 22
.
23
.
.
public boolean busted ( ) { return s core ( ) > 21 � } public boolean is21 ( ) { return s core ( ) 21; } public boolean isBlackJ a c k ( ) { }
24
==
25
.
.
.
26 } 27 2� public c l a s s BlackJ ackCard extend s Card { 29 public BlackJ a c kCard ( int c , Suit s ) { super ( c , s ) ; } public int value ( ) { 3� 31 if ( isAce ( ) ) return 1; 32 else if (faceVa lue >= 11 && fa ceVa lue = 11 && faceVa lue
51
52 53
}
> employee Level s ; / * queues for each call ' s rank * / List < List> callQueue s ;
20
public CallHandler ( ) { . .
22 23
/ * Get s the first availa ble employee who can handle this call . * / public Employee getHandler ForCal l ( Ca l l c a l l ) { . . . }
21
24
25 26
27
28
29
30
31 32
33 34 35 36
37
38
39
40
41 42
43
!;.4 45
308
.
}
/ * Routes the call to a n available employee , or saves i n a queue if no employee * is available . * / p u b l i c void dispatchCall ( Ca ller caller) { C a l l call n e w Call ( caller ) ; dispatchCa l l ( ca ll ) ; } =
/ * Routes the call to a n available employee, o r saves i n a queue if no employee * is available . */ public void dispatchCa l l ( Ca l l c a l l ) { / * Try to route the call to an employee with minimal ra n k . * / Employee emp getHandlerForCa l l ( ca ll ) ; if ( emp ! = n u l l ) { emp . receiveCa ll ( ca ll ) ; call . setHandle r ( emp ) ; } else { /* Place the c a l l i nto corresponding call queue a c cording to its r a nk . * / call . reply ( "Plea se wait f o r free employee t o reply" ) ; callQueues . get ( call . get Ra n k ( ) . getva l ue ( ) ) . add ( ca l l ) ; } } =
Cracking the Coding I nterview, 6th Edition
Solutions to Chapter 7 46
I *An employee got free . Look for a waiting call t hat employee can serve . Ret u r n * t rue if we a s s igned a c a l l , false otherwise . * / public boolean a s signCa l l ( Employee emp) { . . . }
47
48
49
50 } Call represents a call from a u s e r ca n ha ndle it.
.
1
2 3
4
call has a minimum ra n k and is assigned to the first employ ee who
I * Person who is calling . * / private Caller caller;
6 7 8
I * Employee who i s handling call . * / private Employee handler;
9
10 11
public Call (Caller c) { rank = Rank . Responder; caller = c ;
12 13
14
}
15 16
I * Set employee who is handling call . * / public void setHandle r ( Employee e) { handler
17 18
public public public public public
19
20 21
22 24
A
public class Call { I * Minimal rank of employee who can h a ndle t h i s call . * / private Rank rank;
5
23
I Object-Oriented Design
void Rank void Ran k void
e; }
reply ( String message) { . . . } getRank ( ) { return rank; } setRa n k ( Ra n k r ) { rank = r · } inc rementRa nk ( ) { . . . } d i s connect ( ) { . . . } J
}
Employee is a super class for the Dire ctor, Ma nage r, and R e s pondent classes. It is implemented as a n abstract class since there should b e n o reason to instantiate an Employee ty pe d i rectly 1
2 3
4 5
6 7 8
9
10 11
12
13
14 15
16
17
18 19 20 21
22
.
a bst ract class Employee { private Call currentCa ll protected Rank rank;
=
null; .
public Employe e ( C a l lHandler handler) {
.. }
I * St a rt t he conversation * / public void receiveCall (Call c a l l ) { . . . } I *the i s s u e is resolved , f i n i s h the call */ public void callCompleted ( ) { . . . } I * The i s s ue has not been resolved . E s c a late t he c a l l , and a s s ign a new call to * the employee . * / public void escalateAndRea s s i gn ( ) { } . . .
I *Ass ign a new call to an employee , if the employee is free . * / p u b l i c bool ean ass ignNewCall ( ) { . . . } I * Ret urns whether or not t he employee is free . * / public boolean i s F ree ( ) { return currentCall == null ; }
CrackingTheCodinglnterview.com I 6th Ed itio n
309
Sol utio ns to Chapter 7 23
24
}
25
I Object-Oriented Desig n
public Rank getRa nk ( ) { ret u r n ra nk ; }
The Res pondent, Directo r, and Manager classes are now just sim ple extensions of the Emp lo y ee class. 1
class Di rector extends Employee { public Director ( ) { ra n k = Ra n k . Direct o r ;
2 3 4 s
}
6 7
}
class Manager extends Emplo yee { public Manager ( ) { ra n k = Rank . Ma nage r ;
8
9
10
}
11 } 12 1 3 cla s s Res pondent extends Employee { 14 public Res pondent ( ) { 15 ra n k = Ra n k . Res ponder ; 16
}
17
}
This is just one way of desig ning this problem. Note that there are many other ways that a re equa lly good. This may seem like an awfu l lot of code to write in an interview, and it is. We've been much more thorough here than you wou ld need. In a rea l interview, you wou ld likely be m uch lighter on some of the details until you have time to fill them in. 7.3
J u kebox: Design
a m usical jukebox using object-oriented principles. pg 127
SOLUTION
In any object-oriented design question, you first want to start off with asking your interviewer some questions to clarify design constrai nts. Is this ju kebox playing CDs? Records? MP3s? Is it a simu lation on a computer, or is it supposed to represent a physica l jukebox? Does it take money, or is it free? And if it takes money, which cu rrency? And does it del iver cha nge? U nfortu nately, we don't have an interviewer here that we can have this dialogue with. Instead, we'll make some assumptions. We'll assume that the jukebox is a computer simu lation that closely mi rrors physica l ju keboxes, and we'll assume that it's free. Now that we have that out of the way, we'll outline the basic system com ponents: •
Jukebox
•
CD
•
Song
•
Artist Playl ist Display (displays details on the screen)
Now, let's break this down fu rther and think about the possible 310
Cracking the Coding Interview, 6th Edition
actions.
Solutions to Chapter 7 •
I Object-Oriented Design
Playlist creation (includes add, delete, and shuffle)
•
CD selector
•
Song selector Queuing up a song Get next song from playlist
A user a lso can be introd uced: •
Adding
•
Deleting
•
Cred it i nformation
Ea ch of the main system components tra nslates roughly to an object, and each action translates to a method. Let's wa l k through one potential design. The J u kebox class represents the body of the problem. Many of the interactions between the components of the system, or between the system and the user, a re chan neled through here. 1
2
3 4 5
6
public c l a s s J u kebox { p rivate CDPlayer cdPlayer; private User user; private Set cdCollectio n ; private SongSelector t s ;
7 8
public Jukebox ( CDPlayer cdPlaye r , User user, Set cdCol lection , SongSelector t s ) { . . . }
9 10 11
12
public Song getCurrentSong ( ) { ret u r n t s . getCurrentSong ( ) ; } public void setUser ( User u ) { this . user u; } =
}
Like a rea l CD player, the CDP l aye r class supports stori ng just one CD at a time. The CDs that are not in play are stored in the jukebox. public c l a s s CDPlayer { 1 private Playlist p ; 2 p rivate C D c ; 3 4 I * Const actors . * / 5 public CDPlayer (CD c , Playlist p ) { . . . } 6 public CDPlaye r ( Playlist p ) { this . p = p; } 7 8 public CDPlaye r ( C D c ) { this . c c; } 9
10
11
12 13 14 15
=
I * Pl a y song * / public void playSong ( Song s ) { . . . } I *Getters and setters * / public Playlist getPlaylist ( ) { return p ; } public void s etPlaylist ( Playlist p ) { this . p
16 17 public CD getCD ( ) { return c ; } 18 public void s etCD(CD c ) { this . c 1, }
=
p; }
c; }
The Play list ma nages the current and next songs to play. It is essentially a wrapper class for a queue and offers some add itional methods for convenience.
CrackingTheCodinglnterview.com I 6th Edition
311
Solutions to Chapter 7 1
I Object-Oriented Design ·
public c l a s s Playlist { private Song song; private Queue queue ; public Playli st ( Song song, Queue queue ) {
2
3 4 5
6
} public Song getNextSToPlay ( ) { return queue . peek( ) ; } public void queueUpSong ( Song s ) { queue . add ( s ) ; }
7
i
9 10 11
12
13 } The classes for CD, Song, and U s e r are a l l fairly straightforwa rd. They consist mainly of member variables and getters and setters. 1
public cla s s CD { /* data for id, art i s t , songs , etc */ }
2 3
public class Song { / * data for i d , CD ( could be null ) , title, lengt h , etc */ }
4 5 6
public class User { private Stri ng name ; 7 public St ring getName ( ) { return name ; } public void setName ( St r i ng name ) { thi s . name name ; } 8 ' public long get I D ( ) { ret urn ID; } 10 public void setID( long i D ) { ID = iD; } 11 private long ID; 12 public User ( St r i ng name, long iD) { . . } 13 public U s e r getUs e r ( ) { return th i s ; } 14 public stat i c User addUser ( St r i ng name, long iD) { . . } 15 } This is by no means the only "correct" implementation. The i nterviewer's responses to initial questions, a s wel l as other constrai nts, will shape t h e design o f t h e ju kebox classes. .
.
7.4
Parking Lot: Desig n a parking lot using object-oriented princi ples. pg 127
SOLUTION
The wording of this question is vague, just as it would be in an actua l interview. This requ ires you to have a conversation with your interviewer a bout what types of vehicles it ca n support, whether the parking lot has m ultiple levels, and so on. For our pu rposes right now, we'll make the followi ng assumptions. We made these specific assum ptions to add a bit of complexity to the problem without adding too much. If you made different assum ptions, that's tota lly fine. •
•
•
The parking lot has m u ltiple levels. Each level has multiple rows of spots. The parking lot can park motorcycles, ca rs, and buses. The parking lot has motorcycle spots, compact spots, a n d l a rge spots.
A motorcycle can park in any spot. A car can park in either a single compact spot or a single large spot.
312
Cracking
the Coding I nterview, 6th Edition
Solutions to Chapter 7
I Object-Oriented Design
b u s can park in five la rge spots that are consecutive and within the same row. I t cannot park i n small spots.
A
In the below i mplementation, we have created an abstract class Vehic le, from which Ca r, B u s, and Motor c y c le i n herit. T O handle the d ifferent pa rking spot sizes, we have j ust one class P a r k i n gSpot which has a member variable indicating the si ze 1
2 3
4 5
6
7
.
publ ic enum Ve hicleSize { Motorcycle, Compact , public a bs t ract c l a s s Ve hicle { protected Array L i s t < Pa rkingSpot > parkingSpots p rotected String licensePlate ; p rotected int s potsNeeded ; protected Ve hicleSize size;
8 9 10 11 12 13 14
/ * Park ve hicle i n this s pot ( among others , potentially) */ public void parkinSpot ( ParkingSpot s) { parkingSpots . add ( s ) ; }
15
21 22
23
24
25
26 27
28 29
30
31 32 33
34
35 36 37
/ * Remove ca r from s pot , and notify s pot t hat it ' s gone * / p u b l i c void clea rSpots ( ) { . . . } / * Checks if t he s pot is big enough for t he vehicle ( and is available ) . This * compares t he SIZE only . It does not check i f it has enough s pots . * / public abstract boolean canFitinSpot ( Parki ngSpot spot ) ;
} public c l a s s Bus extends Vehicle { public Bu s ( ) { spotsNeeded 5; size Vehic leSize . La rge ; } =
=
/* Checks if t he s pot i s a La rge . Doe s n ' t check num of s pots * / public boolean c a n F itinSpot ( Pa rkingSpot spot ) { }
} public c l a s s Car extends Ve hicle { public Ca r ( ) { s potsNeeded = 1; size = VehicleSize . Compact ; }
38
39
40
41 42
new ArrayList ( ) ;
public int getSpotsNeeded ( ) { retu r n s potsNeeded ; } public Ve hicleSize getSize ( ) { ret urn size; }
15 17 18 19 20
La rge }
/ * Checks i f the s pot i s a Compact o r a La rge . */ public boolean c a n F itinSpot ( Pa rkingSpot s pot ) { . . . }
}
43 public class Motorcycle extends Vehicle { public Motorcycle ( ) { 44 s potsNeeded = 1 ; 45 46 size = Ve hicleSize . Motorcycle; 47 } 48
49
50
}
public boolean canFitr nSpot ( ParkingSpot s pot ) { . . . }
CrackingTheCodinglnterview.com I 6th Edition
313
Solutions to Cha pter 7
I Object-Oriented Design
Th e P a r k in g Lot c lass i s essentially a wra pper class fo r an array of Leve l s . By implementing it this way, we a re able to sepa rate out logic that deals with actua lly fi nding free spots a nd parking cars out from the broader actions of the P a r k i n g L o t . If we didn't do it this way, we would need to hold parking spots in some sort of double a rray (or hash table which m a ps fro m a leve l number to the l ist of spots). It's c l ea n e r to just separate ParkingLot fro m Level. 1
2 3
4
public c l a s s ParkingLot { private Leve l [ ] level s ; private final int NUM_LEVELS
5
public ParkingLot ( ) { . . . }
6 7
8 9
10 11
12
13
14
15 16
17
5;
}
/* Park the vehicle i n a s pot (or multi ple s pots ) . Ret u r n false if failed . */ public boolean parkVehicle (Vehicle veh i c l e ) { . . . }
/* Represents a level in a p a r k i ng garage * / p u b l i c c l a s s Level { private i n t floo r ; private Pa rkingSpot [ ] spot s ; private int ava ilableSpots = 0 ; / / number o f free s pots private static final i nt SPOTS_PE R_ROW 10;
18
public Level ( i nt fl r , int numberSpot s ) { . . . }
20
public int availa bleSpots ( ) { return availa bleSpot s ; }
19
21 22
/ * F i nd a pla c e to park this vehicle . Return false if failed . * / public boolean parkVehicle(Vehicle veh i c l e ) { . . . }
23
24
25
/ * Park a vehicle starting at the s pot s potNumber, a n d continuing until * vehicle . s potsNeeded . */ private boolean pa rkStart ingAtSpot ( int num, Vehicle v) { . . . }
26
27
28
29
/* Find a s pot to park this vehicle . Return index of s pot , o r private i nt fi ndAva ila bleSpot s (Veh icle veh i c l e ) { . . . }
30 31 32
33
34 }
-1
on failure . */
/ * When a car wa s removed from the s pot , inc rement availableSpots */ public void spotFreed ( ) { ava ilableSpot s++; }
Th e
P a r k i n gspo t is implemented by having just a variable which represents the size of the spot. We cou ld have im plemented this by having classes fo r L a rge S po t CompactSpot, a nd Mo t o r c y c l e S po t wh ich in herit from ParkingSpot, but this is probably overkill. The s pots probably do not have different behaviors, other than their sizes.
1
2
3 4 5
6
7
,
public class ParkingSpot { private Vehicle vehicle; private Veh i c leSize s potSi ze; private int row; private int s potNumber; private Level level ;
8 9
10
public Parki ngSpot ( Level lvl , int r , int n , Vehic leSize s ) { . . . } p u b l i c boolean i sAvailable ( ) { return vehicle
11
' '3 1 4
Cracking the Codi ng I nterview, 6th Edition
==
null; }
Solution s to Chapter 7 12
/* C h e c k if the s pot is big enough and is ava i lable * / public boo lean c a n F itVe h i c l e (Ve h i c l e ve h i c l e ) { . . }
13
.
14
15
/* Park vehicle i n t h i s s pot . * / public boolean park ( Vehicle v) {
16 17
}
public i nt get Row ( ) { r et u r n row ; } public i n t g etS p ot Numbe r ( ) { return s potNumber ; }
18
19 20 21 22
23
I Object-Oriented Design
}
/ * Remove vehicle from s pot , and not ify leve l that a new s pot is ava i lable * / public v o i d r em o veve h i c l e ( ) { . . . }
A fu ll implementation of this code, including executable test code, is provided in the downloadable code attachment. 7.5
Online Book Reader: Design the data structures for an online book reader system. pg 127
SOLUTION
Since the problem doesn't describe much a bout the functional ity, let's assume we want to design a basic online reading system which provides the following fu nctional ity: User membership creation and extension. •
Searching the database of books. Reading a book. Only one active user at a time
•
Only one active book by this user.
To implement these operations we may require many other fu nctions, like get. s et, update, and so on. The objects requ i red would likely include U s e r, B oo k, and L i b r a ry.
The class O n l ine Reade rSystem represents the body of our program. We could im plement the class such that it stores information a bout all the books, deals with user management, and refreshes the display, but that would make this class rather hefty. Instead, we've chosen to tear off these components into L i b r a ry, U s e rM a n ager, and D i s play classes. 1
2
3
4 5
6 7
8 9
10
11 12 13
14 15 16
pub l i c c l a ss On li n e R ea d e r s ys t e m { private Libra ry l i bra ry ; private UserManager userManage r ; private Dis p l ay d i s p l ay ; private Book act iveBook ; private User a ct iveU s e r ;
p u bli c On lin e R ea d e r S y st em ( ) { userManager = new UserManage r ( ) ; l i b r a ry new Li b r a r y ( ) ; d i s p lay = new Display ( ) ; } =
pub lic Li bra ry get L i b r a ry ( ) { ret u r n l ib r a r y ; } public UserManager getUserManager ( ) { ret u r n u s e rManage r ; }
CrackingTheCodingl nterview.com I 6th Edition
315
Solutions to Chapter 7 I Object-Oriented Design 17
public Dis play getDi splay ( ) { return d i s play; }
18 19
public Book getAct iveBook ( ) { ret u r n activeBook ; } public void setActiveBook ( Book book) { act iveBook book; display . displayBook ( book) ; }
20
=
21
22 23
24
25 26 27
=
28 29
30
public User getActiveUser ( ) { ret u r n a c t iveUser; } p u b l i c v o i d setActiveUser (User u s e r ) { activeUser user; dis play . di s playus er ( use r ) ; }
}
We then implement sepa rate classes to handle the user manager, the l ibrary, and the display components. 1 public c l a s s Library { 2 private Has hMa p < I nteger, Book> books ; 3
4
public Book addBoo k ( int i d , St ring det a i ls ) { if ( books . containsKey ( id ) ) { return n u l l ; } Book book new Book ( id , deta i l s ) ; books . put ( id , book ) ; ret u r n book ; }
5
6 7 8
=
9
10 11 12
13 14
public boolean remove ( Book b) { return remove ( b . get ID( ) ) ; } public boolean remove ( int id ) { i f ( ! books . containsKey ( id ) ) { return false; } books . remove ( id ) ; return t rue; }
15
16
17 18 19
20 21
22
public Book find ( i nt id ) { return books . get ( id ) ; }
23
24
25 26 27
28 29
} public class UserManager { private Has hMap< Intege r , User> users ;
30 31 32 33
34 35
36
37 38
39
40
41
316
public User addUse r ( i nt id, String det a i l s , int accountType ) { if ( users . cont a i n s Key ( id ) ) { ret u r n n u l l ; } User user new User ( i d , det a i l s , a c cou ntType ) ; users . put ( id , user ) ; ret u r n user; } =
public User find ( int id ) { return users . get ( id ) ; } public boolean remove ( User u ) { ret urn remove ( u . get ID ( ) ) ; } public boolean remove ( i nt id ) {
Cracking the Coding Interview, 6th Edition
Sol utions to Chapter 7 42
if ( ! users . containsKey ( id ) ) { return fa l s e ; } users . remove ( id ) ; return t rue;
43
44 45
46
47 48 49
50
51
52
53
54
I Object-Oriented Oe$ign
}
}
public class Display { private Book activeBook; private User act iveUser; private int pageNumber = 0 ;
55
public void d i s playuser ( User user) { act iveU ser = user; refres hUsername ( ) ; }
56 57
5� 59
public void dis playBook ( Book book) { pageNumber = 0; act iveBook = book;
60 61 62
63
refres hTit le ( ) ; refres hDet a i l s ( ) ; refres h Page ( ) ;
64
65 66
}
67 6!!
public void turn PageForward ( ) { pageNumbe r++ ; refreshPa ge ( ) ; }
69
70
71
72
73 74
public void turnPageBackward ( ) { pageNumbe r - - ; refres hPage ( ) ; }
75
76
77 78
79
public publi c public public
80 81
82
83
void void void void
refreshUsername ( ) { /* updates username d i s play */ } refres hTit le ( ) { / * updates t itle d i s play * / } refres hDeta i ls ( ) { / * updates details d i s play * / } refreshPage ( ) { / * updated page d i s play * / }
}
The classes for U s e r a n d Book simply hold data a n d provide l ittle true functiona l ity. 1 2
3 4 5
6
7
8 9
le
11 12 13
public c l a s s Book { private int book!d; private St ring deta i l s ; public Book ( int id , St ring det ) { bookid = id; det a i l s = det ; } public public public public
int getI D ( ) { return bookl d ; } void setID ( int id ) { bookld = id ; } String getDeta ils ( ) { return details ; } void setDetails ( St r i n g d ) { details = d ; } CrackingTheCodinglnterview.com I 6th Edition
317
Solutions to Chapter 7
I Object-Oriented Design
14 } 15 1 6 publ i c cla s s U s e r { 17 p r ivate int useri d ; 18 p r ivate St ring deta i l s ; private i nt a c countType ; 19 20 21
public void renewMembershi p ( ) {
22
23
public User ( int id, St ring det a i l s , int a c countType) { userid = id ; this . details deta i l s ; this . a ccountType = a c countType; }
24
25
=
26 27
28 29
/ * Getters and setters */
30
publi c int getID ( ) { ret urn userid ; } public void setID( int id ) { userid = id ; } publi c St ring getDetails ( ) { ret urn detai ls ; }
31
32 33
34 35
36 37 38 39
40
41
}
}
public void setDeta ils ( String detail s ) { this . details = details ; } public int getAc countType ( ) { ret urn a ccountType; } public void setAccountType ( int t ) { a ccountType = t ; }
The decision to tea r off user management, libra ry, and display into their own classes, when this fu n ctional ity could have been in the genera l On l i ne Re aderSy st em class, is an interesting one. On a very smal l system, making this decision could make the system overly complex. However, as the system g rows, and more and more fun ctional ity gets added to O n l i n e Reade rSy s t em, brea king off such com ponents prevents this main class from getting overwhel m i ngly lengthy.
7.6
J igsaw: Implement an NxN jigsaw puzzle. Design the data stru ctu res and explain an algorithm to solve the puzzle. Yo u can assume that you have a fit s With method which, when passed two puzzle edges, returns true if the two edges belong together. pg 128
SOLUTION
We have a traditional jigsaw puzzle. The puzzle is g rid-like, with rows and col um ns. Ea ch piece is located in a single row and column and has four edges. Each edge comes in one of three types: inner, outer, and flat. A corner piece, for exa m ple, will have two flat edges and two other edges, which could be inner or outer.
318
Cracking the Codi ng Interview, 6th Edition
Solution s to Chapter 7
I Object-Oriented Design
A s w e solve t h e jigsaw puzzle (manua lly or a lgorithmical ly), we'll need t o store the position o f each piece. We could think a bout the position as a bsolute or relative: Absolute Position: "This •
Relative Position:
piece is located at position ( 1 2, 23):'
"I don't know where this piece is actually located, but I know it is next to this other
piece:' For our solution, we wi ll use the a bsolute position. We'l l need classes to represent P u z z l e, P i e c e, and Edge. Add itionally, we'l l want enums for the d ifferent shapes (inne r, outer, flat) and the orientations of the edges (l eft, top, rig ht, bott om). P u z z l e will start off with a list of the pieces. When we solve the puzzle, we' l l fi l l in an NxN solut ion matrix of pieces. P i e c e wi ll have a hash ta ble that maps from an orientation to the appropriate edge. Note that we might rotate the piece at some point. so the hash ta ble could change. The orientation of the edges will be arbi trarily assigned at first. Edge will have just its shape and a pointer back to its parent piece. It will not keep its orientation. A potential object-oriented design looks like the fol lowing: 1
2
3 4
publ ic enum Orientation { L E FT, TOP, RIGHT, BOTTOM ; / / Should stay i n t h i s order
s
6
7
8 9
10 11 12
publ ic Orientation getOppo s it e ( ) { switch ( t h is ) { case L E F T : return RIGHT ; case RIGHT : ret urn LEFT; case TOP : ret urn BOTTOM ; case BOTTOM : ret urn TOP ; default : retu r n n u l l ; } }
13
}
15
pub l i c
14 16 17 18
19
20 21
22
e n um Sh a p e { I NN E R , OUTER, F LAT ; p u b l i c Shape getOppos ite ( ) { switch ( t h i s ) { case I NNE R : return OUT E R ; case OUT E R : return INNE R ; default : re t u r n n u ll ;
CrackingTheCodingl nterview.com I 6th Edition
319
Sol utions to Chapter 7 I Object-Oriented Design 23
24 25
26 27
22
29
30
31
}
}
}
public class Puz zle { private L inked list< Piece> pieces ; /* Rema i n i ng pieces to put away . */ private Piece [ ] [ ] solution ; private i nt s i ze ;
32
p u b l i c Puzzle ( int s ize, L i n ked list< Piece> pieces ) { . . . }
34 35 36
/ * P u t piece i nto the solution, turn it a ppropriately, and remove f rom l i s t . * /
33
private void set EdgeinSolution ( Linked L i s t < Piece> pieces, Edge edge, int row , int column, Orientation orientatio n ) { Piece piece edge . get ParentPiece ( ) ; piece . s etEdgeAsOrientation ( edge, orientation ) ; pieces . remove ( piece ) ; solution [ row ] [ column] piece; }
37 38
=
39
40 41
=
42
43 44
/ * F i nd the matching piece i n piecesToSearch and insert it at row, column . * /
45
private boolean fitNext Edge ( L inked List< Piece> piecesToSearch, int row, int col ) ;
46 47 48
/* Solve puzzle . */ p u b l i c boolean solve ( ) { . . . }
49
}
51 52
public class Piece { private Has hMap edges
50
53 54
publ i c Piec e ( Edge [ ] edgeList ) { . . . }
55
56
/ * Rot ate edges by " n umberRotations " . * /
57
public void rotateEdges By ( i nt numberRotations ) { . . . }
58
59 6� 61
62 63
94
65
66 67 6�
new Has hMap ( ) ;
public boolean isCorne r ( ) { public boolea n i s Border ( ) {
} }
}
public class Edge { private Shape s hape; private P i e c e pa rent Piece; public Edge ( S hape s ha p e ) { . . } public boolean fitsWit h ( Edge edge ) { . . . } } .
Algorithm to Solve the Puzzle
J ust as a kid might in solving a puzzle, we'l l sta rt with grou ping the pieces into corner pieces, border pieces, and inside pieces. Once we've done that, we'll pick a n a rbitra ry corner piece and put it i n the top left corner. We will then wa lk through the puzzle in order, filling in piece by piece. At each location, we search th roug h the correct g roup of pieces to find the match ing piece. When we i nsert the piece into the puzzle, we need to rotate the piece to fit correctly.
320
Cracking the Codi ng Interview, 6th Edition
Solutions to Chapter 7
The code below outlines this a lg o rithm
I
Object-Orie nted Desig n
.
/ * F i nd the mat c h i ng piece within piecesToSearch and insert it at row, column . * / 2 boolean fitNextEdge ( L i nked list< Piece> piecesToSearch, int row, int column) { 3 if ( row == 0 && column == 0 ) { / / On top left corner, j ust put in a piece 4 Piece p = piecesToSear ch . remove ( ) ; 5 orientTop LeftCorner ( p ) ; solution [ 0 ] [ 0 ] = p ; 6 7 } else { / * Get t he right edge and list to mat c h . * / 8 9 Piece pieceToMa t c h = column == 0 ? solution [ row - 1 ] [ 0 ] : solution [ row ] [ column - 1 ] ; 10 Orientation orientationToMat c h column = = 0 ? Orientation . BOTTOM 11 Orientation . RIGHT ; 12 Edge edgeToMa t c h = pieceToMat c h . get E dgeWit hOrient ation ( orientationToMat c h ) ; 13 1
14
15 16 17 18 19 20 21
/ * Get mat ching edge . */ Edge edge = getMat c h i ngEdge ( edgeToMat c h , piecesToSearc h ) ; if (edge == null ) ret urn false ; / / Can ' t solve /* Insert piece and edge . * / Orientation orientation = orientat ionToMat c h . getOppos ite ( ) ; set EdgeinSolut ion ( piecesToSe a r c h , edge , row, column, orientation ) ;
22 23
24 } 25
26
27
28
29
30 31
32 33 34
35
36
37 38
39 40 41
} return true ;
boolean solve ( ) { /* Group pieces . */ Linked list cornerPieces new L i n ked l i s t < Piece > ( ) ; L i n ked l i s t < Piece> borderPieces new L i n ked l i s t < Piece > ( ) ; L inked l i s t < Piece> ins idePieces new L inked l i s t < Piece > ( ) ; groupPieces ( cornerPieces , borde rPiece s , insidePieces ) ; / * Wal k through puzzle, finding the piece that joins the previous one . * / solution = new Piece [ s i ze ] [ s ize ] ; for ( int row = 0; row < size; row++ ) { for ( i nt column = 0; column < size; col umn++ ) { L inked list< Piece> piecesToSea r c h = get Piece ListToSea r c h ( cornerPie ces , borderPiece s , ins idePieces , row, column ) ; if ( ! fitNextEdge ( p iecesToSe a r c h , row, column ) ) { ret urn false ; } } }
42 43 44 45 ret urn t r ue ; 46 }
The ful l code for this solution can be found i n the downloadable code attachment.
Cracki ngTheCod ingl nterview.com I 6th Edition
321
Solutions to Chapter 7
I Object-Oriented Design
Chat Server: Explain how you wou ld design a chat server. In particular, provide details about the various backend components, classes, and methods. What wou ld be the hardest problems to solve?
7.7
pg 128
SOLUTION
Desig ning a chat server is a huge project, and it is certainly far beyond the scope of what cou ld be com pleted in an interview. After all, tea ms of many people spend months or yea rs creating a chat server. Part of your job, as a candidate, is to focus on an aspect of the problem that is reasonably broad, but focused enough that you cou ld accom plish it during a n interview. It need not match rea l life exactly, but it should be a fair representation of an actua l implementation. For our pu rposes, we'll focus on the core user ma nagement and conversation aspects: adding a user, creating a conversation, u pdating one's status, and so on. I n the i nterest of time and space, we will not go into the networking aspects of the problem, or how the data actually gets pushed out to the clients. We will assume that "friendi ng" is mutual; I am only you r contact if you a re mi ne. Our chat system will support both group chat and one-on-one (private) chats. We will not worry about voice chat. video chat. or file transfer. What specific actions does it need to su pport?
This is also something to d iscuss with you r i nterviewer, but here are some ideas: Signing on line and offline. Add requests (sending, accepting, and rejecting). U pdating a status message. Creating private and group chats. Adding new messages to private and g roup chats. This is just a partial list. If you have more time, you can add more actions. What can we learn about these requirements?
We must have a concept of users, add request status, on line status, and messages. What are the core components of the system?
The system wou ld likely consist of a database, a set of clients, a n d a set of servers. We won't include these pa rts in our object-oriented design, but we can discuss the overa ll view of the system. The database will be used for more permanent storage, such as the user list or chat a rchives. A SQL database is a good bet, or, if we need more scalabil ity, we could potentially use BigTable or a similar system. For com munication between the client and servers, using XML will work well. Although it's not the most compressed format (and you should point this out to you r interviewer), it's nice beca use it's easy for both computers and humans to read. Using XML wi ll make your debugging efforts easier-and that matters a lot. The server will consist of a set of machines. Data wi ll be split across machines, requ i ring us to potentially hop from machine to machi ne. When possible, we will try to replicate some data across machines to minimize the lookups. One major design constraint here is to prevent having a single point of fai l u re. For instance,
322
Cracking the Coding I nterview, 6th Edition
Sol utions to Chapter 7
I Object-Oriented Design
if one machine controlled a l l the user sign-ins, then we'd cut off millions of u sers potentially if a single m ac hin e lost network c onn ecti v ity .
What are the key objects and methods?
The key obj e cts of the system will be a concept of users, conversations, and status messages. We've imple mented a U s e rManager class. If we were looki ng more at the networking aspects of the problem, or a different component, we might have instead d ived into those objects. 1
1 2 3
4 5
/* UserManager serves as a central pla ce for core user actions . */ public class UserManager { private static UserManager insta nce ; / * maps from a u s e r id to a user */ private HashMap < I nteger, User> u s e r s By!d ;
6
/ * maps from an account name to a u s e r * / private HashMap < St r i ng, User> users ByAccou ntName ;
7
8 9
/* maps from the user id to a n online user * / private Has hMap< Integer, User> onli neUsers ;
10
11 12 13
public static UserManager get!nsta n ce ( ) { if ( i nstance null ) insta nce new UserManager ( ) ; ret urn instance; } ==
14
15
16 17
public public public public public
18
1, 20
21 22
}
void void void void void
=
addUs e r ( User fromuser, String toAccou ntName ) { . . . } approveAddRequest (AddRequest req ) { . . . } rej ectAddRequest (AddRequest req ) { . . . } userSignedOn ( String accountName ) { . . . } userSignedOff ( String a c cou ntName ) { . . . }
The method receivedAddRequest, in th e U s e r class, n otifies User B that User A has requested to add him. User B approves or rejects the request (via U s e rMa n ager . approveAd d R e q u e s t or rej ectAddReq uest), and the Us erMa n a g e r ta kes care of adding the users to each other's contact lists. The method s entAdd Request i n the U s e r class is called by U s e rMa n a ge r to add an Add R e q uest to User A's list of requests. So th e flow is:
1 . User A clicks "add user" on the client, and it gets sent to the s erve r. 2. Us e r A calls r e q u estAddUs e r ( U s e r B ) . 3 . Th is method calls UserMan a ge r . addUser. 4.
U s e rMa n a g e r calls both U s e r A . s e n t Ad d Reque s t and User B . rece ivedAddRequest.
Again, this is ju stone way of designing these interactions. It is not the o n l y way, or even th e onl y "good" way. 1 public class User { private int i d ; 2 private UserStatu s status = n u l l ; 3
4 5
/ * maps from the other participant ' s user id to the chat * /
8
/* list of group chats * /
6 7
private H a s hMa p < I nteger, PrivateChat> privateChat s ;
CrackingTheCodinglnterview.com I 6th Edition
323
Solutions to Chapter 7 9 10
I Object-Oriented Desig n
private ArrayList groupChat s ;
11
/* maps from t he other person' s user id to t he add req uest * / private HashMa p < I ntege r , AddRequest> receivedAddReq uest s ;
12
13 14
/* maps from the other person ' s user id to the add request */ private H a s hMa p < I ntege r , AddRequest> s entAddRequest s ;
15
16
17
/ * maps from the user id to user ob j ect */
1�
private H a s hMa p < I nteger, User> cont a ct s ;
19
20 21 22 23 24 25 26 27 28 29 30
private St ring a c countName ; private String fullName ; public public public public public public public public public public public public public public public
31
32
33
34
35
36 37 38
}
U s e r ( int id, String accountName , String fullName ) { . . . } boolean s e ndMessageToUser ( User t o , St ring content ) { . . . } boolean sendMess ageToG roupChat ( int id , String cnt ) { . . . } void setStatus ( UserStatus status ) { } UserStatus getStatus ( ) { . . . } boolean addContact ( User u s e r ) { . . . } void rece ivedAdd Request (AddReq uest req ) { . . . } void sentAddReq uest (AddRequest req ) { . . . } void removeAddRequest (AddRequest req ) { . . . } void requestAddUse r ( String accountName ) { . . . } void addConversation ( PrivateChat conversation ) { } void addConve rsation (GroupChat conversat i o n ) { . . . } int get id ( ) { . . . } String getAccountName ( ) { . . . } String get F u l lName ( ) { . . . }
Th e Conve rsation class is implemented as a n abstra ct class, since all Conve rsations must be either a G r o u p C h a t o r a Pri vateChat, and since these two classes each have their own fu n ctionality. 1 public a bstract class Conversation { 2 protected ArrayL ist part i c i p a nt s ; protected int id ; 3 4 protected Array List mes s a ge s ; 5
6
public Array list getMes s ages ( ) { . . . } public boolean addMe s sage ( Mess age m ) { . . . } public int get id ( ) { . . . }
7
8
9
} 10 11 public c l a s s GroupChat extends Conversation { 12 public void removePart i c i pant ( User user) { . . . } pu blic void addPart i c i pant ( User user) { . . . } 13 14 } 15
16
17
18 19
20
p u b l i c c l a s s PrivateChat extends Conversation { public PrivateChat ( User userl , User user2) { . . . public User getOt herPart i c i pant ( User primary) { . . . } }
21
public c l a s s Mes s age { priva t e St ring content ; private Date date; 23 24 public Mes s age ( String content , Date date ) { . . . } 22
324
Cracking the Coding I nterview, 6th Edition
Solutions to Chapter 7 25
I Object-Oriented Desig n
public String getContent ( ) { . . . } public Date getDate ( ) { . . . }
26
27
} Add R e q u e st and U s e rStat u s a re simple classes with little fu nctionality. Their main pu rpose is to g ro u p data that other c lasses w i l l act u pon. 1
2 3
4
public cla s s AddRequest { private User fromUser ; private User toUse r ; private Date d a t e ;
5
RequestStatus status ;
6 7
public public public public public
8
9
10
11 12 13
AddReques t (User from, User to, Date date ) { . . . } RequestStatus getStat us ( ) { . . . } U s e r get F romUser ( ) { . . . } U s e r getToUser ( ) { . . . } Date getDate ( ) { . . . }
}
14
public cla s s UserStat us { 15 private String mes s age ; private UserStatusType type ; 16 17 public UserStat u s ( Us e rStatusType type, String mes s age ) { . . . } 18 public UserStatusType getStatusType ( ) { . . . } 19 public St r i ng getMe s s a ge ( ) { . . . } 20 } 21
22 public enum UserStatusType { 23 Offline, Away, Idle, Avai lable, Busy 24 } 25 26 public enum RequestStatus { 27 U n rea d Read , Accepted , Rejected 28 } ,
The downloadable code attachment provides a more detailed look at these methods, including i mplemen tations for the methods shown a bove. What problems wou ld be the hardest to solve (or the most interesting)?
The following questions may be interesting to discuss with you r interviewer fu rther. Q 7 : How do we know if someone is on line-I mean, really, really know?
While we wou ld like u sers to tell us when they sign off, we can't know for sure. A user's connectio n might have d ied, for exa m ple. To make s u re that we know when a u s e r has sig ned off, we might try regularly pinging the client to make s u re it's still there. Q2: How do we deal with con flicting information?
We have s ome i nformation stored in the compute r's memory and some in the data base. What happens if they get out of sync? Which o n e i s "rig ht"?
CrackingTheCodingl nterview.com j 6th Edition
325
Sol utions to
Chapter 7
I Object-Oriented Design
Q3: How do we make our server scale?
While we designed out chat server without worryi ng-too much- a bout scalability, in rea l life this would be a concern. We'd need to split our data across many servers, which wou ld increase our concern a bout out-of-sync data. Q4: How we do p reven t denial of service attacks?
Clients can push data to us-what if they try to DOS (denial of service) us? How do we prevent that? 7 .8
Othello: Othello is played as follows: Each Othel lo piece is white on one side and black on the other. When a piece is su rrounded by its opponents o n both the left and right sides, or both the top and bottom, it is said to be ca ptu red and its color is fl ipped. On you r tu rn, you must ca ptu re at least one of you r opponent's pieces. The game ends when either user has no more va lid moves. The win is assigned to the person with the most pieces. Im plement the object-oriented design for Othello. pg 1 28
SOLUTION
Let's start with an exam ple. Suppose we have the following moves in an Othello game:
1 . Initialize the boa rd with two black and two white pieces in the center. The black pieces are placed at the upper left hand and lower right hand corners. 2. Play a black piece at (row 6, column 4). This flips the piece at (row 5, column 4) from white to black. 3. Play a white piece at (row 4, column 3). This flips the piece at (row 4, column 4) from black to white.
This sequence of moves leads to the boa rd below. I
000 00 0
The core objects in Othello are probably the game, the boa rd, the pieces (black or white), and the players. How do we represent these with elegant object-oriented design? Should BlackPiece and WhitePiece be classes?
At first, we might thi n k we wa nt to have a B la c kP i e c e class and a Whi tePiece class, which inherit from an a bstract P i e c e . However, this is proba bly not a great idea. Each piece may fl ip back and forth between colors frequently, so continuously destroying and creati ng what is rea lly the sa me object is proba bly not wise. It may be better to just have a P i e c e class, with a flag in it representing the cu rrent color. Do we need separate Board and Game classes?
Strictly speaking, it may not be necessa ry to have both a Game object and a Boa rd object. Keeping the objects separate allows us to have a logical sepa ration between the boa rd (which conta ins just logic
326
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 7
I Object-Oriented Design
involving placing pieces) and the game (which involves times, game flow, etc.). However, the d rawback is that we are adding extra layers to our program. A function may ca ll out to a method i n Game, only to have it i mmediately call Boa rd. We have made the choice below to keep Game and Board sepa rate, but you should d i scuss this with your i nterviewer. Who keeps score?
We know we should probably have some sort of score keeping for the nu mber of black and white pieces. But who should mainta in this information? One could make a strong a rgu ment for either Game or Boa rd mainta i n ing this information, and possibly even for P i e c e (in static methods). We have i mplemented this with B o a rd hold ing this information, since it can be logically grouped with the boa rd. It i s u pdated by P i e c e or Boa rd calling the c olorChan ged and c o l o rAdded methods with in Board. Should Game b e a Singleton class?
Implementing Game as a singleton class has the advantage of making it easy for anyone to call a method with i n Game, without having to pass a round references to the Game object. However, making Game a singleton means it can only be insta ntiated once. Can we m a ke this assum ption? You should d iscuss this with your interviewer. One possi ble design for Othello is below. publ i c enum Directi on { 2 left , right , up, down 3 } 1
4 5
G
7 8 9
10 11 12 13
14 15
public enum Color { White, Black } public class Game { p r ivate Player [ ] playe r s ; p rivate static Game instance; p rivate Boa r d boa r d ; p rivate f i n a l i n t ROWS = 10; p rivate final int COLUMNS = 10;
15
p r ivate Game ( ) { boa rd = new Boa rd ( ROWS , COLUMNS ) ; players = n ew Player [ 2 ] ; player s [ 0 ] n ew Playe r ( Color . Bl a c k ) ; players [ l ] = n ew Player ( Color . White ) ; }
17 18 19
20
21
22
23
public static Game get i nstance ( ) { if ( in stance == n u l l ) i n stance new Game ( ) ; return i n s tance;
24
=
25
26 27
}
28
29 3ti 31
}
public Boa rd getBoa rd( ) { return boa rd ; }
CrackingTheCodingl nterview.com I 6th Edition
327
Solutions to Chapter 7
I
Object-Oriented Des i g n
The Boa rd class manages the actual pieces themselves. It does not handle m u c h o f t h e game play, leaving that up to the Game class. 1
public class Board { private int blackCount 0; private int whiteCount 0; private Piece [ ] [ ] board ;
2 3
=
4 5
6
public Board ( int row s , int column s ) { bo a r d = new Piece [ rows ] [ columns ] ; }
7 8
9
10
public void i n itialize ( ) { / * initialize center black and white pieces */ }
11 12 13
14 15
/ * Attempt to place a piece of color color at ( row, column ) . Return true if we * were s ucces sful . * / p u b l i c boolean placeColor ( i nt row, i n t column, Color colo r ) {
18
}
16 17 19
20
/* F l i p s pieces start i ng at ( row, col umn ) and proceed ing in direction d . */ private int flipSect io n ( int row, i nt column, Color color, Direction d) {
21 22 23
public int getScore ForColo r ( Color c ) { if ( c == Colo r . Black) ret urn blackCount ; else return whiteCount; }
24 25
26 27
28
/ * Update boa rd with additional newPieces pieces of color newColor . Dec rease * score of opposite colo r . */ public void updatescore ( Color newColor, int newPiece s ) { . . . }
29
30 31
}
}
As d escribed earlier, we implement the black and white pieces with the Piece class, which has a simple Color variable representing whether it is a black or wh ite piece. public class Piece { 1 2 private Color color; 3 public Piece ( Color c ) { color c; }
4
5
public void flip ( ) { if ( color == Colo r . Bla c k ) color else color = Color . Blac k ; }
6 7
8
9
10 11
Colo r . Wh ite;
public Color getColo r ( ) { ret urn color ; }
}
The Player ho lds only a very lim ited amount of information. It does not even hold its own score, but it does have a method one can call to get the score. P l ay e r .getSc o re ( ) will call out to the Game object to retrieve this value. 1 public class Player { 2 private Color colo r ; p u bl i c Playe r ( Color c ) { color 3 c; } �· 5 public int getScore ( ) { . } . .
328
I
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 7 6
7
public boolean playPiece ( int r, int c ) { ret u r n Game . getlnstance ( ) . get.Boa rd ( ) . plac eColor ( r, c , colo r ) ; }
8 9
11
12
I Object-Oriented Design
}
public Color getColo r ( ) { r et u rn color; }
A ful ly functioning (automated) version of this code can be found in the downloadable code attachment.
Remember that in many problems, what you did is less im porta nt than why you did it. You r i nterviewer probably doesn't ca re much whether you chose to im plement Game as a singleton or not, but she proba bly does care that you took the time to think a bout it and discuss the trade-offs. 7.9
Circular Array: I m plement a C i r c u l a rArray class that supports a n array-like data structu re which can be efficiently rotated. If possible, the class should use a generic type (also called a tem plate), and should support iteration via the sta ndard for (Obj o : c i r c u l a rArray ) notation. pg 728
SOLUTION
This problem really has two parts to it. Fi rst, we need to implement the C i r c u l a rArray class. Second, we need to support iteration. We will address these parts separately. Implementing the CircularArray class
One way to implement the C ir c u l a rArray class is to actually shift the elements each time we cal l r otate ( i nt s h i ftRight ) Doing this is, of cou rse, not very efficient. .
Instead, we can just create a member varia ble h e a d which points to what should be conceptually viewed as the start of the circu lar array. Rather than shifting a round the elements in the array, we just increment head by s h ift R i g ht. The code below implements this approach. public c l a s s C i r c ul a rArr ay < T > { 1 pr ivate T [ ] item s ; 2 pr ivate int head = 0; 3 4 public CircularArray ( int s i z e ) { 5 6 items = ( T [ ] ) new Obj ect [ s i z e ] ; 7 8 9
10
11 12 13 14 15
15
}
private int convert ( int index) { if ( index < 0 ) { index + = items . lengt h ; } ret urn ( head + index) % items . lengt h ; }
18
public void rotat e ( int s h iftRight ) { head = convert ( s h ift Right ) ; }
21
p u bli c T get ( int i ) { if ( i < 0 I I i >= items . lengt h) {
17
19 20
CrackingTheCodingl nterview.com I 6th Edition
329
Solutions to Chapter 7 22
I Object-Oriented Desig n "
t hrow new j ava . lang . IndexOutOfBound s E xcept ion ( " . . . ) ; } return items [ convert ( i ) ] ;
23
24
25
}
26 27 28
public void set ( int i , T item) { items [ convert ( i ) ] item; } =
29
30
} There a re a number of things here which are easy to make m istakes on, such as:
I n Java, we can not create an array of the generic type. Instead, we must either cast the array or define i terns to be of type List < T > . For simplicity, we have done the former. •
The % operator will return a negative value when we d o negVa lue % posVa l . For exam ple, - 8 % 3 is 2. This is different from how mathematicians would define the modulus function. We must add i terns . l e n gt h to a negative index to get the correct positive resu lt. -
We need to be sure to consistently convert the raw index to the rotated index. For this reason, we have implemented a conve rt function that is used by other methods. Even the rot ate function uses conve rt. This is a good example of code reuse. Now that we have the basic code for C i r c u l a rArray out of the way, we can focus on im plementing a n iterator. Implementing the Iterator Interface
The second part of this q uestion asks us to implement the C i r c u l a rArray class such that we can do the following: 1
C i rcularArray a r ray = for ( St ring s : a rray ) { }
2
• . .
. .
.
I mplementing this requires im plementing the Iterator interface. The details of this implementation apply to Java, but sim ilar things can be implemented in other languages. lo im plement the Iterator interface, we need to do the following: Mod ify the C i r c u l a rArray definition to add implement s Iterable < T > . This will also require us to add an iterato r ( ) method to C i r c u l a rArray. •
Create a C i r c u l a rArrayrterat o r < T > which im plements Iterator. This will also require us to implement, in the C i r c u l a rArraylterator, the methods h a s Nex t ( ) , next ( ) , a nd r e mo v e ( ) .
Once we've done the above items, the for loop will "magica l ly" work. I n the code below, we have removed the aspects of C i r c u l a rArray which were identical to the earlier implementation. 1 public class CircularAr ray< T> implements Iterable { 2 3 publ i c Iterator iterator ( ) { 4 return new C i rcularArrayiterator ( t hi s ) ; 5 } 6 7
pr ivate cla s s CircularAr rayiterator implements Iterator { / * cur rent re flect'.> the off s et from the rotated head, not from the actua l * start of the raw a r ray . * / p r ivate int _cur rent -1;
8
9
10
BO
=
I
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 7 11
private TI [ ) _items ;
12 13
public C i rcula rArrayiterator ( C i rcularArray a rray ) { items = a r ray . items ;
14 15
}
16 17
@Ove r ride public boolean hasNext ( ) { ret urn _current < items . length
18
19
20
}
21
22
24 25 26
27 28 29
1;
}
@Ove rride public void remove ( ) { th row new Unsuppo rtedOperationException ( "
30
31
32 33
-
@Override public TI next ( ) { _current++; TI item = ( TI ) _items [ convert (_current ) ] ; ret urn item ;
23
34
I Object-Oriented Design
}
}
• . . "
);
}
In the above code, note thatthe fi rst iteration of the for loo p will ca ll h a s Next ( ) a nd then next ( ) . Be very sure that your implementation will return the correct va lues here.
When you get a problem like this one in an interview, there's a good cha nce you don't remember exactly what the various methods a nd interfaces are called. In this case, work through the problem as well as you ca n. If you can reason out what sorts of methods one might need, that a lone will show a good degree of com petency.
CrackingTheCodingl nterview.com I 6th Edition
331
Solutions to Chapter 7 7.1 0
I Object-Oriented Design
Minesweeper: Design and implement a text-based Minesweeper game. M inesweeper i s the classic single-player com puter game where an NxN g rid has B m ines (or bom bs) hidden across the g rid. The remaining cells are either blank or have a number behind them. The numbers reflect the number of bom bs in the su rrou nding eight cells. The user then uncovers a cell. If it is a bomb, the player loses. If it is a num ber, the number is exposed. If it is a blank cell, this cell and a l l adjacent blank cells (up to and including the su rrounding numeric cel ls) a re exposed. The player wins when a l l non-bom b cells are exposed. The player can a lso flag certain places as potential bombs. This doesn't affect game play, other tha n to block the user from accidentally clicking a cell that is thought to have a bomb. (Ti p for the reader: if you're not familiar with this game, please play a few rounds on line first.)
This is a fu lly exposed boa rd with 3 bombs. This is not shown to the user. 1
1
1
*
2
2
1 1
*
1
The player initia l ly sees a boa rd with noth ing exposed.
1 1 2 1 1 1 1
1
*
1 1
Clicking on cell (row ""' 1 , col "" 0) would expose this:
The user wins when everything other than bom bs has been exposed.
2
,m 2
2
k�
pg 129
SOLUTION
Writing an entire game-even a text-based one-would ta ke fa r longer than the allotted time you have in an interview. This doesn't mean that it's not fa i r game as a question. It just means that you r interviewer's expectation will not be that you actually write a l l of this in an interview. It a lso means that you need to focus on getting the key ideas-or structu re-out. Let's start with what the classes a re. We certainly want a C e l l class as well as a Board class. We also prob ably want to have a Game class.
I
We could potentially merge Boa rd and Game together, but it's proba bly best t o keep them sepa rate. Err towa rds more organ ization, not Jess. Board ca n hold the list of Ce 11 objects and do some basic moves with fl ipping over cells. Game will hold the game state and handle user in put.
332
Cracking the Coding I n te rv iew 6th Edition ,
Solutions to Chapter 7
I Object-Oriented Design
Design: Cell
C e l l will need to have knowledge of whether it's a bomb, a nu mber, or a blank. We could potentially su bclass Ce 1 1 to hold this data, but I'm not sure that offers us much benefit. We cou ld also have an enum TYP E { BOMB , NUMB E R , BLAN K } to describe the type of cell. We've chosen not to do this beca use BLANK is really a type of NUMB E R cell, where the number is O. lt's sufficient to just have an i s Bomb flag. It's okay to have made different choices here. These a ren't the only good choices. Explain the choices you make and their tradeoffs with you r i nterviewer. We also need to store state for whether the cell is exposed or not. We probably do not want to subclass C e l l for Exposed C e l l a nd Unexposed C e l l. Th is is a bad idea because Boa rd holds a reference to the cel ls, a nd we'd have to change the reference when we flip a cell. And then what if other objects reference the i nsta nce of Cell? It's better to just have a boolean flag for i s E x p o s ed. We'll do a similar thing for isGue s s . public cla s s Cell { private int row ; 2 3 p rivate int column ; 4 private boolean i s Bomb ; private i n t number; J 6 pr ivate boolean i s Exposed fals e ; 7 private boolean isGuess = false ; 1
, -
=
8 9
public Cell ( int r , int c ) { . .
10 11
public boolean flip ( ) { i s Exposed = true; return ! i s Bomb ; }
public boolean toggleGuess ( ) { if ( ! i s Exposed ) { isGuess ! isGues s ; =
}
return isGuess ; }
25
27
}
/* Getters and setters for above va riables . */
12 13 14 15 16 17 18 19 20 21 22 23 24 26
.
}
/* F u ll code can be found in down loada ble code solutions . */
Design: Board
Board will need to have an a rray of all the C e l l objects. A two-d imension a rray will work just fine. We'll probably want Boa rd to keep state of how m a ny u nexposed cells there are. We'll track this as we go, so we don't have to conti nuously cou nt it. Boa rd will also handle some of the basic algorithms: Initial izing the board a nd laying out the bombs. Fl i pping a cell.
CrackingTheCodinglnterview.com I 6th Edition
333
Solutions to Chapter 7
I Object-Oriented Design
Expanding blank areas. It will receive the game plays from the Game object and ca rry them out. It will then need to return the result of the play, which cou ld be any of {clicked a bomb and lost, clicked out of bounds, clicked an already exposed area, clicked a blank area and sti ll playing, clicked a blank area and won, clicked a number and won}. This is really two different items that need to be returned: successful (whether or not the play was successfully made) and a game state (won, lost, playing). We'll use an additional GamePlayResul t to return this data. We'll a lso use a GamePlay class to hold the move that the player plays. We need to use a row, column, and then a flag to indicate whether this was an actual flip or the user was just marking this as a "guess" at a possible bomb. The basic skeleton of this class might look something l i ke this: 1 public class Boa rd { private int nRows ; 2 3 private int nColumns ; 4 private int nBombs = 0 ; private Cell [ ] [ ] cel l s ; 5 6 private Cell [ ] bombs ; private int numUnexposed Remaining; 7 8 9
11
private void initiali zeBoa r d ( ) { . . . } private boolea n flipCell ( Cell cell ) { . . . } public void expa ndBlank(Cell cel l ) { . . } public UserPlayRes ult playFl i p ( UserPlay play) { . . } public int getNumRemaining ( ) { return numUnexposedRema ining; }
12
13
.
14
15
16
17
}
public Boa r d ( int r, i nt c, int b) {
10
.
}
18 19
public class UserPlay { private int row ; private int col umn ; 20 private boolean isGues s ; 21 22 / * constructor , getters, setters . */ 23 } 24
25
public c l a s s UserPlayResult { private boolean s ucces s ful ; 27 private Game . Gamestate resultingstate; 28 / * constr uctor, getter s , setter s . */ 29
29
}
Design: Game
The Game class will store references to the board and hold the game state. It a l so ta kes the user input and sends it off to Bo a rd. public cla s s Game { 1 2 public enum GameState { WON , LOST, RUNNING } 3
4
µr iv - 1 ) return memo [ n ] ; 13 14 } else { 15 memo [ n ] countWays ( n 15 countWays ( n 17 ret u rn memo [ n ] ;
2
=
1• 19
memo) {
{ - 1 , memo ) + countWays ( n - 2 , memo) - 3, memo ) ;
+
}
}
Regardless of whether or not you use memoization, note that the number of ways will quickly overflow the 3 7, the result has already overflowed. Using a long bounds of a n i nteger. By the time you get to just n will delay, but not completely solve, this issue. =
It is great to co mmun icate this issue to your interviewer. He proba bly won't ask you to work around it (a lthou g h you could, with a B i g i n t e g e r class). but it's nice to demonstrate that you think a bout these issu�s.
CrackingTheCodinglnterview.com I 6th Edition
343
Solutions to Chapter 8
I Recursion and Dynamic Prog ramming
Robot in a Grid: Imagine a robot sitting on the u pper left corner of grid with r rows and c columns. The robot can only move in two d i rections, right and down, but certain cells are "off limits" such that the robot cannot step on them. Design an algorithm to find a path for the robot from the top left to the bottom right.
8.2
pg 7 3 5
SOLUTION
If we picture this grid, the only way to move to spot ( r , c ) is by moving to one of the adjacent spots: ( r - 1 , c ) or ( r, c - 1 ) . So, we need to find a path to either ( r - 1 , c ) or ( r , c - 1 ) . How do we find a path to those spots? TO find a path to ( r - 1 , c ) or ( r , c - 1 ) , we need to move to one of its adjacent cells. So, we need to find a path to a spot adjacent to ( r - 1 , c ) , which are coord inates
( r - 2 , c ) and ( r - 1 , c - 1 ) . or a spot adjacent to ( r , c - 1 ) . which are soots ( r - 1 . c - 1 ) and ( r c - 2 ) . .
Observe that we list the point ( r - 1 , c - 1 ) twice; we'll d iscuss that issue later.
I
Tip: A lot of people use the variable names x and y when dealing with two-d imensional arrays. This can actually ca use some bugs. People tend to think about x as the first coord inate in the matrix and y as the second coord inate (e.g., matrix [ x ] [ y ] ). But, this isn't rea lly correct. The fi rst coordinate is usually thought of as the row num ber, which is in fact the y va lue (it goes verti cal ly!). You should write mat r i x [ y ] [ x ] . Or, just make you r life easier by using r (row) and c (column) instead.
So then, to find a path from the origin, we just work backwards like this. Starting from the last cell, we try to find a path to each of its adjacent cells. The recursive code below im plements this a lgorithm.
1 2 3 4 5
Arraylist < Point> get Pat h ( boolea n [ ] [ ] maze ) { if (maze == null I I maze . length == 0 ) return n ul l ; Arraylist < Point> path = new Arraylist < Point > ( ) ; if ( getPat h ( maze, maze . length 1 , maze [ 0 ] . length ret urn pat h ; } return n ul l ; } -
6
7
8 9
-
1 , path ) ) {
10 boolean getPath ( boolean [ ] [ ] maze, int row , int col , Arraylist < Point> pat h ) { 11 / * If out o f bound s o r not available, return . */ i f ( co l < 0 1 1 r ow < 0 1 1 ! ma z e [ row] [ co l ] ) { 12 13 return fa l s e ;
14
}
15
16
boolean isAtOrigin = ( row
17 18 19
0)
&&
( co l = = 0 ) ;
/ * If t h e re ' s a path f r o m t h e s t a r t to h e r e , add m y location . * / if ( isAtOrigin I I get Pat h ( ma z e , row , c o l 1 , pat h ) I I getPat h ( ma z e , row 1, c o l , p at h ) ) { Point p = new Point( row , col ) ; path . ad d ( p ) ; ret urn true; } -
20 21 22 23
-
24 25 26 27
==
}
return false;
This solution is 0 ( 2r+c ) , since each path has r+c steps and there a re two choices we can make at each step.
344
Crac k i ng the Coding Interview, 6th Edition
Solutions to Cha pter 8
I Recursion and Dynamic Programming
We should look fo r a faster way. Often, we can optimize exponential algorithms by find i ng dupl icate work. What work a re we repeating? If we walk through the algorithm, we'll see that we are visiting squares multiple ti mes. In fact, we visit each square ma ny, many ti mes. After all, we have re squares but we're doing o ( r•c ) work. If we were only visiting each square once, we would probably have an algorithm that was 0 ( re ) (un less we were somehow doing a lot of work during each visit) . How does our cu rrent algorithm work? To find a path to ( r , c ) , we look for a path to an adjacent coor d i nate: ( r - 1 , c ) or ( r , c - 1 ) . Of course, if one of those sq uares is off lim its, we ignore it . Then, we look at their adjacent coord inates: ( r - 2 , c ) ( r - 1 , c - 1 ) , ( r - 1 , c - 1 ) , and ( r, c - 2 ) . The spot ( r - 1 , c - 1 ) appears twice, which means that we're duplicating effort. Ideally, we should remember that we already visited ( r - 1 , c - 1 ) so that we don't waste our time. ,
This is what the dyna mic programming algorithm below does. 1
2 3
4 5
6
7
Arrayl i s t < Point > get Path ( boolea n [ ] [ ] maze) { if ( maze = =null I I maze . lengt h == 0 ) return null ; Arrayl i s t < Point > path = new Array L i s t < Point > ( ) ; Has hSet < Point> failedPoints = new Has hSet < Point > ( ) ; if ( getPath ( maze, maze . lengt h 1, maz e [ 0 ] . length 1, path, failed Point s ) ) { return path ; -
}
return null ;
8
9
-
}
10 11 boolean getPat h ( boolea n [ ] [ ] maze, int row, int col , Arraylist < Point > path, 12
Has hSet < Point> fai ledPoint s ) { / * If out of bou nds or not ava ilable, return . * / if ( col < 0 1 1 row < 0 1 1 ! maze [ row] [ col ] ) { return fa l s e ;
13
14
15
16
}
17 18
Point p = new Point ( row, col ) ;
19
20
/ * If we ' ve a l ready visited this cel l , return . * / if (fai ledPoint s . contains ( p ) ) { return fa l s e ;
21
22 23
}
24
boolean isAtOrigin = ( row == 0 ) & & ( col
25 26
27
29 30
-
31 32
}
33
36
0) ;
/ * If there ' s a path from start to my current loc ation, add my location . */ if ( isAtOrigin I I getPath ( maze, row, col - 1 , pat h , failedPoint s ) I I get Path ( maze, row 1 , col , pat h , failedPoint s ) ) { path . ad d ( p ) ; return t r u e ;
28
34 35
==
failedPoint s . ad d ( p ) ; // Cache res ult return fa lse; }
Th i s s i m p le
change will make our code run su bsta ntially faster. The algorithm will now take
beca use we h it each cell just once.
O ( XV) time
Cracki ngTheCod i nglnterview.com I 6th Edition
345
Solutions to Chapter 8 8.3
I Recursion and Dynamic Programming
Magic Index: A magic index in an array A[ 1 n - 1 ] is defined to be an index such that A[ i ] i . Given a sorted array of distinct integers, write a method to find a magic index, if one exists, i n a rray A. •
.
.
FOLLOW U P What if the values are not disti nct? pg 1 35
SOLUTION
I m med iately, the brute force solution should jump to mind-and there's no shame in mentioning it. We simply iterate through the array, looking for an element which matches this condition. 1 2
i nt magicSlow ( i nt [ ] array) { for ( i nt i 0; i < a r ray . lengt h ; i++ ) { if ( a rray [ i ] == i ) { return i ; } } ret u r n - 1 ; } =
3
4 5
6
7 8
Given that the array is sorted, though, it's very l i kely that we're supposed to use this condition. We may recognize that this problem sounds a lot like the classic binary search problem. Leveraging the Pattern Matching approach for generating a lgorithms, how might we apply binary search here? In binary search, we fi nd an element k by comparing it to the middle element, would land on the left or the right side of x.
x,
and determining if k
Building off this approach, is there a way that we can look at the middle element to determine where a magic index might be? Let's look at a sample array:
When we look at the middle element A [ 5 ] since A [ m i d ] < m i d .
3, we know that the magic index must be on the rig ht side,
Why cou ld n't the magic index be on the left side? Observe that when we move from i to i - 1, the va lue at this index must decrease by at least 1, if not more (since the array is sorted and all the elements are distinct). So, if the middle element is already too small to be a magic index, then when we move to the left, subtracting k indexes and (at least) k va lues, all subsequent elements wi l l also be too small. We continue to apply this recursive algorithm, developing code that looks very much like binary search. 1 2 3
int magicFast ( i nt [ ] a rray) { ret u r n magic F a s t ( a rray, 0 , a rray . lengt h - 1 ) ; }
5 6
int magicFast ( i nt [ ] array, int start, int end ) {
4 7 8
9
10 11
12
346
if ( e nd < s t a rt ) { re t u rn - 1 ; } i n t mid = ( start + end ) I 2 ; if ( a rray[mid ] mid ) { return mi d ; } else if ( a rra y [ mid ] > mid ) { ==
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 8 13
-
14
15
Hi 17
I Recursion and Dynamic Programming
}
return magic Fast ( a rray, sta rt , m i d 1); } else { return magicF ast ( a rray, mid + 1 , e n d ) ; }
Follow Up: What if the elements are not distinct?
If the elements a re not distinct, then this a lgorithm fails. Consider the fol lowing a rray:
When we see that A [ mid ] < mid, we cannot conclude which side the magic i ndex is on. It could be on the right side, as before. Or, it could be on the left side (as it, in fact, is). Could it be anywhere on the left side? Not exactly. Since A[ 5 ] = 3, we know that A[ 4] could n't be a magic index. A [ 4] would need to be 4 to be the magic index, but A [ 4] must be less than or equal to A [ 5 ] . I n fact, when we see that A [ 5 ] = 3, we'll need to recursively search the right side as before. But, to search the left side, we can skip a bunch of elements and only recursively search elements A [ 0] through A [ 3 ] . A [ 3 ] is the first element that could be a magic i ndex. The genera l pattern is that we compare m i d l n d e x and midVa l u e for eq uality first. Then, if they a re not equal, we recursively search the left and right sides as follows: •
Left side: search indices sta rt through Mat h . min (mid I n d ex Right side: search indices Math . max ( m i d i n d e x
+
-
1 , midVa l u e )
.
1 , midVa l u e ) through end.
The code below implements this algorithm. 1
2
3 5
i 7
int magicFast ( int [ ] a rray ) { return magic F ast ( array, 0, a rray . length }
1);
int magicFast ( i nt [ ] array, int start , int end ) { if ( end < start ) retu r n - 1 ; int mid!ndex = ( st a rt + end ) I 2 ; int midValue = a r ray [ midi ndex ] ; if ( midVa lue == midindex) { ret u r n mid!ndex ; }
8
9
10 11 12
13
14 15
/* S e a rch l eft * I i nt left!ndex = Math . mi n ( mid!ndex 1 , midVa lue ) ; int l e ft = magic F ast ( a rray, start , left index ) ; if ( left >= 0) { ret urn left ; } -
16
17 18 19
20 21
/* Search right * / int rightindex = Mat h . ma x ( midi ndex + 1 , midValue ) ; int right = magi cF ast ( a rray, right i ndex, end ) ;
22
23
24
25 26
-
retu r n right ; }
CrackingTheCodinglnterview.com I 6th Edition
347
Solutions to Cha pter 8
I Recursion a nd Dynamic Programming
N ote that i n the a bove code, i f the elements a re a l l distinct, the method operates a lmost identically t o the fi rst solution. Power Set: Write a method to return a l l subsets of a set.
8.4
pg 135 SOLUTION
�· -····· ·· ··-·-··
- ··- ··---
We should first have some reasonable expectations of our time and space complexity. How many subsets of a set a re there? When we generate a subset, each element has the "choice" of either being in there or not. That is, for the first element, there are two choices: it is either i n the set, or it is not. For the second, there a re two, etc. So, doing { 2 * 2 * . . } n times gives us 2" subsets. .
Assuming that we're going to be retu rning a list of subsets, then our best case time is actually the total n u m ber of elements across a l l of those subsets. There a re 2" subsets and each of the n elements wil l be conta ined in half of the su bsets (which 2°-1 su bsets). Therefore, the tota l number of elements across all of those subsets is n * 2°-1. We will not be able to beat O ( n 2 " ) i n space o r time complexity. The subsets of { a 1 , a 2 ,
•
•
•
, aJ a re also called the powerset, P ( { a 1 , a 2 ,
•
•
•
, a 0 } ) , or just P ( n ) .
Solution #1 : Recursion
This problem is a good candidate for the Base Case and Build approach. Imagine that we a re trying to find all su bsets of a set l i ke S = { a 1 , a 2 , , aJ . We can sta rt with the Base Case. •
Base Case: n =
•
•
0.
There is just one subset of the empty set: { } . Case: n =
1.
There a re two subsets of the set { a1 } : { } , { a ) . Case: n
=
2.
There a re four su bsets of the set { a1 , a 2 } : { } , { a ), { a ) , { a1 , a J . Case: n
=
3.
Now here's where things get i nteresti ng. We wa nt to find a way of generating the solution for n on the prior solutions. What is the difference between the solution for n deeply:
3
and the solution for n
3 based
2? Let's look at this more
P(2) = P(3) =
{ } , { a 1 } , { a) , { a " a 2 } { } , { a 1 } , { a 2 } , { a3 } , { a 1 , a) , { a 1 , a J , { a 2 , a J , { a 1 , a2, a 3 } The difference between these solutions is that P ( 2 ) is m issing a l l the subsets conta i n i ng a 3• P(3) - P (2)
=
{ a 3 } , { a 1 , a 3} , { a2, a 3 } , {a1 , al , a 3 }
How can we use P ( 2 ) to create P ( 3 ) ? We can simply clone the subsets in P ( 2 ) and add a3 to them: { } , { a1 } , { az } , { a1 , a 2 } P(2) = { a 3 } , { a 1 , a3} , {a2 , a 3 } , { a 1 , al , a 3} P ( 2 ) + a,
348
Cracking the Cod ing I nterview, 6th Edition
Solutions to Chapter 8
I Recursion and Dynamic Programming
When merged together, t h e li nes a bove make P ( 3 ) . Case: n > 0
Generati ng P ( n ) for the general case is just a simple general ization of the above steps. We compute P ( n - 1 ) , clone the resu lts, and then add a n to each of these cloned sets. The following code implements this algorithm: 1
Array list > getSubset s (Array list< I nteger> set, int i ndex ) { Arraylist > allsubset s ; if ( set . s i ze ( ) == index) { // Base case - add empty set 3 4 alls ubsets new Arraylist > ( ) ; allsubset s . add ( new Arraylist < I nteger> ( ) ) ; / / Empty set 5 6 } else { 7 alls ubsets getSubset s ( set, index + 1 ) ; 8 int item = set . get ( index ) ; ' ArrayList > moresubsets new Array list > ( ) ; 10 for (Arraylist < I nteger> s u bset : allsubset s ) { 11 12 Arraylist < I nteger> news ubset new Arraylist < I nteger > ( ) ; 13 news ubset . addAl l ( s ubset ) ; / / 14 news ubset . add ( item ) ; 15 moresubset s . add ( news ubset ) ; 16 } 17 alls ubsets . addAll ( moresubset s ) ; 2
=
=
=
li
}
19 return allsubset s ; 20 }
This solution will be O ( n 2 " ) in time and space, which is the best we can do. For a slight optim ization, we cou ld also im plement this algorithm iteratively. Solution #2: Combinatorics
Wh ile there's nothi ng wrong with the above solution, there's another way to approach it. Reca ll that when we're generating a set, we have two choices for each element: ( 1 ) the element is in the set (the "yes" state) or (2) the element is not in the set (the "no" state). This means that each subset is a sequence of yeses I nos-e.g., "yes, yes, no, no, yes, no" Thi s g ives us 2" possible su bsets. How can we iterate through a l l possible sequences of "yes" / "no" states for a l l elements? If each "yes" can be treated as a 1 and each "no" can be treated as a 0, then each su bset can be represented as a binary stri ng. Generati ng all su bsets, then, really just comes down to generating all binary num bers (that is, a l l i ntegers). We iterate through a l l num bers from 0 to 2" (exclusive) and translate the binary representation of the num bers into a set. Easy! 1 Arraylist > getSubset s Z (Array L i s t < I nteger> set ) { 2 Arraylist > allsubsets new Arraylist > ( ) ; 3 int max 1 < < set . s ize ( ) ; / * Compute 2An */ 4 for ( i nt k = 0 ; k < max ; k++) { 5 Array L i s t < I nteger> subset converti ntToSet ( k, set ) ; a l l s u bsets . add ( s ubset ) ; 6 7 } 8 return allsubset s ; 9 } =
=
=
Crack i n gTheCod i n gl nterv iew.co m I 6th Edition
349
Solutions to Chapter 8 10 11 12
13 14
15 16
17
I Recursion and Dynamic Programming
Array L i st < I nteger> convertintToSet ( i nt x, Array l i st < I ntege r > set ) { Array l i s t < I nteger> subset = new Array L i st < I nteger > ( ) ; i n t i ndex = 0 ; for ( int k = x; k > 0; k > > = 1 ) { if ( ( k & 1) == 1) { s u b s et . ad d ( set . get ( i ndex ) ) ;
18
19 20 21
}
index++ ;
}
return subset ;
} There's nothing substantially better or worse about this solution compared to the first one. 8.5
Recursive Multiply: Write a recu rsive function to multiply two positive integers without using the * operator (or I operator). You can use addition, su btraction, and bit shifting, but you should minimize the number of those operations. pg 13 5
SOLUTION
Let's pause for a moment and think about what it means to do multiplication.
I
This is a good approach for a lot of i nterview q uestions. It's often useful to think a bout what it rea lly means to do somethi ng, even when it's p retty obvious.
We can think about multiplying 8x7 as doing 8+8+8+8+8+8+8 (or adding 7 eight times). We can also think about it as the number of squares in a n 8x7 grid.
Solution #1
How wou ld we count the number of squa res in this grid? We could just count each cell. That's pretty slow, though. Alternatively, we could count half the squares and then double it (by adding this count to itself). To count half the squares, we repeat the same process. Of course, this "doubli ng" only works if the n um ber is in fact even. When it's not even, we need to do the counting/summing from scratch. int minProduct ( i nt a , int b ) { 1 2 int bigger = a < b ? b : a ; 350
Cracki ng the Coding Interview. 6th E d i ti on
Solutions to Chapter 8
int smaller = a < b ? a : b ; ret urn minProd uctHelper( smaller, bigger ) ;
3
4
5
6
I Recursion and Dynamic Programming
}
7
int minProductHelpe r ( int smaller, int b igge r ) { if ( smaller 0 ) { // 0 x bigger = 0 9 return 0; 10 } el se if ( smaller == 1) { // 1 x b igger b igger ret urn b igger; 11 12 } B
==
13
14 15
/* Compute half . If uneven, compute ot her half . If even, double i t . * / i n t s = smaller > > 1 ; / / Divide b y 2 int sidel = min Prod uct ( s , bigge r ) ; int side2 = s i d e l ; if ( smaller % 2 == 1 ) { s ide2 = minProductHelpe r ( smaller - s , bigger ) ; }
16
17 18 19 20 21 22
23
return s idel }
+
side2;
Ca n we do better? Ye s. Solution #2
If we observe how the recursion operates, we'll noti ce that we have d u plicated work. Consider this e xa mpl e : minProd uct ( 1 7 , 23 ) minProduct ( S , 2 3 ) min Product (4, 23 ) * 2 +
min Prod uct ( 9 , 23 ) minProduct (4, 23 ) +
minProduct ( S , 23 )
The second ca ll to m i n Prod u c t ( 4, 2 3 ) i s unawa re of the p rior cal l, a nd so it repeats the same work. We should ca che these results. 1
2
3 4 5
6
int minProduct ( int a , int b) { int bigge r = a < b ? b : a ; int smaller = a < b ? a : b ; int memo [ ] = new int [ smaller + 1 ] ; ret urn min Prod uct ( smaller, b igger, memo ) ;
7 } 8 9 int minProduct ( int smaller, int bigger, i nt [ ] memo ) { if ( smaller == 0) { 10 return 0; 11 12 } el se if ( smaller = = 1) { 13 return b igge r; 14 } el se if ( memo [ smalle r ] > 0 ) { ret u r n memo [ smaller ] ; 15 16 } 17
18
/ * Compute h a lf . If uneven, compute other half . If even, double i t . * /
CrackingTheCod ingl nterview.com I 6th Edition
3S1
Solutions to Chapter 8 19
int s = smaller >> 1; II Divide by 2 int s idel = minProduct ( s , bigger, memo ) ; I I Compute ha lf i n t s ide2 = s idel; if ( smaller % 2 = = 1 ) { side2 = minProduct ( smaller - s , bigger , memo ) ;
20 21 22 23
24
}
25
26
/* Sum and cache . */ memo [ sma ller] = sidel return memo [ smaller ] ;
27
28 29
I Recursion and Dynamic Prog ramming
+
side2;
}
We can stil l make this a bit faster. Solution #3
One thing we m ig ht notice when we look at this code is that a ca l l to min Prod u c t on an even number is much faster than one on a n odd number. For example, if we ca ll min Product ( 30, 35 ) , then we'll just do m i n P r od u ct ( 1 5 , 3 5 ) and double the result. However, if we do m i n P r od u c t ( 3 1 , 35 ) , then we'll need to call m i n P roduct ( 1 5 , 35 ) and minProd u c t ( 1 6 , 3 5 ) . This i s unnecessa ry. Instead, we can do: minProd u c t ( 31 , 35 ) = 2 * minProd u c t ( 1 5 , 3 5 ) After a l l, since 31
=
2 * 1 5+1, then 31x35
=
+
35
2 * 1 5 * 3 5+35.
The log ic i n this final solution is that on even numbers, we just d ivide sma l l e r by 2 and double the result of the recursive ca ll. On odd num bers, we do the sa me, but then we also add b igge r to this result. In doing so, we have an un expected "wi n!' Our minProduct fu nction just recurses stra ight downwa rds, with increasingly small numbers eac h time. It will never repeat the same call, so there's no need to cache any information. 1
int minProduct ( i nt a , int b ) { int bigger = a < b ? b : a ; int smaller = a < b ? a : b ; return minProductHelpe r ( smaller, bigge r ) ; }
2
3
4
5
6
7
8 9
10
int minProductHelpe r ( int smaller , int bigge r ) { if ( sma ller == 0 ) return 0; else if ( smaller == 1 ) return bigger;
11
int s = smaller > > 1; II Divide by 2 int halfProd = minProductHelpe r ( s , bigge r ) ;
12 13
14
if ( sma ller % 2 == 0 ) { return halfProd + halfProd ; } else { return halfProd + ha lfProd + bigger ;
15
16
17 18 19
}
}
This a lgorithm wil l ru n in 0 ( log s ) time, where s is the smaller of the two numbers.
3 52
Cracking the Coding Interview, 6th Edition
Solutions to Cha pter 8 8.6
I Recu rsion a nd Dynamic Programming
Towers of Hanoi: In the classic problem of the Towers of Hanoi, you have 3 towers and N disks of different sizes which can slide onto any tower. The puzzle sta rts with disks sorted in ascending order of size from top to bottom (i.e., each disk sits on top of an even larger one). You have the fol lowing constraints:
(1 )
Only one disk can be moved at a time.
(2) A disk is slid off the top of one tower onto another tower. (3)
A
disk cannot be placed on top of a smaller d isk.
Write a prog ram to move the disks from the fi rst tower to the last using Stacks. pg 1 35
SOLUTION
This problem sou nds like a good candidate for the Base Case and Build approach.
Let's start with the smallest possible exa mple: n Case n 1.
=
1.
=
1. Can we move Disk 1 from Tower 1 to Tower 3? Yes.
=
2 . Can we move Disk 1 and Disk 2 from Tower 1 to Tower 3? Yes.
We sim ply move Disk 1 from Tower 1 to Tower 3 .
Case n 1.
Move Disk 1 from fower 1 to Tower 2
2.
Move Disk 2 from Tower 1 to Tower 3
3. Move Disk 1 from Tower 2 to Tower 3
Note how in the above steps, Tower 2 acts as a buffer, holding a disk while we move other d isks to Tower 3. Case n 1.
=
3. Can we move Disk 1 , 2, and 3 from Tower 1 to Tower 3 ? Yes.
We know we can move the top two disks from one tower to another (as shown earl ier), so let's assume we've already done that. But instead, let's move them to Tower 2.
2. Move Disk 3 to Tower 3. 3. Move Disk 1 and Disk 2 to Tower 3. We a l ready know how to do this-just repeat what we did in Step 1 .
Case n 1.
=
4. Can we move Disk 1 , 2, 3 and 4 from Tower 1 to Tower 3? Yes.
Move Disks 1 , 2, and 3 to Tower 2. We know how to do that from the earlier examples.
2. Move Disk 4 to Tower 3.
3. Move Disks 1 , 2 and
3
back to Tower 3.
Remember that the labels ofTower 2 and Tower 3 aren't i m portant. They're equ iva lent towers. So, moving disks to Tower 3 with Tower 2 serving as a buffer is equivalent to moving disks to Tower 2 with Tower 3 serving as a buffer.
CrackingTheCodinglnterview.com I 6th Edition
3S3
Solutions to Chapter 8
I Recursion and Dynamic Programming
This approach leads t o a natural recursive a lgorithm. I n each pa rt, we are doing the following steps, outli ned below with pseudocode: 1 moveDisks ( i nt n , Tower origi n , Tower dest ination, Tower buffer) { 2 / * Base ca s e * / if ( n < = 0 ) ret u r n ; 3 4 s
/ * move top n - 1 d i s ks from origin to buffer, using destination a s a buffe r . */ moveDi s k s ( n - 1, origin, buffe r, destination ) ;
6
7 8 9 10
/ * move top from origin to destination moveTop( origin, destination ) ;
11
/ * move t o p n - 1 d i s k s f r o m buffer to destinat ion, u s i ng origin a s a buffe r . * / moveDisks ( n - 1 , buffer, dest ination, origin ) ;
12
13
} The fol lowing code provides a more detai led i m plementation of this algorithm, using concepts of object oriented design. 1
void ma i n (String [ ] a rgs ) { int n = 3 ; Tower [ ] towers = new Tower [ n ] ; for ( i nt i = 0; i < 3 ; i++ ) { towers [ i ] = new Tower ( i ) ; }
2 3
4 5
6 7 8
9 10 11
12 }
13
14 15 16
17
18 19
20 21
for ( i nt i = n - 1 ; i > = 0; i - - ) { towers [ 0 ] . add ( i ) ; } towers [ 0 ] . moveDi s k s ( n , towe rs [ 2 ] , towe rs [ l ] ) ;
cla s s Towe r { private Stack< I nteger> disks ; private int index; public Tower ( i nt i) { disks new St a c k < I nteger> ( ) ; index = i ; }
22 23
24
25 26 27
28 29 30 31 32
33
34. 35
36
37 38
354
public int i ndex ( ) { ret urn i ndex ; } public void add ( i nt d) { if ( ! d i s k s . i s E mpty ( ) && d i s k s . peek ( ) boxes ) { Collections . sort ( boxes , new BoxComparato r ( ) ) ; int [ ] stackMap = new int [ boxes . s ize ( ) ] ; return createSt a ck ( boxes , n u l l , 0 , s t a c kMa p ) ;
1
2
3 4
5
}
8
int createSt a c k (ArrayList< Box > boxes , Box bottom, int offset, i nt [ ] stackMa p ) { if ( offset > = boxes . s i ze ( ) ) return 0 ; / / Base c a s e
6 7 9
10 11
12 13 14 15
16
17
18
19 20
/ *height with this bottom */ Box newBottom = boxes . get ( offset ) ; i n t heightWithBottom = 0 ; if ( bottom == null I I newBottom . canBeAbove ( bottom ) ) { if (sta ckMa p [ offset ] == 0) { s t a c kMa p [ offset ] = createStack ( boxes , newBottom, offset + 1 , stackMa p ) ; stackMa p ( offset ] += newBottom . height ; } heightWithBottom = stackMa p ( offset ] ; }
21 22
/ *without t h is bottom * / int heightWit hout Bottom = createSt a c k ( boxes , bottom, offset
24 25
/* Return better of two options . */ return Mat h . ma x ( heightWithBottom, heightWit houtBottom ) ;
23
26
+
1, stackMa p ) ;
} Again, pay close attention to when you recall and insert values i nto the hash table. It's typi ca l ly best if these a re symmetric, as they a re in lines 1 5 and 1 6- 1 8. 8.1 4
1 (true), & (AN D), I (OR), and " (XOR), and a desired boolean result value r e s u lt, implement a fun ction to count the number of ways of parenthesizing the expression such that it evaluates to r e s u lt. The expression s hould be fu lly parenthes ized (e.g .. ( 0 ) " ( 1 ) ) but not extraneous ly (e.g .. ( ( ( 0 ) ) " ( 1 ) ) ) . Boolean Evaluation: Given a boolea n expression consisti ng of the symbols 0 (fa lse),
EXAMPLE
countE val ( " 1"0 l 0 l 1 " , f a l s e ) - > 2 count E val ( " 0&0&0&1"l l 0 " , t r u e ) - > 10
pg 1 3 6
SOLUTION
As in other recurs ive problems, the key to this problem is to figure out the relations hip betwee n a problem and its s u bproblems. Brute Force
Consider an expression like 0"0&0"1 1 1 and the target result countEval ( 0"0&0"1 1 1 , true) into smaller problems?
true.
How can we break down
We could just essentially iterate through each possi ble place to put a parenthesis.
368
Cracking the Coding I nterview, 6th Edition
Solution s to Chapter 8 countEva l ( 0A0&0"l l l , t r u e ) = count E va l ( 0A0&0" l l l where + count E va l ( 0A0&0Al l l where + count E va l ( 0A0&0" l l l where + countEva l ( 0"0&0"l l l where
paren paren pa ren pa ren
I Recursion and Dynamic Programming
a round a round a round a round
char 1, char 3 , char 5, c h a r 7,
true ) true) true) true)
Now what? let's look atjust one of those expressions-the paren a round c h a r 3. This gives us ( 0"0 )&( 0"1 ) . I n order to make that expression true, both the left and right sides must be true. So: left = "0"0" right = "0Al l 1" count E va l ( left & right , true)
=
count E va l ( left , true ) * countEva l ( right , true )
The reason we mu ltiply the resu lts of the left and rig ht sides is that eac h result from the two sides can be paired u p with each other to form a unique com bination. Each of those terms can now be decomposed i nto smaller problems i n a similar process. What happens when we have an " l " (OR)? Or a n " N' (XOR)? If it's an OR, then either the left or the right side must be true-or both. count Eval ( left I right , t rue ) countEva l ( left , t rue ) * countEva l ( right , fa l s e ) + count E va l ( left , false ) * countEva l ( right , true ) + count E va l ( left , t rue ) * count E va l ( right , true ) If it's an XOR, then the left or the right side can be true, but not both. countEva l ( left " right , true ) = count Eva l ( left , t rue ) * countEva l ( right , false ) + count E va l ( left , f a l s e ) * countEva l ( right , true ) What if we were trying to make the result f a l s e instead? We can switc h countEva l ( left & right , f a l s e ) count E va l ( left , t r u e ) + count Eva l ( left , fa lse ) + count E va l ( left , f a l s e ) count E va l ( left , false ) countEva l ( left right , f a l s e ) countEva l ( left " right , f a l s e ) count E va l ( left , false ) + count E val ( left , t rue )
up the logic from above: * countEva l ( right , f a l s e ) * countEva l ( right , t rue ) * countEva l ( right , fa l s e ) * countEva l ( right , fa l s e ) * countEva l ( right , fa lse ) * count Eva l ( right , true )
Alternatively, we can just use the same logic from a bove and su btract it out from the total number of ways of evalu ating the expression. tot a l E va l ( left ) = countEva l ( left , true ) + countEva l ( left , f a l s e ) tota l E va l ( right ) = countEva l ( right , t r u e ) + count E va l ( right , fa l s e ) total Eval (expres sion ) tot a l Eval ( left ) * tota lEva l ( right ) countEva l ( expres s i o n , fa lse ) = tota l Eval (express ion ) - countEval (exp res sion, t r u e ) =
This makes the code a b i t more concise. 1
2
3 4 s 6
7 8
9
Ul
11
12 13
14
int countEva l ( String s , boolean res u lt ) { if ( s . length ( ) 0 ) return 0 ; if ( s . length ( ) == 1 ) return stringToBoo l ( s )
res ult ? 1
0 J·
i nt ways = 0 ; for ( int i = 1 ; i < s . length ( ) ; i += 2 ) { c h a r c = s . c h a rAt ( i ) ; String left = s . substring ( 0 , i ) ; String right = s . s ubstring ( i + 1 , s . length ( ) ) ; /* Eval uate each side for each res u lt . * / l e ft Tru e = count E va l ( left , true ) ; int leftFalse countEva l ( left , false ) ; int rightTrue = countEva l ( right , t rue ) ;
int
CrackingTheCodinglnterview.com I 6th Edition
369
Solutions to Chapter 8 15
int rightFalse = countEva l ( r ight, false) ; int tota l ( leftTrue + left False) * ( r ightTrue
16
=
17
18 19
21
22
23
24
25 26 27 28 29
right False ) ;
}
30
}
31
int s ubways = result ? tota lTrue ways += s ubways ;
total - totalTrue;
return ways ;
33
}
36
boolean stringToBool ( String c) { return c . equals ( "l") ? true : false;
34 35
+
int tota lTrue = 0 ; if (c == ' " ' ) { I I required : one true and one false tota lTrue = leftTrue * right False + left F a lse * rightTrue ; } else if (c == '&' ) { // required : bot h true tota lTrue = leftTrue * rightTrue; } else if (c == ' I ' ) { I I required : anything but both fa lse totalTrue = leftTrue * rightTrue + leftFalse * rightTrue + leftTrue * right F a l s e ;
20
32
I Recursion and Dynamic Programming
37 }
Note that the tradeoff of computing the fa l s e res ults from the t ru e ones, a nd of computing the { l eftTrue , rightTru e , left F a l s e , a n d right F a l s e } va lues u pfront, is a small amount of extra work in some cases. For example, if we're looking for the ways that a n AND (&) can result in t ru e, we never would have needed the left F a 1 s e and rightF a l s e results. Likewise, if we're looking for the ways that an OR ( I ) can res u lt in fa lse, we never would have needed the leftTrue and ri ght T r u e results. Our current code is blind to what we do and don't actually need to do a nd instead just com putes all of the values. This is probably a reasonable tradeoff to m a ke (especially given the constraints of whiteboa rd coding) as it makes o u r code substa ntially shorter and less tedious to write. Whichever a pproach you make, you s hould discuss the tradeoffs with your interviewer. That sa id, there are more im portant optimizations we can make. Optimized Solutions
If we follow the recurs ive path, we'll note that we end up doing the same com putation repeatedly. Consider the expression 0"0&0"1 I 1 and these recursion paths: •
Add parens a round char 1 . ( 0) " ( 0&01\1 I 1 ) »
•
Add parens a round char 3. ( 0 ) " ( ( 0 ) & ( 01'1 I 1 ) )
Add parens a round char 3. ( 01\0 ) & ( 01\1 I 1 ) »
Add parens a round char 1 . ( ( 0 ) " ( 0 ) ) & ( 01\1 I 1 )
Although these two expressions are different, they have a similar component: ( 0"1 I 1 ) . We should reuse our effort on this. We can do this by using memoization, or a has h table. We just need to store the result of count Eval ( expres sion , resu lt) for each expression and result. If we see an expression that we've calculated before, we just return it from the cache. int count�val ( St ring s , boolea n result , HashMap memo ) { 1 1 if ( s . length ( ) 0 ) ret u r n 0; 3 if ( s . lengt h ( ) == 1 ) return st ringToBoo l ( s ) == result ? 1 : 0; ==
370
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 8
I Recursion and Dynamic Programming
4 5
if ( memo . conta insKey ( res ult + s ) ) return memo . get( res ult
8
for ( i n t i = 1 ; i < s . length ( ) ; i += 2) { char c = s . charAt ( i ) ; String left = s . s ubstring(0, i ) ; String right = s . s ubstring( i + 1, s . lengt h ( ) ) ; int leftTrue = count Eval ( left , true, memo ) ; int leftFalse count Eval ( left, false, memo ) ; int rightTrue countEva l ( right , true, memo) ; int right F a l s e = countEva l ( right, false , memo ) ; int total = ( leftTrue + leftFalse ) * ( r ightTrue + rightFalse ) ;
6 7
9
10 11
12
13
14 15
16
17
=
=
int tota lTrue = 0 ; if ( c " ) { tota lTrue = leftTrue } else if ( c == '&' ) { totalTrue = leftTrue } els e if (c == I ) { totalTrue = leftTrue leftTrue }
19
==
20 21 22
23 25
26 27 28
32 33
34
'
'
'
24
30 31
s);
int ways = 0;
18
29
+
=
'
int s ubways res ult ways += s ubways ;
* right F a ls e
+
leftFalse * rightTrue ;
* rightTrue ;
* rightTrue + leftFalse * rightTrue + * right F a l s e;
tota lTrue
total - tota lTrue ;
}
memo . put( res ult return ways ;
+
s , ways ) ;
} The added benefit of this is that we could actually e nd up with the same su bstring in mu ltiple parts of the expression. For example, an expression like 0A1 A0&0J\1 A0 has two insta nces of 01\1 J\0. By caching the result of the substring value i n a memoization ta ble, we'll get to reuse the result for the rig ht part of the express ion after computing it for the left. There is one further optimization we can make, but it's far beyond the scope of the i nterview. There is a closed form expression for the number of ways of pare nthesizing a n expression, but you wou ld n't be expected to know it It is give n by the Catalan num bers, where n is the number of operators:
C
n
=
( 2n ) ! (n+l) ! n !
We could use this to com pute the total ways of eva luating the expression. Then, rather than computing l eftT r u e and l eft F a l s e, we j ust compute one of those and ca lcu late the other using the Catalan numbers. We would do the same thi ng for the rig ht side.
CrackingTheCoding lnterview.com I 6th Editio n
371
9 Solutions to System Design and Sca l a b i l ity
Stock Data: I magine you a re building some sort of service that will be called by up to l ,000 client applications to get simple end-of-day stock price i nformation (open, close, high, low). You may assume that you already have the data, and you can store it in a ny format you wish. How would you design the client-facing service that provides the information to client applications? You a re responsible for the development, rollout, and ongoing monitoring and maintenance of the feed. Describe the different methods you considered and why you would recommend your approach. You r service can use any technologies you wish, and can distri bute the i nformation to the client applications in any mechanism you choose.
9.1
pg 1 44
SOLUTION
From the statement of the problem, we want to focus on how we actually distribute the information to clients. We ca n assume that we have some scri pts that magically collect the information. We want to start off by thinking about what the different aspects we should consider in a given proposal a re: •
Client Ease of Use: We want the
service to be easy for the clients to implement and useful for them.
Ease for Ourselves: This
service should be as easy as possible for us to implement, as we shouldn't im pose unnecessa ry work on ourselves. We need to consider in this not only the cost of implementing, but also the cost of mai ntena nce. This problem is stated in a "what would you do in the real world" way, so we should think like we would in a real-world problem. Ideally, we do not want to overly constrain ourselves i n the implementation, such that we can't be flexible if the requirements or demands change.
Flexibility for Future Demands:
•
Scalability and Efficiency:
We should be mindful of the efficiency of our solution, so as not to overly
burden our service. With this framework in mi nd, we can consider various proposals. Proposal #1
One option is that we could keep the data in simple text files and let clients download the data through some sort of FTP serve( This wou l d be eas y to maintain i n some sense, since files Gm be easily viewed and backed up, but it would require more complex parsing to do any sort of query. And, if additional data were added to our text file, it might brea k the clients' pa rsing mechanism.
3 72
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 9
I System Design and Scalability
Proposal #2
We could use a standa rd SQL database, and let the clients plug directly into that. This would provide the fol lowi ng benefits:
•
Facilitates an easy way for the clients to do query processing over the data, in case there a re additional fe atu re s we need to support. For example, we cou ld easily and efficiently perform a query such as "return all stocks havi ng an open price greater than N and a closi ng price less than M:' Rolling back, backi ng u p data, and secu rity could be provided using standard database features. We don't have to "rei nvent the wheel;' so it's easy for us to im plement. Reasonably easy for the clients to integ rate i nto existing appl ications. SQL i ntegration is a standard feature i n software development environments.
What are the disadvantages of using a SQL database? It's much heavier weight than we really need. We don't necessarily need all the complexity of a SQL backend to support a feed of a few bits of information. It's difficult for humans to be able to read it, so we'll l i kely need to im plement a n additional layer to view and maintai n the data. This increases our im plementation costs. •
Secu rity: While a SQL database offers pretty wel l defi ned security levels, we would sti l l need to be very ca reful to not give clients access that they shou ldn't have. Additionally, even if clients a re n't doing a nything "malicious;' they might perform expensive and i nefficient queries, and our servers wou ld bear the costs of that.
These disadvantages don't mean that we should n't provide SQL access. Rather, they mean that we should be awa re of the disadvantages. Proposal #3
XML is another g reat option for distributing the i nformation. Our data has fixed format and fixed size: c ompany_n ame, open, h igh, low, c l o s i ng p r i c e. The XML could look like this: 1. < root > 2
< company name="foo"> 3 4 < open > l.26 . 23 5 < high > l. 3 0 . 27 < low> l.22 . 83 < / low> 6 7 < clos i ngPrice>l.27 . 30< /clos i ngPrice> 8 < /compa ny> 9 < c omp a ny name="bar"> 10 < open >52 . 73 < high >60. 27 11 12 < low>50 . 29 < / low> 13 < c l o s ing P ri ce > 54 . 9 1 < / c lo s i ng P ri c e > 14 < / c om p a ny > < /date> 15 < d a t e value="2008 - 10 - 11"> . . . < /date> 16 17 < / root > The advantages of this a pproach i nclude the fol lowing: It's very easy to distribute, and it can also be easily read by both machines and huma ns. This is one reason that XML is a standard data model to share and distribute data. Most languages have a libra ry to perform X M L parsing, so it's reasonably easy for clients to im plement.
CrackingTheCodingl nterview.com I 6th Edition
373
Solutions to Chapter 9
I System Desig n and Scalability
We can add new data to the XML file by adding additional nodes. This would not break the client's parser (provided they have implemented their parser in a reasonable way). •
S ince the data is being stored as XML files, we can use existing tools for backing up the data. We don't need to im plement our own backup tool.
The disadvantages may i nclude: •
This solution sends the clients all the information, even if they only want part of it. It is i nefficient in that way. Performing any queries on the data requires parsing the entire file.
Regard less of which solution we use for data storage, we could provide a web service (e.g., SOAP) for client data access. This adds a layer to our work, but it can provide additional security, and it may even make it easier for clients to integrate the system. However-and this is a pro and a con-clients will be limited to gra bbing the data only how we expect or want them to. By contrast, in a p u re SQL im plementation, clients could query for the highest stock price, even if this wasn't a procedure we "expected" them to need. So which one of these wou ld we use? There's no clear answer. The pure text file solution is probably a bad choice, but you can make a compelling argument for the SQL or XML solution, with or without a web service. The goal of a question like this is not to see if you get the "correct" answer (there is no single correct answer). Rather, it's to see how you design a system, and how you eval uate trade-offs. 9.2
Social Network: How would you design the data structures for a very large social network like
Facebook or Lin ked ln? Describe how you wou ld design a n algorithm to show the shortest path between two people (e.g., Me -> Bob -> Susan -> Jason -> You). pg 1 45
SOLUTION
A good way to approach this problem is to remove some of the constraints and solve it for that situation first. Step 1 : Simplify the Problem-Forget About the Millions of Users
First, let's forget that we're dealing with m i l lions of users. Design this for the simple case. We can construct a graph by treating each person as a node and letting an edge between two nodes indi cate that the two users are friends. lf l wanted to find the path between two people, I cou ld start with one person and do a sim ple breadth-first search. Why would n't a depth-fi rst search work well? First, depth-first search would just find a path. It wou ld n't necessarily find the shortest path. Second, even if we just needed any path, it would be very inefficient. Two users m ight be only one degree of separation apa rt, but I could search millions of nodes i n thei r "subtrees" before finding this relatively immed iate con nection. Alternatively, I could do what's called a bidirectional br�adth-first sea rch. This means doing two breadth first searches, one from the source and one from the destination. When the searches collide, we know we've found a path.
3 74
I
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 9
I System Design and Scalability
I n the i m p le m e n ta tio n, we'll use two classes to help us. BFSData h o ld s the data we need for a breadth-first search, such as the i sVi s ited hash table and the toVi s i t queue. P a t h Node will represent the p a t h as we're search ing it, storing e a c h P e r s o n and the previous Node we visited in this pat h .
1 2
3 4
i; 6
Linked l i st < Person> findPathBiBFS ( Ha s hMap people, int source, int desti nat ion) { B F SData sourceData = new B FSDat a ( people . get ( source ) ) ; B F SData destData new BF SDat a ( people . get (destination ) ) ; =
while ( ! sourceData . i s F i nished ( ) && ! destData . is F inished ( ) ) { I *Search out from source . * / Person collision = search leve l ( people, sourceData, destData ) ; if ( collision ! = n u l l ) { ret urn mergePath s ( sourceDat a , destData , collision . getID ( ) ) ; }
7 8
9
10 11
12 13
I *Search out from destination . */ collision = sear c h Leve l ( people, destData , sou rceData ) ; if ( collision ! = n u l l ) { ret u r n mergePath s ( sourceData , destData , collis ion . getID ( ) ) ; }
14
15 16
17
18
19
20 }
} ret u r n n u l l ;
21
22
/ *Sea rch o n e l e v e l and return collision, if a ny . * / 23 Person sea rch Level ( H a s hMap< Intege r , Person> peo p l e , BFSData primary, B F SData second a r y ) { 24 25 I *We only want to search one level at a t ime . Count how many nodes a re 26 * c u r rently in the primary ' s leve l and only do that many nodes . We ' l l cont i n ue * to add nodes to the end . * / 27 28 int count = primary . toVis it . s i ze ( ) ; for ( i nt i = 0; i < count ; i++ ) { 29 30 I *Pull out first node . */ 31 Pat hNode pat hNode p rimary . toVisit . poll ( ) ; 32 int personid = pathNode . getPerson ( ) . getID ( ) ; =
33
/ *Check if it ' s a l ready been v i s ited . */ if ( seconda ry . vi s ited . cont a i ns Key ( personid ) ) { return pathNode . get Person ( ) ; }
34
35
36 37
38 39
I
*Add friends to q ueue . */ Person pe rson pathNode . get Person ( ) ; Array l i s t < I ntege r > friends = person . get F riends ( ) ; fo r ( int friendid : friend s ) { if ( ! primary . vi s ited . cont a i n s Key ( friend id ) ) { Person friend = people . get ( friendld ) ; PathNode next new PathNode ( friend , pathNode ) ; primary . vi s ited . put ( friend l d , next ) ; primary . toVi s i t . add ( next ) ; } }
40 41
42 43
mergePaths ( BFSData bfs l , BFSData bfs 2 , i n t connection ) { PathNode endl = bfs l . visited . get ( connection ) ; / / endl - > source PathNode e n d 2 = bfs 2 . visited . get ( connection ) ; // e n d 2 - > dest Linked l i s t < Person> pathOne = end l . collapse ( fa ls e ) ; Linked list < Person> pat hTwo = end2 . collaps e ( t r ue ) ; // reverse pathTwo . remove First ( ) ; / / remove connection pathOne . a ddAl l ( pathTwo ) ; // add second path ret urn pathOne ; }
54
55
56 57
58
59 60 61
62 63
64
c l a s s PathNode { private Person person = n u l l ; 67 private Pat hNode previousNode = n u l l ; public PathNod e ( Person p, PathNode previous ) { 68 69 person = p ; 70 previousNode = previous ; 71 } 72 public Person getPerson ( ) { return perso n ; } 73 74 75 public Linked l i s t < Person> collaps e ( boolean sta rtsWithRoot ) { 76 Linkedlist < Person> path = new Linked list< Person > ( ) ; PathNode node = t h i s ; 77 while ( node ! = null ) { 78 79 if ( st a rtsWit hRoot ) { 80 path . addlast ( node . person ) ; 81 } else { 82 path . addFirst ( node . person ) ;
65
66
83
}
84
node = node . previo usNode; } ret urn pat h ;
85
86 87 88 89
}
}
90 c l a s s B F SData { p u b l i c Queue< PathNode> toVi s it = new Linked list< PathNode > ( ) ; 92 p u b l i c HashMa p < I nteger, PathNode> vis ited 93 new Ha s hMa p < I ntege r , Pat hNode > ( ) ; 91
94 95
96 97
98 99
100
101 102
103
104 }
public BF SDat a ( Person root ) { PathNode sourcePath = new Pat hNod e ( root , nul l ) ; toVi s it . a dd ( sourcePat h ) ; vis ited . put ( root . getID ( ) , sourcePat h ) ; } public boolean i s F inished ( ) { ret urn toVis it . is Empty ( ) ; }
Many people are surprised that this is faster. Some quick math can explai n why. S uppose every person has k fri e n d s, a n d n ode S a n d n ode D have a friend C in common.
•
Traditional breadth-first sea rch from S to D: We go through roughly k+k * k n odes: each of S's k fri end s and then each of their k friends. 376
Cracking the Coding Interview, 6th Edition
,
Solutions to Chapter 9
I System Design and Scalability
Bid irectional breadth-first search: We go through 2k nodes: each of S's k friends and each of D's k friends. Of course, 2k is much less than k+k * k . Genera lizing t h i s t o a path of length q, we have this: BFS: O ( k q ) Bidirectional BFS: 0 ( kq 1 2 + kq1 2 ) , which is just 0 ( kq1 2 ) If you i mag i ne a pat h l i ke A - > 8 - > C - > D - > E where each person has 1 00 friends. this is a big difference. BFS will require looking at 1 00 m i l l ion (1004) nodes. A bidi rectional BFS will require looking at only 20,000 nodes (2 x 1002).
A bidirectional BFS will generally be faster than the traditional BFS. However, it requires a ctua l ly having access to both the sou rce node and the destination nodes, which is not a lways the case. Step 2: Handle the Millions of Users
When we deal with a service the size of Linkedln or Facebook, we cannot possibly keep a l l of our data on one machine. That means that our simple Person data structure from a bove doesn't quite work-our friends may not l ive on the same machine as we do. Instead, we ca n re place our list of friends with a list of their IDs, and traverse as follows: 1 . For each friend ID: i nt ma c h i n e_i n d ex 2.
Go to machine #ma c h i ne_index
3.
On that machine, do: Person friend
=
=
getMa c h i neIDForUs e r ( person I D ) ;
get Pers onWit h I D ( pe r s on_i d ) ;
The code below outlines this process. We've defi ned a class Server, which holds a list of a l l the machi nes, and a class Ma c h i ne, which represents a single machine. Both classes have hash tables to efficiently looku p data. 1 2
3
4 5 6
c la s s Serve r { Ha s hMa p < I ntege r , Machine> mac h i nes = new Ha s hMa p < I nteger, M a chi n e > ( ) ; Ha s hMap personToMac h i neMap = new Has hMa p < I ntege r, I ntege r > ( ) ; public Mac h i ne getMac h ineWit hid ( int machineID) { ret u rn machines . get ( ma c hineID ) ; }
7 8 9
public int getMa c h i neIDForUse r ( int personID) { I ntege r machineID = perso nToMa chineMa p . get ( personID ) ; ret u r n ma c h i n e I D null ? - 1 : m a c h i ne I D ; }
10
11
= =
12 13
14
public Person getPersonWit h I D ( int personID) { I ntege r machineID = personToMac h i neMa p . get ( personID) ; if ( ma c h i neID n u l l ) retu rn n u l l ;
1 :; 16 17
==
18 19
Machine machine = getMa c h i neWit h i d ( ma ch i neID) ; if ( ma chine == n u l l ) ret u rn null ;
20
21 22
}
return machine . getPersonWit h I D ( personID) ;
23
}
25
c l a s s Person {
24
CrackingTheCodinglnterview.com I 6th Edition
377
Solutions to Chapter 9 26
private Array l i st < I ntege r > friends private int personID; private String i nfo ;
27
28
29 30
pu b l i c pu b l i c p u bli c pub l ic public public
31
32
33
34 35 36
I System Design and Scalability =
new Arraylist < I nteger > ( ) ;
Person ( i nt id) { t h i s . personID id ; } String getinfo ( ) { return info ; } void set l nfo ( String i nfo) { thi s . i nfo info; } Ar raylist< I nteger > get F riend s ( ) { return friends; } int getID ( ) { return personID; } void addF riend ( int i d ) { friends . ad d ( id ) ; } =
=
}
There a re more optimizations and follow-u p questions here than we could possibly discuss, but here a re just a few possibilities. Optimization: Reduce machine jumps
J u mping from one machine to another is expensive. Instead of ra ndomly jumping from machine to machine with each friend, try to batch these j u mps-e.g., if five of my friends live on one machine, I shou ld look them up all at once. Optimization: Smart division of people and machines
People are much more likely to be friends with people who live in the same cou ntry as they do. Rather than ra ndomly dividing people across machines, try to divide them by country, city, state, and so on. This will red uce the number of jumps. Question: Breadth-first search usually requires "marking" a node as visited. How do you do that in this case?
Usual ly, in BFS, we mark a node as visited by setting a v i s it e d flag in its node class. Here, we don't want to do that. There could be multiple searches going on at the same time, so it's a bad idea to just edit our data. I nstead, we could mimic the marking of nodes with a hash ta ble to look u p a node id and determine whether it's been visited. Other Follow-Up Questions:
In the real world, servers fail. How does this affect you? How could you take advantage of caching? Do you sea rch u nti l the end of the graph (i nfin ite) ? How do you decide when to give u p? I n rea l life, some people have more friends of friends than others, and a re therefore more likely to make a path between you and someone else. How cou ld you use this data to pick where to sta rt traversing? These a re just a few of the fol low-up questions you or the i nterviewer could raise. There are many others. 9.3
Web Crawler: If you were designing a web crawler, how wou ld you avoid getting i nto infinite loops? pg 145
SOLUTION
The first thing to ask ourselves i n this problem is how a n infi nite loop might occur. The simplest answer is that, if we pictu re the web as a g ra p h of l i n ks, a n infinite loop will occur when a cycle occurs.
378
Crack i n g the Coding Interview, 6th Edition
Solutions to Chapter 9
I System Design and Scalability
To prevent infi nite loops, we just need to detect cycles. One way to do this is to create a hash table where we set h a s h [ v ] to t r u e after we visit page v. We can crawl the web using breadth-first search. Each time we visit a page, we gather all its links and insert them at the end of a queue. If we've a l ready visited a page, we ignore it. This is great-but what does it mean to visit page v ? Is page v defi ned based on its content or its U RL? If it's defined based on its URL we must recog nize that URL parameters might indicate a completely different page. For example, the page www . ca reerc up . com/page ? pid=mic rosoft - in t e rview q u e s t i ons is totallyd ifferent from the page www . c a re e r c u p . c om/p age ? p i d=googl e - interv iew q u estions. But, we can also a ppend URL parameters a rbitra rily to any URL without truly changing the page, provided it's not a pa rameter that the web application recognizes and hand les. The page www . c a r ee r c u p . c om ?foob a r=hel l o is the same as www . c a re e r c u p . c om. "Okay, then;· you might say, "let's defi ne it based on its content:' That sounds good too, at fi rst, but it a lso doesn't qu ite work. Suppose I have some ra ndomly generated content on the careercup.com home page. Is it a different page each time you visit it? Not real ly. The rea lity is that there is probably no perfect way to defi ne a "different" page, and this is where this problem gets tricky. One way to tackle this is to have some sort of estimation for deg ree of similarity. If, based on the content and the URL, a page is deemed to be sufficiently similar to other pages, we deprioritize crawl ing its children. For each page, we wou ld come up with some sort of sig natu re based on snippets of the content and the page's URL. Let's see how this would work. We have a database which stores a list of items we need to crawl. On each iteration, we select the hig hest priority page to crawl. We then do the fol lowing: 1 . Open up the page and create a signature of the page based on specific su bsections of the page and its U RL. 2. Query the database to see whether anything with this signature has been crawled recently. 3.
If something with this signature has been recently crawled, insert this page back into the database at a low priority.
4. If not, crawl the page and insert its links into the database.
Under the a bove im plementation, we never "complete" crawling the web, but we wi ll avoid getting stuck in a loop of pages. If we want to al low for the possibility of "fi nishi ng" crawling the web (which would clea rly happen only ifthe "web"were actually a sma ller system, like a n intra net), then we can set a minimum priority that a page must have to be crawled. This is just one, simpl istic solution, and there are many others that a re equally valid. A problem like this wi ll more likely resemble a conversation with your interviewer which cou ld ta ke any number of paths. In fact, the discussion of this problem could have taken the path of the very next problem.
CrackingTheCodinglnterview.com I 6th Edition
379
I System Design and Scalability
Solutions to Chapter 9 9.4
Duplicate URLs: You have 1 0 billion URLs. How do you detect the duplicate documents? In this case, assume "duplicate" means that the U RLs a re identical. pg 145
SOLUTION
Just how much space do 10 billion UR Ls ta ke up? If each URL is an average of 1 00 cha racters, and each char acter is 4 bytes, then this l ist of 1 0 billion U RLs will ta ke up about 4 terabytes. We a re proba bly not going to hold that much data in memory. But, let's just pretend for a moment that we were miraculously holding this data in memory, since it's useful to first construct a solution for the simple version. U nder this version of the problem, we would just create a hash ta ble where each U R L maps to t rue if it's a l ready been found elsewhere in the list. (As a n alternative solution, we cou ld sort the list a nd look for the duplicate values that way. That will take a bunch of extra time and offers few advantages.) Now that we have a solution for the sim ple version, what happens when we have all 4000 gigabytes of data and we can't store it all in memory? We could solve this either by storing some of the data on disk or by splitting up the data across machines. Solution # 1 : Disk Storage
If we stored all the data on one machine, we would do two passes of the document. The first pass would split the list of U RLs i nto 4000 chunks of 1 GB each. An easy way to do that might be to store each U RL u in a fi le named < x > . txt where x h a s h ( u ) % 4000. That is, we d ivide up the U RLs based on their hash va lue (mod ulo the n u m ber of chunks). This way, all URLs with the same hash va l u e would be i n the same file. =
In the second pass, we wou ld essentially implement the simple solution we came up with earlier: load each file into memory, create a hash table of the UR Ls, and look for duplicates. Solution #2: Multiple Machines
The other solution is to perform essentially the same procedure, but to use multiple machines. In this solu tion, rather than storing the data in file < X > . txt. we would send the URL to machine x. Using multiple machi nes has pros and cons. The main pro is that we ca n pa ra l lelize the operation, such that all 4000 chunks a re processed simu lta ne ously. For large amounts of data, this might result in a faster solution. The disadvantage though is that we a re now relying on 4000 different machines to operate perfectly. That may not be realistic (pa rticula rly with more data and more machines), and we'll need to start considering how to handle failure. Additionally, we have increased the com plexity of the system sim ply by involving so many machines. Both are good solutions, though, a nd both should be discussed with you r interviewer.
380
Cracking the Cod ing I nterview, 6th Edition
Solutions to Chapter 9 9.5
\ System Design and Scalability
Cache: Imagine a web server for a simplified search engine. This system has 1 00 machines to respond to search queries, which may th en call out using processSea rch ( s t r i n g q u e ry ) to another cluster of machines t o actually get the result. Th e mach ine w h ic h responds t o a given
q uery is chosen at ra ndom, so you cannot guarantee that the same machine will always respond to same request. The method p roc e s s S e a r c h is very expensive. Design a caching mechanism to cache the results of the most recent queries. Be sure to explain h ow yo u w o u ld u pd a te t h e cac h e when data changes.
the
pg 145
SOLUTION
Before getting into the design of this system, we first have to understand what the question means. Many of the details a re somewhat am biguous, as is expected i n questions li ke th is. We will ma ke reasona b le assum p tions for the pu rposes of this solution, but you should discuss these details-in depth-with your i nter viewer. Assumptions
Here a re a few of the assumptions we make for this solution. Depend ing on the design of your system and how you approach the problem, you may make other assu m ptions. Remember that while some approaches are better than others, there is no one "correct" approach. Other than ca lling out to p roce s s S e a r c h as necessa ry, all query processing happens on the initial machine that was called. •
•
The number of queries we wish to cache is large (millions). Calling between machines is relatively qu ick. The resu lt for a given query is an ordered list of U RLs, each of which has an associated 50 character title and 200 character summary.
•
The most popular queries are extremely popular, such that they wou ld always a ppear i n the cache.
Again, these aren't the only valid assum ptions. This is just one reasonable set of assum ptions. System Requirements
When designing the cache, we know we'll need to support two primary fu nctions: Efficient lookups given a key. Expiration of old data so that it can be replaced with new data. In add ition, we must also handle updating or clearing the cache when the results for a query change. Because some queries a re very common and may permanently reside in the cache, we cannot just wait for the cache to naturally expire. Step 1 : Design a Cache for a Single System
A good way to approach this problem is to sta rt by designing it for a single machine. So, how wou ld you create a data structu re that enables you to easily purge old data and also efficiently look up a va lue based on a key? •
A linked list wou ld allow easy purging of old data, by moving "fresh" items to the front. We cou ld im ple ment it to remove the last element of the linked list when the list exceeds a certa in size.
CrackingTheCodingl nterview.com I 6th Edition
381
5olutions to Chapter 9
J System Design and Scalability
A hash table a llows efficient lookups of data, but it wouldn't ordinarily al low easy data purging. How can we get the best of both worlds? By merging the two data structures. Here's how this works: J ust as before, we create a linked list where a node is moved to the front every time it's accessed. This way, the end of the linked list will always conta in the stalest information. I n addition, we have a hash table that maps from a query to the corresponding node in the linked list. This allows us to not only efficiently return the cached results, b ut a l so to move the a ppropriate node to the front of the list, thereby u pdating its "freshness." For illustrative purposes, abbreviated code for the cache is below. The code attachment provides the full code forth is part. Note that in your interview, it is unlikely that you would be asked to write the fu l l code for this as well as perform the design for the larger system. 1
public class cache { public static int MAX SIZE = 10; public Node head, tail ; public HashMa p ma p ; public int s i ze = 0 ;
2
3 4 5 6 7
public Cache ( ) { ma p = new HashMap ( ) ; }
8 9
10 11
I * Moves node to front of l inked list *I public void moveToF ront ( Node node ) { . . . } p u b l i c void moveToF ront ( String query ) { . . . }
12
13 14 15
I * Removes node from linked list *I public void remove F romLinked List ( Node node ) { . . . }
16 17 18 19
I * Gets results from cache, and updates linked list *I public St ring [ ] getRe s u lts ( St r i ng query ) { if ( ! ma p . contains Key ( query ) ) return null;
20
21 22
Node node = map . ge t ( query ) ; moveToF ront ( node ) ; II update freshness return node . re s u lt s ;
23
24 25
}
26
27
I* Inserts results into linked list and ha s h *I public vo id i nsertRe s u lts ( String query, St ring [ ] result s ) { if ( ma p . contains Key ( query ) ) { II update va lues Node node = map . get ( query ) ; node . re sults = re s u l t s ; moveToF ront ( node ) ; I I update freshness ret urn;
28 29
3e
31
32 33 34
}
35
36
=
Node node new Node ( query, result s ) ; moveToF ront ( node ) ; ma p . put ( query, node ) ;
37
38
3,
40
if ( s ize > MAX_SIZE ) { m a p . r e mo v e ( t a i l . q u e r y ) ; r em ov e F rom L i n ke d L i st ( t a i l ) ; }
41
42
43
382
I
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 9 44
45
I System Design and Sca lability
} }
Step 2: Expand to Many Machines
Now that we understand how to desig n this for a single machine, we need to u ndersta nd how we wou ld design this when queries could be sent to many different machines. Recall from the problem statement that there'i: no guarantee that a particular query wilt be consistently sent to the same machine. The first thing we need to decide is to what extent the cache is shared across machi nes. We have severa l
options to consider. Option 1 : Each machine has its own cache.
A simple option is to give each machine its own cache. This means that if"foo" is sent to machine 1 twice in a short amount of time, the resu lt wou ld be reca lled from the cache on the second time. But, if"foo" is sent first to machine 1 and then to machine 2, it wou ld be treated as a totally fresh query both times. This has the advantage of being relatively quick, si nce no machine-to-machine calls are used. The cache, u nfortu nately, is somewhat less effective as an optimization tool as many repeat queries would be treated as fresh queries. Option 2: Each machine has a copy of the cache.
On the other extreme, we could give each machine a complete copy of the cache. When new items are added to the cache, they are sent to all machi nes. The entire data structure-linked list and hash table would be duplicated. This desig n means that common queries wou ld nearly always be in the cache, as the cache is the same everywhere. The major drawback however is that u pdating the cache means firing off data to N d ifferent machines, where N is the size of the response cluster. Additionally, because each item effectively ta kes up N times as much space, our cache would hold much less data. Option 3: Each machine stores a segment of the cach e.
A third option is to d ivide up the cache, such that each machine holds a different part of it. Then, when machine i needs to look up the results for a query, machine i would figure out which machine holds this va lue, and then ask this other machine (machine j) to look up the query in j 's cache. But how wou ld machine i know which machine holds this part of the hash ta ble? One option is to assign queries based on the formula hash ( query) % N. Then, machine i only needs to apply this formula to know that machine j should store the resu lts for this q uery. So, when a new query comes in to machine i, this machine would apply the formula and call out to machine j. Machine j wou ld then retu rn the value from its cache or call proc e s s S e a rc h ( q uery) to get the results. Machine j woul d update its cache and return the resu lts back to i . Alternatively, you cou ld design the system such that machine j j ust returns nu l l i f i t doesn't have the q u e ry in its current cache. This would require machine i to ca ll proc e s s S e a r c h and then forward the results to machine j for storage. This i mplementation actually increases the number of machi ne-to machine calls, with few adva ntages.
Cracki ngTheCodingl nterview.com I 6th Edition
383
Solutions to Chapter 9
I System Design and Scalabi lity
Step 3 : Updating results when contents change
Recall that some queries may be so popular that, with a sufficiently large cache, they would perma nently be cached. We need some sort of mechanism to allow cached results to be refreshed, either periodically or "on-demand" when certain content cha nges. To answer this question, we need to consider when results would change (and you need to discuss this with your interviewer) . The primary times would be when: 1 . The content at a URL changes (or the page at that URL is removed). 2.
The orderi ng of resu lts change in response to the ra n k of a page changing.
3. New pages appear related to a particular query. 10
handle situations #1 and #2, we could create a separate hash ta ble that would tell us which cached queries are tied to a specific URL. This could be handled completely separately from the other caches, and reside on different machi nes. However, this solution may require a lot of data.
Alternatively, if the data doesn't requ i re instant refreshing (which it probably doesn't), we cou ld periodica lly crawl through the cache stored on each machine to purge queries tied to the updated U RLs. Situation #3 is su bstantially more difficult to handle. We could u pdate single word queries by parsing the content at the new URL and p u rging these one-word queries from the caches. But, this will only handle the one-word queries. A good way to handle Situation #3 (and likely something we'd want to do a nyway) is to implement a n "auto matic time-out" on the cache. That is, we'd im pose a time out where no query, regardless of how popular it is, can sit in the cache for more than x minutes. This will ensure that all data is periodically refreshed. Step 4: Further Enhancements
There a re a number of improvements and tweaks you could make to this design depending on the assump tions you make and the situations you optimize for. One such optimization is to better support the situation where some queries a re very popular. For exam ple, suppose (as an extreme example) a particular string constitutes 1 % of a l l queries. Rather than machine i forward ing the request to machine j every time, machine i could forward the request just once to j , and then i could store the resu lts i n its own cache as well. Alternatively, there may also be some possibility of doing some sort of re-architecture of the system to assign queries to machines based on their hash va lue (and therefore the location of the cache), rather than random ly. However, this decision may come with its own set of trade-offs. Another optimization we could make is to the "automatic time out" mechanism. As initially described, this mechanism purges any data after X minutes. However, we may want to update some data (like cu rrent news) much more frequently than other data (like historica l stock prices). We could implement timeouts based on topic or based on URLs. In the latter situation, each URL would have a time out value based on how frequently the page has been updated in the past. The time out for the query would be the minimum of the time outs for each URL These a re just a few of the enhancements we can make. Remember that i n questions like this, there is no single correct way to solve the problem. These questions are a bout having a discussion with you r inter viewer about desig n criteria and demonstrating your general approach and methodology.
384
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 9 9.6
I System Design and Scalability
Sales Rank: A large eCommerce company wishes to list the best-selling prod ucts, overall and by category. For example, one product might be the # 1 056th best-sel ling prod uct overa l l but the # 1 3th best-selling product under "Sports Equipment" and the #24th best-selling product u nder "Safety:' Describe how you would design this system. pg 1 45
SOLUTION
Let's fi rst start off by making some assumptions to define the problem. Step 1 : Scope the Problem
First, we need to define what exactly we're building. •
We'll assume that we're only being asked to design the components relevant to this question, and not the entire eCommerce system. In this ca se, we might touch the design of the frontend and purchase components, but only as it i mpacts the sales ra n k. We should also define what the sales ran k means. Is it tota l sales over all time? Sales in the last month? Last week? Or some more complicated fu nction (such as one i nvolving some sort of exponential decay of sales data)? This wou ld be something to discuss with your interviewer. We will assume that it is sim ply the total sales over the past week.
•
We will assume that each prod uct can be in multiple categories, and that there is no concept of"subcat egories:'
This pa rt just gives us a good idea of what the problem, or scope of features, is. Step 2: Make Reasonable Assumptions
These are the sorts of things you'd want to d iscuss with your i nterviewer. Because we don't have an inter viewer in front of us, we'l l have to make some assumptions. •
We will assume that the stats do not need to be 1 00% u p-to-date. Data can be up to an hour old for the most popu lar items (for exam ple, top 1 00 in each category), and up to one day old for the less popular items. That is, few people wou ld ca re if the #2,809,1 32th best-sel ling item should have actually been listed as #2,789, 1 58th instead. Precision is im portant for the most popu lar items, but a small degree of error is okay for the less popular items.
•
•
We will assume that the data should be updated every hour (for the most popular items), but the time range for this data does not need to be precisely the last seven days (1 68 hours). If it's sometimes more like 1 50 hours, that's okay. We will assume that the categorizations a re based strictly on the origin of the transaction (i.e., the seller's name), not the price or date.
The im portant thing is not so much which decision you made at each possible issue, but whether it occu rred to you that these a re assumptions. We should get out as many of these assumptions as possible in the beginning. It's possible you will need to make other assu m ptions along the way. Step 3: Draw the Major Components
We should now design just a basic, naive system that describes the major components. This is where you would go up to a whiteboard.
CrackingTheCodinglnterview.com I 6th Edition
385
Solutions to Chapter 9
I System Desig n and Scala bi lity purchase system
sales ra nk data
database
In this simple design, we store every order as soon as it comes into the database. Every hour or so, we pull sales data from the database by category, compute the tota l sales, sort it, and store it in some sort of sales ra n k data cache (which is probably held in memory). The frontend just pulls the sales ra nk from this table, rather than hitting the standard database and doing its own ana lytics. Step 4: Identify the Key Issues Analytics are Expensive
I n the na ive system, we periodically query the database for the number of sales in the past week for each product. This will be fairly expensive. That's ru nning a q uery over all sales for all time. Our database just needs to track the tota l sa les. We'll assume (as noted in the beginning of the solution) that the general storage for purchase history is taken care of in other parts of the system, and we just need to focus on the sales data ana lytics. Instead of listing every purchase in our data base, we11 store j ust the tota l sales from the last week. Each purchase will just update the total weekly sa les. Tracking the total sales takes a bit of thoug ht. If we just use a single column to track the total sales over the past week, then we'll need to re-com pute the total sales every day (si nce the specific days covered in the last seven days change with each day). That is unnecessarily expensive. I nstead, we'll just use a table like this.
This is essentially like a circu lar a rray. Each day, we clear out the corresponding day of the week. On each purchase, we update the total sales count for that product on that day of the week, as wel l as the total cou nt. We wi ll also need a separate table to store the associations of product IDs and categories.
To
get the sales rank per category, we'll need to join these tables.
386
Cracki ng
the Coding Interview, 6th Edition
Sol utions to Chapter 9
I System Desig n and Scalabil ity
Database Writes are Very Frequent
Even with this cha nge, we'll still be hitting the data base very frequently. With the amount of purchases that could come in every second, we'll proba bly wa nt to batch up the database writes. Instead of immediately comm itti ng each pu rchase to the database, we could store purchases in some sort of i n-memory cache (as well as to a log fi le as a backup). Periodically, we'll process the log I cache data, gather the totals, and u pdate the database.
I
We should quickly think about whether or not it's feasi ble to hold this i n memory. If there are 1 0 m i llion prod ucts i n the system, can we store each (along with a cou nt) in a hash table? Yes. If each prod uct I D is four bytes (which is big enoug h to hold up to 4 billion unique IDs) and each count is four bytes (more than enough), then such a hash ta ble would only take a bout 40 mega bytes. Eve n with some additional overhead and su bsta ntial system growth, we would stil l be able to fit this all i n memory.
After u pdating the database, we ca n re-run the sales ra nk data. We need to be a bit ca reful here, though. If we process one prod uct's logs before another's, and re-run the stats in between, we could create a bias i n the data (si nce we're including a larger timespan for one prod uct than its "competing" product). We can resolve this by either ensuring that the sales ran k doesn't ru n until all the stored data is processed (difficult to do when more and more pu rchases a re com i ng i n). or by d ivid ing up the in-memory cache by some time period. If we u pdate the data base for all the stored data up to a particular moment i n time, this ensures that the database will not have biases. Joins are Expensive
We have potentia l ly tens of thousands of prod uct categories. For each category, we'll need to fi rst pull the data for its items (possi blyth rough an expensive join) and then sort those. Alternatively, we could just do one join of prod ucts and categories, such that each prod uct will be listed once per category. Then, if we sorted that on category and then product I D, we could just walk the results to get the sales ra n k for each category.
Prg.fl"!�� "Sports Equi pment" > "Tennis" > "Rackets")? What if data needed to be more accurate? What if it needed to be accurate within 30 minutes for all products?
Think through your design carefully and ana lyze it for the tradeoffs. You might also be asked to go i nto more detail on a ny specific aspect of the prod uct. 9.7
Personal Financial Manager: Explain how you would design a persona l fina ncia l manager (like
Mint.com). This system would con nect to you r bank accou nts, a nalyze you r spending habits, and make recommendations. pg 1 45
SOLUTION ·· ·· ·· · · -·-· · - ·- ·-· - ·- · --- - ------
The fi rst thing we need to do is define what it is exactly that we are building.
388
I
Cracking the Coding Inte rview, 6th Edition
Solutions to Chapter 9
I System Design and Scalability
Step 1 : Scope the Problem
Ordinarily, you wou ld cla rify this system with your interviewer. We'll scope the problem as follows: •
You create an accou nt and add you r ba nk accou nts. You can add multiple bank accounts. You can also add them at a later point in time. It pulls in all you r fi nancial history, or as much of it as you r bank will allow.
•
This financial history includes outgoing money (things you bought or paid for), incoming money (salary and other payments), and you r cu rrent money (what's in you r ba nk account and investments). Each payment tra nsaction has a "category" associated with it (food, travel, clothing, etc.).
•
There i s some sort of data source provided that tells the system, with some reliability, which category a tra nsaction is associated with. The user might, in some cases, override the category when it's improperly assigned (e.g., eating at the cafe of a department store getting assigned to "clothing" rather than "food"). Users will use the system to get recommendations on their spending. These recommendations will come from a mix of "typical" users ("people generally should n't spend more than X% of their income on clothing"), but can be overridden with custom budgets. This will not be a primary focus right now.
•
•
We assume this is just a website for now, although we could potentially tal k about a mobile app as well. We proba bly wa nt email notifications either on a regular basis, or on certa in conditions (spending over a certain threshold, hitting a budget max, etc.). We'll assume that there's no concept of user-specified rules for assigning categories to tra nsactions.
This gives us a basic goal for what we want to build. Step 2: Make Reasonable Assumptions
Now that we have the basic goa l for the system, we should defi ne some fu rther assumptions about the characteristics of the system. •
•
Add i ng or removing ba nk accou nts is relatively unusual. The system is write-heavy. A typical user may make several new tra nsactions daily, although few users would access the website more than once a week. In fact, for many users, their primary interaction might be through email alerts. Once a tra nsaction is assig ned to a category, it will only be changed if the user asks to change it. The system will never reassign a tra nsaction to a d ifferent category "behind the scenes': even if the rules change. This means that two otherwise identica l tra nsactions cou ld be assigned to different categories if the rules cha nged in between each transaction's date. We do this beca use it may confuse users if their spending per category cha nges with no action on their pa rt. The banks probably won't push data to our system. Instead, we will need to pull data from the ba nks. Alerts on users exceeding budgets probably do not need to be sent instantaneously. (That would n't be rea l istic anyway, since we won't get the tra nsaction data instantaneously.) It's proba bly pretty safe for them to be up to 24 hours delayed.
It's okay to make different assumptions here, but you should explicitly state them to your interviewer.
CrackingTheCodingl nterview.com I 6th Edition
389
Solutions to Chapter 9
I System Design and Sca lability
Step 3: Draw the Major Components
The most naive system wou ld be one that pulls ban k data on each login, categorizes all the data, and then a nalyzes the user's budget. This would n't qu ite fit the requirements, though, as we wa nt email notifications on particular events. We can do a bit better. bank data synchronizer
raw transaction data
categorizer
frontend
categorized tra nsactions
budget data
budget analyzer
With this basic arch itecture, the ba nk data is pulled at period ic times (hourly or daily). The frequency may depend on the behavior of the u sers. Less active users may have their accounts checked less frequently. Once new data arrives, it is stored in some list of raw, u nprocessed tra nsactions. This data is then pushed to the categorizer, which assigns each tra nsaction to a category and stores these categorized tra nsactions in another datastore. The budget analyzer pulls in the categorized transactions, updates each user's budget per category, and stores the user's budget. The frontend pulls data from both the categorized tra nsactions datastore as well as from the budget datas tore. Additionally, a user could a lso interact with the frontend by changing the budget or the categorization of their tra nsactions. Step 4: Identify the Key Issues
We should now reflect on what the major issues here m ight be. This will be a very data-heavy system. We want it to feel snappy and responsive, though, so we'll want as much processi ng as possible to be asynchronous. We will almost certainly wa nt at least one task q ueue, where we can q ueue up work that needs to be done. This work will include tasks such as pulling in new ba n k data, re-a na lyzing budgets, and categorizing new ba nk data. It would a lso i nclude re-trying tasks that fai led. These tasks will likely have some sort of priority associated with them, as some need to be performed more often than others. We wa nt to build a task q ueue system that can prioritize some task types over others, whi le still ensuring that all tasks will be performed eventually. That is, we wouldn't want a low priority task to essentially "starve" because there are always higher priority tasks. One im porta nt part of the system that we haven't yet addressed will be the email system. We could use a task to regularly crawl user's data to check if they're exceeding their budget, but that means checking every 390
Cracking the Coding I nterview, 6th Edition
Solutions to Chapter 9
I System Design and Scalability
sing(e user daily. Instead, we'll wa nt to queue a task whenever a transaction occurs that potentially exceeds a budget We can store the cu rrent budget totals by category to make it easy to understand if a new transac tion exceeds the budget. We should also consider i ncorporati ng the knowledge (or assumption) that a system like this will probably have a large number of inactive users-users who signed up once and then haven't touched the system since. We may wa nt to either remove them from the system entirely or deprioritize their accou nts. We'll want some system to track their account activity and associate priority with thei r accou nts. The biggest bottleneck in our system will likely be the massive amount of data that needs to be pulled and analyzed. We should be able to fetch the ba n k data asynchronously and run these tasks across many servers. We should drill a bit deeper into how the categorizer and budget ana lyzer work. Categorizer and Budget Analyzer
One thing to note is that transactions are not dependent on each other. As soon as we get a transaction fo r a user, we can categorize it and integ rate this data. It might be inefficient to do so, but it won't cause any inaccuracies. Should we use a sta ndard database for this? With lots of transactions coming in at once, that might not be very efficient. We certainly don't want to do a bunch of joins. It may be better instead to just store the transactions to a set of flat text fi les. We assumed ea rlier that the categorizations are based on the seller's name alone. If we're assuming a lot of users, then there will be a lot of duplicates across the sellers. If we group the transaction fi les by seller's name, we can ta ke advantage of these duplicates. The categorizer can do something like this: raw transaction data, grou ped by seller
categorized data, grouped by user
u pdate categorized transactions
merge & group by user & category
update budgets
It first gets the raw transaction data, grou ped by seller. It picks the a ppropriate category for the seller (which might be stored in a cache for the most common sellers), and then appl ies that category to all those tra ns actions. After applying the category, it re-groups all the transactions by user. Then, those transactions are inserted into the datastore fo r this user.
CrackingTheCodingl nterview.com I 6th Edition
391
Solutions to Chapter 9
I System Design and Scalability
amazon/ u s er12 1 , $ 5 . 43 , Au g 1 3 u s e r922, $ 1 5 . 39 , Aug 2 7 c omc a s t / u s e r922 , $9 . 29 , Au g 24 u s e r248, $40 . 1 3 , Au g 1 8
user121/ ama zon , s hopping , $5 . 43 , Aug 1 3 u s e r922/ ama zon , s hopping , $ 1 5 . 39 , Aug 2 7 c omc a s t , u t i l i t i e s , $9 . 2 9 , Aug 2 4 u s e r248/ c omca s t , ut i l it i e s , $40 . 1 3 , Au g 18
Then, the budget analyzer comes in. It ta kes the data grouped by user, merges it across categories (so all Shopping tasks for this user i n this timespan are merged), and then updates the budget. Most of these tasks wi ll be handled i n simple log fi les. Only the final data (the categorized transactions and the budget analysis) will be stored i n a database. This minimizes writing and reading from the data base. User Changing Categories
The user might selectively override particular transactions to assign them to a different category. I n this case, we would u pdate the datastore for the categorized tra nsactions. It would also signal a quick recom putation of the budget to decrement the item from the old category and increment the item in the other category. We could also just recompute the budget from scratch. The budget analyzer is fairly quick as it just needs to look over the past few weeks of tra nsactions for a single user. Follow Up Questions
How would this change if you also needed to support a mobile app? How would you design the component which assigns items to each category? How would you design the recommended budgets feature? How wou ld you_ change this if the user could develop rules to categorize all transactions from a partic u l a r seller differently than the defau lt? 9.8
Pastebin: Design a system l i ke Pastebin, where a user can enter a piece of text and get a randomly generated URL for public access. pg 1 45
SOLUTION
We can start with clarifying the specifics of this system. Step 1 : Scope the Problem
The system does not support user accounts or editing documents. •
The system tracks ana lytics of how many times each page is accessed. Old documents get deleted after not being accessed for a sufficiently long period of time.
•
While there isn't true authentication on accessing documents, users should not be able to "guess" docu392
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 9
•
ment
I System Desig n and Scalability
URLs easily.
The system has a frontend as well as a n AP\. The analytics for each
URL ca n be accessed through a "stats" link on each page. It is not shown by defau lt,
though.
Step 2: Make Reasonable Assumptions •
The system gets heavy traffic and contains many millions of documents. Traffic is not equally distributed across documents. Some docu ments get much more access than others.
Step 3: Draw the Major Components
We ca n sketch out a simple design. We'll need to keep track of U R Ls a nd the files associated with them, as well as ana lytics for how often the files have been accessed. How should we store the docu ments? We have two options: we can store them in a database or we can store them on a file. Since the documents can be large and it's unlikely we need searching capabilities, storing them on a file is probably the better choice. A simple design like this might work well: server with files
URL to File Database
server with files
server with files
Here, we have a simple database that looks up the location (server a nd path) of each file. When we have a request for a U RL, we look up the location of the U R L withi n the datastore and then access the file. Additiona lly, we wil l need a database that tracks ana lytics. We can do this with a simple datastore that adds each visit (including timesta m p, I P add ress, and location) as a row in a database. When we need to access the stats of each visit, we pull the relevant data in from this data base. Step 4: Identify the Key Issues
The fi rst issue that comes to m i nd is that some documents will be accessed much more frequently than others. Read ing data from the filesystem is relatively slow compa red with reading from data in memory. Therefore, we probably want to use a cache to store the most recently accessed documents. This will ensure
CrackingTheCodingl nterview.com I 6th Edition
393
Solutions to Chapter 9
I System Design and Scalability
that items accessed very frequently (or very recently) will be quickly accessible. Si nce documents cannot be edited, we will not need to worry about invalidating this cache. We should also potentially consider sharding the data base. We ca n shard it using some mapping from the URL (for example, the URL's hash code modulo some integer), which will allow us to quickly locate the data base which contains this file. In fact, we could even ta ke this a step fu rther. We cou ld skip the database entirely and j u st let a hash of the URL indicate which server contains the document. The URL itself cou ld reflect the location ofthe document. One potential issue from this is that if we need to add servers, it could be d ifficult to redistribute the docu ments. Generating URLs
We have not yet discussed how to actually generate the URLs. We probably do not wa nt a monotonically i ncreasing integer value, as this would be easy for a user to "guess:' We wa nt U RLs to be d ifficult to access without being provided the link. One simple path is to generate a random G U I D (e.g., Sd50e8ac-57cb-4a0d-866 1 -bcdee2548979). This is a 1 28-bit value that. while not strictly guara nteed to be unique, has low enough odds of a collision that we can treat it as unique. The d rawback of this plan is that such a URL is not very "pretty" to the user. We could hash it to a smaller value, but then that increases the odds of collision. We could d o something very similar, though. We could just generate a 1 0-character sequence of letters and num bers, which gives us 3610 possible stri ngs. Even with a billion URLs, the odds of a collision on any specific URL are very low.
I
This is not to say that the odds of a collision over the whole system a re low. They are not. Any one specific URL is unlikely to collide. However, after storing a billion URLs, we are very l i kely to have a collision at some poi nt.
Assuming that we a ren't okay with periodic (even if u nusual) data loss, we'll need to handle these collisions. We ca n either check the datastore to see if the URL exists yet or, if the URL maps to a specific server, just detect whether a file already exists at the destination. When a collision occu rs, we can just generate a new U R L With 3610 possible URLs, collisions would be rare enough that the lazy approach here (detect collisions and retry) is sufficient. Analytics
The final component to discuss is the a nalytics piece. We probably want to display the number of visits, and possibly break this down by location or time. We have two options here: Store the raw data from each visit. Store just the data we know we'll use (number of visits, etc.). You can discuss this with you r interviewer, but it probably makes sense to store the raw data. We never know what features we'll add to the analytics down the road. The raw data allows us flexibility. This does not mean that the raw data needs to be easily searchable or even accessi ble. We can just store a log of each visit in a file, a nd back this u p to other servers.
394
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 9
l System Design and Scalability
One issue here is that this amount of data cou ld be su bstantial. We could potential l y reduce th e space usage considerably by storing data only probabilistically. Each URL wou ld have a stora ge_probab i l i ty asso ciated with it. As the popularity of a site goes up, the storage_p robab ili t y goes down. For example, a popu lar document might have data logged only one out of every ten times, at ra ndom. When we look up the number of visits for the site, we'll need to adjust the va lue based on the probability (for example, by multiplyi ng it by 1 O).This w i l l of course lead to a small inaccuracy. but that may be accepta ble. The log files are not designed to be used frequently. We will want to also store this precom puted data in a datastore. If the analytics just displays the number of visits plus a graph over time, this cou ld be kept in a separate data base. .-..
1 2 a b 31b92p
Dec ember 2013
242119
12ab31b92p
J a nu a ry 2014
429918
Every time a URL is visited, we can increment the appropriate row and column. This datastore can also be sharded by the URL. As the stats a re not listed on the reg ular pages and would generally be of less interest, it should not face as heavy of a load. We cou ld sti ll cache the generated HTML on the frontend servers, so that we don't continu ously reaccess the data for the most popu lar URLs. Follow-Up Questions
How would you support user accou nts? How wou ld you add a new piece of analytics (e.g., referra l sou rce) to the stats page? How would you r design change if the stats were shown with each docu ment?
CrackingTheCodinglnterview.com I 6th Edition
395
10 Sol utions to Sorti ng and S ea rching
Sorted Merge: You a re given two sorted arrays, A and B, where A has a large enough buffer at the end to hold B. Write a method to merge B into A in sorted order.
1 0.1
pg 1 49
SOLUTION
Since we know that A has enough buffer at the end, we won't need to allocate additional space. Our logic should involve simply comparing elements of A and B and inserting them in order, until we've exhausted all elements in A and in B. The only issue with this is that if we insert an element i nto the front of A, then we'll have to shift the existing elements backwards to make room for it. It's better to insert elements into the back of the array, where there's em pty space. The code below does just that. It works from the back of A and B, moving the largest elements to the back of A. 1 void merge ( int [ ] a , int [ ] b , int la stA, int lastB) { 2 int indexA las tA - 1 ; / * Index of last element in array a * / int indexB = lastB - 1 ; / * I ndex of last element i n array b * / 3 int indexMerged = lastB + lastA - 1 ; / * end of merged a r ray * / 4 =
5
6 7
/ * Merge a and b, start ing from the last element in each */ while ( indexB >= 0 ) { / * end of a i s > than end of b */ if ( indexA > = 0 && a [ indexA] > b [ indexB] ) { a [ indexMerged ] = a [ indexA] ; / / copy element indexA- - ; } else { a [ indexMe rged ] = b [ indexB ] ; / / copy element indexB- - ;
8
9
10 11 12 13 14 15 16 17
1S
}
indexMerged - - ; / / move indices }
}
Note that you don't need to copy the contents of A after ru nning out of elements in B. They a re already in place.
396
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 1 0 1 0.2
I Sorting and Searching
Group Anagrams: Write a method to sort an array of str"mgs so tnat all tne anagrnms a(e next to
each other. pg SOLUTION
1 50
Th is problem asks us to group the stri ngs in an a rray such that the anagrams appear next to each other. Note that no specific ordering of the words is required, other than this. We need a quick and easy way of determining if two strings a re anagrams of each other. What defi nes if two words are anagrams of each other? Well, a nagrams are words that have the sa me cha racters but in d ifferent orders. It follows then that if we can put the characters in the same order, we ca n easily check if the new words are identical. One way to do this is to just a pply any sta ndard sorting a lgorith m, like merge sort or quick sort, and modify the compa rator. Th is compa rator will be used to ind icate that two stri ngs which are anagrams of each other are equivalent. What's the easiest way of checking if two words a re anagrams? We could count the occurrences of the distinct characters i n each stri ng and return t ru e if they match. Or, we cou ld just sort the string. After all, two words which are anagrams will look the same once they're sorted. The code below implements the compa rator. 1 c l a s s AnagramCompa rator impleme nts Compa rator { 2
3
4
5 6 7 8
9
10
11 }
public String sortCha r s ( String s ) { c ha r [ ] content = s . toCha rAr ray ( ) ; Arrays . sort ( content ) ; return new String( content ) ; }
publi c i nt co mp a r e ( Stri ng sl, String s 2 ) { ret u r n sortCha r s ( s l ) . compa reTo ( sortCha r s ( s 2 ) ) ; }
Now, just sort the arrays using this c ompa reTo method instead of the usual one. Ar rays . so rt ( a r ray, new AnagramComparator ( ) ) ;
12
Th is a lgorithm will take O ( n log( n ) ) time. Th is may be the best we can do fo r a genera l sorting a lgorith m, but we don't actually need to fu lly sort the array. We only need to group the strings in the a rray by a nagram. We can do this by using a hash table which maps from the sorted version of a word to a list of its anagrams. So, fo r example, a c re will map to the list { a cre , r a c e , care}. Once we've g rou ped all the words i nto these lists by anagram, we can then put them back into the array. The code below implements th is algorith m. vo i d sort ( String [ ] a r ray) { 1 2
3
4
5
6
7 8
HashMa p l ist m a p l i st
new H a s hMaplist ( ) ;
/ * Group wo rd� by dndgrdm */
for (String s : a r ray ) { St ring key = s o rtCha rs ( s ) ; maplist . put ( key, s ) ; }
Cracki ngTheCodinglnterview.com I 6th Edition
397
Solutions to Chapter 1 0 9 10
11 12 13
14 15
16
17
18
19 }
I Sorting and Searching
I *Convert hash table to a r ray */ int index = 0 ; f o r (String key : m a p l i st . keySet ( ) ) { Ar rayl ist l i s t = maplist . get ( key ) ; fo r ( St ri ng t : l i s t ) { array[index] = t ; index + + ; } }
20 21 String sortCh a rs ( St ring s) { 22 cha r [ ] content = s . toC ha rArr a y ( ) ; 23 Arrays . sort ( content ) ; 24 return new St ring( content ) ; 25 } 26 27 I *Has hMa p L ist is a Has hMap that maps from Strings to 28 * ArrayList < Integer> . See a ppendix for im plementation . */
You may notice that the algorithm a bove is a modification of bucket sort. 1 0.3
Search in Rotated Array: Given a sorted array of n integers that has been rotated an u n known
n u m ber of times, write code to find an element in the array. You may assume that the array was originally sorted in increasing order. EXAMPLE l nput: find 5 in {1 5 , 16 , 19 , 2 0 , 2 5 , 1 , 3, 4, 5 , 7, 1 0 , 14} Output: 8 (the index of 5 in the array) pg 1 50
SOLUTION --- O OHOOHOOH
O O O •OOOH O OHOOHOO value 1 1 middle == -1) { h igh mid - 1 ; } else if (middle < v a l u e ) { =
I
Cracking the Coding I nterview, 6th Edition
Solutions to Cha pter 1 0 I Sorting a n d Searching 18
low = mid + 1 ; } else { ret urn mid ; }
1,
20 21
22
23
24
} return - 1 ; }
It turns out that not knowing the length didn't im pact the runtime of the search a lgorithm. We find the length in 0( log n) time and then do the search in 0( log n ) time. Our overall runtime is O( l o g n ) , just as it would be in a norma l array. 1 0.S
Sparse Search: Given a sorted array of strings that is interspersed with empty strings, write a method to find the location of a given string.
EXAM PLE I n put: b a l l , { "at", "" }
C(JJ
,
COJ
,
'"''
, " b a l l" ,
"ca r" ,
'"'
, "" , "d ad" , "" ,
Output: 4 pg
7 50
SOLUTION ··---· -· ·-··· ··-· ·--·--· · · -··-
· ··-··-·-·-··-··- · ·-······- · ·-··-··- ··-··-··- ··-···-- --
- " '···-·· -- ··- ·· -· ··· ··-
..... . . . . ..... . . . . ..... .
· · · ··· · · · · · · ··· · · · · · · ·- ··· ··· ··· ·- ··· ··· ··· ·- ··-..
···--
----
If it weren't for the em pty stri ngs, we cou ld simply use binary sea rch. We would compare the stri ng to be found, s t r, with the midpoint of the array, and go from there. With empty strings interspersed, we can implement a simple modification of binary search. All we need to do is fix the compa rison agai nst mid, in case mid is an em pty string. We simply move mid to the closest non-empty string. The recursive code below to solve this problem can easily be mod ified to be iterative. We provide such an implementation in the code attachment. 1 2
3
4 5
6
7
8 9
10 11
12 13
14
1 .5
16 17
18
19
int sea rch ( St ring [ ] strings , String s t r , int first , int last ) { if ( fi rst > last ) ret urn - 1 ; / * Move mid to the middle * / int mi d ( last + first ) I 2; =
/ * If mid i s empty, find closest if ( strings [mid ] . is Empty ( ) ) { int left = mid - 1 ; int right = mid + 1 ; while ( t rue) { if ( left < first && right > ret urn - 1 ; } else if ( right < = last && mid = right ; brea k ; } else if ( left > = first && mid = left ;
! st rings [ right ] . is Empty ( ) ) { ! st rings [ left ] . i s Empty ( ) ) {
brea k ;
20 23
last ) {
} r ight++ ; left - - ;
21
22
non - empty string . * I
}
}
CrackingTheCod ingl nterview.corn J 6th Edition
I
401
Solutions to Chapter 1 O 24
25 26 27
28
29
30
31 32
33
34
}
I Sorting and Searching
/ * Check for string, and recu r s e if neces sary * / i f ( s t r . equals ( st rings [mid ] ) ) { // Found it ! return mid; } else if ( st rings [mid ] . compa reTo ( st r ) < 0) { / / Sea r c h r i g ht return searc h ( strings , str , mid + 1 , last ) ; } else { / / Sea rch left return searc h ( st rings , str , first , mid - 1 ) ; }
35
int searc h ( St ring [ ] str ings , St ring str ) { ) { if ( st r ings == null I I str == null I I str 37 return - 1 ; 38 } 39 ret u r n searc h ( strings , s t r , 0 , st rings . lengt h - 1 ) ; 40 } 36
""
The worst-case runtime for this algorithm i s O ( n ) . I n fact, it's impossible to have an algorithm for this problem that is better than O(n) in the worst case. After a ll, you could have a n array of all empty stri ngs except for one non-empty stri ng. There is no "smart" way to find this non-empty stri ng. In the worst case, you will need to look at every element in the a rray. Careful consideration should be given to the situation when someone searches for the em pty string. Should we find the location (which is an 0 ( n ) operation)? Or should we handle this as an error? There's no correct answer here. This is an issue you should ra ise with your interviewer. Simply asking this question will demonstrate that you are a carefu l coder. 1 0.6
Sort Big File: Imagine you have a 20 GB file with one stri ng per li ne. Explain how you would sort
the file. pg 1 50
SOLUTION
When an interviewer gives a size limit of 20 giga bytes, it should tel l you something. In this case, it suggests that they don't wa nt you to bring all the data into memory. So what do we do? We only bring part of the data into memory. We'll divide the file into chunks, which are x mega bytes each, where x is the amount of memory we have ava ilable. Each chunk is sorted separately and then saved back to the file system. Once all the chunks are sorted, we merge the chunks, one by one. At the end, we have a fully sorted file. This a lgorithm is known as external sort.
402
Cracking the Coding Interview, 6th Edition
Solutions to
Chapter 1 0 I Sorting and Searching
Missing Int: Given a n in put file with four billion non-negative integers, provide an algorithm to generate a n integer that is not contained i n the file. Assume you have 1 GB of memory available for this task.
1 0.7
FOLLOW U P What if you have only 1 0 MB of memory? Assume that a l l the values are d istinct a n d w e now have no more than one billion non-negative integers. pg 7 5 0 SOLUTION
There are a total of 2'2, or 4 billion, distinct i ntegers possible and 231 non-negative i ntegers. Therefore, we know the input fi le (assuming it is in ts rather than lon gs) contains some duplicates. We have 1 GB of memory, or 8 billion bits. Th us, with 8 billion bits, we can map a l l possible integers to a d istinct bit with the available memory. The logic is as follows: 1 . Create a bit vector (BV) with 4 billion bits. Recal l that a bit vector is an array that com pactly stores boolean values by using an array of ints (or another data type). Each int represents 32 boolea n values. 2. I n itialize BV with all Os. 3. Scan a l l numbers (num) from the fi le and call BV . s e t ( n u m , 1 ) .
4. Now scan again BV from the 0th index. 5. Return the first index which has a value of 0. The following code demonstrates our algorithm. 1
2 3
4 5
6 7
8
9
10
11 12 13
14
long numberDfints ( ( long) I nteger . MAX_VALUE ) + 1 ; byte [ ] bitfield new byte [ ( i nt ) ( numbe rOfi nts / 8 ) ] ; St ring fi lename = =
void findOpe nNumber ( ) th rows F i leNotFoundException { Scanner i n = new Scanne r ( new F i leReade r ( filename ) ) ; while ( i n . hasNext i nt ( ) ) { i nt n = i n . nextlnt ( ) ; /* Finds the corresponding n umber in the bitfield by u s ing the OR operator to * set the nth bit of a byte (e . g . , 10 would correspond to the 2nd bit of * i ndex 2 i n t h e byte a r ray ) . * / bitfield [ n I 8 ] I = 1 « ( n % 8 ) ; }
15
=
for ( int i 0 ; i < bitfield . lengt h ; i++ ) { for ( i nt j 0 ; j < 8 ; j ++ ) { / * Ret r ieves the i ndividual bits o f e a c h byte . W h e n 0 b i t i s fou n d , print * the cor res ponding va lue . */ if ( ( bitfield [ i ] & ( 1 < < j ) ) = = 0 ) { System . out . println (i * 8 + j ) ; ret u r n ; } } }
16
=
17 18
19 20
21 22 23
24 25
}
C racki ngTh eCod i n gl nte rview.c o m I 6th Edition
403
Solutions to Chapter 1 o
I Sorting and Searching
Follow Up: What if w e have only 1 0 MB memory?
It's possi ble to fi nd a missing integer with two passes of the data set. We can divide u p the integers into blocks of some size (we'll discuss how to decide on a size later}. let's just assume that we divide u p the integers into blocks of 1 000. So, block 0 represents the num bers 0 through 999, block 1 represents num bers 1 000 - 1 999, and so on. Si nce all the values a re distinct, we know how many values we should fi nd i n each block. So, we sea rch through the file and count how many values a re between 0 a n d 999, how many a re between 1 000 and 1 999, and so on. If we cou nt only 999 va lues i n a particular ra nge, then we know that a missing int m ust be in that range. In the second pass, we'll actually look for which num ber in that ra nge is missing. We use the bit vector approach from the first part of this problem. We can ignore any number outside of this specific range. The question, now, is what is the appropriate block size? Let's defi ne some va ria bles as follows: Let range S i ze be the size of the ranges that each block in the first pass represents. Let a r rayS i ze represent the number of blocks in the first pass. Note that a r rayS i ze si nce there a re 231 non-negative integers.
"'
2 "/ / rangesize
We need to select a value for rangeS i ze such that the memory from the first pass (the a rray) and the second pass (the bit vector} fit. First Pass: 7he Array
The a rray in the first pass can fit in 1 0 mega bytes, or roughly 223 bytes, of memory. Since each element in the a rray is an int, and an int is 4 bytes, we can hold an array of at most a bout 221 elements. So, we ca n deduce the following: a r raySize .
=
ra ng eS 1 z e ::::
2"
ra ng eS i ze 2" 2 21·
1 2 0 -> 0 2 1
As we noted before, if we make sure the peaks a re i n the right place then we know the valleys a re in the right place.
I
We should be a little cautious here. Is it possible that one of these swa ps could "break" a n earlier part of the sequence that we'd already processed? This is a good thing to worry about but it's not a n issue here. If we're swapping midd l e with l eft, then left is currently a valley. Midd l e i s smaller than l eft, so we're putti ng a n even sma ller element a s a va lley. Nothing will break. All is good!
The code to implement this is below. 1
2
3
4 5
6
7
8
void sortVa l leyPea k ( i nt [ ] a r r a y ) { for ( i nt i = 1 ; i < a rray . length; i += 2 ) { i nt biggesti ndex maxindex ( array, i 1, i, i + 1); if ( i ! = biggestindex) { swa p ( array, i , biggesti ndex ) ; } } } =
-
9 10 int maxi ndex ( i nt [ ] a r ray, int a , int b , i nt c ) 11 int len = a rray . lengt h ; 12 int aValue = a > = 0 & & a < l e n ? a r ra y [ a ] 13 int bVa lue b > = 0 && b < len ? a r ray [ b ] 14 int cVa lue c > = 0 && c < l e n ? a r ra y [ c ] 15 16 int max = Math . ma x ( aVa lue, Math . ma x ( bVa lue, 17 if ( aVa lue == m a x ) ret u r n a ; 18 e l s e if ( bVa lue = = ma x ) return b; 19 else return c; 20 } =
=
This algorithm takes O ( n ) time.
41 6
Cracki ng the Coding Interview, 6th Edition
{
Integer . MIN_VALU E ; Integer . MIN_VALU E ; Integer . MIN_VALUE ; cVa lue ) ) ;
11 Sol utions to Testing
1 1 .1
M istake: Find the mistake(s) i n the following code:
uns igned int i ; for ( i 100; i > = 0 ; - - i ) p r i ntf ( "%d \ n" , i ) ; =
pg 7 57
SOLUTION
There are two mista kes in this code. First, note that an u n s igned i n t is, by definition, always g reater than or equal to zero. The for loop condi tion will therefore a lways be true, and it will loop infinitely. The correct code to print all n u mbers from 1 00 to 1 , is i > 0. If we tru ly wa nted to print zero, we could add a n additional p r i ntf statement after the for loop. 1 uns igned int i ; 2 for ( i 100; i > 0 ; - - i ) printf ( "%d \n", i ) ; 3 =
One additional correction is to use %u i n place of %d, as we are printing u n s igned int. uns igned int i ; for ( i 100; i > 0 ; - - i ) 2 3 printf ( "%u\n", i ) ; 1
=
Th is code will now correctly print the list of all n u m bers from 1 00 to 1 , i n descending order. 1 1 .2
Random Crashes: You a re g iven the sou rce to an application which crashes when it is ru n. After ru nning it ten times in a debugger, you find it never crashes in the same place. The application is single th readed, and u ses only the C standard libra ry. What prog ramming errors could be causing this crash? How wou ld you test each one? pg 1 57
SOLUTION
The question largely depends on the type of appl ication being diag nosed. However, we can g ive some general causes of ra ndom crashes. 1.
"Random Variable:" The application may use some random number or variable component that may not be fixed for every execution of the prog ram. Exa mples include user in put, a random number generated by the program, or the ti me of day.
CrackingTheCodingl nterview.com I 6th Edition
417
Solutions to Chapter 1 1
I Testing
Uninitialized Variable: The application could have a n uninitialized variable which, i n some languages, may cause it to take on a n arbitra ry va lue. The values of this variable could resu lt i n the code ta king a slightly different path each time.
2.
3. Memory leak: The
program may have ru n out of memory. Other culprits are tota lly random for each run since it depends on the number of processes ru nning at that particular time. This a lso i ncludes heap overflow or corruption of data on the stack.
4.
External Dependencies: The progra m may depend o n a nother application, machine, or resource. If there a re multiple dependencies, the program could crash at any poi nt.
lo
track down the issue, we should start with learning as much as possi ble a bout the application. Who is running it? What are they doing with it? What kind of application is it?
Additionally, a lthough the application doesn't crash i n exactly the same place, it's possible that it is linked to specific components or scena rios. For example, it could be that the application never crashes if it's simply launched and left untouched, and that crashes only a ppear at some point after loading a fi le. Or, it may be that a l l the crashes ta ke place within the lower level components, such as file 1 /0. It may be usefu l to approach this by elimination. Close down a l l other applications on the system. Track resource use very carefully. If there are parts of the program we can disable, do so. Run it on a different machine and see if we experience the same issue. The more we can elimi nate (or change), the easier we can track down the issue. Additionally, we may be able to use tools to check for specific situations. For example, to i nvestigate issue #2, we can uti lize ru ntime tools which check for uninitialized varia bles. These problems are as much a bout your brainstorm ing ability as they a re a bout your approach. Do you jump a l l over the place, shouting out random suggestions? Or do you approach it in a logical, structured manner? Hopefu lly, it's the latter. Chess Test: We have the following method used in a chess game: boolean c a nMove To ( i n t x , i n t y ) . This method i s part of the P i e c e class and returns whether o r not the piece can move to position ( x , y ) . Explain how you would test this method.
1 1 .3
pg 1 57
SOLUTION
In this problem, there a re two primary types of testing: extreme case validation (ensuring that the progra m doesn't crash on bad input), a n d genera l case testi ng. We'll start with t h e fi rst type. Testing Type #1 : Extreme Case Validation
We need to ensure that the program handles bad or unusual i n put gracefu lly. This means checking the following conditions: Test with negative numbers for x and y Test with x larger than the width • •
•
•
Test with y larger than the height Test with
a completely full board
Test with a n empty or nea rly em pty boa rd Test with fa r more white pieces tha n black
418
I
Cracking the Coding Interview, 6th Edition
Sol utions to Chapter 1 1
I Testi ng
Test with fa r more black pieces than white For the error cases above, we should ask our interviewer whether we wa nt to return false or throw an excep tion, and we should test accordingly. Testing Type #2: General Testing:
General testing is much more expa nsive. Ideally, we wou ld test every possible board, but there are far too many boards. We can, however, perform a reasonable coverage of different boards. There are 6 pieces in chess, so we can test each piece agai nst every other piece, in every possible direction. This wou ld look something like the below code: 1
2
3 4 5
6
foreach piece a : for each ot her type of piece b ( 6 types foreach direction d C reate a board with piece a . Place piece b in direction d . Try to move - check return val ue .
+
empty s pace )
The key to this problem is recognizing that we can't test every possible scenario, even if we would like to. So, instead, we must focus on the essential areas. 1 1 .4
No Test Tools: How wou ld you load test a webpage without using any test tools? pg 1 57
SOLUTION
Load testing helps to ide ntify a web application's maxi mum operati ng capacity, as well as a ny bottlenecks that may interfere with its performance. Sim ilarly, it can check how an application responds to variations in load. To perform load testing, we must first identify the performance critical scenarios and the metrics which fu lfill our performance objectives. Typical criteria include: Response time •
Throughput Resou rce utilization Maximum load that the system can bear.
Then, we desi g n tests to simu late the load, ta king care to measure each of these criteria. In the absence of formal testing tools, we can basically create our own. For example, we cou ld sim u late concu rre nt u sers by creati ng thousands of virtual users. We wou ld write a m u lti-th readed program with thousands of threads, where each thread acts as a real-world user loading the page. For each user, we wou ld program matically measure response time, data 1/0, etc. We wou ld then analyze the resu lts based on the data gathered d u ring the tests and compare it with the accepted values.
CrackingTheCodinglnterview.com j 6th Edition
419
Solutions to Chapter 1 1 1 1 .5
I Testing
Test a Pen: How would you test a pen?
SOLUTION
pg 1 57 ··-· ·- ······-- . .- .. -· ·- ·- ·· -··- ··- ·· ··
·- ··------
This problem is largely about u nderstanding the constra i nts and a pproaching the problem in a structu red man ner. To
understa nd the constrai nts, you should ask a lot of questions to understa nd the "who, what. where, when, how and why" of a problem (or as many of those as apply to the problem). Remember that a good tester u ndersta nds exactly what he is testi ng before starting the work.
To illustrate the technique in this problem, let us guide you through a mock conversation. Interviewer: How wou ld you test a pen? Candidate: Let me find out a bit a bout the pen. Who is going to use the pen? Interviewer: Probably children. •
Candidate: Okay, that's i nteresti ng. What will they be doing with it? Will they be writing, d rawing, or doing something else with it? Interviewer: Drawing. Candidate: Okay, great. On what? Pa per? Clothing? Walls? Interviewer: On clothing. Candidate: Great. What kind of tip does the pen have? Felt? Bal l poi nt? Is it intended to wash off, or is it i ntended to be permanent? Interviewer: It's intended to wash off.
Many questions later, you may get to this: Candidate: Okay, so as I u ndersta nd it we have a pen that is being ta rgeted at 5 to 1 0-yea r-olds. The pen has a felt tip and comes in red, green, blue and black. It's i ntended to wash off when clothing is washed. Is that correct?
The candidate now has a problem that is significantly d ifferent from what it initially seemed to be. This is not uncommon. In fact, many interviewers intentionally give a problem that seems clear (everyone knows what a pen is!), only to let you discover that it's quite a different problem from what it seemed. Their belief is that users do the same thing, though users do so accidentally. Now that you understand what you're testi ng, it's time to come up with a plan of attack. The key here is structure.
Consider what the different com ponents of the object or problem, and go from there. In this case, the components m ight be: Fact check: Verify that the pen is felt tip and that the ink is one of the allowed colors.
Intended use: Drawi ng. Does the pen write pro perly on clothing? Intended use: Washing. Does it wash off of clothing (even if it's been there fo r a n extended period of time)? Does it wash off in hot, warm and cold water? •
Safety: Is the pen safe (non-toxic) for children?
Unintended uses: How else might children use the pen? They might write on other su rfaces, so you need to check whether the behavior there is correct. They might a lso stomp on the pen, throw it, and so on.
420
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 1 1
I Testing
You'll need to make sure that the pen holds u p u nder these conditions. Remember that i n any testing question, you need to test both the intended and unintended scenarios. People don't always use the prod uct the way you want them to. 1 1 .6
Test an ATM: How would you test a n ATM i n a distributed banking system? pg 1 57
SOLUTION
The first thing to do on this question is to clarify assumptions. Ask the fol lowing questions: Who is going to use the ATM? Answers might be "anyone;• or it might be "blind people;' or any number of other a nswers. What are they going to use it for? Answers might be "withdrawing money;• "transferring money;• "checking their balance," or many other answers. What tools do we have to test? Do we have access to the code, or just to the ATM? Remember: a good tester makes sure she knows what she's testi ng! Once we understa nd what the system looks l i ke, we'l l want to break down the p roblem into different test able components. These components include: Logging in Withd rawing money Depositing money Checking ba lance Tra nsferring money We wou ld probably want to use a mix of manual and automated testing. Manual testi ng wou ld involve going through the steps above, making sure to check for all the error cases (low balance, new account, nonexistent accou nt, and so on). Automated testing is a bit more complex. We'll wa nt to automate all the standard scenarios, as shown above, and we also want to look for some very specific issues, such as race conditions. Ideal ly, we wou ld be able to set up a closed system with fake accou nts and ensu re that, even if someone withdraws and deposits money ra pidly from d ifferent locations, he never gets money or loses money that he shouldn't. Above a l l, we need to prioritize secu rity and reliability. People's accou nts must always be protected, and we must make sure that money is always properly accou nted for. No one wants to unexpectedly lose money! A good tester understa nds the system priorities.
Cracki ngTheCodingl nterview.com I 6th Edition
421
12 Sol utions to C a n d C++
Last K Lines: Write a method to print the last K lines of an input file using C++.
1 2.1
pg 1 63
SOLUTION .. ..
· · · '- "· · ··- ··
··"
..------· --·-·-··- ··- ··- ·· - ··- ··- · · · ··- ······ ·· · ·- ···· · · - ·····-�
One b rute force way could be to cou nt the number of l ines (N) and then print from N - K to Nth line. But this requires two reads of the file, which is u n necessa rily costly. We need a solution which a l lows us to read just once and be able to print the last K li nes. We can allocate an array for a l l K li nes and the last K lines we've read in the array. , and so on. Each time that we read a new line, we purge the oldest line from the array. But-you might ask-would n't this requ i re shifting elements in the a rray, which is also very expensive? No, not if we do it correctly. Instead of shifting the a rray each time, we wil l use a circu lar array. With a circu lar a rray, we always replace the oldest item when we read a new line. The oldest item is tracked in a sepa rate variable, which adjusts as we add new items. The following is an example of a circu lar array: step 1 ( initially ) : a r ray {a , b, step 2 ( insert g) : array {g, b, step 3 ( insert h ) : a r ray {g, h, step 4 ( ins ert i ) : a r ray {g, h,
c, c, c, i,
d, d, d, d,
e, e, e, e,
f} . f} . f} . f} .
p p p p
0 1 2
3
The code below implements this algorithm . 1
void pr int last10Lines ( char* fileName ) { const int K = 10; ifst ream file (fi leName ) ; str ing L [ K ] ; int s ize = 0 ;
2 3
4 5
6
7
8
9
10 11 12 13
14
15
16 17
422
/ * read file l ine by l i ne i n t o c i rcula r a r ray * / / * peek ( ) so a n EOF following a l i ne end ing is not cons idered a separate l ine */ while (file . peek ( ) ! = EOF ) { get line(file, L [ size % K ] ) ; s ize++ ; } /* compute start of c i rcular array, and the size of it * / i n t start size > K ? ( size % K) 0; int c o u nt m i n ( K , size ) ; =
Cracki ng the Coding Interv i ew, 6t h E d iti on
Solutions to Chapter 1 2 18
19 2.2
/ * print el ements in the ord er they were read * / for (int i 0; i < c o u nt ; i++) { cout < < L [ ( sta rt + i ) % K ] < < en d ! ; } =
20
21
I C and C++
}
This solution will requ ire readin g in the 1 2.2
Reverse Str\ng: Implem ent terminated stri ng.
a
whole file,
function
but only ten lines will be in memory at any giVen
v o id reversetchar* str) in
C or C++
point.
which reve rses a null pg 1 63
SOLUTION
This is a classic i nterview question. The only"gotcha" is to try to do it in place, and to be ca refu l for the n u l l character. We wil l implement this in C. 1
2
3
4
5
void reverse ( c har * s t r ) { char* end = s t r ; char tmp ; if ( st r ) { w h ile ( * en d ) { / * find end o f t h e s t r i n g * / ++end ;
6 7
} - - end ; /* set one char back, since last char i s null * /
3 9
10
11
12 13
14
=
15 16
17 1�
/* swa p c h a racters from start of st ring with the end of the string, u n t i l the * pointers meet i n middle . * / while ( s t r < end ) { tmp = * s t r ; *st r++ *end ; * en d - - = tmp; }
}
}
This is just one of many ways to implement this solution. We cou ld even implement this code recursively (but we wou ldn't recommend it). 1 2.3
Hash Table vs STL Map: Com pare and contrast a hash table and a n STL map. How is a hash ta ble implem ented ? If the number of inputs is small, which data structu re options can be used instead of a hash ta ble? pg 1 63
SOLUTION
In a hash table, a value is stored by ca lling a hash fu nction on a key. Values are not stored in sorted order. Additionally, si nce hash tables use the key to find the index that will store the va lue, a n insert or lookup can be done in amortized 0( 1 ) time (assu ming few collisions in the hash ta ble). I n a hash ta ble, one must also handle potential col lisions. This is often done by chai ning, which means to create a linked list of all the val ues whose keys map to a particular index.
CrackingTheCodi n g l nterview.com I 6th Edition
423
Solutions to Cha pter 1 2
I C and C ++
An STL map inserts the key/va lue pairs into a binary search tree based on the keys. There is no need to handle collisions, an d , since the tree is balanced, the insert and lookup time is guaranteed to be O( log N) . How is a hash table implemented?
A hash table i s trad itionally im plemented with an array of linked lists. When we want to insert a key/value pair, we map the key to an index in the array using a hash function. The value is then inserted into the linked list at that position. Note that the elements in a l i n ked list at a particu lar i ndex of the array do not have the same key. Rather, h a s h F u n ct i o n ( ke y ) is the same for these values. Therefore, i n order to retrieve the value for a specific key, we need to store in each node both the exact key and the va lue. To
summarize, the hash ta ble will be im plemented with an a rray of l i n ked lists, where each node in the l i n ked list holds two pieces of data: the va lue and the original key. In add ition, we will want to note the fol lowing design criteria: 1 . We wa nt to use a good hash fu nction to ensure that the keys are wel l d istributed. If they are not well distributed, then we would get a lot of coll isions and the speed to find a n element wou ld decline. 2. No matter how good our hash fu nction is, we will still have col l isions, so we need a method for handling
them. Th is often means chaining via a linked list, but it's not the only way. 3. We may also wish to im plement methods to dynamically increase or decrease the hash ta ble size depend ing on capacity. For exa m ple, when the ratio of the n u m ber of elements to the table size exceeds a certain threshold, we may wish to increase the hash table size. This would mean creating a new hash ta ble and transferring the entries from the old table to the new ta ble. Because this is an expensive opera tion, we want to be ca refu l to not do it too often. What can be used instead of a hash table, if the number of inputs is small?
You can use a n STL map or a binary tree. Although this ta kes 0( log ( n ) ) time, the number of inputs may be small enough to make this time neg ligible. Virtual Functions: How do virtual fu nctions work in C++?
1 2.4
p g 1 64
SOLUTION ----· · - ··- ·-·--· · · · ··�� - -��· ·
A virtua l fu nction depends on a "vtable" o r "Vi rtual Ta ble:' lf any fu nction of a class is declared to be virtual, a vtable i s constructed which stores add resses of the virtual fu nctions of this class. The compiler also adds a hidden vptr va riable in all such classes which points to the vtable of that class. If a virtual fu nction is not overridden in the derived class, the vta ble of the derived class stores the address of the fu nction in its parent class. The vtable is used to resolve the add ress of the fu nction when the virtual fu nction is called. Dynamic binding in C++ is performed through the vtable mech anism. Thus, when we assign the derived class object to the base class pointer, the vptr variable points to the vta ble of the derived class. This assignment ensu res that the most derived virtual fu nction gets called. Consider the following code.
1 2
3 4
cla s s Shape { public : int edge_lengt h ; virtual int ci rcumference ( ) {
424
Cracking the Coding Interview. 6th Edition
Solutions to Chapter 1 2 5
I C and C ++
cout c c "Circ umference of Base C l a s s \n"; return 0 ;
6
7 8
};
9
}
c la s s Tria ngle : p ublic Shape { public : int circumference ( ) { cout c c "C i rcumference of Triangle Cla s s \ n" ; r et u r n 3 * edge_lengt h ; } };
10 11
12
13
14
15
16 17 18 void ma i n ( ) { 19 Shape * x n ew Shape ( ) ; 20 x - > c i r c umference ( ) ; // "Circumfe rence of Base Class" 21 S h a p e *y = n e w T r iangle ( ) ; y - > c i r c umfe rence ( ) ; / / "C ircumfe rence of Triangle Class" 22 23 } =
In the previous example, c i rcumfe renc e is a virtual fu nction in the Shape class, so it becomes virtual in each of the derived classes (Tr i a n g l e, etc). C++ non-virtua l fu nction calls are resolved at compile time with static binding, while virtual fu nction calls a re resolved at runtime with dynamic binding. 1 2.5
Shallow vs Deep Copy: What is the difference between deep copy and shallow copy? Explain how you wou ld use each. pg 1 64
SOLUTION
A shallow copy copies all the member values from one object to a nother. A deep copy does all this and also deep copies any pointer objects. An example of shallow and deep copy is below. str uct Test { 2 char * pt r ; }; 3
1
4 5
6
7
8 9
void shallow_copy ( Test & s r c , Test & dest) { dest . pt r src . pt r ; } =
void deep_copy ( Test & s r c , Test & dest) { dest . pt r = ( char* ) malloc ( strlen ( s rc . pt r ) 11 strcpy ( dest . pt r , src . ptr ) ; 12 } 10
+
1);
Note that s h a l l ow_ copy may cause a lot of programming ru ntime errors, especia lly with the c reation and deletion of objects. Shal low copy should be used very ca refu lly and only when a programmer rea lly under sta nds what he wants to do. I n most cases, shallow copy is used when there is a need to pass i nformation about a com plex structu re without actual duplication of data. One must a lso be carefu l with destruction of objects in a shal low copy. In rea l life, shallow copy is rarely used. Deep copy should be used in most cases, especia lly when the size of the copied structu re is small.
CrackingTheCodinglnterview.com I 6th Edition
425
Solutions to Cha pter 1 2
I C and C + +
Volatile: What is the significance of the keyword "volatile" in C?
1 2.6
pg 1 64
SOLUTION
The keyword volat i l e i nforms the com piler that the value of variable it is applied to can change from the outside, without any update done by the code. This may be done by the operati ng system, the hardwa re, or another thread. Because the va lue can cha nge unexpectedly, the com piler will therefore reload the value each time from memory. A volatile i nteger can be declared by either of the fol lowing statements: int volatile x ; volatile int x ; To declare a poi nter t o a volatile integer, w e do the fol lowing: volatile int * x ; int volatile * x ; A volatile poi nter t o non-volatile data i s rare, but can b e done. i n t * volatile x ; I f you wanted t o declare a volati le va riable poi nter fo r volati le memory (both poi nter address and memory contai ned a re volatile), you would do the following: int volatile * volat ile x; Volatile variables a re not optimized, which can be very usefu l. Imagine this fu nction: 1 i n t opt = 1 ; 2 void F n (void ) { start : 3 4 if (opt 1 ) goto start ; else brea k ; S ==
6
}
At first glance, our code appea rs to loop infinitely. The com piler may try to optimize it to: 1 void F n (void ) {
2
start :
3
i n t opt = 1 ; if (true) goto start ;
4 5
6
}
This becomes a n i nfi nite loop. However, a n external operation might write 'O' to the location of va riable opt, thus brea king the loop. To prevent the compiler from performing such optimization, we want to signal that a nother element of the system cou ld cha nge the variable. We do this using the vola t i l e keyword, as shown below. 1 volatile int opt = 1 ; 2 void F n ( void ) { 3 start : 4 if ( opt 1 ) goto start ; else brea k ; 5 ==
6
}
Volatile variables are also usefu l when multi-threaded programs have global variables a nd any thread can modify these shared va riables. We may not want optim ization on these variables.
426
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 1 2 1 2.7
I C and C++
Virtual Base Class: Why does a destructor in base class need to be declared virtual?
pg 1 64
SOLUTION
Let's think about why we have vi rtual methods to sta rt with. Suppose we have the following code: 1 class F oo { public : void f ( ) ;
2
3 4
};
5 6
class Bar : public Foo { public : void f ( ) ;
7
8
9
} 10 1 1 Foo * p
12
=
new Ba r ( ) ;
p - >f( ) ;
Calling p - > f ( ) will result i n a call to F oo : : f ( ) . This is because p is a pointer to F oo, and f ( ) is not vi rtual.
10 ensure that
p - >f ( ) will invoke the most derived implementation of f ( ) , we need to declare f ( ) to be a vi rtua l fu nction.
Now, let's go back to our destructor. Destructors a re u sed to clean up memory and resou rces. If F oo's destructor were not virtual, then F oo's destructor wou ld be cal led, even when p is really of type B a r. This is why we declare destructors to be virtual; we want to ensu re that the destructor for the most derived class is called. 1 2.8
Copy N ode: Write a method that takes a poi nter to a Node structu re as a parameter and returns a
complete copy of the passed in data structure. The Node data structu re contains two pointers to other Nod es. pg 7 64
SOLUTION
. . ............................, _ .. ,,,,_,,_,_ .. _,.
------- ----
The algorithm wil l maintain a mapping from a node address in the original structure to the corresponding node in the new structu re. This mapping will allow us to discover previously copied nodes d u ring a tradi tional depth-fi rst traversa l of the structu re. Traversa ls often mark visited nodes-the mark can take many forms and does not necessa rily need to be stored i n the node. Thus, we have a simple recursive algorithm: typedef map NodeMa p ; 1 2 3
4 5
6
7
8 9
1e
11 12
Node * copy_recursive ( Node * cur , NodeMa p & nodeMa p) { if ( cur NU L L ) { return NU L L ; ==
}
N o d eMa p : : it e r a t o r i = no deMa p . f in d ( c u r) ; if ( i ! = nodeMap . end ( ) ) { / / we' ve been here before, r et u rn the copy return i - >second ; }
CrackingTheCodingl nterview.com I 6th Edition
427
Sol utions to Chapter 1 2 13 14 15
16
17
I C and C + +
Node * node = new Node ; nodeMa p ( c u r ] = node; I I m a p current before t raversing links node - >pt rl = copy_recu rsive (cu r - > pt rl , nodeMa p ) ; node - >ptr2 copy_recu rsive ( c u r - >ptr2, nodeMap ) ; ret u r n node ; =
18
19
}
w 21
Node * copy_struct u r e ( Node * root ) { NodeMa p nodeMa p ; I I we will need a n empty map ret u r n copy_recu r sive ( root , nodeMa p ) ; 23 24 }
22
Write a smart pointer class. A smart pointer is a data type, usually implemented with tem plates, that simulates a pointer while also providing a utomatic ga rbage collection. It a utomatically counts the n u m ber of referen ces to a SmartPointer object and frees the object of type T when the reference count h its zero.
1 2.9
Smart Pointer:
pg 1 64
SOLUTION
----
--
--
--
·-
--
A smart pointer is the same a s a normal pointer, b ut it provides safety via a utomatic memory ma nagement. It avoids issues like da ngling pointers, memory leaks a n d a l location failures. The smart pointer must main tai n a single reference count for all references to a given object. This is o n e of those problems that seems at fi rst gla nee pretty overwhel m ing, especially if yo u're not a C ++ expert. One useful way to approach the problem is to divide the problem i nto two pa rts: (1 ) outline the pseudocode a n d approac h a n d then (2) implement the detailed code. I n terms of the a pproa c h, we need a reference count va ria ble that is incremented when we a d d a new refer ence to the object a n d decremented when we remove a reference. The code should look someth ing l i ke the below pseudocode: 1
template < class T> c l a s s Sma rtPointer { I * The sma rt pointer c l a s s needs pointers to both the object itself and to the * ref count . These must be point e r s , rather than the actual object or ref count * va lue, si nce the goa l of a sma rt pointer i s that the reference count is * tracked a c ross mult iple sma rt pointers to one object . * I T * obj ; u n s igned * ref_count ; }
2
3
4 5
6 7 8
We know we need constructors a n d a single destructor for this class, so let's a d d those first. 1 Sma rtPoi nter ( T * obj ect ) { 2 I * We want to set the value of T * obj , and set the reference counter to 1 . * I 3 }
4 5
Sma rtPointer ( Sma rtPointer& s pt r ) { I* Th is const r uctor c reates a new s m a r t pointer t h a t poi nts t o an exist ing 7 * object . We will need to fi rst set obj and ref_count to pointer to spt r ' s obj g * and ref_count . Then, because we c reated a new reference to obj , we need to * i n c rement ref_count . */ 9 10 } 6
11
12 13
NSma rtPoi nter ( Sma rtPointer s pt r ) { I * We a re destroying a reference to the obj ect . Dec rement ref _cou nt . If
428
I
Cracking the Coding Interview, 6th Ed ition
Solutions to Chapter 1 2 14
I c and c++
* ref_count is 0, t hen free the memory created by the intege r and destroy the * obj ect . * /
15
16 }
There's one additional way that references can be c reated: by setting o n e SmartPo inter equal to a n other. We'll want to override the equal operator to handle this, but for now, let's sketch the code like this. 1
2 3
4
5
onSetEquals ( SmartPoint ptrl, Sma rtPoint pt r2) { /* If ptrl has a n existing value, dec rement its reference count . Then , copy the * pointers to obj and ref_count ove r . F i nally, since we created a new * reference, we need to inc rement ref_count . * / }
Getting just the approach, even without fi l ling i n the complicated C++ syntax, would count for a lot. Finishing out the code is now j ust a matter of fi l l i n g the details. 1
2
3
4 5
6
7
8 9
10
11
12 13
14 15 16 17
18 19 20
template < c l a s s T> class Sma rtPointer { public : Sma rtPointer ( T * pt r ) { ref = pt r ; ref_count ( un s igned* ) ma l loc ( s izeof ( uns igned ) ) ; * ref_count = 1 ; } =
Sma rtPointer ( Sma rtPointer & s pt r ) { ref s pt r . ref; ref_count s pt r . ref_count ; ++ ( * ref_count ) ; } =
=
/* Ove rride the equal operato r , so t hat when you set one smart pointer equal to * a nother the old sma rt pointer h a s its reference count dec remented a nd the new * smart pointer has its refe rence count incrememented . */ Sma rtPointer & operator= ( SmartPo i nter & spt r ) { if ( t h i s &spt r ) ret urn *this ; ==
21 22
/ * If a l ready a s s igned to an o b j e c t , remove one reference . */ if ( * ref_count > 0) { remove ( ) ; }
23 24 25
26
=
27
=
28
29 30
31
32
33 34 35
36
37
38 39
40
41
42 43
ref s pt r . ref; ref_count s pt r . ref_count ; ++ ( * ref_count ) ; return *this ;
}
�sma rtPointe r ( ) { remove ( ) ; / / Remove one refe rence t o object . } T getValue ( ) { ret urn * ref; } protected : void remove ( ) { - - ( * ref_count ) ; if ( * ref_count
==
0) {
CrackingTheCodinglnterview.com I 6th Edition
429
Solutions to Chapter 1 2 I C a n d C ++ 44 45
46
47
48
}
delet e ref ; free ( ref_count ) ; ref = N U L L ; ref_cou nt = NULL;
49
}
51
T * ref;
50
52
53
uns igned * ref_count ; };
The code fo r this problem i s complicated, and you probably wouldn't be expected to complete i t flawlessly. 1 2. 1 0 Malloc: Write a n aligned malloc and free function that supports allocating memory such that the
memory address returned is d ivisible by a specific power of two. EXAMPLE a l ign_ma l l o c ( 1000 , 1 2 8 ) will return a memory address that is a m u ltiple of 1 28 and that points to memory of size 1 000 bytes. a l ign ed_free ( ) will free memory allocated by a l ign_ma l l oc .
pg 164 SOLUTION
··· · · · · · ······ ·· ····· ······ · ····· · ····-·----
Typically, with ma l l o c , we do not have control over where the memory is allocated within the heap. We just get a pointer to a block of memory which could sta rt at any memory address within the heap. We need to work with these constraints by requesti ng enough memory that we can return a memory address which is divisi ble by the desired value. Suppose we are requesting a 1 00-byte chunk of memory, and we want it to start at a memory address that is a multiple of 1 6. How much extra memory would we need to allocate to ensure that we can do so? We would need to allocate an extra 1 5 bytes. With these 1 5 bytes, plus a nother 1 00 bytes right after that sequence, we know that we would have a memory address d ivisible by 1 6 with space for 1 00 bytes. We cou ld then do something like: 1 2 3
4 5 6
void* aligned_ma l l oc ( size_t req u ired_bytes , size_t a lignment ) { int offset = alignment 1; void* p = ( vo id * ) ma l l o c ( required_bytes + offset ) ; void* q = ( vo id * ) ( ( ( s i ze_t ) ( p ) + offs et ) & - ( a lignment 1)); return q ; -
-
}
Line 4 is a bit tricky, so let's discuss it. Suppose a l ignment is 1 6. We know that one of the first 1 6 memory address in the block at p must be d ivisible by 1 6. With ( p + 1 5 ) & 1 1 10000 we advance as need to this add ress. ANDing the last fou r bits of p + 15 with 0000 guara ntees that this new va lue will be divisible by 16 (either at the orig inal p or i n one of the following 1 5 addresses}. •
•
•
This solution is almost perfect, except for one big issue: how do we free the memory? We've allocated an extra 1 5 bytes, in the a bove example, and we need to free them when we free the "real" memory. We can do this by storing, in this "extra" memory, the address of where the fu ll memory block begins. We will store this immediately before the aligned memory block. Of course, this means that we now need to allocate even more extra memory to ensure that we have enough space to store this pointer.
430
Cracking the Coding I nterview, 6th Edition
Sol utions to Chapter 1 2
I C and C++
Therefore, to guarantee both an a ligned add ress and space for th is pointer, we will need to al locate a n addi tional a l ignment - 1 + s i z e o f ( vo i d * ) bytes. The code below implements this approach. 1
2 3
4 5
6
7 8
'
10
11
12 13
14 15
16 17
void* aligned_ma lloc ( si ze_t required_byt e s , size_t alignment ) { void * p l ; / / initial blo c k void* p2; // a lig n e d blo c k i n sid e i n i t i a l blo ck int offs et = a l ig n me nt - 1 + s i zeo f ( void * ) ; NU L L ) { if ( ( pl = ( void * ) malloc ( required_bytes + offset ) ) ret urn NU L L ; } p2 = ( void* ) ( ( ( s i ze_t ) ( pl ) + offset ) & - ( a lignment - 1 ) ) ; ( ( void * * ) p2 ) [ - 1 ] = pl ; return p2; } void aligned_free ( void *p2) { /* for consist ency, we use the same names as a ligned_malloc * / void* pl = ( ( void* * ) p 2 ) [ - 1 ] ; free ( pl ) ; }
Let's look at the poi nter a rith metic i n lines 9 and 1 5. If we treat p2 as a void * * (or an a rray of void * 's), we can just look at the index - 1 to retrieve p l . In a l i gned_free, we ta ke p 2 as the same p 2 returned from a l i g n e d _ma l l oc . As before, we know that the value of p l (which points to the beginning of the fu ll memory block) was stored just before p 2 . By freeing pl, we dea l locate the whole memory block. 1 2.1 1 20 Alloc: Write a fu nction in C cal led m y 2 DAl loc which allocates a two-d imensional array.
Minimize the number of ca lls to ma l loc and make sure that the memory is accessible by the notation a r r [ i] [ j ] . pg 7 64
SOLUTION
As you may know, a two-dimensional a rray is essentially an array of a rrays. Since we use poi nters with arrays, we can use double poi nters to create a double array. The basic idea is to create a one-d imensional array of pointers. Then, for each a rray index, we create a new one-d imensional a rray. Th is gives us a two-d imensional a rray that can be accessed via array ind ices. The code below implements this. l
1 3
4 5
6 7
8
9
int** my2DAlloc( int rows , int co l s ) { int** rowpt r; int i ; rowpt r = ( int * * ) malloc ( rows * sizeof ( i nt * ) ) ; for ( i = 0; i < rows ; i++ ) { rowpt r [ i ] = ( int * ) ma lloc ( cols * s i zeof ( int ) ) ; } return rowptr ; }
Observe how, in the above code, we've told rowpt r where exactly each index should point. The following d i a g ram re pre s e n ts how this memory is allocated.
CrackingTheCodinglnterview.com I 6th Edition
43 1
Solutions to Cha pter 1 2
I C and C + +
To free this memory, we cannot simply ca ll free on rowpt r. We need to make sure to free not only the memory from the fi rst ma lloc cal l, but a lso each subseq uent ca ll. 1
void my2DDealloc ( int * * rowpt r , int rows ) { for ( i = 0 ; i < rows ; i++) { free ( rowpt r [ i ] ) ; } free ( rowpt r ) ;
2
3
4 5
6
} Rather than al locating the memory in many different blocks (one block for each row, plus one block to specify where each row is located), we can allocate this in a consecutive block of memory. Conceptua l ly, for a two-dimensional a rray with five rows and six columns, this would look like the following.
If it seems strange to view the 2D array like this (and it probably does), remember that this is fundamenta l ly no different than the fi rst diagram. The only difference is that the memory is in a contiguous block, so our first five (in this example) elements point elsewhere in the same block of memory. To 1
2 3
4 5
6 7
implement this solution, we do the following. int * * my2DAlloc ( int rows , int col s ) { int i ; int header = rows * sizeof ( i nt * ) ; int data = rows * cols * s izeof( int ) ; int * * rowpt r = ( int * * ) ma l l o c (header + dat a ) ; if ( rowpt r == NUL L ) return NUL L ;
8
int* buf = ( int * ) ( rowpt r + rows ) ; for ( i = 0 ; i < row s ; i++ ) { rowpt r [ i ] = buf + i * col s ;
9
10
11 12 13
}
return rowpt r ;
}
You should ca refu lly observe what is happening on lines 1 1 through 1 3. If there a re five rows of six columns each, a r ray [ 0 ] will point to a r r ay [ S ] . a r r ay [ l ] will point to a r ray [ 1 1 ] . and so on. Then, when we actually ca ll a r r ay [ 1 ] [ 3 ] , the com puter looks u p a r ray [ l ] , which is a pointer to another spot in memory-specifically, a pointer to a rray [ 5 ] . This element is treated as its own array, and we then get the third (zero-indexed) element from it. Constructing the a rray in a single call to m a l l o c has the added benefit of al lowing disposa l of the array with a single f ree ca ll rather than using a special fun ction to free the remaining data blocks.
432
Cracking the Cod ing I nterview, 6th Edition
13 Sol utions to Java
1 3.1
Private Constructor: I n terms of inheritance, what is the effect of keeping a constructor private? pg 1 61
SOLUTION
Declaring a constru ctor p rivate on class A means that you can only access the (private) constructor if you could also access A's private methods. Who, other than A, can access A's private methods and constructor? A's inner classes can. Additionally, if A is an inner class of Q, then Q's other inner classes can. This has d i rect impl ications for inherita nce, since a su bclass calls its pa rent's constructor. The class A can be i n herited, but only by its own or its pa rent's inner classes. 1 3.2
Return from Finally: In Java, does the finally block get executed if we insert a retu rn statement
inside the try block of a try-catch-finally? pg 1 67
SOLUTION
Yes, it will get executed. The f i n a l ly block gets executed when the t ry block exits. Even when we attempt to exit with in the t ry block (via a ret u r n statement, a c ont i n u e statement, a b r e a k statement or any exception), the f i n a l ly block will still be executed. Note that there a re some cases in which the fina lly block will not get executed, such as the following: •
•
If the vi rtual machine exits d u ring t ry / c a t c h block execution. If the thread which is executing during the t ry I catch block gets killed.
1 3.3
Final, etc.: What is the difference between f i n a l, f i n a l ly, and f i n a l i ze? pg 1 67
SOLUTIONS
Despite their similar sounding names, f i n a l, f i n a l ly and f i n a l i z e have very different pu rposes. speak in ve ry genera l terms, f i n a l is used to control whether a variable, method, or class is "change able:· The finally keyword is used in a t ry / c a t c h block to ensure that a seg ment of code is always executed. The fina l i z e ( ) method is called by the garbage col lector once it determines that no more references exist.
To
CrackingTheCodinglnterview.com I 6th Edition
433
Solutions to Chapter 1 3
I Java
Fu rther detai l on these keywords and methods is p rovided below. final
The fi nal statement has a different meaning depending on its context. •
When a pplied to a variable (primitive): The value of the va riable cannot cha nge.
•
When appl ied to a va riable (reference): The reference variable cannot point to a ny other object on the heap.
•
When applied to a method: The method cannot be overridden.
•
When applied to a class: The class cannot be subclassed.
finally keyword
There is an optional f i n a l l y block after the t ry block o r after the c a t c h block Statements i n the f i n a l l y block wi ll always be executed, even if an exception is th rown (except if Java Vi rtual Machine exits from the t ry block). The fina l ly block is often used to write the clean-up code. It will be executed after the t ry a nd c a t c h blocks, but before control transfers back to its origin. Watch how this plays out in the example below. 1
public static String lem ( ) { System . out . print l n ( "lem'' ) ; return "return from lem'' ; }
2 3
4 5
6
7
public static String foo ( ) { int x 0; int y = 5 ; t ry { System . out . println ( "start t ry" ) ; i nt b = y I x ; System . out . p rintln ( "end t ry" ) ; return " returned f rom t ry" ; } catch ( Except ion ex ) { System . out . p r i nt ln ( "catch" ) ; ret u r n lem ( ) + " I ret u r ned from catch"; } fina l ly { System . out . println ( "fina l ly" ) ; } } =
8 9
10 11
12 13
14
15
16
17 18
19
20
21 22
23
24
25
26 27 28 29
30
31
public static voi d bar ( ) { System . out . println ( "start bar'' ) ; String v = foo ( ) ; System . out . println ( v ) ; System . out . pri nt l n ( "end ba r'' ) ; } public static void main ( St ri ng [ ] a rgs ) { ba r ( ) ; }
The output for this code is the followi ng: 1 st a rt bar 434
Cracking the Coding I nterview, 6th Edition
Solutions to Chapter 1 3 2
3 4
5
6
7
I Java
sta rt try catch lem fi nally
return from 1.em end bar
\
returned from eaten
Look ca reful ly at lines 3 to 5 in the output. The c a t c h block is fu lly executed (including the fu nction ca l l in the retu rn statement), then the f i n a l l y block, a nd then the fu nction actually retu rns. finalize()
The automatic garbage collector calls the f i n a l i z e ( ) method just before actually destroying the object. A class can therefo re override the f i n a l i z e ( ) method from the O b j e c t class in order to defi ne custom behavior during garbage col lection. 1
prot ected void fi n a l ize ( ) throws Throw a ble {
3
}
/ * Cl o s e open files , release resources , etc * /
2
Generics vs. Templates: Expla in the difference between templates in C++ and generics in Java.
1 3.4
pg 1 6 7
SOLUTION
Many program mers consider templates and generics to be essentially equivalent because both al low you to do something like L i s t < St r i n g > . B ut how each language does this, and why, va ries significantly. ,
The implementation of Java generics is rooted in an idea of "type erasu re:' This technique eliminates the parameterized types when source code is translated to the Java Virtual Machine (JVM) byte code. For exa m ple, suppose you have the Java code below: 1
2 3
Vecto r < String> vector = new Vector ( ) ; vec t o r . a d d ( n ew String( "hello" ) ) ; String s t r vector . ge t ( 0 ) ; =
During compilation, this code is re-written into: l Vector vector = n ew Vector ( ) ; 2
3
vector . add ( new String( "hello" ) ) ; String str = ( St r i n g ) vector . ge t ( 0 ) ;
The use of Java generics did n't really change much a bout our capabil ities; it just made things a bit prettier. For this reason, Java generics a re sometimes ca lled "syntactic sugar:' This is quite different from c++. I n C++, templates are essentially a glorified macro set, with the compiler creating a new copy of the template code fo r each type. Proof of this is i n the fact t hat an insta nce of MyC l a s s < F o o > will not share a static varia ble with MyC l a s s < B a r > . Two instances of MyC l a s s < F o o > , however, will share a static variable. To illustrate this, consider the code below: / * * * My C l a s s . h * * * / 1 2
3
4 5
• 7
templat e < c l a s s T> c l a s s MyCl a s s {
public :
static int va l ; MyClass ( i nt v ) { val
v· } ,
};
Cracki ngTheCod ingl nterview.com I 6th
Edition I
435
Solutions to Chapter 1 3 8 9
I Java
I * * * MyClas s . c pp * * * I
10 11
12 13
14
template i n t MyC l a s s < T > : : b a r ;
template c l a s s MyCl a s s < Foo> ; template c l a s s MyClas s < Ba r > ;
1 5 I * * * ma i n . c p p * * * I MyC l a s s < Foo> * fool 1 7 MyC l a s s < Foo> * fool 18 MyC la s s < Ba r > * barl 19 MyC la s s < Ba r > * bar2
16
10
21
i nt i nt 2 3 i nt 24 i n t 22
=
fl fool - >va l ; fl = fool - >va l ; bl = barl- >va l ; bl = b a r l - > va l ;
n ew My C la s s < Foo> ( l0 ) ;
new MyC l a s s < Foo > ( lS ) ; new MyC la s s < Ba r > ( l0 ) ; new MyC la s s < Ba r > ( 35 ) ; II II II II
will wi ll will will
equal equal equal eq ual
15 15 35 35
I n Java, static variables are shared across instances ofMyC l a s s, regard less ofthe different type parameters. Java generics and (++ tem plates have a number of other differences. These include: C++ templates can use primitive types, l i ke i nt. Java cannot and m ust instead use Integer. In Java, you can restrict the template's type parameters to be of a certa i n type. For instance, you might use generics to im plement a Ca rdDec k and specify that the type pa rameter must extend from C a rd Game. In C ++, the type parameter can be instantiated, whereas Java does not support this. In Java, the type parameter (i.e., the Foo in MyC l a s s < Foo>) cannot be used for static methods and va ria bles, since these would be shared between MyC l a s s < Foo> and MyC l a s s < B a r > . In C++, these classes are different, so the type pa rameter can be used for static methods and va riables. In Java, all instances of MyC l a s s , regardless of their type parameters, are the same type. The type parameters a re erased at runtime. In C++, instances with d ifferent type pa rameters are different types. Remember: Although Java generics and (++ tem plates look the same in many ways, they are very different. 1 3.S
TreeMap, HashMap, LinkedHashMap: Explain the differences between T reeMa p, H a s hMap, and
L i n kedH a s hMap. Provide an example of when each one would be best. pg
1 67
SOLUTION
All offer a key->va lue map and a way to iterate through the keys. The most important distinction between these classes is the time guarantees and the ordering of the keys. Has hMap offers 0 ( 1 ) lookup and insertion. If you iterate through the keys, though, the ordering of the keys is essentially a rbitra ry. It is implemented by an a rray of linked lists. •
TreeMap offers 0 ( l o g N ) lookup and insertion. Keys a re ordered, so if you need to iterate through the keys in sorted order, you can. This means that keys must im plement the Compa r a b l e interface. TreeMap is implemented by a Red-Black Tree. L i n kedHa s hMap offers 0 ( 1 ) lookup and i nsertion. Keys are ordered by their insertion order. ft is implemented by doubly-linked buckets.
Imagine you passed an empty T reeMa p, H a s hMa p, and L i n kedHa s hMa p into the following function:
436
Cracking the Co din g Interview, 6th Edition
Solutions to Cha pter 1 3 1
void insertAnd Print (Abstra ctMap map) { int [ ] array = { 1 , - 1 , 0 } ; fo r ( int x : a r ray) { ma p . put ( x , Inte g e r . t o St r in g ( x ) ) ;
2
3
4
5
}
6
7
for ( i nt k : map . keySet ( ) ) { System . out . pri nt ( k + " , " ) ;
8
g
10
I Java
}
}
The output for each will look like the results below.
( a ny ord e r i n g ) Very im portant: The output o f L i n kedHa shMap a n d TreeMap must look like the above. For Has hMap, the output was, in my own tests, { 0 , 1 , - 1 }, but it could be any ordering. There is no guamntee on the ordering. When might you need ordering in real life? Suppose you were creating a mapping of names to P e r son objects. You might want to period ically output the people in alphabetical order by name. A Tr eeMa p lets you do this. A TreeMap also offers a way to, given a na me, output the next 10 people. This could be useful for a "More" fu nction in many applications. A L i n kedHa s hMap is useful whenever you need the ordering of keys to match the ordering of inser tion. This might be useful in a caching situation, when you want to delete the oldest item. Generally, unless there is a reason not to, you wou ld use Has hMa p. That is, if you need to get the keys back in insertion order, then use L i n kedHa s hMap. If you need to get the keys back in their true/natu ral order, then use TreeMap. Otherwise, H a s hMap is probably best. It is typically faster and requires less overhead. 1 3.6
Object Reflection: Expla in what object reflection is i n Java and why it is useful. pg 168
SOLUTION
Object Reflection is a feature in Java that provides a way to get reflective information a bout Java classes and objects, and perform operations such as: 1. Getting information a bout the methods and fields present inside the class at runtime. 2. Creating a new instance of a class. 3. Getting and setting the object fields directly by getting field reference, regard less of what the access
mod ifier is. The code below offers an exa m ple of object reflection. 1
/ * Parameters */
2
Obj ect [ ] d o u b l eA rg s
4
/ * Get cla s s * /
3
5
6
=
new Obj ect [ ] { 4 . 2 ,
Cla s s rectangleDefinition
3.9
};
C l a s s . fo r Na m e ( " MyP r o j . Recta ngle" ) ;
CrackingTheCodingl nterview.com I 6th Edition
437
/ Java
Solutions to Chapter 1 3 7
8
/ * Equivalent : Rectangle rectangle = new Rectangle ( 4 . 2 , 3 . 9 ) ; */ C l a s s [ ] doubleArgsClass new Cla s s [ ] { double . c las s , double . cl as s } ; Cons tructor doubleArgs Const ructor = rectangleDefinition . getCon st ructor ( doubleArgsC l a s s ) ; Rectangle rectangle = ( Rectangle ) doubleArgsConst r uctor . newlnstance (doubleArgs ) ; =
9
10
11 12 13
/* Equivalent : Double area = rectangle . area ( ) ; * / 14 Met hod m rectangleDefini t i on. getDeclaredMethod ( "area") ; 1 s Double area ( Double ) m . i nvoke ( rectangle ) ; =
=
This code does the equiva lent of: 1 Rectangle rectangle = new Rectangle ( 4 . 2 , 3 . 9 ) ; 2 Double area = rectangle . area ( ) ; Why Is Object Reflection Useful?
Of course, it doesn't seem very useful in the above exam ple, but reflection can be very usefu l i n some cases. Three main reasons are:
1 . It can help you observe or manipulate the runtime be havior of applications. 2. It can help you debug or test programs, as you have direct access to methods, constructors, a nd fields. 3.
You can cal l methods by name when you don't know the method in advance. For example, we may let the user pass in a class name, parameters for the constructor, and a method name. We can then use this information to create an object a nd ca l l a method. Doing these operations without reflection would require a com p l ex series of if-statements, if it's possible at all. Lambda Expressions: There is a class Cou n t ry that has methods getCont i n ent ( ) a nd get Popu lation ( ) . Write a fu nction int getPopu lat ion ( L i s t < Country> countrie s , String continent ) that computes the total population o f a given continent, given a l ist of all countries and the name of a continent.
1 3.7
pg
SOLllTION
------ ·-··· · ..-·-·..··-·-. .
168
. ... . _,_,, _ _ _ _ _ _ _ _
This question really comes i n two pa rts. First, we need to generate a list of the countries i n North America. Then, we need to compute their total population. Without lambda expressions, this is fai rly straig htforward to do. 1
2
3 4 S
6
7 8
9
10
int getPopulation ( Li st countries , String cont inent ) { int s um = 0 ; for ( Country c : countrie s ) { if ( c . getCont inent ( ) . eq u a l s ( cont i nent ) ) { sum += c . getPopu lation( ) ; } } return sum ; } im plement this with lambda expressions, let's break this up i nto multiple parts.
First, we use filter to get a list of the countries in the specified continent. 1 S t ream nort hAmerica = count ries . stream ( ) . filter ( 2 country - > { return country . getCont inent ( ) . equa l s ( cont i nent ) ; }
438
Cracking the Cod ing Interview, 6th Edition
Sol utions to Chapter 1 3 3
I Java
);
Second, we convert this i nto a list of populations using map.
1
St ream< Intege r > populations = northAmerica . ma p ( c - > c . getPopulation ( ) 3 ); Thi rd and finally, we compute the sum using red u c e . 2
1
int population = population s . reduce (0, ( a , b ) - > a
+
b);
This fu nction puts it a l l together. i nt getPopulat ion ( List countr ies , String cont inent ) { / * F i lter countries . * / Str ea m < Co u ntr y > s u b l ist = countries . st ream ( ) . fi l te r ( country - > { ret urn country . getcontinent ( ) . equa l s ( continent ) ; } );
1 2 3
4 5
6 7
/ * Convert to l ist of populations . * / Stream< Integer> pop ulations = s u b l i s t . ma p ( c - > c . getPopu lation ( ) );
8 9
10
11 12
/* Sum l ist . * / int population = populations . reduce ( 0 , (a, b ) - > a ret u r n populatio n ;
13
14
15
+
b);
}
Alternatively, because of the nature of this specific problem, we ca n a ctually remove the f i l t e r enti rely. The red u c e operation can have logic that maps the population of cou ntries not in the right conti nent to zero. The sum will effectively disrega rd countries not withi n c o n t inent. 1
int get Populat ion ( L i st count r i e s , String continent ) { Stream< I nteger> populations = countries . st ream ( ) . ma p ( c - > c . getCont inent ( ) . eq ua l s ( cont i n e nt ) ? c . get Population ( ) return pop u l a tions . r e d u ce ( o , ( a , b ) - > a + b ) ; }
2 3
4
5
0) ;
La mbda fu nctions were new to Java 8, so if you don't recognize them, that's probably why. Now is a great time to learn a bout them, though! 1 3.8
Lam bda Rand om: Using Lambda expressions, write a function L i s t < I n t e g e r > getRa ndomS u b s e t ( L i s t < I n t e g e r > 1 i s t ) that retu rns a ra ndom su bset of a rbitra ry size. A l l su bsets (including t h e empty set) should b e eq u a l ly l i kely t o b e chosen.
pg 439
SOLUTION ----
··· ·
·
·
""""
"
'"
"""'
It's tempti ng to a pproach this problem by picking a subset size from 0 t o N and then generati ng a ra ndom su bset of that size. That creates two issues: 1.
We'd have to weight those proba bilities. If N > 1, there a re more su bsets of size N / 2 than there are of su bsets of size N (of which there is always only one).
2. It's actua l ly more difficult to generate a su bset of a restricted size (e.g., specifically 1 O) than it is to
generate a su bset of a ny size.
C ra c k i n gThe Co d i n gl n terv iew.c o m I 6th Editi o n
439
Solutions to Chapter 1 3
I
Java
Instead, rather than generating a subset based on sizes, let's think a bout it based on elements. (The fact that we're told to use lambda expressions is also a hint that we should think about some sort of iteration or processing through the elements.) Imagine we were iterating through { 1 , 2 , 3 } to generate a subset. Should 1 be in this su bset ?
We've got two choices: yes or no. We need to weight the probability of"yes" vs. "no" based on the percent of su bsets that contain 1. So, what percent of elements contain 1 ? For any specific element, there are as many subsets that contain the element as do not contain it. Consider the fol lowing: {} {2} {3} {2, 3}
{1} {1 , 2 } {1, 3} { 1 , 2, 3 }
Note how the difference between the subsets on the left and the subsets on the right is the existence of 1 . The left and right sides must have the same number of su bsets because we can convert from one to the other by just adding an element. This means that we can generate a random subset by iterating through the list and flipping a coin (i.e., deciding on a 50/50 chance) to pick whether or not each element will be in it. Without lambda expressions, we can write something like this:
1 2
List get Ra ndomSubset ( List< Integer> l ist ) { L i s t < I ntege r > subset = new ArrayList < I nteger> ( ) ; 3 Ra ndom random new Random ( ) ; 4 for ( i nt item : list ) { /* F li p coi n . * / 5 if ( random . nextBoolea n ( ) ) { 6 7 s ubset . add ( item ) ; 8 } 9 } return subset ; 10 11 } =
To implement this approach using lambda expressions, we can do the following: 1 2 3
4 5
6
7
List < I ntege r > getRa ndomS ubset ( List < I nteger> list ) { Ra ndom ra ndom = new Random ( ) ; List< I ntege r > subset = list . st ream( ) . fi l t e r ( k - > { return ra ndom . nextBoolea n ( ) ; / * F l ip c oi n . */ } ) . collect ( Collectors . tolist ( ) ) ; return subset ; }
O r, we can use a p redicate (defined with in the class or withi n the function): 1
2
3
4 5
Random ra ndom = new Random ( ) ; Predicate flipCoi n o -> { return ra ndom . next Boolea n ( ) ; }; =
6
L i s t < I nt eg e r > get Ra ndomSubset ( List< I nteger> list ) { List s u b s et = list . strea m ( ) . f i lte r (flipCoi n ) . collect ( Collectors . to l i st ( ) ) ; ret u r n subset ; ' 10 }
7 8
The nice thing a bout this implementation is that now we can apply the flipCoin predicate in other places.
440
I
Cracking the Cod i n g Interview, 6th Edition
14 Sol utions to Data bases
Questions 1 through 3 refer to the followi ng database schema:
r!�r¥ie Requ est ID
int
Apt ID
int
BuildingID
int
UnitNumber
va rch a r ( 1 0)
ComplexID
int
St a tus
varchar ( 100)
Bu i l d ingID
int
BuildingName
va rchar ( 100 )
Apt ID
int
Address
va r c h a r ( 5 00)
C9111plexes
AptTeriant!!
ComplexID ComplexName
Tenant ID
varchar( 100)
Apt ID
I int I int
D e s c r ip t ion
va rch a r ( 5 00 )
Tenants
TenantID TenantName
va r c h a r ( 100 )
Note that each apartment can have multiple tena nts, and each tenant can have m u ltiple apartments. Each apartment belongs to one buildi ng, and each building belongs to one complex. 1 4.1
Multiple Apartments: Write a SQL query to get a list of tena nts who are renting more than one a partment. pg 1 72
SOLUTION To
im plement this, we can use the HAVING and GROU P BY clauses and then perform an INNER JOIN with
Tenants . 1
2 3
4
5
S E L ECT Tena nt Name F ROM Tenants INN E R J OIN ( S E L ECT TenantID F ROM AptTe n a nt s GROUP BY TenantID HAVING count ( * ) > 1) C ON T e n a nts . T e n a n t I D C . Te n a n t I D �
Whenever you write a GROUP BY clause i n a n i nterview (or in real life), make sure that anything in the S E L ECT clause is either an aggregate fu nction or conta ined within the GROUP BY clause.
CrackingTheCodinglnterview.com I 6th Edition
441
Solutions to Chapter 1 4
] Databases
Open Requests: Write a SQL query to get a list of all buildings and the number of open requests
1 4.2
(Req u e s t s in which stat u s equals 'Open'). pg 1 73
SOLUTION
This problem uses a straightforward join of Req u e s t s and Apa rtment s to get a list of building IDs and the number of open requests. Once we have this list, we join it again with the B u ildings table. 1 S E L ECT Build ingName , ISNU L L ( Count , 0) as 'Count ' 2
F ROM Build ings L E F T J OI N ( S E L E CT Apartment s . BuildingID, count ( * ) as 'Count ' F ROM Req uests I NN E R JOIN Apartment s ON Request s . AptID = Apartment s . AptID WHE R E Req ues t s . Status = 'Open' GROUP BY Apartments . B u i ldingID ) ReqCounts ON ReqCount s . BuildingID = Buildings . BuildingID
3 4 5
6
7
8
9
Queries like this that utilize su b-queries should be thoroughly tested, even when coding by hand. It may be usefu l to test the inner part of the query first. and then test the outer pa rt. Close All Requests: Building #1 1 is u ndergoing a major renovation. Implement a query to close a l l
1 4.3
requests from apartments in this building. p g 1 73
SOLUTION
U PDATE queries, like S E L ECT queries, can have WH E R E clauses. To implement this query, we get a list of a l l apartment I Ds withi n building #1 1 and t h e list o f update requests from those apartments. 1
UPDATE Req ues t s SET Sta t us = ' C l osed ' WHERE Ap tID IN ( S E LECT AptID F ROM Apartme nts WHE RE Build ingID
2
3
=
11)
Joins: What a re the different types of joins? Please explain how they differ and why certai n types
1 4.4
a re better in certain situations. pg 1 73
SOLUTION -
- � � = �== ------
------
J O I N is used to com bine the resu lts of two tables. To perform a J O IN, each of the tables must have at least one field that will be used to find matching records from the other table. The join type defines which records will go into the resu lt set. Let's take for example two tables: one table lists the "regular" beverages, and a nother lists the calorie-free beverages. Each table has two fields: the beverage name and its prod uct code. The "code" field will be used to perform the record matching. Regular Beverages:
Coca - Cola
442
Cracking the Coding I nterview, 6th Edition
COCACOLA
Solutions to Cha pter 1 4
I
Data bases
Pepsi
Calorie-F ree Beverages:
Diet Coc a - Cola
COCACOLA
Fresca
FR ESCA
Diet P epsi Pepsi L ight P u rified
Water
P EPS I P E PS I
water
If we wa nted to join Beverage with C a l o r ie - F re e Beve rage s , we would have many options. These a re discussed below. I N N E R JOIN: The result set wou ld contain only the data where the criteria match. In our exa mple, we would get three records: one with a COCACO LA code and two with P E P S I codes. •
OUTE R JOIN: An OUT E R JOIN will always conta in the resu lts of I N N E R J O I N, but it may a lso conta in some records that have no matching record i n the other table. OUT E R JOINs a re divided into the following subtypes:
1 4.S
»
L E FT OUT E R JOIN, or simply L E F T JOIN: The result wi l l contain all records from the left table. If no matching records were found in the right table, then its fields will contain the NU L L va lues. In our exa m ple, we wou ld get fou r records. I n addition to I N N E R JOIN results, B UDWE I S E R would be listed, because it was in the left ta ble.
»
RIGHT OUT E R JOIN, or simply RI GHT JOIN: This type ofjoin is the opposite of L E FT JOIN. It will conta in every record from the rightta b le; the missing fields from the left table will be NU L L . Note that i f we have two ta bles, A a n d B , then we can sa y that the statement A L E F T JOIN B is equivalent to the statement B RI GHT JOIN A. In our example a bove, we will get five records. I n add ition to I N N E R J O I N results, F R E SCA a n d WAT E R records will b e listed.
»
F U L L OUTE R JOIN: This type of join combi nes the resu lts of the L E FT and RIGHT JOI NS. All records from both tables will be included in the result set, regard less of whether or not a matching record exists in the other table. If no matching record was found, then the corresponding result fields will have a NU L L value. I n our example, we will get six records. Denormalization: What is denormal ization? Explain the pros and cons. pg 1 73
SOLUTION
Denormalization is a database optimization tech nique in which we add red undant data to one or more ta bles. This can help us avoid costly joins in a relational database. By contrast, in a traditional normalized database, we store data in sepa rate logical tables and attem pt to minimize red undant data. We may strive to have only one copy of each piece of data in the database. For exam ple, in a normalized database, we might have a Cou r s e s ta ble and a Tea c he r s ta ble. Each entry in Cou rses wou ld store the t e a c h e rID for a Cou r s e but not the t e a c he rName. When we need to retrieve a l ist of all Cou r s e s with the Tea c h e r na me, we would do a join between these two tables.
CrackingTheCodingl nterview.com I 6th Edition
443
Sol utio ns to Chapter 1 4
I Databases
Jn some ways, this is great; if a teacher changes his or her name, we only have to update the name in one place. The drawback, however, is that if the ta bles a re large, we may spend an un necessarily long time doing joins on tables. Denorma lization, then, strikes a different com promise. U nder denormalization, we decide that we're okay with some redunda ncy and some extra effort to update the database in order to get the efficiency advan tages of fewer joins.
U pdates and inserts a re more expensive.
Retrieving data is faster si nce we do fewer joins.
Denorma lization can make update and i nsert code Queries to retrieve can be simpler (and therefore less likely to have bugs), since we need to look at harder to write. fewer ta bles. Data may be inconsistent. Which is the "correct" va lue for a piece of data? Data redundancy necessitates more storage. In a system that demands scalability, like that of any major tech companies, we almost always use elements of both normalized and denormal ized databases. 1 4.6
Entity-Relationship Diagram: Draw a n entity-relationship diagram for a database with companies, people, and professionals (people who work fo r companies). pg 7 13
SOLUTION ------ · -· ·- ·- ·-··- ·· - ··-·- · ·-··-·- ·-··- · ----
People who work for Compa n i es are Profes siona l s. So, there is a n ISA ("is a") relationship between People and P rofe s s iona l s (or we could say that a P rofe s s i o n a l is derived from People). Each P rofe s s iona l has add itional information such as deg ree and work experiences in add ition to the properties derived from P eo p le. A Profe s s io n a l works for one company at a time (probably-you might want to validate this assump tion), but Compa n i e s can hire many P rofe s s iona l s . So, there is a many-to-one relationship between Profe s s io n a l s and Comp a n i e s. This "Works For" relationship can store attributes such as an employee's start date and salary. These attributes are defined only when we relate a P rofe s s iona l with a Company. A P e rson can have multiple phone numbers, which is why P hone is a mu lti-valued attri bute.
444
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 1 4 I Databases
Date of J oining ISA
N --- = @GPACutOff) Honor s INNER J OIN Students ON Honors . Student ID = Student . St udentID
Be very careful a bout what implicit assum ptions you make. If you look at the above database descri ption, what potentially incorrect assumption do you see? One is that each cou rse can only be taught by one professor. At some schools, courses may be taught by multi ple professors. However, you will need to make som e assu m ptions, or you'd drive yourself crazy. Which assumptions you make is less im portant tha n just recognizing that you made assu m ptions. Incorrect assum ptions, both in the real world and in an interview, can be dealt with as long as they are acknowledged. Rem ember, additionally, that there's a trade-off between flexi bility and complexity. Creating a system i n
which a cou rse can h a ve multiple profossors does i ncrease t h e data base's flexibi lity, but i t also i ncreases its
compl exity. If we tri ed to make our d atabase flexible to every possible situation, w e'd wind up with some thing hopelessly com plex.
Make you r design reaso nably flexible, and state any other assu m ptions or constra ints. This goes for not just database design, but object-oriented design and prog ramming in general. 446
Cracking the Coding Interview, 6th Edition
15 Sol utions to Th reads and Locks
1 5.1
Thread vs. Process: What's the difference between a th read and a process? pg 1 7 9
SOLUTION touchedNodes ) { VisitState [ ] visited = new Vis itStat e [ maxLocks ] ; for ( i nt i = 0; i < maxLocks ; i++ ) { visited [ i ] = VisitState . F RESH; } return ha sCycle ( vi s ited , touc hedNodes ) ; }
96
98
100
103 104 10S 106 107 10!! 109 110 111 112 113
114
115
116 117 118 119 121 121 122 123 124
125
126
127
private boolean ha sCycle (Vi s itState [ ] visited, H a s hMa p < I nteger , Boolea n> touc hedNodes ) { if ( touchedNodes . cont a i n s Key ( lockid ) ) { touchedNodes . put ( lockid, t r ue ) ; } if ( vi s ited [ lockid ] = = Vis itState . VISITING ) { / * We looped back to this node while still visiting it, so we know there ' s * a cycle . * / ret u r n t rue ; } else if ( vi s ited [ lockid ] == VisitState . F RESH) { vis ited [ lockid ] = VisitState . VISITING ; for ( Loc kNode n : children) { if ( n . h a s cycle ( vi s ited , touc hedNodes ) ) { ret u r n t r u e ; } } vis ited [ lockld ] VisitState . VISITED;
CrackingTheCodinglnterview.com I 6th Editio n
4SS
Solutions to Chapter 1 5 1 28
I Threads and Locks
}
ret urn fal se;
129
130
}
131 132
public Lock getloc k ( ) { if ( lo c k = = null ) lock ret u rn l o c k ; }
133 1 34 135
136 137
1 38 }
=
new Reent rantLock ( ) ;
public int ge t l d ( ) { return lock l d ; }
As always, when you see code this complicated and lengthy, you wou ldn't be expected to write all of it. More likely, you would be asked to sketch out pseudocode and possibly implement one of these methods. Call In Order: Su ppose we have the fol l owing code:
1 5.5
public class Foo { public Foo ( ) { . . . } public void first ( ) { } public vo i d s e con d ( ) { . . . } public vo i d t h i r d ( ) { } } .
.
.
.
.
.
The same instance of Foo wi l l be passed to three different threads. Th readA will cal l first t h re a d B wil l cal l second, and t h read( wil l cal l third. Design a mechanism to ensure that fi rst is called before s e c ond and second is ca l led before t h i rd. pg 180
SOLUTION · ···· · · ··· ··· ······· ·· ··· ·· · ·· · ············· · · ·· ·· ·· ·· ···· ········ ·· · · · · ·· ··· ·· · ·· ····· ············-
· ·· ·· ···· ··-· ·-·-··- · ·- ··· · · ·· · ·-··- ·· ···· -··- ··- ·· -·- ·- ·- ··-·-· ·-··- ··-·· -··- ··- · ·· ·· ·· - ·· ··- ·· -·
·· ··· ·· ·· ·· ··· ··· ·· ··-·· -· ·-·-
----
The general logic is to check if fi rst ( ) has completed before executing s ec ond ( ) , and if second ( ) has completed before cal l i ng t h i r d ( ) Because we need to be very carefu l about thread safety, simple boolean flags won't do the job. .
What about using a loc k to do something like the below code? public cla s s FooBad { public int pau seTime 1000 ; public Reent rant Lock lockl, lock2;
1
2
=
3
4 s
public FooBad ( ) { t ry { lock1 = new Reent rant Lo ck ( ) ; lock2 new Reentrant lock ( ) ;
6 7
8
=
9
10 11 12 13
14 15
16
17
lockl . lock ( ) ; lock2 . lo ck ( ) ; } cat c h ( . . . ) {
20
456
}
public void first ( ) { t ry { lock1 . unlock ( ) ; / / mark finished w i t h first ( ) } catch ( ) { }
19
19
. • .
}
_ .
.
. _
_
}
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 1 5 21
22
I Threads and Locks
public void s econd ( ) { t ry { lockl . lo c k ( ) ; / / wait unt i l finis hed with firs t ( ) lockl . unlock ( ) ;
23
24 25
26
27 28
lock2 . unlock ( ) ; / / ma r k finis hed wit h s econd ( ) } catch ( . . . ) { . . . }
29 30
}
31
32 33
public void t h i r d ( ) { t ry { lock2 . lock( ) ; // wa it until finis hed with t h i rd ( ) lock2 . unlock ( ) ;
34
35
36
37
38 39
} catch ( . . . ) { . . . } }
}
This code won't a ctually q uite work d ue to the concept of lock ownership. One thread is a ctually performing the lock (in the FooBad constructor), but d ifferent th read s attempt to unlock the locks. This is not allowed, and you r code will raise an exception. A lock in Java is owned by the same th read which locked it. I n stead, we can replicate this beh avior with sema phores. The logic is identical. 1 2
3
4 5
6 7
8
publ ic cla s s Foo { publ ic Semaphore seml, s em2 ; public Foo ( ) { t ry { s eml new Semaphore ( l ) ; s em2 new Semaphore ( ! ) ;
9
seml . ac q u i re ( ) ; s em2 . a cquire ( ) ; } catch ( . . . ) { . . . }
10
11 12 13
14 15
16
} public void fir s t ( ) { try {
17 1� 19
20
21 22
23 24 25
s eml . relea s e ( ) ; ) { ... } } catch ( .
28
29
30
31 32
.
public void s econd ( ) { t ry { seml . a cquire ( ) ; s eml . release ( ) ;
26
27
•
}
s em2 . release ( ) ; } catch ( . . . ) { . . . } }
public void third ( ) { t ry { sem2 . a cquire ( ) ;
CrackingTheCodinglnterview.com I 6th Edition
457
Solutions to Chapter 1 5 33 34
35 36
37
I Threads and Locks
sem2 . release ( ) ;
}
t S.6
}
} cat ch (
.
•
.
) {
.
.. }
Synchronized Methods: You are given a class with syn c h ro n i z e d method A
method B. If you have two threads in one insta nce of a program, can they both same time? Can they execute A and B at the same time?
and a normal execute A at the pg
1 80
SOLUTION
By applying the word sync h r on i z ed to a method, we ensure that two threads cannot execute synchro nized methods on the same object instance at the same time. So, the a nswer to the fi rst part rea lly depends. If the two threads have the same i nstance of the object, then no, they cannot simu lta neously execute method A. However, if they have different i nstances of the object, then they can. Conceptually, you can see this by considering locks. A synchronized method applies a "lock" on all synchro nized methods in that i nsta nce of the object. This blocks other threads from executing synchronized methods withi n that i nstance. In the second pa rt, we're asked if th r ead 1 can execute synchronized method A while th read2 is executi ng non-synchronized method B. S ince B is not synchronized, there is nothing to block threadl from executing A while thread2 is executing B. This is true regard less of whether threadl and thread2 have the same insta nce of the object. Ultimately, the key concept to remember is that only one synchronized method can be in execution per instance of that object. Other threads ca n execute non-synchronized methods on that i nstance, or they can execute any method on a d ifferent instance of the object. 1 5.7
l to n. However, when the number is d ivisible by 3, print "Fizz'� When it is d ivisible by 5, print "Buzz''. When it is d ivisible by 3 and 5, print"FizzBuzz''. I n this problem, you a re asked to do this in a multithreaded way. Im plement a multithreaded version of FizzBuzz with four threads. One thread checks for d ivisi bility of 3 and pri nts"Fizz''. Another thread is responsible for d ivisibility of 5 and prints"Buzz''. A third thread is responsible for d ivisibility of 3 and 5 and prints "FizzBuzz''. A fourth thread does the numbers.
FizzBuzz: In the classic problem FizzBuzz, you a re told to print the num bers from
pg
780
SOLUTION
Let's start off with im plementing a single threaded version of FizzBuzz. Single Threaded
Although this problem (in the single threaded version) shouldn't be hard. a lot of candidates overcom pli cate it. They look for something "beautiful" that reuses the fact that the divisible by 3 and 5 case ("FizzBuzz"} seems to re sem bl e the individual cases ("Fizz" a n d "Buzz") . I n actuality, the best way to do it consideri ng readability and efficiency, is just the stra ig htforward way. 1
void fizzbu z z ( int n) {
458
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 1 5
==
4 5
6 7
==
8
9 10 11
13
Threads and Locks
for ( i nt i = 1 ; i < = n; i++) { if ( i % 3 0 && i % 5 == 0 ) { System . out . println ( "FizzBuzz" ) ; } else if ( i % 3 == 0) { System . out . print l n ( "F i z z") ; 0) { } else if ( i % 5 System . out . print l n ( "Buzz") ; } else { Syst e m . o ut . p r i n t l n ( i ) ; } }
2 3
12
I
}
The primary thing to be ca refu l of here is the order of the statements. If you put the check for divisibility by 3 before the check for divisibility by 3 and 5, it won't print the right thing. Multithreaded To
do this mu ltithreaded, we want a structu re that looks something like this: Th read
F i z zB u z z Th read
if i d i v by 3 && 5 print F i z z B u z z i n c rement i repeat u n t i l i > n
if i div by only 3 print F i z z i n c rement i repeat u n t i l i > n
B u z i Th read
N(Jmb e r Thread
if i d iv b y on l y 5
if
print B u z z i n c rement i r e peat u n t i l i > n
i not d i v b y 3 or 5 print i i n c rement i repeat until i > n
The code for this wi ll look something like: 1
2 3
4 5
6 7 8
9
while ( t rue) { if ( c u rrent > max ) { ret u r n ; } if ( / * divisibility test * / ) { System . out . println ( / * print s om e thi n g * / ) ; c u r r en t ++ ; } }
We'l l need to add some synchronization in the loop. Otherwise, the value of c u r rent cou ld change between li nes 2 - 4 and li nes 5 - 8, and we can inadvertently exceed the intended bou nds of the loop. Addi tionally, incrementing is not thread-safe. To actually im plement this concept, there are many possi bil ities. One possi bility is to have four enti rely separate thread classes that share a reference to the current variable (which can be wra p ped in an object). The loop for each thread is su bsta ntially similar. They just have different ta rget values for the divisi bility checks, and different print values.
CrackingTheCod inglnterview.corn I 6th Edition
459
Solutions to Chapter 1 S
current % 3
current % 5
I Threads and Locks
==
0
true
true
false
false
==
0
true
false
true
false
FizzBuzz
Fizz
Bu z z
current
to print
For the most part this can be handled by taking in "ta rget" parameters and the value to print. The output for the Number thread needs to be overwritten, thoug h, as it's not a simple, fixed string. We can implement a F i z z Bu z zTh read class whi c h handles most of this. A Number Thread class can extend F i z z Bu z zThread and override the p r int method. 1
=
{ new F i z z B u z zThread ( t rue, true, n , " F i z z B u z z " ) , new FizzBuzzThread ( t rue, f a l s e , n , " F i zz " ) , new F i zzBu z zThrea d ( fa l s e , true, n , " Bu z z " ) , new NumberThre a d ( f a l s e , fa lse, n ) } ; for (Thread t h read : t h read s ) { thread . st a rt ( ) ; }
Thread [ ] t h reads
2
3
4 5
5
7
8 9
public c l a s s F i z zB u z zThread extends Thread { private static Object lock = new Object ( ) ; 11 protected static int c u r rent = 1 ; 12 private int max; 13 private boolean div3, d i v s ; 14 private String toPrint ; 15 16 public F i z z B u z zThread ( boolean div3, boolean d i v s , i n t m a x , St ring toPrint ) { 17 this . div3 = div3; 1g this . d ivs = divS; 19 t h i s . max = max; 20 this . toPrint = toPrint ; 21 } 10
22
23
public void print ( ) { System . out . print l n ( toPrint ) ; }
24 25 26
27
public void ru n ( ) { while (true) { synchroni zed ( lock) { if ( c u r rent > max) { r e tu r n ;
28 29
30 31 32
}
33
34
if ( ( c u r rent % 3 == 0 ) 0) ( cu rrent % 5 print ( ) ; current++; }
35
36
37
38
39 40
}
41
div3 && divs ) {
}
}
42
}
44
public c l a s s NumberThread extends F i z z B u z zThread {
43
460
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 1 5 45
public NumberThread ( boolean div3 , boolean div5 , int max) { supe r ( div3, divS, max, null ) ; }
46
47
4i 49 S0
51 52
I Threads and Locks
}
public void pri n t ( ) { System . out . print l n ( current ) ; }
Observe that we need to put the comparison of c u r rent a n d max before the if statement, to ensure the value will only get pri nted when c u rrent is less than or equal to max. Alternatively, if we're worki ng i n a language whi c h supports this (Java 8 and many other languages do), we can pass i n a v a l i d a t e method an d a p r i n t method as parameters_ 1 2 3
4 5
6
7
8 9 10
=
int n 100 ; Thread [ ] t hread s { new F BThread ( i - > new F BThread ( i - > new F BThread ( i - > new FBThread ( i - > for ( Thread t hread : t h read . st a rt ( ) ; } =
i % 3 == i % 3 i % 3 != i % 3 != t h reads } ==
0 && 0 && 0 && 0 && {
i i i i
% % % %
=;;:;o
5 5 ! 5 5 ! =
=
==
0, 0, 0, 0,
i i i i
-> -> -> ->
" FizzBuzz " , n ) , "Fizz", n } , " B u zz " , n ) , Intege r . tost ring ( i ) , n ) } ;
public c l a s s F BThread extends Thread { private stat i c Object lock = new Object ( } ; protected static int current 1; 13 14 private int max ; 15 private Predi cate< Intege r > validat e ; 16 private F u n ct ion printer ; 17 int x 1; 11
12
=
=
18 19
public FBThread ( Predicate validate, F unction < I ntege r , String> print e r , int max) { t h i s . validate validate; this . printer = print e r ; t h i s . max = max; }
28 21
=
22
23
24 2 max) { return; } if ( va lidate . test ( current } ) { System . out . print l n ( printer . apply ( current ) ) ; c u rrent++;
27 28
29
30
31
32 33
34 35
}
36 37
38
39
}
}
}
}
There a re of course m a ny oth e r ways of i m pleme nti ng this as
well.
CrackingTheCodingJnterview.com I 6th E d iti on
461
16 Sol utions to Moderate
Number Swapper: Write a fu nction to swa p a number i n place (that is, without tempora ry va riables).
1 6.1
pg 1 8 1
SOLUTION
This is a classic interview problem, and it's a reasonably straightforward one. We'l l wa l k through this using a to indicate the original value of a and b to indicate the original value of b. We'll also use di ff to indicate e e the value of a e - be . Let's pictu re these on a number line for the case where a > b.
d i ff
0
First, we briefly set a to d i ff, which is the right side of the above number li ne. Then, when we add b and d i ff (and store that value in b), we get a 0• We now have b = a 0 and a = d if f . All that's left to do is to set a equ a l to a e di ff, which is just b a. -
-
The code below implements this. 1 / / Example for a = 9 , b = 4 2 a a - b; // a 9 - 4 = 5 3 b = a + b; / / b = 5 + 4 = 9 4 a = b - a ; // a = 9 - 5
We can implement a similar solution with bit manipulation. The benefit of this solution is that it works for more data types than just integers. 1 / / Example for a = 101 (in binary ) and b = 1 1 0 2 a aAb; // a 101 A 1 1 0 011 3 b : a A b ; / / b = 011A110 : 101 4 a = aA b ; / / a = 011A101 = 1 1 0
This code works by using XORs. The easiest way t o see how this works i s by focusing on a specific bit. I f we can correctly swa p two bits, then we know the entire operation works correctly. Let's ta ke two bits, x and 1. 2.
X
=
X
A
y
y,
a n d wa l k through this line by li ne.
This line essentia lly checks if x and y have different va lues. It will resu lt in Y
462
=
X
A
Y
Cracking the Coding Interview, 6th Edition
1
if and only if x !
=
y.
Sol utions to Chapter 1 6
Or: y
I Moderate
{0 if origin a l ly same, 1 if d iffe re n t } " { origi n a l y }
=
Observe that XORing a bit with 1 a lways fl ips t h e bit, whereas XO Ring with 0 w i l l never change it. Therefore, if we do y x's original va lue. Otherwise, if x
==
=
1 " { o r igin a l y } when x !
y , then we do y
=
=
y, then y will be flipped and therefore have
0 " { o r i g i n a l y } a nd the va lue of y does not change.
Either way, y will be equal to the orig inal value of x. 3.
X
X
=
A
Or: x
Y
{ 0 if origin a l ly same, 1 i f d iffe rent} " { origina l x }
At this poi nt, y i s equal to the orig inal va lue of x . Th is line i s essentially equ iva lent to the line a bove it, but for diffe rent va riables. lf we do x
1 " { o r i g i n a l x } when the values a re d ifferent, x will be fli pped.
If we do x
0 " { origina 1 x } when the values are the same, x will not be changed.
Th is operation happens for each bit. Since it correctly swa ps each bit, it will correctly swap the entire num ber. Word Frequencies: Design a method to find the frequency of occurrences of any given word in a
1 6.2
book. What if we were running this a lgorithm multiple times? pg 1 8 1
SOLUTION
Let's start with the simple case. Solution: Single Query
In this case, we simply go through the book, word by word, and count the number of times that a word appea rs. This will ta ke 0 ( n ) time. We know we ca n't do better than that since we must look at every word in the book. 1 int g et F r e q ue n cy ( St r i ng [ ] book, St ring word ) { 2
word = word . t rim ( ) . tolowerCase ( ) ; i nt c o u n t = 0;
3
4
for ( St r ing w : boo k ) { if ( w . t r im ( ) . toLowerCa s e ( ) . eq u a l s ( wo rd ) ) {
5
6 7
count++;
8 9
rn
} }
}
r et u r n
count ;
We have a lso converted the stri ng to lowercase and trimmed it. You can d iscuss with your interviewer if this is necessary (or even desired). Solution: Repetitive Queries If w e' re doing the operation repeated ly, then we can probably afford to ta ke some time and extra memory to do pre- processing on the book. We can create a hash table which maps from a word to its freq uency. The freq uency of a ny word ca n be easily looked up in 0 ( 1 ) time. The code for this is below. 1 HashMap s et u pD i c t io n a ry ( St r i ng [ ] boo k ) { 2 Ha s h M ap t a b l e =
Cracki ngTheCodinglntervi ew.com I 6th Editi o n
463
Solutions to Chapter 1 6 3
I Moderate
new HashMap ( ) ; for ( St r i n g wo rd : book) { word = wo rd . to LowerCa se ( ) ; if ( word . t r im ( ) ! = ) { if ( ! ta b l e . conta i n s Key (word ) ) { table . put (word, 0 ) ;
4 5
6
""
7
8 9 } 10 table . p ut ( word, table . get ( wo rd ) + 1 ) ; 11 } 12 } 13 ret u r n t a b l e ; 14 } 15 16 int get F requency ( Ha s hMap table, String word ) { 17 if (table == null I I word = = nu l l ) ret urn - 1 ; 18 word = word . to LowerCa s e ( ) ; if ( t able . conta i n s Key ( word ) ) { 19 20 return table . get ( word ) ; 21 } 22 ret u r n 0 ; 23
}
Note that a problem like this is actua lly relatively easy. Thus, the interviewer is going to be looking heavily at how careful you are. Did you check for error conditions? Intersection: Given two straight line segments (represented as a start point and an end point), compute the point of intersection, if a ny.
1 6.3
pg 1 8 1
SOLUTION
We fi rst need to think a bout what it means for two line segments to i ntersect. For two infinite lines to intersect, they only have to have different slopes. If they have the same slope, then they must be the exact same line (same y-intercept). That is: s lope 1 ! = s lope 2 OR s lope 1 slope 2 AND intersect
1
==
intersect 2
For two straight lines to i ntersect, the condition above must be true, plus the point of intersection must be within the ranges of each line segment. extended infi n ite s egments intersect AND intersection i s wit hin line segment 1 (x and y coordinates ) AND intersection is within line segment 2 (x a n d y coordinates )
What ifthe two segments represent the same infinite line? I n this case, we have to ensure that some portion of their segments overlap. If we order the line segments by their x locations (start is before end, point 1 is before point 2), then an intersection occurs only if: As s ume : startl . x < start2 . x && sta rt l . x < endl . x && start2 . x < end2 . x Then intersection occurs if : start2 is between startl a nd en dl
We ca n now go ahead a nd implement this a lgorithm.
464
Cracking the Cod i n g Interview, 6th
Edition
Solutions to Chapter 1 6 1
2
3
4 5
6 7
Point /* * if if if
8
9
I
Moderate
intersection ( Point startl , Point endl , Point start2, Point e nd 2 ) { Rearranging these so that , in order of x values : s t a rt is before end and point 1 is before point 2 . This will make some of the later logic s impler . * I ( s tartl . x > endl . x ) swa p ( startl, endl ) ; ( st a rt 2 . x > end2 . x ) swa p ( start2, end2 ) ; ( st a rt l . x > s t a rt 2 . x ) { swa p ( start l , start 2 ) ; swa p ( endl, end2 ) ;
}
10 11
/ * Compute lines ( including s lope a nd y - intercept ) . */ Line linel new L i n e ( start l , endl ) ; Line l i ne2 = new L i n e ( start2, end2 ) ;
12
13
14 15
/* If the lines are parallel, t hey intercept only if t hey have the s ame y * i ntercept a nd start 2 is on line 1 . */ if ( linel . s lope == l i ne2 . s lope ) { i f ( linel . yintercept == l i ne2 . y i ntercept && is Betwee n ( start l , start2, e nd l ) ) { ret u r n start2;
16 17
18 19
20
21
}
22
return null;
23 24 25 26 27
} /* Get intersection coord inate . */ double x = ( line2 . yintercept - linel . yintercept ) I ( linel . s lope - l i ne2 . s lope ) ; double y = x * l i nel . s lope + line l . y i ntercept ; Point intersection = new Point ( x , y ) ;
28 29
/* Check if wit h i n line segment range . */ if ( is Between ( st a rt l , intersection , end l ) && is Between ( start2, intersection, end2 ) ) { return intersect ion ;
30
31
32 33
34
}
ret urn n u l l ; 35 36 } 37 38 / * C h e c k s if middle i s between start a nd e n d . * / 3 9 boolean is Betwee n ( double start , double middle, double e nd ) { 40 if ( start > end ) { 41 ret urn end < = middle && middle < = start ; 42 } else { 43 ret u r n start < = middle && middle 1 , 2 3
* 25 - > 2 , etc . * / f ac t o rs 0 f5 ( i n t i ) { int co unt = 0; while ( i % 5 == 0) { count++; i I= 5 ; } return count ;
int
4 5 6 7 8
9
10
11
12 13
14 15
16
17
18
}
i n t count FactZeros ( int nu m ) { i n t co un t = 0; for ( i n t i = 2; i < = num; i++ ) { co un t + = factorsOfS ( i ) ; } return count ; }
This isn't bad, but we can make it a little more efficient by directly counting the factors of 5. Using this approach, we wou ld first cou nt the number of m u ltiples of 5 between 1 and n (which is '.Vs ). then the n u m ber of m u ltiples of 25 ( Yz's ), then 1 25, and so on. To count how many multiples ofm a re in n, we can just d ivide n by m. 1 int count FactZeros ( int nu m ) { 2 1nt count = 0; 3
4 5
if ( num < 0) { ret u rn - 1 ; }
CrackingTheCodinglnterview.com I 6th Edition
473
Solutions to Chapter 1 6
Moderate
6
>
=
for ( int i 5; num I i count += num I i ; } return count ;
7
8
9
10
0; i * = 5 ) {
}
This problem is a bit of a brainteaser, but it can be approached logically (as shown above). By thinking through what exactly will contri bute a zero, you can come up with a solution. You should be very clear in your rules upfront so that you can implement it correctly. Smallest Difference: Given two a rrays of integers, com pute the pair of values (one value in each
1 6.6
array) with the smallest (non-negative) difference. Return the difference. EXAMPLE Input: {1 , 3, 1 5, 1 1 , 2}, {23, 1 27, 235, 1 9, 8} Output: 3. That is, the pair (1 1 , 8). pg 1 8 1
SOLUTION
Let's sta rt fi rst with a brute force solution. Brute Force
The simple brute force way i s t o just iterate through a l l pairs, compute the difference, and compare i t t o the cu rrent minimum difference. 1 int findSma llestDifference ( int [ ] arrayl, i nt [ ] a rr ay 2 ) { 2 if ( arrayl . lengt h = = 0 I I array2 . length 0 ) return - 1 ; ==
3 4
9
int min = I ntege r . MAX_VALUE ; for ( int i 0 ; i < a rray1 . lengt h ; i++) { for ( int j 0; j < array2 . lengt h ; j++) { if ( Mat h . abs ( a rrayl [ i ] - array2 [ j ] ) < min ) { min = Mat h . abs ( arrayl [ i ] - array2 [ j ] ) ; }
11
}
5
=
6 7
=
8
10
12
13
}
return min;
}
One minor optim ization we could perform from here is to retu rn immediately if we find a difference of zero, since this is the smallest difference possi ble. However, depending on the i n put, this might actually be slower. This will only be faster if there's a pa i r with difference zero early in the list of pairs. But to add this optimiza tion, we need to execute an additional line of code each time. There's a tradeoff here; it's faster for some inputs and slower for others. Given that it adds complexity in reading the code, it may be best to leave it out. With or without this "optimization;' the algorithm will take O ( AB ) time. Optimal
A more optimal approach is to sort the arrays. Once the arrays are sorted, we can find the minimum differ ence by iterating throug h the array.
474
Cracking the Cod i n g
I n te rv i ew 6th E d i ti on .
Solutions to Chapter 1 6
I Moderate
Con sider the fol lowi ng two a rrays: A : { 1 , 2, 1 1 , 1 5 } B : { 4 , 1 2 , 1 9 , 2 3 , 127 , 2 3 5 } Try t h e followi ng a p p roach:
1.
S u p pose a pointer a poi nts to t h e beg i n n i n g of A and a poi nter b poi nts to t h e begi n ning of
cu rre nt differe nce betwe en a and b is
2.
B. The
3. Store this as the m i n .
How c a n w e (potentially) m a ke t h i s difference smaller? Well, t h e va lue at b i s bigger t h a n t h e va l u e at
a,
s o moving b wi l l o n l y make t h e differe nce l a rg e r. Therefore, w e want t o move a .
3.
Now a poi nts t o
2
and b (st i l n poi nts t o 4 . T h i s d iffe rence is
2, s o w e shou ld
u pdate m i n . Move a, since
it is sma l l e r.
4. Now a poi nts t o 1 1 a n d b poi nts t o 4. Move b .
5.
Now a poi nts t o
11
a n d b poi nts to
1 2. Update m i n t o 1.
Move b .
And so on. 1
int findSma l l e s t Diffe rence ( int ( ] a r rayl , i nt ( ] a r ray 2 ) {
2
Arrays . s o rt ( a r rayl ) ;
3
Arrays . s o rt ( a r ray2 ) ;
4
int a =
5 6 7 8
0;
int b = 0 ; int d iffe rence while ( a
> 3 1 ) & 0xl ) ; }
1 1 int getMaxNaive ( int a , int b ) { int k s ign ( a - b ) ; 12 int q fli p ( k ) ; 13 14 ret u r n a * k + b * q ; 15 } =
=
-
This code almost works. It fai ls, u nfortunately, when a b overflows. Suppose, for example, that a is INT_MAX 2 and b is 1 5 I n this case, a b will be greater than I N T_MAX and wi ll overflow, resulti ng in a negative value. -
-
-
.
We can implement a solution to this problem by using the sam e a pproach. Our goal is to maintain the condition where k is 1 when a > b. We wi ll need to use more complex logic to accom plis h this. -
When d oes a b overflow? It will overflow o n ly when a is positive a n d b is negative, or the other way a ro un d. It may b e difficu lt to specia l ly detect the overflow condition, but we can detect when a a n d b have different signs. Note that if a and b have different signs, then we want k to equ a l s i g n ( a ) . The logic looks like: 1
if a and b have d ifferent s igns : 1. I I if a > 0, then b < 0, and k II if a < 0, t hen b > 0, and k 0. s i gn ( a ) I I so either way , k let k s ign ( a ) else let k s ign ( a - b ) I I ove rflow is impos si ble
2
3
4
=
5
6
7
The code below implements this, using m u lti plicatio n i nstead of if-statements. int getMax ( int a , int b ) { 1 int c = a - b ; 2 3
4
int sa int sb int S C
5
6
7 8
I* Goa l : define a value k which i s 1 if a > b and 0 if a < b . * ( if a = b , it does n ' t matter what value k is ) * I
9
10
11
I I If a a n d b have d iffe rent s igns , t hen k
12
int us e_s ign_of_a
13
14 17
s ign ( a )
sa A sb;
=
16
s ign ( a - b )
int k use_s ign_of_a * sa + use_s ign_of_c * s c ; int q = flip ( k ) ; I I oppos ite o f k =
18 19
21
=
II I f a a n d b have t he s ame s ign, t hen k i n t use_sign_of_c flip(sa A s b ) ;
15
20
s i gn ( a ) ; II if a >= 0, t hen 1 else 0 sign ( b ) ; II if b > = 0, then 1 e l s e 0 s i gn ( c ) ; I I depends on whether or not a - b overflows
return a * k }
4 76
+
b * q;
Cracking the Coding I nterview, 6th Edition
Solutions to Chapter 1 6
I
Moderate
Note that for cla rity, we split u p the code into many different methods and variables. This is certainly not the most compact or efficient way to write it, but it does make what we're doing much cleaner. English I nt: Given any integer, print a n English phrase that describes the integer (e.g .. "One Thousand, Two Hundred Thirty Fou r").
1 6.8
pg 1 82
SOLUTION
This is not an especially challenging problem, but it is a somewhat ted ious one. The key is to be orga nized i n how you approach the problem-and to make sure you have good test cases. We can think about converting a number like 1 9,323,984 as converting each of three 3-d igit segments of the number, and inserti ng "thousa nds" and "millions" in between as appropriate. That is, c on ve rt ( 1 9 , 3 2 3 , 9 84 ) - c on ve rt ( 1 9 ) + " million " + c on ve rt ( 3 2 3 ) + " thousand " + c on ve rt ( 9 84 ) The code below implements this algorithm. 1 St ring [ ] smalls = {"Zero", "On e", "Two" , " Th ree ", "Four" , "F ive" , "Six" , "S eve n", "Eight", "Nine", "Ten", "E leven", "Twelve" , "T h i rt een ", " F ou rt een ", "F ifteen", 2 3 "Sixteen", "Seventeen", " E ig h t ee n", "Nineteen" } ; 4 St ring [ ] tens = {"", '"' , "Twe nt y ", " Th i rt y ", "F o rt y", "F ifty" , "Sixty" , "Seventy", 5 6 7
8 9
"E ighty" , "Ninety" } ; String [ ] bigs = { "'', "Thou sa nd", "Mi l l ion", "Billion" } ; St ring hundred = "Hund red" ; String negat ive = "Negat ive" ;
10
St ring convert ( i nt num ) { if ( num = = 0 ) { return smalls [ 0 ] ; 12 13 } e l s e i f ( num < 0 ) { 14 return negative + " " 15 } 11
16 17
L inked list parts int chunkCount = 0;
18 19
20
22
}
28
32
35
36 37
" " + bigs [ c hunkCount ] ;
n u m / = 1000; / / s h i ft c h un k c h un k Count + + ;
27
33 34
+
}
25 26
31
new L i n ked L ist ( ) ;
=
23 24
29
convert ( - 1 * num ) ;
while ( num > 0) { if ( num % 1000 ! = 0 ) { String c h unk c on ve r tC h u n k ( num % 1000 ) parts . addFirst ( c hunk) ;
21
30
+
return listToSt ring ( parts ) ; }
St ring convertChu n k ( int numbe r) { Li nked list parts new L inked list ( ) ; =
/ * Con ve r t hundreds p lace * / if ( n umber > = 100 ) { parts . addLast ( smalls [ number I 100] ) ;
CrackingTheCodinglnterview.com I 6th E ditio n
477
Solutions to Chapter 1 6
I Moderate
parts . add Last ( hundred ) ; number %= 100;
38 39 40
}
41
42
/ * Convert tens place * / if ( number >= 10 && number = 20) { p a rts . ad d l a st ( tens ( number I 10] ) ; number %= 10; }
50
/ * Convert ones place */
45
46
47 49
51
if ( number > = 1 && num ber < = 9) { pa rts . addlast ( smalls ( number ] ) ; }
52
53
54
55 59 57
re turn listToSt ring ( parts ) ; }
/* Convert a linked l i s t of s t r ings to a s t ring, d iv id ing it up wit h s p a ces . */ St ring listToSt ring ( Linked listcSt ring> p a rts ) { 59 StringBuilder sb = new St ringBuilde r( ) ; 60 while ( p a rts . s ize ( ) > 1 ) { s b . append ( p a rts . pop ( ) ) ; 61 62 s b . append (" " ) ; 63 } s b . append ( pa rts . pop ( ) ) ; 64 re t u rn sb . toSt ring ( ) ; 65 66 } 58
The key i n a problem like this is to make sure you consider all the special cases. There are a lot of them. 1 6.9
Operations: Write methods to implement the m u ltiply, subtract. and divide operations for integers. The resu lts of all of these are integers. Use only the add operator. pg 1 82
SOLUTION
The only operation we have to work with is the add operator. In each of these problems, it's usefu l to think in depth a bout what these operations rea lly do or how to phrase them in terms of other operations (either add or operations we've a l ready completed). Subtraction
How can we phrase su btraction in terms of add ition? This one is pretty straig htforward . The operation a - b is the same thing as a + ( - 1 ) * b . However, because we are not allowed to use the * {mu ltiply) operator, we must i m plement a negate function. /* F l ip a pos it ive s ign to negative or nega t ive s ign to pos . */ 1 2 int negate ( int a ) { 3 int neg = 0 ; 4 int newSign = a < 0 ? 1 -1; 5 while ( a l = 0) { neg += newSign; 6 7 a += newSign; 8
}
478
Cracking the Coding Interview, 6th Edition
Solutions to Cha pter 1 6 9
10 11 12 13
14
15
I Moderate
ret urn neg;
} /* Subt ract two n umbers by negat ing b and adding t hem */ int minus ( i nt a , int b) { ret urn a + negat e ( b ) ;
}
The negation of the value k is implemented by adding -1 k times. Observe that this will ta ke 0 ( k ) time.
If optimizing is something we value here, we can try to get a to zero faster. (For this explanation, we'll assume that a is positive.) To do this, we can first red uce a by 1, then 2, then 4, then 8 , and so on. We'll call this va lue delta. We want a to reach exactly zero. When red ucing a by the next delta wou ld change the sign of a, we reset delta back to 1 and repeat the process. For exam ple: a: 29 delta :
28
-1
26 -2
-4
22 -8
14
-1
13
-2
11
-4
7
6
-1
-2
4
-4
0
The cod e below implements this algorithm. 1 2 3
4 5
6 7 8
9
10 11
12
13 14 15
i nt negat e ( int a ) { int neg = 0; i nt newSign = a < 0 ? 1 -1; i nt delta = newSign ; while ( a ! = 0) { boolean d iffe rentSigns = ( a + delta > 0) ! = ( a > 0 ) ; if ( a + delta ! = 0 && d ifferentSigns ) { / / If delta is t oo big, reset it . delta = newSign; } neg += delt a ; a + = delt a ; delta += delt a ; // Double the delta } return neg; }
Figuring out the runtime here ta kes a bit of calcu lation.
Observe that red ucing a by half ta kes 0 ( log a ) work. Why? For each round of "red uce a by ha lf; the a bso lute values of a and delta always add up to the same num ber. The values of d e l t a and a will converge at Yi . Since d e lta is being doubled each time, it wi l l take O ( log a ) steps to reach half of a. We d o O(log a) rounds. 1 . Red ucing a to Yi takes O ( log a ) time.
2.
Red ucing Yi to X ta kes O ( log Yi ) time.
3. Red ucing X to Ys ta kes O ( log X ) time . ... As so on, for O ( log a ) rounds. The runtime therefore is O ( log a + log( Yi ) + log ( ;/,; ) + . . . ) , with O ( log a ) terms in the expression. Reca ll two rules of logs: •
l og ( xy )
•
log (
;< )
=
=
log x + lo g y
log x - l o g y.
CrackingTheCodinglnterview.com j 6th Edition
479
Sol utions to Cha pter 1 6
I Moderate
If we apply this to the a bove expression, we get: 1.
O ( log a + log ( � ) + log ( % ) +
.
.
•
)
2. O ( log a + ( log a - log 2 ) + ( log a - log 4 ) + ( log a - log 8 ) + +
3. O ( ( lo g a ) * ( log a ) - ( log 2 + log 4
4. O ( ( log a ) * ( log a ) - ( 1 + 2 + 3 + 5. 0 ( ( l og a ) * ( log a ) 6. 0 ( ( log a ) 2 )
1 1 d rop
-
(log a)(l + leg a) / h
)
11
•
.
•
log 8
+
•
.
•
•
•
•
+ log a ) ) ll O ( log a ) terms
+ log a ) ) ll computing the values of logs ·
apply equation for sum of 1 through k
second term from step s
Therefore, the ru ntime is O ( ( log a ) 2 ) . This math is considerably more complicated than most people would be able to do (or expected to do) i n an i nterview. You cou ld make a simplification: You do O ( log a ) rounds and the longest round ta kes 0( log a ) work. Therefore, as an u pper bound, negate takes 0( ( log a ) 2 ) time. I n this case, the upper bou nd happens to be the true time. There are some faster solutions too. For example, rather than resetting d e lta to 1 at each round, we could change d e lta to its previous value.This would have the effect of d e lt a "counting u p" by mu ltiples of two, and then "counting down" by multiples of two. The ru ntime of this approach would be 0 ( log a ) . However, this implementation would require a stack, division, or bit shifting-any of which might violate the spirit of the p roblem. You could certainly discuss those im plementations with your interviewer though. Multiplication
The connection between addition and multiplication is equally straightforward. To multiply a by b, we just add a to itself b times. 1 /* Multiply a by b by adding a to itself b t imes */ 2 int multiply ( int a , int b ) { if ( a < b ) { 3 ret urn mult i p l y ( b , a ) ; I I algorithm i s faster if b < a 4 5
}
6
7
8
9 10
minu s ( i , 1 ) ) {
if (b < 0) { s um = negate ( sum ) ;
12
}
return sum;
13
15
=
}
11
14
=
int s um 0 ; for ( int i = a bs ( b ) ; i > 0; i s um += a ;
}
16
I * Return absolute va lue * I int abs ( int a ) { if ( a < 0) { 18 19 ret urn negate ( a ) ; 28 } else { 21 return a ; 17
22
23
}
}
The one thing we need to be ca refu l of i n the a bove code is to properly handle multiplication of negative num bers. lf b is negative, we need to flip the value of s um. So, what this code really does is: multiply ( a , b) < - - abs ( b ) * a * ( - 1 if b < 0) .
480
Cracking the Coding I nterview, 6th Edition
Solutions to Chapter 1 6
I Moderate
We also implemented a simple a b s function to help. Division Of the three operations, division is certainly the hardest. 1he good thing is that we can
use the multiply ,
.
s u bt r a c t and negate methods now to implement d ivide.
We are trying to com pute x where X = :;;;: . Or, to put this another way, find x where a bx. We've now cha nged the problem into one that can be stated with something we know how to do: m ultiplica tion. =
We cou ld implement this by mu ltiplying b by prog ressively higher values, until we reach a. That would be fa irly inefficient, particu la rly g iven that our implementation of multi ply involves a lot of adding. =
Alternatively, we can look at the equation a xb to see that we ca n com pute x by adding b to itself repeatedly u ntil we reach a. The number of times we need to do that will equal x. Of cou rse, a might not be evenly divisible by b, and that's okay. I nteger division, which is what we've been asked to imp lement, is supposed to tru ncate the resu lt. The code below implements this algorithm. 1 int d ivide ( int a , int b ) th rows j ava . lang . Arit hmet i c E xcept ion { 2
if ( b == 0 ) { throw new j ava . l a ng . Arit hmet i c E xception ( " E RROR" ) ; } a bs ( a ) ; int absa abs ( b ) ; int absb
3
4 5
6 7
B
=
int product 0; int x = 0; while ( product + a bsb < = absa ) { / * don ' t go past a * / product + = a bsb ;
9 10 11
12
x++ ;
13
}
14
1s 16
17 18 19
20 }
if ( ( a < 0 & & b < 0 ) I I ( a > 0 & & b > 0 ) ) { ret u r n x ; } else { return negate ( x ) ; }
I n tackling this problem, you should be awa re of the following: A logical approach of going back to what exactly multipl ication and division do comes i n handy. Remem ber that. All (good) interview problems ca n be approached in a logical, method ical way! The interviewer is looking for this sort of logical work-you r-way-throug h-it approach. Th is is a great problem to demonstrate you r ability to write clean code-specifically, to show you r ability to reuse code. For exam ple, if you were writing this solution and didn't put negate in its own method, you should move it into its own method once you see that you'll use it multiple times. Be ca refu l a bout making assumptions while cod ing. Don't assume that the num bers are a l l positive or that a is bigger than b.
Cracki ngTheCod inglnterview.com I 6th Edition
481
Solutions to Cha pter 1 6 1 6. 1 0
I Moderate
living People: Given a list of people with their birth and death yea rs, im plement a method to
com pute the yea r with the most number of people alive. You may assume that all people were born between 1 900 and 2000 (inclusive). If a person was alive d u ring any portion of that yea r, they should be included in that year's count. For example, Person (birth == 1 908, death =: 1 909) is included in the counts for both 1 908 and 1 909. pg 7 82
SOLUTION
_ ,, ............ . ............... ... ..... .. .. ... ........... ...... . . ... . -_ _
The fi rst thing we should do is outline what this solution will look l i ke. The interview question hasn't speci fied the exact form of i n put. In a real i nterview, we could ask the interviewer how the i n put is structu red . Alternatively, you can expl icitly state you r (reasonable) assumptions. Here, we'll need to make our own assum ptions. We wil l assume that we have an array of simple Person objects: 1
public class Person { public int birt h ; public int deat h ; public Person ( int birthYear , int deathYear ) { birt hYea r ; b i rth deat h = deathYea r ; } }
2
3
4 5
6 7
8
We could have a lso given Person a get B i rthYea r ( ) and getDeathYe a r ( ) objects. Some would a rgue that's better style, but for com pactness and clarity, we'll just keep the va ria bles public. The i mporta nt thing here is to actually use a Person object. This shows better style than, say, having a n integer array for birth yea rs a n d an i nteger array for death years (with a n implicit association of bi rths [ i ] and deat h s [ i ] being associated with the same person). You don't get a lot of cha nces to demonstrate great coding style, so it's valuable to take the ones you get. With that in m i nd, let 's sta rt with a brute force algorithm. Brute Force
The brute force a lgorithm fa lls directly out from the wording of the problem. We need to find the year with the most number of people alive. Therefore, we go through each yea r and check how many people are alive in that year. 1 int maxAliveYear ( Person [ ] people, int min , int max) { int maxAlive = 0 ; 2 3 int maxAliveYear = min; 4 5 6
for ( int yea r = min; yea r < = max ; year++ ) { int a l ive = 0 ; for ( Person person : people) { if ( person . birth < = yea r && yea r ma xAl ive ) { ma xAlive a l ive; ma xAliveYea r = yea r ; =
13
14 15
}
16
482
}
}
I
Cracking the Coding Inte rview, 6th Edition
Solutions to Cha pter 1 6
I Moderate
17 18
ret urn maxAliveYea r ; } N ote that we have passed in the values for the min year ( 1 900) and max year (2000). We shouldn't hard code these values. 19
The runtime of this is 0 ( RP ) , where R is the range of years (1 00 in t h i s case) and P is the number of people. Slightly Better Brute Force
A slig htly bette r way of doing this is to create an a rray where we track the n u m ber of people born in each year. Then, we iterate through the list of people and increment the array for each year they are alive. 1 2 3
4
5
6 7
i nt maxAliveYea r ( Person [ ] people, i nt min, int max ) { int [ ] yea r s createYea rMa p ( people, min, max ) ; i nt best = getMaxl ndex ( years ) ; return best + min ; } =
/ * Add each person ' s yea rs to a yea r ma p . */ int [ ] createYea rMa p ( Pe rson [ ] people, i nt min, int max ) { 9 int [ ] yea rs = new i nt [max - min + 1 ] ; 1e for ( Pe rson person : people ) { 11 incrementRange ( yea r s , person . bi rt h - min, person . death - mi n ) ; 12 } ret u r n yea r s ; 13
8
14
15
}
16 / * Inc rement a r ray for each value between left a n d right . * / 1 7 void inc rementRa nge ( i nt ( ] value s , int left , int right ) { 18 for ( i nt i = left ; i < = right ; i++ ) { values [ i ] ++; 19 2e
}
21 } 22 23 / * Get index of largest element i n a r ray . * / 2 4 int getMaxindex ( i nt [ ] val ues ) { i nt max = 0 ; 25 25 for ( int i = 1; i < values . lengt h ; i++ ) { 27 if ( va lues [ i ] > values [max ] ) { 28 max i; 29 } 30 } 31 return max; 32 } Be ca refu l on the size of the array in line 9. If the ra nge of years is 1 900 to 2000 inclusive, then that's 1 0 1 years, not 1 00. That i s why the array has size max - m i n + 1 . =
Let's t h i n k a bout t h e runtime b y brea king this i nto parts. We create an R-sized a rray, where R is the m i n and max years. Then, for P people, we iterate through the yea rs (Y) that the person is alive. •
Then, we iterate through the R-sized a rray again.
The tota l runtime is O ( PY + R ) . I n the worst case, Y is R and we have done no better than we d i d i n the fi rst algorithm.
CrackingTheCodinglntervlew.com I 6th Edition
483
Solutions to Chapter 1 6
I Moderate
More Optimal
Let's create an example. (In fact, an example is really helpful in almost a l l problems. Ideally, you've already done this.) Each column below is matched, so that the items correspond to the same person. For compact ness, we'l l just write the last two digits of the year. birt h : 12 deat h : 15
20 90
10 98
01 72
10 98
23 82
13 98
90 98
83 99
75 94
It's worth noting that it doesn't really matter whether these years are matched up. Every birth adds a person and every death removes a person. Since we don't actually need to match up the births and deaths, let's sort both. A sorted version of the yea rs might help us solve the p roblem. b i rt h : 01 deat h : 15
10 72
10 82
12 90
13 94
20 98
23 98
75 98
83 98
90 99
We can try walking through the yea rs. At yea r 0, no one is alive. At year 1 , we see one birth. At years 2 through 9, nothing happens. Let's skip ahead until yea r 1 0, when we have two births. We now h ave three people alive. At year 1 5, one person dies. We a re now down to two people alive. And so on. If we wa lk through the two arrays like this, we can track the number of people alive at each poi nt.
1
int maxAliveYear ( Person [ ] people, int min, int max) { i nt [ ] b i rths = getSortedYea rs ( people, t r ue ) ; i nt [ ] deat hs = getSortedYear s ( people, fa l se ) ;
2 3
4 5
int int int int int
6
7 8 9 10 11
=
,
=
=
/ * Wa l k t hrough a rrays . */ while ( bi rt h ! ndex < birth s . lengt h ) { if ( b irt hs [ birthindex] < = deaths [ deat h i ndex ] ) { current lyAl ive++ ; / / incl ude birt h i f ( c u r rent lyAlive > maxAlive ) { maxAlive = current lyAl ive ; maxAliveYear birt h s [ birthi ndex ] ; } birth!ndex++ ; / / move b i r t h index } else if ( b irt hs [ b i rt h i ndex ] > deaths [ deathindex ] ) { current lyAlive - - ; / / incl ude deat h deat h ! ndex++ ; / / move deat h index } }
12 13 14 15
16
17
=
18
19
20 21 22 23
24
25 26 27 28 29
birthi ndex 0; deathindex = 0; 0· current lyAlive maxAlive 0; maxAliveYear min ;
}
ret u r n maxAl iveYear;
/* Copy birth yea r s or death yea rs ( d e p e n d i n g on t he va l ue of copyB irthYea r into
484
Cracking the Coding Interview, 6th Edition
so•utions to 30 31
32
*
=
=
34
=
35
}
36
Arrays . sort ( years ) ; return year s ;
37
38
I Moderate
integer array, then sort a rray . */ getSortedYear s ( Person [ ] people , boolea n copyBirthYea r ) { i nt [ ] yea rs n ew int [ people . lengt h ) ; for (int i 0 ; i < people . lengt h ; i++ ) { yea rs [ i ] copyBirthYea r ? people [ i ] . birth people [ i ] . deat h ;
i nt [ )
33
Chapter 1 6
}
There are some very easy things to mess u p here. On line 1 3, we need to think ca refu lly about whether this should be a less than (< ) or a less than or eq uals ( a l l lengths ( int k, int s horter , i nt longe r ) { Has hSet< I ntege r> lengt hs new Has hSet< Integer> ( ) ; Has hSet visited new Has hSet ( ) ; getAll Lengths ( k , 0, s ho rter, longe r , lengt h s , vis ited ) ; ret urn lengt h s ; } =
�
void getAllLengths ( int k, int tota l , i nt s horte r , int longer, Has hSet < I ntege r> lengt h s , Has hSet visited ) { if ( k 0) { 11 lengths . a dd (total ) ; 12 return; 13 } St ring key k + " " + tota l ; 14 9 10
==
CrackingTheCodinglnterview.com I 6th Edition
I
487
Solutions to Chapter 1 6 I Moderate 15
if ( vis ited . conta ins ( key ) ) { return;
16 17
}
18
-
getAllLengths ( k 1, total getAl l lengt hs ( k 1, tot a l v i s ited . add ( key) ;
19
-
20 21
+
+
shorter , shorter, longer, lengt hs , visited ) ; longer , s horter , longer , lengths , v i s ited ) ;
} For simplicity, we've set the key to be a string representation of tot a l and the current plank count. Some people may argue it's better to use a data structure to represent this pair. There a re benefits to this, but there are d rawbacks as well. It's worth discussing this tradeoff with your interviewer. The runtime of this algorithm is a bit tricky to figure out. One way we can think a bout the runtime is by understa nding that we're basica lly filling in a ta ble of SUMS x P LANK COUNTS. The biggest possible sum is K * LONG E R and the biggest possible plank count is K. Therefo re, the runtime will be no worse than 0( K2 * LONG E R ) . Of course, a bunch of those sums will never actually be reached. How many unique sums can we get? Observe that any path with the same number of each type of planks will have the same sum. Since we can have at most K pla nks of each type, there are only K d ifferent sums we can ma ke. Therefo re, the table is really KxK, and the runtime is O ( K 2 ) . Optimal Solution
If you re-read the prior paragraph, you might notice something interesting. There a re only K d istinct sums we can get. Isn't that the whole point of the problem-to find all possible sums? We don't actually need to go through all arrangements of pla nks. We just need to go through all unique sets of K planks (sets, not orders!). There are only K ways of picking K planks if we only have two possible types: {O of type A, K of type BJ, { 1 of type A, K 1 of type BJ, {2 of type A, K 2 of type BJ, ... -
-
This can be done in just a simple for loop. At each "sequence''. we just compute the sum. 1 Has hSet< I nteger> alllengths ( i nt k, int shorter , int longer ) { 2 Has hSet lengths = new Has hSet < Intege r> ( ) ; 3 for ( i nt nShorter 0 ; nshorter < = k ; nshorter++ ) { 4 int nlonger = k - nShorter; s int length nShorter * shorter + nLonger * longer; lengths . add ( lengt h ) ; 6 =
=
7 8
9
}
ret urn lengths ;
}
We've used a Hash S et here for consistency with the prior solutions. This isn't rea lly necessary though, since we should n't get any duplicates. We could instead use a n Array L ist. If we do this, though, we just need to handle an edge case where the two types of planks are the same length. In this case, we would just return an Array l i s t of size 1 .
488
I
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 1 6 1 6. 1 2
I
Moderate
XML Encoding: Si nce
XML is very verbose, you a re given a way of encod i ng it where each tag gets mapped to a pre-defi ned i nteger value. The language/grammar is as follows: E lement
Att r i b u t e
END
Tag Value
Tag Att ributes END Childre n END Tag Value
--> --> - -> --> -->
0 s ome predefined m a ppin g t o int s t r i n g value
For example, the following XML might be converted
i nto the com pressed stri ng below (assuming a mapping of family - > 1 , person - > 2 , f i r stName - > 3 , l astName - > 4 , state - > S).
< pe r s o n firstName = "Gayle " >Some Mes s age < / f a mil y >
Becomes: 1 4 McDowe l l
5
CA 0 2 3 Gayle 0 Some Mes s age 0 0
Write code to pri nt the encoded version of a n XML element (passed i n E l ement and At t ribute objects) . pg 1 82
SOLUTION
Si nce we know the element wil l be passed in as an E l ement and Att r ibute, our code is reasonably simple. We can im plement this by a pplying a tree-like approach. We repeated ly call e n c ode ( ) on parts of the XML structure, handling the code in slightly different ways depending on the type of the XML element. 1
void encode ( E lement root , St ringBuilder s b ) { en code ( root . getNameCode ( ) , s b ) ; 3 for ( Att rib u t e a : root . a tt rib u t e s ) { 4 en code ( a , s b ) ; 5 } encode ( "0", s b ) ; 6 7 if ( root . v a l u e ! n u l l && root . value ! = ) { 8 encode ( root . value, s b ) ; } else { 9 fo r ( E l e m e nt e : root . c h i ld r e n ) { l0 11 encode (e, s b ) ; 12 } 13 } 14 encode ( "0", s b ) ; 15 } 16 17 void e ncod e ( S t r i ng v , S t r i ngBu i l d e r s b ) { 18 s b . append (v ) ; 19 s b . append ( " " ) ; 20 } 21 22 v o i d en code ( Attribute att r , St ringB u i lder s b ) { 23 encode ( att r . getTagCode ( ) , s b ) ; 24 encode ( att r . value, s b ) ; 25 } 2
=
"''
26
CrackingTheCodinglnterview.com j 6th Editi on
I
489
Solutions to Chapter 1 6
I Moderate
27 String encodeToSt ring ( E lement root ) { StringBuilder s b = new St ringBuilder ( ) ; 28 29
30
31 }
encode ( root , sb ) ; ret urn s b . toSt ring( ) ;
Observe in line
1 7,
the use of the very simple e n c ode method for a string. This is somewhat unnecessary; all it does i s insert the string and a space following it. However, using this method is a nice touch as it ensures that every element wil l be inserted with a space surrounding it. Otherwise, it might be easy to break the encoding by forgetting to append the empty string. 1 6.1 3 Bisect Squares: Given two squares on a two-dimensional pla ne, find a line that would cut these two
squares in half. Assume that the top and the bottom sides of the square ru n para l le l to the x-axis. pg 1 82
SOLUTION
Before we sta rt, we should think about what exactly this problem means by a "line:' Is a line defined by a slope and a y-intercept? Or by any two points on the line? Or, should the line be really a line segment, which sta rts and ends at the edges of the squares? We will assume, since it makes the problem a bit more i nteresting, that we mean the third option: that the line should end at the edges of the squares. In an i nterview situation, you should discuss this with you r interviewer. This line that cuts two squa res in half must connect the two midd les. We can easily calculate the slope, knowing that s lope = ::=:; . Once we calculate the slope using the two midd les, we can use the same equation to calculate the start a nd end points of the line segment. I n the below code, we will assume the origin ( 0 , 0 ) is in the upper left-hand corner. public c la s s Square {
1
2 3
public Point middle ( ) { ret urn new Point ( (this . left + this . right ) / 2 . 0, (this . top + t his . bottom ) I 2 . 0 ) ;
4 5
6
}
7 �
9
10 11
12 13 14
15
16
17
18
19
20
21 22
23
24 25
490
/ * Return the point where the line segment connecting midl and mid2 intercepts * the edge of square 1 . That is, d raw a line from mid2 to mid l , and continue it * out until the edge of the square . * I p u b l i c Point extend ( Point mid l , Point m i d 2 , double s iz e ) { / * F ind what d i rect ion t h e l i n e m i d 2 - > m i d l goes . * / double xdi r midl . x < mid2 . x ? - 1 1; double ydir midl . y < mid2 . y ? -1 : 1 ; =
/ * I f midl a n d mid2 have the s ame x value, then the s lope calculation will * throw a divide by 0 exception . So, we compute t h i s specially . * / if (midl . x mid2 . x ) { return new Point (mid l . x, midl . y + yd i r * size / 2 . 0 ) ; ==
}
double s lope = (midl . y - mid2 . y ) / ( midl . x - mid2 . x ) ; double xl 0; double yl 0; = =
Cracking t h e Co d i n g Interview, 6th Edition
Solutions to 25
*
28
29
30
==
=
34
35 36
=
37
38
=
3'
=
40 41
47
48 49
50 51
} public Line cut ( Square other) { / * Cal culate where a line betwee n each middle would collide with the edges of * the squares * I Point pl extend ( t h i s . middle ( ) , othe r . middle ( ) , t h i s . s ize ) ; Point p2 extend ( t h i s . middle ( ) , othe r . middle ( ) , 1 * this . s i ze ) ; Point p3 extend ( ot he r . middle ( ) , t h i s . middle ( ) , other . s ize ) ; Point p4 extend ( ot her . middle ( ) , this . middle ( ) , -1 * other . s i ze ) ; =
=
=
/* Of a bove point s , find s t a rt and end of lines . Sta rt is farthest left (with * top most a s a t ie breake r ) and end is farthest right (with bottom most a s * a tie brea ke r . * /
53
54
55
Poi nt start = p l ; Point end pl; { p2, p 3 , p4} ; Poi nt [ ] points for ( int i 0 ; i < point s . lengt h ; i++ ) { if ( points [ i ] . x < start . x I I ( points [ i ] . x s t a rt . x && points [ i ] . y < s t a rt . y ) ) { s t a rt points [ i ] ; } else if ( points [ i ] . x > end . x I I end . x && points [ i ] . y > end . y ) ) { ( points [ i ] . x end points [ i ] ; } }
56
=
57
=
58
=
59
60 61
==
=
62
63 64
��
=
65
66 67
68
-
=
52
6,
is
=
33
45 46
hit s ize I 2 units away from the middle on the y axis . If the s lope
if ( Math . abs ( s lope ) 1) { xl midl . x + x d i r * s i ze I 2 . 0 ; yl midl . y + yd ir * s i ze / 2 . 0; } else if ( Mat h . abs ( s lope ) < 1 ) { / / s h a llow s lope xl midl . x + xdir * s i ze / 2 . 0; yl = s lope * ( x l - midl . x ) + midl . y ; } else { / / steep s lope yl midl . y + ydir * s ize I 2 . 0; xl ( yl - midl . y ) I s lope + midl . x ; } ret u r n new Point ( xl , yl ) ;
32
44
Moderate
* "shallow" ( < 1 ) the end of the line s egment will hit s ize / 2 units away * f rom the middle on the x axis . * /
31
43
\
/ * Calculate s lope us ing the equation ( yl - y2 ) I ( xl - x2 ) . * Not e : if the s lope is "steep" ( > 1 ) then the end of the line segment will
27
42
Chapter 1 6
ret urn new L i n e ( s t a rt , end ) ; }
The main goal of this problem is to see how ca reful you are a bout cod ing. It's easy to glance over the special cases (e.g., the two squ a res having the same midd le). You should make a list of these special cases before you start the problem and make sure to handle them appropriately. Th is is a q uestion that req uires carefu l a n d thoroug h testing.
CrackingTheCodinglnterview.com I 6th Edition
491
Sol utions to Cha pter 1 6 1 6.1 4
I Moderate
Best Line: Given a two-dimensional graph with poi nts on it find a line which passes the most
number of poi nts. pg 7 83
SOLUTION
This solution seems quite straightforward at fi rst. And it is-sort of. We just"draw" an infinite line (that is, not a line segment) between every two points and, using a hash table, track which line is the most common. This will take O ( N 2 ) time, since there a re N2 line segments. We wil l represent a line as a slope and y-intercept (as opposed to a pair of poi nts), which allows us to easily check to see if the line from ( xl , yl ) to ( x 2 , y 2 ) is equivalent to the l i ne from ( x 3 , y 3 ) to ( x4 , y4 ) . To
find the most common line then, we just iterate through all lines segments, using a hash table to cou nt the number of times we've seen each line. Easy enough!
However, there's one little complication. We're defining two lines to be equal ifthe lines have the same slope and y-intercept. We a re then, fu rthermore, hashing the li nes based on these values (specifical ly, based on the slope). The problem is that floating point numbers cannot always be represented accurately in binary. We resolve this by checking if two floati ng point numbers a re within an e p s i l o n value of each other. What does this mea n for our hash table? It means that two lines with "equal" slopes may not be hashed to the same val ue.lo solve this, we wil l rou nd the slope down to the next epsilon and use this floored S lope as the hash key. Then, to retrieve all l ines that a re potentially equal, we wi l l search the hash table at three spots: floored S l ope, flooredS lope - epsilon, and floored Slope + e p s i l on. This wil l ensure that we've checked out all li nes that might be equal. 1
/ * Find line that goes t hrough most number of point s . */ Line findBestLine ( G raphPoint [ ] point s ) { H a s hMap List linesBySlope = get L i stOfLines ( point s ) ; return getBest Line ( lines BySlope ) ; }
2 3
4 5 6 7 8
/ * Add each pair of points as a line to the list . * / Has hMap List getListOf L i n e s ( GraphPoint [ ] point s ) { 9 Has hMap List lines BySlope = new Has hMap L i s t < Double, Line> ( ) ; for ( i nt i = 0; i < points . lengt h ; i++ ) { 10 for ( i nt j = i + 1 ; j < point s . lengt h ; j ++ ) { 11 12 Line line = new Line ( points [ i ] , points [ j ] ) ; 13 double key Line . floorToNearest E p silon ( line . s lope ) ; 14 lines BySlope . put ( key, line ) ; 15 } =
16 17 18
}
return lines BySlope ; }
19 20 / * Retur n the line with t he most e qui va l en t other lines . */ 2 1 Line getBestLine ( H a s hMap List lines BySlope) { n ul l ; L i n e bestLine 22 int bestCount = 0; 23 24
25 26
27
492
Set s lopes
=
lines BySlope . keySet ( ) ;
for ( double s lope : s lope s ) {
Cracking the Coding Interview, 6th E d itio n
Solutions to Cha pter 1 6 28
l
Moderate
Arraylist < L i ne > l i nes = l i nesBySlope . get ( slope ) ; for ( L ine l i ne : l i nes ) { I* count l i nes that a re equivalent to current line * I int count countEquivalent l i nes ( l i nesBySlope , l i ne ) ;
29
30
31
=
32 33
I * if better than cu rrent l i n e , re p l a ce it * I
34
if
35
36
( count > bestCount ) { best L i ne = line; bestCount count ; be stline . Print ( ) ; System . out . print l n ( bestCount ) ; =
37
38
39 40
}
41
}
} ret u r n best L i n e ;
42
43 } 44 45 I * Check hashmap for lines that a re equivalent . Note that we need to check one * eps ilon a bove and below the actual slope s i n ce we ' re defining two lines as �6 * equivalent if they ' re wit h i n an epsilon of each ot he r . * I 47 4 8 i n t count Eq uivalent l i nes ( H a s hMa pList linesBySlope , L i ne l i n e ) { double key = L i ne . floorToNea rest E ps ilon ( line . s lope ) ; 49 50 int count = count Equivalentlines ( l ines BySlope . get ( key ) , l i ne ) ; 51 count += countEquiva lent l i nes ( l inesBySlope . get (key - Line . epsilon ) , l i ne ) ; 52 count += countEquivalent l i nes ( l i n e s BySlope . get (key + L i ne . epsilon ) , l i ne ) ; ret urn count ; 53 54 } 55
56 57
58
59
6t'J
I * Count l i nes within a n a r ray of lines which a re " equivalent " ( s lope and * y- intercept a re wit h i n an epsilon v a l ue ) to a given line * I
i n t countEquivalent lines (Array L i st < L i ne > l i nes , L i ne l i n e ) { if ( lines == n u l l ) ret u r n 0 ;
61
i n t count = 0 ; f o r ( L ine parallelline : lines ) { if ( parallelline . i s E q uiva lent ( li ne ) ) { count++;
62
63 64
65
}
66
67 68 69
70
71
72
73 74 75
76
77
73 79
80
81 82
83
}
}
ret urn count ;
public c l a s s L i ne { public static double eps ilon = . 0001 ; public double slope, intercept ; private boolean infinite_s lope false; =
public L i ne ( G ra phPoi nt p , Gra phPoi nt q ) { if ( Math . a bs ( p . x - q . x ) > epsilon ) { II if x ' s a re different s lope ( p . y - q . y ) I ( p . x - q . x ) ; II compute slope p . y - slope * p . x; II y intercept from y=mx+b intercept } else { infinite_s lope = t r u e ; intercept = p . x ; I I x - i ntercept , since slope is infinite } } =
=
CrackingTheCodinglnterview.com I 6th Edition
493
Solutions to Chapter 1 6 84
85 86
I
Moderate
public static double floorToNea rest E p s i l o n ( double d ) { int r ( int ) ( d I epsilon ) ; return ( ( double) r) * epsilon; } =
87
88
89 90
public boolean i s E q uiva lent ( double a, double b) { return ( M a t h a b s ( a - b ) < epsilon ) ; }
91
.
92
93 94 95 96 97
98
99 100
101
102 } 103 104 105
public boolea n i s E quiva lent (Object o) { L in e 1 = ( L i n e ) o ; if ( is Equivalent ( l . s lope , s l op e ) && i s E q u ivalent ( l . i ntercept , int e r ce pt ) && ( i nfinite_s lope == l . infinite_slope ) ) { return true ; } return fa l s e ; }
/* HashMapList i s a H a shMa p that maps from Strings t o * Array L i s t < I nteger> . See a p pendix for impl em e nt a ti o n . */
We need to be careful about the calculation of the slope of a line. The line might be completely vertical, which means that it doesn't have a y-intercept and its slope is infinite. We can keep track of this in a separate flag (infi n i te_s lope). We need to check this condition in the equals method. 1 6.1 5 Master Mind: The Game of Master Mind is played as follows:
The computer has four slots, and each slot will contain a ball that is red (R). yellow (Y), green (G) or blue (B). For example, the com puter might have RGGB (Slot # 1 is red, Slots #2 and #3 a re green, Slot #4 is blue). You, the user, a re tryi ng ito guess the solution. You mig ht, for exam ple, guess YRGB. When you guess the correct color for the correct slot, you get a "hit." If you guess a color that exists but is in the wrong slot, you get a "pseudo-h it:' Note that a slot that is a hit can never count as a pseudo-hit. For example, if the actua l sol ution is RGBY and you g uess GGRR, you have one hit and one pseudo hit. Write a method that. given a guess a nd a solution, returns the number of hits and pseudo-hits. pg 183
SOLUTION
This problem is straightforward, but it's surprisingly easy to make little mista kes. You should check your code extremely thorough ly, on a va riety of test cases. We'll implement this code by first creating a freq uency array which stores how many times each character occurs in solut io n, excluding times when the slot is a "hit." Then, we iterate through g u e s s to count the number of pseudo-hits. The code below implements this a lgorith m. 1
2
c l a s s Re s u l t
{ public int hits
494
I
�
0;
Cracki ng the Coding I nterview, 6th Edition
Solutions to Chapter 1 6
3
public i nt pseudoHits = 0;
5 6
public St ring toString ( ) { return " ( " + hits + " , "
4 7
8
9
}
+
pseudoHits
+
" "
)
I
Moderate
;
}
10
i nt code ( char c ) { swit c h ( c ) { 12 case ' B ' : ret u r n 0 ; 13 14 case 'G' : 15 return 1 ; case ' R ' : 16 17 return 2 ; case ' Y ' : 18 19 return 3 ; defa ult : 20 ret u r n - 1 ; 21 22 } 23 } 11
24 25
26 27
28
29
i nt MAX_CO LORS =
Result est imate ( String gues s , St ring solut io n ) { if ( gues s . length ( ) ! = solut io n . length ( ) ) ret u r n n u l l ;
30 31
32
33
34 35
3i 37 38
39
40 41
42
43
44
45 46 47
48
49 50 51 52 S3
4;
54 55 }
Res ult res = new Result ( ) ; i nt [ ] freq uencies = new int [ MAX_COLORS ] ; / * Compute hits a nd build freq uency table */ for ( int i = 0; i < guess . length ( ) ; i++ ) { if ( gues s . charAt ( i ) == solutio n . charAt ( i ) ) { res . hits++; } else { / * Only inc rement the frequency table (which will be used for pseudo - hits ) * if it ' s not a hit . If it ' s a hit, the s lot has a l ready been "used . " */ i nt code = code ( s olution . charAt ( i ) ) ; frequencies [ code] ++; } } /* Compute pseudo- hits */ for ( int i 0 ; i < gues s . length ( ) ; i++ ) { i nt code cod e ( gues s . c h a rAt ( i ) ) ; if ( code >= 0 && frequencies [ code ] > 0 && gues s . charAt ( i ) ! = solutio n . charAt ( i ) ) { res . pseudoHits++; frequencies [ code ] - - ; } } return res ; =
=
Note that the easier the algorithm for a problem is, the more important it is to write clean and correct code. In this case, we've p u l led c od e ( ch a r c ) into its own method, and we've created a Re s u lt class to hold the resu lt, rather than just printing it.
CrackingTheCodingl nterview.com I 6th Editio n
495
Solutions to Chapter 1 6
I Moderate
1 6.1 6 Sub Sort: Given an a rray of integers, write a method to find indices m and n such that if you sorted
elements m through n , the entire a rray wou ld be sorted . Minimize n - m (that is, find the smal lest such sequence). EXAMPLE lnput: l , 2 , 4 , 7 , 10 , 11 , 7 , 1 2 , 6 , 7 , 16 , 1 8 , 19 Output: ( 3 , 9 ) pg
1 83
SOLUTION
Before we begin, let's make sure we u nderstand what our a nswer will look l i ke. If we're looking for just two indices, this indicates that some middle section of the array will be sorted, with the start and end of the array a l ready being in order. Now, let's approach this problem by looking at a n exam ple. 1, 2 , 4, 7 , 10, 1 1 , 8, 1 2 , 5 , 6 , 16, 18, 19
Our first thought might be to just find the longest increasing subsequence at the beginning and the longest increasing subsequence at the end. left : 1 , 2 , 4, 7, 10, 1 1 middle : 8, 1 2 right : 5 , 6, 1 6 , 18, 19
These subsequences are easy to generate. We just start from the left and the right sides, and work our way i nward. When an element is out of order, then we have found the end of our increasing/decreasing subse quence. In order to solve our problem, though, we wou ld need to be able to sort the middle pa rt of the array and, by doing just that, get all the elements in the a rray in order. Specifically, the fol lowing wou ld have to be true: /* a l l items on left are sma l ler t h a n a l l items in middle */ min ( middle) > end( left )
/ * a l l items i n middle are smaller than a l l items in right */ max ( middle) < start ( right ) Or, i n other words, for all elements: left < middle < right
In fact this condition will never be met. The m iddle section is, by definition, the elements that were out of order. That is, it is always the case that left . end > middle . s ta rt a nd midd l e . end > right . s t a rt. Th us, you ca nnot sort the middle to make the entire a rray sorted. But, what we can do is shrink the left and right subsequences u ntil the ea rlier conditions are met. We need the left part to be sma ller than a l l the elements in the m iddle and right side, and the right pa rt to be bigger than all the elements on the left and right side. Let m i n equal min ( middle a nd r i g h t s i d e ) and max equal max ( midd le a nd left side ) . Observe that since the right and left sides are a l ready in sorted order, we only actually need to check their start or end point. On the left side, we start with the end of the su bsequ e n ce (value 1 1 , at element 5) a nd move to the left. The value m i n equals 5 . Once we find an element i such that a r r a y [ i ] < m i n, we know that we cou ld sort the m iddle and have that part of the a rray a p pea r in order.
496
Crack ing the Coding I nterview, 6th Edition
Solutions to Chapter 1 6
I Moderate
side. The value max equals T 2. So, we begin with the start of the 6 . \Nhen right subsequence \value 6) ancl m ov e to tne right.We compare the max of 12 to 6, then 7 , then 1 . bsequence) su increasing an it's ince (s it after be d l u o c 2 1 than smaller elements no that know reach 1 6, we sorted. rray a entire the make to sorted l u be co now d Thus, the m iddle of the array
Then, we do a s i m i la r t h i n g on the
rig ht
The following code implements this algorithm. 1 2
void findUnsortedSequence (int [ ] array) {
/ / find left s u b s e q u e n c e int e n d _lef t = findE ndOfleftSubsequence ( a rray) ;
3
4
if (end_left >= a rray . length - 1 ) return; // Already s o rted
5
� 7
I I find right s u bsequence
int sta rt_right = findSta rtOfRightSubsequence ( a r ray ) ;
8
9
// get min and max int max_index = end_left ; II max of left s ide int min_index sta rt_right ; // min of right s ide for ( int i = end_left + 1; i < sta rt_right ; i++ ) { if ( a rray [ i ] < a rray [ mi n_index ] ) min_index = i ; if ( a r ray [ i ] > array [ max_index ] ) max_index i; }
10 11 12
=
13
=
14 15
16
17
I I s l ide left unt i l less than a rray [min_index] int left_i ndex = s h rinkleft ( a r ray, min_i ndex , end_left ) ;
1�
19
20
II s l ide right unt i l greater t h a n a r ray [ max_index ] int right_index = shrinkRight ( a rray, max_index , sta rt_r ight ) ;
21
22
23 24. 25 26
27
28 29
30
31 32
33
34 35
36
37 38 39
40 41 �2 43
45
46
47
48 49
50
System . out . print l n ( left_index }
+
"
" + right_index ) ;
int findE ndOfLeftSubsequence ( i nt [ ] a r ra y ) { for ( int i = 1 ; i < array . lengt h ; i++ ) { if ( a rray [ i ] < array [ i - 1 ] ) return i - 1 ; }
}
ret urn a rray . length - 1 ;
int findSta rtOfRightSubsequence( int [ ] a r ray ) { for ( int i = a r ray . length - 2 ; i > = 0 ; i - - ) { if ( a r ray [ i ] > a r ray [ i + 1 ] ) ret urn i + 1 ;
}
}
ret urn 0 ;
int s h r inkleft ( int [ ] a r ray, int mi n_index, i nt s t a rt ) { int comp = a rray [min_index ] ; for ( i nt i sta rt - 1; i >= 0; i - - ) { if ( a rray [ i ] < = comp ) ret urn i + 1 ; =
return 0; }
int s h r i nkRight ( int [ ] a r ray, int max_index, int start ) { int comp a r ray [ ma x_index ] ; for ( i nt i start ; i < array . lengt h ; i++ ) { =
=
CrackingTheCodinglnterview.com I 6th Edition
497
Solutions to Chapter 1 6 Sl
52
53
}
if
(array (i 1
>=
I Moderate
comp )
return i
-
1;
return a r ray . length - 1 ;
54 }
Note the u se of other methods in this solution. Although we cou ld have jammed it a l l into one method, it wou ld have made the code a lot harder to u ndersta nd, maintain, and test. In y our interview coding, you should prioritize these aspects. 1 6.1 7 Contiguous Sequence: You a re given an a rray of integers (both positive a nd negative). Find the
contiguous sequence with the la rgest sum. Retu rn the sum. EXAMPLE In put: 2 , - 8 , 3 , - 2 , 4 , - 10 •
Output: 5 ( i . e , { 3, - 2 , 4} ) pg 183
SOLUTION
This is a challenging problem, but an extremely common one. Let's approach this by looking at a n example: 2 3 -8 -1 2 4 -2 3 If we think about our a rray as having alternating sequ ences of positive and negative numbers, we ca n observe that we wou ld never include only part of a negative subsequence or part of a positive sequence. Why wou ld we? Including part of a negative su bsequence wou ld make things u nnecessa rily negative. a nd we should just instead not include that negative seq uence at all. Likewise, including only part of a positive subsequence wou ld be strange, since the sum would be even bigger if we included the whole thing.
For the pu rposes of coming up with our algorithm, we ca n think about our a rray as being a sequence of a lternating negative and positive n u mbers. Each n umber corresponds to the sum of a subsequence of posi tive numbers of a su bsequence of negative n u m bers. For the a rray above, our new reduced a rray wou ld be: 5
-9
6
-2
3
This doesn't give away a g reat algorithm immediately, but it does help us to better understa nd what we're working with. Consider the a rray a bove. Would it ever make sense to have { 5 , - 9 } in a su bsequence? No. These n u m bers sum to 4 so we're better off not i ncluding either number, or possibly just having the sequence be just { 5 }). -
,
When wou ld we wa nt negative numbers included in a su bseq uence? Only if it a l lows us to join two positive su bsequences, each of which have a sum greater than the negative value. We ca n approach this in a step-wise man ner, sta rti ng with the first element in the array. When we look at 5, this is the biggest sum we've seen so far. We set maxSum to 5, and sum to 5. Then, we consider -9. If we added it to s um, we'd get a negative va lue. There's no sense i n extending the subsequence from 5 to -9 (which "reduces"to a sequence of just -4), so we just reset the value of s um. Now, we consider 6. This subsequence is greater than 5, so we u pdate both maxSum a nd sum. Next, we look at - 2 . Add ing this to 6 will set sum to 4. Si nce this is sti ll a "value add" (when adjoined to another, bigger sequence), we m ig h t want { 6 , 2 } i n o u r max su bsequence. We'l l update s um, but not maxSum. -
498
Cracking the C o d i ng Interv i ew, 6th Edition
Solutions to Chapter 1 6 I
Moderate
Finally, we look at 3. Adding 3 to s u m (4) gives us 7, so we update maxsum. The max subsequence is there fore the seq uence { 6 , - 2 , 3 } . When we look a t this in the fu lly expanded array, our logic i s identical. The code below implements this a lgorithm. 1 i nt g et Ma xS um ( i nt [ ] a ) { int maxsum =
2
3 4
=
=
5
6
7
=
8 9
10
}
11
12
13
0;
int s um 0; fo r ( i nt i 0 ; i < a . length; i++ ) { s um += a [ i ] ; if ( m ax s um < sum) { m ax s um sum; } else i f ( s um < 0) { s um = 0;
}
ret u r n m ax s um ;
}
If the a rray is all negative numbers, what is the correct behavior? Consider this simple array: { - 3 , - 1 0 , - 5 } . You cou ld make a good a rgu ment that the maximum sum i s either: 1 . -3 (if you assume the subsequence can't be em pty)
2. 0 (the su bsequence has length 0) 3. MINIMUM_INT (essential ly, the error case). We went with option #2 (ma xSum = 0), but there's no "correct" answer. This is a great thing to d iscuss with you r interviewer; it will show how detai l-oriented you a re. 1 6.1 8 Pattern Matching: You a re given two stri ngs, pattern and v a l ue. The pattern stri ng consists of
just the letters a and b, describing a pattern with in a string. For example, the stri ng c a t c atgoc atgo matches the pattern a a b a b (where cat is a and go is b). It also matches patterns like a, ab, and b. Write a method to determ ine if v a l u e matches pattern. p g 1 83
SOLUTION
As always, we can sta rt with a simple brute force approach. Brute Force
A brute force a lgorithm is to just try all possible values for a and b and then check if this works. We cou ld do this by iterating through all su bstri ngs for a and all possible su bstrings for b. There a re 0 ( n 2 ) su bstrings in a stri ng of length n , s o this wi ll actua lly take O ( n 4 ) time. But then, for each va lue of a a n d b, we need to build the new string of this length and com pare it for equality. This buildi ng/compa rison step ta kes O ( n ) time, giving an overall runtime of O ( n 5 ) . 1 for each po s s ib l e s u bst r i ng a 2 for each po s s i bl e s u b s t r i ng b 3 c a n d id a t e = buildF romPatt ern ( patte rn, a , b ) 4 if c a n d i d a te equa ls value r et u r n t rue 5 Ouch.
Crack i ngTheCodinglnterview.com I 6th E d iti on
I
499
I Moderate
Solutions to Chapter 1 6
One easy optimization is to notice that if the pattern starts with 'a: then the a stri ng must start at the of value. (Otherwise, the b string m ust start at the begin n ing of va l ue.) Therefore, there aren't 2 0( n ) possible values for a; there a re 0 ( n ) . be g i n n i n g
The algorithm then i s to check if the pattern starts with a or b. I f it starts with b, we can "invert" i t (flipping each 'a' to a 'b' and each 'b' to a n 'a') so that it starts with 'a'. Then, iterate through all possible substri ngs for a (each of which must begin at index 0) and all possible su bstrings for b (each of which must begin at some character afte r the end of a). As before, we then compare the string for this pattern with the original string. This algorithm now ta kes O( n4) time. There's one more m i nor (optional) optimization we can make. We don't actually need to do this "inversion" if the string starts with 'b' instead of'a'. The bu i l d F romPattern method can take care of this. We can think a bout the first character in the pattern as the "ma i n" item a nd the other character as the alternate character. The bu i l d F romPatte rn method can build the appropriate string based on whether'a' is the main char acter or a lternate character. 1
boolean doesMatch ( St r i ng pattern, String value) { if ( pattern . length ( ) == 0 ) ret urn value . length ( )
2
3
==
0;
=
4 5 6
int size value . length ( ) ; for ( int mainSize = 0 ; mainSize < s i z e ; mainSize++) { String ma in = va l ue . substring ( 0, mainSize ) ; for ( int a ltStart = mainSize; a ltStart < = size; -altStart++) { for ( int a lt End a ltSta rt ; a ltEnd < = size; altEnd++) { String alt = value . s ubstring ( a ltSta rt, altEnd ) ; String cand = buildFromPattern ( pattern, main, a lt ) ; i f ( c a nd . equals (value ) ) { return true; } } }
7
8
=
9 10 11 12 13 14 15 16 } 17 return fa l s e ; 18 } 19 20 String build F romPattern ( St r i ng pattern, String ma i n , String alt) { 21 StringB uffer s b new StringBuffer ( ) ; 22 char first = pattern . charAt ( 0 ) ; 23 f o r ( c har c : pattern . toCha rArray ( ) ) { if ( c == first ) { 24 25 s b . a ppend (main ) ; 26 } else { s b . append ( a lt ) ; 27 =
28
}
29
30
31
} return s b . toString ( ) ; }
We should look for a more optimal algorithm. Optimized
Let's think through our curre nt algorithm. Searching through all values for the main string is fai rly fast (it takes 0 ( n ) time). It's the alternate string that is so slow: 0 ( n 2 ) time. We should study how to optimize that.
500
C ra ck i ng the Cod ing Interview, 6th E d itio n
Solutions to Chapter 1 6
I Moderate
Suppose we have a pattern like a a b a b and we're comparing it to the string c a t c atgoc atgo. Once we've pi c ked "ca t " a s the value for a to try, then the a stri ngs are going to take up nine characters (three a stri ngs with le ngth three each). Therefore, the b stri ngs must take up the re m a i n ing four cha racters, with each having length two. Moreover, we actually know exactly where they must occur, too. If a is c a t , and the pattern is a a ba b, then b must be go. In othe r words, once we've picked
we've p i ck e d b too. There's no need to ite ra te . Gath e ri ng some basic stats on p a t t e r n (number of a s, number of bs, first occu rrence of each) a nd ite ra tin g throu g h values for a (or whichever the main string is) will be suffic i e nt . 1
2 3
boolean doesMatch ( St r i ng patte rn, St ring value ) { if ( pattern . length ( ) == 0 ) return value . length ( )
4
char mainChar = patt e r n . charAt ( 0 ) ; char altChar = mainChar 'a' ? 'b' int s i ze = value . lengt h ( ) ;
5
==
5
7 8
i nt int i nt int
9
10 11
12 15
16
17
1�
2�
22 23 24 25
33
34
35 36
} }
return fa lse; }
int countOf ( String pattern, char c ) { i nt count 0; for ( int i = 0 ; i < pattern . lengt h ( ) ; i++) { if ( pattern . charAt ( i ) == c ) { count++ ; =
37
38
39 40 41
=
=
27
31
countOfMai n = cou ntOf ( pattern, mainCha r ) ; countOfAlt = pattern . lengt h ( ) - countOfMai n ; firstAlt pattern . indexOf ( altChar ) ; ma xMainSize = size I countOfMa i n ;
St r i ng cand build F r omPattern ( pattern, first , second ) ; if ( cand . equals ( va l ue ) ) { ret u r n true; }
26
32
;
=
21
30
I
==
19
2�
fa
0 ,·
for ( int mainSize = 0; mainSize < = maxMa inSize; mainSize++ ) { i nt remai n i nglength = s i ze - mainSize * countOfMa i n ; St ring first = value . s ubstring ( 0 , mainSize ) ; if (cou ntOfAlt = = 0 I I rema ining Length % countOfAlt == 0 ) { i nt a l t index = fi rstAl t * mainSize; int a ltSize = countOfAlt 0 ? 0 : rem a i n i nglength I countOfAlt ; String second countOfAlt = = 0 ? "" : value . substring ( a lt i ndex, a ltSize + altindex) ;
13 14
29
a,
}
}
return count ; } St r i ng build F r omPattern ( . . . ) {
/ * same as befo re * / }
This algorithm takes O ( n 2 ) , si nce we it e ra t e through O(n) possi b i l iti e s for t he main string and do O ( n ) work to b u i l d and compa re the strings.
Observe that we've also cut down the p o ss i b iliti e s for the main string that we try. If there are three i nsta nce s of the main string, then its length ca nnot be any more than one third of v a l u e.
Cracki ngTheCodingl nterview.com I 6th Edition
501
Solutions to Chapter 1 6
I Moderate
Optimized (Alternate)
If you don't like the work of building a string o nly to com pare it (a nd then destroy it), we can elim inate this. I nstea d, we can iterate through the values for a and b as before. But this time, to check if the string matches the pattern {given those values for a and b), we walk through v a l ue, comparing each su bstring to the first instance of the a and b strings. 1
boolea n doesMatch ( St r i ng pattern, String value) { if ( pattern . length ( ) == 0 ) ret urn value . length ( )
2
3 4
char mainChar = pattern . charAt (0 ) ; char altChar = mainChar == ' a ' ? ' b ' i nt size = value . length ( ) ;
5 6 7
8
i nt i nt int int
9
10 11
12 13
countOfMai n = countOf ( pattern , mainCha r ) ; countOfAlt = pattern . length ( ) - countOfMai n ; firstAlt = pattern . indexOf ( a ltCha r ) ; maxMa inSize = s i ze I countOfMa i n ;
==
16 17
==
18 19
20
21
}
22
}
} ret urn fa l s e ;
23
24 } 26
'aI ;
f o r ( int mainSize = 0 ; mainSize < = maxMainSize; mainSize++ ) { i nt rema i n i ngLength = s ize - mainSize * countOfMa i n ; if ( countOfAlt = = 0 I I rema i n i ng length % countOfAlt 0) { i nt altindex = firstAlt * mainSize ; i nt a ltSize = countOfAlt 0 ? 0 : rema i n i ngle ngth / countOfAl t ; if ( matches ( pattern, val ue , mainSize , altSize, altlndex ) ) { ret urn t r u e ;
14 15
25
0 ,·
/*
Iterates through pattern a nd value . At each character within pattern, checks if * this is the main string or the alternate string . Then checks if the next set of * characters in value match the original set of those chara cters ( e ither the main 29 * o r the alternate . * / 3 0 boolean matches ( St ring pattern , String value , int mainSize, i nt altSize, i nt fi rstAlt ) { 31 32 i n t stringlndex = mainSize; for ( int i = 1; i < pattern . length ( ) ; i++) { 33 34 i nt size pattern . charAt ( i ) = = pattern . cha rAt ( 0 ) ? mainSize : altSize ; 35 i nt offset = pattern . charAt ( i ) == pattern . cha rAt ( 0 ) ? 0 : fi rstAlt ; 36 if ( ! i s E q ua l ( value , offset , stringindex, s i ze ) ) { 37 return fal s e ; 38 } 39 stringlndex += s i z e ; 40 } ret urn true ; 41 42 } 43 44 /* Checks if two s ubstrings a re equal , start i ng at given offsets a nd continuing to 45 * size . * / 4 6 boolean i s E q ua l ( String s l , i n t offset l , i nt offset2, i n t size) { 47 for ( i nt i 0 ; i < s i z e ; i++) { 48 if ( s l . charAt ( offsetl + i ) ! = s l . ch a rAt ( o ffs et 2 + i ) ) { 49 return f a l s e ; 27 28
=
=
502
I
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 1 6 50 51
52
53
}
\
Moderate
} } return true ;
This a lgorithm will stil l ta ke 0 ( n2 ) time, but the benefit is that it can short circuit when matches fail early (which they usually will). The previous algorithm must go through all the work to build the string before it can learn that it has failed. 1 6. 1 9 Pond Sizes: You have an integer matrix representing a plot of land, where the va lue at that location
represents the height a bove sea level. A va lue of zero ind icates water. A pond is a region of water connected vertical ly, horizontal ly, or diagonally. The size of the pond is the tota l number of connected water cells. Write a method to compute the sizes of all ponds in the matrix. EXAM PLE I nput: 0 0 1 0
2 1 0 1 0 1 1 0 1 1 0 1 Output: 2, 4, 1 (in any order) pg 1 84
SOLUTION
The fi rst thing we can try is just wa lking through the array. It's easy enough to find water: when it's a zero, that's water. Given a water cell, how can we compute the amount of water nearby? If the cell is not adjacent to any zero cells, then the size of this pond is 1 . If it is, then we need to add in the adjacent cells, plus any water cells adjacent to those cells. We need to, of course, be careful to not recount any cells. We can do this with a modi fied breadth-fi rst or depth-first search. Once we visit a cell, we permanently mark it as visited. For each cel l, we need to check eight adjacent cells. We could do this by writing in li nes to check u p, down, left, right, and each of the four diagonal cells. It's even easier, though, to do this with a loop. 1
Array l i st < I ntege r > computePo ndS izes ( i nt [ ] [ ] land ) { Arraylis t < I ntege r > pondSizes = new Array L i st < I nteger> ( ) ; 3 for ( i nt r = 0; r < land . lengt h ; r++ ) { 4 for ( int c = 0 ; c < land ( r] . lengt h ; c++) { 5 if ( la nd [ r) [ c ] == 0 ) { / / Opt i o na l . Would ret urn a nyway . 6 i nt size = computeSize ( la n d , r , c) ; 7 pond Sizes . add ( s ize) ; 8 } 9 } rn } 11 ret urn pondSize s ; 12 } 2
13
14 15
19 17 18
19
i nt c omput e Si ze ( i nt [ ] [ ] land, int row, i nt col ) { / * I f o u t o f bounds or al ready v i s ited . * / i f ( row < 0 I I c o l < 0 I I row >= land . length I I col >= land [ row ) . length I I land ( row ] [ co l ] ! = 0 ) { / / vis ited or not water ret urn 0; }
Cracki ngTheCodinglnterview.com I 6th Edition
503
Solutions to Chapter 1 6
I Moderate
int size = 1 ; land ( row] [col ] - 1 ; / / M a r k v i s ited for ( int d r = - 1 ; dr < = 1; d r++ ) { for ( int de = - 1 ; de < = 1 ; de++) { s i z e += computeSize( land , row + dr, col + d e ) ;
20
=
21
22 23
24 25
}
26
}
27
return s i z e ; } I n this case, we marked a cell as visited by setting its value to -1 . This allows u s to check, in one line ( l a nd [ row ] [ c o l ] l = 0), if the value is va lid d ry land or visited. I n either case, the va lue will be zero.
28
You might also notice that the for loop iterates through nine cells, not eight. It includes the current cell. We could add a line in there to not recurse if dr = = 0 and de = = 0. This rea lly doesn't save us much. We'll execute this if-statement in eight cells unnecessa rily, just to avoid one recursive call. The recursive ca ll returns immediately si nce the cell is marked as visited. If you don't like modifying the in put matrix, you can create a secondary v i s ited matrix. 1
2
3
4 5
6
7 � 9
10
Array list< Integer > computePondSizes ( int [ ] [ ] land ) { boolean [ ] [ ] v i s ited = new boolean [ land . length ] [ la nd [0] . lengt h ] ; Arraylist< Integer > pondSizes = new Arraylist < I nteger > ( ) ; for ( int r = 0 ; r < land . lengt h ; r++ ) { for ( int c = 0 ; c < land [ r ] . lengt h ; c++ ) { int size = computeSize( land , v i s ited , r , c ) ; if ( s ize > 0 ) { pondSizes . ad d ( s i ze ) ; }
} }
11
ret u rn pondSizes ;
12
13
14 15
16 17
1�
19
20
}
int computeS i z e ( int [ ] [ ] land , boolean [ ] ( ] v i s ited , int row, int col ) { / * If out of bounds o r a lready v i s ited . */ if ( row < 0 1 1 col < 0 1 1 row >= land . l ength 1 1 c o l > = land [ row] . length 1 1 v i s ited [ row] [col ] 1 1 land [ row ] [col ] ! = 0) { ret u r n 0 ;
}
21
int s i z e = 1 ; vis ited [ row ] [ co l ] = t rue; f o r ( int dr = - 1 ; dr < = 1 ; dr++) { for ( int de = - 1 ; de < = 1 ; de++ ) {
22 23
24
25
s ize += computeS i z e ( land , v i s ited , row + d r , col + de ) ;
26
27 28
29
}
}
ret u r n s i z e ;
}
Both implementations a re O ( WH ) , where W is the width of the matrix and H is the heig ht.
I
Note: Many people say "O ( N ) " or "O ( N2 ) '; as though N has some inherent meaning. It doesn't. Suppose this were a square matrix. You could describe the runtime as 0 ( N ) or 0 ( N2 ) . Both a re correct, depending on what you mean by N. The runtime is O ( N2 ) , where N is the length of one side. Or, if N is the number of cells, it is O ( N ) . Be carefu l by what you mean by N. I n fact, it might be safer to just not use N at all when there's any ambigu ity as to what it could mean.
504
Cracking the Coding Interview, 6th Ed iti o n
Solutions to Chapter 1 6
\
Moderate
Some people will miscompute the runtime to be O ( N4) , reasoning that the comput e S i z e method could ta ke as long as 0 ( N2 ) time and you might ca ll it as much as O ( N 2 ) times (and apparently assuming an NxN matrix, too). While those are both basically correct statements, you can't just multiply them together. That's beca use as a single ca ll to c omputeS i z e gets more expensive, the num ber of times it is called goes down. For example, suppose the very first call to c omputeSize goes through the entire matrix. That might ta ke O ( N2 ) time, but then we never ca ll computeS i z e again. Another way to compute this is to think about how many times each cell is "touched" by either call. Each cell will be touched once by the c omputePond S i z e s function. Additionally, a cell might be touched once by each of its adjacent cells. This is still a constant number of touches per cell. Therefore, the overall runtime is O ( N2 ) on an NxN matrix or, more generally, O ( WH ) . 1 6.20 T9: 0n old cell phones, users typed o n a numeric keypad and the phone would provide a list of words
that matched these num bers. Each digit mapped to a set of 0 - 4 letters. Implement an algorithm to return a list of matching words, given a sequence of digits. You are provided a list of valid words (provided in whatever data structure you'd like). The mapping is shown in the diagram below: 1
2
3
a bc
def
4
5
6
ghi
jkl
mno
7
8
pqrs
tuv
9 wxyz
0
EXAMPLE Input:
8733
Output:
tree, u s ed pg 1 84
SOLUTION
We could approach this in a couple of ways. Let's start with a brute force algorithm. Brute Force
Imagine how you would solve the problem if you had to do it by hand. You'd probably try every possible value for each digit with all other possible values. This is exactly what we do algorithm ical ly. We ta ke the fi rst digit and run through all the characters that map to that digit. For each character, we add it to a p r efix va ria ble and recurse, passing the prefix downward. Once we run out of characters, we print p refix (which now contains the full word) if the string is a valid word. We will assume the list of words is passed in as a HashSet. A Ha s hSet operates similarly to a hash table, but rather than offering key->value lookups, it ca n tell us if a word is conta ined in the set in 0 ( 1 ) time. 1 Array L i s t < St ring> getVa lid T9Words ( String numbe r, H a shS et < St r i ng> word List ) { 2 3
4 s 6
}
ArrayList results = new Ar ray list < S t r i ng > ( ) ; getValidWord s ( number, 0, "", word l i s t , result s ) ; retu rn r e s u l t s ;
CrackingTheCodinglnterview.com I 6th Edition
SOS
Solutions to Chapter 1 6
I Moderate
7
void getVa lidWord s ( String n umber , i nt index, String prefix, H a s hSet wordSet , Arraylist < St r i ng> res u lt s ) { 9 / * If it ' s a complete word , print it . * / 10 if ( i ndex == n umber . lengt h ( ) && wordSet . contains ( prefix ) ) { 11 result s . add ( prefix ) ; 12 ret u r n ; 13 } 8
14
15
/* Get cha racters t hat match t his d igit . * / c h a r d igit = numbe r . c h a rAt ( i ndex) ; char [ ] lett e r s = getT9Chars ( d igit ) ;
16
17
18 19
20 21
22
23
24
25
26
}
/* Go t h rough all remaining option s . */ if ( letters ! = null) { f o r ( c har letter : lette rs) { getVal idWord s ( numbe r, i ndex + 1, prefix } }
+
lette r , wordSet , result s ) ;
27
/ * Ret urn array of cha racters t hat map to t h i s digit . * / c h a r [ ] getT9C h a r s ( c har digit ) { 29 if ( ! Character . i s Digit ( digit ) ) { 30 ret urn n u l l ; 31 } 32 int dig = Character . getNume ricVal ue ( d igit ) - Cha racte r . getNumericVa l ue ( '0' ) ; 33 ret urn t9Letters [ d i g ] ; 34 }
28
35
3i
/ * Mapping o f d igits to letters . */ c ha r [ ] [ ] t 9 Letters { null, null, { ' a ' , ' b ' , ' c ' } , { ' d ' , ' e ' , ' f ' } , { 'g ' , ' h ' , ' i ' } , { ' j ' .J ' k ' , " l ' } , { 'm' , ' n ' , ' o ' } , { ' p ' , ' q ' , ' r ' , ' s ' } , { ' t ' , ' u' , ' v ' } , { 'w' , ' x' , ' y ' , ' z ' } 40 } ; 37 38 39
=
This a lgorithm nu n s i n 0 ( 4N ) time, where N is the length of the string. This is becau se we recursively bra nch four times for each call to getVa l idWords, a nd we recurse u ntil a call stack de pth of N. This is very, very slow on large strings. Optimized
Let's return to thinking a bout how you would do this, if you were doi ng it by hand. Imagine the exam ple of 3 3 8 3 5 6 76368 (which corresponds to deve lopme nt). If you were doi ng this by hand. I bet you'd skip over sol utions that start with fftf [ 3 3 8 3], as no valid word s sta rt with those cha racters. Ideal ly, we'd like our progra m to make the same sort of optimizatio n: stop recursing down paths which wil l obviously fail. S pecifically, ifthere a re no word s in the d ictionarythat sta rt with prefix, stop recursing. The Trie data structure (see "Tries (Prefix Trees)" on page 1 05} ca n do this for us. Whenever we reach a stri ng which is not a va lid prefix, we exit. 1 Arraylist getVa lidT9Wo rd s ( St r i ng n umber, Trie t rie ) { Arr ay li s t < String> results = new Arraylist ( ) ; 2 3 getVa lidWord s ( n umbe r , 0, "" , t rie . get Root ( ) , result s ) ; ret urn result s ; 4 5 } 6
506
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 1 6 7
8 9
10
11
12
13
14 15
16 17
==
I * Get c h a racters t h at match t h i s d igit * I c h a r d igit = number . cha rAt ( index ) ; char [ ] letters = getT9Chars ( digit ) ;
19
20 21
I* Go t h rough a l l rema i n i ng option s . *I if ( letters ! = n u l l ) { for ( c h a r letter : letters ) { TrieNode child = t rieNode . getCh ild ( letter ) ; I * If t here are words t hat start wit h prefix + l e tt e r , * t hen continue recu rsing . *I if ( ch ild ! = null } { getValidWord s ( number, index + 1 , pr e f i x + letter, c h i l d , result s ) ;
22
23
24
25
26 27 28
29
}
30
32
Moderate
void getvalidWords (String n um b e r , int index, String prefix , TrieNode trieNode , Array L i s t < St r i ng > result s ) { I * If it ' s a complete word , print it . *I i f ( i ndex n umb e r . l e ngt h ( ) ) { if ( t rieNode . terminates ( ) ) { I I I s complete word result s . add ( prefix ) ; } return; }
1�
31
(
}
}
}
It's difficult to describe the ru ntime of this algorithm since it depends on what the language looks like. However, this "short-circuiting" will make it ru n much, much faster in practice.
Most Optimal
Believe or n ot. we ca n actu a l ly m a ke it run even faste r. We just need to do a l i tt l e bit of preprocess ing. That's not a big deal though. We were doing that to build the trie a nyway. This problem is asking us to list a l l the words represented by a particular number in T9. Instead of trying to do this "on the fly" (and going through a lot of possibil ities, many of which won't actually work), we can just do this in advance. Our algorithm now has a few steps: Pre-Computation: 1 . Create a hash table that maps from a sequence of digits to a list of strings. 2. Go through each word in the dictionary and convert it to its T9 representation (e.g., APP L E - > 2775 3).
Store each of these in the a bove hash table. For example, 8733 would map to { u s ed , t ree }.
Word Lookup: 1 . Just look up the entry in the hash table and return the list
That's it! I* WORD LOOKUP */ 1 2 Array list getValidT9Words (St ring numbers, 3 Has hMapList dictionary) { 4 retu r n dict i o n a ry . get ( numbers ) ; s } 6 Cra ck in gTh eCod i n gl n te rv i ew.com I 6th Edition
507
Solutions to Chapter 1 6 7
\
Moderate
/ * PRE COMPUTATION * /
8 9
/ * Create a h a s h table that maps from a number to a l l words that have this 10 * numerical representation . */ 1 1 HashMa p L i s t initializeDi ctionary ( St r i ng [ ] word s ) { / * Create a h a s h table t h a t m a p s from a letter t o t h e d igit * / 12 13 HashMa p letterToNumberMa p = createLette rToNumberMa p ( ) ; 14
15
16 17
18 19
20
/ * Create word - > number map . * / HashMa plist wordsToNumbers = new Has hMapList ( ) ; for ( St ring word : words ) { String numbers = convertToT9 (word, lette rToNumberMa p ) ; wo rdsToNumbers . put ( numbe r s , word ) ; } return wo rdsToNumbers ;
21 22 } 23 24 / * Convert mapping of numbe r - > letters into letter - > numbe r . * / 2 5 HashMap createlette rToNumbe rMa p ( ) { 26 Has hMa p letterToNumberMa p 27 new HashMa p ( ) ; 28 for ( int i 0; i < t9 Letters . lengt h ; i++ ) { 29 cha r ( ] letters t9Letters [ i ] ; 30 if ( letters ! = null) { 31 for ( char letter : letters ) { 32 c h a r c = Cha racte r . forDigit ( i , 1 0 ) ; letterToNumberMap . put ( letter, c ) ; 33 34 } 35 } 36 } 37 return letterToNumberMa p ; 38 } =
=
39
40 /* Convert from a string to its T9 representat ion . */ 4 1 String conve rtToT9 ( St r i ng word , Has hMa p < C h a racter, Cha racter> lette rToNumberMa p ) { St ringBuilder sb new Stri ngBuilder ( ) ; 42 43 for ( char c : word . toCharArray ( ) ) { 44 if ( letterToNumberMa p . cont a i n s Key ( c ) ) { 45 char d igit = letterToNumberMa p . get ( c ) ; 46 s b . a ppend ( d igit ) ; 47 } 48 } 49 return s b . toSt ring ( ) ; 50 } 51 5 2 c h a r [ ] [ ] t9Letters = / * Same a s before * / 53 5 4 / * Has hMa pList i s a HashMap t h a t maps from St rings t o * Arraylist< I nteger> . See a ppendix f o r implementation . * / 55 =
Getting the wordsthat m a p to t h i s number w i l l run in O ( N ) time, where N i s the number of digits. T h e O ( N ) comes i n during the hash table look u p (we need to convert the number to a hash t a b l e ). I f you know the words \jre never longer than a certain max size, then you could also descri be the runtime a s 0( 1 ) . N ote that it's easy to t h i n k, "Oh, linear-that's not that fast:' But it depends what it's linear on. Linear o n the length of the word is ext re m e ly fast. Linear on the length of the dictionary is not so fast.
508
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 1 6 1 6.21
I
Moderate
Sum Swap:
Given two arrays of integers, find a pair of values (one value from each array) that you can swa p to g ive the two arrays the same sum.
EXAMPLE I n put: {4, 1, 2, 1 , 1, 2} and { 3 ,
Output: { 1 ,
3}
6,
3, 3 } pg 1 84
SOLUTION
We should start by trying to understand what exactly we're looking for. We have two arrays and their sums. Although we likely aren't g iven their sums u pfront, we can just act like we are for now. After all, com puting the sum is a n O(N) operation and we know we can't beat O(N) a nyway. Com puting the sum, therefore, won't impact the ru ntime. When we move a (positive) va lue a from a rray A to a rray B, then the sum of A drops by a and the sum of B i ncreases by a . We are looking for two va lues, a a n d b, such that: s umA - a
+
b
=
sum B - b
+
a
Doing some qu ick math: 2a - 2b = s umA - s um B a - b = ( s umA - s u m B ) I 2 Therefore, we're looking for two values that have a specific ta rget difference: ( s umA - sumB) / 2. Observe that because that the ta rget must be an integer (after a l l, you can't swa p two integers to get a non integer difference), we can conclude that the difference between the sums must be even to have a valid pair. Brute Force
A brute force algorithm is simple enough. We just iterate through the arrays and check a l l pairs of va lues. We ca n either do this the "na ive" way (com pare the new sums) or by looking for a pair with that difference. Na ive approach: i nt [ ] findSwapValues ( int [ ] arrayl, i nt [ ] array2 ) { 2 int s uml sum (arrayl ) ; 3 int s um2 s um (a r ray2 ) ; 1
=
4 5
for ( i nt one : arrayl) { for ( i nt two : array2 ) { i nt newSuml = s uml - one + two ; int newsum2 = sum2 - two + one ; if ( n ew S u ml n e wS u m2 ) { int [ ] va l u e s {one, two } ; return va lues ; } } }
6
7
8 '
==
1f:l
=
11
12 13 14
15
16 17
}
return null;
Ta rget approach: 1
i nt [ ] fi ndSwa pValues ( i nt [ ] arrayl, i nt [ ] array2 ) {
CrackingTheCodingl nterview.com I 6th Edition
509
Solutions to Chapter 1 6 2
I Moderate
I nteger ta rget = getTa rget ( a rrayl , array2 ) ; if (ta rget n u l l ) return null;
3
==
4 5
for ( int one : a rrayl) { for ( int two : array2) { if ( one - two ta rget ) { i nt [ ] values {one, two } ; return value s ; } } }
6
7
==
8
=
9
13
11
12 13
14 15 16 17
18 19
20
return nul l ; } I nteger getTa rget ( int [ ] a r rayl, i nt [ ] a rray2) { int s uml s um ( a rrayl ) ; int s um2 s um ( a rray2 ) ; =
if ( ( s uml - sum2 ) % 2 ! = 0) return null ; 21 22 return ( s uml - sum2 ) I 2 ; 23 }
We've used a n I nt ege r (a boxed data type) as the return value for getTa rget. This allows us to distin guish an "error" case. This algorithm ta kes O( AB ) time. Optimal Solution
This problem reduces to finding a pa ir of values that have a particular difference. With that i n m ind, let's revisit what the brute force does. In the brute force, we're looping through A and then, for each element, looking for a n element i n B which gives us the "right" difference. If the value in A is 5 and the ta rget is 3, then we must be looking for the value 2. That's the o n ly value that could fulfill the goal. That is, rather than writing one - two == ta rget, we could have written two How ca n we more quickly find an element in B that eq uals one - ta rget?
one
-
t a rget.
We can do this very quickly with a hash table. We just throw all the elements in B i nto a hash table. Then, iterate throug h A and look for the a ppropriate element in B. 1 int [ ] findSwapValues ( int [ ] a rray l , i nt [ ] a rray2 ) { 2 Integer ta rget getTa rget ( a rrayl, a rray2 ) ; 3 if ( t a rget null) return null; 4 return findDiffe rence ( a rrayl, a r ray2, ta rget ) ; } 5 =
==
6 7 8 9
10 11
12 13
14 15
/ * F ind a p a i r of values with a s pecific d ifference . */ int [ ] findDifference ( i nt [ ] a rrayl, i nt [ ] a rray2 , int t a rget ) { H a s hSet < I nteger> contents 2 getContent s ( array2 ) ; for ( int one : a rray l ) { int two one - target ; if ( content s 2 . contains ( two ) ) { int [ ] va lues = {one , two } ; return values ; }
510
=
=
Cracking the Coding I nte rvi ew, 6th Ed i t i on
Solutions to Chapter 1 6 16
}
17
1�
19
20 21
22
23
24 25
26
27
28
I Moderate
ret u r n n u l l ; }
/* Put c ontents of a r ray into h a s h set . */ HashSet < I nteger> getContents ( int [ ] array) { HashSet < I ntege r> set = new H a s hS et < I n t eg e r > ( ) ; for ( i nt a : array) { set . add ( a ) ; } ret urn set ; }
This solution will ta ke O ( A+B ) time. This i s the Best Conceivable Runtime (BCR), since we have to at least touch every element in the two arrays. Alternate Solution If the
1
2 3
4
5
7
8 9
arrays are sorted, we can iterate through them to fi nd an appropriate pair. This will req uire less space. int [ ] findSwapValue s ( i nt [ ] arrayl, i nt [ ] array2 ) { Int e g e r ta rget = getTarget ( a r rayl, a r ray2 ) ; if ( t a rge t == nul l ) ret u r n n u l l ; return f i ndDiffe rence ( a rrayl, a r ray2, ta rget ) ; } i nt [ ] find O iff e r e n c e ( i nt [ ] a r rayl , i nt [ ] a r ra y 2 , int t a rg e t ) { 0; int a
i nt b
10 11 14
15
16 17 18 19
20
21
22 23
24 25
26 28
0;
while (a < a rr ay l . l e n gth && b < array2 . lengt h ) { i nt difference = arrayl [ a ] - a r ray2 [ b ] ; /* C o m pa r e differe n c e t o ta rget . If d iff e r e n ce is too s m a ll, t he n m ake it * bigge r by moving a to a bigge r value . If it is too big, then make it * s ma l l e r by moving b t o a bigger value . If it ' s j u s t right , return this * pai r . */ if ( d iff e r e n c e == ta rget ) { i nt [ ] va l u e s = { a r rayl [ a ] , a rray2 [ b ] } ; ret u r n va l u e s ; } else if ( d iffe rence < ta rget ) { a++ ; } else { b++ ; } }
12 13
27
=
ret u r n
null;
}
This algorithm takes O ( A + B ) time but requ i res the a rrays to be sorted. If the arrays aren't sorted, we can still apply this algorithm but we'd have to sort the a rrays first. The overa l l runtime would be O ( A log A + B log B ) .
Cracki ngTheCodingl nterview.com I 6th Edition
S1 1
Solutions to Chapter 1 6
I Moderate
1 6.22 Langton's Ant: An ant is sitting on an infinite g rid of white and black squares. It initia lly faces rig ht.
At each step, it does the following: (1 ) At a white square, fli p the color of the square, turn 90 degrees rig ht (clockwise), and move forward
one u n it. (2) At a black squa re, fl ip the color of the squa re, turn 90 degrees left (counter-clockwise), and move forward one unit. Write a program to simu late the first K moves that the a nt makes a nd print the final board as a g rid. Note that you are not provided with the data structure to represent the g rid. This is something you must design yourself. The only i n put to you r method is K. You should print the fi nal grid and return nothing. The method signature might be something like void p r i ntKMoves ( i nt K ) . pg 1 85
SOLUTION
At first glance, this problem seems very straig htforwa rd: create a grid, remember the a nt's position and orientation, fl ip the cel ls, turn, and move. The i nteresti ng part comes i n how to handle an i nfinite grid. Solution # 1 : Fixed Array
Technically, since we're only running the fi rst K moves, we d o have a max size for the grid. The a nt ca nnot move more than K moves in either d i rection. If we create a g rid that has width 2K and height 2K (a nd place the ant at the center), we know it will be big enough. The problem with this is that it's not very extensible. If you run K moves and then want to ru n a nother K moves, you might be out of luck. Additionally, this solution wastes a good amount of space. The max might be K moves in a particular dimen sion, but the ant is probably going in circles a bit. You probably won't need all this space. Solution #2: Resizable Array
One thought is to use a resiza ble a rray, such as Java's Array L i s t class. This a llows us to grow an a rray as necessa ry, while sti l l offering 0 ( 1) amortized i nsertion. The problem is that our g rid needs to g row i n two dimensions, but the Array L i s t is only a single a rray. Additionally, we need to grow "backwa rd" i nto negative va lues. The Arrayl is t class doesn't support this. However, we take a similar a pproach by building our own resiza ble g rid. Each time the a nt hits an edge, we double the size of the grid i n that dimension. What about the negative expa nsions? While conceptua l ly we can ta lk a bout something being at negative positions, we cannot actually access array indices with negative va lues. One way we can handle this is to create "fake indices." Let us treat the a nt as being at coord inates ( - 3 , - 1 0 ) , but track some sort of offset or delta to translate these coordinates i nto a rray indices. This is actually u n necessa ry, though. The a nt's location does not need to be publicly exposed or consistent (unless, of cou rse, indicated by the i nterviewer). When the a nt travels i nto negative coordinates, we can double the size of the a rray and just move the ant and a l l cells i nto the positive coordi nates. Essentially, we a re relabeling a l l the indices. This relabeling wil l not impact the big 0 time since we have to create a new matrix a nyway. 1 public class Grid { 2 p ri va t e boolea n [ ] [ ] grid ;
S1 2
Cracking the Coding I nterview, 6th Edition
Solutions to Chapter 1 6 3
4 5
6
7 8 9
10 11 12
13 14
15
16
17 1� 19 20 21 22 23
24
25
26 27
private Ant ant
/ * Copy old values i nto new a r ray, with an offset / s h ift appl ied to the row and * columns . * I
private void copyWit hSh ift ( boolea n [ ) [ ) oldGrid, boolea n [ ] [ ] newGrid, int s hiftRow, int s hiftColumn ) { for ( i nt r = 0; r < oldGrid . lengt h ; r++ ) { for ( int c = 0 ; c < oldGrid [ 0 ] . lengt h ; c++ ) { newGrid [ r + s h iftRow ] [ c + s hiftCol umn ] = oldG rid [ r ] [ c ] ; } } }
/* E n s u re t hat t he given pos it ion will fit on the a rray . If nece s s a ry, double * the s i ze of the matrix, copy the old values ove r , and adj ust the ant ' s * position so that it ' s in a posit ive range . * I
private void ensure F it ( Position po s ition) { int s hiftRow = 0; int s h iftColumn = 0 ; /* C a l c ulate new n umber of rows . * I int numRows = grid . lengt h ; if ( pos ition . row < 0) { s hiftRow = n umRows ; numRows * =2 ; } e l s e if ( po s it ion . row > = n umRows ) { numRows * =2 ; }
29
30 31 32
33
34 35
/* C a l c ulate new number of columns . * I i n t numColumns = grid [ 0 ] . lengt h ; i f ( posit ion . column < 0 ) { s h iftColumn = numCo lumn s ; numColumns * =2; } else if ( po s it ion . column >= numColumn s ) { numColumns * =2 ; }
36 37
38 39
40
41
42 43
44 45
/ * Grow array, if neces s a ry . Sh ift ant ' s position too . * I
46 47 48
49
50 51
52
54
55
SG
57
58
new Ant ( ) ;
public Grid ( ) { grid = new boolean [ l ] [ l ] ; }
28
53
I Moderate
}
if ( numRows ! = grid . lengt h 1 1 n umColumns ! = grid [ 0 ] . lengt h ) { boolean [ ] [ ) newGrid = new boo lea n [ numRows ] [ n umColumn s ] ; copyWithShift ( g rid , newGrid , s h iftRow, s h iftcolumn ) ; ant . adj ustPosition ( s hiftRow, s hiftColumn ) ; grid = newG rid ; }
/ * F lip color of cells . * I private void flip ( Pos ition positio n ) { int row = pos it ion . row ; int column = position . column; grid [ row ] [ column ] = grid [ row ] [ co lumn ]
?
false
true ;
CrackingTheCodinglnterview.com I 6th Edition
513
Solutions to Cha pter 1 6 59
}
60 61 62
/ * Move a nt . * / p u b l i c void move ( ) { a nt . turn ( grid [ ant . pos ition . row ] [a nt . position . column ] ) ; flip ( a nt . position ) ; ant . move ( ) ; ens ureF it ( a nt . position ) ; / / grow }
63
64
65
66 67
68
69 70
/ * Print board . */ public String toSt ring ( ) { StringBuilder sb new St ringBuilder ( ) ; for ( int r 0; r < grid . lengt h ; r++ ) { for ( int c = 0; c < grid [ 0 ] . lengt h ; c++ ) { if ( r a nt . position . row && c == a nt . pos ition . column ) { s b . a ppend ( a nt . orientation ) ; } else if (grid [ r ] [ c ] ) { s b . append ( "X " ) ; } else { s b . append ( "_" ) ; } } s b . append ( " \n " ) ; } \n" ) ; s b . append ( "Ant : " + a nt . o rientation + ret urn s b . toSt ring ( ) ; }
71
=
72
=
73
74
==
75
76
77 78
79
80 81
82 83
84
!'!5
86
87
I Moderate
}
We pulled the Ant code into a separate class. The nice thing about this is that if we need to have multiple a nts for some reason, we can easily extend the code to support this. public c l a s s Ant { 1 2 public Pos ition position new Pos ition ( 0 , 0 ) ; 3 public Orientation orientation = Orientation . right ; =
4 5
public void turn ( boolea n clockwise ) { orientation = orientation . getT u r n ( clockwise ) ; }
6 7 8 9
public void move ( ) { if ( o rientation == Orientation . left ) { position . column - - ; } else if ( orientation == Orientation . right ) { pos ition . col umn++; Orientation . u p ) { } e l s e if (orientation position . row- - ; } e l s e if (orientation = = Orientation . down ) { position . row++ ; } }
10 11
12 13
14
==
15
16 17 18 19
20 21
22
23
24 25
}
S14
public void a d j ustPosition ( i nt s hiftRow, int s h iftColumn ) { pos ition . row += s hiftRow; position . column += s h iftColumn; }
Cracking the Coding I nterview, 6th Edition
Solutions to Chapter 1 6
I Moderate
Orientation is also its own e num, with a few useful functions. 1
2
3 4
public enum Orientation { left , u p , right , down ;
public Orientation getTurn ( boolean clockwis e ) { if ( t h i s == left ) { return clockwise ? up : dow n ; } else if ( t h i s == u p ) { return clockwise ? right : left ; } else if ( t h i s == right ) { return clockwise ? down up; } e l s e { I I down return clockwise left right ; } }
5
6
7
8
9
10 11
12
13
14
15
@o verride
16 17 18
public St ring toSt ring ( ) { if ( t h i s = = left ) { return "\u2 190"; } else if ( t h i s == u p ) { return "\u2191"; } else if (this = = right ) { ret u r n "\u2192"; } else { II down ret urn "\u2193"; } }
1'
20
21
22 23
24 25 26 27 28
}
We've a lso put Pos ition into its own simple class. We could just as easily track the row and column sepa rately. 1
2
3 4 S
6 7
8
public c l a s s Posit ion { public int row ; public int colum n ; public Position ( int row, int colum n ) { t h i s . row row; t h i s . column column ; } =
=
} 9 This works, but it's actually more complicated than is necessary. Solution #3: HashSet
Although it may seem "obvious" that we would use a matrix to represent a grid, it's actually easier not to do that. All we actually need is a list of the white squares (as well as the ant's location and orientation). We can do this by using a HashSet of the white squares. If a position is i n the hash set then the square is white. Otherwise, it is black. The one tricky bit is how to print the board. Where d o we start printing? Where do we end? Since we will need to print a g rid, we can track what should be top-left and bottom-rig ht corner of the g rid. Each time the ant moves, we compare the ant's position to the most top-left position and most bottom right position, u pdating them if necessa ry.
CrackingTheCodinglnterview.com I 6th Edition
515
Solutions to Cha pter 1 6 1 2
I Moderate
public c l a s s Board { private Has hSet < Posit ion> whites = new Has hSet < Pos ition> ( ) ; private Ant ant = new Ant ( ) ; private Pos ition topLeftCo rner = new Pos i t io n ( 0 , 0 ) ; private Position bottomRightCorner new Position (0, 0 ) ;
3
4 5
6
7
public Boa rd ( ) { }
8
9
10 11
12
13
14 15 16
17
18
19 20 21 22 23
24
25 26
27
28 29
30
/ * Move a nt . * / p u b l i c void move ( ) { a nt . tu r n ( i sWhite ( a nt . pos ition ) ) ; / / Turn f l i p ( ant . position ) ; // f l i p ant . move ( ) ; / / move ensureFit ( a nt . position ) ; } / * F l i p color of cells . * / private void f l i p ( Pos ition pos itio n ) { if ( whites . contai n s ( pos ition ) ) { whites . remove ( pos ition ) ; } else { whites . add ( position . c lone ( ) ) ; }
}
/ * G row grid by t ra c king the most top- left a nd bottom - r ight pos itions . */ private void ens ureFit ( Po s ition pos itio n ) { int row = position . row ; int column = pos ition . column; topLeftCorne r . row = Mat h . mi n (topLeftCorne r . row, row) ; top leftCor ne r . column = Math . mi n ( top leftCorner . column, column ) ;
31
32 33
34
35
36
37 38
39
40 41 42 43
44 45 46 47
48 49 50 51
52
53
54 SS
56
516
bottomRightCorne r . row = Mat h . ma x ( bottomRightCorner . row, row) ; bottomRightCorner . column = Mat h . ma x ( bottomRightCorner . column, column ) ; }
/ * Check if cell is white . * / public boolean isWhite( Position p ) { ret u r n whites . contains ( p ) ; } /* Check if cell is white . * / public boolea n isWhit e ( int row , int column ) { ret u r n whites . conta i n s ( new Pos itio n ( row, column ) ) ; } / * Print board . */ p u b l i c String toString ( ) { St ringBuilder sb = new St ringBui lder ( ) ; i n t rowMin = top LeftCorner . row; i n t rowMax bottomRightCorner . row ; i n t colMin top LeftCo rner . column ; int colMax = bottomRightCorner . column ; for ( int r rowMin ; r < = rowMax; r++ ) { for ( int c = colMi n ; c null - - > - 32
Read +s. Apply it to proc e s s ing. Apply processing to resu lt. Clear proc e s s i n g. =
process ing { + , s } - - > null res u lt -32 - - > -27 =
The code below implements t h i s algorithm. 1
2 3
4 5
6
7
8 9
10
/* Compute the res u lt of t he a rit hmetic seq uence . T h i s wo rk s by read ing left to * right and applying each term to a res u lt . When we see a multiplication or * d i v i s ion , we i n s t ea d a pply this s e q u e nce to a tempo ra ry va riable . * / double comp ute (St r i ng sequenc e ) { Arraylist terms Te rm . p a r se T e rmS equ e n ce ( se q u e n ce ) ; if (terms == null) retu rn I nt e ge r . MIN_VALUE ; =
=
double res ult 0; Te rm pr o c e s s ing null ; fo r ( i nt i = 0; i < te rms . si z e ( ) ; i++ ) =
{
Cra ckingTheCodinglnterview.com
I 6th Edition
525
Solutions to Cha pter 1 6
I
Moderate
12
Term current = terms . get ( i ) ; Term next = i + 1 < terms . s i ze ( ) ? terms . get ( i
14 15
/ * Apply t he current term to "process i ng" . */ proce s s ing collapseTerm ( proce s sing, current ) ;
11 13
16 17
/*
+
1)
null;
If
next term is + or -, then this cluster is done and we s hould apply "p roce s s i ng" t o "result" . */ if ( next = = null I I next . getOperator ( ) == Operato r . ADD I I next . getOpe rato r ( ) == Operator . SUBTRACT) { result = applyOp ( re s u lt , proces s i ng . getOperato r ( ) , process ing . getNumber ( ) ) ; proces s ing = n u l l ; } *
18
19
20
21 22 23
24 } 25 ret urn res u l t ; 26 27 } 28
29 / * Collapse two terms toget her using the operator i n secondary and the n umbers 30 * from each . * / 3 1 Term collapseTerm ( Term primary , Term secondary) { 32 if ( primary n u l l ) ret urn secondary; 33 if ( secondary = = n ul l ) ret urn primary ; 34 35 double value = applyOp ( p rima ry . getNumbe r ( ) , secondary . getOperato r ( ) , 36 secondary . getNumber ( ) ) ; 37 primary . s etNumbe r ( va l ue ) ; 38 ret u r n primary; 39 } ==
40
41 double applyOp ( double left , Operator op, double 42 if ( o p Operator . ADD) ret urn left + right ; 43 else if (op Operato r . SUBTRACT) ret urn left 44 else if ( o p == Operator . MULTIPLY) ret u r n left 45 else if (op = = Operato r . DIVIDE ) ret urn left I 46 else ret u r n right ; 47 } 48 49 public c l a s s Term { public enum Operator { 50 51 ADD, SUBTRACT, MULTIPLY, DIVIDE , B LANK 52 } 53 p rivate double value; 54 55 private Operator operator = Operato r . BLANK; ==
56
57 58 59
60 61
62 63 64 65 66
526
right ) { - right ; * right ; right ;
public Term ( double v , Operator o p ) { value = v ; operator = o p ; } p u b l i c double getNumbe r ( ) { ret urn va lue; } public Operator getOperato r ( ) { return operato r ; } public void setNumbe r ( double v ) { value = v; } /* Parses arithmet i c sequence into a list of Terms . For exampl e , 3 - 5*6 becomes
Cracking the Coding Interview, 6th Edition
Solutions to Cha pter 1 6 67
I Moderate
* something like : [ { BLANK , 3 } , { SUBTRAC T , 5 } , {MULTIPLY , 6 } ) . formatted , returns null . */ public static ArrayList p a r s e Term S eq uence ( S t r i n g s equence ) { / * Code can be found i n downloadable solutions . * / } * If improp erly
68
69 70 71
72
} This ta kes 0 ( N ) time, where N is the length of the i nitial string. Solution
#2
Alternatively, we can solve this problem using two stacks: one for numbers and one for operators. 2 - 6 - 7 * 8 I 2 + s The processing works as follows: • •
Each time we see a number, it gets pushed onto numbe rs t a c k . Operators get pushed onto operatorStac k-as long as the operator has higher priority than the current top of the stac k. If priority ( c u rre ntOpe rato r ) count ( l s )
1 is removed . count ( 0s ) > count ( l s )
So, if c ou n t ( 0 s ) < = c ount ( l s ) , then v is even. lf c o u n t ( 0 s ) > c ou n t ( l s ) , then v is odd. We can now remove all the evens and focus on the odds, or remove all the odds and focus on the evens. Okay, but how do we figu re out what the next bit in v is? If v were contained in our (now smal ler) list, then we should expect to find the fol lowi ng (where c ount 2 indicates the number of Os or 1 s in the second least significant bit): count2 ( 0s )
=
count , ( ls )
OR
count2 ( 0s )
1
=
+
count, ( l s )
As i n the earlier example, we can deduce the va lue of the second least significant bit (LSB ) of v.
a 0 is removed . count, ( 0s ) count, ( l s )
a 0 is removed . count, ( 0s ) < count, ( l s )
a 1 i s removed . count, ( 0s ) > count2 ( l s )
a 1 i s removed . count, ( 0s ) > count,( ls )
=
Aga in, we have the same concl usion: lf c ount 2 ( 0s )
c ount 2 ( 1 s ) , then L S B2 ( v ) = 1. We can repeat this process for each bit. On each iteration, we count the n u m ber of Os and 1 s in bit i to check if L S B ; ( v ) is 0 or 1 . Then, we discard the n u m bers where L S B i ( x ) ! = L S B i ( v ) . That is, if v is even, we discard the odd numbers, and so on. By the end of this process, we will have com puted all bits i n v. I n each successive iteration, we look at n, then n I 2, then n I 4, and so on, bits. This resu lts in a runtime of 0 ( N ) . I f it helps, we can also move through this more visually. I n the first iteration, we start with all the num bers: 00000 00001 00010
00100 00101 00110 001 1 1
01000 01001 01010 01011
Since c ount 1 ( 0 s ) > c ou nt / ls ) , we know that L S B 1 ( v ) L S B 1 ( x ) ! = L S B1 ( v ) . eeeee
OOlee
000-W
e0tte
00001
00101
001 1 1
01000
01001
�
Cracking the Coding I nterview, 6th Edition
=
1. Now, d isca rd all numbers x where ettee
01101
01011
Now, c o u nt 2 ( 0s ) > cou nt 2 ( 1 s ) , so we know that L S B 2 ( v ) L S B, ( x ) ! = L S B2 ( v ) .
534
01100 01101
1 . Now, discard all num bers x where
Solutions to Chapter 1 7
OOWi
�
OOHe
0i010 01011
00111
This time, count 3 ( 0s ) zeroBits n e w Array List< Bitinteger> ( input . s ize ( ) / 2 ) ; 12 13 for ( Bitlnteger t : input ) { 14 if ( t . fetch ( col umn ) 0) { zeroBits . add ( t ) ; 15 16 } else { oneBits . add ( t ) ; 17 18 } 7
=
=
==
1, 20
}
if ( zeroBits . s ize ( ) < = oneBits . s i ze ( ) ) { i nt v findMi s s ing( zeroBit s , column + 1 ) ; return ( v < < 1 ) I 0; } else { i nt v findMi s s i n g ( oneBit s , column + 1 ) ; return (v 1 ; len- - ) { 6 for ( int i = 0; i < = a rray . lengt h - len ; i++) { 7 if ( ha s Eq u a l LettersNumbers ( a rray, i , i + len - 1 ) ) { 8 ret urn e xtractSuba rray ( a rray, i , i + len - 1 ) ; 9 } 10 } 11 } ret urn n u l l ; 12 13 } 4
14
15 / * Check if suba rray has equal number of letters and numbers . * / 1 6 boolean hasEqual LettersNumbers ( c h a r [ ] a r r a y , int s t a rt , int end ) { 17 int counter 0; 18 for ( int i = start ; i < = end ; i++) { 19 if (Cha racter . is lette r ( a rray [ i ] ) ) { co u n t e r ++ ; 20 21 } else if (Cha racte r . is Digit ( a r r ay [ i ] ) ) { 22 counte r - - ; 23 } 24 } 25 return counter 0 ,· 26 } =
27 28
/ * Return suba rray of a r ray between start and end ( inclus ive ) . * / c ha r [ ] ext ractS uba rray ( c h a r [ ] a r ray, int start , i n t end ) { c h a r [ ] subarray new c ha r [ end - start + 1 ] ; 30 for ( int i start ; i < = end ; i++) { 31 32 subarray [ i - start ] = a r ray [ i ] ;
29
=
=
536
Cracking the Cod i n g Interview, 6th Edition
Solutions
Chapter
17
I
Hard
} ret urn suba r ray;
33 34 35
to
}
Despite the one optim ization we made, this algorithm is still O ( N2 ) , where N is the length of the array. Optimal Solution
What we're trying to do is find a subarray where the count of letters equals the count of num bers. What if we just started from the begi nni ng, cou nti ng the number of letters and numbers? #a #1
a
a
a
a
2 0
3 0
4
4
1
a
1 0
1
4
s
0
1
2
2
1
1
s
s 4
3
a
a
7
8
9
1
a
a
a
7
a
6 4
9 10 11 12 1 3 14
4
5
5
5
6
6
6
6
6
1
a
a
a
6
Certai nly, whenever the number of letters equals the number of num bers, we can say that from i ndex 0 to that index is an "equal" suba rray. That will only tell us equal suba rrays that sta rt at index 0. How ca n we identify a l l equal subarrays? Let's pictu re this. Su ppose we inserted an equal suba rray (like a 11ala) after an array like a l a a a l . How wou ld that im pact the cou nts? a 1 a a a 1
a 1 1 a 1 a
1
#a 1 1 2 3 4 4 I s s s 6 6
#1 0 1 1 1 1 2 I 2 3 4 4 s s
Study the numbers before the suba rray (4, 2) and the end (7, 5). You might notice that, while the va lues aren't the same, the differences are: 4 2 7 5 . This makes sense. Since they've added the same number of letters and num bers, they should maintai n the same difference. -
I
=
-
Observe that when the difference is the same, the suba rray starts one after the initial matching index and continues through the final match ing index. This explains line 1 0 in the code below.
Let's update the earlier array with the differences. #a #1
a
a
1 0 1
2 0 2
ii!
3 0 3
4
1
a
4
4
5
0
1 3
2 2
2 3
1
a
4
1
5
3 2
1
1
ii!
4
7 5
8 5
9 5
3
2
3
4
a
a
5 4
6 4
1
2
7
a
1
a
ii!
a
a
6 4
6 5
6 6
7
a
9 10 11 12 1 3 14 6
3
6
6
8
Whenever we retu rn the same difference, then we know we have found an equal subarray. To find the biggest suba rray, we just have to find the two ind ices fa rthest apart with the same value. 10 do so, we use a
hash table to store the first time we see a particu lard ifference.Then, each time we see the same difference, we see if this suba rray (from first occu rrence of this index to current index) is bigger than the cu rrent max. If so, we update the max. 1 cha r [ ] findlongestSuba rray (cha r [ ] a rray) { 2
/ * Compute deltas between count of numbe rs a nd count of letters . */ int [ ] deltas computeDeltaArray ( a r ray ) ;
3 4
=
S 6 7
/ * F i nd pa i r in deltas wit h matching va lues and la rgest spa n . * / i nt [ ] match = find LongestMatch ( deltas ) ;
8 9
10
11 12
}
/ * Return t he subarray . Note t hat it starts one * after* t he initial occ urence of * t h i s delta . * / ret u r n extract ( a rray , ma t c h [ 0 ] + 1, m a t c h [ l ] ) ;
C ra cki n gTh eCod i n g l n terv i ew.co m j 6t h Edition
537
Solutions to Chapter 1 7 13
14 15
16 17 18
19
20
21 22
23 24 25 26
27 28
29
30 31
32 33
34
35
36
37 38
39 40
41 42
43
44
45
46 47
48
49 50
I Hard
I * Compute the d ifference between the number of letters and numbers between the * beginning of the a rray a nd each index . * I
i nt [ ] computeDeltaArray ( ch a r [ ] a r r a y ) { int [ ] deltas = n ew int [ a rray . length ] ; int delta = 0 ; for ( int i = 0; i < a rray . lengt h ; i++ ) { if ( C h a racter . is letter ( a rray [ i ] ) ) { delta++ ; } else i f (Cha ra cte r . i s Digit ( a rray [ i ] ) ) { delta - - ; } delta s [ i ] = delta ; } return delta s ; } I * Find the matching p a i r of values in the deltas array with the la rgest * d iffe rence i n indices . * I
int [ ] find longestMatch ( int [ ] delta s ) { HashMap< Integer, Integer > map = new HashMap ( ) ; map . put (0, 1); i nt [ ] max = new int [ 2 ] ; for ( int i = 0 ; i < delt as . lengt h ; i++ ) { if ( ! map . cont a i n s Key ( deltas [ i ] ) ) { ma p . put ( deltas [ i ] , i ) ; } else { int match map . get ( deltas [ i ] ) ; int distance = i - matc h ; int longest = max [ l ] - max [ 0 ] ; if ( d istance > longe s t ) { max [ l ] i; max [ 0 ] mat c h ; } } } return max; }
51
-
=
� =
cha r [ ] extract ( cha r ( ] a rray, int start, int end ) { I * same * I }
This solution takes 0 ( N ) time, where N is size of the array. 1 7.6
Count of 2s: Write a
method to count the number of 2s between 0 and n. pg 1 86
SOLUTION
Our first approach to this problem can be-and proba bly should be-a brute force solution. Remember that i nterviewers want to see how you're approaching a problem. Offering a brute force solution is a great way to start. 1 I * Counts the number of ' 2 ' d igits between 0 and n * I 2 int numberOf2s i nRa nge ( int n ) { int count = 0 ; 3 4 for ( int i = 2 ; i < = n ; i++ ) { I I Might a s well start at 2 5 count += numbe r0f2s ( i ) ;
538
Cracking the Coding Interview, 6th Ed ition
Solutions to Chapter 1 7 6
9 10 11 12 13 14 15 16 17 18 19
H a rd
} ret urn count ;
7
8
I
} / * Counts the number of ' 2 ' digits in a single number * / i n t number0f2s ( int n ) { int count = 0 ; while (n > 0) { if ( n % 1 0 = = 2 ) { count++;
} n
= n I 10; } return count ;
20 } The only interesting part i s that it's probably cleaner to separate out numb e r0f2 s into a sepa rate method. This demonstrates an eye for code clea nli ness.
Improved Solution
Rather than looking at the problem by ra nges of num bers, we ca n look at the p roblem digit by dig it. Pictu re a sequence of num bers: 0 10 20
1 11 21
2 12 22
3 13 23
4 14 24
5 15 25
6 16 26
7 17 27
8
9 19 29
18 28
1 1 0 1 1 1 1 1 2 1 1 3 114 1 1 5 1 1 6 1 1 7 1 1 8 1 19
We know that roughly one tenth of the time, the last digit will be a 2 since it happens once i n a ny sequence of ten num bers. In fact, a ny digit is a 2 roughly one tenth of the time. We say "roughly" because there are (very common) boundary conditions. For example, between 1 and 1 00, the 1 O's digit is a 2 exactly ,Y10 •h of the time. However, between 1 and 37, the 1 O's digit is a 2 much more than 1 /1 o•h of the time. We can work out what exactly the ratio is by looking at the three cases individually: digit < 2 , d igit 2, and d igit > 2.
==
Case dig it < 2
Consider the va lue x = 6 1 5 2 3 and d = 3, and observe that x [ d ] 1 (that is, the dth digit of x is 1 ) There are 2s at the 3rd digit in the ra nges 2000 - 2999, 1 2000 - 1 2999, 2 2000 - 2 2999, 32000 32999, 42000 - 42999, and 5 2 000 - 5 2999. We will not yet have hit the ra nge 62000 - 62999, so there are 6000 2s total in the 3rd digit. This is the same amount as if we were just cou nting all the 2s in the 3 rd digit between 1 and 60000. =
.
In other words, we ca n round down to the nea rest 10d+1, and then divide by 1 0, to com pute the n u m ber of 2s in the dth dig it. if x [ d ] < 2 : c ount 2 s i n R a ngeAt Digit ( x d ) let y round down t o nearest 10d+i ,
=
return
y
I 10
CrackingTheCod ingl nterview.com I 6th Edition
539
Solutio ns to Chapter 1 7
I Hard
Cose digit > 2
Now, let's look at the case where dth digit of x is greater than 2 (x [ d ] > 2 ) . We can apply almost the exact same logic to see that there are the same number of 2s in the 3rd digit in the ra nge 0 - 6 3 5 2 5 as there as in the ra nge 0 - 70000. So, rather than rounding down, we round up. if x [ d ] > 2 : c o un t 2 s i n R an g eAtDigit ( x , d ) let y round up to nearest 10•>+1 return y / 10 =
=
Cose digit = 2
The final case may be the trickiest, but it follows from the earlier logic. Consider x = 6 2 5 2 3 and d = 3. We know that there a re the same ra nges of 2s from before (that is, the ranges 2000 - 2 999, 1 2000 - 1 2999, ... , 5 2 000 - 5 2 999 ). How many appear in the 3rd digit in the final, pa rtial ra nge from 62000 - 6 2 5 2 3 ? We l l, that should b e pretty easy. It's j ust 5 24 (62000 , 6200 1 , . . . , 6 2 5 2 3). if x [ d ] 2 : c o un t2s i n R an g eAtDigit ( x , d ) = l et y round down to nea rest 10"•1 l et z right s ide of x ( i . e . , x % 10ct ) return y I 10 + z + 1 =
=
=
Now, a l l you need is to iterate through each digit in the number. Implementing this code is reasona bly stra ig htforwa rd. int count2s inRa ngeAtDigit ( int number, int d ) { 1
2
=
int powerOf10 ( int) Math . pow ( 10, d ) ; int nextPowerOf10 = power0f10 * 10; int right number % power0f10;
3 4
=
5
6 7
=
int roundDown number - number % nextPowerOfl0; int roundup roundDown + nextPowerOf10;
� 9
int digit if ( d igit return } e l s e if return } else { ret urn
10 11
12
13
14 15
16 17
18
}
=
( number I powe r0fl0) % 10;
< 2) { // roundDown (d igit = = roundDown
if t he d igit in s pot d igit is I 10; 2) { I 10 + right + 1 ;
roundup / 10;
}
19
int count2sl nRa nge ( int number ) { int c o un t 0; 21 int len String . va l ueOf ( n umbe r ) . lengt h ( ) ; 22 for ( int d igit 0 ; d igit < len; d igit++ ) { 23 count += count2slnRa ngeAtDigit ( number, digit ) ; 24 } 25 return count ; 20
=
=
=
26
}
This question req uires very ca refu l testing. Ma ke sure to generate a list of test cases, and to work through each of them.
540
Cracking t h e Coding I nterview, 6th Edition
Solutions to 1 7.7
Chapter 1 7
I
Hard
1 0,000 most common baby names and their frequencies (the number of babies with that name). The only problem with this is that some names have multiple spellings. For example, "John" and "Jon" a re essentially the same name but would be listed sepa rately in the list. Given two lists, one of names/frequencies and the other of pairs of eq u ivalent names, write an algorithm to print a new list of the true frequency of each name. Note that if John and Jon are synonyms, and Jon and Johnny are synonyms, then John and Johnny are synonyms. (It is both transitive and symmetric.) In the final list, any name can be used as the "real" name.
Baby Names: Each year, the government releases a list of the
EXAMPLE In put: Names: John (1 5), Jon (1 2). Chris (1 3), Kris (4), Christopher ( 1 9) Synonyms: (Jon, John), (John, Johnny), (Chris, Kris), (Chris, Christopher) Output: John (27), Kris (36)
pg 1 87
SOLUTION
Let's start off with a good example. We want an example with some names with multiple synonyms and some with none. Additional ly, we want the synonym list to be diverse i n which name is on the left side and which is on the rig ht. For example, we would n't want Johnny to always be the name on the left side as we're creating the group of (John, Jonathan, Jon, and Johnny). This list should work fairly well.
C a r l e t on
Carrie
Ca rlton
5
The final list should be something like: John (33), Ka ri (8), Davis(2), Ca rleton ( 1 0). Solution # 1
Let's assume o u r ba by names list i s given to us as a hash ta ble. ( I f not, it's easy enough t o build one.) We can start reading pairs in from the synonyms list. As we read the pair (Jonathan, John), we can merge the counts for Jonathan and John together. We'll need to remember, though, that we saw this pair, because, in the future, we could discover that Jonathan is equ ivalent to something else. We can use a hash table (Ll) that maps from a name to its "true" name. We'l l also need to know, given a "true" name, all the names equiva lent to it. This will be stored in a hash table L 2. Note that L 2 acts as a reverse lookup of L l . READ ( J onatha n , J o h n )
Cracki ngTheCodi ngl nterview.com j 6th Edition
I 541
Solutions to Chapter 1 7
I Hard
Ll . ADD J onathan - > John L2 . ADD John -> J onathan READ ( J o n , Johnny) L l . ADD Jon - > Johnny L2 . ADD Johnny - > Jon READ ( J oh n ny, J o h n ) L l . ADD Johnny - > J o h n L l . UPDATE Jon - > J o h n L 2 . UPDATE John - > Jonatha n , J ohnny, J o n
I f w e later fi nd that J o h n is eq uivalent to, say, Jonny, we'll need t o look up the names in L l a n d L 2 and merge together all the names that a re equivalent to them. This will work, but it's u n necessa rily complicated to keep track of these two lists. I nstead, we ca n think of these names as"eq uivalenceclasses:'When we fi nd a pair (Jonathan, John), we put these in the same set (or eq uivalence classes). Each name maps to its equivalence class. All items in the set map to the same i nstance of the set. If we need to merge two sets, then we copy one set i nto the other and update the hash ta ble to point to the new set. READ ( J onatha n , John) CREAT E Setl = Jonatha n , John Li . ADD Jonathan -> Seti Li . ADD John -> Set i READ ( J o n , Johnny) CREATE Set2 J o n , Johnny L l . ADD Jon - > Set2 Li . ADD Johnny - > Set2 READ ( J oh n ny , J o h n ) COPY Set2 i n t o Seti . Set l = J onatha n , John, Jon, Johnny L i . UPDATE Jon - > Seti Li . UPDATE Johnny - > Seti =
In the last step above, we iterated through a l l items in S et 2 and u pdated the reference to point to S e t l . A s w e do this, w e keep track of t h e total freq uency o f names. 1 2
3
4 5
Has hMa p < String, Integer > trulyMostPopular ( Ha s hMap names , String [ ] [ ] syn onyms ) { /* Pa rse l i s t and initial ize equiva lence classes . * / HashMap groups = const ructGroups ( names ) ;
6
/* Merge equivale nce classes together . * / mergeClas s e s (group s , synonyms ) ;
7 8
9
/ * Convert back to ha s h ma p . * / ret urn convert ToMa p ( groups ) ; 10 11 } 12 1 3 / * Th is is t h e core o f t h e a lgorithm . Read th rough each pa i r . Merge t h e i r * equivalence classes and update the ma pping of the secondary cla s s to point to 14 * t h e f i rs t s et . * / 15 16 void mergeC l a s s e s ( Has hMa p group s , String [ ] [ ] synonyms ) { for ( St r i ng [ ] e n t ry : synonyms ) { 17 18 String namei entry [ 0 ] ; 19 St ring name2 entry [ i ] ; 20 NameSet setl groups . get ( namel ) ;
542
Cracking the Coding Interview, 6th Edition
Solutions to Cha pter 21
I
Hard
NameSet set2 = groups . get ( name2 ) ; if ( s etl ! = set 2 ) { / * Always merge the smaller set into the bigger one . */ NameSet smaller = set2 . s ize ( ) < setl . s i ze ( ) ? set2 : set l ; NameSet bigge r set2 . s ize ( ) < setl . s i ze ( ) ? setl : set 2 ;
22 23
24
25 26
=
27
/ * Merge lists */ Set otherNames smaller . getNames ( ) ; int freq uency smaller . get F requency ( ) ; bigger . copyNamesWit h F requency (otherName s , frequency ) ;
28
=
29
=
30 31
32
33
34
35
36
}
37
/ * Update mapping */ for ( St ring name : otherName s ) { groups . put ( name, b igger ) ; }
}
38
}
40
/*
39
17
Read t h rough ( name, freq uency ) pairs a nd init i a l i ze a mapping of names to * NameSet s (equivalence classes ) . * / 4 2 H a s hMap constructGroups ( Ha s hMap name s ) { 43 HashMap groups = new HashMap ( ) ; for ( Entry ent ry : names . e nt rySet ( ) ) { 44 45 String name e nt ry . getKey ( ) ; 46 int freq uency entry . getValue ( ) ; 47 NameSet group = new NameSet ( name , freq uency ) ; 48 groups . put ( name, grou p ) ; 41
=
=
49
50 51
52
53 54 55
56
57
58 59
60 61
62
63
64 65 6i
67
68 69
7t'J
71 72
73
74
75 76
}
ret urn groups ; } HashMap convertToMa p ( Ha s hMap groups ) { H a s hMap list new HashMap ( ) ; for (NameSet group : groups . values ( ) ) { list . put ( grou p . get RootName ( ) , grou p . get F requency ( ) ) ; =
}
return list ; }
public c l a s s NameSet { private Set names private int frequency = 0; private String rootName ;
new Has hSet ( ) ;
public NameSet ( St ring name , int freq ) { names . add ( name ) ; frequency freq ; rootName name ; =
=
}
public void copyNamesWit h F requency ( Set < St r ing> more, int freq ) { names . addAl l ( more ) ; frequency + = freq ; }
CrackingTheCodingl nterview.corn I 6th Edition
543
Solutions to Chapter 1 7 77
78 79 80 81
}
I
Hard
public Set < String> getNames ( ) { return names ; } public St ring getRootName ( ) { return rootName; } public i nt get Frequency( ) { return frequency ; } public int s ize ( ) { return names . si ze ( ) ; }
The runtime of the algorithm is a bit tricky to figure out. One way to think about it is to think about what the worst case is. For this algorithm, the worst case is where all names are equivalent-and we have to constantly merge sets together. Also, for the worst case, the merging should come in the worst possible way: repeated pairwise merging of sets. Each merging requires copying the set's elements into an existing set and updating the pointers from those items. It's slowest when the sets are larger. If you notice the parallel with merge sort (where you have to merge sing le-element arrays into two-element arrays, and then two-element arrays into four-element arrays, until finally having a full array), you might guess it's O ( N log N ) . That is correct. If you don't notice that parallel, here's another way to think a bout it. •
•
•
Imagine we had the names (a, b, c, d, , z ). In our worst case, we'd first pair up the items into equivalence , (w, x, y, classes: (a, b), (c, d), (e , f) , . , (y, z ) . Then, we'd merge pairs of those: (a , b, c, d), (e, f, g, h), z). We'd continue doing this until we wind u p with just one class. .
.
•
.
•
At each "sweep" through the list where we merge sets together, half of the items get moved into a new set. This ta kes O ( N ) work per sweep. (There are fewer sets to merge, but each set has grown la rger.) How many sweeps do we do? At each sweep, we have half as many sets as we did before. Therefore, we do O ( log N) sweeps. Since we're doing 0 ( log N) sweeps and O ( N ) work per sweep, the total runtime is O ( N log N ) . This is pretty good, but let's see if we can make it even faster. Optimized Solution
To optimize the old solution, we should think about what exactly makes it slow. Essentially, it's the merging and updating of pointers. So what if we just didn't do that? What if we marked that there was an equivalence relationship between two names, but didn't actually do anything with the information yet? In this case, we'd be building essentially a g raph. Kar i
Carrie
Now what? Visually, it seems easy enough. Each component is a n equiva lent set of names. We just need to group the names by their component, sum up their frequencies, and return a list with one a rbitrarily chosen name from each group. 544
I
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 1 7
I
H a rd
I n practice, how does this work? We could pick a name and do a depth-fi rst (or breadth-first) search to sum the frequencies of all the names i n one component. We would have to make sure that we hit each compo nent exactly once. That's easy enough to achieve: mark a node as visited after it's discovered in the graph search, and only start the search for nodes where v i s ited is false. 1 HashMap t r u lyMostPopul a r ( Ha s hMap names , 2 String ( ] ( ] synonyms ) { 3 I * C reate data . * I 4 G ra p h g ra p h = constructGraph ( names ) ; s connectEdges ( graph, s y no nym s ) ; 6
/*
7 8
9
10 } 11 12 13
14
15
16 17
18
19
20
21
22 23
F i n d component s . * I HashMap root Names return rootName s ;
getTrueFrequencies (graph ) ;
/ * Add all names to graph a s nodes . */ Graph con st ructGraph ( Ha s hMap names ) { Graph graph new Graph ( ) ; for ( E nt ry entry : names . entrySet ( ) ) { String name = entry . getKey ( ) ; int frequency = ent ry . getValue ( ) ; graph . c reateNod e ( name , frequency ) ; } return graph; } =
/ * connect s y nonymous s pellings . * I void connect Edges (Graph graph, String [ ] [ ] synonyms ) { 25 for ( String [ ] entry : synonyms ) { 26 St ring namel = entry [ 0 ] ; 27 String name2 entry [ l ] ; graph . addEdge ( namel , n a me2 ) ; 28 24
�
29
30
31
}
}
32 / * Do DFS of each component . If a node has been vis ited before, then it s component * has a l ready been computed . * I 3 4 Has hMap getTrue F requencies (Graph graph ) { 35 Has hMap rootNames = new Ha s hMap ( ) ; for (GraphNode node : graph . getNodes ( ) ) { 36 37 if ( ! node . isVi s ited ( ) ) { I I Already vis ited this component i nt fre q ue nc y getComponent F requency ( node ) ; 38 39 String name node . getName ( ) ; 40 rootNames . put ( name, frequency ) ; 41 } 33
=
=
42 43
44 45
46
47 48
49
50
51
52
}
return rootNames ; } I * Do depth- first search to find the tota l frequency of this component , and mark * each node as vis ited . */ i nt getComponent F requency (Gra phNode nod e ) {
if ( node . isVis ited ( ) ) return 0 ; / / Already vis ited
node . s et isVis ited (true ) ; int sum node . get F requency ( ) ; =
CrackingTheCodinglnterview.com I 6th Edition
545
Solutions to Chapter 1 7 53
I Hard
for ( Gra phNode c h i ld : node . getNeighbor s ( ) ) { sum += getComponentFrequency ( c hild ) ; } return s u m ;
54
55
56 57 } 58 59 /* Code for GraphNode and Graph is fa irl y self- expla n atory, but can be found in * the downloadable code solutions . */ 60
To analyze the efficiency, we can think about the efficiency of each part of the algorithm.
Read ing in the data i s linear with respect to the size of the data, so it takes O ( B + P ) time, where B is the number of baby names and P is the number of pairs of synonyms. This is because we only do a constant amount of work per piece of i n put data. To com pute
•
the frequencies, each edge gets "touched" exactly once across all of the graph searches and each node gets touched exactly once to check if it's been visited. The time of this part is O ( B + P ) .
Therefore, the total time of the algorithm is O ( B must at least read in the B + P pieces of data. 1 7.8
+
P ) . We know we cannot do better than this si nce we
Circus Tower: A circus is desig ning a tower routine consisting of people standing atop one another's shoulders. For practical and aesthetic reasons, each person must be both shorter and lig hter than the person below him or her. Given the heights and weights of each person in the circus, write a method to com pute the largest possible numbe r of people in such a tower. pg 187
SOLUTION
When we cut out all the "fluff" to this problem, we can u ndersta nd that the problem is really the following. We have a list of pairs of items. Find the longest sequence such that both the first and second items are in non decreasin g order.
One thing we might fi rst try is sorting the items on an attribute. This is useful actually, but it won't get us all the way there. By sorti ng the items by height. we have a relative order the items must a ppear in. We sti l l need to find the longest increasing subsequence of weight though. Solution 1 : Recursive
One approach is to essentially try all possibilities. After sorting by heig ht, we iterate through the a rray. At each element. we branch into two choices: add this element to the subsequence (if it's va lid) or do not.
1
ArrayL i s t < Ht wt > longestincrea s ingSeq (Arra y L i s t items ) { Collect i o ns . sort ( items ) ; return bestSeqAtlndex( items , new Arrayl i s t < Htwt > ( ) , 0 ) ; }
2 3
4 5
6 7
Arrayl ist bestSeqAt lndex(Arraylist array, Arraylist sequence, i n t index) { if ( i ndex >= array . s ize ( ) ) return sequence;
8
9
10
11
546
HtWt value = array . get ( index ) ;
Cra c ki n g the Cod i n g Interview, 6th Edition
Solution5 12
=
14
is
16 17
=
J
18
Array List
19
20
21
bestWithout = best SeqAtlnde x ( a rray, sequen c e , index
if ( bestWit h == null I I bestWithout . s i ze ( ) return bestWithout ; } else { return bestwith ; }
22 23
24
>
}
29
boolean c a nAppend (Array list solut i o n , HtWt value ) { if ( solution == null ) ret urn false ; if ( solution . s i ze ( ) == 0 ) ret urn t rue;
30 31
+
l) ;
bestWith . s ize ( ) ) {
26
27 28
! Hard
Arraylist bestWith = null ; if ( ca nAppend ( sequence, value ) ) { (ArrayL ist ) sequence . clone ( ) ; ArrayLis t s equenceWi th sequenceWitn . aaa (value) ; bestSeqAti ndex(array , sequenceWi th, index + 1 ) ; bes tWith
13
25
to Cha pter 1 7
HtWt last = solution . get ( solution . s i ze ( ) - l ) ; 32 return last . is Before (value ) ; 33 34 } 35 36
Arraylist max (Array L i s t < HtWt > seql, ArrayList seq2 ) { if ( s eql == n u l l ) { ret urn seq2 ; 38 39 } else if ( seq 2 = = null ) { return seql ; 40 41 } 42 ret urn seql . si ze ( ) > seq2 . s ize ( ) ? seql seq2; .C.3 } 37
44 45
public c l a s s HtWt implements Compara ble { private int height ; private int weight ; public HtWt ( int h , int w ) { height h ; weight 48
46 47
49
50
51 52 53 54
55
56
57 58 59 60
61 62
63 64 GS
66
67
=
w; }
public int compa reTo ( HtWt second ) { if ( t h i s . height ! = second . height ) { return ( ( Integer ) t h i s . height ) . compa reTo ( second . height ) ; } else { return ( ( I ntege r ) t h i s . weight ) . compareTo ( second . weight ) ; } } /*
Returns t rue if "t his" s hould be lined up before "other" . Note t hat it' s pos s i ble that t h i s . is Before (other) and other . i s Before ( t h i s ) a re both false . * This is d iffe rent from the compareTo method , where if a < b t hen b > a . * / public boolean i s Befor e ( HtWt othe r ) { if ( height < other . height && weight < other . weight ) { ret urn true ; } else { ret urn false ; } } *
CrackingTheCodinglnterview.com I 6th Edition
547
Solutions to Chapter 1 7
I Hard
68 } lhis algorithm will ta ke 0 (2") ti m e We can o pti m ize it using memoization (that is, c a c h i n g the best sequences). .
There's a cleaner way to do this though. �olution #2: Iterative
Imagine we had the longest subsequence that terminates with each element, A [ 0 ] through A [ 3 ] . Could we use this to fi nd the longest subs equ e n ce that terminates with A [ 4 ] ? Array : 13, 14, Longest ( ending Longest ( e nding Longest (ending Longest ( e n d i ng Longest ( ending
10, 1 1 , 12 with A [ 0 ] ) : with A [ l ] ) : with A [ 2 ] ) : with A [ 3 ] ) : wit h A [ 4] ) :
13 13 , 1 4 10 10, 1 1 10, 11, 12
Sure. We j u st append A[ 4 ] on to the longest s u bs equ ence that it can be a ppended to. This is now fairly stra ightforward to implement. 1
ArrayList longest i n c rea s i ngSeq (ArrayList a r ray ) { Collections . sort ( a rray ) ;
2 3
4
ArrayList solutions ArrayList bestSequence = null ;
5
6 7
8 9
new ArrayList ( ) ;
/ * F i nd t he longest subsequence t hat terminates with each element . Track the * longest overall subsequence as we go . * / for ( i nt i 0 ; i < array . s i z e ( ) ; i++ ) { Arrayl ist longestAtindex bestSeqAt i ndex ( a r ray, solutions , i ) ; solut io ns . add ( i , longestAtindex ) ; bestSequence max ( bestSequence, longestAtindex ) ; } =
10 11 12 13
14
=
=
15 ret u r n bestSeq uence; 16 } 17 18 / * F i nd the longest subsequence which t e rminates with this element . */ 1 9 ArrayList bestSeqAt i ndex (Array List a r ray, 20 ArrayList solut ion s , int i ndex ) { 21 HtWt value a r ray . get ( index ) ; 22 23 ArrayList bestSequence new ArrayList ( ) ; 24 25 /* F i nd the longest subsequence that we can append this element to . * / 26 for ( int i = 0 ; i < index; i ++ ) { 27 ArrayL ist solution = solutions . get ( i ) ; 28 if ( c a nAppend ( solution , value ) ) { 29 bestSequence max ( solution , bestSequence ) ; 30 } 31 } =
=
=
32 33
34 35 36
548
/* Append element . */ ArrayList best best . add ( value ) ;
(ArrayList ) bestSequence . c lone ( ) ;
Cracking the Coding I nterview, 6th Edition
Solutions to Chapter t 7 37
I
H a rd
return best;
38
}
This algorithm operates in O ( n 2 ) time. An O ( n log ( n ) ) algorithm does exist but it is considerably more complicated and it is highly unlikely that you would derive this in an interview-even with some help. However, if you a re interested in exploring this solution, a quick internet sea rch will turn up a number of explanations of this solution. ,
1 7 .9
an algorithm to find the kth number such that the only prime factors are 3, 5, and 7. Note that 3, 5, and 7 do not have to be factors, but it should not have any other prime factors. For example, the first several m ultiples would be (in order) 1 , 3, 5, 7, 9, 1 5, 2 1 .
Kth Multiple: Design
pg 7 87
SOLUTION
Let's fi rst understa nd what this problem is asking for. It's asking for the kth smallest number that is in the form 3" * Sb * 7c. Let's start with a brute force way of finding this. Brute Force
We know that biggest this kth number cou ld be is 3k * Sk * Jk. So, the "stu pid" way of doing this is to compute 3" * Sb * 7c for a l l va lues of a, b, and c between 0 and k. We can throw them all into a l ist, sort the list, and then pick the kth smal lest value. 1
4
int getKthMagicNumbe r ( int k) { Array List < I nteger> pos s i b i l ities Collections . so rt ( poss ibilit ies ) ; return pos s i b ilities . get ( k ) ;
5
}
2 3
6
a llPos sibleKFactors ( k ) ;
7
ArrayL i s t < I ntege r> a l lPoss ibleK F a ctors ( int k ) { Array l i s t < I ntege r > values = new Array list< Integer> ( ) ; 9 for ( int a = 0; a < = k; a++) { I I loop 3 int powA = ( int ) Mat h . pow ( 3 , a ) ; 10 11 for ( int b = 0; b < = k ; b++ ) { II loop 5 12 int powB = ( int ) Mat h . pow ( 5 , b ) ; 13 for ( int c = 0; c append x*3, x*5 and x*7 to Q3, QS, and Q7. Remove x from Q3. QS -> a ppend x*5 and x*7 to QS and Q7. Remove x from QS.
Q7 -> only a ppend x * 7 to Q7. Remove x from Q7. 6.
Repeat steps 4 - 6 until we've found k e l e m e nts .
The code below im plements this algorithm. 1
2
3 4 5
6 7
8 9
10
int get KthMagicNumber( int if ( k < 0 ) { ret urn 0; } i nt val = 0; Queue< I nteger> queue3 Queue< Integer> queues Queue< Intege r > queue7 q ueue3 . add ( 1 ) ;
11 13
14
=
:1 5
li 17
18
19
2Q
21 22 23
24 25
26
27 28 3�
new Linked l i st < I nteger > ( ) ; new Linked l i s t < I ntege r > ( ) ; new L inked L i s t < I nteger > ( ) ;
I* I nclude 0th t hrough kt h iteration * I for ( int i = 0; i < = k ; i++ ) { Intege r . MAX_VALU E ; i nt v3 queue3 . s i ze ( ) > 0 ? q ueue3 . peek ( ) Integer . MAX_VALUE ; int vs queues . s i ze ( ) > 0 ? queues . peek ( ) I ntege r . MAX_VALU E ; i nt v7 = queue7 . s ize ( ) > 0 ? q ueue7 . peek ( ) val = Mat h . mi n ( v 3 , Mat h . mi n ( v S , v7) ) ; if ( va l == v 3 ) { I I enqueue into q ueue 3 , S and 7 queue3 . remove ( ) ; queue3 . add ( 3 * val ) ; queues . add ( s * val ) ; } else if ( va l == vs ) { II enqueue i nto queue s and 7 queues . remove ( ) ; q ueueS . add ( S * val ) ; } else if ( v a l == v7) { II enqueue into Q7 queue7 . remove ( ) ; } queue7 . add ( 7 * va l ) ; II Always enqueue into Q7 } ret urn va l ;
12
29
k) {
}
When you get this q uestion, do your best to solve it-even though it's really d iffi cult. You can sta rt with a brute force a pproach (challenging, but not quite as tricky), and then you can sta rt trying to optimize it. Or, try to find a pattern in the numbers. Chances are that you r interviewer will hel p you along when you get stuck. W hatever you do, don't give up! Think out loud, wonder out loud, and explain you r thought process. Your interviewer will probably j ump in to guide you. Remember, perfection on this problem is not e xp e ct e d. You r performance is evalu ated in compa rison to other candidates. Everyone struggles on a tricky problem.
C r a c k i n gTheCodin gl n te rview.com I 6th E d ition
553
Solutions to Cha pter 1 7 1 7 .1 O
I Hard
Majority Element: A majority element is a n element that makes u p more than half of the items in Given a positive integers array, find the majority element. If there is no majority element. return - 1 . Do this in 0 ( N ) time and O ( 1 ) space.
a n array. Input: Output:
1 2 5 s
9
5 9
5 5 5
pg 1 87 SOLUTION
Let's start off with an example: 3 1 7 1 3 7 3 7 1 7 7
One thing we can notice here i s that if the majority element (in this case 7) appea rs less often in the begin ning, it must appear much more often toward the end. That's a good observation to make. This i nterview question specifically requires us to do this in 0 ( N ) time and 0 ( 1 ) space. Nonetheless, some times it can be useful to relax one of those requirements and develop an algorithm. Let's try relaxing the time requirement but staying firm on the 0( 1) space requirement. Solution #1 (Slow)
One simple way to do this is to just iterate through the array and check each element for whether it's the majority element. This ta kes O ( N 2 ) time and 0 ( 1 ) space. 1
2
3
4 5
6
7
8
int fi ndMa jorityE lement ( i nt [ ] array ) { for ( int x : a rray) { if ( va lidate ( a rray, x ) ) { return x ; } } return - 1 ; }
9 10 boolean va lidate ( i nt [ ] array, int majority) { 11 int count 0; 12 for ( int n : array ) { if ( n majority) { 13 14 count++ ; 15 } 16 } 17 18 return count > a rr ay l e n gt h I 2 ; 19 } =
==
.
This does not fit the time requirements of the problem, but it is potentially a starting poi nt. We can think about optimizing this. Solution #2 (Optimal)
Let's think about what that a lgorithm did on a particular exam ple. Is there anything we can get rid of?
I n the very first validation pass, we select 3 and validate it as the majority element. Several elements later, we've stil l counted just one 3 and several non-3 elements. Do we need to continue checking for 3?
554
Cracking the Coding I nterview, 6th Edition
Solutions to Chapter 1 7
!
Hard
On one hand, yes. 3 could redeem itself and be the majority element. if there a re a bunch of 3s later in the array. On the other hand, not really. If 3 does redeem itself, then we'll encou nter those 3s later on, in a subsequent val idation step. We could terminate this validate ( 3 ) step. That logic is fine for the fi rst element. but what a bout the next one? We would immediately terminate
validate ( 1 ) , val idate ( ? ), and so on.
Since the logic was okay for the first element. what if we treated all subsequent elements like they're the first element of some new suba rray? This would mean that we start validate ( a rray [ l ] ) at index 1 , validat e ( a r ray [ 2 ] ) at index 2, and so on. What wou ld this look like? val i d a t e ( 3 ) sees 3 - > c o untYe s = 1, countNo sees 1 - > countYes = 1 , c ountNo
0 1 T E RMINAT E . 3 is not majo rity thus fa r . =
=
validate ( ! )
sees 1 - > countYes = 0 , countNo = sees 7 - > cou ntYes 1 , countNo = TE RMINATE . 1 i s not majority t h u s va l i d a t e ( 7 ) sees 7 - > countYes = 1 , countNo = sees 1 - > countYes = 1 , countNo = T E RMINATE . 7 is not majority thus =
validate ( ! )
1, c ountNo sees 1 - > countYes 2, countNo sees 1 - > countYes 2, c ountNo sees 7 - > countYes 2, countNo sees 7 - > c o untY e s TE RMINATE . 1 i s not majority thus =
validate ( ! )
0 1 fa r . 0 1 fa r . 0 0 1
1 far .
sees 1 - > countYes = 1 , countNo = 0 sees 7 - > countYes = 1, countNo = 1 TE RMINATE . 1 is not majority t h u s fa r .
validate ( ? ) sees sees sees sees sees sees
7 7 3 7 7 7
1, countYes countYes 2, 2, countYes countYes = 3 , countYes = 4, - > countYes = 5 ,
-> -> -> -> ->
countNo c ountNo c ountNo c ountNo c ountNo c ountNo
0 0 1 1 1 1
Do we know at this point that 7 is the majority element? Not necessa rily. We have eliminated everything before that 7, and everything after it. But there could be no majority element. A q u i ck v a l i d a t e ( 7 ) pass that starts from the beg inning can confi rm if 7 is actually the majority element. This validate step will be O ( N ) time, which is a lso our Best Conceivable Runtime. Therefore, this final v a l idate ste p won't impact our total runtime. This is pretty good, but let's see if we can make this a bit faster. We should notice that some elements are being "inspected" repeatedly. Can we get rid of this? Look at the first va l i d a t e ( 3 ) . This fails after the
subarray [ 3, 1 ], because 3 was not the majority element. But because v a l i d a te fails the instant an element is not the majority element, it also means nothing else in that subarray was the majority element. By o u r earlier logic, we don't need to call va l id at e ( 1 ) . We know that 1 did not appear more than half the time. If it is the majority element. it'll pop up later. Cracki ngTheCodingl nterview.com I 6th Editi o n
SSS
Solutions to Chapter 1 7
I Hard
Let's try this again and see if it works out. va lid a t e ( 3 ) sees 3 - > c ou n tYe s = 1 , countNo
sees 1 - > countYes = 1 , countNo TERMINATE . 3 is not ma j ority thus s kip 1 va lidate ( 7 ) s e e s 7 > countYes 1 , countNo sees 1 - > countYes 1 , countNo = TERMINATE . 7 is not ma jo rity thus skip 1 validate ( ! ) sees 1 - > countYes = 1 , countNo sees 7 - > countYes = 1 , countNo TERMINATE . 1 is not majority thus skip 7 va lidate ( 7 ) sees 7 - > countYes = 1 , countNo sees 3 - > countYes = 1 , countNo = TERMINATE . 7 is not majo rity thus skip 3 va lid a t e ( 7 ) 1 , countNo sees 7 - > countYes 2 , countNo sees 7 - > countYes 3 , countNo sees 7 - > countYes -
=
=
=
= =
=
0 1 fa r . 0 1
fa r . 0 1 fa r . 0 1 fa r . 0 0 0
Good! We got the right answer. But did we just get lucky? We should pause fo r a moment to think what this algorithm is doing. 1.
2.
We sta rt off with [ 3] and we expand the subarray until 3 is no longer the majority element. We fail at [ 3 , 1 ] . At the moment we fail, the subarray can have no majority element. Then we go to [ 7 ] and expand u ntil [ 7 , 1 ] . Again, we termi nate and nothing could be the majority element in that suba rray.
3. We move to [ 1 ] and expand to [ 1 , 7 ] . We terminate. Nothing there cou ld be the majority element.
4. We go to [ 7 ] and expand to [ 7 , 3 ] . We terminate. Nothing there could be the majority element. 5. We go to [ 7 ] and expand until the end of the array: [ 7 , 7 , 7 ] . We have fo und the majority element
(and now we must validate that). Each time we terminate the v a l idate step, the suba rray has no majority element. This means that there are at least as many non-7s as there a re 7s. Although we're essentially removing this suba rray from the original a rray, the majority element will still be fo und in the rest of the a rray-a nd will still have majority status. Therefo re, at some point, we will discover the majority element. Our algorithm can now be run in two passes: one to find the possi ble majority element and another to vali date it. Rather than using two variables to count (countVes and countNo), we'll just use a single c ount variable that increments and decrements. 1 2 3
4 5
6
7
int findMajorityE lement ( int [ ] a r ray) { i n t c a ndidate = getcand idat e ( a r ray ) ; r et u r n va l idate ( a rray, candidate) ? candidate } int getcandidate ( int [ ] a r ray) { int ma j o r ity 0; =
556
I
Cracking the Coding Interview, 6th Edition
-1;
Solutions to Chapter 1 7 8
int count = 0 ; fo r ( int n : a rray ) { if ( count == 0 ) { / / No majo rity element in previous set . majority n; } if (n == majority) { count++; } else { count - ;
9
10
11
=
12 13
14
15 16
-
17 1e 1�
20 21
22
23 ZlJ.
25
26 27 28 29 30
31
I Hard
} return ma j o rity; } boolean validate ( i nt [ ) a r ray, int majority) { int count = 0; fo r ( int n : a rray ) { if (n == majority) { count++; } } ret urn count > a r ray. lengt h I 2; }
This algorithm runs in O ( N ) time and 0 ( 1 ) space. 1 7 .1 1 Word Distance: You have a large text fi le containing words. Given a ny two words, find the shortest
dista nce (in terms of number of words) between them in the fi le. If the operation will be repeated many times for the same fi le (but different pairs of words), can you optimize your solution? pg 187
SOLUTION
We will assume for this question that it doesn't matter whether wordl or word 2 appears first. This is a ques tion you should ask your interviewer. To solve this problem, we can traverse the file j ust once. We remember throughout our traversal where we've last seen word l and word2, storing the locations in location! and locat ion2. If the cu rre nt locations a re better than our best known location, we upd ate the best locations. The code below implements this algorithm. 1
2 3
4 5
6 7
8
9 1�
11 12
13
LocationPa i r findClosest ( String [ ] word s , St ring word l , St ring word 2 ) { LocationPair best = new LocationPa i r ( - 1 , - 1 ) ; LocationPa i r current = new LocationPa i r ( - 1 , - 1 ) ; for ( i nt i = 0; i < word s . lengt h ; i++ ) { String word = word s [ i ) ; if ( word . equals ( word l ) ) { current . locationl = i ; best . updateWithMi n ( current ) ; } else if ( word . equals ( word 2 ) ) { c u r rent . location2 = i ; best . u pd atewithMi n ( c u rrent ) ; / / If s horter, update values } }
CrackingTheCodinglnterview.com \ 6th Editi on
557
Solutio ns to Chapter 1 7 14
15
16 17
18
19
2e
21
22 23
I Hard
return best ; } public class LocationPa i r { public int locat ionl , locat ion2 ; public LocationPa i r ( int first, int second ) { s et Loc a tio n s ( first , second ) ; } public void setLocation s ( i nt this . locat ion! fi rst ; this . locat ion2 = second ; }
24
25
26
27 28
first,
i nt second ) {
public void setlocations ( LocationPa i r loc ) { set locations ( loc . locationl, loc . location2 ) ; }
29
30
31
32
public int distance ( ) { return Mat h . abs ( locationl - locat ion2 ) ; }
33
34 35
36 37
public boolean isVa l id ( ) { return location! >= 0 && location2 >= 0 ;
38
}
39
40 41
42
43
44
45 }
public void updateWithMin ( LocationPa ir loc ) { if ( ! i sVa lid ( ) I I lac . distance ( ) < d istance ( ) ) { setLocations ( loc ) ; } }
If we need to repeat the operation for other pairs of words, we can create a hash ta ble that maps from each word to the locations where it occurs. We'll only need to read through the list of words once. After that poi nt, we can do a very similar algorithm but just iterate through the locations directly. Consider the fol lowing lists of locations. listA : { 1 , 2 , 9 , 1 5 , 2 5 } list B : {4 , 10, 19}
Pi cture poi nters pA a nd p B that point to the beginning of each list. Our goal is to make pA and p B point to values as close together as possible. The first potential pai r is ( l , 4 ) . What i s the next pair we can fi nd? If we moved pB, then the dista nce would definitely get larger. I f we moved pA, though, we might get a better pair. Let's do that. The second potential pair is ( 2, 4 ) . This is better than the previous pair, so let's record this as the best pair. We move pA again and get ( 9 , 4 ) . This is worse than we had before. Now, since the value at pA is bigger than the one at pB, we move pB. We get ( 9 , 10 ) . Next we get ( 1 5 , 1 0 ) , then ( l S , 1 9 ) , then ( 2 5 , 19 ) . We can im plement this algorithm a s shown below. 1
LocationPai r f i ndClosest ( String wordl, String word2, 558
Cracking the Coding I nterview, 6th Edition
Solutions to Chapter 1 7 2
HashMapliSt locat ions ) { ArrayList < I nteger> locat ion s ! = locations . get (word l ) ; ArrayList locat ions2 = locations . get (word2) ; ret urn findMinDist ancePa i r ( locat ionsl, locations2 ) ;
3
4 5
6 7
8
9
10
11 12 13
14
} LocationPa i r findMinDistancePa i r ( ArrayL i st < I nteger> a rrayl , Arrayl i s t < I nteger> arrayZ) { if ( a rrayl == null I I ar ray2 null I I a rrayl . si ze ( ) == 0 a r ray2 . s i ze ( ) 0) { ret urn n u l l ; } :=
int indexl = int index2 = Locat ionPair LocationPair
16 17
18
19 2� 22
23
24
25 26
27
28
29 32
0; 0; best = new LocationPa i r ( arrayl . get ( 0 ) , array2 . get ( 0 ) ) ; current = new LocationPa i r ( a rrayl . get ( 0 ) , a r ray2 . get ( 0 ) ) ;
while ( indexl < arrayl . s ize ( ) && index2 < a rray2 . s ize ( ) ) { current . set Locations ( a rrayl . get ( indexl ) , a r ray2 . get ( index2 ) ) ; best . updateWithMin ( c urrent ) ; // If shorter, update values if ( c u rrent . locationl < c u r rent . locat ion2 ) { indexl++; } else { index2 ++; } }
21
31
II
:=
15
30
I Hard
return best ; }
33 34
/ * Precomputation . * / t-lashMaplist getWord Locations ( String [ ] wor d s ) { 35 HashMaplist locations new HashMaplist ( ) ; for ( int i 0 ; i < words . lengt h ; i++) { 36 37 locations . put (word s [ i ] , i ) ; 38 } ret urn location s ; 39 40 } =
41 42
43
/* *
HashMa p List is a Ha s hMap that maps from St rings to Arraylist < I nteger> . See appendix for implementation . * /
The precom putati o n step of this alg orithm will take O ( N ) time, where N is the n u m ber of words i n the stri ng. Fi n d i n g the closest pa i r of locations will take O ( A + B) time, where A i s the n u m ber of occurrences of the fi rst word and B is the n u m ber of occu rrences of the second word.
Cracki ngTheCodinglnterview.com I 6th Editi on
559
Solutions to Chapter 1 7
I Hard
1 7. 1 2 BiNode: Consider a
simple data structure ca lled Bi Node, which has pointers to two other nodes. The data structure B iNode could be used to represent both a binary tree (where nod e1 is the left node and node 2 is the right node) or a doubly linked list (where nod e1 is the previous node and nod e2 i s the next node). I m plement a method to convert a binary search tree (im plemented with B i Node) into a dou bly li nked list. The values should be kept i n order and the operation should be performed in place (that is, on the original data structure). pg 7 88
SOLUTION --·-··--·-
-·-
--
.. . . .- ·- ·- · ·-··-· ·--· ----
This seemingly com plex problem can be implemented quite elegantly using recursion. You will need to understand recursion very well to solve it. Picture a simple binary search tree:
The convert method should transform it into the below dou bly linked list: 0 1 2 3 4 5 6 Let's approach this recursively, starting with the root (node 4). We know that the left and right ha lves of the tree form their own "su b-parts" of the linked list (that is, they a ppear consecutively in the linked list). So, if we recursively converted the left and right subtrees to a dou bly linked list, could we build the final linked list from those parts? Yes! We would simply merge the d ifferent parts. The pseudocode looks something like: 1
2
3
4 5
6
B iNode convert ( BiNode node) { BiNode left convert ( node . left ) ; BiNode right = convert ( node . right ) ; merge l i st s ( left , node, right ) ; return left ; II front of left } =
To actually implement the nitty-gritty details of this, we11 need to get the head and tail of each linked list. We can do this several different ways. Solution #1 : Additional Data Structure
The fi rst, and easier, approach is to create a new data structure called Node P a i r which holds just the head and tai l of a linked list. The convert method can then retu rn something of type Nod e P a i r. The code below implements this approach. 1
p r ivat e class NodePa i r {
560
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 1 7 2
BiNode head , t a i l ;
3 4
public NodePa i r ( BiNode head , BiNode t a i l ) t h i s . head head ; this . t a i l = t a i l ; }
5
6
7 8 9
10
11 12 13 14 15
16 17 18 19
I Hard
{
} public NodePa ir convert ( BiNode root ) { if ( root null ) return null ; ==
NodePa i r partl NodePa ir part2
convert ( root . nodel ) ; convert ( root . node2) ;
if ( pa rt l ! = n u l l ) { concat ( partl . ta i l , root ) ; }
20
if ( part2 ! = n u l l ) { concat ( root , part2 . head ) ; 21 22 } 23 return new NodePa i r ( partl == null 24 part2 == null 25 26 }
?
?
root root
pa rtl . head , part2 . t a i l ) ;
27
28 public stat ic void concat ( BiNode x, BiNode y) { 29 x . node2 y; 30 y . nodel = x ; 31 }
The a bove code sti ll conve rts the B i Node data structu re in pl ac e. We're just using NodePai r a s a way to retu rn additional data. We could have alternatively used a two-element BiNode array to fu lfill the same pu rposes, but it looks a bit messier (and we like clean code, especially in an interview). It'd be nice, though, if we could do this without these extra data structu res-and we can. Solution #2: Retrieving the Tail
Instead of retu rning the head and ta il of the linked l ist with NodePai r, we can return just the head, and then we can use the head to fi nd the tail of the linked list. 1
2
3
4
BiNode convert ( BiNode root ) { if ( root == n u l l ) ret urn n u l l ; convert ( root . nodel ) ; convert ( root . node2 ) ;
5
BiNode partl BiNode pa rt2
6 7
if ( pa rtl ! = null) {
8
9
Hl 11
12 13 14 15
}
concat ( getTa i l ( partl ) , root ) ;
if ( pa rt2 ! = nu l l ) { concat ( root , part2 ) ; } ret urn partl
null
root
part l ;
CrackingTheCodinglnterview.com I 6th Edition
561
Solutions to Chapter 1 7 16
I Hard
}
17
18 public static BiNode getTai l ( BiNode node) { if ( node == null) ret u r n null; 19 while ( node . node2 ! = null) { 20 21 node = node . node2; 22 } return node ; 23 24 }
Other than a call to getTa i l , this code is almost identica l to the fi rst solution. It is not, however, very effi cient. A leaf node at depth d wi ll be "touched" by the getTa i 1 method d times (one for each node above it), leading to an 0 ( N 2 ) overall runtime, where N is the number of nodes in the tree. Solution #3: Building a Circular Linked List
We can build our third and final approach off of the second one.
This a pproach requires retu rning the head and tail of the linked list with B i N o d e . We can do this by returning each list as the head of a circular lin ked list. To get the tail. then, we simply call head . n o d e l. B i Node convertToCircula r ( B iNode root ) { if ( root == n u l l ) ret u rn null;
1
2 3
4
BiNode partl BiNode part3
5
6
7
8
9
10
11 12 13
14 15
16 17
18 19 20 21 22 23 24 25 26
27 28 29
30
31 32
convertToC ircula r ( root . nodel ) ; convertToCircul a r ( root . node2) ;
if ( partl == null && part3 == n u l l ) { root . nodel = root ; root . node2 = root ; ret u rn root ; } BiNode tail3 = ( part3 null) null
pa rt 3 . nodel ;
/ * jo i n left t o root * / i f ( partl = = n u l l ) { concat ( pa rt 3 . nodel, root ) ; } else { concat ( pa rt l . node l , root ) ; } I * j oin right to root * / i f ( part3 == n u l l ) { co ncat ( root, partl ) ; } else { concat ( root , part3 ) ; } I *j oin right to left * / if ( pa rt l ! = null && part3 ! = n u l l ) { concat ( t a i l 3 , partl ) ; }
33 ret u rn partl root part l ; null 34 } 35 3 6 I *Convert l i s t t o a circular l inked l ist , then brea k the c i rc u l a r connecti o n . * /
S62
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 1 7 37
38
39
40
41
42
I Hard
BiNode convert (BiNode root ) { BiNode h e a d = convertToCi rcula r ( root ) ; head . nodel . node2 null; head . nodel null; return hea d ; } =
=
Observe that we have moved the main pa rts of the code into c onvertToC i r c u l a r. The c onvert method calls this method to get the head of the circular l i n ked list, and then brea ks the circular connection. The approach takes O ( N ) time, since each node is only touched an average of once (or, more accurately, 0 ( 1 ) times). 1 7.1 3 Re-Space: Oh, no! You have accidentally removed all spaces, punctuation, and capitalization in a
lengthy document. A sentence like "I r e s et t h e c omput e r . It s t i l l d i d n ' t boot ! " beca me "i reset t h e c omputeri t s t i l ld id n t boot''. You'll deal with the punctuation and capi talization later; right now you need to re-insert the spaces. Most of the words are in a dictionary but a few are not. Given a dictionary (a list of strings) and the document (a string), design an a lgorithm to unconcatenate the document in a way that minimizes the number of un recognized cha racters. EXAMPLE In put:
j e s s loo ked j u s t l i ket imh e r b rot h e r
Output: jes s loo ked j u s t like t im h e r b rother ( 7 unrecogn ized cha racters) pg 1 88
SOLUTION · · · · · · · · · ·······-··
··-· · - ·
. . . ...
'
.. ...
'
· ·· ·-··
·-· -· ··· ··· · -· ·-
------
Some interviewers like to cut to the chase and give you the specific problems. Others, though, like to give you a lot of unnecessary context, l i ke this problem has. It's useful in such cases to boil down the problem to what it's really all about. In this case, the problem is really about finding a way to break up a string into separate words such that as few characters as possible are "left out" of the parsing. Note that we do not attem pt to "understand" the string. We cou ld just as well parse "t h i s i s awe some" to be "t h i s is a we some" as we could "t h i s i s awesome:· Brute Force
The key to this problem is finding a way to define the solution (that is, parsed string) in terms of its subprob lems. One way to do this is recu rsing through the string. The very first choice we make is where to insert the first space. After the first cha racter? Second character? Th ird cha racter? Let's imagine this in terms of a string l i ke t h i s i smike sfavo r i t efood. What is the first space we insert? If we insert a space after t, this gives us one invalid character. After t h is two invalid cha racters. After t h i is th ree invalid characters. At t h i s we have a complete word. Th is is zero invalid characters. •
At t h i s i is five invalid cha racters . ... and so on.
CrackingTheCodinglnterview.com I 6th Edition
563
Solutions to Chapter 1 7
I Hard
After we choose the first space, we can recu rsively pick the second space, then the third spa ce, and so on, u ntil we a re done with the stri ng. We take the best (fewest inva lid characters) out of all these choi ces and retu rn. What shou ld the fu nction retu rn? We need both the number of invalid characters in the recu rsive path as we l l as the a ctu a l parsi ng. Therefore, we just retu rn both by u sing a cu stom-bu ilt Pa r s e R e s u lt class. 1
St ring bestSplit ( Has hSet dictionary, St ring sentence) { ParseResult r = s p l it ( d ictiona ry, sentence, 0) ; return r null ? null : r . parsed ; }
2
3
==
4 5
6
7
8
ParseResult s plit ( H a s hSet < St ring> d i ct ionary, String sentence, int start ) { if ( start >= sentence . lengt h ( ) ) { ret urn new Pa rse Res ult ( 0 , ); } '"'
9
10 11
int best invalid = Intege r . MAX_VALUE ; St ring bestPa rs ing null; St ring partial = "" ; int index start ; while ( i ndex < sentence . lengt h ( ) ) { char c = sentence . cha rAt ( index ) ; partial += c ; int inva lid = d i ct ionary . contains ( partial) ? 0 : partia l . lengt h ( ) ; if ( invalid < besti nva lid) { / / S hort c i r c uit /* Re c u r s e , putting a s p a ce after this cha racte r . If t h i s is bette r than * the c u r rent best option, replace t he best option . * / ParseResult result = s p l it ( d i ct iona ry, sentence , index + 1 ) ; i f ( invalid + res ult . inva lid < best inva l i d ) { bestinvalid = invalid + result . inva l i d ; bestPa rsing = partial + " " + res ult . pa rsed ; if ( bestrnvalid == 0 ) brea k; / / Short c i r c uit } } =
12 13
14 15
=
16 17
18
19
20 21
22 23 24 25 26 27 28 29
30 31
32
33
34
}
index++ ; } ret urn new ParseResult ( bestinva lid, bestPa rsing) ;
35
public class ParseResult { public int inva lid = Intege r . MAX_VALUE ; 37 public St ring parsed = " "; 38 public Pa rseRes ult ( int inv, St ring p ) { inva lid = inv ; 39 parsed = p ; 40 41 } 42 } 36
We've a pplied two short circuits here. Line 22: If the number of cu rrent invalid characters exceeds the best known one, then we know this recursive path wi ll not be ideal. There's no point in even taking it. Line 30: If we have a path with zero inva lid characters, then we know we can't do better than this. We m ig ht as well accept this path.
564
Cracking t h e Coding Interview, 6th Edition
Solutions to Chapter 1 7 I Hard What's the runtime of this? It's difficult to truly describe i n practice as it depends on the (English) language. One way of looking at it is to imagine a biza rre language where essentially all path s i n the recu rsion a re taken. I n this case, we are making both choices at each character. If there are n cha racters, this is an 0 ( 2 " ) runtime. Optimized
Commonly, when we have exponential runtimes for a recurs ive a lgorithm, we optimize them through memoization (that is, caching results). To do so, we need to find the common subproblems. Where do recursive paths overlap? That is, where a re the common subproblems? Let's again imagine the string t h i s i smike sfavori tefood. Again, imagine that everything is a valid word. In this case, we attempt to insert the fi rst space after t as well as after th (and many other choices). Think a bout what the next choice is. s pl it ( t h i s ismikesfavo ritefood ) - > t + s plit ( h i s i smikesfavoritefood ) OR th + s plit ( is i smikesfavoritefood ) OR . . . s p l it ( hi s i smikesfavoritefood ) - > h + s p l it ( i s ismikesfavoritefood ) OR . . .
Adding a space after t and h leads to the same recursive path as inserting a s pace after t h . There's no sense in computi ng s pl it ( isismikesfavoritefood ) twice when it will lead to the same result. We should instead cache the result. We do this using a hash table which maps from the current substring to the P a r s e R e s u l t object. We don't actually need to makethe current substring a key. The start index in the string sufficiently represents the substring. After all, if we were to use the substring, we'd really be using s enten c e . s u bst ring ( start , s enten c e . lengt h ) . This hash table will map from a start index to the best parsing from that index to the end of the string. And, s i nce the start index is the key, we don't need a true hash ta ble at all. We can just use an array of Par s e R e s u l t objects. This wi ll also serve the purpose of mapping from an index to a n object. The code is essentially identica l to the earlier function, but now takes in a memo table (a cache). We look up when we first call the function and set it when we return. 1
2 3
4
5
6
7
8
9
10
11 12 13
String bestSplit ( Has hSet dict iona ry, St ring sentence) { ParseRes ult [ ] memo = new Pars eResult [ sentence . lengt h ( ) ] ; ParseResult r s p l it ( d ictiona ry, sentence , 0, memo ) ; ret urn r == null ? null : r . pa rsed; } =
ParseResult s plit ( H a s hSet < St ring> dictionary, String sentence, int start , ParseRes ult [ ] memo ) { if ( start > = sentence . lengt h ( ) ) { return new ParseResult ( 0 , ); } if ( memo [ start ] ! = null ) { return memo [ start ] ; } ""
CrackingTheCod ingl nterview.com I 6th Edition
I 565
Sol utions to Chapter 1 7 14 15 16 17 18 19 20
int bestl nvalid = I ntege r . MAX_VALUE ; St ring be stPa rsing = n u l l ; String partial " ; int index = start ; while ( i ndex < sentence . length ( ) ) { char c = sentence . charAt ( index ) ; partial += c ; int invalid = dictiona ry . conta i n s ( partial) ? 0 : partial . length ( ) ; if ( inva lid < bestlnvalid ) { I I Short circuit I * Recu r s e , putting a s pace after t h i s c h a racte r . If t h i s is better t h a n * t h e current best option , replace t h e b e s t option . * I ParseResult result = s p l it ( d ictio n a ry , sentence, index + 1 , memo ) ; if ( invalid + result . invalid < bestinva l i d ) { bestlnvalid = invalid + result . invalid ; bestPa r s ing partial + " " + result . pa rsed ; if ( best!nvalid == 0 ) break; I I Short circuit } } =
21
22 23
24 25
26 27
28
29 31
32 33
34 35
index++ ; } memo [ start ] = new ParseResult ( bestinva l i d , bestPars ing) ; return memo [ start ] ;
36 38
"
=
30
37
I Hard
}
U nderstanding the runtime of this is even trickier than i n the prior solution. Again, let's imagine the truly biza rre case, where essentially everything looks l i ke a va lid word.
One way we can approach it is to rea l ize that s p l it ( i ) will only be computed once for each value of i. What happens when we ca l l s p l it ( i ) , assuming we've already called s p l i t ( i+l ) through s p l i t ( n -
1)?
s plit ( i ) - > s p l it ( i split ( i s pl it ( i s pl it ( i
calls :
+ + + +
1) 2)
3)
4)
s p l it ( n - 1 )
Each of the recursive calls has already been computed, so they j ust return immed iately. Doing n - i calls at O ( l ) time each ta kes O ( n - i) time. This means that s p l i t ( i) takes O ( i ) time at most. We can now apply the same logic to split ( i - 1 ) , s p l it ( i - 2 ) , and so on. lf we m a ke l call to compute s p l i t ( n - 1 ) , 2 calls to compute s p l i t ( n - 2 ) , 3 ca lls to com pute s p l i t ( n - 3 ) , ... , n calls to compute s p l i t ( 0 ) , how many calls tota l do we do? This is basically the sum of the numbers from 1 through n, which is O ( n 2 ) . Therefore, the runtime of this function i s 0 ( n2 ) .
566
C ra cki n g the Coding I n te rv iew, 6th Edition
Solutions to Cha pter 1 7 1 7. 1 4
Smallest K: Design an algorithm to find the smallest K num bers in a n array.
I Hard pg 1 88
SOLUTION
There are a number of ways to a pproach this problem. We wil l go through three of them: sorting, max heap, and selection ra n k. Some of these a lgorith ms req uire mod ifyi ng the array. This is someth ing you should discuss with your inter viewer. Note, thou g h, that even if mod ifying the orig inal array is not acceptable, you can always clone the array a nd mod ify the clone instead. This wil l not im pact the overa ll big 0 time of any algorithm. Solution
1 : Sorting
We can sort the elements in ascending order and then ta ke the first mi llion numbers from that. 1
2
3
.il 5
i nt [ ] smallest K ( i nt [ ] array, int k ) { if ( k array . lengt h ) { th row new I l l ega lArgume ntExceptio n ( ) ; }
6
/ * Sort a r r ay * / Arrays . sort ( a r ray) ; .
7
8
9
10 11 12
13
14
1 :;
}
/* Copy first k element s . * / i nt [ ] sma l lest = new int [ k ] ; for ( i nt i = 0 ; i < k ; i++ ) { smallest [ i ] = a r r a y [ i ] ; } return sma l lest;
The time c o mpl e x i ty is O ( n l og ( n ) ) . Solution 2 : Max Heap
We can us e a max heap to solve this problem. We first create a max heap (largest element at the top) for the first million num bers. Then, we traverse through the list. On each element, if it's smaller than the root, we insert it into the heap and delete the largest element (wh ich will be the root). At the end of the traversal, we will have a heap containing the smallest one million num bers. This algorithm is O ( n l og ( m ) ), where m is the number of values we are looking for. 1
2
3 4 5
int [ ] smallest K ( i nt [ ] array, int k ) { if ( k < = 0 I I k > a rr ay . l en gt h ) { throw n ew I l l eg a lArgume ntE xce pti o n ( ) ; }
6
7
8 9
}
PriorityQueue< I nteger> heap ret u r n h ea pTointArray ( h ea p ) ;
=
get KMaxHeap ( array, k ) ;
10 / * C r eat e max heap of smallest k e l em e n t s */ 11 PriorityQueue g et KM ax H ea p ( i n t [ ] array, int k ) { 12 Pri o rit yQu e u e < Int e g e r > heap .
=
CrackingTheCodinglnterview.com I 6th Edition
567
Solutions to Chapter 1 7 13
new PriorityQueue ( k , new MaxHea pCompa rator ( ) ) ; for ( int a : a rray) { if ( heap . size ( ) < k ) { / / If s pace remaining hea p . add ( a ) ; } e ls e if (a < heap . peek( ) ) { // If full a nd top is sma l l heap . pol l ( ) ; / / remove h ighest heap . add ( a ) ; / / i n s e rt new element } } return heap;
14
15
16 17
18 19
20
21
22 23 24 25
I Hard
}
Convert heap to int a rray . * / i nt [ ] heapToi ntArray( PriorityQueue heap) { i nt [ ] a rray new int [ heap . s i ze ( ) ] ; 27 while ( ! heap . is Empty( ) ) { 28 29 a r ray [ h ea p . size ( ) - 1 ] = hea p . poll ( ) ; 30 } return a r ray; 31 32 }
26
/*
=
33
3 4 c l a s s MaxHeapComparator imp lements Compa rator < I ntege r > { 35 public int compa re ( I nteger x , Integer y) { return y - x; 36 37 } 38 } Java's uses the Priori tyQu e u e class to offer heap-like fu nctionality. By defau lt, it operates as a min heap,
with the smallest element on the top. To switch it to the bigg est element on the top, we can pass i n a different compa rator. Approach 3: Selection Rank Algorithm (if elements are u nique)
Selection Rank is a well-known algorithm in computer science to find the ith smallest (or largest) element in an a rray in linear time. If the elements are unique, you can fi nd the ith smallest element in expected 0 ( n ) time. The basic algo rithm operates like this: 1 . Pick a ra ndom element in the array and use it as a "pivot." Partition elements around the pivot, keeping track of the number of elements on the left side of the partition. 2. If there a re exactly i elements on the left, then you just retu rn the biggest element on the left. 3.
If the left side is bigger than i, repeat the a lgorithm on just the left part of the array.
4. If the left side is smaller than i, repeat the a lgorithm on the rig ht, but look for the element with ran k i - leftS i z e.
Once you have found the ith smallest element, you know that all elements smaller than this will be to the left of this (since you've partitioned the array accordi ngly). You can now just return the first i elements. The code below implements this algorithm. 1
2 3
4 5
i nt [ ] smallestK ( int [ ] a rray, int k ) { if ( k < = 0 I I k > a r ray . lengt h ) { throw new I l lega lArgumentException ( ) ; }
5 6 11
Cracki ng the Coding Interview, 6th Editi o n
Solutions to Chapter 1 7 i
=
int t h re s hold ran k ( a rray, k - 1 ) ; i nt [ ] smallest = new i nt [ k ] ; int count 0; for ( int a : a rray) { if ( a < = threshold ) { smallest [ count ] a; count++; }
7
8
=
9
10
=
11 12
13
14
}
return smallest ;
15 16
17 18
19
20
21 22 23
l Hard
}
/ * Get element with rank . */ int ra n k ( i nt [ ] a rray, int rank) { return ran k ( a rray, 0, a rray . length - 1 , rank ) ; }
/ * Get element with rank between left a nd right indices . */ 2 4 int rank ( i nt [ ] a rray, int left , int right , int r a n k ) { 25 int pivot = a rray [ randomlnt l n Range ( left , right ) ] ; 26 int left E nd = partition ( a rray, left , right , pivot ) ; int leftSize left E nd - left + 1 ; 27 28 if ( rank leftSize - 1) { 29 return max ( a rray, left , leftEnd ) ; 30 } else if ( rank < leftSize) { 31 return rank ( a rray, left , left E n d , rank ) ; 32 } else { 33 return rank ( a rray, left E nd + 1 , right , rank - leftSize ) ; =
==
34 35
36
37 38 39
40 41
42 43
44
tlS
46 47
48 49
50
51 52 53
54 55 56 57
58
59
60
61
}
}
/* Pa rtition a rray a round pivot such that all elements < = pivot come before a l l * e lements > pivot . * / int partition ( int [ ] a rray, int left , int right , int pivot ) { while ( left < = right ) { if ( a rray [ left ] > pivot ) { /* Left i s bigger than pivot . Swap it to the right s ide, where we know it * s hould be . */ swa p ( a rray, left , right ) ; right - - ; } else if ( a rray [ right ] = a r ray . lengt h ) { 39 40 t h row new I llegalArgumentExceptio n ( ) ; 41 } 42 ret urn rank ( a r ray, k, 0, a r ray . le ngt h - 1 ) ; 43 } 44 45 / * F i nd value with rank k in s u b a rray between s t a rt and end . */ 4 6 int rank ( i nt [ ] a rray, int k , int s t a rt , int end ) { 47 / * Partition a rray a round a n arbit r a ry pivot . * / int pivot = a r ray [ ra ndomi ntinRange ( st a rt , end ) ] ; 48 49 PartitionResult pa rtition = partitio n ( a rray, start , end, pivot ) ; int leftSize = partition . leftSize; 50 int middleSize pa rtition . middleSize; 51 =
52
/ * Search portion of array . */ if ( k < leftSize ) { / / Rank k is on left half return rank ( a rray, k, start, start + leftSize 1); } else if ( k < leftSize + middleSize ) { // Rank k i s i n middle return pivot ; / / middle i s all pivot values } else { // Rank k is on right ret urn rank ( a rray, k - leftSize - middleSize, start + leftSize + middleSize, end ) ;
53 54
-
55 56 57 58
59 60 61 52 63
'4
65 66
67
68
69 70 71
72 73
74 75 76 77
78
79
80 81 82
83
} } / * Partition result into < pivot , equal to pivot - > bigger t h a n pivot . */ PartitionResult p a rtit ion ( int [ ] a rray, int s t a r t , int end, int pivot ) { int left start ; / * Stays at ( right ) edge of left side . */ int right = end ; /* Stays at ( left ) edge of right side . */ int middle s t a rt ; / * Stays at ( right ) edge of midd le . */ while ( middle pivot ) { /* Middle is bigger than the pivot . Right could have any value . Swa p t hem, * then we know t hat the new right is bigger t h a n the pivot . Move right by * one . * / swa p ( a rray, middl e , right ) ; right - - ; pivot ) { } else if ( a rray [ middle ] =
=
==
CrackingTheCodingl nterview.com I 6th Edition
571
Solutions to Chapter 1 7 84
I Hard
/* Middle is equal to the pivot . Move b y one . */ middle++ ;
85
86
}
87
88
}
89
/ * Ret u r n sizes of left and middle . * / ret u r n new PartitionResult ( left - start, right - left + 1 ) ; 90 91 }
Notice the change made to s mallest K too.We can't simply copy all elements less than or equal to t h r e s hold i nto the array. Si nce we have duplicates, there could be many more than k elements that are less than or equal to threshold. (We also can't just say "okay, only copy k elements over." We could inadvertently fill up the array early on with "equal" elements, and not leave enoug h space for the smaller ones.) The solution for this is fairly simple: only copy over the smaller elements first, then fill up the array with equal elements at the end. 1 7.1 5 Longest Word: Given a list of words, write a program to find the longest word made of other words
in the list. pg 188
SOLUTION ----- ··- ··- · ·-···-·-··-
- ·-
··-
This problem seems complex, so let's simplify it. What if we just wa nted to know the longest word made of two other words in the list? We could solve this by iterating through the list, from the longest word to the shortest word. For each word, we would split it into all possible pairs and check if both the left and right side are conta ined in the list. The pseudocode for this would look like the followi ng: 1 2
String get longestWo rd ( St r i ng [ ] l ist ) { St ring [ ] a r ray = l i s t . SortB y L e ngt h ( ) ; / * Create map for easy lookup * / Has hMap map = new HashMa p < St r i ng, Boolea n > ;
3 4 5
6
for ( St ring st r : a rray ) { ma p . put ( st r , t r ue ) ; }
7 8
9
10 11 12
13 14
15 16 17 18
19 20 21
22 }
for ( St ring s : a r ray ) { // Divide into every poss ible pair for ( int i = 1; i < s . lengt h ( ) ; i++) { String left = s . substring(o, i ) ; String right = s . s u bstring ( i ) ; / / Check if bot h s i d e s a re in t h e a r ray if ( m a p [ l e ft ] == t rue && map [ right ] == t r u e ) { ret u r n s ; } } } ret u r n st r ;
This works great for when we just wa nt to know composites of two words. But what if a word cou ld be formed by any num ber of other words?
S 71
Cracking th e Coding Interview, 6th Edition
Solutions to Cha pter 1 7
I Hard
I n this case, we could apply a very similar approach, with o ne m odification: rather than sim ply looki ng up if the right side is in the array, we wou l d recu rsively see if we can build the rig ht side fro m the other e lements in the array. The code below impleme nts th is algorith m: 1
String print LongestWOrd ( St ring a r r [ ] ) { Ha s hMa p map = new H a s hMap ( ) ; for ( St r i ng str : a r r ) { 3 ma p . put ( st r , t r ue ) ; 4 5 } 6 Ar rays . s ort ( a r r , new Le ngthCompa rato r ( ) ) ; / / Sort by length for ( String s : a r r ) { 7 � if ( ca n B u i ldWOrd ( s , t rue , ma p ) ) { 9 Sys t em . out . printl n ( s ) ; return s ; 10 11 } 12 } 13 retu r n 14 } 2
"" . ,
15 16
boolean can B u i ldWOrd ( St r i ng str, boolean isOriginalWOrd, HashMap map) { 18 if ( ma p . contains Key ( st r ) && ! isOriginalWOrd ) { 19 return map . get ( st r ) ; 20 } for ( int i = 1; i < st r . length ( ) ; i++ ) { 21 St ring left str . s ubstring ( 0 , i ) ; 22 St ring right = str . s ubstring ( i ) ; 23 if ( m a p . conta i n s Key( left ) && ma p . get ( left ) t rue && 24 canBu ildWOrd ( right , fa l se , ma p ) ) { 25 ret u r n t rue ; 26 27 } 28 } 29 ma p . put ( st r , fal se ) ; retu rn fa l se ; 30 31 } 17
=
N ote that in th is sol ution we h ave performed a small o pti mization. We use a dynamic prog ramming / memoization approach to cache the resu lts between ca lls. Th is way, if we repeated ly need to check if the re's any way to build "te s t i n gteste r;' we'll only h ave to c o m pute it o n ce.
A boole a n flag isOrigina lWord is used tocomplete t h e above optimization. The m ethod c a n B ui ldWord is called for the o riginal word a nd for each substring, a nd its fi rst step is to check the cache for a prev iously ca lcu lated res u lt. However, for the original words, we have a proble m: map is i niti a l i zed to t r u e for them, but we don't want to return t rue (si nce a word can n ot be com posed sole ly of itse lf). Therefore, for the original word, we simply bypass th is check using the i sOriginalWord flag.
CrackingTheCodinglnterview.com j 6th Edition
S 73
Solution s to Cha pter 1 7
I Hard
1 7.1 6 The Masseuse: A popular masseuse receives a sequence of back-to-back appointment req uests
and is debating which ones to accept. She needs a 1 5-minute brea k between appointments and therefore she cannot accept any adjacent requests. Given a sequence of back-to-back a ppoint ment requests (all m u ltiples of 1 5 m i nutes, none overlap, and none can be moved), find the optimal (highest total booked minutes) set the masseuse can honor. Return the number of minutes. EXAMPLE In put: { 3 0 , 1 5 , 60, 7 5 , 4 5 , 1 5 , 1 5 , 4 5 } Output: 180 minutes ( { 3 0 , 6 0 , 4 5 , 4 5 } ) .
pg 1 88
SOLUTION
Let's start with a n exam ple. We'll d raw it visually to get a better feel for the problem. Each number indicates the number of minutes in the appointment.
I r0
=
75
I
r1
=
105
90
135
Alternatively, w e could have also divided all the values (including the break) by 1 5 minutes, to give u s the array { S , 7, 8 , 5 , 6, 9 } . This would be equivalent, but now we would want a l -minute brea k. The best set ofappointments for this problem has 330 m i n utes tota l, formed with { r 0 = 7 5 , r 2 = 1 2 0 , r 5 = 1 3 5 } . Note that we've intentionally chosen a n exa mple in which the best sequence o f appointments was not formed through a strictly alternating sequence. We should also recognize that choosing the longest appointment first (the "greedy" strategy) would not necessarily be optimal. For exam ple, a sequence like { 45 , 6 0 , 45 , 1 5 } would not have 60 in the optimal set. Solution #1 : Recursion
The first thing that may come to mind is a Fecursive solution. We have essentia lly a sequence of choices as we walk down the list of appointments: Do we use this appointment or do we not? If we use appointment i, we must skip appointment i + 1 as we can't take back-to-back appointments. Appointment i + 2 is a possibility (but not necessarily the best choice). 1
int maxMinutes ( int [ ] mas sages ) { return maxMinutes (mas sages , 0) ; }
2
3
4 5
int maxMinute s ( int ( ] massage s , int index) { if ( index > = massage s . lengt h ) { // Out of bound s return 0 ; }
6
7
8 9
10
/ * Best with t h i s reservat ion . * / i n t bestWith mas sages ( i ndex] + maxMinute s ( ma s sage s , i ndex + 2 ) ;
11
=
12
13
/ * Best without this reservation . * / int bestWithout = maxMinut e s ( ma ssage s , index + l ) ;
14 15
16
17
18
}
574
/ * Retu rn best of t h i s s ubarray, starting from index . * / return Mat h . ma x ( b e s tW it h , bestWithout ) ;
Cracking the Coding I nterview, 6th Edition
Solutions to Chapter 1 7
I Hard
The runtime of this solution is 0 ( 2" ) because at each element we're making two choices and we do this n times (where n is the num ber of massages). The space com plexity is O( n ) due to the recursive call stack. We can a lso depict this through a recursive call tree on an array of length 5. The number in each node repre sents th e index value in a ca ll to maxMi nutes. O bserve that, for example, maxMinutes (ma s sages , 0) calls maxMinutes (mas sages J 1 ) and maxMi nutes (massages , 2 ) .
As with many recursive problems, we should eva luate i f there's a possibility t o memoize repeated subprob lems. Indeed, there is. Solution #2: Recur5ion + Memoization
We will repeated ly ca ll maxMinutes on the same in puts. For example, we'll cal l it on index 2 when we're deciding whether to ta ke appoi ntment 0. We'll also ca ll it on index 2 when we're deciding whether to take appoi ntment 1 . We should memoize this. Our memo ta ble is just a mapping from index to the max minutes. Therefore, a simple array will suffice. 1
2 3
4 5
6
7
8 9
10
int maxMinutes ( int [ ] massage s ) { int [ ] memo = new int [mas sages . length ] ; r etu r n maxMinutes ( ma s sages , 0 , memo ) ; } int m a xMi nut e s ( i nt [ ] mas sages , int index, i nt [ ] memo ) { if ( i nd e x > = mas sages . lengt h ) { retu r n 0; } if ( memo [ i ndex ] == 0 ) { i n t bestWit h = massages [ index ] + maxMinutes ( ma s sages , index int bestWithout = maxMin utes ( ma s sage s , i ndex + 1 , m em o ) ; memo [ index ] Math . ma x ( be stWit h , bestWithout ) ; }
11 12 13 14 15 16 17 18 To
+
2 , memo ) ;
=
ret u r n
m e mo [ i n d ex ] ;
}
determine the runtime, we'll d raw the same recu rsive ca ll tree as before but gray-out the calls that will return immediately. The calls that wi ll never happen wi l l be deleted entirely.
CrackingTheCod ingl nterview.com I 6th Edition
575
Solutions to Chapter 1 7
I Hard
If we d rew a bigger tree, we'd see a similar pattern. The tree looks very linear, with one branch down to the left. This gives us an O ( n ) runtime and O ( n ) space. The space usage comes from the recu rsive cal l stack as well as from the memo table. Solution #3: Iterative
Can we do better? We certainly can't beat the time complexity since we have to look at each a ppointment. However, we might be able to beat the space complexity. This would mean not solving the problem recu r sively. Let's look at our first example again.
45 1
r
5
1s
As we noted i n the problem statement, we cannot take adjacent a ppointments.
I
r
1
=
45
1
There's a nother observation, though, that we can make: We should never skip three consecutive appoint ments. That is, we might skip r 1 and r 2 if we wanted to take r 0 and r 3• But we would never ski p r 1, r 2, and r 3 • This would be suboptimal since we could always improve our set by g ra bbing that middle element. This means that if we take r 0, we know we11 definitely skip r 1 and definitely take either r 2 or r r This substan tially li mits the options we need to eva luate and opens the door to an iterative solution. Let's think about our recu rsive + memoization solution and try to reverse the logic; that is, let's try to approach it iteratively. A useful way to do this is to approach it from the back and move toward the sta rt of the a rray. At each point, we find the solution for the suba rray. be s t ( 7 ) : What's the best option for { r 7 •
best ( 6 ) : What's the best option for {
45 } ? We can get 45 min. if we take r 7, so best ( 7 )
r6
best ( 5 ) : What's the best option for { r 5 »
take r 5
»
take b es t ( 6 )
=
=:
»
take r 4
»
take best ( S )
=
=
576
} ? Still 4S min., so best ( 6 )
15,
•
•
•
} ? We can either:
=
45, or:
60. =:
45 ,
•
•
=:
•
}? We can either:
45, or:
60. =
best ( 3 ) : What's the best option for { r 3 =
•
45 and merge it with best ( 6 )
The fi rst gives us 90 minutes, best ( 4) take r,
•
45.
best ( 4 ) : What's the best option for { r 4
ll
•
15 and merge it with best ( 7 )
The first gives us 60 minutes, best ( 5) =
15,
90. =
75,
75 and merge it with b e s t ( 5 )
Cracking the Coding I nterview, 6th Edition
• =
•
•
} ? We can either:
60,
or:
=
45.
45
Solutions to Cha pter 1 7 I Hard »
ta ke best ( 4 )
The first gives us •
1 35
=
90. =
min utes, best ( 3)
best ( 2 ) : What's the best option for { r2 »
take r 2
=
=
135. 60, . . . } ? We can either:
60 and merge it with best ( 4 )
=
90, or:
" take best ( 3 ) = 1 3 5 . =
The fi rst gives us 1 50 min utes, best ( 2 ) b e s t ( 1 ) : What's the best option for { r 1 »
.
1 5 , . . } ? We c a n either:
=
1 5 and merge it with best ( 3 ) take r 1 ta ke best ( 2 ) 1 5 0.
»
Either way, best ( 1 ) •
=
150.
=
take r0
»
take best ( l )
135, or:
1 5 0.
best ( 0 ) : What's the best option fo r { r 0 »
=
=
30, . . . }? We can either:
30 and merge it with best ( 2 ) =
=
1 5 0, or:
1 5 0. 180.
The fi rst gives us 1 80 m inutes, best ( 0 ) Therefo re, we retu rn 1 80 minutes.
The code below implements this algorithm. 1 int maxMinutes ( i nt [ ] mass ages ) { 2 / * Al locat ing two ext r a s lots in the array so we don ' t have to do bou nds 3 * checking on lines 7 and 8 . */ 4 int [ ] memo = new int [massages . lengt h + 2 ] ; memo [ mass ages . lengt h ] = 0; 5 6 memo [ mas s ages . length + 1 ) = 0; 7 for ( int i = ma s s ages . lengt h - 1 ; i >= 0; i - - ) { int bestWith = mas sages [ i ] + memo [ i + 2 ] ; 8 int bestWit hout = memo [ i + 1 ) ; 9 10 memo [ i ] Mat h . max ( bestWit h , bestWithout ) ; 11 12
13
=
}
return memo [ 0 ) ; }
The runtime of this solution is 0 ( n ) and the space complexity is also 0 ( n ) . It's nice i n some ways that it's iterative, but we haven't actu ally "won" anything here. The recursive solution had the same time and space complexity. Solution #4: Iterative with Optimal Time and Space
In reviewing the last solution, we can recognize that we only use the values in the memo table for a short a mount of time. O nce we are several elements past an index, we never use that element's index again. In fact, at any given index i, we only need to know the best va lue from i + 1 and can get rid of the memo table a n d just use two i ntegers. 1 int maxMinutes ( i nt [ ] mass ages ) { 2 int oneAway = 0 ; 3 int twoAway 0; 4 for ( int i = massages . length 1; i >= 0; i- - ) { s int bestWith mass ages [ i ] � twoAway; int bestWithout = oneAway ; 6
i
+ 2. Therefo re, we
=
-
=
CrackingTheCodinglnterview.com I 6th Edition
S77
Solutions to Chapter 1 7
I Hard
int c u r r e nt
7
8
twoAway oneAway
9
10 11 12
}
= Mat h . ma x ( bestwith , bestWithout ) ; oneAway; current ;
=
=
} return oneAway ;
This gives us the most opti mal time and space possible: O ( n ) time and 0 ( 1 ) space. Why did we look backward? It's a common technique in many problems to walk backward through an array. However, we can walk forward if we want. This is easier for some people to think about, and harder for others. I n this case, rather than asking "What's the best set that sta rts with a [ i ] ?'; we wou ld ask "What's the best set that ends with a [ i ] ?" 1 7.1 7 Multi Search: Given a stri ng b and an a rray of smaller stri ngs T, design a method to search b for
each small string in T. pg 1 89
SOLUTION
Let's sta rt with an example: T b
= =
{ " i s", "ppi", "hi", "sis", "i", "ssippi"} rrmi s s i s s i ppi"
Note that in our exa mple, we made sure to have some strings (like "is") that appear multiple times in b. Solution # l
The naive solution i s reasonably straightforwa rd. Just search through the bigger string for each instance of the smaller stri ng. 1
H a s hMa p L i s t < String, Integer> searchAl l ( String big, String ( ] smalls ) { HashMa p l i s t < Str ing, Intege r > lookup new HashMapList ( ) ; for ( String sma l l : smalls ) { ArrayL i s t < I nteger> locations search ( big, small ) ; lookup . p ut ( smal l , locations ) ; } return lookup; } =
2 3
4 5
=
6
7
8
9
10 11
/* Find all locations of the smaller string within the bigger string . */ Arra y L i s t < Intege r > search ( String big, St ring s m a l l ) { Arraylist < I ntege r > locations = new Array list ( ) ; 13 14 for ( int i 0 ; i < big . lengt h ( ) - small . lengt h ( ) + 1; i++ ) { 15 i f ( isSubstringAt Location ( big, sma l l , i ) ) { locations . add ( i ) ; 16 17 } 18 } 19 return locations ; 20 }
12
=
21
22 23
24 2S
/ * C h e c k if sma l l appears at index offset within big . * / boolean isSubstringAt Location ( String big, String sma l l , i n t offset ) { for ( int i 0 ; i < sma l l . lengt h ( ) ; i++ ) { if ( b i g . c h a rAt ( off s et + i ) ! = small . charAt ( i ) ) {
5 78
=
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 1 7 26
return fa l se ;
27 28 29
} } return true ;
30
}
33
/ * HashMapList is a HashMap t hat ma ps from Strings to * Arraylist < I ntege r > . See appendix for implementatio n . */
31 3 2.
We
I Hard
cou ld have also used a s u b string and equals fu nction, instead of writing isAtLocation. Th is is slightly faster (though not in terms of big 0) because it doesn't req u i re creati ng a bunch of su bstrings.
This will take 0( kbt ) time, where k is the length of the longest stri ng in T, b is the length of the bigger stri ng, and t is the number of smaller strings within T. Solution #2
To optim ize this, we shou ld think about how we can tackle all the elements in T at once, or somehow re-use work. One way is to create a trie-like data stru ctu re using each suffix in the bigger stri ng. For the stri ng b i b s , the suffix list wou ld be: bibs, ibs, bs, s . The t ree for this i s below.
s
s s "
"
Then, a l l you need to do is search in the suffix tree for each stri ng in T. Note that if B were a word, you wou ld come up with two locations. 1
2
3
4
5
6 7 8
9
10 11 12 13
14
HashMa p L i s t < St ring, I nteger> searchAl l ( String big, String [ ] small s ) { HashMapList lookup = new H a s hMapList < String, Integer > ( ) ; Trie tree c reateTrieF romString ( big ) ; for ( String s : sma l l s ) { /* Get terminating location of each o c c urrence . */ Arrayl i s t < I nteger> locations t ree . sea rc h ( s ) ; =
=
/*
Adj ust t o starting location . */ subtr ac tVa lu e ( loca t ions s . le ngth ( ) ) ; ,
/* I nsert . */ lookup . put ( s , locations ) ;
} return lookup;
CrackingTheCodingl nterview.com I 6t h Edition
579
Solutions to Chapter 1 7 15 16 17
I Hard
}
Trie createTrieF romSt r i ng ( String s ) { Trie trie = new Trie ( ) ; for ( int i = 0 ; i < s . lengt h ( ) ; i++ ) { 19 20 String s uffix = s . substring ( i ) ; 21 trie . insertString ( s uffix, i ) ; 22 } ret u r n trie; 23 24 } 25 26 void s ubtractValue (Arraylist < I nteger > location s , int delt a ) { 27 if ( locations == n u l l ) return; for ( int i = 0; i < locations . s ize ( ) ; i++ ) { 28 29 locations . set ( i , locations . get ( i ) - delta ) ; 30 } 31 }
18
32 33
public c l a s s Trie { private TrieNode root
34
35
36
=
new TrieNode ( ) ;
public Trie ( String s ) { insertString ( s , 0 ) ; } p u b l i c Trie ( ) { }
37
38
39
40
public Array l i s t < I nteger> s e a r c h ( String s ) { ret u r n root . search ( s ) ; }
44
p u b l i c void insertString ( String str, int locat ion ) { root . i nsert String ( st r , location ) ; }
41 42 43 45
46
47 48
49 50
51
52
53
54 55
56
}
p u b l i c Tr ieNode get Root ( ) { ret urn root ; }
public c l a s s TrieNode { private H a s hMa p childre n ; private Arraylist indexe s ; p r ivate c h a r v a l u e ;
57 58
59
60 61
62
63 64
65 66
67 68
69 70
580
public TrieNode ( ) { children new H a s hMa p ( ) ; indexes = new ArrayList ( ) ; } =
public void insertString ( String s , int index ) { indexes . add ( index ) ; if ( s ! = null && s . lengt h ( ) > 0) { value = s . c h a rAt ( 0) ; TrieNode child = null; if ( children . cont a i n s Key (value ) ) { c h ild children . get (value ) ; } else { child new TrieNode ( ) ; =
=
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 1 7 71
72 73
75 76
77
78
}
79
80
==
82
83 84 85
86 87
88 89
96
91 92 93
95
96 97
98
100 } 102
==
public boolean terminates ( ) { ret urn childre n . cont a i n s Key( r \0' ) ; }
94
103
childre n . put ( value, child ) ; } St ring rema inder s . substring ( l ) ; child . i nsertSt ring( rema inder, i ndex + 1 ) ; } else { children . put ( < \0' , n u ll ) ; / / Terminating cha racter }
public ArrayList < I nteger> searc h ( St r i ng s ) { if ( s null I I s . lengt h ( ) 0) { ret urn i ndexe s ; } else { char first = s . charAt ( 0 ) ; if ( children . cont a i n s Key( first ) ) { St r i n g rema inder = s . s u b s t r i ng ( l ) ; return childre n . get ( first ) . search ( rema inde r ) ; } } retu r n null ; }
81
161
H ard
=
74
99
I
public TrieNode getChild ( c har c ) { ret u r n childre n . get ( c ) ; }
/* HashMapList is a H a s hMap t hat maps from St ri ngs to * Arraylist < I nteger> . See a ppendix for implementation . */
It ta kes O( b 2 ) time to create the tree a nd O( kt ) time to search for the locations.
I
Reminder: k is the length of the longest string in T, b is the length of the bigger string, and t is the number of smaller stri ngs with in T.
The tota l ru nti me is 0( b' + kt ) . Without some add itional knowledge of the expected input, you cannot d i rectly compare 0 ( b kt ) , which was the ru ntime of the prior solution, to O( b2 + kt ) . If b is very large, then 0 ( b kt ) is prefera ble. But if you have a lot of smaller stri ngs, then 0( b 2 + kt ) might be better. Solution #3
Alternatively, we can add all the smaller stri ngs into a trie. For example, the stri ngs { i , i s , p p , ms } would look like the trie below. The asterisk (*) hanging from a node indi cates that this node com pletes a word.
CrackingTheCodingl nterview.com I 6th Edition
581
Solutions to Chapter 1 7 I Hard
s
* *
*
*
Now, when we want to find all words in m i s s i s s ip p i, we search through this trie starting with each word. m: We would first look u p in the trie starti ng with m, the fi rst letter in m i s s i s s i ppi. As soon as we go to mi, we terminate. i : Then, we go to i, the second character in m i s s i s s ippi. We see that i is a complete word, so we add it to the list. We also keep going with i over to i s . The string i s is also a complete word, so we add that to the list. This node has no more children, so we move onto the next character in m i s s i s s i pp i . s : We now go t o s . There is n o upper-level node fo r s, s o we go onto the next character. s: Another s. Go on to the next cha racter. i: We see a nother i. We go to the i node i n the trie. We see that i is a complete word, so we add it to the list. We also keep going with i over to is. The string is is also a com plete word, so we add that to the list. This node has no more children, so we move onto the next character in m i s s i s s i ppi. s : We go to s . There is no upper-level node for s. s : Another s . Go on to the next character. i: We go to the i node. We see that i is a complete word, so we add it to the trie. The next character in m i s s i s s i ppi is a p. There is no node p, so we break here. p: We see a p. There is no node p. p: Another p. i: We go to the i node. We see that i is a complete word, so we add it to the trie. There a re no more characters left in m i s s i s s ippi, so we are done. Each time we find a complete "small" word, we add it to a list along with the location in the bigger word (mi s s i s s i ppi) where we found the small word. The code below implements this algorithm. 1
HashMa pList searchAl l ( St ring big, String [ ] smalls ) { HashMa pList lookup new HashMap List ( ) ; int maxlen = big . length ( ) ; Tr ieNode root = c reateTreeF romSt rings ( smalls , maxLen ) . getRoot ( ) ;
2
=
3
4 5 6
for ( int i 0; i < big . length ( ) ; i++ ) { Arraylist strings findStri ngsAtloc ( root , big, i ) ; inserti ntoHa shMa p ( st rings , lookup, i ) ; } =
=
7
8 9
10
11
582
return lookup;
Cracking the Cod ing Interview. 6th Edition
Solutions to Chapter 1 7
� Hard
12 } 13 14 / * Insert each s t ri ng into trie (provided string is not longer than maxLen ) . * / 1 5 Trie createTreeF romSt r i ngs ( String[ ] smalls , int maxLen ) { Trie tree new Trie ( '"') ; 16 for (String s : sma lls ) { 17 if ( s . lengt h ( ) < = maxle n ) { 18 t ree . i n sertSt ring( s , 0 ) ; 19 26 } 2.1 } 22 ret u r n tree; 23 } �
24 25
/* F i n d s t r i ngs in t r ie t h at s t a rt at index " start" within b ig . * / 26 Array l i s t < St r i ng> findSt r ingsAt loc (TrieNode root , String big, i n t start ) { 27 Array list st rings = new Array l i s t < St r ing> ( ) ; int index = start ; 28 while ( i ndex < big . le ngth ( ) ) { 29 root = root . getChi l d ( big . charAt ( i ndex ) ) ; 30 31 if ( root == null ) brea k ; 32 i f ( root . terminates ( ) ) { // Is complete string, a d d to list 33 strings . add ( big . substring ( start , index + 1 ) ) ; 34 } index++; 35 36 } return strings ; 37 38 } 39
40
41
/ * HashMa p l i s t < String, Integer> is a H a s hMap t hat maps from Strings to * Arrayli s t < I nteger> . See appendix for implementation . * /
This a lgorithm ta kes 0 ( kt ) time t o create the trie and 0 ( b k ) time to search for a l l the strings.
I
Reminder: k is the length of the longest stri ng in T, b is the length of the bigger string, and t is the number of s m all e r strings within T.
The total time to solve the question is 0 ( kt + b k ) . Solution #1 was O ( kbt ) . We know th at O ( kt + b k ) will be faster than O ( kbt ) . Solution #2 wa s 0 ( b2 + kt ) . Since b wi l l a lw ays be bigg e r than k (or if it's not, then we know this rea lly long stri ng k cannot be found i n b), we know Solution #3 is also faster than Solution #2.
CrackingTheCodingl nterview.com I 6th Editio n
583
Solution s to Chapter 1 7 1 7.1 8
I Hard
Shortest Supersequence: You are given two arrays, one shorter {with all distinct elements) and one
longer. Find the shortest subarray in the longer array that contains all the elements in the shorter array. The items can appear in any order. EXAMPLE
I nput: { 1, s , 9 }
7 , 9 . 1 . 1 , 5, 8, 8, 9 , 7} Output: [ 7 , 1 0 ] ( the u n d e r l i ned portion a bove ) {7 , 5 , 9 , 0 , 2 , 1 , 3 , 5 .
pg 189
SOLUTIONS
'''
-··- ··- -·-··-·· -··-· ·-·· -· ·-·- ·-· ·----
As usual, a brute force approach is a good way to start. Try thinking a bout it as if you were doing it by hand. How would you do it? Let's use the example from the problem to wa l k through this. We'll call the smaller a rray smal lArray and the bigger array bigArray. Brute Force
The slow, "easy" way to do this is to iterate through bigArray and do repeated small passes through it. At each index in b igArray, sca n forward to find the next occurrence of each element in smal lArray. The largest of these next occurrences will tell us the shortest subarray that starts at that index. (We'l l call this concept"closure:'That is, the closure is the element that "closes" a complete suba rray starting at that index. For example, the closure of index 3-which has value 0-in the example is i ndex 9.) By finding the closures for each i ndex i n the array, we can find the shortest subarray overall.
1
2
Ra nge shortestSupersequence ( i nt [ ] bigArray, int [ ] sma l lArray) { int bestStart 1; int bestEnd -1; for ( i nt i 0 ; i < bigArray . lengt h ; i++ ) { int end findClosure ( bigArray, smallArray, i ) ; if ( end 1 ) brea k ; if ( bestStart - 1 I I end - i < bestEnd - bestStart ) { bestStart i; best End end ; } } ret u r n new Ra nge ( bestStart , bestEnd ) ; } =
-
=
3
4
=
=
5
6
==
7
-
==
8
=
9
10 11
12 13
14
=
15
/ * G iven a n index, find the closure ( i . e . , the element which terminates a complete * s u barray containing all e lement s in smal lArray ) . This will be the max of the * next locations of each element in s ma l lArray . */ 1 8 int findClosure ( i nt [ ] bigArray, int [ ] smallArray, int index ) { 19 int max -1; for ( i nt i 0; i < smallArray . lengt h ; i ++ ) { 20 i n t next = findNext insta nce ( bigArray, smallArray [ i ] , index ) ; 21 if ( next == - 1 ) { 22 23 ret u r n - 1 ; 24 } 25 max Mat h . max ( next , max ) ; 26 } li 17
=
=
=
584
Cracking the Co d i n g Interview,
6th Editio n
Solutions to Chapter 1 7 27
I Hard
return max ;
28 f 29 30
31
32
33
34
35
36 37
38
/* Find next insta nce of element starting from index . * / int findNextinstance ( i nt [ J a rray, int element, int index) { for ( int i = index ; i < a r ray . lengt h ; i++) { i f ( a rray [ i ] element ) { return i ; } } return - 1 ; } ==
.39 40 public cla s s Ra nge { 41 private int start ; 42 privat e int end; 43 public Range ( int s , int e) { 44 start s; end = e; 45 } 46 47 48 public int lengt h ( ) { ret urn end - start + 1 ; } 49 public int getStart ( ) { ret urn start; } 50 public int getEnd ( ) { return end; } =
51 52
53 54 55
}
public boolean shorterTha n ( Ra nge othe r ) { return lengt h ( ) < othe r . length ( ) ; }
Th is algorithm will potentially take 0 ( 5 8 2 ) time, where B is the length of b i gString and S is the length of sma l lString. This is because at each of the B characters, we potentially do O ( S B ) work: S scans of the rest of the string, which has potentially B characters. Optimized
Let's think a bout how we can optim ize th is. The core reason why it's slow is the repeated sea rches. Is there a faster way that we can fi nd, given a n index, the next occurrence of a particular character? Let's th ink a bout it with an example. Given the array below, is there a way we could q u ickly find the next 5 from each location?
7, 5, 9,
0,
2,
1, 3, 5, 7 , 9, 1, 1, 5, 8, 8 , 9, 7
7
' s
Yes. Because we're going to have to do this repeated ly, we can precompute this information i n just a single (backwards) sweep. Iterate through the a rray backwa rds, tracking the last (most recent) occurrence of 5. value index
next
5
0 1
Doing this fo r each of { 1 ,
9
1
1
5,
7
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
7
7
7
7
7
7
12
12
12
12
12
x
x
x
x
9 } ta kes just 3 backwards sweeps.
Some people wa nt to merge this into one backwards sweep that handles a l l th ree values. It feels faster-but it's n ot really. Doing it in one backwards sweep means doing th ree compa risons at each iteration. N moves through the l ist with three comparisons at each move is no better than 3N moves and one comparison at each move. You might as wel l keep the code clea n by doing it in separate sweeps.
CrackingTheCodingl nterview.com I 6th Edition
585
Sol utions to Chapter 1 7
l::t:value index
next 1 next
5
7
5
9
3
5
7
9
s
8
8
.9
7
2
4
5
6
7
i�
�!Hi
l
0 .. ) 2 1 )':lit
0
8
9
10
11
12
13
14
15
16
5
5
10
10
10
10
10
11
x
x
x
x
x
3
5
5
5
5
7
7
7
7
7
7
12
12
12
12
12
x
x
x
x
2
2
2
9
9
9
9
9
9
9
15
15
15
15
15
15
x
1
next 9
I Hard
1
The findNext i n st a n c e fu nction can now just use this table to find the next occurrence, rather than doing a sea rch. But, actually, we ca n make it a bit simpler. Using the table above, we can quickly com pute the closure of each index. It's just the max of the column. If a column has an x in it, then there is no closure, at this indicates that there's no next occurrence of that character. The difference between the i ndex a nd the closure is the smallest suba rray sta rting at that index. 7,
5, o,9
0 '"2"' :i�t \'g
5
�\ii �.,
index
0
1
3
5
6
7
8
next 1
5
5
5
5
5
5
10
10
10
next 5
1
1
7
7
7
7
7
7
12
next 9
2
2
2
9
9
9
9
9
9
closure
5
5
7
9
9
9
10
10
d iff .
5
4
5
6
5
4
4
3
= 0 ; i - - ) { 22 if ( bigArray [ i ] == va l u e ) { 23 next = i ; 24 } 2S next s [ i ] = next ; 26 } 27 return next s ; 28 } 29 30 /* Get closure for each index . * / 9
586
Cracking the Coding Interview, 6th Edition
Solutions to Cha pter 1 7 31
32
33
34 35 3'
i nt [ ] getClos u res ( i nt [ ] [ ] next E lements ) { int [ ] maxNext E lement = new i nt [ next E lements [ 0 ] . lengt h ] ; for ( in t i = 0; i < nextElemen ts [ 0 ) . lengt h ; i++ ) { getClos ureFori ndex ( nextE lements , i ) ; maxNextEl ement [ i ] =
} return maxNext E lement ;
37
}
39
/*
38
40
41 42 43
44
45
45 47
4� 49
50 51
I Hard
Given a n index and the t a b le of next element s , find the closure for t h i s i ndex (which will be the min of this column ) . * / int getClosureForindex ( i nt [ ] [ ] next E lement s , int index) { int max = - 1 ; for ( i nt i = 0 ; i < nextE lements . lengt h ; i++ ) { if ( next E lements [ i ] [ index) == - 1 ) { return - 1 ; } max Mat h . ma x ( ma x , nextE lements [ i ) [ index] ) ; } ret urn max ; } *
=
52
/* Get s hortest closure . * / Range getShortestClos ure ( i nt [ ) closures ) { int bestStart = - 1 ; i nt bestEnd = - 1 ; 55 56 for ( int i 0; i < closures . lengt h ; i++ ) { 57 if ( c los u res [ i ) == - 1 ) { 58 b rea k ; 59 } 60 i nt current = closures [ i ] - i ; i f ( bestSt a rt = = - 1 I I cu rrent < bes t E nd - bestSt a rt ) { 51 62 bestStart = i ; 63 best E nd = closures [ i ) ; 64 } 65 } return new Ra nge ( bestSt a rt , bestE nd ) ; 66 67 } 53 54
=
This a l g o rithm wi l l potentially take O ( S B ) time, where B is the length of
bigSt ring and S is the length of sma l lSt ring. This is because we do S sweeps t h ro u g h the a rray to b u i l d up the n e xt occu rrences t a b l e
a n d e a c h swee p ta ke s O ( B ) time. It uses
0(SB)
space.
More Optimized While o u r soluti o n is fa i rly o pt i m a l, we c a n red uce the space usage. Remember the table we created:
v a l u e�.
i\ 7
1
5
5
n e xt 9
2
2
next
I
1
0
index
next 5
clos u re
I
1 5
9; e
5
I
1 5
2
3
7
7
5
I
2 7
2 4
I
9
5
s
5
9
1
7
I
9 9
I
;3
,,,
'
6
s
7
9
7
8
9
10
10
10
9
9
12
12
1s
5
10
10
7
7
7
9
9
9
10
I
9
10
I
12
12
1
J
11
10
I
12 15
11
I
12 1s
15
5
12 x
12
I 15 x
·B
"8
14
13 x
x
1s x
x
I
x
15 x
9
7
15
16
x
x
x
x
15 x
1
x x
CrackingTheCodinglnterview.com \ 6th Edition
587
Solutions to Cha pter 1 7
I Hard
In actuality, all we need is the closure row, which is the minimum of all the other rows. We don't need to store all the other next occu rrence information th e e nti re tim e. I nstead, as we do each sweep, we just u pdate the closure row with the minimums. The rest of the algorithm works esse nti a lly the same wa y. 1
Ra nge s hortestSupersequence ( int [ ] big, i nt [ ] sma l l ) { i nt [ ] closures = getClos ures ( big, small ) ; return getShortestClos u re ( closures ) ; }
2
3 4 5
6 7
8
9 10 11
12 13
14
/ * Get closure for each index . */ i nt [ ] getClosure s ( i nt [ ] big, i nt [ ] s ma l l ) { i nt [ ] closure = new int [big . length ] ; for ( i nt i = 0; i < sma l l . lengt h ; i++) { sweepForClosure ( b ig, closure, small [ i ] ) ; } r e t u r n closure; }
15
/ * Do backwa rds sweep a n d update the closures list with the next occurrence of 1 6 * v a l u e , if i t ' s l a t e r t h a n the c u r r e n t clos u re . * / 17 void sweepForClosure ( i nt [ ] b i g , i nt ( ] c l o s u re s , i n t v a l u e ) { int next = - 1 ; 18 for ( i nt i = big . lengt h - 1 ; i >= 0 ; i - - ) { 19 20 if ( b ig [ i ] == value) { next = i ; 21 22 } 23 if ( ( next == -1 1 1 closures [ i ] < next ) && 24 ( closures ( i ] ! = - 1 ) ) { closures [ i ] = next ; 25 26 } 27 } 28 } 29
30 /* Get shortest closure . * / Range getSho rtestClos u re ( int [ ] closure s ) { Range s hortest = new Range ( 0 , closures [ 0 ] ) ; 33 for ( int i = 1; i < closures . lengt h ; i++ ) { 34 if ( c losures [ i ] == - 1 ) { 35 brea k ; 36 } 37 Range range = new Range ( i , closures [ i ] ) ; 38 if ( ! s hortest . s horterTha n ( range ) ) { 39 shortest range ; 40 } 41 } 42 return shortest ; 43 } 31 32
=
This stil l runs in O ( S B ) time, but it now only takes O ( B ) additional m e m o ry
.
Alternative & More Optimal Solution
There's a to ta lly different way to approach it. Let's suppose we had a list of the occurrences of each element
in
smallArray.
588
Cracking the Coding Interview. 6t h Edition
Solutions to Chapter 1 7
value index
7 0
5 1
1 - > { 5 , 10 , 1 1 } - > { 1 , 7 , 12} 9 - > { 2 , 3 , 9 , 15}
9 2
9 3
2
4
1 5
3
16
7
6
I Hard
5
What is the very first valid su bsequence (which conta ins 1 , 5, and 9)? We can just look at the heads of each list to tell us this. The minimum of the heads is the start of the range and the max of the heads is the end of the ra nge. In this ca se, the fi rst ra nge is [l , 5]. Th is is cu rrently our "best" su bsequence. How can we find the next one? Wel l, the next one will not include index 1 , so let's remove that from the list. 1 - > { 5 , 10, 1 1 } 5 - > { 7 , 12 } 9 - > { 2 , 3 , 9, 15 } The next subsequence is [2, 71. This is worse than the earlier best, so we can toss it. Now, what's the next su bsequence? We can remove the m i n fro m earlier (2) a n d find out. 1 - > { 5 , 10, 11} 5 -> { 7 , 12 } 9 - > { 3 , 9 , 15 } The next su bsequence is [3, 7], which is no better or worse than our cu rrent best. We can continue down this path each time, repeating this process. We wil l end up iterating through all "m inimal" su bsequences that start from a given poi nt. 1 . Cu rrent subsequence is [min of heads, max of heads]. Compare to best su bsequence and u pdate if
necessa ry. 2. Remove the minimum head. 3. Repeat.
This wi ll give us an 0 ( S B ) time complexity. This is because for each of B elements, we are doing a compar ison to the S other list heads to find the minimum. This is pretty good, but let's see if we ca n make that minimum computation faster. What we're doing in these repeated minimum calls is ta king a bunch of elements, finding and removi ng the mini mum, a d d i ng in one more element, and then fi n d i ng the minimum again. We can make this faster by using a min-heap. Fi rst, put each of the heads in a min- heap. Remove the minimum. Look up the list that this minimum came from and add back the new head. Repeat. 10 get the list that the minimum element came from, we'll need to use a Hea pNode class that stores both the l o c a t ionWi t h i n l i st (the index) and the l i st Id. This way, when we remove the minimum, we can jump back to the correct list and add its new head to the heap. 1 Range s hortestSuperseq uence ( i nt [ ] a r ray, int [ ] e lements ) { 2 3
4
5
6
7
8
9
10 11
=
Arraylist > locations getLocations F o r E lement s (a rray, element s ) ; if ( locations null ) return null; return getShortestClo s u re ( locations ) ; ==
}
/ * Get list of queues ( li n ked lists ) storing t he ind ices at which each element in * sma l lArray a p pears in bigArray . * /
Arraylist > get locat ionsForElement s ( int [ ] big, int [ ] sma l l ) { / * Initialize ha s h ma p from item va lue to locat ions . */ HashMa p < I nteger, Queue < Intege r > > item locations =
CrackingTheCodinglnterview.com j 6th Edition
589
Sol utions to Cha pter 1 7 12
I Hard
new HashMa p < I nteger, Queue < I ntege r > > ( ) ; for ( int s : small ) { Queue queue = new L i nked List< I nteger> ( ) ; itemlocations . put ( s , queue ) ; }
13
14 15
16
17
18
/ * Wa l k t h rough big a rray, adding t he item locations to hash map */ for ( int i = 0; i < big . lengt h ; i++ ) { Queue < I nteger> q ueue = itemlocations . get ( b ig [ i ] ) ; if ( q ueue ! = null ) { queue . add ( i ) ; } }
19
20 21
22 23
24 25
29 27
Arraylist > alllocations = new Arraylist > ( ) ; a l lLocation s . addAll ( itemLocat ions . va lues ( ) ) ; ret urn a l l location s ;
28
29 } 30 31
32
33
34
Range getShortestClosure (ArrayList > list s ) { PriorityQue ue minHeap = new PriorityQue ue ( ) ; i nt m a x = I ntege r . MI N_VALU E ;
35
36 37
38 39
40 41 42 43 44 45 49 47 48 49
50 51
52
53
54 55 Si
57
58
59
90 il
62 63
94 95 66
67
590
/ * I nsert m i n element from e a c h list . * / fo r ( int i = 0; i < l i s t s . s ize ( ) ; i++ ) { int head = lists . get ( i ) . remove ( ) ; minHeap . a d d ( new HeapNode ( head, i ) ) ; max Mat h . max ( max, head ) ; } =
int min = minHeap . pee k ( ) . locat ionWit h i n L ist ; int bestRangeMin min; int bestRangeMax = ma x ; =
while ( t rue ) { I * Remove min node . * / HeapNode n = minHea p . poll ( ) ; Que ue < I nteger> list = lists . get ( n . listid ) ; / * Compa re ra nge to best range . */ min = n . locationWit h i n l i s t ; if ( m a x - min < bestRa ngeMax - bestRa ngeM i n ) { bestRa ngeMax = m a x ; bestRa ngeMi n min; } =
I * If there a re no more e leme n t s , then t here ' s no more s ubsequences and we * can brea k . * / if ( l ist . s ize ( ) 0) { break ; } ==
/ * Add new head of l i s t to heap . * / n . locationWit hinList = list . remove ( ) ; minHeap . add ( n ) ; max = Mat h . max ( max, n . locationWit h i nl i s t ) ;
Cracking the Co d i n g I nterview, 6th Edition
Solutions to Cha pter 1 7 68
}
78
ret urn new Range ( bestRangeMin, bestRangeMax ) ;
71
l Hard
}
We're going through B elements i n getSh orte stClosu re, a n d each time pass i n the for loop will ta ke O ( l og S ) time (the time to i nsert/remove from the heap). Th is algorith m will therefore take 0 ( B log S ) time i n the worst case. 1 to N appearing exactly once, except for one n u m ber that is missing. How can you find the missing n um ber i n O ( N ) time and 0 ( 1 ) space? What if there were two num bers missi ng?
1 7.1 9 Missing Two: You are given an array with all the n u m bers from
pg 7 89
SOLUTIONS ··· ···· · · ·· · ···· · · · ·
·· ·· ·· · · · ·· · ··· ··· · · · · ··· ·· · · · · · · ···-·-----
Let's sta rt with the first part: find a missing n umber i n O ( N ) time a n d 0 ( 1 ) space. Part 1 : Find One Missing N umber
We have a very constrain ed problem here. We can't store all the va lues (that wou ld take O ( N ) space) and yet, somehow, we need to have a "record" of them such that we can identify the missing n u m ber. This suggests that we need to do some sort of computation with the values. What characteristics does this computation need to have? Unique. If this computation gives the same result on two a rrays (which fit the description in the
problem), then those a rrays must be equivalent (same missing n u mber). That is, the result of the com pu tation must u n iquely correspond to the specific a rray and missing number. Reversible. We need some way of getting from the result of the calculation to the missing number. •
Constant Time: The calcu lation can be slow, but it must be constant time per element i n the a rray. Constant Space: The calcu lation can req u i re additional memory, but it must be O ( 1 ) memory.
The "unique" requ i rement is the most interesting-and the most challenging. What calcu lations can be performed on a set of n u m bers such that the missing n u mber will be discoverable? There are actually a n umber of possibilities. We could do someth ing with prime n um bers. For example, for each va lue x in the a rray, we mu ltiply r e s u l t by the xth prime. We wou ld then get some va lue that is i ndeed u n ique (since two different sets of primes can't have the same product). Is this reversible? Yes. We could ta ke r e s u l t and d ivide it by each prime number: 2, 3, 5, 7, and so on. When we get a non-i nteger for the ith pri me, then we know i was missing from our a rray. Is it consta nt time a n d space, though? Only if we had a way of getting the ith prime n um ber i n 0 ( 1 ) time and 0 ( 1 ) space. We don't have that. What other calcu lations could we do? We don't even need to do all this prime number stuff. Why not just multiply all the n u m bers together? •
•
•
Unique? Yes. Pictu re 1 * 2 * 3 * * n . Now, imagine crossing off one number. This will give us a different result than if we crossed off any other number. Constant time and space? Yes.
Cracki ngTheCodinglnterview.com I 6th Edition
591
Solutions to Cha pter 1 7
I Hard
Reversible? Let's think a bout this. If we compare what our product is to what it would have been
without a number removed, ca n we find the missing number? Sure. We just divide fu l l_prod u c t by a c t u a l_prod u c t. This will tel l us which number was missing from a ctua l_product. There's just one issue: this product is real l y, realry, realry big. I f n is 20, the product will be somewhere around 2,000,000,000,000,000,000.
We can stil l approach it this way, but we'll need to use the Big!nteger class. 1
2
3 4
int m i s s i ngOne ( int [ ] a rr ay ) { Biginteger fullProduct = p roductToN ( a rray . length + 1 ) ; Biginteger actualProd uct = new Bigintege r ( "l") ; for ( int i 0 ; i < a r ray . lengt h ; i++ ) { Big Integer value = new Bigintege r ( a r ray[ i ] + '"' ) ; actualProduct = actualProduct . multi p l y ( value ) ; }
5
=
6 7
8 9
10 Biginteger m i s s i ngNumber = fullProduct . d ivide ( a ctualProduct ) ; 11 ret u r n I nteger . parseint (missi ngNumber . toSt ring ( ) ) ; 12 } 13 14 Biglnteger p roductToN ( int n ) { 15 Biglnteger ful lProduct new Biglntege r ( "l" ) ; 16 for ( int i 2 ; i < = n; i++) { 17 fullProduct fullProduct . mu l t iply( new Biglnteger ( i + " ") ) ; 18 } 19 retu r n fullProduct ; 20 } =
=
=
There's no need for all of this, though. We can use the sum instead. It too will be unique. Doing the sum has a nother benefit: there is a l ready a closed form expression to compute the sum of n ( n +l) numbers between 1 and n. This is --2- .
I
Most candidates probably won't remember the expression fo r the sum of numbers between 1 and n, and that's okay. You r interviewer mig ht, however, ask you to derive it. Here's how to think about that: you can pa ir u p the low and high va lues in the sequence of 0 + 1 + 2 + 3 + . . . + n to get: ( 0 , n ) + ( 1 , n - 1 ) + ( 2 , n - 3 ) , and so on. Each of those pairs has a su m of n and there a re ..!'.p.. pa irs. But what if n is even, such that ..!lf!.. is not an integer? In this case, J?a ir n (n+l) n/ pa i rs with sum n+l. Either way, the math works out to -2- . up low and high va lues to get /2
Switching to a sum will delay the overflow issue su bsta ntially, but it won't wholly prevent it. You should discuss the issue with you r i nterviewer to see how he /she wou ld l i ke you to handle it. Just mentioning it is plenty sufll cient for many interviewers. Part 2: Find Two Missing Numbers
This is su bsta ntially more difficu lt. Let's start with what our earlier approaches will tell us when we have two missing numbers. Sum: Using this approach will give us the sum of the two values that a re missing. Product: Using this approach will give us the product of the two va lues that are missing. U nfortunately, knowing the sum isn't enough. If, for exa mple, the sum is 1 0, that cou ld correspond to (1 , 9), (2, 8), and a handful of other pairs. The same could be said for the product.
592
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 1 7
I Hard
We're again at the same point we were in the first part of the problem. We need a calculation that can be applied such that the resu lt is unique across all potentia l pairs of missing numbers. Perhaps there is such a calcu lation (the prime one wou ld work, but it's not constant time), but you r inter viewer probably doesn't expect you to know such math. What else can we do? Let's go back to what we can do. We can get x + y and we can a lso get x * y. Each result leaves us with a number of possi bilities. But using both of them na rrows it down to the specific numbers. s um - > y = s um - x x + y product - > x ( s um - x ) = product x * y x * s um - x2 = product x * s um - x2 - product = 0 - x2 + x * s um - product = 0
At this point, we can apply the quadratic formula to solve for x. O nce we have x, we can then compute y. There are actually a number of other calculations you can perform. In fact, almost any other calculation (other than "linear" calcu lations) will give us values for x and y . For thi s pa rt, let's use a different calculation. Instead of using the product of 1 * 2 * . . . * n , w e can use the sum of the squa res: 12 + 22 + . . . + n2• This will make the Biginteger usage a little less critical, as the code will at least run on small values of n. We can discuss with ou r i nterviewer whether or not this is important. x + y s -> y = s - x x2 + y2 = t t - > x2 + ( s - x ) 2 =
=
2x2 - 2s x + s 2 - t
Reca ll the quadratic formula: x ( - b + - s q rt ( b2 =
-
0
4 a c ) ] I 2a
where, in this case: 2 a b c
=
-25 = 52-t
Implementing this i s now somewhat straightforward. 1 i nt [ ] m i s s i ngTwo ( i nt [ ] a rr ay ) {
int max_value = a r ray . length + 2 ; int rem_sq u a re = s q u a reSumToN ( max_va lue, 2 ) ; int rem_one = max_va lue * ( max_va lue + 1 ) I 2 ;
2 3
4 5
6 7
for ( i nt i = 0 ; i < a r ray . lengt h ; i++ ) { rem_square - = a r ray [ i ] * a rray [ i ] ; rem_one = a rr ay [ i ] ; }
8 9
-
10 11
12 13
}
return solveEquation ( rem_one, r em_sq u a r e ) ;
14
int squa reSumToN ( int n , int powe r ) { i n t s um = 0 ; fo r ( i nt i 1; i < = n; i++ ) { 16 17 sum += ( i nt ) Mat h . pow ( i , powe r ) ; lo } 19 retu rn s u m ; 20 } 15
=
CrackinglheCodinglnterview.com I 6th Edition
593
Solutions to Chapter 1 7 I Hard 21
22 int [ ] solveEquation ( int rl, int r2) { / * a xA2 + bx + c * --> 24 * x = [ - b + - s q r t ( bA2 - 4a c ) ) I 2a 25 * In this case , it has to be a + no t a 26 27 int a 2; 28 int b 2 * rl ; 29 int c rl * r l - r 2 ;
23
-
*/
c
-
=
30 31
double p a rt l = - 1 * b; double part2 = Math . sqrt ( b * b - 4 * a * c ) ; double part3 2 * a;
32
33
=
34 35
int solutionx ( i nt ) ( ( pa rt l + part2) I part3 ) ; int solutionY = rl - s o l ut ionX; =
36
37 38 39
40
int [ ) solution = { solutionx, solut ionY} ; ret u rn solution ; }
You might notice that the quadratic formu la usually gives us two answers (see the + or - pa rt), yet in our code, we only use the ( +) resu lt. We never checked the ( - ) answer. Why is that? The existence of the "a lternate" solution doesn't mean that one is the correct solution and one is "fake:' Jt means that there a re exactly two va lues for x which will correctly fu lfi ll our equation: 2x2 - 2sx + ( s 2 - t ) = 0. That's true. There a re. What's the other one? The other value is y ! If this doesn't immediately make sense t o you, rememberthat x a n d y a re i nterchangeable. Had w e solved for y earlier instead of x, we would have wou nd up with an identical equation: 2y 2 - 2s y + ( s 2 - t ) 0. So of cou rse y could fu lfi ll x's equation and x cou ld fu lfill y's equation. They have the exact same equa tion. Since x and y a re both solutions to equations that look like 2 [ s ometh i n g ] 2 - 2s [ s omet h i n g ] + s 2 - t = 0, then the other s ometh i n g that fu lfills that equation must be y. =
Sti ll not convinced? Okay, we ca n do some math. Let's s a y we tookt h e a lternate va luefor x: [ - b - s q rt ( b 2 - 4a c ) ] I 2a . What's y? x + y r1 y r1 x r 1 - [ - b - s q rt ( b 2 4a c ) ] / 2 a [ 2a * r 1 + b + s q rt ( b2 - 4ac ) ] / 2 a =
=
-
-
Pa rtially plug i n va lues for a and b, but keep the rest of the equation as-is: = [ 2 ( 2 ) * r 1 + ( - 2 r , ) + sq rt ( b 2 - 4a c ) ] /2a = [ 2 r + s q rt ( b2 - 4ac ) ] /2a 1 Reca ll that b =
=
- 2 r 1 . Now, we wind u p with this equation: 4a c ) ] /2a [ - b + sqrt ( b2
Therefore, if we u se x for the va l u e for y.
-
=
( pa rt l + p a rt 2 ) I pa rt3, then we'll get ( pa rt1 - pa rt 2 ) I p a rt 3
We don't care which one we call x and which one we ca ll y, so we can use either one. lt'll work out the same in the end.
594
Cracking the Coding Interview, 6th Edition
Solutions to Cha pter 1 7
I Hard
1 7.20 Continuous Median: Numbers are randomly generated and passed to a method. Write a program
to find and mainta in the median value as new values are generated. pg 7 8 9
SOLUTIONS
One solution is to use two priority heaps: a max heap for the values below the median, and a min heap for the values above the median. This will divide the elements roughly in ha lf, with the middle two elements as the top of the two hea ps. This makes it trivial to find the median. What do we mean by "roughly in ha lf:' though? "Roughly" means that, if we have a n odd number of values, one heap will have a n extra value. Observe that the following is true: If maxHea p . s i z e ( ) > minHea p . s i z e ( ) , maxHe a p . top ( ) will be the median. ==
If maxHea p . size ( ) minHe a p . s i z e ( ), then the average of maxHea p . top ( ) and minHe a p . top ( ) will be the median. By the way in which we rebalance the hea ps, we will ensure that it is always maxHeap with extra element. The algorithm works as follows. When a new va lue arrives, it is placed in the maxHea p if the value is less than or equal to the median, otherwise it is placed into the minHe ap. The heap sizes can be equal, or the maxHe ap may have one extra element. This constraint can easily be restored by shifting an element from one heap to the other. The median is available in consta nt time, by looking at the top element(s). Updates take O( log ( n ) ) time. 1
2
3
4
5 6 7
Compa rato r < I ntege r > maxHea pCompa rator, mi nHea pCompa rator; PriorityQueue< I ntege r> maxHea p, minHeap; vo id /* * if
add NewNumbe r ( int ra ndomNumbe r ) { Note : addNewNumber maintains a condition that maxHea p . s i ze ( ) >= minHea p . s i ze ( ) */ ( ma xHea p . s i ze ( ) == minHeap . s i ze ( ) ) { if ( ( minHeap . pee k ( ) ! = null) && ra ndomNumber > minHeap . pee k ( ) ) { maxHea p . offer ( minHea p . po l l ( ) ) ; minHea p . offe r ( ra ndomNumber ) ; } else { maxHea p . offer ( ra ndomNumbe r ) ; } } else { if ( ra ndomNumber < maxHea p . peek ( ) ) { minHeap . offe r ( ma xHea p . po ll ( ) ) ; maxHea p . offe r ( ra ndomNumbe r ) ; } else { minHea p . offer ( randomNumber ) ; } }
8
9
10
11
12
13 14 15
16
17
18 19
20 21
22 23
24 25
}
26 27
double getMedian ( ) { /* maxHeap is a lways at least a s big a s mi nHea p . So if maxHeap i s empty, then 28 * minHeap is also . */ 29 if ( ma xHea p . i s Empty ( ) ) { 30 return 0 ; 31 }
CrackingTheCodinglnterview.com j 6th Edition
595
Solution s to Chapter 1 7 32
if ( maxHeap . s ize ( )
I Hard
m i nH ea p . siz e ( ) ) { ret urn { ( double ) m i nHea p . peek ( ) + ( double )maxHe a p . peek( ) ) / 2 ; } else {
33 ?, tl
/ * If maxHeap and minHeap a re of
35
-�6 37 38
39
==
}
}
different s i zes , then maxHeap must have e l em e nt . */
* extra element . Return max Hea p ' s t o p return m ax Hea p . p ee k ( ) ;
one
1 7.21 Volume of Histogram: Imagine a histogram (bar graph). Desig n an algorithm to compute the
volume of water it could hold if someone poured water across the top. You can assume that each histogram bar has width 1 . EXAMPLE
lnpu t { 0 , 0 , 4 , 0 , 0 , 6 , 0 , 0 , 3 , 0 , 5 , 0 , 1 , 0 , 0 , 0 } ( B l a c k b a r s a re t h e h istogram . Gray i s water . )
Output: 26
0 0 4 0 0 6 0 0 3 0 5 0 1 0 0 0 pg 189
SOLUTION
This is a difficult problem, so let's come up with a good example to help us solve it.
0 0 4 0 0 6 0 0 3 0 8 0 2 0 5 2 0 3 0 0 We should study this example to see what we ca n learn from it. What exactly dictates how big those gray areas are? Solution #1
Let's look at the tallest bar, which has size 8. What role does that bar play? It plays a n important role for being the highest, but it actually would n't matter if that bar instead had height 1 00. It would n't affect the vol u me. The tal lest bar forms a barrier for water on its left and rig ht. But the volume of water is actually controlled by the next hig hest bar on the left and right. Water on immediate left of tallest bar: The next tallest bar on the left has height 6. We can fi l l up the
•
a rea in between with water, but we have to deduct the height of each histogram between the tallest and next tallest. This gives a volume on the immed iate left of: ( 6 - 0 ) + ( 6 - 0 ) + ( 6 - 3 ) + ( 6 - 0 ) = 21. Water on immediate right of tallest bar: The next tallest bar on the right has height 5. We can now
596
C racki ng the Coding I nterview, 6th Editi o n
Hard
Solutions to Chapter 1 7 I com pute the volume: ( 5 - 0 )
+
( 5 - 2 ) + ( 5 -0)
1 3.
Th is just te lls us part of the volume.
0 0 4 0 0 6 0 0 3 0 8 0 2 0 5 2 0 3 0 0
What about the rest? We have essenti ally two su bgraphs, one on the left and one on the right. To find the volume there, we repeat a very simi lar process. 1.
Find the max. (Actually, this is given to us. The hig hest on the left subgraph is the right border (6) and the hig hest on the right subgraph is the left border (5).)
2. Find the second ta llest in each subgraph. I n the left su bgraph, this is 4. In the right subgraph, this is 3. 3. Compute the vo lume between the tallest and the second ta llest. 4. Recurse on the edge of the graph. The code below im plements this a lgorithm. 1
2 3
4 5
int computeHistogramVolume ( i nt [ ] histogra m ) { int start = 0; int end hi stogram . length 1; �
int max = findindexOfMa x ( h i stogram, start , end ) ; s ubg r aphVol ume ( h ist og ra m, start , max, true ) ; int leftVol ume int rightVolume subgraphVolume ( h i stogram, max, end, fals e ) ;
6
=
7
=
8
9
10 11
12 13
14
15 16
17 18 19
20
21
22
23
24
25 26 27
2�
29
30
-
return l eftVolume + rightVol ume ; } /* Compute the vol ume of a s u bgraph of the histogram . One max is at e ither start * or end (de pend ing on is Left ) . Find s econd tallest , then compute volume between * t a l lest and s econd tal lest . Then compute volume of s u bgraph . * / i n t subgraphVolume ( int [ ] histogram, i n t start, i n t end, boolean i s l ef t ) { if ( s t a rt > = end ) return 0; int sum = 0; if ( is left ) { int max = findindexOfMa x ( histogram, start , end - 1 ) ; sum += borde redVolume ( histogram, max , end ) ; s um += s u bgraphVolume ( histogram, s t a rt , max, is left ) ; } else { int max = findindexOfMa x ( histogram, start + 1 , end ) ; sum += borde redVol ume ( h istogram, s t a rt , max ) ; sum += s u bgraphVolume ( h istogram, max , end, is Left ) ; } return sum; }
CrackingTheCodinglnterview.com I 6t h Edition
I
597
Solutio ns to Chapter 1 7
I Hard
F ind tallest b a r in h istogram between s t a rt and end . * / int findindexOfMax( i nt [ ] histogram, int start , int end ) { i n t indexOfMax = start ; 33 34 for ( i nt i = s t a rt + 1 ; i < = end ; i++) { if ( h i stogram ( i ] > histogram ( indexOfMax] ) { 35 i ndexOfMax = i ; 36 37 } 38 } ret u r n i ndexOfMax ; 39 40 } 31
32
41 42
43
44
45
46
/*
/* *
Compute volume between start and end . As s umes t hat t allest bar i s at s t a rt and s econd t a l lest is at end . * / int borderedVolume ( int [ ] h istogram, int start , int e nd ) { if ( st a rt >= end ) ret u r n 0 ;
47
int min = Mat h . mi n ( hi stogram [ st a rt ] , hi stogram [ e nd ] ) ; int sum 0; for ( int i = start + 1; i < e n d ; i++) { s um += min - histogram [ i ] ; } ret u r n s um ;
48
=
49
50
51
52
53
}
This a lgorithm takes O ( N2 ) time in the worst case, where N is the nu mber of bars in the histogram. This is because we have to repeatedly sca n the histogram to find the max height. Solution #2 (Optimized)
To optimize the previous algorithm, let's think about the exact cause of the i nefficiency of the prior a lgo rithm. The root cause is the perpetual calls to find ind exOfMax. This suggests that it should be our focus for optimizing. One thing we should notice is that we don't pass in a rbitrary ra nges i nto the f i nd i nd exOfMax function. It's actually a lways finding the max from one point to an edge (either the right edge or the left edge). Is there a quicker way we could know what the max height is from a given point to each edge? Yes. We could precompute this information in O ( N ) time. In two sweeps through the histogram (one moving right to left and the other moving left to right), we can create a table that tells us, from any index i, the location of the max index on the right a nd the max index on the left.
INJEX: HEIGfr: INJEX LEFT MAX : INDEX RIG-ff MAX:
0 3 0 5
1 1 0 5
2 4 2 5
3 0 2 5
The rest of the algorithm precedes essentially the same way.
5 98
I
Cracking the Coding Interview, 6th Edition
4 0 2 5
5 6 5 5
6 0 5 7
7 3 5 7
8 0 5 9
9 2 5 9
Solutions to Chapter 1 7 We've chosen to use a H i s t ogramData two-dimensional array. 1
2 3
4 5
HistogramData [ ] data
10
17
18
19
return leftVol ume + rightVolume ; }
HistogramData [ ] c reateHistogramDat a ( i nt [ ] histo) { HistogramData [ ] histogram = new HistogramData [ histo . lengt h ] ; for ( int i = 0; i < histo . lengt h ; i++) { histogram [ i ] = new HistogramDat a ( histo [ i ] ) ; }
20
/* Set left max index . * / i nt max!ndex = 0; for ( i nt i = 0; i < histo . lengt h ; i++ ) { if ( h isto [ maxlndex ] < histo [ i ] ) { maxlndex = i ; } histogram [ i ] . set LeftMaxlndex (maxi ndex ) ; }
21 22 23 24 25 26 27 28
29 36
/* set right max index . */ maxrndex = histogram . length - 1; for ( i nt i = histogram . lengt h - 1 ; i > = 0 ; i - - ) { if ( histo [maxlndex ] < histo [ i ] ) { maxlndex = i ; } histogram [ i ] . setRightMaxi ndex (maxindex ) ; }
31
32
33
34
35
36
37
38
39
40
createHistogramDat a ( histogram ) ;
=
9
15 16
=
int max = data [ 0 ] . getRightMaxlndex ( ) ; // Get overall max subgraphVol ume (data , s t a rt , max, true ) ; i nt leftVo lume int rightVolume = subgraphVolume ( d a t a , max, end , fals e ) ;
8
13 14
i nformation, but we could a lso use
-
7
12
extra
int computeHistogramVolume ( i nt [ ] histogram) { int s t a rt = 0; int end = histogram . length 1;
6
11
object to store this
I Hard
return histogram; }
41 42
/* Compute t he volume of a subgraph of t he histogram . One max is at either start * or end (d epend ing on is Left ) . F i nd second tallest, then compute vol ume between 43 * t a llest and second tallest . Then compute vol ume of subgraph . */ 4 4 int s u bgraphVolume (HistogramData [ ] h istogram, int s t a rt , int end , 45 boolea n is Left ) { 46 if ( st a rt >= end ) ret u r n 0 ; 47 int sum = 0 ; if ( is left ) { 48 int max = histogram [end - 1 ] . getleftMaxlndex ( ) ; 49 50 s um + = borderedVolume ( histogram, max, end ) ; 51 s um + = s u bgra phVol ume ( histogram, start , max, i s left ) ; 52 } else { int max = histogram [ s t a rt + 1 ] . get RightMaxi ndex ( ) ; 53 54 s um += borde redVol ume ( histogram, s t a r t , max) ;
CrackingTheCodingl nterview.com I 6th Edition
599
a
Solutions to Cha pter 1 7 55
sum += su bgra phVolume ( h istogram , max , end , is Left ) ;
56 57
}
58
59 } 60
61 62
ret urn sum;
/* Compute volume between sta rt and end . Ass umes that t a l lest ba r is at start and * second t a l lest is at end . */ int borde redVolume (HistogramData [ ] data, int start , int end ) { if ( st a rt >= end ) ret urn 0;
63
64
65 66
i nt min = Math . mi n ( data [ s t a rt ] . getHeight ( ) , data [ e nd ] . getHeight ( ) ) ; int sum = 0; fo r ( i nt i = s t a rt + 1 ; i < end; i++ ) { sum += min - data [ i ] . getHeight ( ) ; } ret u rn sum ;
67
68
69
70 71 72 73
74 75 76
77
78
} public class HistogramData { private int height ; private int leftMaxindex = - 1 ; private int rightMaxl ndex = - 1 ;
79
pu blic public public public public public
80 81 82 83
84
85
I Hard
HistogramData ( int v ) { height = v; } int getHe ight ( ) { ret urn height ; } int get LeftMaxlndex ( ) { ret u r n leftMaxlndex; } idx; } ; void set LeftMaxindex ( int idx) { leftMaxlndex int getRightMaxlndex ( ) { ret u rn rightMa xindex; } void setRightMaxlndex ( int idx) { rightMaxlndex = idx; } ;
}
This algorithm ta kes O ( N ) time. Si nce we have to look a t every bar, we cannot d o better than this. Solution #3 (Optimized & Simplified)
While we ca n't make the solution faster in terms of big 0, we can m a ke it much, much simpler. Let's look at an example again in light of what we've just lea rned a bout potentia l a lgorithms.
0 0 4 0 0 6 0 0 3 0 8 0 2 0 5 2 0 3 0 0
As we've seen, the volume of water in a particu lar area is determi ned by the tallest bar to the left and to the right (speci fically, by the shorter of the two tallest bars on the left and the ta llest bar on the right). For example, water fills in the area between the bar with height 6 and the bar with height 8, up to a height of 6. It's the second tallest, therefore, that determines the height. The total volume of water is the volume of water a bove each histogram bar. Ca n we efficiently com pute how much water is a bove each histogram bar?
600
Cracking the Cod ing I n te rvi ew,
6th E d iti on
Solutions to Cha pter 1 7
\
Hard
Yes. In Solution #2, we were a ble to precompute the height of the tallest bar on the left and rig ht of each i ndex. The minimums of these wil l ind icate the "water level" at a bar. The difference between the water level and the height of this bar will be the volume of water.
HEIGHT : 0 0 4 0 0 6 0 0 3 0 8 0 2 0 5 2 0 3 0 0 LEFT MAX: 0 0 4 4 4 6 6 6 6 6 8 8 8 8 8 8 8 8 8 8 RIG-IT MAX : 8 8 8 8 8 8 8 8 8 8 8 5 5 5 5 3 3 3 0 0
MIN : 0 0 4 4 4 6 6 6 6 6 8 5 5 5 5 3 3 3 0 0 DELTA: 0 0 0 4 4 0 6 6 3 6 0 5 3 5 0 1 3 0 0 0
Our algorithm now runs i n a few simple steps: 1 . Sweep left to right, tracking the max height you've seen and setting left max. 2. Sweep right to left, tracking the max height you've seen and setting rig ht max. 3. Sweep across the histogram, computing the minimum of the left max and right max for each index. 4. Sweep across the histog ram, computing the delta between each minimum a nd the bar. Sum these
deltas. In the actual implementation, we don't need to keep so much data around. Steps 2, 3, and 4 can be merged into the same sweep. First, compute the left maxes in one sweep. Then sweep through in reverse, tracking the right max as you go. At each element. calculate the min of the left and right max and then the delta between that (the "min of maxes"} and the bar heig ht. Add this to the sum. / * Go t h rough each bar and co mp u t e the volume of water ab o v e it . 1 2 * vo lum e of water at a bar =
3
h e ight - min (ta l lest bar o n left , ta llest bar on right ) * 4 [ where above equation i s p o sitiv e ] * 5 * Compute the left max i n the first swee p , t hen sweep aga i n to compute t he right 6 * max, minimum of the bar height s , and the delta . * / 7 i nt co mp u t e Hist o gr a mVo l u m e ( i nt [ ] h i s t o ) { 8 / * Get left m ax */ 9 int [ ] leftMaxes = new int [ hist o . l e ngt h ] ; 10 int l e ftM ax hist o [ 0 ) ; 11 for ( int i = 0 ; i < histo . lengt h ; i++ ) { 12 leftMax Mat h . ma x ( leftMa x, histo [ i ] ) ; 13 leftMaxes [ i ] = leftMa x; 14 } =
=
15
16 17 18
19
20
21
22
23
24 25
26
int sum = 0; / * Get right max * / i n t rightMax = histo [ histo . lengt h - 1 ) ; for ( i nt i = hist o . l e ngt h - 1 ; i >= 0; i - - ) { rightMax = Mat h . ma x ( rightMax, histo [ i ] ) ; int secondTallest M a t h . min ( rightM ax , leftMaxes [ i ] ) ; =
/ * If there a re talle r t h i ngs on t he left and right side, t hen t here is water * a bove t h i s ba r . Compute t he vol ume and add to the sum . */ if ( s econdTa llest > histo [ i ] ) { CrackingTheCodinglnterview.corn I 5t h Edition
601
Solutions to Chapter 1 7 27
2�
29
}
30
31 32
I Hard
s um + = s e cond T a l l e st - histo [ i ] ;
}
retu r n sum; }
Yes, this rea lly is the entire code! It is still 0 ( N ) time, but it's a lot simpler to read and write. 1 7 .22 Word Transformer: Given two words of equal length that a re in a dictionary, write a method to
transform one word into another word by changing only one letter at a time. The new word you get in each step must be in the dictiona ry. EXAMPLE In put: DAMP, LIKE Output: DAMP -> LAMP -> LIMP -> LIME -> LIKE
pg 1 89
SOLUTION -----
.
·-·· - ·--·-··- · ·-· ·-··- · ·- ··-·· -· ·-·-·· -· ·- ··-·· -· · ·· · ·-·· ···
Let's start with a na ive solution and then work our way to a more optimal solution. Brute Force
One way of solving this prob lem is to just tra nsform the words i n every possible way (of cou rse checking at each step to ensure each is a va lid word), and then see if we ca n reach the fi n a l word. So, for example, the word bold would be transformed into: £Old, Qo ld, . . . . �o ld b�ld, bQld, . . . , b�ld bo£d, boQd, . . . , bo�d •
bol� bolQ, . . . , bol�
We will term inate (not pursue this path) ifthe string is not a va lid word or if we've already visited this word. This is essentially a depth-fi rst search where there is a n "edge" between two words if they a re only one edit apart. This means that this a lgorithm will not fi nd the shortest path. It will only fi nd a path. If we wa nted to find the shortest path, we would want to use b readth-fi rst search.
1 2
L i n kedlist t r a n sform( St r i ng start , St ring stop , St r i ng [ ] wo rds ) { Has hSet diet = setupDict ionary (words ) ; Has hSet vis ited = new Has hSet ( ) ; r et u r n t ransfo r m ( v i s ited, start , stop , diet ) ; }
3
4 5 6 7
Has hSet set upDictionary (String [ ] wo rd s ) { Has hSet h a s h = new Has hSet ( ) ; for ( St r i ng word : words ) { 9 has h . add (word . to lowerCa se ( ) ) ; 10 8
11 12
}
r et u rn h a s h ;
13
}
15
Linked l i s t < String> t r a n s form ( Ha s hSet visited, St ring startWord,
14
602
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 1 7 16
St ring stopWord, Set < St ring> d ictionary) { if ( st a rtWOrd . equals ( stopWord ) ) { Linked List path new Linked L i s t < St r i ng> ( ) ; path . a dd ( startWord ) ; return pat h ; } else if (vis ited . conta i n s ( st a rtWOrd ) 1 1 ! d ict ionary . cont a i n s ( sta rtWord ) ) { ret u rn null;
17
1�
=
19
20 21 22
23
}
25 26
vis ited . ad d ( startWord ) ; ArrayL i s t < St ring> words = wordsOneAway ( st a rtWord ) ;
24
27 28
for ( St ring word : word s ) { Linked List< St ri ng> p a t h = t ra nsform (vis ited, word, stopWord , dictionary ) ; if ( path ! = n u l l ) { pat h . addFirst ( s t a rtWord ) ; ret u r n pat h ;
29 30 31 32
33 34
35 36
37 38
I Hard
}
}
ret u r n n u l l ;
}
39
Array list wordsOneAwa y ( St ring word ) { Array L i s t words = new ArrayL i s t < St ring> ( ) ; 41 for ( int i = 0 ; i < word . lengt h ( ) ; i++ ) { 42 for ( char c ' a ' ; c < = ' z ' ; c++ ) { 43 St ring w = word . substring ( 0 , i ) + c + word . s ubstring ( i + 1 ) ; words . ad d ( w ) ; 44 45 } 40
=
46 47 48
}
}
ret urn wo rd s ;
One major inefficiency in this algorithm is finding all strings that are one edit away. Right now, we're fi nding the stri ngs that a re one ed it away and then eliminating the invalid ones. Ideal ly, we wa nt to only go to the ones that a re valid. Optimized Solution
To travel to only valid words, we clea rly need a way of going from each word to a list of a l l the valid related words. What ma kes two words "related" (one ed it away)? They a re one ed it away if a l l but one character is the same. For exa mple. b al l and b i l l are one edit away, because they a re both in the form b_l l . Therefore, one approach is to group all word s that look like b_l l together. We can do this for the whole d ictionary by creating a mapping from a "wi ldca rd word" (like b_ll) to a list of all words in this form. For exa mple, for a very small dictionary like { a l l , i l l , a i l , a p e , a l e } the mapping might look like this: _il - > a i l le - > ale ll - > a l l , ill - > ape a_e - > ape, ale a_l - > a l l , a i l
_pe
CrackingTheCodinglnterview.com I 6th Edition
603
Solutions to Chapter 1 7
i -1 - > a i- > a l_ - > ap_ - > il -> -
I Hard
ill ail a l l , ale ape ill
Now, when we want to know the words that are one edit away from a word like a le, we look u p _le, a_e, a nd a l in the hash table. The algorithm is otherwise essentia l ly the same. 1 Linkedlist t ransform ( String start , String stop, St ring [ ] word s ) { 2 HashMa p L i s t < St ring, String> wildca rdToWordList = c reateWi ldca rdToWordMa p ( words ) ; 3 HashSet vis ited = new Has hSet ( ) ; 4 ret urn tra nsform (visited, start , stop, wildca rdToWord L i st ) ; 5 } 6 7
8 9
10
11 12 13
14
15 16
17 18
/ * Do a dept h - first search from sta rtWord to stopWord , t raveling through each word * that is one edit away . * / Linked list tra nsform ( Ha shSet visited , String start , String stop, HashMa p L i s t < St ring, String> wildcardToWord List ) { i f ( st a rt . equals ( stop ) ) { Linkedlist p a t h = new L i n ked list ( ) ; path . ad d ( st a rt ) ; return pat h ; } else i f ( v i s i ted . contains ( st a rt ) ) { return null ; } vis ited . ad d ( st a rt ) ; ArrayList words
19
20
21
22 23
for ( St ring word : word s ) { L i nked l i s t path = transform ( v i s ited , word , stop, wildca rdToWord list ) ; if ( path ! = null ) { path . a d d F i rst ( st a rt ) ; return path ; } }
24
25
26 27 28 29
30
31
32
getvalid LinkedWords ( start, wildca rdToWord list ) ;
}
return null ;
33
/ * Insert words in dict ionary into mapping from wildcard form -> word . */ Ha s hMapL ist createWildca rdToWo rdMa p ( St r i ng [ ] word s ) { 35 HashMapList wildca rdToWords = new HashMapList ( ) ; 36 for ( St r i ng word : wo rds ) { 37 Array l i s t < St r i n g > linked = getWi ldcardRoots ( word ) ; 38 for ( String l i nkedWord : linked ) { 39 wildca rdToWords . put ( linkedWord , word ) ; 40 } 41 } 42 return wildca rdToWo rd s ; 43 } 34
45
46 47
/* Get list of wildc ards assoc iated with word . * / Array L i s t < String> getWi ldca rdRoot s ( String w ) { Array List words new Array L i s t < String> ( ) ;
604
=
Cracking the Coding
Interview, 6th Edition
Solutions to Cha pter
for ( int i
51
52
53
54
H a rd
=
0; i < w . lengt h ( ) ; i++) { String wo r d w . substring(0, i ) + "_" word s . add ( word ) ;
48 49 50
17 I
=
+
w . substring ( i + 1 ) ;
} return wor d s ; }
55
/* Return words t hat are one edit away . * I Arraylist getValid LinkedWords ( St r i ng word, 57 H a s h Ma plist wildca rdToWords) { Arraylist wil d ca r d s getWi ldcardRoot s (word ) ; 58 Arraylist l i n kedWords = new Arrayl i s t ( ) ; 59 for ( St ring wildcard : wildca rd s ) { 60 ArrayL ist words = wildca rdToWords . get (wildcard ) ; 61 for ( String linkedWord : word s ) { 62 63 if ( ! linkedWord . eq u a l s ( wo rd ) ) { linkedWords . ad d ( linkedWord ) ; 64 65 } 66 } 67 } return linkedWords; 68 69 } 56
,
=
70 71 72
/* *
HashMa p li s t < String, String> is a HashMap that maps from St ri ngs t o Arraylist . See a p pend ix for implementation . * /
Th is w i l l work, b u t w e c a n still make i t faster. One optimization is to switch from depth-fi rst search to breadth-fi rst search. If there are zero paths or one path, the a lgorithms a re equiva lent speeds. However, if there are multiple paths, breadth-fi rst sea rch may run faster. Breadth-first search finds the shortest path between two nodes, whereas depth-first search finds any path. This mea ns that depth-first search might ta ke a very long, windy path in order to find a connection when, in fact, the nodes were qu ite close. Optimal Solution
As noted earlier, we can optimize this using breadth-fi rst search. Is this as fast as we can make it? Not qu ite. Imagine that the path between two nodes has length 4. With breadth-fi rst search, we will visit about nodes to find them.
1 54
Breadth-first search spans out very qu ickly. Instead, what if we searched out from the source and destination nodes simu ltaneously? I n this case, the breadth-first sea rches would collide after each had done a bout two levels each. Nodes travelled to from sou rce: 1 52 •
Nodes travel led to from destination: 1 52
Total nodes:
1 52
+
1 52
This is much better than the trad itional breadth-fast search. We wi l l need to track the path t h a t we've travelled
at e ach n od e.
CrackingTheCodingl nterview.com I 6th Edition
605
Solutions to Chapter 1 7
I Hard
1o
implement this approach, we've used a n add itional class B F SData. B F SData helps u s keep things a bit clearer, a nd a llows us to keep a similar framewo rk for the two simultaneous breadth-fi rst searches. The alternative is t o kee p passing aro u nd a bunch of sepa rate varia bles. 1
L i n ked list tra nsform ( St ring sta rtWord , String stopWord , St ring [ ] word s ) { HashMaplist wildca rdToWordList getWi ldca rdToWord list (words ) ;
2
=
3 4
BFSData sourceData = new B FSData ( st a rtWord ) ; BF SData destData = new BF SData ( stopWord ) ;
5
6 7
while ( ! sou rceData . i s F i nished ( ) && ! d estData . i s F i n i s hed ( ) ) { / * Sea r c h out from source . * / St ring collision = searchLevel (wildca rdToWord l i s t , sourceData , destData ) ; if ( collision ! = n u l l ) { ret urn mergePat h s ( sou rceData , destDat a , collision ) ; }
8
9
10 11 12
13
14
15
16 17
18
19 2�
21 22
23
}
/ * Sea r c h out from destination . */ c o l l i s io n = searchLevel (wildca rdToWord List, destDat a , sou rceData ) ; if ( co l l i s ion ! = n u l l ) { ret urn mergePaths ( sourceDa t a , destDat a , collision ) ; }
ret u r n n u l l ; }
24 / * Sea r c h one level a nd ret urn collision, if a ny . */ 2 5 St ring sea r c h level ( H a s hMap l i s t < String, St ring> wildca rdToWord l i s t , 26 BFSData primary, B FSData second ary) { 27 /* We only want to s e a r c h one level at a t ime . Count how many nodes a re 28 * currently i n the primary ' s level and only do that many nodes . We ' ll continue * to add nodes to t h e end . * /
29
30 31
int count = primary . toVi s it . s i z e ( ) ; for ( i nt i = 0 ; i < count ; i++) { /* Pull out first node . */ Pat hNode pathNode = primary . toVisit . po ll ( ) ; St ring word = pathNode . getWord ( ) ;
32
33 34
35
36
/* Check if it ' s a l ready been vis ited . */ if ( seconda ry . vis ited . contains Key (word ) ) { return pathNode . getWord ( ) ; }
37
38
39
40 41
/* Ad d friends to queue . * / Arraylist < String> word s getvalidLinkedWord s (word , wildca rdToWord list ) ; for ( St ring w : word s ) { if ( ! primary . vis ited . cont a i n s Key ( w ) ) { PathNode next = new PathNode (w, pat hNode ) ; primary . vi s ited . put (w, next ) ; p r imary . tovisit . add ( next ) ;
42 43
=
44
45
46 47
48 49
}
}
50 51
52 53
} return null ; }
606
Cracking the Coding Interview, 6th Edition
Solutions to Cha pter 1 7
I
H a rd
54
Li n ked list mergePaths ( B FSData bfs l , BFSData bfs2 , String connect ion ) { PathNode e n d l = bfs l . vis ited . get ( connection ) ; // e n d l - > source 55 PathNode end2 = bfs 2 . vi s ited . get ( connection ) ; / / end2 - > dest 56 Linked l ist pathOne = end 1 . collaps e ( false ) ; / / forward 57 Linked list pathTwo e nd 2 . collaps e ( t rue ) ; / / reverse 58 pathTwo . removeF irst ( ) ; / / remove connection 59 pathOne . addAll ( pathTwo ) ; / / add second path 60 return pathOne ; 61 62 } 63 l i d l i nkedWords a re 64 / * Methods getWildca rdRoot s , getWildca rdToWord L i s t , a nd getVa */ . solution * the same as in the earlier 65 =
66 67
68
69
70 71 72
public class BFSData { public Queue< PathNode> toVi s it = new LinkedList< PathNode > ( ) ; new HashMap ( ) ; p u b l i c H a s hMa p vis ited =
public B FSDat a ( String root ) { Pat hNode sourcePath new PathNode ( root , n u ll ) ; toVi s it . ad d ( sourcePat h ) ; ·vi s ited . put ( root , sourcePat h ) ; } =
73 74
75 76 77
78
79
80
81
}
public boole an i s F i n ished ( ) { return toVi s it . is E mpty ( ) ; }
82 public class PathNode { private String word null; private PathNode previous Node = null ; 85 public PathNode ( String word , PathNode previous ) { this . word = word ; 86 previousNode = previous ; 87 88 } 8, public St ring getword ( ) { 90 91 ret u r n word ; 92 } 83 84
93
94 95
96 97
98
99
100 101
102
103
104
105
106
107
109 } mg
=
/ * Traverse path a nd ret urn linked list of nodes . * / public L inked list colla p s e ( boolean start sWith Root ) { Linked L i s t < String> path new Linked l i st < St r i ng> ( ) ; PathNode node = this ; while ( node ! = n u l l ) { if ( startsWithRoot ) { path . a ddlast ( node . word ) � } else { pat h . addFirst ( node . word ) ; } node node . previousNode ; } ret urn pat h ; } =
=
CrackingTheCodinglnterview.com I 6th Edition
607
Solutions to Chapter 1 7
I Hard
110 / * Ha s hMa p l ist i s a Ha s hMa p that maps from Strings to 1 1 1 * Array L i s t < I ntege r > . See a ppendix for implementation . * /
This algorithm's runtime is a bit harder to describe since it depends on what the language looks l i ke, as well as the actual source and desti nation words. One way of expressing it is that if each word has E words that a re one edit away and the source and destination a re d ista nce D, the runtime is 0 ( E012 ) . This is how much work each breadth-first search does. Of cou rse, this is a lot of code to im plement in an i nterview. It just wou ld n't be possible. More real istically, you'd leave out a lot of the details. You might wrlte just the skeleton code of t ra n sform and s e a r c h leveL but leave out the rest. 1 7.23 Max Square Matrix: Imagine you have a square matrix, where each cell (pixel) is either black or
white. Design an a lgorithm to find the maximum s u bsquare such that all four borders a re fi lled with black pixels. pg 7 90
SOLUTION
Like many problems, there's an easy way and a hard way to solve this. We'l l go through both solutions. The "Simple" Solution: 0( N4 )
We know that the biggest possible square has a length of size N, and there is only one possible square of size NxN. We can easily check for that square and return if we find it. If we do not fi nd a square of size NxN, we can try the next best thing: ( N - 1 ) x ( N - 1 ) . We iterate through a l l squares of this size and return the first one we find. We then do the same for N - 2, N - 3, and so on. Since we are searching progressively smaller squares, we know that the first square we fi nd is the biggest. Our code works as follows: 1
Subsquare findSquare ( i nt [ ] [ ] matrix) { for ( i nt i = mat rix . lengt h ; i >= 1 ; i - - ) { Subsquare square = findSquareWithSize ( matrix, i ) ; if ( square ! = null ) ret u r n square; } ret u r n n u l l ; }
2
3 4 5
6
7 8
9
1e 11 12
Subsquare findSquareWithSize ( i nt [ ] [ ] matrix, int squareSize ) { /* On an edge of length N, there are (N - sz + 1) squares of length s z . * / i nt cou nt = matrix . length - squa reSize + 1 ;
13 14
/ * Iterate through all squa res with s ide length squareSize . * / f o r ( int row = 0; row < c o u n t ; row++ ) { for ( int col = 0 ; col < count ; col++ ) { if ( is Square ( matrix, row, col , squa reSize ) ) { ret u r n new Subsquare ( row, col , squa reSize ) ; } } } ret u r n null;
15 16
17 18 19
2.0
21
22 } 23
2�
boo l e a n i s s q u a r e ( int ( ] ( ] matrix, int row , int col , i n t s i ze ) \
608
I
Cracki ng th e Co di n g Interview, 6th Edition
Solutions to Chapter 1 7 25
I I Check top and bottom borde r . 0 ; j < s i ze; j++ ) { for ( int j if ( m a t r i x [ r o w] [ c o l+j ] 1) { ret u r n fa l s e ; } if ( matrix [ row+si ze - l ] [ col+j ] ret u r n fa l s e ; } }
26
=
27
= =
28 29
30 31
32
33
34 35
=
37
= =
38
39
40 41
42 43
45
1){
I I C h eck left and right border . for ( i nt i 1 ; i < s i ze - 1 ; i++ ) { if ( matrix [ row+i ] [ col ] 1){ r et u r n fa l s e ; } 1) { if ( matrix [ row+i ] [ col+s i ze - 1 ] return f a l s e ; } } return true ;
36
44
I Hard
}
Pre-Processing Solution: O ( N3 )
A large part of the slowness of the "simple" solution a bove is due to the fact we have to do O ( N ) work each time we want to check a potential square. By doing some pre-processing, we can cut down the time of i s S q u a re to 0 ( 1 ) . The time of the whole a lgorithm is reduced to O { N3 ) . I f we analyze what i s S q u a r e does, we rea lize that a l l it ever needs to know i s if the next squa reS i z e items, o n the right of a s wel l a s below particular cells, a re zeros. We c a n pre-compute this data in a stra ig ht forwa rd, iterative fashion. We iterate from rig ht to left, bottom to top. At each cell, we do the following computation: if A [ r ] [ c ] is white, zeros rig h t and zeros below a re 0 else A [ r ] [ c ] . ze r os Rig h t A [ r ] [ c + 1 ] . zerosRight + 1 A [ r ] [ c ] . zeros Below = A [ r + l ] ( c ] . z e r os B e l ow + 1 =
Below is a n example of these values for a potential matrix. ( 0s rig h t , 0s b e l ow )
Origi na l Matrix
0, 0
1,3
0, 0
w
B
w
2,2
1,2
0,0
B
B
w
2,1
1,1
0, 0
B
B
w
Now, instead of iterating through O ( N ) elements, the i s S q u a r e method just needs to check z e r o s Right and zeros Below for the corners. Our code for this algorithm is below. Note that f i n d S q u a re and findSqua reWi th S i z e is eq uiva lent, other than a ca ll to p r o c e s sMa t r ix and working with a new data type thereafter.
1 2
public c l a s s Squa reCell { publi c int zerosRight = 0 ;
CrackingTheCodingl nterview.com I 6th Edition
609
Solutions to Chapter 1 7 3
4
}
5
6
I Hard
public i nt zerosBelow = 0 ; I * decla rat ion, getters , setters * I
7
Subsquare findSq uare ( i nt [ ] [ ] matrix) { SquareCell ( ] [ ] processed = proce s s Sq u a re (matrix ) ; 9 for ( int i = matrix . lengt h ; i > = 1 ; i - - ) { 10 Subsquare square findSqua reWithS i ze ( p rocessed , i ) ; if ( s q uare ! null ) return square; 11 12 } 13 ret u r n n u l l ; 14 } 15 1 6 Subsquare f indSqua reWit hSize ( SquareCel l [ ] [ ] proces sed, i n t s ize ) { 17 I * equiva lent to first algorithm *I 18 } 8
=
=
19 20
boolean isSq ua re (Squa reCell [ ] [ ] matrix, int row, i nt col, i nt s z ) { Squa reCell topleft = mat rix[ row] [ col ] ; Squa reCell topRight = mat r i x [ row] [ col + s z - 1 ] ; Squa reCell bottomleft = mat rix [ row + s z - l ] [ col ] ;
21
22 23
24
25
26
27 28
29
30 31
}
32 33
34 35
36 37
I* Check top, left , right , and bottom edges , res pectively . * I if (top Left . zerosRight < sz I I topLeft . zerosBelow < sz I I topRight . zerosBelow < sz I I bottomLeft . ze rosRight < s z ) { return fal s e ; } ret urn true;
Squa reCe ll [ ] [ ] processSquare ( int [ ] [ ] mat r i x ) { Squa reCell [ ] [ ] processed = new Squa reCe ll [ mat rix . length ] [ matrix . length ] ; =
38
3'
40
41
42 43
44
45 46
47
48 49 50 51
52 53
54
55 56 57
58
610
for ( int r mat rix . length - 1 ; r >= 0; r - - ) { for ( int c = mat rix . length - 1 ; c > = 0; c - - ) { i nt rightZeros 0; i nt belowzeros = 0 ; I I only need to pro c e s s if it ' s a b l a c k cell i f (matrix [ r ] [ c ] 0) { rightZeros++; belowZe ros++ ; II next column over i s on s ame row if ( c + 1 < mat rix . lengt h ) { Squa reCell previous = proce s s e d [ r ] [ c + 1 ] ; rightZeros += previous . zerosRight ; } if ( r + 1 < matrix . lengt h ) { Squa reCell previous = proces s ed [ r + 1 ] [ c ] ; belowZeros += previous . zerosBelow; } } processed [ r ] [ c ] - new Squa reCe ll ( ri ghtZeros , be lowZe ros ) ; } } return p roce s s e d ; =
==
Cracki ng the Cod in g Interview, 6th Edition
Sol utions to Chapter 1 7 59
I Hard
}
1 7.24 Max Submatrix:
Given an NxN matrix of positive and negative integers, write code to find the submatrix with the largest possi ble sum. p g 1 90
SOLUTION
- --- -----
This problem can be a pproached in a variety of ways. We'll start with the brute force solution and then optimize the solution from there. Brute Force Solution: O ( N6)
Like many "maximizing" problems, this problem has a stra ig htforward brute force solution. This solution simply iterates through all possible submatrices, computes the sum, and finds the la rgest. To iterate through all possible submatrices (with no du plicates), we simply need to iterate through all ordered pa i rs of rows, and then a l l ordered pairs of columns. This solution is O ( N6 ) , si nce we iterate through O ( N4 ) submatrices and it takes O ( N 2 ) time to compute the area of each. 1
SubMatrix getMaxMatrix ( int [ ] [ ] matr i x ) { int rowCount matrix . lengt h ; int columnCount = matrix [ 0 ] . lengt h ; 3 4 SubMat rix best = n u l l ; 5 for ( int rowl = 0 ; rowl < rowCount ; rowl++ ) { for ( int row2 rowl ; row2 < rowCount ; row2++ ) { 6 7 for ( i nt coll = 0 ; coll < columnCount ; coll++) { 8 for ( int col2 = coll ; col2 < columncount ; col2++ ) { 9 int s um = s um (matrix, rowl , coll, row2, col2 ) ; 10 if ( best == null 1 1 best . getSum ( ) < s um ) { 11 best = new SubMat r i x ( rowl , c o l l , row2 , col2, s um) ; 12 } 13 } 14 } 15 } 16 } 17 ret u r n best ; 18 } 2
=
=
19 20
int sum ( i nt [ ] [ ] matrix, int rowl, int coll, int row2 , i nt col2) { i n t s um = 0; 22 for ( i nt r = rowl ; r 0 ? sumThrough [ r ] [ c - l ] : 0; 26 27 int top = r > 0 ? s umTh rough [ r - l ] [ c ] : 0 ; 28 i nt ove rlap = r > 0 && c > 0 ? sumThrough [ r - l ] [ c - 1 ] : 0; 29 s umThrough [ r ] [ c ] = left + top - ove rlap + matrix [ r] [ c ] ; 30 } 31 } 32 ret u r n s umThrough ; 33 } 34 35 i nt sum ( int [ ] [ ] s umThrough, int r l , int c l , int r2, i nt c2) { 36 i n t topAnd left = rl > 0 && c l > 0 ? s umThrough [ rl - l ] [ c l - 1 ] 0 ,· 37 int left = cl > 0 ? s umThrough [ r2 ] [ cl - l ] : 0 ; int top = r l > 0 ? sumThrough [ rl - l ] [ c 2 ] : 0 ; 38 i nt full = sumThrough [ r2 ] [ c2 ] ; 39 40 ret u r n full - left - top + topAnd left ; 41 } This algorithm ta kes 0 ( N4 ) time, since it goes through each pa ir of rows and each pair of columns. Optimized Solution: O ( N3 )
Believe i t o r not, a n even more optimal solution exists. If we have R rows and C columns, we can solve it in O ( R 2 C ) time. Recall the solution to the maximum suba rray problem: "Given an array of integers, fi nd the suba rray with the largest sum:' We can find the maximum suba rray in O ( N ) time. We will leverage this solution for this problem. Every submatrix can be represented by a contig uous sequence of rows a nd a contiguous sequence of columns. If we were to iterate through every contig uous sequence of rows, we wou ld then just need to find, for each of those, the set of columns that gives us the hig hest sum. That is: 1
2 3
4 5
6
7
8
maxSum = 0 fo reach rowSta rt in rows fo rea c h rowEnd in rows /* We have ma ny pos s i ble s ubma t r ices with rowSta rt and rowEnd a s the top a nd * bottom edges of the matrix . F ind the colSta rt a nd colEnd edges that give * the highe s t sum . */ maxsum max ( runn ingMa xSum, maxsum) ret u r n maxSum =
Now the question is, how do we efficiently find the "best" c o lSta rt and c o l E nd?
Pictu re a submatrix: CrackingTheCodinglnterview.com I 6th Edition
613
Solution:» to Chapter 1 7
I Hard rowSt art
9 -3 6
�
12
-
I
8
1
7
6
-4
-4
-5
3
I
rowEnd
3
-2
-2
4
8
9
-
I
7
-5
Given a rowSt a rt and rowEnd, we want to find the colSt a rt and c o l E n d that give us the hig hest possi ble sum. 10 do this, we can sum up each col umn and then apply the maximumS ubArray function explained at the beginning of this problem. For the earlier example, the maximum suba rray is the fi rst through fourth col umns. This means that the maximum su bmatrix is (rowSta rt, first c o lumn) through (rowEnd, fourth c o l umn). We now have pseudocode that looks like the fol lowing. 1
=
maxsum 0 foreach rowStart i n rows fore a c h rowE nd i n rows foreach col i n columns partialSum [ co l ] s um of mat rix [ rowSta rt , col ] through mat r i x [ rowEnd , col] runningMaxSum maxSubAr ray ( pa rtialSum) maxSum = max ( runningMaxSum, maxSum) return maxSum
2 3
4 5
=
6
=
7
8
The sum in li nes 5 and 6 ta kes R * C time to compute (since it iterates through rowSt a rt through rowE nd), so this gives us a runtime of O ( R 3 C ) . We're not qu ite done yet. In li nes 5 and 6, we're basi cally add i ng up a [ 0 ] . . . a [ i ] from scratch, even though in the previous itera tion of the outer for loop, we already added up a [ 0 ] . . . a [ i - 1 ] . Let's cut out this d u pli cated effort. 1 2
maxSum = 0 forea ch rowSta rt in rows clear a rray partialSum foreach row E nd i n rows foreach col in columns partia lSum [ col ] += mat r i x [ rowEnd, c o l ] runningMaxSum = maxSubArray ( pa rtialSum) maxSum = max ( ru n n ingMaxSum , maxSum) ret u r n maxSum
3 4 S
6
7 8 9
Our full code looks l i ke this:
1
SubMatrix getMaxMatrix ( int [ ] [ ] mat rix) { int rowCount matrix . length ; int colCount = mat rix [ 0 ] . length ; SubMatrix best null;
2
=
3 4 5
=
6
for ( i nt rowSta rt 0; rowSta rt < rowCount ; rowSta rt++ ) { i nt [ ] partialSum new int [ colCount ] ; =
7
=
9
1� 11
61 4
=
for ( i nt row E nd rowStart ; rowEnd < rowCount ; rowEnd++ ) { /* Add values at row rowEnd . */ for ( int i 0; i < colCount ; i++ ) { =
Cracking the Coding I nterview, 6th Edition
Solutions to Chapter 1 7 p a rti a lSu m [ i ] += ma t r i x [ r ow E n d ] ( i ] ;
12 13 14
}
15
if
17
18
20 21
22
maxSubArray (partia lSum , colCount) ; (best == null / I be st . getSum ( ) < bestRange . s um) { best = new SubMat rix ( rowStart , bestRa nge . start, rowEnd , bestRa nge . end , bestRa nge . s um ) ;
Ra nge bestRa nge
16 19
I Hard
=
} } } ret urn bes t ;
23 } 24 25 Ra nge maxSubArray ( int [ ] array, i nt N ) { 26 Ra nge best = nul l ; int start = 0; 27 28 int s u m 0; 29 30
31
32
33 34
35 36
37
38
=
for ( i nt i = 0; i < N; i++ ) { s um += array [ i ] ; if ( best == n u l l I I s u m > best . s um) { best = new Range ( start, i, s um ) ; } / * If runni ng_s um is < 0 no poi nt in t r y i ng to cont i n ue t he series . Reset . */ i f ( s um < 0) { s t a rt i + 1; s um = 0; } =
39 40 41 } 42 ret urn best ; 43 } 44 4 5 p u b l ic class Ra nge { public i nt start , end, sum; 46 �7 publi c Ra nge ( int start , int end, i nt s u m ) { this . start start ; 48 this . end end; 49 50 this . s um = s u m ; 51 } 52 } =
This was a n extremely complex problem. You would not be expected to fig u re out this entire problem i n a n interview without a lot o f help from you r interviewer. 1 7 .25 Word Rectangle: Given a list of millions of words, desig n an algorith m to create the largest possible
rectangle of letters such that every row forms a word (reading left to rig ht) and every column forms a word (reading top to bottom). The word s need not be chosen consecutively from the list, but all rows must be the same length and all colum ns must be the same height. pg 7 90
SOLUTION -··-· ·- ······-· ·- ······-· ·- ·· · · -·-·· - ·- ·- ··- ·-
- ·-·· -· ··-· · -·-··· - ······-·-·· ··· ·-·- · ·
Many problems i nvolving a dictionary can be solved by doing some pre-processing. Where can we do pre processing?
CrackingTheCodingl nterview.com I 6th Edition
615
Solutions to Chapter 1 7
I Hard
Well, if we're going to create a recta ngle of words, we know that each row must be the same length and each column must be the same length. So let's group the words of the dictionary based on their sizes. Let's call thi s g rouping D, where D [ i ] contains the list of words of length i. Next, observe that we're looking for the largest recta ngle. What is the largest recta ngle that could be formed? It's l ength ( la rge st word )1. 1
2
3
4
s 6
int maxRect angle longestWord * long2st Word ; for z maxRect angle to 1 { for each pair of numbers ( i , j ) where i*j z { I* attempt to make rect angle . ret urn if s u c c e s s fu l . * I =
=
=
}
}
By iterating from the biggest possible rectangle to the smallest, we ensure that the first va lid rectangle we fi nd will be the largest possible one. Now, for the hard pa rt: makeRectangl e ( int 1 , int h ) . This method attempts to build a recta ngle of words which has length 1 and height h. One way to do this is to iterate through a l l (ordered) sets of h words and then check if the columns are a lso va lid words. This will work, but it's rather inefficient. Imagine that we a re trying to build a 6x5 recta ngle and the first few rows a re: there q ueen pizza At this point, we know that the fi rst column starts with tq p. We know-or should know-that n o d ictionary word starts with tq p. Why do we bother continuing to build a recta ngle when we know we'l l fa il to create a va lid one in the end? This leads us to a more optimal solution. We can build a trie to easily look up if a su bstri ng is a prefix of a word in the d ictionary. Then, when we build our rectangle, row by row, we check to see if the columns are a l l valid prefixes. If not, we fa il immediately, rather than continue to try to build this recta ngle. The code below im plements this a lgorithm. It is long and com plex, so we will go through it step by step. First, we do some pre-processing to group words by their lengths. We create an a rray of tries (one for each word length), but hold off on building the tries until we need them. 1 Wo rdGroup [ ] gro u p l ist WordGroup . createWordGroups ( l ist ) ; int maxWord Lengt h gro u p l ist . lengt h ; 2 3 Trie t rie list [ ] = new Trie [ maxWord Length] ; =
=
The maxRectangle method is the "main" pa rt of our code. It starts with the biggest possible recta ngle area (which is ma xWord l ength 2 ) and tries to build a recta ngle of that size. If it fa ils, it subtracts one from the a rea and attem pts this new, smaller size. The fi rst recta ngle that can be successfu lly built is guaranteed to be the biggest. 1 Rect a ngle ma xRecta ngle ( ) { i nt maxSize = maxWordlengt h * maxWord Lengt h ; 2 for ( i nt z = maxSize; z > 0; z - - ) { I I start from biggest a rea 3 for ( int i 1 ; i maxWordLengt h ) { maxWord lengt h = list [ i ] . length ( ) ; } }
15
16 17
=
18 19
=
20 21
22
23
24
/ *Group t he wo rds in t he dictionary into lists of words of same lengt h . * group list [ i ] will cont ain a list of word s , each of le ngth ( i+l ) . */ grouplist new WordGroup [ maxWordlengt h ] ; for ( i nt i = 0 ; i < list . lengt h ; i++ ) { 1 i n stead of j u st wordle ngt h since this is used a s I *We do wordlength * an index and no wo rds are of length 0 * / int word Length l i st [ i ] . lengt h ( ) 1; i f (groupList [wordLengt h ] null ) { groupList [wordLengt h ] new WordGroup ( ) ; } groupL ist [ word Lengt h ] . addWord ( l ist [ i ] ) ;
25 26
=
27
28
-
29
30
=
31
32
=
33
34 35
}
36
ret u r n group l i s t ;
37 38
-
==
} }
The ful l code for this problem, including the code for Trie and TrieNode. can be found in the code attachment. Note that in a p r o bl e m as complex as th is, you'd most likely only need to write the pseudocode. Writing the entire code would be nearly im possible in such a short amount of time.
CrackingTheCodinglnterview.com j 6th Edition
619
Solutions to Chapter 1 7 I Hard 1 7.26 Sparse Similarity: The similarity of two documents (each with distinct words) is defined to be the
size of the intersection divided by the size of the union. For exam ple, if the documents consist of i ntegers, the similarity of { l , 5 , 3} and { 1 , 7, 2 , 3} is 0 . 4, because the i ntersection has size 2 and the union has size 5. We have a long l ist of documents (with d istinct values and each with a n associated I D) where the simila rity is believed to be "spa rse:'That is, any two a rbitrarily selected documents are very likely to have similarity 0. Design an algorithm that returns a list of pairs of document IDs and the associated similarity. Print only the pa irs with similarity greater than 0. Em pty documents should not be printed at all. For simplicity, you may assume each document is represented as an a rray of distinct integers. EXAMPLE Input: 1 3 : { 14, 1 5 , 100, 9, 3 } 16 : { 3 2 , 1 , 9 , 3 , 5 } 19 : { 1 5 , 29 , 2 , 6 , 8 , 7 } 24 : { 7 , 1 0 }
Output: IDl , ID2
SIMI LARITY
13, 19 1 3 , 16
0.1 0 . 25
19, 24
0 . 14285714285714285 pg
1 90
SOLUTION
This sounds like quite a tricky problem, so let's start off with a brute force algorithm. If nothing else, it will help wra p our heads a round the problem. Remember that each document is an a rray of distinct "words'; and each is just an i nteger. Brute Force
A brute force algorithm is as simple as just comparing a l l arrays to all other a rrays. At each compa rison, we compute the size of the intersection and size of the u n io n of the two a rrays. Note that we only want to print this pair if the similarity is greater than 0. The u n ion of two arrays can never be zero (unless both a rrays are em pty, in which case we don't want them printed a nyway). Therefore, we are really just printing the similarity if the intersection is greater than 0. How do we compute the size of the intersection and the u n ion? The intersection means the number of elements in com mon. Therefore, we can just iterate through the fi rst a rray (A) and check if each element is in the second array (B). If it is, increment a n i n t e r s e c t i o n variable. 10 com pute the
union, we need to be sure that we don't double count elements that are in both. One way to do this is to count up all the elements in A that a re not in B. Then, add in all the elements in B. This wi ll avoid double counting as the duplicate elements a re only counted with B. Alternatively, we can think about it this way. If we did dou ble count elements, it would mean that elements in the intersection (in both A and B) were counted twice Therefore. the easy fix is to just remove these dupl icate elements. .
620
Cracking the Cod ing Interview, 6th Edition
Solutions to Chapter 1 7 unio n ( A, B )
=
A
+
I Hard
B - int e rs ec t ion (A, B )
This means that a l l we rea lly need to do i s compute the intersection. We can derive the union, a n d therefore simila rity, from that immed iately. This gives us a n O ( AB ) algorithm, just to compare two arrays (or documents). However, we need to do this for all pairs of D documents. If we assume each document has at most W words then the runtime is O( 02 W2) . Slightly Better Brute Force
As a quick win, we can optimize the computation for the similarity of two arrays. Specifically, we need to optimize the intersection computation. We need to know the number of elements in common between the two arrays. We can throw a l l of A's elements into a hash table. Then we iterate through B, incrementing intersect ion every time we find an element in A. This ta kes O ( A
+
B ) time. If each array has size W and we do this for D arrays, then this ta kes O ( D2 W ) .
Before implementing this, let's fi rst think about the classes we'll need. We'll need to return a list of document pairs and their similarities. We1 1 use a Doc P a i r class for this. The exact return type wil l be a hash table that maps from Doc P a i r to a double representing the similarity. 1 2 3
public class Doc Pa i r { public int docl, doc2 ;
4
pu blic Do c P a i r ( int d l , int d 2 ) { docl dl; do c 2 d2; }
5 6
=
7
8 9 10 11 12 13 14 15 16
@Ove rride public boo lean equa l s (Object o) { if ( o insta nceof DocPa i r ) { DocPair p ( DocPa i r ) o ; return p . docl docl && p . do c 2 } return f a ls e ; } =
==
17
18 19 20
}
doc2;
@Override public int has hCode ( ) { return ( d o c l * 31)
A
do c 2 ; }
It wi ll a lso be usefu l to have a class that represents the documents. 1
2
3 4 5
public c l a s s Document { private Arraylist wor d s ; p rivate int doc i d ;
7 8
public Document ( int id, Array l i s t < I ntege r > w ) { docid id ; wo rds w; }
10 11
public Arraylist getWords ( ) { return wo rds ; } public i n t getid ( ) { return docid ; }
6 9
=
=
CrackingTheCodinglnterview.com I 6th Editio n
621
Solutions to Chapter 1 7 12
I Hard
public int s i ze ( ) { return words == null ? 0
13
:
words . s ize ( ) ; }
}
Strictly speaki ng, we don't need any of this. However, readability is im portant, and it's a lot easier to read
A rr a y L i s t < D o c u me n t > t han A r r a y l i s t < A r r ay l i s t < I n t e g e r > > .
Doing this sort of th ing not only shows good cod ing style, it also makes your life i n an interview a lot easier. You have to write a lot less. (You probably would not define the entire Doc ument class, unless you had extra time or your interviewer asked you to.) 1
2
3
4 5
Has hMap computeSimilarities ( Arraylist doc ument s ) { H a s hMa p < DocP a i r , Dou ble> s imilarities = new HashMa p < DocPair, Double > ( ) ; fo r ( i nt i = 0; i < document s . s i ze ( ) ; i++ ) { fo r ( int j = i + 1 ; j < document s . size ( ) ; j++) { Doc ument docl document s . get ( i ) ; Doc ument doc2 = document s . get ( j ) ; do uble sim computeSimilarity(docl, doc2 ) ; if ( s im > 0) { Do cPair pa i r new Doc Pa ir ( docl . getid ( ) , doc2 . get ld ( ) ) ; s imilarit ies . put ( pa i r , s i m ) ; } } } ret urn s imilarit ies ; } =
6
=
7
8 9
=
10 11
12 13
14 15 16 17
li 19
20
21
double computeSimila rity( Document docl, Doc ument doc2) { int intersection 0; Has hSet < I nteger> set l new Has hSet < I ntege r> ( ) ; set l . addAl l ( docl . getWo rds ( ) ) ; =
=
22
fo r ( i nt word : doc2 . getWo rds ( ) ) { if ( set l . contains (word ) ) { intersectio n++; } }
23 24 25
26
27 28 29
38
do uble union docl . si ze ( ) + doc2 . s i ze ( ) - intersectio n ; ret urn i ntersection I unio n; =
}
Observe what's happening on line 28. Why did we make union a d ouble, when it's obviously an integer? We did this to avoid a n integer division bug. If we did n't do this, the division would "round" down to an integer. This would mean that the similarity would almost always return 0. Oops! Slightly Better Brute Force (Alternate)
If the documents were sorted, you could compute the i ntersection between two documents by wa lking through them in sorted order, much like you would when doing a sorted merge of two arrays. This would take O ( A + B ) time. This is the same time as our current algorithm, but less space. Doing this on D documents with W words each would take 0 ( 02 W ) time. Si nce we don't know that the arrays a re sorted, we could fi rst sort them. This would take O( D time. The ful l runtime then is 0(0 * W l o g W + 02 W) .
622
Cracking
the Cod ing Interview, 6th Edition
*
W log W)
Solutions to Chapter 1 7
l Hard
We ca nnot necessa rily assume that the second part "dominates" the first one, beca use it doesn't neces sari ly. It depends on the relative size of D and log W. Therefore, we need to keep both terms in our runtime expression. Optimized (Somewhat)
It is usefu l to create a larger example to really understand the problem. 13 : 16: 19 : 24 :
{14, 15 , 100, 9, 3 } { 3 2 , 1 , 9, 3, 5 } { 1 5 , 29 , 2, 6, 8 , 7 } {7, 10, 3 }
At fi rst, we might try various techniques that a llow us to more quickly eliminate potential compa risons. For example, could we com pute the min and max values in each array? If we did that. then we'd know that a rrays with no overlap in ranges don't need to be compared. The problem is that this doesn't rea lly fix our runtime issue. Our best runtime thus far is O ( D 2 W ) . With this change, we're sti ll going to be com paring all O ( D 2 ) pairs, but the O ( W ) part might go to 0 ( 1 ) sometimes. That 0 ( D2 ) part is going to be a rea lly big problem when D gets large. Therefore, let's focus on red ucing that O ( D 2 ) factor. That is thebottleneck" in our solution. Specifically, this means that, given a document doc A, we want to find a l l docu ments with some simila rity-and we want to do this without "talking" to each docu ment. What would make a docu ment similar to docA? That is, what characte ristics define the documents with simila rity > O? Su ppose docA is {14, 1 5 , 100, 9, 3 } . For a document to have similarity > 0, it needs to have a 1 4, a 1 5, a 1 00, a 9, or a 3 . How can we quickly gather a list of all documents with one of those elements? The slow (and, rea l ly, only way) is to read every single word from every single document to find the docu ments that conta in a 1 4, a 1 5, a 1 00, a 9, or a 3. That will take O ( D W ) time. Not good. However, note that we're doing this repeatedly. We can reuse the work from one ca ll to the next. If we build a hash ta ble that maps from a word to all docu ments that contain that word, we can very qu ickly know the documents that overlap with docA. 1 -> 2 -) 3 -) 5 -) 6 -) 7 ->
16 19 1 3 , 16, 24 16 19 19, 24 8 - > 19 9 - ) 13, 16
When we want to know all the docu ments that overlap with docA, we just look up each of doc A's items in this hash table. We'll then get a list of all documents with some overlap. Now, all we have to d o is com pare doc A to each of those documents. If there a re P pa irs with similarity > 0, and each document has W words, then this will ta ke 0 ( PW ) time (plus O ( DW ) time to create and read this hash table). Si nce we expect P to be much less than D2, this is much better than before.
CrackingTheCodinglnterview.com I 6th Edition
623
Solutions to Cha pter 1 7
I Hard
Optimized {Better)
Let's think a bout our previous algorithm. ls there a ny way we can make it more optimal? If we consider the runtime-O ( PW + DW) -we probably can't get rid of the O ( DW) fa ctor. We have to touch each word at least once, and there are O ( DW) words. Therefore, if there's an optimization to be made, it's probably i n the 0 ( PW ) term. It would be d ifficult to eliminate the P part in O ( PW ) because we have to at least print all P pairs (which takes O ( P ) time). The best place to focus, then, is on the W part. ls there some way we ca n do less than O ( W ) work fo r each pa ir of similar documents? One way to tackle this is to analyze what information the hash table gives us. Consider this list of documents: 12 : 13 : 14 : 15 : 17 :
{1, {5, {4, {1, {1,
5, 3, 3, 5, 6}
9}
1, 8} 2} 9, 8}
l f w e look u p document 1 2's elements i n a hash table for this document, we'l l get: 1 - > { 1 2 , 13, 15, 17 } 5 - > { 1 2 , 1 3 , 15 } 9 - > { 1 2 , 15 }
This tells us that documents 1 3, 1 5, and 1 7 have some simila rity. U nder our current algorithm, we would now need to compare document 1 2 to documents 1 3, 1 5, and 1 7 to see the number of elements document 1 2 has in common with each (that is, the size of the intersection). The union can be computed from the document sizes and the intersection, as we did before. Observe, though, that document 1 3 a ppeared twice in the hash ta ble, document 1 5 appeared three times, and document 1 7 appea red once. We discarded that information. But can we use it instead? What does it i nd icate that some documents a ppea red multiple times and others didn't? Document 1 3 a ppea red twice because it has two elements (1 and 5) in common. Document 1 7 appeared once because it has only one element ( 1 ) in common. Document 1 5 a ppeared three times because it has three elements ( 1 , 5, and 9) in common. This information can actually directly give us the size of the inter section. We could go through each document, look up the items i n the hash table, and then count how many times each document appears in each item's lists. There's a more direct way to do it. 1 . As before, build a hash table for a list of documents. 2. Create a new hash ta ble that maps from a document pair to an integer (which will indicate the size of the intersection). 3. Read the first hash table by iterating through each list of documents. 4. For each fist of documents, iterate through the pairs in that list. Increment the intersection count for
each pair. Comparing this runtime to the previous one is a bit tricky. One way we can look at it is to realize that before we were doing O ( W ) work for each similar pair. That's because once we noticed that two documents were similar, we touched every single word in each document. With this algorithm, we're only touching the words that actually overlap. The worst cases are still the same, but for many inputs this algorithm will be faster. 1 HashMap 2
computeSimilarities ( Ha s hMa p < I nteger, Document > docume nt s ) {
624
Cracking the Coding Interview, 6th Edition
Solutions to Chapter 1 7 3
=
5
7 8 9
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
29
30
31 32 33
34 35
36
37
3�
H a rd
HashMa p L i s t < I nteger, I nteger> wordToDoc s groupWord s ( documents ) ; HashMa p < Doc Pair, Dou ble> s imila rities = computeintersections (wo rdToDoc s ) ; adjustToSimila rities (document s , s imila rities ) ; ret urn s imila rities ;
4 6
I
} / * Creat e h a s h table from each word to where it appea r s . * / HashMapList < I nteger, I ntege r > groupWord s ( HashMa p < I nteger, Docume nt > document s ) { HashMapList < I nteger, Intege r > wordToDocs = new HashMaplist < I nteger, I nteger> ( ) ; for ( Document doc : document s . values ( ) ) { Array L i s t < I nteger> words doc . getWo rd s ( ) ; f o r ( i nt word : word s ) { wordToDoc s . put (word, doc . get id ( ) ) ; } } =
ret u r n wordToDocs ; } /* Compute intersections of document s . Iterate t h rough each list of document s and * then each pair with i n that list , i n c rementing the intersection of each page . * / Has hMa p comp uteintersections ( HashMap L is t < I nteger, I ntege r > wordToDocs { HashMap s imilarit ies = new Has hMap ( ) ; Set < I nteger> words wordToDocs . keySet ( ) ; for ( i nt word : word s ) { Arraylist < I nteger> docs = wordToDoc s . get (word ) ; Collections . sort ( docs ) ; f o r ( i nt i = 0 ; i < docs . s i z e ( ) ; i++ ) { for ( int j = i + 1; j < docs . si z e ( ) ; j++) { i n c rement ( s imila rities , docs . get ( i ) , doc s . get ( j ) ) ; } } } =
39 return s imila rit ies ; 40 } 41 42 /* Inc rement the intersection s i ze of each doc ument pair . * / 43 void inc rement (HashMap s imila rities , int docl, int doc2 ) { Do c P a i r p a i r = new DocPair (docl, doc2 ) ; 44 45 if ( ! s imila rities . containsKey ( pa i r ) ) { s imila rities . put ( pa i r , 1 . 0 ) ; 46 47 } else { 48 s imila rities . put ( pa i r , s imila rities . get ( pa i r ) + 1 ) ; 49 } 50 } 51
52 / * Ad j u s t the intersection value to become t he s imila rity . */ 53 void adj ustToSimilarities ( H a s hMa p < I nteger, Do cument> documents , 54 HashMa p s imila rities ) { 55 for ( E nt ry ent ry : s imilarit ies . ent rySet ( ) ) { 56 DocPa i r pair ent ry . get Key ( ) ; 57 Double intersection = entry . getVal ue ( ) ; 58 Document docl documents . get ( pa i r . docl ) ; =
=
CrackingTheCodinglnterview.com I 6th Editio n
I
625
Solutions to Chapter 1 7 59
=
Document doc2 document s . get ( pa i r . do c2 ) ; double union ( d o u b l e ) docl . s i ze ( ) + doc2 . s i ze ( ) - intersect i o n ; entry. s etValue ( i ntersect io n / union ) ;
60
=
61 62
63
64 65 66
I Hard
}
}
/ * Has hMapList< Integer, Intege r > is a Ha s hMap t hat ma p s from I nteger t o * Arraylist < I nteger > . See a ppendix for implementati o n . * /
For a set of documents with sparse similarity, this will run m uch faster than the original na ive a lgorithm, which com pares a l l pa irs of documents d i rectly. Optimized (Alternative)
There's an alternative algorithm that some ca ndidates might come up with. It's slig htly slower, but stil l quite good. Reca ll our earlier a lgorithm that com puted the simila rity between two documents by sorting them. We can extend this a pproach to m ultiple documents. Imagine we took a l l of the words, tagged them by their original document, and then sorted them. The prior list of documents would look like this: Now we have essentially the same a pproach as before. We iterate through this l ist of elements. For each sequence of identica l elements, we increment the intersection cou nts for the corresponding pair of docu ments. We will use an Element class to group together documents and words. When we sort the list, we will sort fi rst on the word but break ties on the document ID. 1
cla s s Element implements Compa rable < Element > { public i nt word, document ; public Element ( i nt w, i nt d ) { word = w ; document d; }
2 3
4 5
=
6
7 8
9
10
==
11
12 13
14 15
16 17 18
19 20 21
22
23
24
25
25 27
/ * When we s o rt t he words , t h i s f u n ct i o n will be used to compa re the words . * / public int compa reTo ( E lement e ) { i f (word e . word) { ret u r n document - e . document ; } ret u r n word - e . wo r d ; }
}
Has hMa p < Do c Pa i r , Double> computeSimi l a r ities ( H a s hMa p < I nteger, Document > document s ) { Arraylist < E lement > elements sortWo rds (do c ument s ) ; Ha s hMa p < DocPair, Do uble> similarities = comput e l ntersections ( element s ) ; adj ustToSimilarities (document s , s imila r ities ) ; ret u r n s imi l a r it ie s ; } =
/ * Throw a l l words into o n e l i s t , sorting b y t h e word a n d t h e n t he document . * / Arraylist < E lement > s ortWords ( H a s hMa p < I nteger, Document > docs ) { Arraylist < E lement> elements = new Arraylist < E lement > ( ) ;
626
I
Cracki ng the Coding Interview, 6th Editio n
Solutions to Cha pter 1 7
J
Hard
for ( Document doc : docs . values ( ) ) { Arrayl i st < I nteger> words = doc . getWords ( ) ; for ( int word : word s ) { element s . add ( new E lement (word, doc . getld ( ) ) ) ; } } Collections . so rt ( element s ) ; ret u r n element s ;
28 29
30
31
32
33
34
35 36 } 37 38 /* Inc rement the intersection s i z e of each document p a i r . * / 3 9 void i n c rement ( H a s hMa p s imila rities , i n t docl , i nt d o c 2 ) { 40 Do cPair pair = new DocPa i r (docl, doc2 ) ; if ( ! s imila rities . contain sKey ( pa i r ) ) { 41 42 s imila rities . put ( pa i r , 1 . 0 ) ; 43 } else { 44 s imila rities . put ( pa i r , s imilarities . get ( p a i r ) + 1 ) ; 45 } 46 } 47
48 / * Adj ust the intersection value to become the simila rity . */ HashMap computelntersection s ( Arraylist < E lement > elements ) { 50 H a s hMap s imila rities = new Ha s hMap ( ) ;
49 51
52
for ( int i = 0 ; i < elements . s ize ( ) ; i++ ) { E lement left element s . get ( i ) ; for ( int j i + 1; j < elements . s ize ( ) ; j++) { E lement right elements . get ( j ) ; if (left . word ! = right . word ) { bre a k ; } i n c rement ( s imila rities , left . document , right . document ) ; } } ret u r n s imilarities ; =
53
54
=
55
=
56
57 58 59
60
61
62
63
64 65
66
67 68 69
76 71
72
73
74
75 76
} / * Ad j u st the intersection value to become the s imila rity . * void adjustToSimi la ritie s ( Ha s hMa p < I nteger, Document> documents , HashMap s imila rities ) { for ( E ntry entry : s imila rities . ent rySet ( ) ) { DocPa i r pair = ent ry . getKey ( ) ; Double intersection = ent ry . getValue ( ) ; Document docl documents . get ( p ai r . docl ) ; Document doc2 = document s . get ( p ai r . doc2 ) ; double union = ( double ) docl . s i ze ( ) + doc2 . s ize ( ) - intersection; entry . setVa l ue ( intersection / union ) ; } } =
The first step of this alg orithm is slower t h a n that of the prior algorithm, since it has to sort rather than just add to a list. The second step is essentia l ly equ iva l e nt. Both wi ll run m u c h faster than the o rig i n a l n a ive algorithm.
CrackingTheCodinglnterview.com I 6th Edition
627
XI Adva n ced Topics
WRed-black trees? Dijkstra's a lgorithm? Topologica l sort?
hen writing the 6th ed ition, I had a number of debates about what should and shouldn't be included.
On one ha nd, I'd had a number of req uests to include these topics. Some people insisted that these topics are asked "a ll the time" (in which case, they have a very different idea of what this phrase means!). There was clearly a desire-at least from some people-to include them. And learning more ca n't h u rt, rig ht? On the other ha nd, I know these topics to be rarely asked. It happens, of cou rse. Interviewers are ind ividuals and might have their own ideas of what is"fa ir game"or"relevant"for an interview. But it's ra re. When it does come up, if you don't know the topic, it's unlikely to be a big red flag.
I
Adm ittedly, as a n interviewer, I hove asked candidates questions where the solution was essen tially an appl ication of one of these a lgorithms. On the rare occasions that a cand idate already knew the algorithm, they d id not benefit from this knowledge (nor were they h u rt by it). I want to eva luate you r abil ity to solve a problem you haven't seen before. So, I'll take into account whether you know the underlying a lgorithm in advance.
I bel ieve in giving people a fa ir expectation of the interview, not scaring people into excess studying. I also have no interest in making the book more "advanced" so as to help book sales, at the expense of your time and energy. That's not fair or rig ht to do to you. (Add itionally, I didn't want to give interviewers-who I know to be read ing th is-the impression that they can or should be covering these more adva nced topics. Interviewers: If you ask about these topics, you're testing knowledge of algorithms. You're just going to wind up eliminating a lot of perfectly smart people.) But there a re many borderline "im portant"topics. They're not often asked, but sometimes they are. U ltimately, I decided to leave the decision in you r ha nds. After a l l, you know better than I do how thorough you want to be in your preparation. If you want to do an extra thorough job, read th is. If you just love learning data structu res and algorithms, read this. If you want to see new ways of a pproaching problems, read this. But if you're pressed for time, this studying isn't a super high priority. � Useful Math
Here's some math that can be usefu l in some questions. There a re more formal proofs that you can look up on line, but we'll focus here on giving you the intuition behind them. You can think of these as informal proofs.
CrackingTheCodingl nterview.com I 6th Edition
629
Advanced Topics
XI.
S u m of Integ ers 1 throug h N
What is 1 + 2 + . . + n ? Let's figure it out by pa iring up low values with high values. .
If n
If n
is even, we pair 1 with n, 2 with n is
odd, we pair 0 with n, 1 with n
pair #
a
b
1
1
n
2
2
3
n
-
-
1, 1,
and so on. We will have f pairs each with sum n +
1
pair #
a
b
a+b
n
n
n +
1
1
0
n +
1
2
1
n +
3
1
n
3
4
n
3
n +
1
4
n T
n T
f+1
n +
1
-2-
n+l
.(o+ l)
n
2
4
I n either case, the s u m is
1.
and so on. We will have _!1;1 pairs with sum n.
a+b
n - 2
3
-
n
-
1
n
2
n
3
n
n+l
n-1
2-
2
n
total ;
n(n+l)
2- .
This reasoning comes up a lot i n nested loops. For exam ple, consider the following code: 1
2
3
4 5
for ( i nt i 0 ; i < n ; i++ ) { for ( int j i + 1 ; j < n ; j ++ ) { System . out . println ( i + j ) ; } } =
=
On the first iteration of the outer for loop, the i n ner for loop iterates n 1 times. On the second iteration of the outer for loop, the inner for loop iterates n - 2 times. Next, n 3, then n 4, and so on. There a re �; 1 ' total iterations of the inner for loop. Therefore, this code takes 0 ( n 2 ) time. -
-
-
Sum of Powers of2
Consider this sequence: 2° + 21 + 22 + ... + 2" . What is its result? A nice way to see this is by looking at these values in binary.
Therefore, the sum of 2° + 21 + 22 +
... +
2"
21
00010
2
22
00100
4
would, in base 2, be a seq uence of (n +
1)
1 s. This is 2°+1
-
1.
Takeaway: T h e s u m o f a sequence o f powers o ft w o i s roug hly equal t o t h e next value i n t he sequence. Bases oflogs
Suppose we have something i n log 2 (log base 2). How do we convert that to log 10? That is, what's the relationship between logb k and log, k? 630
Cracking the Coding Interview, 6th Edition
XI.
=
log. ( b' ) c logxb
=
=
1 og
-
=
log k. x
I I This i s the definition of log . // T a ke log of bot h s ides of b' = k . I I R u l e s of logs . You can move out the exponent s . // Dividing above exp res s ion and s u bst itut ing c .
=
logxk logxk
=
logb k and y
Let's do some math. Assu me c lo� k c - > b' k
Advanced Topics
log,k/
/log,b bk Therefore, if we want to convert log p to log 10, we just do this: 2
c
=
logrn p =
log2 P
l og2 1 0
Takeaway: Logs of different bases a re only off by a consta nt factor. For this reason, we largely ignore what
the base of a log within a big 0 expression. It doesn't matter si nce we d rop constants anyway. Permutations
How many ways are there of rea rranging a stri ng of n unique characters? Wel l, you have n options for what to put in the first characters, then n - 1 options for what to put in the second slot (one option is taken), then n - 2 options for what to put i n the third slot, and so on. Therefore, the tota l number of stri ngs is n ! . n!
=
!]
* !!___:__l * n..___:___l * D.___:_l * . . . * 1
What if you were form ing a k-length stri ng (with all unique characters) from n tota l unique characters? You can fol low similar logic, but you'd just stop you r selection/m u ltiplication earlier.
n! (n-k) !
!l
* !!___:__l * n..___:___l * D.___:_l * . . . * ,n - k + 1
Combinations
Suppose you have a set of n distinct characters. How many ways are there of selecting k characters into a new set (where order doesn't matter)? That is, how many k-sized subsets are �l\e re out of n disti nct elements? This is what the expression n-choose-k means, which is often written
( k ).
Imagine we made a list of all the sets by first writi ng all k-length su bstri ngs and then taking out the d u pli cates. From the a bove Permutations section, we'd have
"/(n - k)
!
k-length substrings.
Since each k-sized subset ca n be rearranged k ! unique ways into a string, each subset will be duplicated kl times in this list of substrings. Therefore, we need to divide by k ! to take out these duplicates.
n! n! _ ( nk ) - _l_ k l * (n - k) ! - k!(n - k) ! _
_
_ _
Proof by Induction
I nduction is a way of provi ng something to be true. It is closely related to recursion. It takes the following form. >=
Task: Prove statement P ( k ) is true for all k
b.
Base Case: Prove the statement is true for P ( b ) . This is usually just a matter of plugging in num bers. Assum ption: Assume the statement is true for P ( n ) . Inductive Step: Prove that if the statement is true for P ( n ) , then it's true for P ( n+l ) . This i s like domi noes. I f the fi rst domino fa lls, and one domino always knocks over the next one, then all the domi noes must fa ll. Let's use this to prove that there a re 2 n su bsets of a n n-element set. Definitions: let S
=
{a1 ,
a2,
a3,
•
•
•
,
aJ
be the n-element set.
Cra cki n gT heCod ingl n te rview.com I 6th Edition
631
XI.
Advanced Topics
Base case: Prove there are 2° subsets of { } . This is true, since the only subset of {} is { } . n Assume that there are 2 subsets of { a 1 , a , a 3 , . , an} . 2 Prove that there a re 2°+1 subsets of { a 1 , a 2 , a 3 , . . . , a n+1 } . Consider the subsets of { a 1 , a , a 3 , , a n+1 } . Exactly half will contain a n+1 and half will not. 2 , a n }. We assumed The subsets that do not contain a n+l are just the su bsets of { a 1 , a , a 3 , 2 there a re 2" of those. •
•
•
•
•
•
•
•
Since we have the same number of subsets with x as without x, there a re 2n subsets with a n+i ·
Therefore, we have 2" + 2" subsets, which is 2°+1 .
Many recursive a lgorithms can be proved valid with induction. � Topological Sort
A topologica l sort of a directed graph is a way of ordering the list of nodes such that if ( a , b) is a n edge in the graph then a will appear before b in the list. If a graph has cycles or is not directed, then there is no topological sort. There are a number of applications for this. For example, suppose the graph represents parts on an assembly line. The edge (Hand le, Door) indicates that you need to assemble the handle before the door. The topo logica l sort would offer a valid ordering for the assembly l i ne. We can construct a topologica l sort with the fol lowing a pproach. 1.
Identify all nodes with no incoming edges a nd add those nodes to our topological sort. »
We know those nodes a re safe to add fi rst since they have noth ing that needs to come before them. Might as well get them over with!
»
We know that such a node must exist if there's no cycle. After all, if we picked an arbitrary node we could just wal k edges backwards arbitra ri ly. We'll either stop at some point (in which case we've found a node with no i ncoming edges) or we'll retu rn to a prior node (in which case there is a cycle).
2. When we do the above, remove each node's outbound edges from the graph. »
Those nodes have already been added to the topological sort, so they're basica lly irreleva nt. We ca n't violate those edges a nymore.
3. Repeat the above, adding nodes with no incoming edges and removing their outbound edges. When all the nodes have been added to the topological sort, then we a re done. More formally, the a lgorithm is this: 1 . Create a q ueue order, which will eventua l ly store the va lid topologica l sort. It is currently em pty. 2 . Create a queue proc e s s Next. This q ueue will store the next nodes to process.
3. Count the number of incoming edges of each node and set a class variable n od e . inbound. Nodes typi ca lly only store their outgoing edges. However, you can count the inbound edges by wa lking through each node n a nd, for each of its outgoing edges ( n , x ) , incrementing x . i n bound. 4. Wa l k through the nodes again and add to p r o c e s s Next any node where x , i n bound 5. While proc e s sNext is not em pty, do the fol lowi ng: »
632
Remove first node n from proc e s s Next.
Cracking the Codi n g Interview, 6th Edition
0.
XI. »
For each edge ( n , x ) , decrement x . in bound. lfx . in bound
»
Append n to order.
==
Advanced Topics
0, a ppend x to p r o c e s s N e xt.
6. If order contains all the nodes, then it has succeeded. Otherwise, the topolog ical sort has fa i led d u e
t o a cycle. This algorithm does sometimes come u p in interview questions. You r interviewer probably wou ldn't expect you to know it offhand. However, it wou ld be reasonable to have you derive it even if you've never seen it before. � Dij kstra's Algorithm
In some gra phs, we m ight want to have edges with weig hts. If the gra p h represented cities, each edge might represent a road and its weight might represent the travel time. In this case, we might want to ask, just as your GPS mapping system does, what's the shortest path from you r cu rrent location to another point p? This is where Dijksta's a lgorithm comes in. Dijkstra's algorithm is a way to find the shortest path between two points in a weig hted directed graph (which m ight have cycles). All edges must have positive values. Rather than just stating what Dijkstra's algorithm is, let's try to derive it. Consider the earlier described graph. We cou ld find the shortest path from s to t by litera l ly ta king a l l possible routes using actua l time. (Oh, and we'll need a machine to clone ou rselves.) 1 . Sta rt off at s . 2 . For each of s's outbound edges, clone ou rselves a n d start wa lking. I f t h e edge ( s , x ) h a s weight 5, we
should actu a l l y ta ke 5 minutes to get there. 3. Each time we get to a node, check if anyone's been there before. If so, then just stop. We're automatically
not as fast as another path since someone beat us here from s. If no one has been here before, then clone ou rselves and head out in all possible d i rections. 4. The fi rst one to get to t wins.
This works just fi ne. But, of cou rse, in the rea l a lgorithm we don't want to l itera l ly use a timer to find the shortest path. Imagine that each clone could jump immed iately from one node to its adjacent nodes (regardless of the edge weig ht), but it kept a time_so_far log of how long its path wou ld have taken if it did wal k at the "true" speed. Additionally, only one person moves at a time, and it's always the one with the lowest time_ so_fa r. This is sort of how Dijkstra's a lgorithm works. Dijkstra's algorithm finds the minimum weight path from a start node s to every node on the graph. Consider the fol lowing graph.
CrackingTheCodingl nterview.com I 6th Edition
633
XI. Adva nced To p ics
Assume we a re tryi ng to find the shortest path from a to i. We'll use Dijkstra's algorithm to find the shortest path from a to all other nodes, from which we will clearly have the shortest path from a to i. We first initial ize severa l variables: path_we ight [ node ] : maps from each node to the tota l weight of the shortest path. All values are initialized to infinity, except for path_weight [ a ] which is initial ized to 0. previou s [ node ] : maps from each node to the previous node in the (current) shortest path. rema i n i ng: a priority queue of all nodes in the graph, where each node's priority is defi ned by its path_weight. Once we've initialized these va lues, we can start adj usting the values of path_weight.
I
A (min) priority queue is a n abstract data type that-at least in this case-su pports insertion of a n object and key, removing the object with the m i n i m u m key, and decreasing a key. (Think of it like a typical q ueue, except that, instead of removing the oldest item, it removes the item with the lowest or hig hest priority.) It is an a bstract d ata type because it is defined by its behavior (its operations). Its underlying implementation can va ry. You could implement a priority queue with an a rray or a m i n (or max) heap (or many other data structu res).
We iterate through the nodes in rema i n ing (until rema i n i n g is empty), doing the fol lowing: 1.
Select the node in rema i n ing with the lowest value in path_we ight. Cal l this node n.
2. For each adjacent node, compare path_weight [ x ] (which is the weight of the current shortest path from a to x) to path_we ight [ n ] + ed ge_we ight [ ( n , x) ] . That is, could we get a path from a to x with lower weight by going through n instead of our current path? If so, update path_we ight and p reviou s. 3. Remove n from rema i n i ng.
When rema i n i n g is empty, then pat h_weight stores the weight of the cu rrent shortest path from a to each node. We can reconstruct this path by tracing through previous. Let's wal k through this on the a bove graph. 1. 2.
The fi rst value of n is a . We look at its adjacent nodes (b, c, and e), u pdate the va lues of path_we ight (to 5, 3, and 2) and previou s (to a ) and then remove a from rema i n i ng. Then, we go to the next smallest node, which is e. We previously updated path_weight [ e ] to be 2. Its adjacent nodes a re h and i, so we update path_we ight (to 6 and 9 ) and p r e v io u s for both of those.
634
Cracking the Coding Interview, 6th Edition
XI. Adva nced Topics
Observe that 6 is pat h_we ight [ e] (wh ich is 2) + the weight of the edge ( e , h ) (wh ich is 4). 3.
The next smal lest node is c, which has path_weight 3. Its adjacent nodes are b and d. The va lue of pat h_we ight [ d ] is infin ity, so we update it to 4 (which is pat h_we ight [ c ] + weight ( edge c , d ) . The va lue of pat h_we ight [ b ] has been previously set to 5. However, si nce pat h_we ight [ c ] + weight ( edge c , b ) (which is 3 + 1 4) is less than 5, we update path_we ight [ b ] to 4 and prev ious to c. This ind icates that we wou ld improve the path from a to b by going throu gh c . =
We continue doing t h i s until rema i n i n g i s em pty. The following diagram shows the cha nges to the p at h _ w e ig h t (left) and p r ev ious (right) at each step. The topmost row shows the cu rrent va lue for n (the node we are removing from rema i n ing). We black out a row after it has been removed from rema i n ing.
Once we're done, we can fol low this chart backwards, sta rting at i to find the actua l path. In this case, the smallest weight path has weight 8 and is a > c > d > g > i. -
-
-
-
Priority Queue and Runtime
As mentioned earlier, our a lgorithm used a priority queue, but this data structu re can be implemented in different ways. The runtime of this algorithm depends heavily on the implementation of the priority queue. Assume you have v vertices and e nodes. •
If you implemented the priority queue with a n array, then you would call remove_min u p to v times. Each operation wou ld ta ke O ( v ) time, so you'd spend O ( v2 ) time in the remove_min ca lls. Addition ally, you wou ld update the va lues of pat h_we ight and p revious at most once per edge, so that's 0 ( e ) time doing those u pdates. Observe that e must be less than of equal to v2 since you can't have more edges than there are pairs of vertices. Therefore, the tota l runtime is 0 ( v2 ) . If you implemented the priority queue with a min heap, then the remove_min calls will each ta ke 0 ( l o g v ) time (as will inserting and updating a key). We will do one remove_min call for each vertex, so that's O ( v log v ) (v vertices at 0 ( log v ) time each). Add itionally, on each edge, we might ca ll one u pdate key or insert operation, so that's 0 ( e log v ) . The tota l runtime is 0( ( v + e ) log v ) .
Whic h one is better? Well, that depends. If the graph has a lot of edges, then v2 will be close to e. I n this case, you might be better off with the a rray implementation, as O ( v2 ) is better than 0( ( v + v2) log v ) . However, if the graph is spa rse, then e is much less than v2• In this case, the min heap implementation may be better.
CrackingTheCodingl nterview.com \ 6th Edition
635
XI.
Adva nced Topics
� Hash Table Col l ision Resolution
Essentially any hash table can have collisions. There are a number of ways of handling this. Chaining with Linked Lists
With this approach (which is the most common), the hash ta ble's a rray maps to a l i nked list of items. We just add items to this linked list. As long as the number of collisions is fairly small, this will be quite efficient. In the worst case, lookup is 0 ( n ) , where n is the number of elements in the hash ta ble. This would only happen with either some very strange data or a very poor hash fu nction (or both). Chaining with Binary Search Trees
Rather than stori ng collisions i n a linked list, we could store collisions in a binary search tree. This will bring the worst-case runtime to 0 ( l og n ) . I n practice, we wou ld rarely take this approach u nless we expected a n extremely nonuniform distri bution. Op en Addressing with Linear Probing
I n this approach, when a collision occurs (there is a l ready a n item stored at the designated index), we j ust move on to the next index in the array u ntil we find a n open spot. (Or, sometimes, some other fixed distance, like the index + 5 .) If the number of collisions is low, this is a very fast and space-efficient solution. One obvious drawback of this is that the tota l number of entries in the hash ta ble is limited by the size of the array. This is not the case with chaining. There's another issue here. Consider a hash table with an underlying array of size 1 00 where indexes 20 through 29 a re fi l led (and nothing else). What a re the odds of the next insertion going to i ndex 30? Theodds are 1 0% because an item mapped to any index between 20 and 30 will wind up at index 30. This causes an issue called clustering. Quadratic Probing and Double Hashing
The distance between probes does not need to be linear. You could, for exam ple, increase the probe distance quadratically. Or, you could use a second hash function to determine the probe distance. � Rabin-Karp Su bstring Search
The brute force way to search for a substring S in a larger string B ta kes 0 ( s ( b - s ) ) time, where s is the length of S and b is the length of B. We do this by searching through the fi rst b s + 1 characters in B a nd, for each, checking if the next s cha racters match S . -
The Rabin-Karp algorithm optimizes this with a little trick: i f two strings a re the same, they must have the same hash va lue. (The converse, however, is not true. Two different strings can have the same hash value.) Therefore, if we efficiently precompute a hash value for each sequence of s characters withi n B, we can find the locations of S in O ( b ) time. We then just need to va lidate that those locations really do match S. For exa m ple, imagine our hash fu nction was simply the sum of each character (where space = 0, a = 1, b 2, and so on). If S is e a r and B = doe a re h ea ring me, we'd then just be looking for sequences where the sum is 24 (e + a + r) . This happens three times. For each of those locations, we'd check if the string rea lly is ear. =
636
Cracking the Coding Interview, 6th Edition
XI. Adva nced Topics e
sum
6
of next 3:
19
:;24
..
23
13
41
13
If we com puted these sums by doing ha sh ( · d o e ) , then h a sh ( ' oe we wou ld stil l be at 0 ( s ( b - s ) ) time. '
lnstead, we com pute the hash values by recognizing that h a s h ( ' oe c ode ( ' ) . This ta kes 0 ( b ) time to com pute all the hashes.
+
'
'
)
30
21
20
18
5
) , then hash ( ' e a ' ) , and so on, =
h a s h ( ' doe ' ) - cod e ( ' d ' )
'
You might argue that. still, in the worst case this will ta ke 0 ( s ( b - s ) ) time since many of the hash va lues could match. That's absol utely true-for this hash fu nction. In practice, we would use a better rolling hash function, such as the Rabin fingerpri nt. This essentia lly treats a stri ng like doe as a base 1 2 8 (or however many cha racters are in our a lphabet) num ber. hash ( ' doe ' ) cod e ( ' d ' ) * 1282 + cod e ( ' o ' ) * 1281 + code( ' e ' ) * 128° =
This hash fu nction will allow us to remove the d, shift the o a nd e, and then add in the space. hash ( ' oe ' ) = ( ha sh ( ' doe ' ) - code( ' d ' ) * 1282 ) * 128 + code ( ' ' ) This will considerably cut down on the number of fa lse matches. Using a good hash function l i ke this will give us expected time com plexity of O ( s + b ) a lthough the worst case is 0( sb ) . ,
Usage of this a lgorithm comes u p fa irly frequently i n interviews, so it's useful to know that you can identify substri ngs in linear time. �
AVL Trees
An AVL tree is one of two common ways to implement tree bala ncing. We will only discuss insertions here, but you can look up deletions sepa rately if you're interested. Properties
An AVL tree stores in each node the height of the subtrees rooted at this node. Then, for any node, we can check if it is height bala nced: that the height of the left su btree and the height of the right subtree differ by no more than one. This prevents situations where the tree gets too lopsided. balance ( n )
=
-1
n . left . height - n . right . height high ra nges) instead of simple values. A hotel could use this to store a list of all reservations and then efficiently detect who is staying at the hotel at a particular time. Graph coloring: A way of colori ng the nodes in a graph such that no two adjacent vertices have the same color. There a re various algorithms to do thi ngs like determ i ne if a graph can be colored with only K colors. P,
NP, and NP-Complete: P. NP. and N P-Complete refer to classes of problems. P problems a re p rob lems that can be q uickly solved (where "q u ickly" means polynom ia I ti me). NP pro bl ems a re those where, given a solution, the solution can be q uickly verified. N P-Complete problems a re a subset of N P prob lems that can all be reduced to each other (that is, if you found a solution to one p roblem, you could twea k the solution to solve other problems i n the set in polynomial time).
It is an open (and very famous) question whether P = N P. but the a nswer is generally believed to be no. Combinatorics and Probability: There a re various things you can learn a bout here, such as ra ndom va riables, expected value, and n-choose-k. Bipartite Graph: A bipartite graph is a graph where you can divide its nodes i nto two sets such that every edge stretches across the two sets (that is, there is never an edge between two nodes in the same set). There is an algorithm to check if a graph is a bipartite graph. Note that a bipartite graph is eq uiva lent to a graph that can be colored with two colors. Regular Expressions: You should know that regular expressions exist and what they can be used for (rough ly). You can also learn a bout how an algorithm to match regular expressions would work. Some of the basic syntax behind reg ular expressions could be useful as well.
There is of course a great deal more to data structu res and algorithms. If you're interested i n exploring these topics more deeply, I recommend picking up the hefty Introduction to Algorithms ("CLRS" by Cormen, Leiserson, Rivest and Stein) or The Algorithm Design Manual (by Steven Skiena).
644 I
Cracking the Coding Interview, 6th Edition
Code Li bra ry
Cthe fu l l code for a solution with the solution, but in some cases it got quite redundant.
e rta i n patterns came up while implementing the code for this book. We've tried to generally i nclude
This a ppendix provides the code for a few bf the most usefu l chunks of code. All code for the book can be downloaded from Cracki ngTheCodinglnterview.com . ._
HashMaplist
The Ha shMa p l i st class is essentially shorthand for Has hMap > . It allows us to map from an item of type of T to an Array l i s t of type E . For example, we might want a data structure that maps from a n integer to a list of strings. Ordina rily, we'd have to write something like this: 1
2 3
4 5
6
7 8
9
H a s hMa p < I nteger , Arrayl i s t < St r i n g > > maplist = new HashMa p < I nteger, Array l i s t < S t r i ng > > ( ) ; for ( string s : strings ) { i nt ke y c omp ut eVa l ue ( s ) ; if ( ! maplist . containsKey ( key ) ) { maplist . put ( key, new ArrayList ( ) ) ; } maplist . get ( key) . add ( s ) ; } =
Now, we can j ust write this:
1
2 3
4
5
H a s hMapl i st < I ntege r , String> maplist for ( String s : strings ) { i nt key computeVa l u e ( s ) ; maplist . put ( key, s ) ; }
new HashMaplist < I nteger, String> ( ) ;
=
It's not a big change, but it makes our code a bit simpler.
1
2
3
public cla s s H a s hMa p l i s t < T , E > { private H a s hMa p < T , Arraylist < E > > map
4
/ * I nsert item i nto list at key . */
5
=
public void put (T key , E item) { i f ( l ma p . conta in s Ke y ( key ) ) { ma p . put ( key, new Array l i s t < E > ( ) ) ; } map . get ( key ) . add ( item) ;
6
7
8 9
646
Cracking the Coding I nterview, 6th Edition
new HashMap > ( ) ;
XI. 10
}
11
12
/* Insert list of items at key . */ public void put ( T key, Array L i st < E > items ) { map . put ( key, items ) ; }
13
14
15
16
17 18
/* Get list of items at key . * / publi c ArrayList < E > get ( T key ) { return ma p . get ( key) ; }
19
20 21 22
/* Check if h a s hmaplist contains key . * / p u b l i c boolean cont a i n s Key ( T key ) { ret urn ma p . cont a i nsKey ( key ) ; }
23
24 25
26
27
/ * Check if list at key contains value . * / public boolean cont a i n s KeyValue ( T key, E value ) { ArrayList < E > list get ( key ) ; if ( l ist n u l l ) return fa lse; ret u r n list . cont a i n s (value ) ; }
2� 29
=
30
==
31 32
33
34
/ * G e t the list of keys . * / public set keyset ( ) { return ma p . keySet ( ) ; }
35
36 37 38 39
40 41 42
43
Code Li brary
}
@Override public St ring toSt r i ng ( ) { return map . t oSt ring ( ) ; }
� TreeNode {Binary Search Tree)
While it's perfectly fine-even good-to use the built-in binary tree class when possible, it's not a lways possible. In many questions, we needed access to the interna ls of the node or tree class (or needed to tweak these) and thus cou ldn't use the built-i n libraries. The TreeNode class supports a variety of fu nctiona lity, much of which we would n't necessarily want for every question/solution. For example, the TreeNode class tracks the parent of the node, even though we often don't use it (or specifically ban using it). For simplicity, we'd implemented this tree as storing integers for data.
1 2 3
4
5 6 7 8
9
public c l a s s TreeNode { public int dat a ; public TreeNode left , right , parent ; private int s i z e 0; =
public TreeNode ( i nt d ) { data d; size = 1 ; }
CrackingTheCodinglnterview.com I 6th Edition
647
XI. Code Library 10
11
publ i c void insertinOrde r ( int d ) { if ( d < = dat a ) { if ( left == n u l l ) { set leftChild ( new TreeNode ( d ) ) ; } else { left . inserti nOrde r ( d ) ; } } else { if ( right == n u l l ) { setRightChild ( new TreeNode ( d ) ) ; } else { right . insert i nOrde r ( d ) ; } } s i ze++; }
12 13
14 15
16 17 18
19
20 21 22
23 24 25
26
27 28
public int s i ze ( ) { ret urn s i ze; }
29
30 31
32
public TreeNode find ( int d ) { if ( d == data ) { ret urn this ; } else if ( d data) { ret urn right ! = null ? right . f ind ( d ) : n u l l ; } ret urn n u l l ; }
33
34 35
36 37
38 39
46 41
42 43
p u b l i c void setLeftChild (TreeNode left ) { this . left = left ; if ( left ! = n u l l ) { left . pa rent this ; } }
44
45
46
=
47
48
49
50
public void setRightChild ( T reeNode r ight ) { this . right = right ; if ( right ! = n u l l ) { right . parent = this ; } }
51
52 53
54
55 s• 57
}
Th is tree is i m p l e m e nted to be a b i n a ry search tree. Howeve r, you ca n use it for other p u rposes. You wou l d j u st n e e d t o u s e t h e s et LeftC h i l d/s e t R i g h t C h i l d m ethods, o r t h e l eft a n d r i g h t c h i l d va r i a bles. For this reason, we have kept these m ethods and variables p u b l i c . We need t h i s sort of a ccess for m a n y p robl ems.
648
Cracking the Coding I nterview, 6th Edition
XI. I!>
Code Library
LinkedlistNode (Linked List)
Like the TreeNode class, we often needed access to the internals of a linked list in a way that the built-in linked list class wou ld n't support. For this reason, we implemented our own class and used it for many problems.
1
public c l a s s LinkedListNode { p u bli c L i n kedlistNode next , prev, l a s t ; public int data; public Linked l istNode ( int d , Linked listNode n , LinkedListNode p ) { data = d ; setNext ( n ) ; setPreviou s ( p ) ; }
2 3
4 s
6
7 8
9
10
public Linked l i stNode ( i nt d) { data = d ; }
11
12
13 14
public LinkedListNode ( ) { }
15 16
public void setNext ( L i nked L istNode n) { next = n ; if (this == last ) { last = n ; } if ( n ! = null && n . prev ! = t h i s ) { n . setPrevious ( t h i s ) ; } }
17
18 19
20 21
22 23
24 25
26
public void setPrevious ( L inked ListNode p ) { prev = p ; if ( p ! = n u l l && p . next ! = t h i s ) { p . setNext ( t h i s ) ; } }
27
28
29
30 31
32 33
public LinkedListNode clone ( ) { LinkedL istNode next2 = nul l ; i f ( next ! = null) { next2 = next . clone ( ) ; } Linked L istNode head2 = new LinkedLi stNod e ( data, next 2 , null ) ; return head2 ; }
34 35
36 37
38
39 40 41
}
Again, we've kept the methods and va riables public because we often needed this access. This wou ld a l low the user to "destroy" the linked list, but we actually needed this sort of fu nctional ity for our pu rposes. I!>
Trie & TrieNode
The trie data structu re is used in a few problems to make it easier to look up if a word is a prefix of any other words in a dictionary (or l ist of va lid words). This is often used when we're recu rsively building words so that we can short circuit when the word is not valid. CrackingTheCodinglnterview.com j 6th Edition
649
XI. Code 1
Library
public c l a s s Trie { I I The root of this t rie . private TrieNode root;
2
3
4 5
I * Takes a list of s t r i ngs as an a rgument, and constructs a trie t hat stores * these strings . */ public Trie (Arraylist list ) { root = new TrieNode ( ) ; for (St ring word : list) { root . addWo rd (word ) ; } }
5
7
8
9
10 11
12 13
14 15
/ * Takes a l i s t o f s t r i ngs a s a n a rgument, a nd constructs a t r i e t hat stores * these st rings . */ public Trie ( St r i ng [ ] list ) { root = new TrieNode ( ) ; for ( St ring word : l i s t ) { root . addWo rd (word ) ; } }
16
17
18 19
20 21
22
23 24
I * Checks whet her t h i s trie conta ins a s t r i ng wit h the prefix passed in as * a rgument . *I public boolean conta ins ( St ring prefix , boolean exact ) { TrieNode lastNode root; int i 0; for ( i 0; i < prefix . lengt h ( ) ; i++ ) { lastNode = lastNode . getChild ( p refix . c ha rAt ( i ) ) ; i f ( lastNode null ) { return fa l s e ; }
25 26
27 28
=
=
=
29 30 31
==
32
33 34 35
}
return ! exact I I lastNode . terminates ( ) ;
35
}
37
38
public boolean contains ( St ring prefix ) { return contains ( prefix, false ) ; }
39
40 41
42
43 44
45
}
public TrieNode getRoot ( ) { return root ; }
The Trie class uses the TrieNode class, which is implemented below. 1
public c l a s s TrieNode { / * The c hildren of t h i s node in the t rie . * / private H a s hMap childre n ; private boolean terminates fal s e ;
2
3
4
=
5
6
I * The character stored in t h i s node as data . */ private char chara ct e r ;
7
8 9
10
650
/ * Co nst ructs a n empty trie node and initial izes the l i s t of its children t o an * empty h a s h ma p . Used o n l y to const ruct t he root node of the t rie . */
Cracking the Coding I nterview, 6th Edition
XI. Cod e 11
public T r ieNode ( ) { ch ildren = new H a s hMap ( ) ; }
12 13
14 15
/* Constructs a trie node and stores t h i s cha racter as the node ' s value . * Initializes t he list of child nodes of this node to an empty ha s h ma p . * / publ ic TrieNode ( char c h a racte r ) { this ( ) ; t h i s . cha racter = cha racter; }
16 17
18
19
20 21
22
/* Returns the cha racter data stored in this node . */ public char getCha r ( ) { ret urn character; }
23
24 25
26
27
/* Add this word to the trie, and recurs ively c reate the child * nodes . */ public void addWo rd ( String word ) { if (word == null I I wo rd . i s Empty ( ) ) { ret u r n ; }
28
29
30 31
32
33
34
char firstChar
35
36
38
39
4e 41
42 43 44
if (word . length ( ) > 1 ) { child . addWord ( wo rd . s ubstring ( l ) ) ; } else { c hild . setTerminates (true ) ; }
4.5
46 47
}
48 49
/ * F i nd a child node of t h i s node t hat has the c h a r a rgument as its data . Ret urn * null if no such child node is present i n the t rie . * / p u b l i c TrieNode getChild ( c har c ) { return c h i ldren . get ( c ) ; }
50
51
52
53
54 55
/ * Returns whet her t h i s node represents the end of a complete word . * / p u b l i c boolean terminates ( ) { ret urn terminate s ; }
56
57 58 59
60
61
62 64
word . c ha rAt ( 0 ) ;
TrieNode child getChild ( fi rstCha r ) ; if ( c hild == nul l ) { child = new TrieNode (firstCha r ) ; children . put (firstChar, child ) ; }
37
63
Library
}
/ * Set whet her this node is t he end of a complete word . * / public void setTerminates ( boolean t ) { terminates = t ; }
CrackingTheCodingl nterview.com j 6th Edition
651
I H i nts for Data Structu res
#1 .
1 .2
Describe what it means for two strings to be permutations of each other. Now, look at that definition you provided. Can you check the strings against that definition?
#2.
3.1
A stack is sim ply a data structure in which the most recently added elements are removed first. Can you simulate a single stack using a n a rray? Remember that there a re many possible solutions, and there are tradeoffs of each.
#3.
2.4
There a re many solutions to this problem, most of which are equally optimal in ru ntime. Some have shorter, cleaner code than others. Can you bra i nstorm different solutions?
#4.
4.1 0
If T2 is a su btree of Tl, how will its in-order traversal com pare to Tl's? What a bout its pre-order and post-order traversa l?
#5.
2.6
A pa lindrome is something which is the same when written forwards and backwards. What if you reversed the linked list?
#6.
4.1 2
Try simplifying the problem. What if the path had to sta rt at the root?
#7.
2.5
Of course, you cou ld convert the linked lists to integers, compute the sum, and then convert it back to a new linked list. If you did this in an interview, your interviewer wou ld l i kely accept the answer, and then see if you cou ld do this without converti ng it to a number and back.
#8.
2.2
What if you knew the linked list size? What is the difference between finding the Kth-to last element and finding the Xth element?
#9.
2.1
# 1 0.
4.8
If each node has a link to its parent, we could leverage the a pproach from question 2.7 on page 95. However, our interviewer might not let u s make this assu m ption.
#1 1 .
4. 1 0
The in-order traversals won't tell us much. After a l l, every binary sea rch tree with the same values (rega rdless of structure) will have the same i n-order traversal. This is what in-order traversal means: contents are in-order. (And if it won't work in the specific case of a binary sea rch tree, then it certa i n ly won't work for a genera l binary tree.) The pre order traversa l, however, is much more indicative.
#1 2.
3.1
We cou ld simu late three stacks in an a rray by just allocating the fi rst third of the a rray to the first stack, the second third to the second stack, and the final third to the third stack. One might actually be much bigger than the others, though. Can we be more flexible with the divisions?
Have you tried a hash table? You should be able to do this in a single pass of the linked list.
CrackingTheCo d i n g l nterview.com j 6th Edition
653
1
I
H i nts for
Data Struct u res
#1 3.
2.6
Try using a stack.
#1 4.
4. 1 2
Don't forget that paths could overlap. For example, if you're looking for the sum 6, the paths 1 - > 3 - > 2 and 1 - > 3 - > 2 - >4 - > - 6 - >2 are both valid.
#1 5.
3.5
One way of sorting an a rray is to iterate through the array and insert each element into a new array in sorted order. Can you do this with a stack?
#1 6.
4.8
The first common ancestor is the deepest node such that p and q a re both d escendants Think a bout how you might identify this node.
#1 7.
1 .8
If you just cleared the rows and columns as you found Os, you'd likely wind u p clearing the whole matrix. Try finding the cells with zeros first before making any cha nges to the matrix.
# 1 8.
4. 1 0
You may have concluded that if T2 . p r e o rd e rTra v e r s a l ( ) is a su bstring of Tl . p reord e rT ra v e r sa l ( ) , then T2 is a subtree of Tl. This is a l most true, except that the trees could have duplicate va lues. Suppose Tl and T2 have all duplicate values but different structures. The pre-order traversals will look the same even though T2 is not a su btree of Tl. How can you handle situations like this?
#1 9.
4.2
A minimal binary tree has a bout the same number of nodes on the left of each node as on the right. Let's focus on just the root for now. How would you ensure that about the same number of nodes a re on the left of the root as on the right?
#20.
2.7
You can do this in O ( A+B ) time and 0 ( 1 ) additional space. That is, you do not need a hash table (although you cou ld do it with one).
#21 .
4.4
Think about the definition of a bala nced tree. Can you check that condition for a single node? Can you check it for every node?
#22.
3.6
We could consider keeping a single linked list for dogs and cats, and then iterating thro u g h it to find the fi rst dog (or cat) . What is the im pact of doing this?
#23.
1 .5
Sta rt with the easy thing. Ca n you check each of the conditions separately?
#24.
2.4
Consider that the elements don't have to stay in the same relative order. We only need to ensure that elements less than the pivot must be before elements g reater than the pivot. Does that help you come up with more solutions?
#25.
2.2
If you don't know the linked list size, can you compute it? How does this impact the runtime?
#26.
4.7
Build a d i rected graph representi ng the dependencies. Each node is a project and an edge exists from A to B if B depends on A (A must be b u i lt before 8). You can a lso build it the other way if it's e a s i e r for you.
#27.
3.2
Observe that the minimum element doesn't change very often. It only changes when a smaller element is added, or when the smallest element is popped.
#28.
4.8
How would you figure out if p is a descendent of a node n ?
#29.
2.6
Assume you have the length of the l i n ked list. Can you implement this recursively?
#30.
2.5
Try recursion. Suppose you have two lists, A 1- > 5 - >9 (representing 95 1 ) and B 2 - > 3 - > 6 - > 7 (representing 7632), and a function that operates on the remainder of the lists (5 - >9 and 3- > 6 - > 7). Could you use this to create the sum method? What is the relationship between s um ( l - > 5 - >9 , 2 - > 3 - > 6 - > 7 ) and s um ( 5 - >9 , 3 - > 6 - >7)?
654
I
Cracki ng the Coding Interview, 6th Edition
=
.
1
I Hints for Data Structures
#3 1 .
4.1 0
Although the problem seems l i ke it stems from dupl icate values, it's really deeper than that. The issue is that the pre-order traversal is the same only because there are null nodes that we skipped over (because they're null). Consider inserti ng a placeholder value into the pre-order traversa l string whenever you reach a null node. Reg ister the null node as a "rea l" node so that you can distinguish between the d ifferent structu res.
#32.
3.5
Imagine you r secondary stack is sorted. Can you insert elements into it in sorted order? You m ight need some extra storage. What could you use for extra storage?
#33.
4.4
If you've developed a brute force solution, be carefu l a bout its runtime. If you are computing the height of the su btrees fo r each node, you cou ld have a pretty inefficient algorithm.
#34.
1 .9
If a string is a rotation of a nother, then it's a rotation at a particular poi nt. For example, a rotation of wat e r bot t l e at cha racter 3 means cutting waterbottl e at cha racter 3 and putti ng the right half (e r bot t l e ) befo re the left half (wat).
#35.
4.5
If you traversed the tree using a n in-order traversal and the elements were truly in the rig ht order, does this ind icate that the tree is actually i n order? What happens for duplicate elements? If duplicate elements a re allowed, they must be on a specific side (usua l ly the left).
#36.
4.8
Start with the root. Can you identify if root is the fi rst common ancestor? If it is not, can you identify which side of root the first common ancestor is on?
#37.
4. 1 0
Alternatively, we can handle this problem recursively. Given a specific node within Tl, can we check to see if its su btree matches T2?
#38.
3.1
If you want to a llow for flexible divisions, you can shift stacks a round. Can you ensure that a l l available capacity is used ?
#39.
4.9
What is the very fi rst value that must be in each array?
#40.
2.1
Without extra space, you'll need O ( N2 ) time. Try using two poi nters, where the second one searches a head of the first one.
#41 .
2.2
Try implementing it recu rsively. If you could find the (K - l)th to last element, can you find the Kth element?
#42.
4. 1 1
Be very carefu l in this problem to ensure that each node is equa l ly likely and that you r solution doesn't slow down the speed of standard binary search tree a lgorithms (like i n s e rt, find, and d e l ete). Also, remember that even if you assume that it's a balanced binary search tree, this doesn't mean that the tree is ful l/complete/perfect.
#43.
3.5
Keep the secondary stack in sorted order, with the biggest elements on the top. Use the primary stack fo r additional storage.
#44.
1 .1
Try a hash table.
#45.
2.7
Examples will help you. Draw a pictu re of intersecting linked lists and two equiva lent linked lists (by va lue) that do not intersect.
#46.
4.8
Try a recu rsive approach. Check if p and q a re descendants of the left su btree and the right subtree. If they are descenda nts of different su btrees, then the cu rrent node is the first common ancestor. If they a re descenda nts of the same su btree, then that su btree holds the first common ancestor. Now, how do you implement this efficiently?
Cracki ngTheCodinglnterview.com I 6th Edition
655
1
I H i nts for Data Structures
#47.
4.7
Look at this graph. ls there any node you can identify that will defi n itely be okay to build fi rst?
#48.
4.9
The root is the very fi rst value that must be in every array. What can you say a bout the order of the va lues in the left subtree as compared to the values in the right su btree? Do the left subtree values need to be i nserted before the right subtree?
#49.
4.4
What if you cou ld modify the binary tree node class to allow a node to store the height of its subtree?
#SO.
2.8
There are really two parts to this problem. Fi rst, detect if the linked list has a loop. Second, figure out where the loop sta rts.
#S1 .
1 .7
Try thinking a bout it layer by layer. Can you rotate a specific layer?
#S2.
4.1 2
If each path had to start at the root, we could traverse all possible paths starting from the root. We can track the sum as we go, incrementing tota l P a t h s each time we find a path with our target sum. Now, how do we extend this to paths that can sta rt anywhere? Remember: Just get a brute-force a lgorithm done. You can optimize later.
#S3.
1 .3
It's often easiest to modify strings by going from the end of the string to the beginning.
#S4 .
4. 1 1
Thi s is you r own binary search tree class, so you can maintain any i nformation about the tree structure or nodes that you'd like (provided it doesn't have other negative implica tions, like making i n s e rt much slower). In fact, there's probably a reason the i nterview question specified that it was you r own class. You probably need to store some addi tional information i n order to im plement this efficiently.
#SS.
2.7
Focus fi rst on just identifying if there's a n i ntersection.
#S6.
3.6
Let's suppose we kept separate lists for dogs and cats. How would we find the oldest anima l of any type? Be creative!
#S7.
4.5
#S8.
3.1
Try thinking a bout the array as circu l a r, such that the end of the a rray "wraps around "to the start of the a rray.
#S9.
3.2
What if we kept track of extra data at each stack node? What sort of data might make it easier to solve the problem?
#60.
4.7
If you identify a node without any incoming edges, then it ca n defi nitely be built. Find this node (there cou ld be mu ltiple) and add it to the build order. Then, what does this mean for its outgoing edges?
#61 .
2.6
In the recursive approach (we have the length of the list), the middle is the base case: i s P e rmutat ion ( midd l e ) is true. The node x to the immediate left of the middle: What can that node do to check if x- >midd l e - >y forms a palindrome? Now suppose that checks out. What about the previous node a? If x - >midd l e - >y is a palindrome, how can it check that a - > x - >middle - >y - > b is a palindrome?
#62.
4.1 1
656
To be a binary sea rch tree, it's not sufficient that the left . value < "' c u r rent . v a l u e < right . v a l u e for each node. Every node on the left must be less than the current node, which must be less than all the nodes on the right.
As a naive "brute force" algorithm, can you u se a tree traversal algorithm to implement this algorithm? What is the ru ntime of this?
Cracki ng the Coding Interview, 6th Edition
1
I
H i nts for Data Structu res
#63.
3.6
Think a bout how you'd do it in rea l life. You have a list of dogs in chronologica l order and a list of cats in chronological order. What data would you need to find the oldest animal? How would you maintain this data?
#64.
3.3
You will need to keep track of the size of each su bstack. When one stack is full, you may need to create a new stack.
#65.
2.7
Observe that two i ntersecti ng linked lists will always have the same last node. Once they i ntersect, a l l the nodes after that will be equal.
#66.
4.9
The relationshi p between the left su btree values and the right su btree va lues is, essen tially, anythi ng. The left su btree values cou ld be inserted before the right su btree, or the reverse (right va lues before left), or any other ordering.
#67.
2.2
You mightfin d it useful to retu rn multiple va lues. Some languages don't directly support this, but there are workarounds in essentially a ny language. What a re some of those worka rou nds?
#68.
4.1 2
lo extend this to paths that sta rt a nywhere, we ca n just repeat this process for all nodes.
#69.
2.8
To identify if there's a cycle, try the "ru nner" approach descri bed on page 93. Have one pointer move faster than the other.
#70.
4.8
In the more naive algorithm, we had one method that indicated if x is a descendent of n, and another method that would recurse to find the first common ancestor. This is repeated ly sea rching the same elements in a su btree. We should merge this i nto one f i r stCommonAnc e stor function. What retu rn va lues wou ld give us the information we need ?
#71 .
2.5
Make sure you have considered linked lists that a re not the same length.
#72.
2.3
Pictu re the list 1 - > 5 - >9 - > 1 2. Removing 9 wou ld make it look like 1 - > 5 - >12. You only have access to the 9 node. Ca n you make it look l ike the correct answer?
#73.
4.2
You could im plement this by finding the "ideal" next element to add and repeatedly ca lling i n s e rtVa l u e. This will be a bit inefficient, as you wou ld have to repeated ly traverse the tree. Try recursion instead. Can you divide this problem i nto subproblems?
#74.
1 .8
Can you use 0 ( N) additional space instead of 0( N2 ) ? What information do you really need from the list of cells that are zero?
#75.
4.1 1
Alternatively, you cou ld pick a ra ndom depth to traverse to and then ra ndomly traverse, stopping when you get to that depth. Think this through, though. Does this work?
#76.
2.7
You can determine if two linked lists i ntersect by traversing to the end of each and comparing their tai ls.
#77.
4.1 2
If you've designed the algorithm as described thus far, you' l l have a n O ( N log N ) a lgorithm i n a balanced tree. This is because there a re N nodes, each of which is at depth O ( log N ) at worst. A node is touched once for each node a bove it. Therefore, the N nodes will be touched O ( log N ) time. There is an optim ization that wi ll give us a n O ( N ) algorith m.
#78.
3.2
Consider having each node know the minimum of its "substack" (a ll the elements beneath it, including itself).
#79.
4.6
Thi n k a bout how an in-ordertraversa l works and try to "reverse engi neer" it.
CrackingTheCodinglnterview.com I 6th Edition
I
657
1
I H i nts for Data Structu res The f i r s tCommonAn c e stor function could return the first common ancestor (if p and q are both contained in the tree), p if p is i n the tree and not q, q if q is in the tree and not p, and n u l l otherwise.
#80.
4.8
#8 1 .
3.3
#82.
4.9
Break this down into subproblems. Use recursion. If you had all possible sequences for the left su btree and the right subtree, how could you create all possible sequences for the entire tree?
#83.
2.8
You ca n use two pointers, one moving twice as fast as the other. lf there is a cycle, the two poi nters will collide. They will land at the same location at the same time. Where do they land? Why there?
#84.
1 .2
There is one solution that is O ( N log N ) time. Another solution uses some space, but is O ( N ) time.
#85.
4.7
Once you decide to build a node, its outgoing edge can be deleted. After you've done this, can you find other nodes that a re free and clear to build?
#86.
4.5
If every node on the left must be less than or equal to the current node, then this is really the same thing as saying that the biggest node on the left must be less than or equal to the current node.
#87.
4. 1 2
What work is d u p licated in the cu rrent brute-force a lgorithm?
#88.
1 .9
We a re essentia l ly asking if there's a way of splitting the first string into two parts, x and y, such that the first string is xy and the second string is yx. For exam ple, x wat and y = e r bott le. The first string is xy wate r bott le. The second string is yx e r bottl ewat.
Popping an element at a specific su bstack wi ll mean that some stacks aren't at ful l capacity. Is this an issue? There's n o right answer, but you should t h i n k a bout how to handle this.
=
=
=
#89.
4.1 1
Picking a random depth won't help u s much. Fi rst. there's more nodes at lower depths than higher depths. Second, even if we re-balanced these probabilities, we could hit a "dead end" where we meant to pick a node at depth 5 but hit a leaf at depth 3. Re-balancing the probabilities is an interesting , though.
#90.
2.8
If you haven't identified the pattern of where the two pointers start, try this: Use the linked list 1 ->2->3->4->5->6->7->8->9->?, where the ? links to another node. Try making the ? the first node (that is, the 9 points to the 1 such that the entire linked list is a loop). Then make the ? the node 2. Then the node 3. Then the node 4. What is the pattern? Can you explain why this happens?
#91 .
4.6
Here's one step of the log ic: The successor of a specific node is the leftmost node of the right su btree. What if there is no right subtree, though?
#92.
1 .6
Do the easy thing first. Compress the string, then compare the lengths.
#93.
2.7
Now, you need to find where the linked lists i ntersect. Suppose the linked lists were the same length. How could you do this?
658
I
Cracking the Coding I nterview, 6th Edition
I
j H i nts for Data Structures
#94.
4.1 2
Consider each path that sta rts from the root (there are N such paths) as an array. What our brute-force algorithm is really doing is ta king each a rray and finding all contiguous subsequences that have a particular sum. We're doing this by com puting all suba rrays and their sums. It might be useful to just focus on this little subproblem. Given an array, how would you find a l l contig uous su bsequences with a particular sum? Again, think about the du plicated work in the brute-force a lgorith m.
#95.
2.5
Does you r algorithm work on linked lists like 9->7->8 and 6->8-> 5? Dou ble check that.
#96.
4.8
Ca refu l! Does you r a lgorithm handle the case where only one node exists? What will happen? You might need to tweak the return values a bit.
#97.
1 .5
What is the relationship between the "insert character" option and the "remove char acter" option? Do these need to be two separate checks?
#98.
3.4
The major difference between a queue and a stack is the order of elements. A queue removes the oldest item and a stack removes the newest item. How cou ld you remove the oldest item from a stack if you only had access to the newest item?
#99.
4.1 1
A naive approach that many people come up with is to pick a ra ndom number between 1 and 3. If it's 1 , retu rn the cu rrent node. If it's 2, branch left. If it's 3, branch right. This solution doesn't work. Why not? Is there a way you ca n adjust it to make it work?
#1 00.
1 .7
Rotating a specific layer wou ld just mean swapping the values in four arrays. If you were asked to swa p the va lues in two arrays, could you do this? Can you then extend it to four arrays?
#1 01 .
2.6
Go back to the previous hint. Remember: There are ways to return mu ltiple values. You can do this with a new class.
#1 02.
1 .8
You probably need some data storage to mai ntain a list of the rows and col umns that need to be zeroed. Can you red uce the additional space usage to 0 ( 1 ) by using the matrix itself for data storage?
#1 03.
4. 1 2
We a re looking for suba rrays with sum ta rgetS um. Observe that we can track in consta nttimetheva l u e of r u n n i n gSum i , where this is the sum from element O through element i . For a suba rray of element i through element j to have sum t a r getS u m, r u n n i ngSumi _ 1 + t a r getS um must equal r u n n i ngSum j (try d rawing a pictu re of an array or a n u m ber line). Given that we can track the r u n n i ngSum as we go, how can we qu ickly look up the number of ind ices i where the previous equation is true?
#1 04.
l .9
Th ink about the earlier hi nt. Then think a bout what happens when you concatenate e rbottl ewat to itself. You get e r bottl ewate rbott l ewat.
#1 05.
4.4
You don't need to modify the binary tree class to store the height of the su btree. Can you r recu rsive fu nction compute the height of each su btree while also checking if a node is balanced? Try having the fu nction return m u ltiple va lues.
#1 06.
1 .4
You do not have to-and should not-generate a l l permutations. This wou ld be very ineflicient.
#1 07.
4.3
#1 08.
4.1 2
Try mod ifying a g raph sea rch a lgorithm to track the depth fro
1 the root.
,
Try using a hash ta ble that maps from a r u n n i ngSum va lue to he number of elements with this runn ingS um.
I ln I 6th Edition
CrackingTheCodinglnterview.co
659
1
I
H i nts
for Data Structu res
#1 09.
2.5
#1 1 0.
1 .6
#1 1 1 .
2.7
If the two linked lists were the same length, you could traverse forward i n each u ntil you fo und an element in common. Now, how do you adjust this for lists of d ifferent lengths?
#1 1 2.
4.1 1
The reason that the earlier solution (picking a ra ndom number between 1 and 3) doesn't work is that the probabilities for the nodes won't be equal. For example, the root will be retu rned with proba bility X , even if there a re 50+ nodes in the tree. Clea rly, not all the nodes have proba bility X , so these nodes won't have equal probability. We can resolve this one issue by picking a ra ndom number between 1 and s i z e_of_t ree instead. This only resolves the issue fo r the root, though. What about the rest of the nodes?
#1 1 3.
4.5
Rather than validating the current node's value against leftT ree . max and r ightT ree . min, can we flip a round the logic? Va lidate the left tree's nodes to ensure that they a re smaller than c u r rent . v a l ue.
#1 1 4.
3.4
We can remove the old est item from a stack by repeatedly removi ng the newest item (inserting those into the temporary stack) u ntil we get down to one element. Then, after we've retrieved the newest item, putting all the elements back. The issue with this is that doing several pops in a row will require O ( N ) work each time. Can we optimize for scenarios where we might do several pops in a row?
#1 1 5.
4.1 2
O nce you've solid ified the algorithm to find a l l contiguous suba rrays in an a rray with a given sum, try to a pply this to a tree. Remember that as you're traversing and mod ifying the hash table, you may need to "reverse the damage" to the hash table as you traverse back up.
#1 1 6.
4.2
Imagine we had a c re ateMi n ima l Tree method that returns a minimal tree fo r a given a rray (but fo r some stra nge reason doesn't operate on the root of the tree). Cou ld you use this to operate on the root of the tree? Cou ld you write the base case fo r the fu nction? Great! Then that's basica lly the entire function.
#1 1 7.
1.1
Cou ld a bit vector be useful?
#1 1 8.
1 .3
You might find you need to know the number of spaces. Ca n you just count them?
#1 1 9.
4. 1 1
The issue with the earlier solution is that there could be more nodes on one side of a node than the other. So, we need to weight the probability of going left and right based on the number of nodes on each side. How does this work, exactly? How can we know the number of nodes?
#1 20.
2.7
Try using the difference between the lengths of the two linked lists.
#121 .
1 .4
What characteristics would a string that is a permutation of a palindrome have?
#1 22.
1 .2
Cou ld a hash table be useful ?
#1 23.
4.3
A hash ta ble o r a rray that maps from level number t o nodes a t that level might also be useful.
660
I
For the fo llow-up question: The issue is that when the linked lists aren't the same length, the head of one linked list might represent the 1 OOO's place while the other represents the l O's place. What if you made them the same length? Is there a way to modify the l i n ked list to do that, without changing the va lue it represents? Be careful that you a ren't repeatedly concatenating strings together. This can be very inefficient.
Cracking the Coding Interview, 6th Edition
1
I
H i nts fo r Data Structu res
#1 24.
4.4
#1 25.
4.7
#1 26.
2.2
#1 27.
4.1
Two well-known algorithms can do this. What are the tradeoffs between them?
#1 28.
4.5
Thi n k a bout the c h e c kBST fun ction as a recu rsive fu nction that ensures each node is within a n a llowa ble ( m i n , max ) range. At first, this ra nge is infinite. When we traverse to the left, the m i n is negative infinity and the max is root . v a l u e . Can you implement this recu rsive function and properly adjust these ra nges as you traverse the tree?
#1 29.
2.7
If you move a pointer in the longer linked list forward by the d ifference in lengths, you can then apply a similar approach to the scenario when the l i n ked lists are equal.
#1 30.
1 .5
Can you do all three checks i n a single pass?
#1 3 1 .
1 .2
Two stri ngs that a re permutations should have the same cha racters, but in different orders. Can you m a ke the orders the same?
#1 32.
1 .1
Can you solve it i n O ( N log N ) time? What might a solution l i ke that look like?
#1 33.
47
Pick an a rbitra ry node and do a depth-first search on it. Once we get to the end of a path, we know that this node can be the last one built, since no nodes depend on it. What does this mean a bout the nodes right before it?
#1 34.
1 .4
Have you tried a hash table? You should be able to get this down to 0 ( N ) time.
#1 35.
4.3
You should be able to come up with an algorithm involving both depth-first search and breadth-first sea rch.
#1 36.
1 .4
Can you red uce the space usage by using a bit vector?
.
Actually, you can just have a single c h e c kHeight function that does both the height computation and the balance check. An integer return value can be used to ind icate both. As a totarry different approach: Consider doing a depth-first search starting from an arbi tra ry node. What is the relationship between this depth-first search and a valid b uild order? Can you do it iteratively? I magine if you had two pointers pointing to adjacent nodes and they were moving at the same speed through the linked list. When one hits the end of the linked list, where will the other be?
CrackingTheCodinglnterview.com I 6th Edition
T
661
II H i nts for Concepts and Algorith ms
#1 37. #1 38.
5. 1
8.9
Brea k this into pa rts. Focus first on clearing the appropriate bits. Try the Base Case and Build approach.
#1 39.
6.9
Given a specific door x , on which rounds will it be toggled (open or closed)?
#1 40.
1 1 .S
What does the interviewer mean by a pen? There a re a lot of different types of pens. Make a list of potential q uestions you would wa nt to ask.
#141.
7.1 1
This is not as com plicated as it sou nds. Start by making a list of the key objects in the system, then think about how they interact.
#1 42.
9.6
Fi rst, start with making some assumptions. What do and don't you have to build?
#1 43.
5.2
To wrap your head a round the problem, try thinking a bout how you'd do it fo r integers.
#1 44.
8.6
Try the Base Case a nd Build approach.
#1 45.
5.7
Swapping each pair means moving the even bits to the left and the odd bits to the right. Can you break this problem into parts?
# 1 46 .
6. 1 0
Sol ution 1 : Sta rt with a simple approach. Can you just divide up the bottles into groups? Remember that you can't re-use a test strip once it is positive, but you can reuse it as long as it's negative.
#1 47.
5.4
Get Next: Start with a brute fo rce solution fo r each.
#1 48.
8. 1 4
Can we just try all possibilities? What would this look like?
#1 49.
6.5
#1 50.
8.7
Approach 1 : Suppose you had a l l permutations of a b c . How ca n you use that to get all permutations of abed?
#1 5 1 .
5.5
Reverse engineer this, sta rting from the outermost layer to the in nermost layer.
#1 52.
8.1
Approach this from the top down. What is the very last hop the child made?
#1 53.
7. 1
#1 54.
6 .7
#1 55.
662
8.1 3
Play a round with the jugs of water, pouring water back and forth, and see if you can measure anything other than 3 quarts or 5 quarts. That's a start.
Note that a "card deck" is very broad. You might want to think about a reasonable scope to the problem. O bserve that each family wil l have exactly one girl. Will sorting the boxes help i n any way?
Cracking the Coding Interview, 6th Edition
11
I
H i nts for Concepts
and Algorithms
# 1 56.
6.8
This is really an algorithm problem, a nd you should a pproach it as such. Come u p with a brute force, compute the worst-case number of drops, then try to optimize that.
#1 57.
6.4
In what cases will they not collide?
#1 58.
9.6
We've assumed that the rest of the eCom merce system is already hand led, and we just need to dea l with the analytics part of sales ra nk. We can get notified somehow when a purchase occu rs.
#1 59.
5.3
Start with a brute force solution. Can you try all possibilities?
#1 60.
6.7
Think about writing each fa mily as a sequence of Bs a nd Gs.
#1 61 .
8.8
You could handle this by just checking to see if there a re dupl icates before printing them (or adding them to a list). You can do this with a hash table. In what case might this be okay? I n what case might it not be a very good solution?
#1 62.
9.7
Will this appl ication be write-heavy or read-heavy?
#1 63.
6.1 0
Solution 1 : There is a relatively simple approach that works in 28 days, in the worst case. There are better a pproaches though.
#1 64.
1 1 .5
Consider the scenario of a pen for children. What does this mea n? What are the different use cases?
#1 65.
9.8
Scope the problem well. What will and won't you tackle as part of this system?
#1 66.
8.5
Think a bout multiplying 8 by 9 as counting the number of cells in a matrix with width 8 and height 9.
#1 67.
5.2
In a number like 8 9 3 (in base 1 O), what does each digit signify? What then does each digit in . 1 0010 sign ify in base 2?
#1 68.
8.1 4
We can think a bout each possibility as each place where we ca n put parentheses. Th is means a round each operator, such that the expression is split at the operator. What is the base case?
#1 69.
5.1
To
#1 70.
8.3
Start with a brute force a lgorithm.
#1 7 1 .
6.7
You ca n attempt this mathematica lly, a lthough the math is pretty difficu lt. You might find it easier to esti mate it up to fam il ies of, say, 6 children. This won't give you a good mathematical proof, but it m ight point you in the right direction of what the a nswer might be.
#1 72.
6.9
In which cases would a door be left open at the end of the process?
#1 73.
5.2
A number such as 8 9 3 (in base 1 0) ind icates 8 Tra nslate this system into base 2.
#1 74.
8.9
Suppose we had all va lid ways of writing two pairs of parentheses. How could we use this to get all va l id ways of writing three pa irs?
#1 75.
5 .4
Get Next: Picture a binary number-something with a bunch of 1 s and Os spread out throughout the num ber. Su ppose you flip a 1 to a 0 a nd a 0 to a 1 . In what case will the number get bigger? In what case will it get smaller?
•
clear the bits, create a "bit mask" that looks like a series of 1 s, then Os, then 1 s.
•
* 10-1
+
9 * 10-2
+
CrackingTheCodingl nterview.com I 6th Edition
3 * 10-3•
663
11
I H i nts for Concepts and Algorith ms Think about what sort of expectations on fresh ness and accuracy of data is expected. Does the data a lways need to be 1 00% up to date? Is the accuracy of some produ cts more im porta nt than others?
#1 76.
9.6
#1 77.
1 0.2
How do you check if two words are anagrams of each other? Think about what the defi nition of "a nagram" is. Explain it in your own words.
#1 78.
8.1
If we knew the number of paths to each ofthe steps before step 1 00, could we compute the number of steps to 1 00?
#1 79.
7.8
Should white pieces and black pieces be the same class? What are the pros and cons of this?
#1 80.
9.7
Observe that there is a lot of data coming in, but people probably a ren't reading the data very frequently.
#181.
6.2
Calculate the proba bility of winning the first game a nd winning the second game, then compare them.
#1 82.
1 0.2
Two words are anagrams if they contain the same characters but i n different orders. How can you put characters in order?
#1 83.
6. 1 0
Solution 2: Why do we have such a time lag between tests a nd results? There's a reason the q uestion isn't phrased as just "m inimize the number of rounds of testing:' The time lag is there for a reason.
#1 84.
9.8
How evenly do you think traffic is distributed? Do a l l documents get roughly the same age of traffic? Or is it likely there are some very popu lar documents?
#1 85.
8.7
Approach 1 : The permutations of abc represent all ways of ordering a b c . Now, we want to create all orderings of a b e d . Ta ke a specific ordering of abed, such as bdea. This b d e a string represents a n ordering of abe, too: Remove the d and you get b c a . Given the string be a, ca n you create all the "related" orderings that include d, too?
#1 86.
6.1
You can only use the sca le once. Th is means that all, or almost all, of the bottles must be used. They a lso must be hand led in d ifferent ways or else you could n't distinguish between them.
#1 87.
8.9
We could try generating the solution for three pairs by taking the list of two pairs of parentheses and adding a third pair. We'd have to add the third paren before, a round, a nd after. That is: ( ) < SOLUTION > , ( < SOLUTION > ) , < SO LUTION > ( ) . Wi ll this work?
#1 88.
6.7
Logic might be easier than math. Imagine we wrote every birth into a giant stri ng of Bs and Gs. Note that the groupings of fa milies are irrelevant for this problem. What is the proba bility of the next character added to the string being a B versus a G?
#1 89.
9.6
Purchases will occur ve ry freq uently. You probably want to limit database writes.
#1 90.
8.8
If you haven't solved 8.7 yet, do that one first.
#191.
6. 1 0
Solution 2: Consider running m u ltiple tests at once.
#1 92.
7.6
#1 93.
1 0 .9
664
I
A common trick when solving a jigsaw puzzle is to separate edge and non-edge pieces. How will you represent this in an object-oriented manner? Sta rt w it h a na ive solution. {But hopefu lly not too naive. You should be able to use the fact that the matrix is sorted.)
Cracking the Coding Interview, 6th Edition
11
j H i nts for Con cepts and Algorithms
#1 94.
8.1 3
We c a n sort t h e boxes b y a n y d imension in descending order. This w i l l give us a pa rtia l order for the boxes, i n that boxes later in t h e a rray must a ppea r before boxes earlier i n the array.
#1 95.
6.4
The only way they won't collide is if all three a re wa lking in the same direction. What's the proba bility of all th ree wa lking clockwise?
#1 96.
W. 1 1
Imagine the a rray were sorted in ascending order. Is there any way you could "fix it" to be sorted into a lternating peaks and va lleys?
#1 97.
8.1 4
The base case is when we have a single value, 1 or 0.
#1 98.
7.3
Scope the problem first and make a list of you r assum ptions. It's often okay to make reasonable assumptions, but you need to make them explicit.
#1 99.
9.7
The system will be write-heavy: Lots of data being i m ported, but it's rarely being read.
#200.
8.7
Approach 1 : Given a string such as bca, you ca n create all permutations of a bed that have { a , b, c } in the order bca by inserting d into each possible location: dbca, bd c a , bed a, bead. Given all permutations of abc, can you then create all permutations of a be d ?
#201 .
6.7
Observe that biology hasn't cha nged; o n l y t h e conditions under which a fa mily stops having kids has cha nged. Each pregna ncy has a 50% odds of being a boy and a 50% odds of being a girl.
#202.
5.5
What does it mean if A & B
#203.
8.5
If you wa nted to count the cells in an 8x9 matrix, you cou ld count the cells in a 4x9 matrix and then double it.
#204.
8.3
You r brute force algorithm probably ra n in O ( N ) time. If you're trying to beat that ru ntime, what ru ntime do you think you will get to? What sorts of a lgorithms have that ru ntime?
#205.
6.1 0
Solution 2: Think about trying to figure out the bottle, d igit by dig it. How can you detect the first digit in the poisoned bottle? What about the second digit? The third digit?
#206.
9.8
How will you handle generating U RLs?
#207.
1 0.6
Thi n k a bout merge sort versus qu ick so rt. Wou ld one of them work well for this pu rpose?
#208.
9.6
You a lso want to limit joins because they ca n be very expensive.
#209.
8.9
The problem with the solution suggested by the earlier hint is that it might have dupli cate values. We cou ld elimi nate this by using a hash table.
#2 1 0.
1 1 .6
Be ca refu l a bout you r assu m ptions. Who are the users? Where a re they using this? It m ight seem obvious, but the rea l answer might be different.
#21 1 .
1 0.9
We can do a binary sea rch in each row. How long will this take? How can we do better?
#21 2.
9.7
Think a bout things like how you're going to get the ba nk data (will it be pul led or pushed?), what features the system will support, etc.
#21 3.
7.7
As a lways, scope the problem. Are "friendships" mutual? Do status messages exist? Do you support group chat?
#2 1 4.
8. 1 3
Try to brea k it down into subproblems.
==
0?
CrackingTheCodinglnterview.com J 6th Edition
665
11
I H i nts for Concepts and Algorithms
#21 5.
5.1
It's easy to create a bit mask of Os at the beginning or end. But how do you create a bit mask with a bunch of zeroes in the middle? Do it the easy way: Create a bit mask for the left side and then a nother one for the right side. Then you can merge those.
#21 6.
7.1 1
What is the relationship between files and directories?
#21 7.
8.1
We can compute the number of steps to 1 00 by the number of steps to 99, 98, and 97. This corresponds to the child hopping 1 , 2, or 3 steps at the end. Do we add those or multiply them? That is: ls it f ( 100 ) = f ( 99 ) + f ( 98 ) + f ( 97 ) or f ( 100 ) = f ( 99 ) * f ( 98 ) * f ( 9 7 ) ?
#21 8.
6.6
This is a logic problem, not a clever word problem. Use logic/math /algorithms to solve it.
#21 9.
1 0. 1 1
Try walking through a sorted a rray. Can you just swap elements u ntil you have fixed the array?
#220.
1 1 .5
Have you considered both intended uses (writing, etc.) and unintended use? What a bout safety? You would not want a pen for children to be dangerous.
#22 1 .
6.1 0
Solution 2: Be very ca reful a bout edge cases. What ifthe third digit in the bottle number matches the first or second digit?
#222.
8.8
Try getting the count of each character. For example, ABCAAC has 3 As, 2 Cs, and 1 B.
#223.
9.6
Don't forget that a product can be listed under multiple categories.
#224.
8.6
You can easily move the smallest disk from one tower to another. It's a lso pretty easy to move the smallest two disks from one tower to another. Can you move the smallest three disks?
#225.
1 1 .6
In a real interview, you would also want to discuss what sorts of test tools we have avail a ble.
#226.
5.3
Flipping a 0 to a 1 can merge two sequences of 1 s-but only if the two sequences are separated by only one 0.
#227.
8.5
Th ink a bout how you m i g ht handle this for odd numbers.
#228.
7.8
What class should mainta in the score?
#229.
1 0.9
If you're considering a particular column, is there a way to q uickly elimi nate it (in some cases at least)?
#230.
6. 1 0
Solution 2: You can ru n an additional day of testing to check digit 3 in a different way. But again, be very ca reful a bout edge cases here.
#231 .
1 0.1 1
Note that if you ensure the peaks are in place, the valleys will be, too. Therefore, your iteration to fix the array can skip over every other element.
#232.
9.8
If you generate U RLs ra ndomly, do you need to worry a bout collisions (two documents with the same URL)? If so, how can you handle this?
#233.
6.8
As a fi rst approach, you might try something like binary sea rch. Drop it from the SOth floor, then the 75th, then the 88th, and so on. The problem is that if the first egg drops at the 50th floor, then you'll need to start d ropping the second egg sta rting from the 1 st floor and going up. This could take, at worst, 50 d rops (the 50th floor d rop, the 1 st floor d rop, the 2nd floor drop, and up through the 49th floor drop). Can you beat this?
666
Cracking the Codin g I nterview, 6th Edition
11
I H i nts for Concepts and Algorithms
#234.
8.5
If there's duplicated work across different recu rsive calls, can you cache it?
#235.
1 0.7
Would a bit vector help?
#236.
9.6
Where would it be appropriate to cache data or queue up tasks?
#237.
8.1
We mu ltiply the values when it's "we do this then this:' We add them when it's "we do this or this:'
#238.
7.6
Think about how you might record the position of a piece when you find it. Shou ld it be stored by row and location?
#239.
6.2
To ca lcu late the probability of winning the second game, start with calcu lating the proba bility of making the first hoop, the second hoop, and not the third hoop.
#240.
8.3
Can you solve the problem in O(log N)?
#241 .
6. 1 0
Sol ution 3: Thi n k a bout each test strip as being a binary indicator for poisoned vs. non poisoned.
#242.
5.4
#243.
8.9
Alternatively, we cou ld think about doing this by moving through the stri ng and adding left and right parens at each step. Wi l l this elimi nate d u plicates? How do we know if we can add a left or right paren?
#244.
9.6
Depending on what assum ptions you made, you might even be able to do without a database at all. What wou ld this mean? Wou ld it be a good idea?
#245.
7.7
Th is is a good problem to think a bout the major system com ponents or tech nologies that wou ld be usefu l.
#246.
8.5
lf you're doing 9*7 (both odd num bers), then you cou ld do 4*7 and 5*7.
#247.
9.7
Try to red uce unnecessa ry data base queries. If you don't need to perma nently store the data i n the data base, you might not need it in the database at a l l.
#248.
5.7
Can you create a number that represents just the even bits? Then can you shift the even bits over by one?
#249.
6.10
Solution 3: If each test stri p is a binary i ndicator, can we map , integer keys to a set of 1 0 binary indicators such that each key has a unique configuration (mapping)?
#250.
8.6
Think a bout moving the smal lest disk from tower X=0 to towerY=2 using tower Z=l as a temporary holding spot as having a solution for f ( l , X=0, Y=2 , Z= l ) . Movi ng the smal lest two disks is f ( 2 , X=0, Y=2 , Z=l ) . Given that you have a solution for f ( l , X=0 , Y= 2 , Z=l ) and f ( 2 , X=0 , Y= 2 , Z=l ) , can you solve f ( 3 , X=0, Y= 2 , Z=l ) ?
#251 .
1 0.9
Since each column is sorted, you know that the va lue can't be i n this column if it's smaller than the min value in this col u m n. What else does this tel l you?
#252.
6.1
What ha ppens if you put one pill from each bottle on the sca le? What if you put two pills from each bottle on the sca le?
#253.
1 0.1 1
Do you necessa rily need the arrays to be sorted ? Can you do it with an u nsorted array?
Get Next: If you flip a 1 to a 0 and a 0 to a 1 , it will get bigger if the 0-> 1 bit is more signifi cant than the 1 ->0 bit. How can you use this to create the next biggest number (with the same number of 1 s)?
CrackingTheCodingl nterview.com I 6th Edition
667
11
I Hi nts for Concepts and Algorithms
#254.
1 0.7
To do it with less memory, can you try multiple passes?
#255.
8.8
To
#256.
1 0.5
Try modifying binary search t o handle this.
#257.
1 1 .1
There a re two mistakes i n this code.
#258.
7.4
Does the parking lot have multiple levels? What "features" does it support? Is What types of vehicles?
#259.
9.5
You may need to make some assum ptions (in part because you don't have an inter viewer here). That's okay. Make those assu m ptions explicit.
#260.
8. 1 3
Think a bout the fi rst decision you have to make. The fi rst decision is which box will be at the bottom.
#261 .
5.5
If A & B == 0, then it means that A and B never have a 1 at the same spot. Apply this to the equation in the problem.
#262.
8.1
What is the runtime of this method? Thi n k carefully. Can you optimize it?
#263.
1 0.2
Can you leverage a standard sorting a lgorithm?
#264.
69
.
Note: If an integer x is divisible by a, and b = x I a, then x i s also divisible by b. Does this mean that a l l numbers have an even number of factors?
#265.
8.9
Adding a left or right paren at each step wi l l eliminate duplicates. Each su bstri ng will be unique at each step.Therefore, the total string will be u nique.
#266.
1 0.9
If the value x is smaller than the sta rt of the col u m n, then it a lso can't be i n a ny columns to the rig ht.
#267.
8.7
Approach 1 : You can create all permutations of abed by computing a l l permutations of a b c and then inserti ng d into each possible location within those.
#268.
1 1 .6
What a re the different features and uses we would want to test?
#269.
5.2
How would you get the fi rst digit in 89 3? If you m u ltiplied by 1 0, you'd shift the values over to get 8 . 9 3 . What happens if you m u ltiply by 2?
#270.
9.2
To find the connection between two nodes, would it be better to do a breadth-fi rst search or depth-first search? Why?
#27 1 .
7.7
How will you know if a user signs offiine?
#272.
8.6
Observe that it doesn't really matter which tower is the source, destination, or buffer. You can do f ( 3 , X=0, Y=2 , Z = l ) by first doing f ( 2 , X=0, Y = l , Z=2 ) (moving two disks from tower 0 to tower 1 , using tower 2 as a buffer), then moving disk 3 from tower 0 to tower 2, then doing f ( 2 , X=l , Y= 2 , Z=0) (moving two disks from tower 1 to tower 2, using tower 0 as a buffer). How does this process repeat?
#273.
8.4
How can you build all subsets of { a , b , c } from the subsets of { a , b } ?
#274.
9.5
Think a bout how you could design this for a single machi ne. Would you want a hash table? How would that work?
#275.
71
668
.
get all permutations with 3 As, 2 Cs, and 1 B, you need to fi rst pick a sta rti ng char acter: A, B, or C. If it's an A, then you need a l l permutations with 2 As, 2 Cs, and 1 B .
it
paid?
•
How, if at a l l, will you handle aces?
Cracking the Coding Interview, 6th Edition
11
I H i nts for Concepts and Algorithms
#276.
9.7
#277.
1 0. 1 1
Su ppose you had a sequence of three elements ( { 0, 1 , Z } , in any order. Write out all possible sequences for those elements and how you can fix them to make 1 the pea k.
#278.
8.7
Approach 2: If you had a l l permutations of two-character su bstrings, cou ld you generate all permutations of three-character su bstrings?
#279.
1 0.9
Think a bout the previous hint in the context of rows.
#280.
8.5
Alternatively, if you're doing 9 * 7, you could do 4"7, double that, and then add 7.
#28 1 .
1 0.7
Try using one pass to get it down to a range of values, and then a second pass to find a specific va lue.
#282.
6.6
Suppose there were exactly one blue-eyed person. What would that person see? When wou ld they leave?
#283.
7.6
Which will be the easiest pieces to match fi rst? Can you start with those? Which will be the next easiest, once you've nai led those down?
#284.
6.2
If two events are mutually exclusive (they can never occur simu lta neously), you ca n add their probabil ities together. Can you find a set of mutually exclusive events that repre sent making two out of three hoops?
#285.
9.2
A breadth-first search is probably better. A depth-first search can wind up going on a long path, even though the shortest path is actually very short. Is there a modification to a breadth-fi rst sea rch that might be even faster?
#286.
8.3
Binary sea rch has a runtime of 0( log N ) . Can you apply a form of binary search to the problem?
#287.
7.1 2
I n order to handle collisions, the hash table should be an array of linked lists.
#288.
1 0.9
What would happen if we tried to keep track of this using an array? What a re the pros and cons of this?
#289.
1 0.8
Can you use a bit vector?
#290.
8.4
Anyth ing that is a su bset of { a , b} is also a su bset of { a , b , c } . Which sets are subsets of { a , b , c } but not { a , b } ?
#291 .
1 0.9
Can we use the previous hi nts to move u p, down, left, a n d right around the rows and columns?
#292.
1 0. 1 1
Revisit the set of sequences for { 0 , 1 , 2 } that you just wrote out. I magine there are elements before the leftmost element. Are you sure that the way you swa p the elements won't invalidate the previous part of the array?
#293.
9.5
Can you com bine a hash table and a linked list to get the best of both worlds?
#294.
6.8
It's actually better for the fi rst d rop to be a bit lower. For example, you cou ld d rop at the 1 0th floor, then the 20th floor, then the 30th floor, and so on. The worst case here will be 1 9 d rops ( 1 0, 20, ..., 1 00, 91, 92, ..., 99). Can you beat that? Try not ra ndomly guessing at different solutions. Rather, think deeper. How is the worst case defined? How does the number of drops of each egg factor into that?
As much work as possible should be done asynchronously.
CrackingTheCodingl nterview.com I 6th Edition
669
11
I H i nts for Concepts and Algorithms
#295.
8.9
We can ensure that this string is valid by counting the number of left and right parens. It is always valid to add a left paren, up u ntil the total n u m ber of pai rs of parens. We can add a right paren as long as count ( left pa ren s ) b and 0 otherwise, then you cou ld return a * k + b * ( not k ) . But how d o you create k?
#51 4.
1 6. 1 0
Sol ution 2 : Do you actually need to match the birth yea rs and death years? Does it matter when a specific person d ied, or do you just need a list of the years of deaths?
#51 5.
1 7.5
Sta rt with a brute force solution.
#51 6.
1 7. 1 6
Recursive solution: You can optimize this approach through memoization. What i s the runtime of this approach?
#51 7.
1 6.3
How can we find the intersection between two li nes? If two line segments intercept, then this must be at the same point as their "infinite" extensions. Is this intersection point within both lines?
#51 8.
1 7.26
Solution 1 : What is the relationship between the intersection and the union? Can you compute one from the other?
#5 1 9.
1 7.20
Reca l l that the median means the number for which half the numbers are larger and half the numbers are smaller.
#520.
1 6. 1 4
You cant tru ly try a l l possible lines in the world-that's infinite. But you know that a "best'' line must intersect at least two points. Can you connect each pair of points? Can you check if each line is indeed the best line?
#521 .
1 6.26
Can we just process the expression from left to rig ht? Why might this fai l ?
#522.
1 7.1 0
Start with a brute force solution. Can you just check each value to see if it's the majority element?
5b
CrackingTheCod ingl nterview.com I 6th Edition
*
7c
look
681
IV
I H ints for Additional Review Problems
#523.
1 6. 1 0
Solution 2: O bserve that people a re "fu ngible:' lt doesn't matter who was born and when they died. All you need is a list of birth years and death yea rs. This m ight make the ques tion of how you sort the list of people easier.
#524.
1 6.25
Fi rst scope the problem. What a re the features you would want?
#525.
1 7.24
Can you do any sort of precomputation to make computing the sum of a su bmatrix 0( 1) ?
#526.
1 7. 1 6
Recursive solution: The runtime of your memoization a pproach should be O ( N ) , with O ( N ) space.
#527.
1 6.3
Think carefully a bout how to handle the case of line segments that have the same slope and y-intercept.
#528.
1 6. 1 3
To cut two squares in half, a line must go through the m iddle of both squares.
#529.
1 6. 1 4
You should be a ble to get to an O ( N2 ) solution.
#530.
1 7. 1 4
Consider thinking a bout reorganizing the data in some way or using additional data structures.
#53 1 .
1 6. 1 7
Picture the a rray as a lternating sequences of positive and negative numbers. O bserve that we would never include j ust part of a positive sequence or part of a negative sequence.
#532.
1 6. 1 0
Solution 2 : Try creating a sorted list of births and a sorted list of deaths. Ca n you iterate through both, tracking the number of people alive at any one time?
#533.
1 6.22
O ption #2:Think about how an Arraylist works. Ca n you use an Array L i s t for this?
#534.
1 7.26
Sol ution 1 : To u nderstand the relationship between the u nion and the intersection of two sets, consider a Venn diagram (a diagram where one circle overlaps a nother circle).
#535.
1 7 .22
Once you have a brute force solution, try to find a faster way of getting all va lid words that are one edit away. You don't wa nt to create all strings that are one edit away when the vast majority of them a re not va lid dictionary words.
#536.
1 6.2
Can you use a hash table to optimize the repeated case?
#537.
1 7 .7
An easier way of taking the above approach is to have each name map to a list of alter nate spelli ngs. What should happen when a name in one group is set equal to a name i n a nother group?
#538.
1 7. 1 1
You could build a lookup table that maps from a word to a list of the locations where each word appears. How then could you find the closest two locations?
#539.
1 7.24
What if you precomputed the sum of the submatrix sta rting at the top left corner and continuing to each cel l ? How long would it ta ke you to compute this? If you did this, could you then get the sum of a n a rbitrary submatrix i n 0 ( 1 ) time?
#540.
1 6.22
O ption #2: It's not i mpossible to use an Arrayli st, but it would be ted ious. Perhaps it would be easier to build your own, but specia lized for matrices.
#541 .
1 6. 1 0
Solution 3 : Each birth adds one person and each death removes a person. Try writing an exa m ple of a list of people (with birth and death yea rs) and then re-formatting this into a list of each year and a + 1 for a birth and a -1 for a death.
682
Cracking t h e Coding Interview, 6th Edition
IV
I
H i nts fo r Additi o n a l Review Problems
#542.
1 7. 1 6
#543.
1 7. 1 5
Extend the earlier idea to m u ltiple words. Can we just break each word up in a l l possible ways?
#544.
1 7.1
You can think a bout binary add ition as iterating through the num ber, bit by bit, adding two bits, and then ca rrying over the one if necessa ry. You could also think a bout it as grouping the operations. What if you first added each of the bits (without ca rryi ng any overflow)? After that, you can handle the overflow.
#545.
1 6.2 1
Do some math here or play a round with some examples. What does this pair need to look like? What can you say a bout their values?
#546.
1 7.20
Note that you have to store all the elements you've seen. Even the smal lest of the first 1 00 elements cou ld become the median. You ca n't just toss very low or very high elements.
#547.
1 7.26
Solution 2: It's tempting to try to think of m i nor optim izations-for example, keeping track of the min and max elements in each array. You cou ld then figure out quickly. in specific cases, if two a rrays don't overlap. The problem with that (and other optimiza tions along these l i nes) is that you sti ll need to compare all documents to all other docu ments. It doesn't leverage the fact that the similarity is spa rse. G iven that we have a lot of documents, we rea lly need to not compa re a l l documents to all other documents (even if that comparison is very fast). All such solutions will be O ( D2 ) , where D is the number of documents. We shouldn't compare all documents to all other documents.
#548.
1 6.24
Start with a brute force solution. What is the runtime? What is the best conceivable runtime for this problem?
#549.
1 6. 1 0
Sol ution 3 : What i f you created a n a rray of yea rs and how the population cha nged in each yea r? Cou l d you then find the yea r with the hig hest population?
#550.
1 7.9
In looking for the kth smal lest va lue of 3" * 5° * 7c, we know that a, b, and c will be less than or equal to k. Can you generate all such num bers?
#55 1 .
1 6. 1 7
Observe that if you have a sequence of values which have a negative sum, those will never start or end a sequence. (They could be present in a sequence if they connected two other sequences.)
#552.
1 7. 1 4
Can you sort the numbers?
#553.
1 6. 1 6
We can think a bout the a rray as d ivided into th ree suba rrays: L E FT, MIDD LE, RIGHT. L E F T and RIGHT a re both sorted. The MIDD L E elements a re in an arbitrary order. We need to expand MIDD L E until we could sort those elements and then have the entire a rray sorted.
#554.
1 7.1 6
Iterative solution: It's probably easiest to sta rt with the end of the a rray and work b a c k wards.
#555.
1 7.26
Solution 2: If we ca n't com pare a l l documents to a l l other documents, then we need to d ive down and sta rt looking at things at the element level. Consider a na ive solution and see if you can extend that to m u ltiple documents.
Iterative solution: Take the recu rsive solution a n d investigate it more. Can you imple ment a similar strategy iteratively?
CrackingTheCodinglnterview.com I 6th Edition
683
IV
I
H i nts for Additio n a l Review Problems
#556.
1 7.22
To qu ickly get the valid words that are one edit away, try to group the words in the d ictionary in a usefu l way. Observe that all words in the form b_l l (such as bill, b a l l, bell, and bull) will be one edit away. However, those aren't the only words that a re one edit away from bill.
#557.
1 6.21
When you move a value a from a rray A to array B, then A's sum decreases by a and B's sum increases by a. What happens when you swa p two values? What would be needed to swa p two va lues and get the same sum?
#558.
1 7.1 1
If you had a l ist of the occurrences of each word, then you are really looking for a pair of values within two arrays (one value for each a rray) with the smal lest difference. This could be a fairly similar algorithm to your initial algorithm.
#559.
1 6.22
Option #2: One approach is to just double the size of the a rray when the ant wa nders to an edge. How will you handle the ant wandering into negative coordinates, though? Arrays ca n't have negative ind ices.
#560.
1 6.1 3
Given a line (slope and y-i ntercept), can you find where it intersects another line?
#56 1 .
1 7 .26
Solution 2: One way to think about this is that we need to be able to very qu ickly pull a list of all documents with some similarity to a specific document. (Again, we should not do this by saying "look at all docu ments a nd q uickly eliminate the dissimilar docu ments:'That will be at least O ( D2 ) .)
#562.
1 7.1 6
Iterative solution: Observe that you wou ld never skip three a ppoi ntme nts i n a row. Why wou ld you? You would always be a ble to take the middle booking.
#563.
1 6. 1 4
Have you tried using a hash table?
#564.
1 6.21
If you swa p two va lues, a and b, then the sum of A becomes s umA - a + b and the sum of B becomes sumB - b + a. These sums need to be equal.
#565.
1 7 .24
If you can precom pute the sum from the top left corner to each cell, you can use this to com pute the sum of an a rbitra ry submatrix in 0 ( 1 ) time. Pictu re a particular submatrix. The full, precomputed sum will include this submatrix, an a rray i mmediately a bove it (C), and array to the left (B), and an a rea to the top and left (A). How can you compute the sum of just D?
I yl
y2
xl
x2
A
c
B
D
#566.
1 7.1 0
Consider the brute force solution. We pick an element and then val idate if it's the majority element by counting the number of matching and non-matching elements. Suppose, for the first element, the fi rst few checks reveal seven non-matching elements and three matching elements. Is it necessary to keep checking this element?
#567.
1 6.1 7
Sta rt from the beginn ing of the a rray. As that subsequence gets larger, it stays as the best subsequence. Once it becomes negative, though, it's useless.
684
Cracking the Coding I nterview, 6th Edition
IV
I Hints for Additional Review Problems +
1,
#568.
1 7. 1 6
Iterative solution: If you take appointment i, you will never take appointment i but you will always take appoi ntment i + 2 or i + 3.
#569.
1 7.26
Solution 2: Building off the earlier hint, we can ask what defines the list of docu ments with some similarity to a document like {1 3, 1 6, 2 1 , 3}. What attributes does that list have? How would we gather all documents like that?
#570.
1 6.22
#57 1 .
1 6.21
You are looking for va lues a and b where s umA - a + b = s umB - b + a. Do the math to work out what this means fo r a and b's values.
#572.
1 6.9
Approach these one by one, sta rting with su btraction. Once you've completed one fu nction, you ca n use it to implement the others.
#573.
1 7.6
Start with a brute force solution.
#574.
1 6.23
Sta rt with a brute force solution. How many times does it cal l rand 5 ( ) in the worst case?
#575.
1 7.20
Another way to think a bout this is: Can you maintain the bottom half of elements and the top half of elements?
#576.
1 6. 1 0
Solution 3: Be carefu l with the little deta ils in this problem. Does you r a lgorithm /code handle a person who dies in the same yea r that they are born? This person should be cou nted as one person in the population cou nt.
#577.
1 7.26
Solution 2: The list of docu ments similar to (1 3, 1 6, 2 1 , 3} includes a l l docu ments with a 1 3, 1 6, 2 1 , and 3. How can we efficiently find this list? Remember that we'll be doing this fo r many documents, so some precomputing ca n make sense.
#578.
1 7. 1 6
Iterative solution: Use a n example and work backwards. You ca n easily find the optimal solution for the subarrays { rJ, { rn_1, rJ. { rn_2, , rJ. How would you use those to qu ickly find the optimal solution for { r 0 _ 3 , , rJ ?
Option #2: Observe that nothing in the problem stipulates that the label fo r the coor dinates must remain the same. Can you move the ant and all cells into positive coord i nates? In other words, what would happen if, whenever you needed to grow the a rray in a negative d i rection, you relabeled all the ind ices such that they were still positive?
•
•
•
•
•
•
#579.
1 7.2
Suppose you had a method s huffl e that worked on decks up to n - 1 elements. Cou ld you use this method to implement a new s huffl e method that works on decks up to n elements?
#580.
1 7.22
Create a mapping from a wildcard form (like b_l l) to all words in that form. Then, when you wa nt to find all words that a re one ed it away from bill, you ca n look up _i l l, b_l l, bi_l, and b i l_ in the mapping.
#581 .
1 7.24
The sum of just D will be s u m (A&B&C&D ) - s u m ( A&B ) - s u m ( A&C ) + s u m ( A ) .
#582.
1 7.1 7
Can you use a trie?
#583.
1 6.21
If we do the math, we are looking for a pai r of values such that a - b ( s u mA sumB ) I 2. The problem then reduces to looking for a pai r of values with a particu lar difference.
#584.
1 7.26
Solution 2: Try building a hash ta ble from each word to the documents that conta in this word . This will allow us to eas i ly find all docu ments with some similarity to (1 3, 1 6, 2 1 , 3}.
#585.
1 6.5
=
How does a zero get into the result of n ! ? What does it mean? CrackingTheCodinglnterview.com I 6th Edition
685
IV
I H ints for Additional Review Problems
#586.
1 7.7
If each name maps to a list of its alternate spellings, you might have to update a lot of lists when you set X and Y as synonyms. If X is a synonym of {A, B, C }, and Y is a synonym of { D , E , F } then you would need to add {Y, D , E , F } to A's synonym list, B's synonym list, C's synonym list, and X's synonym list. Ditto for {Y, D , E , F }. Can we make this faster?
#587.
1 7.16
Iterative solution: If you take a n appointment, you ca n't ta ke the next a ppointment, but you can ta ke a nything after that. Therefore, opt ima l ( ri, . . . , r0 ) max ( r1 + op t im a l ( r i+i ' . . . , r J , optima l ( r i+1 ' . . . , r) ) . You can solve this itera tively by working backwa rds. =
#588.
1 6.8
Have you considered negative numbers? Does you r solution work for va lues like 1 00,030,000?
#589.
1 7.1 5
When you get recursive algorithms that are very inefficient, try looking for repeated subproblems.
#590.
1 7. 1 9
Pa rt 1 : I f you have to find the missing number i n 0 ( 1 ) space a n d 0 ( N ) time, then you can do a only constant number of passes through the array and can store only a few va ria bles.
#591 .
1 7.9
#592.
1 6.2 1
A brute force solution is to j ust look through all pairs of values to find one with the right difference. This will probably look like an outer loop through A with an inner loop through B. For each va lue, compute the difference and compa re it to what we're looking for. Can we be more specific here, though? Given a value in A and a target difference, do we know the exact value of the element within B we're looking for?
#593.
1 7. 1 4
What about using a heap o r tree o f some sort?
#594.
1 6.1 7
If we tracked the ru nning sum, we should reset it as soon as the su bsequence becomes negative. We would never add a negative sequence to the beginning or end of another subsequence.
#595.
1 7.24
With precomputation, you should be able to get a ru ntime of O ( N4 ) . Can you make this even faster?
#596.
1 7.3
Try this recursively. Suppose you had an algorithm to get a su bset of size m from n - 1 elements. Could you develop a n algorithm to get a su bset of size m from n elements?
#597.
1 6.24
Can we make this faster with a hash table?
#598.
1 7.22
You r previous algorithm proba bly resembles a depth-fi rst search. Ca n you make this faster?
#599.
1 6.22
Option #3: Another thing to think a bout is whether you even need a g rid to implement this. What information do you actually need in the problem?
#600.
1 6.9
Subtraction: Would a negate function (which converts a positive integer to negative) help? Can you implement this using the add operator?
#601 .
1 7.1
Focus on just one of the steps a bove. If you "forgot" to ca rry the ones, what would the add operation look like?
686
Look at the list of a l l va lues for 3• * 5b * 1 c . Observe that each va lue in the list will be 3*(some p revious va lue), S*(some p revious va lue), or ?*(some previous va lue).
Cracking the Cod ing Interview, 6th Edition
IV
\
Hints for Additional Review Problems
#602.
1 6.2 1
What the brute force really does is look for a va lue with in B which equals a - t a rget. How can you more quickly find this element? What approaches help us quickly find out if an element exists within an array?
#603.
1 7.26
Solution 2: Once you have a way of easily finding the documents similar to a particular document, you can go through and just compute the similarity to those docu ments using a simple algorithm. Can you make this faster? Specifically, ca n you compute the similarity directly from the hash table?
#604.
1 7.1 0
The majority element wil l not necessa rily look like the majority element at first. It is possible, for example, to have the majority element appear in the first element of the array and then not appear again for the next eight elements. However, in those cases, the majority element will appear later in the array (in fact, many times later on in the array). It's not necessa rily critical to conti nue checking a specific instance of an element for majority status once it's already looking "u nli kely:'
#605.
1 7.7
Instead, X, A, B, and C should map to the same instance of the set { X , A, B , C } . V, D, E, and F should map to the same instance of {Y, D , E , F } . When we set X and Y as synonyms, we can then just copy one of the sets into the other (e.g., add {V, D , E , F } to { X , A, B , C } ). How else do we change the hash table?
#606.
1 6.2 1
We can use a hash table here. We can also try sorting. Both help us locate elements more quickly.
#607.
1 7. 1 6
Iterative solution : I f you're ca refu l about what data you really need, you should b e a ble to solve this in 0 ( n ) time and 0 ( 1 ) additional space.
#608.
1 7. 1 2
Think a bout it this way: I f you had methods cal led c onve rt Left and convert Rig h t (which would convert left and right subtrees to doubly linked lists), cou ld you put those together to convert the whole tree to a doubly l i nked list?
#609.
1 7. 1 9
Pa rt 1 : What i f you added u p a l l the values in the array? Could you then figure out the missing number?
#61 0.
1 7.4
How long would it take you to figure out the least significant bit of the missing number?
#61 1 .
1 7.26
Solution 2: Imagine you are looking up the docu ments similar to { 1 , 4, 6} by using a hash table that maps from a word to documents. The same document ID appears mu ltiple times when doing this lookup. What does that indicate?
#61 2.
1 7.6
Rather than counting the number of twos in each num ber, think about digit by digit. That is, count the number of twos in the fi rst digit (for each number), then the number of twos in the second digit (for each num ber), then the number of twos in the third digit (for each number), and so on.
#61 3.
1 6.9
Multiply: it's easy enough to implement mult i ply using add. But how do you handle negative numbers?
#61 4.
1 6. 1 7
You can solve this in O ( N ) time and 0 ( 1 ) space.
#61 5.
1 7.24
Su ppose this was just a single array. How could we com pute the subarray with the largest sum? See 1 6.1 7 for a solution to this.
#61 6.
1 6.22
Option #3: All you actually need is some way of looking up if a cell is wh ite or black (and of course the position of the ant). Can you just keep a list of a l l the wh ite cells?
CrackingTheCodingl nterview.corn I 6th Edition
687
IV
I H i nts for Additional Review Problems
#61 7.
1 7.1 7
One solution is to insert every suffix of the larger string i nto the trie. For example, if the word is dogs, the suffixes would be dogs, ogs, gs, and s . How wou l d this help you solve the problem? What is the runtime here?
#61 8.
1 7.22
A breadth-first search will often be faster than a depth-fi rst search-not necessarily in the worst case, but in many cases. Why? Can you do something even faster than this?
#61 9.
1 7.5
What if you just started from the beginning, cou nting the number of As and the number of Bs you've seen so fa r? (Try making a table of the array and the number of As and Bs thus far.)
#620.
1 7. 1 0
Note a lso that the majority element must b e the majority element for some suba rray and that no subarray can have multiple majority elements.
#621 .
1 7.24
Suppose I just wanted you to find the maximum submatrix starting at row rl and ending at row r2, how could you most efficiently do this? (See the prior h int.) If I now wanted you find the maximum subarray from rl to ( r2+2 ) , could you do this effi ciently?
#622.
1 7.9
Since each number is 3, 5, or 7 times a previous value in the list, we could just check all possible values and pick the next one that hasn't been seen yet. This will result i n a lot of dupl icated work. How can we avoid this?
#623.
1 7. 1 3
Can you just try a l l possibilities? What might that look like?
#624.
1 6.26
Multiplication and division a re higher priority operations. In an expression like 3 *4 + 5 * 9 / 2 + 3, the multiplication and division pa rts need to be grou ped together.
#625.
1 7. 1 4
I f you picked a n arbitra ry element, how long would i t take you t o figure out the ran k of this element (the number of elements bigger or sma ller than it)?
#626.
1 7. 1 9
Part 2: We're now looking for two missing n u mbers, which we will call a and b . The approach from part 1 will tel l us the sum of a a nd b, but it won't actually tell us a and b. What other calculations could we do?
#627.
1 6.22
Option #3: You could consider keeping a hash set of a l l the wh ite cells. How will you be a ble to pri nt the whole g rid, though?
#628.
1 7. 1
The adding step a lone would convert 1 + 1 -> 0, 1 + 0 -> 1 , 0 + 1 -> 1, 0 + 0 -> 0. How do you do this without the + sign?
#629.
1 7.21
What role does the ta llest bar i n the histogram play?
#630.
1 6.25
What data structu re would be most useful for the looku ps? What data structure would be most useful to know and maintain the order of items?
#63 1 .
1 6. 1 8
Sta rt with a brute force a pproach. Can you try all possibilities for a and b?
#632.
1 6.6
What if you sorted the a rrays?
#633.
1 7. 1 1
Can you just iterate through both a rrays with two poi nters? You should be able to do it in O ( A +B ) time, where A and B are the sizes of the two a rrays.
#634.
1 7.2
You could build this a lgorithm recu rsively by swa pping the nth element for any of the elements before it. What would this look like iteratively?
#635.
1 6.2 1
What if the sum of A is 1 1 and the sum of B is 8? Can there be a pa i r with the right differ ence? Check that you r solution ha ndles this situation appropriately.
688
Cracking the Coding I nterview, 6th Edition
IV
I Hi nts for Additional Review Problems
#636.
1 7.26
Solution 3: There's an alternative solution. Consider ta king all of the words from all of the documents, throwing them i nto one giant list, and sorting this list. Assume you cou ld still know which document each word came from. How cou ld you track the similar pairs?
#637.
1 6.23
Make a ta ble indicating how each possible sequence of ca lls to rand s ( ) wou ld map to the result of rand7 ( ) . For example, if you were implementing rand 3 ( ) with ( rand2 ( ) + rand2 ( ) ) % 3, then the table wou ld look like the below. Ana lyze this tabl e. What can it tell you ? Result 2nd 1st 0 0 1 1
0 1 0 1
0 1 1 2
#638.
1 7.8
This problem asks us to find the longest sequence of pairs you ca n build such that both sides of the pair a re constantly increasing. What if you needed only one side of the pair to increase?
#639.
1 6.1 5
Try fi rst creating an array with the frequency that each item occurs.
#640.
1 7.2 1
Pictu re the tallest bar, and then the next tallest bar on the left and the next ta llest bar on the right. The water will fill the area between those. Ca n you calculate that area? What do you do about the rest?
#641 .
1 7.6
Is there a faster way of calcu lating how many twos are in a particular digit across a ra nge of numbers? Observe that roughly Yie th of a ny digit should be a 2-but only roughly. How do you make that more exact?
#642.
1 7.1
You can do the add step with a n XOR.
#643.
1 6. 1 8
Observe that one of the su bstri ngs, either a o r b , m u st sta rt at the beginning of the stri ng. That cuts down the n u m ber of possibilities.
#644.
1 6.24
What if the array were sorted?
#645.
1 7.1 8
Start with a brute force solution.
#646.
1 7. 1 2
Once you have a basic idea for a recu rsive a lgorithm, you might get stuck on this: some times you r recu rsive algorithm needs to retu rn the start of the linked list, and some times it needs to return the end. There are mu ltiple ways of solving this issue. Brainstorm some of them.
#647.
1 7. 1 4
I f you picked a n a rbitra ry element, you would, on average, wind u p with an element a round the 50th percentile mark (half the elements above it and half the elements below). What if you did this repeated ly?
#648.
1 6.9
#649.
1 7.1 9
Part 2: There a re a lot of different calcu lations we could try. For example, we could m u ltiply all the numbers, but that will only lead us to the product of a and b.
#650.
1 7.1 0
Try this: Given an element, start checking if this is the sta rt of a suba rray for which it's the majority element. Once ifs become "unlikely" (appears less than half the time), sta rt checking at the next element (the element after the subarray).
Divide: If you're trying to compute, where X = % , remember that a = bx. Ca n you find the closest value for x? Remember that this is integer division and x should be a n integer.
CrackingTheCodingl nterview.com
\ 6th Edition
689
IV
I H i nts fo r Additional Review Problems
#651 .
1 7.21
You can calculate the area between the tallest bar overall and the ta llest bar on the left by just iterating through the histogra m and subtracting out any bars in between. You can do the same thing with the rig ht side. How do you handle the remainder of the g raph?
#652.
1 7 .1 8
One brute force solution is to take each sta rti ng position and move forward u ntil you've found a su bsequence which contains all the target characters.
#653.
1 6.1 8
Don't forget to handle the possi bility that the fi rst character i n the pattern is b .
#654.
1 6.20
In the real world, we should know that some prefixes/substrings won't work. For example, consider the number 3383 5676368. Although 3383 does correspond to fftf, there a re no words that sta rt with fftf. ls there a way we can short-circuit i n cases l i ke this?
#655.
1 7.7
An alternative approach is to think of this as a graph. How would this work?
#656.
1 7. 1 3
You can think a bout the choices the recursive algorithm makes i n one of two ways: ( 1 ) At each cha racter, should I put a space here? (2) Where should I put the next space? You can solve both of these recursively.
#657.
1 7 .8
#658.
1 7.21
You can handle the remainder ofthe graph by just repeating this process: find the tal lest bar and the second tal lest bar, and subtract out the bars in between.
#659.
1 7.4
To find the least significant bit of the missing n u mber, note that you know how many 0s and ls to expect. For example, if you see three 0s and three ls in the least significant bit, then the missing n u m ber's least significant bit must be a 1. Thi n k a bout it: in any sequence of 0s and ls, you'd get a 0, then a 1, then a 0, then a 1, and so on.
#660.
1 7.9
Rather than checking all values in the list for the next value (by m ultiplying each by 3, 5, and 7), think a bout it this way: when you insert a value x into the list, you can "create"
If you needed only one side of the pair to increase, then you would just sort a l l the values on that side. You r longest sequence would in fact be all of the pairs (other than any d u plicates, since the longest sequence needs to strictly increase). What does this tell you about the original problem?
the values 3x, Sx, and 7 x to be used later. #661 .
1 7. 1 4
Think a bout the previous hint some more, particu larly i n the context of quicksort.
#662.
1 7.2 1
How can you make the process of finding the next ta llest bar on each side faster?
#663.
1 6. 1 8
Be carefu l with how you ana lyze the runtime. If you iterate through 0 ( n2) substri ngs and each one does an 0 ( n ) stri ng comparison, then the tota l runti me is O( n 3 ) .
#664.
1 7. 1
Now focus o n the carryi ng. I n what cases wil l values ca rry? How do you apply the carry to the n u m ber?
#665.
1 6.2 6
Consider thinking a bout it as, when you get to a m u ltiplication or division sign, jumping to a separate "process" to compute the result of this chunk.
#666.
1 7.8
If you sort the values based on heig ht, then this will tell you the ordering of the final pairs. The longest sequence must be in this relative order (but not necessarily containing all of the pairs). You now just need to find the longest increasing su bsequence on weight while keeping the items in the same relative order. This is essentially the same problem as having an array of i ntegers and trying to find the longest sequence you can build (without reordering those items).
690
Cracking the Coding Interview, 6th Edition
IV
I H i nts for Additional Review Problems
Consider the th ree subarrays: L E FT, MIDD L E, RIGHT. Focus on just this question: Can you sort middle such that the entire array becomes sorted ? How would you check this?
#667.
1 6. 1 6
#668.
1 6.23
#669.
1 7.1 8
#670.
1 6.6
Thi n k about how you wou ld merge two sorted a rrays.
#67 1 .
1 7.5
When the a bove tables have eq ual va lues for the number of As a n d Bs, the entire suba rray (sta rting from index 0) has a n eq ual number of As and Bs. How could you use this ta ble to find qualifying suba rrays that don't start at index O?
#672.
1 7. 1 9
Pa rt 2 : Adding the num bers together wi l l tell us the resu lt of a + b . Mu ltiplying the num bers together will tell us the result of a * b. How can we get the exact values for a and b?
#673.
1 6.24
If we sorted the a rray, we could do repeated binary sea rches for the complement of a n u mber. What if, instead, the a rray is given to us sorted ? Could we then solve the problem in O ( N ) time and 0 ( 1 ) space?
#674.
1 6. 1 9
I f you were given the row and column of a water cell, how can you find all connected spaces?
#675.
1 7.7
We ca n treat adding X, Y as synonyms as adding an edge between the X node and the Y node. How then do we figure out the groups of synonyms?
#676.
1 7.21
Can you do precomputation to compute the next ta llest bar on each side?
#677.
1 7. 1 3
Will the recu rsive algorithm hit the same subproblems repeatedly? Can you optim ize with a hash table?
#678.
1 7. 1 4
What if, when you picked a n element, you swapped elements around (as you d o i n quicksort) s o that the elements below i t would b e located before the elements above it? If you did this repeatedly, cou ld you find the smallest one million num bers?
#679.
1 6.6
Imagine you had the two a rrays sorted and you were walking through them. If the pointer in the first array poi nts to 3 and the pointer in the second a rray poi nts to 9, what effect wi ll moving the second poi nter have on the difference of the pair?
#680.
1 7.1 2
handle whether your recu rsive a lgorithm should retu rn the start or the end of the linked list, you could try to pass a parameter down that acts as a flag. This won't work very well, though. The problem is that when you call c o nvert ( c u rrent . left ) , you want to get the end of l eft's linked list. This way you can join the end of the linked list to c u rrent. But, if c u rrent is someone else's right su btree, c onver t ( c u rrent ) needs to pass back the start of the linked list (which is actually the sta rt of c u r rent . l eft's linked list). Really, you need both the start and end of the linked list.
#681 .
1 7. 1 8
Consider the previously explained brute force solution. A bottleneck i s repeatedly asking for the next insta nce of a particular cha racter. Is there a way you can optimize this? You should be able to do this in 0 ( 1 ) time.
Looking at this ta ble again, note that the number of rows will be S k, where k is the max number of calls to rand s ( ) . I n order to make each value between 0 and 6 have equal probability, � th of the rows must map to 0, � th to 1, and so on. Is this possible?
Another way of thi nking a bout the brute force is that we ta ke each starting index and insta nce of each element in the ta rget string. The maxim um of all these next instances ma rks the end of a su bsequence which contains all the ta rget cha racters. What is the runtime of this? How can we make it faster?
find the next
To
CrackingTheCodinglnterview.com I 6th Edition
691
IV
I H i nts for Additional Review Problems
#682.
1 7.8
Try a recursive approach that just eva luates a l l possibilities.
#683.
1 7.4
Once you've identified that the least significant bit is a 0 (or a 1 ) you can ru le out all the numbers without 0 as the least significant bit. How is this problem different from the earlier part?
#684.
1 7.23
Start with a brute force solution. Can you try the biggest possible square first?
#68 5 .
1 6.1 8
#686.
1 7 .9
When you add x to the list of the first k va lues, you can add 3x, Sx, and 7x to some new list. How do you make this as optimal as possible? Would it make sense to keep multiple queues of va lues? Do you always need to i nsert 3x, 5x, and 7x? Or, perhaps sometimes you need to insert only 7x? You want to avoid seeing the same number twice.
#687.
1 6.1 9
Try recursion to count the number of water cells.
#688.
1 6.8
Consider dividing up a number i nto sequences of three digits.
#689.
1 7. 1 9
Part 2 : We could do both. I f we know that a + b 8 7 a n d a * b = 962, then we can solve for a and b: a = 1 3 and b 74. But this will also result in having to multiply really large numbers. The product of all the nu mbers could be larger than 1 0157• Is there a simpler calculation you can make?
,
Suppose you decide o n a specific value for the "a" part of a pattern. How many possibili ties a re there for b?
=
=
#690.
1 6. 1 1
Consider building a diving board. What are the choices you make?
#691 .
1 7. 1 8
Can you precompute the next instance of a particu lar character from each index? Try using a m ulti-dimensional a rray.
#692.
1 7. 1
The carry w i l l happen when you a re doing 1 number?
#693.
1 7.21
As an alternative solution. think a bout it from the perspective of each bar. Each bar will have water on top of it. How much water will be on top of each bar?
#694.
1 6.25
Both a hash table and a doubly linked list wou ld be useful. Can you combine the two?
#695.
1 7.23
The biggest possible square is NxN. So if you try that square first and it works, then you know that you've found the best square. Otherwise, you can try the next smallest square.
#696.
1 7.1 9
Part 2: Almost any "eq uation" we can come u p with will work here (as long as it's not equivalent to a l i near sum). It's just a matter of keeping this sum small.
#697.
1 6.23
It is not possible to divide S k even ly by 7. Does this mean that you can't implement rand7 ( ) with r a n d S ( ) ?
#698.
1 6.26
You can also mainta in two stacks, one for the operators and one for the numbers. You push a number onto the stack every time you see it. What a bout the operators? When do you pop operators from the stack and apply them to the numbers?
#699.
1 7.8
Another way to think a bout the problem is this: if you had the longest sequence ending at each element A [ 0 ] through A [ n - 1 ] , cou ld you use that to Ii nd the longest sequence ending at element A [ n - 1 ] ?
#700.
1 6.1 1
Consider a recursive solution.
692
Cracking the Coding Interview, 6th Edition
+
1 . How do you a pply the carry t o the
IV I
H i nts
for Add itional Rev iew Problems
#70 1 .
1 7. 1 2
Many people get stuck at this point and a ren't sure what to do. Sometimes they need the sta rt of the linked list, and sometimes they need the end. A given node doesn't necessa rily know what to return on its c onvert call. Sometimes the simple solution is easiest: always return both. What are some ways you could do this?
#702.
1 7. 1 9
Pa rt 2: Try a sum of squares of the values.
#703.
1 6.20
A trie might help us short-circuit. What if you stored the whole list of words in the trie?
#704.
1 7.7
Each connected subgraph represents a group of sy nonyms. To fi nd each group, we can do repeated breadth-fi rst (or depth-first) sea rches.
#705.
1 7.23
Describe the ru ntime of the brute force solution.
#706.
1 6. 1 9
How can you make sure that you're not revisiting the same cel ls? Think about how breadth-first search or depth-first search on a graph works.
#707.
1 6.7
When a > b, then a - b > 0. Ca n you get the sign bit of a - b?
#708.
1 6. 1 6
In order to be able to sort MIDD L E and have the whole array become sorted, you need MAX ( L E F T ) i ) = 3 a nd c ou n t ( B , 0 - > i ) 7. This means that there are four more B s than As. If you find a later spot j with the same difference (cou n t ( B , 0 - > j ) - c ount ( A , 0 - > j ) ), then this indicates a su ba rray with an equal number of As a nd Bs.
#71 4.
1 7.23
Can you do preprocessing to optimize this solution?
#7 1 5.
1 6. 1 1
Once you have a recu rsive algorithm, think a bout the ru ntime. Can you make this faster? How?
#71 6.
1 6. 1
#7 1 7.
1 7. 1 9
Pa rt 2 : You might need the quadratic formu la. It's not a big deal if you don't remember it. Most people won't. Remember that there is such a thing as good enough.
#71 8.
1 6. 1 8
Si nce the value of a determines the value of b (and vice versa) and either a or b must start at the beginning of the va lue, you should have only 0 ( n ) possi bilities for how to split u p the pattern.
#7 1 9.
1 7.1 2
You could return both the start and end of a linked list i n multiple ways. You could return a two-element a rray. You could define a new data structu re to hold the start and end. You could re-use the B iNode data structu re. If you're working in a lang uage that supports this (like Python), you cou ld just return multiple values. You cou ld solve the problem as a circular linked list, with the sta rt's previous pointer pointing to the end (and then break the circu lar list in a wra pper method). Explore these solutions. Which one do you like most and why?
Let d i f f be the difference betwee n a and b. Can you use d i f f in some way? Then can you get rid of this tempora ry variable?
Cracki ngTheCodinglnterview.com I 6th Edition
693
IV
I
H i nts for Add itional Review Problems
#720.
1 6.23
You can im plement rand7 ( ) with rand 5 ( ) , you just can't do it determi nistically (such that you know it will defin itely terminate after a certai n number of calls). Given this, write a solution that works.
#721 .
1 7.23
You should be able to do this in O ( N 3 ) time, where N is the length of one dimension of the square.
#722.
1 6.1 1
Consider memoization to optimize the runtime. Think ca refully a bout what exactly you cache. What is the runtime? The runtime is closely related to the max size of the table.
#723.
1 6. 1 9
You should have an algorithm that's 0 ( N2) on a n NxN matrix. If your algorithm isn't, consider if you've miscomputed the runtime or if your algorithm is suboptimal.
#724.
1 7.1
You might need to do the add/ca rry operation more than once. Adding c a r ry to sum might cause new values t o carry.
#725.
1 7. 1 8
Once you have the precomputation solution figured out. think a bout how you can reduce the space complexity. You should be able to get it down to O ( S B ) time and 0 ( B) space (where B is the size of the larger array and S is the size of the smaller array).
#726.
1 6.2 0
We're probably going to run this algorithm many times. If we did more preprocessing, is there a way we could optimize this?
#727.
1 6.1 8
You should be able to have an 0( n 2 ) algorithm.
#728.
1 6.7
Have you considered how to handle integer overflow in a - b?
#729.
1 6.5
Each factor of 1 0 in n ! means n ! is d ivisible by 5 and 2.
#730.
1 6. 1 5
For ease and clarity in im plementation, you might want to use other methods and classes.
#73 1 .
1 7.1 8
Another way to think a bout it is this: I magine you had a list of the indices where each item appeared. Could you find the first possible subsequence with all the elements? Could you find the second?
#732.
1 6.4
If you were designing this for an NxN board, how might your solution change?
#733.
1 6.5
Can you count the number of factors of 5 and 2? Do you need to count both?
#734.
1 7.21
Each bar will have water on top of it that matches the minimum of the tallest bar on the left and the tal lest bar on the right. That is, wat e r_on_top [ i ] mi n ( t a l l e st_ ba r ( 0 - > i ) , t a l l e s t_ba r ( i , n ) ) . =
#735.
1 6. 1 6
Can you expand the middle until the earlier condition i s met?
#736.
1 7.23
When you're checking to see if a particular square is valid (all black borders), you check how many black pixels are a bove (or below) a coordi nate and to the left (or right) of this coordinate. Can you precom pute the number of black pixels above and to the left of a given cell?
#737.
1 6.1
You could a lso try using XOR.
#738.
1 7.22
What if you did a breadth-first search sta rting from both the source word and the desti nation word?
#739.
1 7. 1 3
I n real life, we would know that some paths will not lead to a word. For example, there a re no words that start with he l loth i s i sm. Can we terminate early when going down a path that we know won't work?
694
Cracking the Coding I nterview, 6th Edition
IV
I Hints fo r Additional Review Problems
#740.
1 6. 1 1
There's a n alternate, clever (and very fast) solution. You can actually do this in l i nea r time without recursion. How?
#74 1 .
1 7. 1 8
Consider using a heap.
#742.
1 7.2 1
You should be able to solve this in O ( N ) time and O ( N ) space.
#743
1 7. 1 7
Alternatively, you could insert each of the smaller strings into the trie. How would this
.
help you solve the problem? What is the runtime? #744.
1 6.20
With preprocessi ng, we can actually get the lookup time down to 0 ( 1 ) .
#745.
1 6.5
Have you considered that 2 5 actually accounts fo r two factors of 5?
#746.
1 6. 1 6
You should be able to solve this in O ( N ) time.
#747.
1 6. 1 1
Th ink about it this way. You a re picking K planks and there a re two d ifferent types. All choices with 1 0 of the first type and 4 of the second type will have the same sum. Can you j ust iterate through all possi ble choices?
#748.
1 7.25
Can you use a trie to term i nate early when a rectangle looks invalid?
#749.
1 7. 1 3
For early termination, try a trie.
CrackingTheCodingl nterview.com I 6th Edition
695
X IV About the Author
Gayle Laakmann McDowell has a strong background in soft ware development with extensive experience on both sides of the hiring table.
She has worked for Microsoft, Apple, and Google as a software engineer. She spent three yea rs at Google, where she was one of the top interviewers and served on the hiring comm ittee. She interviewed hundreds of ca ndidates in the U.S. and abroad, assessed thousands of candidate interview packets for the hiring committee, and reviewed many more resumes. As a ca ndidate, she interviewed with-and received offers from twelve tech companies, including Microsoft, Google, Amazon, I BM, and Apple. Gayle founded Ca reerCu p to enable candidates to perform at their best d u ring these challenging interviews. Ca reerCu p.com offers a data base of thousands of interview questions from major companies and a forum for interview ad vice. In addition to Cracking the Coding other two books:
Interview,
Gayle has written
Cracking the Tech Career: Insider Advice on Landing a Job at Google, Microsoft, Aople. or Any Top Tech
provides a broader look at the interview process for major tech companies. It offers insight into how anyone, from college freshmen to marketing professionals, can position themselves for a career at one of these companies. Company
focuses on product ma nagement roles at startu ps and big tech companies. It offers strategies to brea k into these roles and teaches job seekers how to prepare for PM i nterviews. Cracking the PM Interview: How to Land a Product Manager Job in Technology
Through her role with CareerCup, she consults with tech companies on their h i ring process, leads technical interview training workshops, and coaches engineers at sta rtups for acquisition interviews. She holds bachelor's deg ree and master's degrees in computer science from the U nive rsity of Pennsy l va ni a and an MBA from the Wharton School. She lives in Palo Alto, California, with her husband, two sons, dog, and com puter science books. She still codes daily.
696
Cracking t h e Cod ing I nterview, 6th Edition
Amazon.corn's #1 Best-Selling Interview Book I I am not a recruiter. I am a software engineer. And as such. I know what it's like to be asked to whip up brilliant algorithms on the spot and then write flawless code on a whiteboard. I've been through this-as a candidate and as an interviewer. Cracking the Coding Interview. 6th Edition is here to help you through this process, teaching you what you need to know and enabling you to perform at your very best. I've coached and interviewed hundreds of software engineers. The result is this book. Learn how to uncover the hints and hidden details in a question, discover how to break down a problem into manageable chunks. develop techniques to unstick yourself when stuck. learn (or re-learn) core computer science concepts. and practice on 189 interview questions and solutions. These interview questions are real: they are not pulled out of computer science textbooks. They reflect what's truly being asked at the top companies. so that you can be as prepared as possible.
• 189 programming interview questions, ranging from the basics to the trickiest algorithm problems. • A walk-through of how to derive each solution, so that you can learn how to get there yourself. • Hints on how to solve each of the 189 questions. just like what you would get in a real interview. • Five proven strategies to tackle algorithm questions, so that you can solve questions you haven't seen. • Extensive coverage of essential topics. such as big 0 time. data structures. and core algorithms. • A "behind the scenes· look at how top companies. like Google and Facebook. hire developers. t
• Techniques to prepare for and ace the ·sof " side of the interview: behavioral questions. • For interviewers and companies: details on what makes a good interview question and hiring process.
Gayle Laakmann McDowell is the founder and CEO of CareerCup and the author of Cracking the PM Interview and Cracking the Tech Career. Gayle has a strong background in software development. having worked as a software engineer at Google. Microsoft. and Apple. At Google. she interviewed hundreds of software engineers and evaluated thousands of hiring packets as part of the hiring committee. She holds a B.S.E. and M.S.E. in computer science from the University of Pennsylvania and an MBA from the Wharton School. She now consults with tech companies to improve their hiring process and with startups to prepare them fo r acquisition interviews. ISBN 9780984782857
90000 >