Elements of Programming Interviews The Insiders' Guide Adnan Aziz Tsung- Hsien Lee Amit Prakash This document is a sampling of our book, Elements of. Book Elements of Programming Interviews: The Insiders' Guide By Adnan Aziz, Tsung- Hsien Lee, Amit Prakash The Python version of EPI is available on. The Insiders' Guide. Adnan Aziz. Tsung-Hsien Lee. Amit Prakash. This document is a sampling of our book, Elements of. Programming Interviews (EPI).

Author: | SHERYL TOPOLSKI |

Language: | English, Spanish, Indonesian |

Country: | Hungary |

Genre: | Environment |

Pages: | 468 |

Published (Last): | 27.12.2015 |

ISBN: | 771-1-72640-177-9 |

ePub File Size: | 21.66 MB |

PDF File Size: | 13.29 MB |

Distribution: | Free* [*Register to download] |

Downloads: | 48744 |

Uploaded by: | GLENN |

Contribute to krishabhishek/test development by creating an account on GitHub. You Can Download and Read With File Format Pdf ePUB MOBI and Kindle Version. More Info==> Elements of Programming Interviews: The Insiders' Guide. This document is a sampling of our book, Elements of Program- ming Interviews in Elements of Programming Interviews: The Insiders' Guide.

Adnan Aziz is a Research Scientist at Facebook. Previously, he was a professor at the Department of Electrical and Computer Engineering at The University of Texas at Austin, where he conducted research and taught classes in applied algorithms. He received his Ph. When not designing algorithms, he plays with his children, Laila, Imran, and Omar. He received both his M. He has a passion for designing and implementing algorithms. He likes to apply algorithms to every aspect of his life.

Embeds 0 No embeds. No notes for slide.

Elements of Programming Interviews: The Insiders' Guide to download this book the link is on the last page 2. Description Elements of Programming Interviews Have you ever Wanted to work at an exciting futuristic company? Struggled with an interview problem that could have been solved in 15 minutes? Wished you could study real-world computing problems?

The core of EPI is a collection of problems with detailed solutions, including over figures and tested programs.

The prob Full description 3. Book Details Author: Paperback Brand: Book Appearances 5. The Insiders' Guide, click button download in the last page 6. If you do have a few months to spare, grab this, your time won't be wasted. Dec 26, Endilie Yacop Sucipto rated it it was amazing. This is actually a good book and I found it to have a much better content than another popular book "Cracking the Coding Interview" by Gayle.

Oct 07, Vishwanath Gulabal rated it it was amazing. Must read for interview preparation. Dec 16, Sefa rated it it was amazing Shelves: As good as "Cracking the coding interview", if not better. Twice as many problems and solutions than CCI. However, unlike CCI, it doesn't have enough review before each topic. You need to check out another source there are plenty for that. I would love to see a similar book for Python.

Dec 20, Piyush Maheshwari rated it it was amazing. This book contains a really good collection of interview questions. It's pretty comprehensive in its coverage. However the explanations were a bit terse at places, they could use more pictorial explanations. Nov 17, Ankush Arora marked it as to-read. Still reading. Sandy rated it really liked it Nov 10, Timon rated it really liked it Jun 21, Lauren rated it it was amazing Sep 27, Vasudha S rated it did not like it Sep 17, Michal Durkovic rated it really liked it Dec 08, Vignesh V rated it it was amazing Jun 05, Alan Gonzalez rated it it was amazing Oct 26, Shivani Sinha rated it really liked it Dec 01, Jae Jang rated it it was amazing Apr 01, Anmol rated it it was amazing Sep 03, Shaun rated it it was amazing Apr 09, Yi Zhang rated it it was amazing Aug 06, Ema Jones rated it really liked it Mar 21, Jack Richins rated it liked it Feb 09, Pooshan rated it it was amazing Nov 24, Dave rated it liked it Jul 26, Stephen Rosenthal rated it it was amazing Sep 29, There are no discussion topics on this book yet.

Readers Also Enjoyed. Goodreads is hiring! If you like books and love to build cool products, we may be looking for you. About Adnan Aziz. Adnan Aziz. Books by Adnan Aziz. Trivia About Elements of Progr No trivia or quizzes yet.

Welcome back. The justification for this is as follows. When selecting a problem keep the following in mind: There should be at least two or three opportunities for the candidates to redeem themselves. Problem 6. This book gives interviewers access to a fairly large collection of problems to choose from. Most big organizations have a structured interview process where designated interviewers are responsible for probing specific areas.

Outside of a stress interview. At a high level. The exception to this rule is if you want to test the candidate's response to stress. You should standardize scoring based on which hints were given. Often the best way of doing this is to construct a test case where the candidate's solution breaks down. An overconfident candidate: It is common to meet candidates who weaken their case by defending an incorrect answer.

Many things can happen in an interview that could help you reach a decision. To give the candidate a fair chance. In such situations. It is a good idea to check the appropriateness of a problem by asking one of your colleagues to solve it and — seeing how much difficulty they have with it. Whiteboard snapshots and samples of any code that the candidate wrote should also be recorded. Here are situations that may throw you off: A candidate that gets stuck and shuts up: Some candidates get intimidated by the problem.

At the same time. For example you could assert that a particular path will not make progress. Although isolated minor mistakes can be ignored. Coming up with the right set of hints may require some thinking. Scoring and reporting At the end of an interview. A verbose candidate: Candidates who go off on tangents and keep on talking without making progress render an interview ineffective.

It is important to put the candidate at ease. No unnecessary domain knowledge it is not a good idea to quiz a candidate on advanced graph algorithms if the job does not require it and the candidate does not claim any special knowledge of the field.

You do not want to give away the problem. Conducting the interview Conducting a good interview is akin to juggling.

When the right choice is not clear. The litmus test is to see if you would react positively to the candidate replacing a valuable member of your team. Fibonacci heaps. Other data structures. The data structures described in this chapter are the ones commonly used. Just as a musician. See Solution Don't forget that the basic types differ among programming languages. Java has no unsigned integers. Solutions often require a combination of data structures.

Bear in mind developing problem solving skills is like learning to play a musical — instrument books and teachers can point you in the right direction.

Different data structures are suited to different applications. It is precisely for this reason that EPI focuses on problems. Strings Know how strings are represented in memory.

Heaps Key benefit: Lists Understand trade-offs with respect to arrays. Binary search trees Key benefit: O logn insertions. Know hash functions for integers. Table 4. Hash tables Key benefit: The variable y is 1 at exactly the lowest bit of x that is 1. Understand node fields. Know array and linked list implementations. Be familiar with notion of balance. Another approach. Know about depth. Data structures. Arrays Fast access for element at an index. O log n insertion.

Understand implementation using array of buckets and collision chains. Key disadvantages: The overall complexity is 0 n where n is the length of the integer. Min-heap variant. Stacks and queues Recognize where last-in first-out stack and first-in first-out queue semantics are applicable. One straightforward approach is to iteratively test individual bits using the value 1 as a bitmask. A common problem related to basic types is computing the number of bits set to 1 in an integer-valued variable x.

Data structure Key points Primitive types Know how int. Binary trees Use for representing hierarchical data. In this case. The following problem arises when optimizing quicksort: Subtracting one from x underflows. Consider sharpening your bit manipulation skills by writing expressions that use bitwise operators. If x is 64 bits.

Now suppose x is 0. It is easy to introduce errors in code that manipulates bit-level data. Subtracting one from x changes the rightmost bit to zero and sets all the lower bits to one if you add one now. This calculation is robust it is — correct for unsigned and two's-complement representations.

Reading past the last element of an array is a common error The time complexity is 0 s.

Problems involving manipulation of bit-level data are often asked in interviews. Details are given in Solution 6.

The bit words are computed using bitmasks and shifting. The effect is to mask out the rightmost one. In practice. This fact can also be very useful. Computing the parity of an integer is closely related to counting the number of bits set to 1. The key to the solution is to maintain two regions on opposite sides of the array that meet the requirements.

Array lookup and insertion are fast. Details are given in Solution 7. Let L be a singly linked list. We treat strings separately from arrays because certain operations which are — — commonly applied to strings for example. Assume its nodes are numbered starting at 0. The look-and-say problem entails computing the nth integer in this sequence.

Although the problem is cast in terms of integers. In the context of this book we view a list as a sequence of nodes where each node has a link to the next node in the sequence.

Our solution to the look-and-say problem illustrates operations on strings. Define the zip of L to be the list consisting of the interleaving of the nodes numbered 0. The look-and-say sequence begins with 1. Lists are usually building blocks of more complex data structures. Zipping a list. Note that nodes are reused— no memory has been allocated. Figure 4. A list is similar to an array in that it contains objects in a linear order..

In a doubly linked list each node also has a link to the prior node. The operation of this program is illustrated in Figure 4. Suppose you were asked to write a program that computes the zip of a list.. The key differences are that inserting and deleting elements in a list has time complexity 0 1. The number in hex below each node represents its address in memory. Details are given in Solution 9. Details are given in Solution This problem can be solved by a multistage merge process.

Binary trees most commonly occur in the context of binary search trees. Both are commonly implemented using linked lists or arrays. Suppose you were asked to design an algorithm that combines the set of files into a single file R in which trades are sorted by timestamp. Entries are trade-file pairs and are ordered by the timestamp of the trade.

Binary trees are the subject of Chapter Let's say you are given a set of files. Similar to lists. Each trade appears as a separate line containing information about that trade.

Refer to Solution Naively implemented. Your task is to design and implement an application programming interface API for locking. A reasonable API is one with a method for checking if a node is locked. A heap resembles a queue. A node may be locked if and only if none of its descendants and ancestors are locked. Lines begin with an integer-valued timestamp. Processes need to lock resource nodes.

A hash table is a data structure used to store keys, optionally, with corresponding values. Inserts, deletes and lookups run in 0 1 time on average. One caveat is that — these operations require a good hash function a mapping from the set of all possible keys to the integers which is similar to a uniform random assignment.

Another caveat is that if the number of keys that is to be stored is not known in advance then the hash table needs to be periodically resized, which, depending on how the resizing is implemented, can lead to some updates having 0 n complexity.

Suppose you were asked to write a program which takes a string s as input, and returns true if the characters in s can be permuted to form a string that is palindromic, i. Working through examples, you should see that a string is palindromic if and only if each character appears an even number of times, with possibly a single exception, since this allows for pairing characters in the first and second halves.

A hash table makes performing this test trivial. We build a hash table H whose keys are characters, and corresponding values are the number of occurrences for that character. The hash table H is created with a single pass over the string.

After computing the number of occurrences, we iterate over the key-value pairs in H. If more than one character has an odd count, we return false; otherwise, we return true. Suppose you were asked to write an application that compares n programs for plagiarism.

Specifically, your application is to break every program into overlapping character strings, each of length , and report on the number of strings that appear in each pair of programs.

A hash table can be used to perform this check very efficiently if the right hash function is used. Binary search trees BSTs are used to store objects that are comparable. BSTs are the subject of Chapter The underlying idea is to organize the objects in a binary tree in which the nodes satisfy the BST property on Page AVL trees and red-black trees are BST implementations that support this form of insertion and deletion.

BSTs are a workhorse of data structures and can be used to solve almost every data structures problem reasonably efficiently.

It is common to augment the BST to make it possible to manipulate more complicated data, e. As an example application of BSTs, consider the following problem.

You are given a set of line segments. Each segment is a closed interval [l,, r,] of the X-axis, a color, and a height. For simplicity assume no two segments whose intervals overlap have the same height. When the X-axis is viewed from above the color at point x on the X-axis is the color of the highest segment that includes x.

If no segment contains x,. You are to implement a function that computes the sequence of colors as seen from the top. The key idea is to sort the endpoints of the line segments and do a scan from left-to-right. As we do the scan, we maintain a list of line segments that intersect the current position as well as the highest line and its color.

To quickly lookup the highest line in a set of intersecting lines we keep the current set in a BST, with the interval's height as its key. We describe algorithm design patterns that we have found to be helpful in solving — interview problems. Keep in mind that you may have to use a combination of approaches to solve a problem. Analysis principle Key points Concrete examples Manually solve concrete instances of the problem and then build a general solution.

Find such a solution and improve upon it. Reduction Use a well-known solution to some other problem as a subroutine. Graph modeling Describe the problem using a graph and solve it using an existing algorithm. Specifically, the following types of inputs can offer tremendous insight: Problems 6. Here is an example that shows the power of small concrete example analysis.

Given a set of coins, there are some amounts of change that you may not be able to. Technique Key points Sorting Uncover some structure by sorting the input.

Recursion If the structure of the input is defined in a recursive manner, design a recursive algorithm that follows the input definition. Cache for performance. For example, if your coins are 1,1, 1,1, 1, 5, 10, 25, then the smallest value of change which cannot be made is Suppose you were asked to write a program which takes an array of positive integers and returns the smallest number which is not to the sum of a subset of elements of the array.

A brute-force approach would be to enumerate all possible values, starting from 1, testing each value to see if it is the sum of array elements. However, there is no simple efficient algorithm for checking if a given number is the sum of a subset of entries in the array. Heuristics may be employed, but the program will be unwieldy. We can get a great deal of insight from some small concrete examples.

A trivial observation is that the smallest element in the array sets a lower bound on the change amount that can be constructed from that array, so if the array does not contain a 1, it cannot produce 1. However, it may be possible to produce 2 without a 2 being present, since there can be 2 or more Is present. Continuing with a larger example, 1,2,4 produces 1,2, 3, 4, 5, 6, 7, and 1,2,5 produces 1, 2, 3, 5, 6, 7, 8. Now consider the effect of adding a new element u to the collection. Another observation is that the order of the elements within the array makes no difference to the amounts that are constructible.

However, by sorting the array allows us to stop when we reach a value that is too large to help, since all subsequent values.

Specifically, let M[i-1] be the maximum constructible amount from the first i elements of the sorted array. To illustrate, suppose we are given 12, 2,1,15, 2, 4. This sorts to 1, 2, 2, 4,12, The maximum constructible amount we can make with the first element is 1. The third element, 2, allows us to produce all values up to and including 5.

The fourth element, 4, allows us to produce all values up to 9. We stop 10 is the smallest number that cannot be constructed. The code implementing this approach is shown below. The time complexity as a function of n, the length of the array, is 0 n log n to sort and 0 n to iterate, i.

The smallest nonconstructible change problem on the current page, the Towers of Hanoi Cases do not have to be mutually exclusive; however, they must be exhaustive, that is cover all possibilities. These cases are individually easy to prove, and are exhaustive. Case analysis is commonly used in mathematics and games of strategy.

Here we consider an application of case analysis to algorithm design. Your task is to identify the largest, second-largest, and third-largest integers in S using SORT 5 to compare and sort subsets of S; furthermore, you must minimize the number of calls to S0RT5.

If all we had to compute was the largest integer in the set, the optimum approach would be to form five disjoint subsets Si, This takes six calls to S0RT5 but leaves ambiguity about the second and third largest integers. It may seem like many additional calls to SORT 5 are still needed. The solutions to Problems Other terms are exhaustive search and generate-and-test.

Often this algorithm can be refined to one that is faster. At the very least it may offer hints into the nature of the problem. One straightforward solution is to sort A and interleave the bottom and top halves of the sorted array. Alternatively, we could sort A and then swap the elements at the pairs A[l],A[2j , A[3],A[4j , Both these approaches have the same time complexity as sorting, namely 0 n log n.

You will soon realize that it is not necessary to sort A to achieve the desired — configuration you could simply rearrange the elements around the median, and then perform the interleaving. Median finding can be performed in time almost certain 0 n , as per Solution Finally, you may notice that the desired ordering is very local, and realize that it is not necessary to find the median. In code: This approach has time complexity 0 n , which is the same as the approach based on median finding.

However, it is much easier to implement and operates in an online fashion, i. As another example of iterative refinement, consider the problem of string search: Since s can occur at any offset in t, the brute-force solution is to test for a match at every offset. This algorithm is perfectly correct; its time complexity is 0 nm , where n and m are the lengths of s and f. See Solution 7. After trying some examples you may see that there are several ways to improve the time complexity of the brute-force algorithm.

As an example, if the character f[i] is not present in s you can skip past f[i]. Furthermore, this skipping works better if we match the search string from its end and work backwards. These refinements will make the algorithm very fast linear time on random text and search strings; however, the worst-case complexity remains 0 nm.

You can make the additional observation that a partial match of s that does not result in a full match implies other offsets that cannot lead to full matches. As another example, the brute-force solution to computing the maximum subarray sum for an integer array of length n is to compute the sum of all subarrays, which has 0 n3 time complexity.

This can be improved to 0 n2 by precomputing the sums of all the prefixes of the given arrays; this allows the sum of a subarray to be computed in 0 1 time. The natural divide-and -conquer algorithm has an 0 n log n time complexity. Finally, one can observe that a maximum subarray must end at one of n indices, and the maximum subarray sum for a subarray ending at index i can be computed from previous maximum subarray sums, which leads to an 0 n algorithm.

Details are presented on Page Many more sophisticated algorithms can be developed in this fashion. A natural approach may be to rotate the first string by every possible offset and then compare it with the second string.

This algorithm would have quadratic time complexity. You may notice that this problem is quite similar to string search, which can be done in linear time, albeit using a somewhat complex algorithm. Therefore, it is natural to try to reduce this problem to string search.

Indeed, if we concatenate the second string with itself and search for the first string in the resulting string, we will find a match if and only if the two original strings are rotations of each other. This reduction yields a linear time algorithm for our problem. The reduction principle is also illustrated in the solutions to converting between different base representations Problem 7.

Usually, you try to reduce the given problem to an easier problem. This shows that the given problem is difficult, which justifies heuristics and approximate solutions. Drawing pictures is a great way to brainstorm for a potential solution. For example, suppose you are given a set of exchange rates among currencies and you want to determine if an arbitrage exists, i.

An arbitrage is possible for this — — set of exchange rates: USD 1 0. We can model the problem with a graph where currencies correspond to vertices, exchanges correspond to edges, and the edge weight is set to the logarithm of the exchange rate. If we can find a cycle in the graph with a positive weight, we would have found such a series of exchanges. Such a cycle can be solved using the Bellman- Ford algorithm. This is described in Solution The solutions to the problems of painting a Boolean matrix Problem The solution to the calendar rendering problem Problem Naive strategies yield quadratic run times.

However, once the interval endpoints have been sorted, it is easy to see that a point of maximum overlap can be determined by a linear time iteration through the endpoints. This sort sequence, which in some respects is more natural, does not work. However, some experimentation with it will, in all likelihood, lead to the correct criterion.

Sorting is not appropriate when an 0 n or better algorithm is possible. Another good example of a problem where a total ordering is not required is the problem of rearranging elements in an array described on Page Furthermore, sorting can obfuscate the problem. A recursive algorithm is often appropriate when the input is expressed using recursive rules, such as a computer grammar.

More generally, searching, enumeration, divide-and-conquer, and decomposing a complex problem into a set of similar smaller instances are all scenarios where recursion may be suitable.

String matching exemplifies the use of recursion. Suppose you were asked to write a Boolean-valued function which takes a string and a matching expression, and returns true if and only if the matching expression "matches" the string. This problem can be solved by checking a number of cases based on the first one or two characters of the matching expression, and recursively matching the rest of the string. A divide-and-conquer algorithm works by decomposing a problem into two or more smaller independent subproblems until it gets to instances that are simple enough to be solved directly; the results from the subproblems are then combined.

More details and examples are given in Chapter 18; we illustrate the basic idea below. A triomino is formed by joining three unit-sized squares in an L-shape. A mutilated chessboard henceforth 8x8 Mboard is made up of 64 unit-sized squares arranged. Suppose you are asked to design an algorithm that computes a placement of 21 triominoes that covers the 8x8 Mboard.

Since the 8x8 Mboard contains 63 squares, and we have 21 triominoes, a valid placement cannot have overlapping triominoes or triominoes which extend out of the 8x8 Mboard. Divide-and-conquer is a good strategy for this problem. Instead of the 8x8 Mboard, let's consider an n X n Mboard. A 2 X 2 Mboard can be covered with one triomino since it is of the same exact shape. However, you will quickly see that this line of reasoning does not lead you anywhere.

Another hypothesis is that if a placement exists for an nxn Mboard, then one also exists for a 2m X 2M Mboard. Now we can apply divide-and-conquer. The gap in the center can be covered with a triomino and, by hypothesis, we can cover the four n X n Mboards with triominoes as well.

Hence, a placement exists for any n that is a power of 2. In particular, a placement exists for the 23 X 23 Mboard; the recursion used in the proof directly yields the placement. Divide-and-conquer is usually implemented using recursion. However, the two — concepts are not synonymous. Recursion is more general subproblems do not have to be of the same form. In addition to divide-and-conquer, we used the generalization principle above. The idea behind generalization is to find a problem that subsumes the given problem and is easier to solve.

We used it to go from the 8x8 Mboard to the 2" X 2" Mboard. Another good example of divide-and-conquer is computing the number of pairs of elements in an array that are out of sorted order Problem A key aspect of DP is maintaining a cache of solutions to subinstances. It is most natural to design a DP algorithm using recursion. Usually, but not always, it is more efficient to implement it using iteration. As an example of the power of DP, consider the problem of determining the number of combinations of 2, 3, and 7 point plays that can generate a score of Let C s be the number of combinations that can generate a score of s.

The recursion ends at small scores, specifically, when 1. This phenomenon results in the run time increasing exponentially with the size of the input. The solution is to store previously computed values of C in an array of length Dynamic programming is the subject of Chapter The solutions to problems A greedy algorithm is one which makes decisions that are locally optimum and never changes them.

This strategy does not always yield the optimum solution. Furthermore, there may be multiple greedy algorithms for a given problem, and only some of them are optimum. For example, consider 2n cities on a line, half of which are white, and the other half are black. We want to map white to black cities in a one-to-one fashion so that the total length of the road sections required to connect paired cities is minimized.

Multiple pairs of cities may share a single section of road, e. The most straightforward greedy algorithm for this problem is to scan through the white cities, and, for each white city, pair it with the closest unpaired black city.

This algorithm leads to suboptimum results. Consider the case where white cities are at 0 and at 3 and black cities are at 2 and at 5. If the straightforward greedy algorithm processes the white city at 3 first, it pairs it with 2, forcing the cities at 0 and 5 to pair.

However, a slightly more sophisticated greedy algorithm does lead to optimum results: More succinctly, let W and B be the arrays of white and black city coordinates. The idea is that the pairing for the first city must be optimum, since if it were to be paired with any other city, we could always change its pairing to be with the nearest black city without adding any road.

Briefly, an invariant is a condition that is true during execution of a program. This condition may be on the values of the variables of the program, or on the control logic.

A well-chosen invariant can be used to rule out potential solutions that are suboptimal or dominated by other solutions. An invariant can also be used to analyze a given algorithm, e. Here our focus is on designing algorithms with invariants, not analyzing them. As an example, consider the 2-sum problem.

We are given an array A of sorted integers, and a target value K. The brute-force algorithm for the 2-sum problem consists of a pair of nested for loops. Its complexity is 0 n2 , where n is the length of A. A faster approach is to add each element of A to a hash H, and test for each i if K - A[i] is present in H.

While reducing time complexity to 0 n , this approach requires 0 n additional storage for H. A natural approach then is to initialize i to 0, and ; to n - 1, and then update i and j preserving the following invariant: Therefore, we can increment i, and preserve the invariant. At each step, we increment or decrement i or j. Since there are at most n steps, and each takes 0 1 time, the time complexity is 0 n. Correctness follows from the fact that the invariant never discards a value for i or j which could possibly be the index of an element which sums with another element to K.

Identifying the right invariant is an art. Usually, it is arrived at by studying concrete examples and then making an educated guess.

Often the first invariant is too strong, i. Binary search, which we study in Chapter 12, uses an invariant to design an 0 log n algorithm for searching in a sorted array. Solution Other examples of problems whose solutions make use of invariants are finding the longest subarray containing all entries Problem The run time of an algorithm depends on the size of its input. A common approach to capture the run time dependency is by expressing asymptotic bounds on the worst- case run time as a function of the input size.

Specifically, the run time of an algorithm on an input of size n is O f n if, for sufficiently large n, the run time is not more than f n times a constant. The big-O notation indicates an upper bound on running time.

Similarly, consider the naive algorithm for testing primality that tries all numbers from 2 to the square root of the input number n.

- THE LOGIC BOOK 6TH EDITION
- POKEMON CRYSTAL GUIDE BOOK
- CANON POWERSHOT SD1200 IS USER GUIDE PDF
- MANHATTAN GMAT SENTENCE CORRECTION GUIDE PDF
- CAMBRIDGE GRAMMAR OF ENGLISH A COMPREHENSIVE GUIDE PDF
- THE MYSTERIOUS BENEDICT SOCIETY AND THE PRISONERS DILEMMA PDF
- THE JUNGLE BOOK 2016 RINGTONE
- THK LINEAR GUIDES EPUB DOWNLOAD
- PROFESSIONAL VISUAL BASIC 2008 EBOOK
- VISHNU PURAN IN ENGLISH PDF
- LINCOLN CHILD EBOOK
- FU?BALL TRIX EBOOK
- EBOOK OOP PHP INDONESIA
- SWAMI VIVEKANANDA CHICAGO SPEECH IN HINDI PDF