You are on page 1of 48

List, Stack, Queue

Discuss the stack data structure. Implement a stack in C using either a linked list or a dynamic array, and
justify your decision. Design the interface to your stack to be complete, consistent, and easy to use.

A stack is a last-in-first-out (LIFO) data structure: Elements are always removed in the reverse order in which they
were added. The add element and remove element operations are conventionally called push and pop, respectively.
Stacks are useful data structures for tasks that are divided into multiple subtasks. Tracking return addresses,
parameters, and local variables for subroutines is one example of stack use; tracking tokens when parsing a
programming language is another.

One of the ways to implement a stack is by using a dynamic array, an array that changes size as needed when
elements are added. The main advantage of dynamic arrays over linked lists is that arrays offer random access to the
array elements - you can immediately access any element in the array simply by knowing its index. However,
operations on a stack always work on one end of the data structure (the top of the stack), so the random accessibility
of a dynamic array gains you little. In addition, as a dynamic array grows, it must occasionally be resized, which
can be a time-consuming operation as elements are copied from the old array to the new array.

Conversely, linked lists usually allocate memory dynamically for each element, and that overhead can be significant
when dealing with small-sized elements, especially if heuristics are used to minimize the number of times the array
has to be resized. For these reasons, a stack based on a dynamic array is usually faster than one based on a linked
list. In the context of an interview, though, the primary concern is ease and speed of implementation: Implementing
a linked list is far less complicated than implementing a dynamic array, so a linked list is probably the best choice
for your solution. 实现见手写部分

head and tail are global pointers to the first and last element, respectively, of a singly-linked list of integers.
Implement C functions for the following prototypes:

bool remove( Element *elem );


bool insertAfter( Element *elem, int data );

The argument to remove is the element to be deleted. The two arguments to insertAfter give the data for the
new element and the element after which the new element is to be inserted. It should be possible to insert at
the beginning of the list by calling insertAfter with NULL as the element argument. These functions should
return true if successful and false if unsuccessful.

Your functions must keep the head and tail pointers current.

ANS:

bool remove( Element *elem ){


Element *curPos = head;

if( !elem )
return false;
if( elem == head ){
head = elem->next;
delete elem;
/* special case for 1 element list */
if( !head )
tail = NULL;
return true;
}

while( curPos ){
if( curPos->next == elem ){
curPos->next = elem->next;
delete elem;
if( curPos->next == NULL )
tail = curPos;
return true;
}
curPos = curPos->next;
}

return false;
}

bool insertAfter( Element *elem, int data ){


Element *newElem, *curPos = head;

newElem = new Element;


if( !newElem )
return false;
newElem->data = data;

/* Insert at beginning of list */


if( !elem ){
newElem->next = head;
head = newElem;

/* Special case for empty list */


if( !tail )
tail = newElem;
return true;
}
while( curPos ){
if( curPos == elem ){
newElem->next = curPos->next;
curPos->next = newElem;

/* Special case for inserting at end of list */


if( !(newElem->next) )
tail = newElem;
return true;
}
curPos = curPos->next;
}

/* Insert position not found; free element and return failure */


delete newElem;
return false;
}

Find and fix the bugs in the following C/C++ function that is supposed to remove the head element from a
singly-linked list:

void removeHead( Node *head ){


delete head; /* Line 1 */
head = head->next; /* Line 2 */
}

Ans:

void removeHead( Node **head ){


Node *temp;
if( !(*head) ){
temp = (*head)->next;
delete *head;
*head = temp;
}

Mth-to-Last Element of a Linked List

Given a singly-linked list, devise a time- and space-efficient algorithm to find the mth-to-last element of the
list. Implement your algorithm, taking care to handle relevant error conditions. Define mth to last such that
when m = 0, the last element of the list is returned.

Element *findMToLastElement( Element *head, int m ){


Element *current, *mBehind;
int i;

/* Advance current m elements from beginning,


* checking for the end of the list
*/
current = head;
for (i = 0; i < m; i++) {
if (current->next) {
current = current->next;
} else {
return NULL;
}
}

/* Start mBehind at beginning and advance pointers


* together until current hits last element
*/
mBehind = head;
while( current->next ){
current = current->next;
mBehind = mBehind->next;
}

/* mBehind now points to the element we were


* searching for, so return it
*/
return mBehind;
}

Given a linked list which is sorted. How will you insert in sorted way.

-------------------------------------------------------------------------------------------------------------------------------------------

Write C code for (a) deleting an element from a linked list (b) traversing a linked list

Delete an element from a doubly linked list.

Given a singly linked list, print out its contents in reverse order. Can you do it without using any extra space?

How can you print a singly linked list in reverse order?    (it's a huge list and you cannot use recursion)
Ans: reverse the pointers till you reach the end and print, and reverse as you return.

Reverse a singly linked list recursively. The function prototype is node * reverse (node *) ;

ANS.

node * reverse (node * n)


{
node * m ;

if (! (n && n -> next))


return n ;

m = reverse (n -> next) ;


n -> next -> next = n ; // m->next = n;
n -> next = NULL ;
return m ;
}

Write a nonrecursive procedure to reverse a singly linked list in O(n) time using constant extra space.
Write an algorithm for printing a singly linked list in reverse, using only constant extra space. This
instruction implies that you cannot use recursion but you may assume that your algorithm is a list member
function. Can such an algorithm be written if the routine is a constant member function?
Reversal of a singly linked list can be done recursively using a stack, but this requires O(N) extra space. The
following solution is similar to strategies employed in garbage collection algorithms (first represents the first node
in the non-empty node in the non-empty list). At the top of the while loop, the list from the start to previousPos is
already reversed, whereas the rest of the list, from currentPos to the end is normal. This algorithm uses only
constant extra space.

//Assuming no header and that first is not NULL (list is not empty)
Node * reverseList(Node *first)
{
Node * currentPos, *nextPos, *previousPos;

if ( ! (first && first -> next) )


return first;

previousPos = NULL;
currentPos = first;
nextPos = first->next;
while (nextPos != NULL)
{
currentPos -> next = previousPos;
perviousPos = currentPos;
currentPos = nextPos;
nextPos = nextPos -> next;
}
currentPos->next = previousPos;
return currentPos;
}

Given a singly linked list, find the middle of the list.

idea 和在 list 中找环一样. HINT. Use the single and double pointer jumping. Maintain two pointers, initially
pointing to the head. Advance one of them one node at a time. And the other one, two nodes at a time. When the
double reaches the end, the single is in the middle. This is not asymptotically faster but seems to take less steps than
going through the list twice.

Given two sorted lists, L1 and L2, write a procedure to compute L1 ∩ L2 using only the basic list operations.

// Assumes both input lists are sorted


template <typename Object>
list<Object> intersection( const list<Object> & L1, const list<Object> & L2)
{
list<Object> intersect;
typename list<Object>:: const_iterator iterL1 = L1.begin();
typename list<Object>:: const_iterator iterL2= L2.begin();
while(iterL1 != L1.end() && iterL2 != L2.end())
{
if (*iterL1 == *iterL2)
{
intersect.push_back(*iterL1);
iterL1++;
iterL2++;
}
else if (*iterL1 < *iterL2)
iterL1++;
else
iterL2++;
}
return intersect;
}
Given two sorted lists, L1 and L2, write a procedure to compute L1 ∪ L2 using only the basic list operations.

// Assumes both input lists are sorted


template <typename Object>
list<Object> listUnion( const list<Object> & L1, const list<Object> & L2)
{
list<Object> result;
typename list<Object>:: const_iterator iterL1 = L1.begin();
typename list<Object>:: const_iterator iterL2= L2.begin();
while(iterL1 != L1.end() && iterL2 != L2.end())
{
if (*iterL1 == *iterL2)
{
result.push_back(*iterL1);
iterL1++;
iterL2++;
}
else if (*iterL1 < *iterL2)
{
result.push_back(*iterL1);
iterL1++;
}
else
{
result.push_back(*iterL2);
iterL2++;
}
}
while(iterL1 != L1.end())
{
result.push_back(*iterL1);
iterL1++;
}
while(iterL2 != L2.end())
{
result.push_back(*iterL2);
iterL2++;
}
return result;
}

Write routines to implement two stacks using only one array. Your stack routines should not declare an overflow
unless every slot in the array is used.

Two stacks can be implemented in an array by having one grow from the low end of the array up, and the other
from the high end down.

*a. Propose a data structure that supports the stack push and pop operations and a third operation findMin, which
returns the smallest element in the data structure, all in O(1) worst case time.

*b. Prove that if we add the fourth operation deleteMin which finds and removes the smallest element, then at least
one of the operations must take Ω(logn) time.
(a) C++: Let E be our extended stack. We will implement E with two stacks. One stack, which we’ll call S, is used
to keep track of the push and pop operations, and the other M, keeps track of the minimum. To implement E.push
(x), we perform S.push (x). If x is smaller than or equal to the top element in stack M, then we also perform M.push
(x). To implement E.pop( ) we perform S.pop( ). If x is equal to the top element in stack M, then we also M.pop( ).
E.findMin( ) is performed by examining the top of M. All these operations are clearly O (1).
(a) C: Let E be our extended stack. We will implement E with two stacks. One stack, which we’ll call S, is used to
keep track of the Push and Pop operations, and the other, M, keeps track of the minimum. To implement Push(X,E),
we perform Push(X,S). If X is smaller than or equal to the top element in stack M, then we also perform Push(X,M).
To implement Pop(E), we perform Pop(S). If X is equal to the top element in stack M, then we also Pop(M).
FindMin(E) is performed by examining the top of M. All these operations are clearly O(1).
(b) This result follows from a theorem that shows that comparison sorting must take W(Nlog N) time. O(N)
operations in the repertoire, including deleteMin, would be sufficient to sort.

*Show how to implement three stacks in one array.

Three stacks can be implemented by having one grow from the bottom up, another from the top down, and a third
somewhere in the middle growing in some (arbitrary) direction. If the third stack collides with either of the other
two, it needs to be moved. A reasonable strategy is to move it so that its center (at the time of the move) is halfway
between the tops of the other two stacks.

Suppose we have an array-based list a[0..n -1] and we want to delete all duplicates. Give an algorithm to solve this
problem in O(n log n) time.

Sort the list, and make a scan to remove duplicates (which must now be adjacent).

Swap two adjacent elements by adjusting only the pointers (and not the data) using

a. singly linked lists,

// beforeP is the cell before the two adjacent cells that are to be swapped
// Error checks are omitted for clarity
void swapWithNext(Node * beforep)
{
Node *p , *afterp;
p = before->next;
afterp = p->next; // both p and afterp assumed not NULL
p->next = afterp-> next;
beforep ->next = afterp;
afterp->next = p;
}

b. doubly linked lists.

// p and afterp are cells to be switched. Error checks as before


void swapWithNext(Node * p)
{
Node *beforep, *afterp;
beforep = p->prev;
afterp = p->next;
p->next = afterp->next;
beforep->next = afterp;
afterp->next = p;
p->next->prev = p;
p->prev = afterp;
afterp->prev = beforep;
}

A deque is a data structure consisting of a list of items, on which the following operations are possible:

push(x,d): Insert item x on the front end of deque d.

pop(d): Remove the front item from deque d and return it.

inject(x,d): Insert item x on the rear end of deque d.

eject(d): Remove the rear item from deque d and return it.

Write routines to support the deque that take O(1) time per operation.

This requires a doubly linked list with pointers to the head and the tail. In fact it can be implemented with a list by
just renaming the list operations.
template <typename Object>
class deque
{
public:
deque() { list();}
void push (Object obj) { dlist.push_front(obj);}
Object pop (); {Object obj= dlist.front(); dlist.pop_front(); return obj;}
void inject(Object obj); { dlist.push_back(obj);}
Object eject(); {Object obj= dlist.back (); dlist.pop_back (); return obj;}
private:
list<Object> dlist;
};

Efficiently implement a stack class using a singly linked list, with no header or tail nodes.

template <typename Object>


struct node
{
node () { next = NULL;}
node (Object obj) : data(obj) {}
node (Object obj, node * ptr) : data(obj), next(ptr) {}
Object data;
node * next;
};

template <typename Object>


class stack
{
public:
stack () { head = NULL;}
~stack() { while (head) pop(); }

void push(Object obj)


{
node<Object> * ptr = new node<Object>(obj, head);
head= ptr;
}

Object top()
{
if(head == NULL)
throw UnderflowException;
return (head->data);
}

void pop()
{
if(head == NULL)
throw UnderflowException;
node<Object> * ptr = head->next;
delete head;
head = ptr;
}

private:
node<Object> * head; // head 最好换成 topOfStack
};

Efficiently implement a queue class using a singly linked list, with no header or tail nodes.

template <typename Object>


struct node
{
node () { next = NULL;}
node (Object obj) : data(obj) {}
node (Object obj, node * ptr) : data(obj), next(ptr) {}
Object data;
node * next;

};

template <typename Object>


class queue
{
public:
queue () { front = NULL; rear = NULL;}
~queue() { while (front) deque(); }

void enque(Object obj)


{
node<Object> * ptr = new node<Object>(obj, NULL);
if (rear)
rear= rear->next = ptr;
else
front = rear = ptr;
}

//Object getFront()
//{
// if(front == NULL)
// throw UnderflowException;
// return (front ->data);
//}

Object deque()
{
if(front == NULL)
throw UnderflowException;
Object temp = front->data;
node<Object> * ptr = front;
if (front->next == NULL) // only 1 node
front = rear = NULL;
else
front = front->next;
delete ptr;
return temp;
}

private:
node<Object> * front;
node<Object> * rear;
};

Efficiently implement a queue class using a circular array. Your may use a vector (rather than a primitive array) as
the underlying array structure. (The following implementation holds maxSize−1 elements. 0 位置不存储元素.
Front 为第一个元素前面的位置, rear 为最后一个元素的位置.)

template <typename Object>


class queue
{
public:
queue(int s): maxSize(s), front(0), rear(0) {elements.resize(maxSize);}
queue (): maxSize(100), front(0), rear(0) {elements.resize(maxSize);}
~queue() { while (front!=rear) deque(); }

void enque(Object obj)


{
if (full()){
elements.resize(maxSize*2+1);
if(front != 0){
for(int i=0; i< front; i++){
elements[i+ maxSize] = elements[i];
}
rear += maxSize;
}
maxSize = maxSize*2+1;
}

elements[(rear=(rear+1) % maxSize)] = obj;


// rear = (rear + 1) % maxSize;
}

Object deque()
{
if (!empty())
{
Object temp = elements[(front +1 ) % maxSize]; // temp = elements[front]; 若 front 指 first 元素
front = (front +1 ) % maxSize;
return temp;
}
else
throw UnderflowException;
}
bool empty() {return front == rear;}

bool full() { return (rear + 1) % maxSize == front;}

private:
int front, rear;
int maxSize;
vector<Object> elements ;

};

Cycle in singly linked list

You are given a linked list that is either NULL-terminated (acyclic), as shown in Figure 4-5, or ends in a
cycle (cyclic), as shown in Figure 4-6.

Write a function that takes a pointer to the head of a list and determines whether the list is cyclic or acyclic.
Your function should return false if the list is acyclic and true if it is cyclic. You may not modify the list in
any way.

Ans: advance the pointers at different speeds?

In the acyclic list, the faster pointer will reach the end. In the cyclic list, they will both loop endlessly. The
faster pointer will eventually catch up with and pass the slower pointer. If the fast pointer ever passes the
slower pointer, you have a cyclic list. If it encounters a NULL pointer, you have an acyclic list. In outline form,
this algorithm looks like this:

Start two pointers at the head of the list


Loop infinitely
If the fast pointer reaches a NULL pointer
Return that the list is NULL terminated
If the fast pointer moves onto or over the slow pointer
Return that there is a cycle
Advance the slow pointer one node
Advance the fast pointer two nodes

You can now implement this solution:

/* Takes a pointer to the head of a linked list and determines if


* the list ends in a cycle or is NULL terminated
*/
bool determineTermination( Node *head ){
Node *fast, *slow;
fast = slow = head;
while( true ){
if( !fast || !fast->next )
return false;
else if( fast == slow || fast->next == slow )
return true;
else {
slow = slow->next;
fast = fast->next->next;
}
}
}

Is this algorithm faster than the earlier solution? If this list is acyclic, the faster pointer comes to the end after
examining n nodes, while the slower pointer traverses 1/2 n nodes. Thus, you examine 3/2n nodes, which is an O(n)
algorithm.

What about a cyclic list? The slower pointer will never go around any loop more than once. When the slower
pointer has examined n nodes, the faster pointer will have examined 2n nodes and have “passed” the slower pointer,
regardless of the loop’s size. Therefore, in the worst case you examine 3n nodes, which is still O(n). Regardless of
whether the list is cyclic or acyclic, this two-pointer approach is much better than the one-pointer approach to the
problem.

Idea: Use two iterators p and q, both initially at the start of the list. Advance p one step at a time, and q two
steps at a time. If q reaches the end there is no cycle; otherwise, p and q will eventually catch up to each other
in the middle of the cycle.

Suppose we have a pointer to a node in a singly linked list that is guaranteed not to be the last node in the list. You
don’t have pointers to any other nodes (except by following links). Describe an O(1) algorithm that logically
removes the value stored in such a node from the linked list, maintaining the integrity of the linked list.

Answer: Copy the value of the item in the next node (that is, the node that follows the referenced node) into the
current node (that is, the node being referenced). Then do a deletion of the next node. Code:
currnet->data = currnet->next->data;

currnet->next = currnet->next->next;

ListNode *tmp = currnet->next;

delete tmp;

Suppose that a singly linked list is implemented with both a header and a tail node. Describe O(1) algorithms to

a. Insert item x before position p (given by an iterator).

b. Remove the item stored at position p (given by an iterator).

(a) Add a copy of the node in position p after position p; then change the value stored in position p to x.
(b) Set p->data = p->next->data and set p->next = p->next->next. Then delete p->next. Note that the tail node
guarantees that there is always a next node.

Under what circumstances can one delete an element from a singly linked list in constant time?

ANS. If the list is circular and there are no references to the nodes in the list from anywhere else! Just copy the
contents of the next node and delete the next node. If the list is not circular, we can delete any but the last node
using this idea. In that case, mark the last node as dummy!

The Josephus problem is the following mass suicide "game": n people, numbered 1 to n, are sitting in a circle.
Starting at person 1, a handgun is passed. After m passes, the person holding the gun commits suicide, the body is
removed, the circle closes ranks, and the game continues with the person who was sitting after the corpse picking up
the gun. The last survivor is tried for n - 1 counts of manslaughter. Thus, if m = 0 and n = 5, players are killed in
order and player 5 stands trial. If m = 1 and n = 5, the order of death is 2, 4, 1, 5.

a. Write a program to solve the Josephus problem for general values of m and n. Try to make your program as
efficient as possible. Make sure you dispose of cells.

b. What is the running time of your program?

c. If m = 1, what is the running time of your program? How is the actual speed affected by the free routine for large
values of n (n > 10000)?

This is a standard programming project. The algorithm can be sped up by setting M' = M mod N, so that the hot
potato never goes around the circle more than once, and then if M' > N/ 2, the potato should be passed in the reverse
direction (passing the potato appropriately in the alternative direction). This requires a doubly linked list. The worst-
case running time is clearly O(N min(M, N)), although when these heuristics are used, and M and N are comparable,
the algorithm might be significantly faster. If M = 1, the algorithm is clearly linear.

The VAX/VMS C compiler’s memory management routines do poorly with the particular pattern of frees in this
case, causing O(Nlog N) behavior.
Tree
Do a breadth first traversal of a tree.

Do level order traversal of a binary tree in linear time (list out root, then nodes at depth 1, followed by nodes at
depth 2, and so on):
Ans: Put the root on an empty queue. Then repeatedly dequeue a node and enqueue its left and right children (if
any) until the queue is empty. This is O(N) because each queue operation is constant time and there are N enqueue
and N dequeue operations.

Compute the number of nodes, leaves and full nodes (full node is the node that has both left and right children) of a
binary tree.

All these routines take linear time.

// These functions use the type Node, which is the same as BinaryNode.
int countNodes( Node *t )
{
if( t == NULL )
return 0;
return 1 + countNodes( t->left ) + countNodes( t->right );
}

int countLeaves( Node *t )


{
if( t == NULL )
return 0;
else if( t->left == NULL && t->right == NULL )
return 1;
return countLeaves( t->left ) + countLeaves( t->right );
}

int countFull( Node *t )


{
if( t == NULL )
return 0;
int tIsFull = ( t->left != NULL && t->right != NULL ) ? 1 : 0;
return tIsFull + countFull( t->left ) + countFull( t->right );
}

Heaps are trees (usually binary trees) with a twist: Each child of a node has a value less than or equal to the node’s
own value. (The data implementation of a heap, however, can be different from what we discussed previously.)
Consequently, the root node always has the largest value in the tree, which means that it’s possible to find the
maximum value in constant time: Simply return the root value. Insertion and deletion are still O(log(n)), but lookup
becomes O(n). It is not possible to find the next-higher node to a given node in O(log(n)) time or to print out the
nodes in sorted order in O(n) time as in a BST.

You could model the patients waiting in a hospital emergency room with a heap. As each patient enters, he or she is
assigned a priority and put into the heap. A heart attack patient would get a higher priority than a patient with a
stubbed toe. When a doctor becomes available, the doctor would want to examine the patient with the highest
priority. The doctor can determine the patient with the highest priority by extracting the max value from the heap,
which is a constant time operation.

Tip If extracting the max value needs to be fast, then use a heap.

Common Searches

It’s nice when you have a tree with ordering properties such as a BST or a heap. Often you’re given a tree that isn’t
a BST or a heap. For example, you may have a tree that is a representation of a family tree or a company job
hierarchy. You have to use different techniques to retrieve data from this kind of tree. One common class of
problems involves searching for a particular node. Two very common search algorithms are used to accomplish this
task.

Breadth-First Search

One way to search a tree is to do a breadth-first search (BFS). In a BFS you start with the root, move left to right
across the second level, then move left to right across the third level, and so forth. You continue the search until
either you have examined all of the nodes or you find the node you are searching for. The time to find a node is
O(n), so this type of search is best avoided for large trees. A BFS also uses a large amount of memory because it is
necessary to track the child nodes for all nodes on a given level while searching that level.

Depth-First Search
Another common way to search for a node is by using a depth-first search (DFS). A depth-first search follows one
branch of the tree down as many levels as possible until the target node is found or the end is reached. When the
search can’t go down any farther, it is continued at the nearest ancestor with unexplored children. DFS has much
lower memory requirements than BFS because it is not necessary to store all of the child pointers at each level. In
addition, DFS has the advantage that it doesn’t examine any single level last (BFS examines the lowest level last).
This is useful if you suspect that the node you are searching for will be in the lower levels. For example, if you were
searching a job hierarchy tree looking for an employee who started less than three months ago, you would suspect
that lower-level employees are more likely to have started recently. In this case, if the assumption were true, a DFS
would usually find the target node more quickly than a BFS.

There are other types of searches, but these are the two most common that you will encounter in an interview.

Traversals

Another common type of tree problem is called a traversal. As opposed to a search, where you look for a particular
node and stop when you find it, a traversal visits every node and performs some operation on it. There are many
types of traversals, each of which visits nodes in a different order, but you’re only likely to be asked about the three
most common types of traversal for binary trees:

 Preorder traversal of a node performs the operation first on the node itself, then on its left descendants, and
finally on its right descendants. In other words, a node is always visited before any of its children.

 Inorder traversal performs the operation first on the node’s left descendants, then on the node itself, and
finally on its right descendants. In other words, the left subtree is visited first, then the node itself, and then
the node’s right subtree.

 Postorder traversal performs the operation first on the node’s left descendants, then on the node’s right
descendants, and finally on the node itself. In other words, all of a node’s children are visited before the
node itself.

These traversals can also happen with nonbinary trees as long as you have a way to classify whether a child is “less
than” (on the left of) or “greater than” (on the right of) its parent node.

Recursion is usually the simplest way to implement a traversal. See the problems in the chapter for some examples.

Tip If you’re asked to implement a traversal, recursion is a good way to start thinking about the problem.

Given a binary tree with nodes, print out the values in pre-order / in-order / post-order without using any extra
space. 见手写代码

Lowest Common Ancestor


Given the value of two nodes in a binary search tree, find the lowest (nearest) common ancestor. You may
assume that both values already exist in the tree.
Algo:
Examine the current node
If value1 and value2 are both less than the current node's value
Examine the left child
If value1 and value2 are both greater than the current node's value
Examine the right child
Otherwise
The current node is the lowest common ancestor

Code:

This solution may seem to suggest using recursion because it is a tree and the algorithm has a recursive structure to
it, but recursion is not necessary here. Recursion is most useful when moving through multiple branches of a tree or
examining some special pattern of nodes. Here you are only traveling down the tree. It’s easy to implement this
kind of traversal iteratively:

Node findLowestCommonAncestor( Node root, int value1,


int value2 ){
while( root != null ){
int value = root.getValue();
if( value > value1 && value > value2 ){
root = root.getLeft();
} else if( value < value1 && value < value2 ){
root = root.getRight();
} else {
return root;
}
}

return null; // only if empty tree


}

// Overload it to handle nodes as well


Node findLowestCommonAncestor( Node root, Node child1,
Node child2 ){
if( root == null || child1 == null || child2 == null ){
return null;
}

return findLowestCommonAncestor( root, child1.getValue(),


child2.getValue() );
}
What’s the running time of this algorithm? You are traveling down a path to the lowest common ancestor. Recall
that traveling a path to any one node takes O(log(n)). Therefore, this is an O(log(n)) algorithm. In addition, this is
slightly more efficient than a similar recursive solution because you don’t have the overhead of repeated function
calls.

Implement a function to check if a tree is balanced. For the purposes of this question, a balanced tree is defined to
be a tree such that no two leaf nodes differ in distance from the root by more than one.

The idea is very simple: the difference of min depth and max depth should not exceed 1, since the difference of the
min and the max depth is the maximum distance difference possible in the tree.
public static int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}

public static int minDepth(TreeNode root) {


if (root == null) {
return 0;
}
return 1 + Math.min(minDepth(root.left), minDepth(root.right));
}

public static boolean isBalanced(TreeNode root){


return (maxDepth(root) - minDepth(root) <= 1);
}

Bit-manipulation

Write a function that determines the number of 1 bits in the computer’s internal representation of a given
integer.
Sol1: Start with count = 0
While the integer is not 0
If the integer AND 1 equals 1, increment count
Shift the integer one bit to the right
Return count

int numOnesInBinary( int number )


{
int numOnes = 0;
while( number != 0 ){
if( ( number & 1 ) == 1 )
numOnes++;
number = number >>> 1;
}
return numOnes;
}

Sol2: Start with count = 0


While the integer is not zero
AND the integer with the integer – 1
Increment count
Return count

int numOnesInBinary( int number ){


int numOnes = 0;
while( number != 0 ){
number = number & (number – 1);
numOnes++;
}
return numOnes;
}

Write a function that determines whether a computer is big-endian or little-endian.

Set an integer to 1
Cast a pointer to the integer as a char *
If the dereferenced pointer is 1, the machine is little-endian
If the dereferenced pointer is 0, the machine is big-endian
/* Returns true if the machine is little-endian, false if the
* machine is big-endian
*/
bool endianness(){
int testNum;
char *ptr;

testNum = 1;
ptr = (char *) &testNum;
return (*ptr); /* Returns the byte at the lowest address */
}

Using a union, the code is as follows:

/* Returns true if the machine is little-endian, false if the


* machine is big-endian
*/
bool endianness(){
union {
int theInteger;
char singleByte;
} endianTest;

endianTest.theInteger = 1;
return endianTest.singleByte;
}

Give a very good method to count the number of ones in a "n" (e.g. 32) bit number.

ANS. Given below are simple solutions, find a solution that does it in log (n) steps. 

Iterative:
function iterativecount (unsigned int n)
begin
int count=0;
while (n)
begin
count += n & 0x1 ;
n >>= 1;
end
return count;
end

Sparse Count:
function sparsecount (unsigned int n)
begin
int count=0;
while (n)
begin
count++;
n &= (n-1);
end
return count ;
end

Reverse the bits of an unsigned integer.

#define reverse(x) \
(x=x>>16|(0x0000ffff&x)<<16, \
x=(0xff00ff00&x)>>8|(0x00ff00ff&x)<<8, \
x=(0xf0f0f0f0&x)>>4|(0x0f0f0f0f&x)<<4, \
x=(0xcccccccc&x)>>2|(0x33333333&x)<<2, \
x=(0xaaaaaaaa&x)>>1|(0x55555555&x)<<1)

Compute the number of ones in an unsigned integer.


#define count_ones(x) \
(x=(0xaaaaaaaa&x)>>1+(0x55555555&x), \
x=(0xcccccccc&x)>>2+(0x33333333&x), \
x=(0xf0f0f0f0&x)>>4+(0x0f0f0f0f&x), \
x=(0xff00ff00&x)>>8+(0x00ff00ff&x), \
x=x>>16+(0x0000ffff&x))

Compute the discrete log of an unsigned integer.


#define discrete_log(h) \
(h=(h>>1)|(h>>2), \
h|=(h>>2), \
h|=(h>>4), \
h|=(h>>8), \
h|=(h>>16), \
h=(0xaaaaaaaa&h)>>1+(0x55555555&h), \
h=(0xcccccccc&h)>>2+(0x33333333&h), \
h=(0xf0f0f0f0&h)>>4+(0x0f0f0f0f&h), \
h=(0xff00ff00&h)>>8+(0x00ff00ff&h), \
h=(h>>16)+(0x0000ffff&h))

If I understand it right, log2(2) =1, log2(3)=1, log2(4)=2..... But this macro does not work out log2(0) which does
not exist! How do you think it should be handled?

How do we test most simply if an unsigned integer is a power of two?


#define power_of_two(x) \ ((x)&&(~(x&(x-1))))

Set the highest significant bit of an unsigned integer to zero.

#define zero_most_significant(h) \ 
(h&=(h>>1)|(h>>2), \ 
h|=(h>>2), \ 
h|=(h>>4), \ 
h|=(h>>8), \ 
h|=(h>>16))

How would you reverse the bits of a number with log N arithmetic operations, where N is the number of bits in the
integer (eg 32,64..)
ANS: Flip adjacent bits, then flip adjacent 2 bit sets, then 4-bits and so on. Each of this swap can be done in
constant time using appropriate masks and shifts.
What is the simples way to check if the sum of two unsigned integers has resulted in an overflow.
If (a+b) < a or (a+b) < b then overflow has occurred

Array and String


Find the First Nonrepeated Character

Write an efficient function to find the first nonrepeated character in a string. For instance, the first
nonrepeated character in “total” is ‘o’ and the first nonrepeated character in “teeter” is ‘r’. Discuss the
efficiency of your algorithm.

Algo: First, build the character count hash table:


For each character
If no value is stored for the character, store 1
Otherwise, increment the value
Second, scan the string:
For each character
Return character if count in hash table is 1
If no characters have count 1, return null

Remove Specified Characters

Write an efficient function in C# that deletes characters from a string. Use the prototype

string removeChars( string str, string remove );

where any character existing in remove must be deleted from str. For example, given a str of “Battle of the
Vowels: Hawaii vs. Grozny” and a remove of “aeiou”, the function should transform str to “Bttl f th Vwls:
Hw vs. Grzny”. Justify any design decisions you make and discuss the efficiency of your solution.

Alog: 1. Set all the elements in your lookup array to false. 2. Iterate through each character in remove, setting
the corresponding value in the lookup array to true. 3.Iterate through str with a source and destination index,
copying each character only if its corresponding value in the lookup array is false.

stri ng removeChars( string str, string remove ){


char[] s = str.toCharArray();
char[] r = remove.toCharArray();
bool[] flags = new bool[128]; // assumes ASCII!
int len = s.Length;
int src, dst;
// Set flags for characters to be removed
for( src = 0; src < len; ++src ){
flags[r[src]] = true;
}
src = 0;
dst = 0;

// Now loop through all the characters,


// copying only if they aren't flagged
while( src < len ){
if( !flags[ (int)s[src] ] ){
s[dst++] = s[src];
}
++src;
}

return new string( s, 0, dst );


}
Reverse Words

Write a function that reverses the order of the words in a string. For example, your function should
transform the string “Do or do not, there is no try.” to “try. no is there not, do or Do”. Assume that all words
are space delimited and treat punctuation the same as letters.

ANS. Write a routine to reverse a character array. Now call it for the given array and for each word in it.

void reverseWords( char str[] ){


int start = 0, end = 0, length;

length = strlen(str);
/* Reverse entire string */
reverseString(str, start, length - 1);

while( end < length ){


if( str[end] != ' ' ){ /* Skip non-word characters */

/* Save position of beginning of word */


start = end;

/* Scan to next non-word character */


while( end < length && str[end] != ' ' )
end++;
/* Back up to end of word */
end--;
/* Reverse word */
reverseString( str, start, end );
}
end++; /* Advance to next token */
}
}

void reverseString( char str[], int start, int end ){


char temp;
while( end > start ){
/* Exchange characters */
temp = str[start];
str[start] = str[end];
str[end] = temp;

/* Move indices towards middle */


start++; end--;
}
}

Integer/String Conversions
Write two conversion routines. The first routine converts a string to a signed integer. You may assume that
the string only contains digits and the minus character (‘-’), that it is a properly formatted integer number,
and that the number is within the range of an int type. The second routine converts a signed integer stored as
an int back to a string.

From String to Integer

Start number at 0
If the first character is '-'
Set the negative flag
Start scanning with the next character
For each character in the string
Multiply number by 10
Add (digit character – '0') to number
Return number
int strToInt( string str ){
int i = 0, num = 0;
bool isNeg = false;
int len = str.Length();
if( str[0] == '-' ){
isNeg = true;
i = 1;
}

while( i < len ){


num *= 10;
num += ( str[i++] - '0' );
}

if( isNeg )
num *= -1;

return num;
}

From Integer to String

If number less than zero:


Multiply number by –1
Set negative flag
While number not equal to 0
Add '0' to number % 10 and write this to temp buffer
Integer divide number by 10
If negative flag is set
Write '-' into next position in temp buffer
Write characters in temp buffer into output string in reverse order:

public static final int MAX_DIGITS = 10;

String intToStr( int num ){


int i = 0;
boolean isNeg = false;
/* Buffer big enough for largest int and - sign */
char[] temp = new char[ MAX_DIGITS + 1 ];

/* Check to see if the number is negative */


if( num < 0 ){
num *= -1;
isNeg = true;
}

/* Fill buffer with digit characters in reverse order */


do {
temp[i++] = (char)((num % 10) + '0');
num /= 10;
} while( num != 0 );

StringBuffer b = new StringBuffer();


if( isNeg )
b.append( '-' );

while( i > 0 ){
b.append( temp[--i] );
}

return b.toString();
}

Given only putchar (no sprintf, itoa, etc.) write a routine that prints out an unsigned long in decimal. Try doing it
without using extra storage.
ANS: 这里扩展成可以将任意整数以 2 进制到 16 进制输出, 采用 C++, 可换成 C 代码
const string DIGIT_TABLE = “0123456789abcdef”; // #define DIGIT_TABLE “0123456789abcdef”
const int MAX_BASE = DIGIT_TABLE.length(); // #define MAX_BASE 16

void printIntRec(long n, int base) {


if(n>=base)
printIntRec(n / base, base);
cout<< DIGIT_TABLE[n % base];
}

void pintInt(long n, int base){ //driver routine


if(base <= 1 || base > MAX_BASE)
cerr<<“Cannot print in base ”<<base<<endl;
else{
if(n < 0){
cout<<“-”;
n = -n;
}
printIntRec(n, base);
}
}

Permutations of a String
Implement a routine that prints all possible orderings of the characters in a string. In other words, print all
permutations that use all the characters from the original string. For example, given the string “hat”, your
function should print the strings “tha”, “aht”, “tah”, “ath”, “hta”, and “hat”. Treat each character in the
input string as a distinct character, even if it is repeated. Given the string “aaa”, your routine should print
“aaa” six times. You may print the permutations in any order you choose.

If you're past the last position


Print the string
Return
Otherwise
For each letter in the input string
If it's marked as used, skip to the next letter
Else place the letter in the current position
Mark the letter as used
Permute remaining letters starting at current position + 1
Mark the letter as unused
void permute( String str ){
int length = str.length();
boolean[] used = new boolean[ length ];
StringBuffer out = new StringBuffer();
char[] in = str.toCharArray();

doPermute( in, out, used, length, 0 );


}

void doPermute( char[] in, StringBuffer out,


boolean[] used, int length, int level ){
if( level == length ){
System.out.println( out.toString() );
return;
}

for( int i = 0; i < length; ++i ){


if( used[i] ) continue;

out.append( in[i] );
used[i] = true;
doPermute( in, out, used, length, level + 1 );
used[i] = false;
out.setLength( out.length() - 1 );
}
}
Combinations of a String
Implement a function that prints all possible combinations of the characters in a string. These combinations
range in length from one to the length of the string. Two combinations that differ only in ordering of their
characters are the same combination. In other words, “12” and “31” are different combinations from the
input string “123”, but “21” is the same as “12”.

For each letter from input start position to end of input string
Select the letter into the current position in output string
Print letters in output string
If the current letter isn't the last in the input string
Generate remaining combinations starting at next position with iteration starting
at next letter beyond the letter just selected

void combine( string str ){


int length = str.Length;
char[] instr = str.ToCharArray();
StringBuilder outstr = new StringBuilder();
doCombine( instr, outstr, length, 0, 0 );
}

void doCombine( char[] instr, StringBuilder outstr, int length,


int level, int start ){
for( int i = start; i < length; i++ ){
outstr.Append( instr[i] );
Console.WriteLine( outstr );

if( i < length - 1 ){


doCombine( instr, outstr, length, level + 1, i + 1 );
}

outstr.Length = outstr.Length - 1;
}
}

Given an array of characters. How would you reverse it? How would you reverse it without using indexing in the
array.

'reverse a string' coding question - first have them write a func that prints out a C string without looping constructs
or using local vars. Then if they get that, ask them to implement a reverse string function in the same manner as the
first one. Don't say "use recursion" - let them figure out its straightforward applicability to the problem. That's,
IMHO, how you can gauge if they 'think recursively' when lightly nudged in that direction:
void print(char *s) {
if (*s != 0) {
putchar(*s);
print(s+1);
}
}

void printreverse(char *s) {


if (*s != 0)
printreverse(s+1);
putchar(*s);
}
}

int main() {
char *s = "Hello world";
print(s);
putchar('\n');
printreverse(s);
putchar('\n');
}

Given a sequence of characters. How will you convert the lower case characters to upper case characters. ( Try
using bit vector - solutions given in the C lib -typec.h)
void toUpper(char* str)
{
int i = 0;
while(str[i] != '\0')
{
if(str[i] >= 'a' && str[i]<= 'z')
str[i] += 'A' - 'a';
i++;
}
}

Bubble Sort:

Start at the beginning of an array and swap the first two elements if the first is bigger than the second.Go to the next
pair, etc, continuously making sweeps of the array until sorted.O(n^2).
Selection Sort:

Find the smallest element using a linear scan and move it to the front.Then, find the second smallest and move it,
again doing a linear scan.Continue doing this until all the elements are in place.O(n^2).
Merge Sort:

Sort each pair of elements.Then, sort every four elements by merging every two pairs.Then, sort every 8 elements,
etc.O(n log n) expected and worst case.
Quick Sort:

Pick a random element and partition the array, such that all numbers that are less than it come before all elements
that are greater than it.Then do that for each half, then each quarter, etc.O(n log n) expected, O(n^2) worst case.
Bucket Sort:

Partition the array into a finite number of buckets, and then sort each bucket individually.This gives a time of O(n +
m), where n is the number of items and m is the number of distinct items.
Big vs Little Endian:

In big endian, the most significant byte is stored at the memory address location with the lowest address. This is
akin to left-to-right reading order. Little endian is the reverse: the most significant byte is stored at the address with
the highest address.
Stack (Memory)

When a function calls another function which calls another function, this memory goes onto the stack. An int (not a
pointer to an int) that is created in a function is stored on the stack.
Heap (Memory)

When you allocate data with new() or malloc(), this data gets stored on the heap.

Threads

A thread is the fundamental unit of execution within an application: A running application consists of at least one
thread. Each thread has its own stack and runs independently from the application’s other threads. Threads share the
resources used by the application as it runs, such as file handles or memory, which is why problems can occur. Data
corruption is a common side effect of having two threads simultaneously write data to the same block of memory,
for example.

Threads can be implemented in different ways. On most systems, threads are created and managed by the operating
system. Sometimes, however, the threads are actually implemented by a software layer above the operating system,
such as the runtime system for an interpreted programming language. Conceptually, however, they behave the
same, and the remainder of this chapter assumes that the operating system manages the threads. (Such threads are
often called native or kernel-level threads.)

Because the number of threads that can be executed at any given instant is limited by the number of processors in
the computer, the operating system will rapidly switch from thread to thread, giving each thread a small window of
time in which to run. This is known as preemptive threading, because the operating system can suspend a thread’s
execution at any point in order to let another thread run. (A cooperative model, on the other hand, requires a thread
to explicitly suspend its own execution in order to let other threads run.) Swapping one thread out and another in is
referred to as a context switch.

System Threads versus User Threads

It’s useful to distinguish between system threads and user threads. A system thread is created and managed by the
system. The first (main) thread of an application is a system thread, and the application often exits when the first
thread terminates. User threads are explicitly created by the application to do tasks that cannot or should not be done
by the main thread.

Applications that display user interfaces must be particularly careful with how they use threads. The main thread in
such an application is usually called the event thread, because it waits for and delivers events (such as mouse clicks
and key presses) to the application for processing. Generally speaking, causing the event thread to block for any
length of time is considered bad programming practice, because it leads to (at best) an unresponsive application or
(at worst) a frozen computer. Applications avoid these issues by creating threads to handle time-consuming
operations, especially those involving network access.

Monitors and Semaphores

In order to avoid data corruption and other problems, applications must control how threads interact with shared
resources, referred to as thread synchronization. The two fundamental thread synchronization constructs are
monitors and semaphores. Which you use depends on what your system or language supports.

A monitor is a set of routines that are protected by a mutual exclusion lock. A thread cannot execute any of the
routines in the monitor until it acquires the lock, which means that only one thread at a time can execute within the
monitor; all other threads must wait for the currently executing thread to give up control of the lock. A thread can
suspend itself in the monitor and wait for an event to occur, in which case another thread is given the chance to
enter the monitor. At some point the suspended thread is notified that the event has occurred, allowing it to awake
and reacquire the lock at the earliest possible opportunity.

A semaphore is a simpler construct, just a lock that protects a shared resource. Before using a shared resource, the
application must acquire the lock. Any other thread that tries to use the resource is blocked until the owning thread
releases the lock, at which point one of the waiting threads (if any) acquires the lock and is unblocked. This is the
most basic kind of semaphore, a mutual exclusion, or mutex, semaphore. There are other semaphore types, such as
counting semaphores (which let a maximum of n threads access a resource at any given time) and event semaphores
(which notify one or all waiting threads that an event has occurred), but they all work in much the same way.

Monitors and semaphores are equivalent, but monitors are simpler to use because they handle all details of lock
acquisition and release. When using semaphores, an application must be very careful to release any locks a thread
has acquired when it terminates; otherwise, no other thread that needs the shared resource can proceed. In addition,
every routine that accesses the shared resource must explicitly acquire a lock before using the resource, something
that is easily forgotten when coding. Monitors always and automatically acquire the necessary locks.
Most systems provide a way for the thread to timeout if it can’t acquire a resource within a certain amount of time,
allowing the thread to report an error and/or try again later.

There is a cost to using monitors and semaphores, of course, because extra time is required to acquire the necessary
locks whenever a shared resource is accessed.

Deadlocks

As soon as two or more threads contend for a shared resource, the possibility of deadlock occurs, which happens
when two threads each have a lock on a resource needed by the other thread. Because neither thread can continue
running, the threads are said to be deadlocked. Typically, the only way to resolve a deadlock is to forcefully
terminate one of the threads, which is not a great solution. The best solution is deadlock avoidance - using careful
programming to ensure that deadlock can never occur.

Deadlock Conditions

In order for a deadlock to occur, you must have the following four conditions met:
1. Mutual Exclusion: Only one process can use a resource at a given time.
2. Hold and Wait: Processes already holding a resource can request new ones.
3. No Preemption: One process cannot forcibly remove another process’ resource.
4. Circular Wait: Two or more processes form a circular chain where each process is waiting on another resource in
the chain.
Deadlock Prevention

Deadlock prevention essentially entails removing one of the above conditions, but many of these conditions are
difficult to satisfy.For instance, removing #1 is difficult because many resources can only be used by one process at
a time (printers, etc).Most deadlock prevention algorithms focus on avoiding condition #4: circular wait.

Virtual memory is a computer system technique which gives an application program the impression that it has
contiguous working memory (an address space), while in fact it may be physically fragmented and may even
overflow on to disk storage. Systems that use this technique make programming of large applications easier and use
real physical memory (e.g.RAM) more efficiently than those without virtual memory.

Page Fault: A page is a fixed-length block of memory that is used as a unit of transfer between physical memory
and external storage like a disk, and a page fault is an interrupt (or exception) to the software raised by the
hardware, when a program accesses a page that is mapped in address space, but not loaded in physical memory.

Thrash is the term used to describe a degenerate situation on a computer where increasing resources are used to do a
decreasing amount of work.In this situation the system is said to be thrashing.Usually it refers to two or more
processes accessing a shared resource repeatedly such that serious system performance degradation occurs because
the system is spending a disproportionate amount of time just accessing the shared resource.Resource access time
may generally be considered as wasted, since it does not contribute to the advancement of any process.In modern
computers, thrashing may occur in the paging system (if there is not ‘sufficient’ physical memory or the disk access
time is overly long), or in the communications system (especially in conflicts over internal bus access), etc.

Virtual Methods

Important  Describe what virtual methods are and why they are useful.

A virtual method is a method whose implementation is determined at run time based on the actual type (class) of the
invoking object. Nonstatic Java methods are always virtual, so Java programmers may have trouble answering this
one; but in C# and C++, methods are only virtual when declared with the virtual keyword - nonvirtual methods are
the default.

Virtual methods are used for polymorphism. Consider the following three C++ classes:

class A {
public:
void print() { cout << "A"; }
}

class B : A {
public:
void print() { cout << "B"; }
}

class C : B {
public:
void print() { cout << "C"; }
}

Because print is declared as nonvirtual, the method that is invoked depends on the type used at compile time:

A *a = new A();
B *b = new B();
C *c = new C();

a->print(); // "A"
b->print(); // "B"
c->print(); // "C"
((B *)c)->print(); // "B"
((A *)c)->print(); // "A"
((A *)b)->print(); // "A"

Now redeclare print as virtual:

class A {
public:
virtual void print() { cout << "A"; }
}

class B : A {
public:
virtual void print() { cout << "B"; }
}
class C : B {
public:
virtual void print() { cout << "C"; }
}

Now the run-time type of the object determines the method invoked:

A *a = new A();
B *b = new B();
C *c = new C();

a->print(); // "A"
b->print(); // "B"
c->print(); // "C"
((B *)c)->print(); // "C"
((A *)c)->print(); // "C"
((A *)b)->print(); // "B"

A C++ version of the Shape class defined at the beginning of the chapter would need to declare the draw method as
virtual in order for the paintShapes method - which would only have references to Shape instances - to work.

Once you explain what virtual methods are and why they’re useful, talk about their advantages and disadvantages.
The primary advantage was just described: the run-time method selection. Virtual methods are also used to declare
abstract methods. The disadvantages are that it takes longer to invoke a virtual method (at a minimum, one lookup
needs to be done in a table to find the right method - you can’t jump directly to the method as you can with
nonvirtuals) and that extra memory is required to store the information needed for the lookup.

Multiple Inheritance

Important  Why do C# and Java disallow the multiple inheritance of classes?

In C++ it’s legal for a class to inherit (directly or indirectly) from more than one class, which is referred to as
multiple inheritance. C# and Java, however, limit classes to single inheritance - each class inherits from a single
parent class.

Multiple inheritance is a useful way to create classes that combine aspects of two disparate class hierarchies,
something that often happens when using different class frameworks within a single application. If two frameworks
define their own base classes for exceptions, for example, you can use multiple inheritance to create exception
classes that can be used with either framework.

The problem with multiple inheritance is that it can lead to ambiguity. The classic example is when a class inherits
from two other classes, each of which inherits from the same class:

class A {
protected:
bool flag;
};

class B : public A {};

class C : public A {};

class D : public B, public C {


public:
void setFlag( bool nflag ){
flag = nflag; // ambiguous
}
};

In this example, the flag data member is defined by class A. But class D descends from class B and class C, which
both derive from A, so in essence two copies of flag are available because there are two instances of A in D’s class
hierarchy. Which one do you want to set? The compiler will complain that the reference to flag in D is ambiguous.
One fix is to explicitly disambiguate the reference:

B::flag = nflag;

Another fix is to declare B and C as virtual base classes, which means that only one copy of A will exist in the
hierarchy, eliminating any ambiguity.

There are other complexities with multiple inheritance, such as the order in which the base classes are initialized
when a derived object is constructed, or the way members can be inadvertently hidden from derived classes.
Because of these complexities, some languages restrict themselves to the much simpler single inheritance model.
On the other hand, single inheritance is also very restrictive, because only classes with a common ancestor can share
behaviors. Interfaces mitigate this restriction somewhat by allowing classes in different hierarchies to expose
common interfaces even if they’re not implemented by sharing code.

Compare and contrast a hash table vs.an STL map.How is a hash table implemented? If the number of inputs is
small, what data structure options can be used instead of a hash table?

In a hash table, a value is stored by applying hash function on a key. Thus, values are not stored in a hash table in
sorted order. Additionally, since hash tables use the key to find the index that will store the value, an insert/lookup
can be done in amortised O(1) time (assuming only a few collisions in the hash table).One must also handle
potential collisions in a hash table.
In an STL map, insertion of key/value pair is in sorted order of key. It uses a tree to store values, which is why an
O(log N) insert/lookup is required. There is also no need to handle collisions. An STL map works well for things
like:
»»find min element
»»find max element
»»print elements in sorted order
»»find the exact element or, if the element is not found, find the next smallest number
How is a hash table implemented?
1. A good hash function is required (e.g.: operation % prime number) to ensure that the hash values are uniformly
distributed.
2. A collision resolving method is also needed: chaining (good for dense table entries), probing (good for sparse
table entries), etc.
3. Implement methods to dynamically increase or decrease the hash table size on a given criterion.For example,
when the [number of elements] by [table size] ratio is greater than the fixed threshold, increase the hash table
size by creating a new hash table and transfer the entries from the old table to the new table by computing the
index using new hash function.
What can be used instead of a hash table, if the number of inputs is small?
You can use an STL map. Although this takes O(log n) time, since the number of inputs is small, this time is
negligible.
What is the difference between deep copy and shallow copy? Explain how you would use each.
struct Test {
char * ptr;
};
void shallow_copy(Test & src, Test & dest) {
dest.ptr = src.ptr;
}
void deep_copy(Test & src, Test & dest) {
dest.ptr = malloc(strlen(src.ptr) + 1);
memcpy(dest.ptr, src.ptr);
}

Note that shallow_copy may cause a lot of programming run-time errors, especially with the creation and deletion
of objects.Shallow copy should be used very carefully and only when a programmer really understands what he
wants to do.In most cases shallow copy is used when there is a need to pass information about a complex structure
without actual duplication of data (e.g., call by reference).One must also be careful with destruction of shallow
copy.
In real life, shallow copy is rarely used.There is an important programming concept called “smart pointer” that, in
some sense, is an enhancement of the shallow copy concept.
Deep copy should be used in most cases, especially when the size of the copied structure is small.
What is the significance of the keyword “volatile” in C?
Volatile informs the compiler that the value of the variable can change from the outside, without any update done
by the code.
Declaring a simple volatile variable:
volatile int x;
int volatile x;
Declaring a pointer variable for a volatile memory (only the pointer address is volatile):
volatile int * x;
int volatile * x;
Declaring a volatile pointer variable for a non-volatile memory (only memory contained is volatile):
int * volatile x;
Declaring a volatile variable pointer for a volatile memory (both pointer address and memory contained are
volatile):
volatile int * volatile x;
int volatile * volatile x;
Volatile variables are not optimized, but this can actually be useful.
Volatile variables are also useful when multi-threaded programs have global variables and any thread can modify
these shared variables.Of course, we don’t want optimization on them.
What is name hiding in C++?
In C++, when you have a class with an overloaded method, and you then extend and override that method, you must
override all of the overloaded methods.
For example:
1 class FirstClass {
2 public:
3 virtual void MethodA (int);
4 virtual void MethodA (int, int);
5 };
6 void FirstClass::MethodA (int i) {
7 std::cout << “ONE!!\n”;
8}
9 void FirstClass::MethodA (int i, int j) {
10 std::cout << “TWO!!\n”;
11 }
This is a simple class with two methods (or one overloaded method).If you want to override the one-parameter
version, you can do the following:
1 class SecondClass : public FirstClass {
2 public:
3 void MethodA (int);
4 };
5 void SecondClass::MethodA (int i) {
6 std::cout << “THREE!!\n”;
7}
8 void main () {
9 SecondClass a;
10 a.MethodA (1);
11 a.MethodA (1, 1);
12 }
However, the second call won’t work, since the two-parameter Method A is not visible. That is name hiding.
Why does a destructor in base class need to be declared virtual?
Calling a method with an object pointer always invokes:
»»the most derived class function, if a method is virtual.
»»the function implementation corresponding to the object pointer type (used to call the method), if a method is
non-virtual.
A virtual destructor works in the same way.A destructor gets called when an object goes out of scope or when we
call delete on an object pointer.
When any derived class object goes out of scope, the destructor of that derived class gets called first.It then calls its
parent class destructor so memory allocated to the object is properly released.
But, if we call delete on a base pointer which points to a derived class object, the base class destructor gets called
first (for non-virtual function).For example:
1 class Base {
2 public:
3 Base() { cout << “Base Constructor “ << endl; }
4 ~Base() { cout << “Base Destructor “ << endl; } /* see below */
5 };
6 class Derived: public Base {
7 public:
8 Derived() { cout << ”Derived Constructor “ << endl; }
9 ~Derived() { cout << ”Derived Destructor “ << endl; }
10 };
11 void main() {
12 Base *p = new Derived();
13 delete p;
14 }
Output:
Base Constructor
Derived Constructor
Base Destructor
If we declare the base class destructor as virtual, this makes all the derived class destructors virtual as well.
If we replace the above destructor with:
1 virtual ~Base() {
2 cout << “Base Destructor” << endl;
3}
Then the output becomes:
Base Constructor
Derived Constructor
Derived Destructor
Base Destructor
So we should use virtual destructors if we call delete on a base class pointer which points to a derived class.

Explain what happens, step by step, after you type a URL into a browser.Use as much detail as possible.

1. Browser contacts the DNS server to find the IP address of URL.


2. DNS returns back the IP address of the site.
3. Browser opens TCP connection to the web server at port 80.
4. Browser fetches the html code of the page requested.
5. Browser renders the HTML in the display window.
6. Browser terminates the connection when window is closed.
One of the most interesting steps is Step 1 and 2 - “Domain Name Resolution.” The web ad dresses we type are
nothing but an alias to an IP address in human readable form.Mapping of domain names and their associated
Internet Protocol (IP) addresses is managed by the Domain Name System (DNS), which is a distributed but
hierarchical entity.
Each domain name server is divided into zones.A single server may only be responsible for knowing the host names
and IP addresses for a small subset of a zone, but DNS servers can work together to map all domain names to their
IP addresses.That means if one domain name server is unable to find the IP addresses of a requested domain then it
requests the information from other domain name servers.
BGP: Border Gateway Protocol
BGP is the core routing protocol of the Internet.“When a BGP router first comes up on the Internet, either for the
first time or after being turned off, it establishes connections with the other BGP routers with which it directly
communicates.The first thing it does is download the entire routing table of each neighboring router.After that it
only exchanges much shorter update messages with other routers.
BGP routers send and receive update messages to indicate a change in the preferred path to reach a computer with a
given IP address.If the router decides to update its own routing tables because this new path is better, then it will
subsequently propagate this information to all of the other neighboring BGP routers to which it is connected, and
they will in turn decide whether to update their own tables and propagate the information further.”
RIP: Routing Information Protocol
“RIP provides the standard IGP protocol for local area networks, and provides great network stability, guaranteeing
that if one network connection goes down the network can quickly adapt to send packets through another
connection.“
“What makes RIP work is a routing database that stores information on the fastest route from computer to
computer, an update process that enables each router to tell other routers which route is the fastest from its point of
view, and an update algorithm that enables each router to update its database with the fastest route communicated
from neighboring routers.”
OSPF: Open Shortest Path First
“Open Shortest Path First (OSPF) is a particularly efficient IGP routing protocol that is faster than RIP, but also
more complex.”
The main difference between OSPF and RIP is that RIP only keeps track of the closest router for each destination
address, while OSPF keeps track of a complete topological database of all connections in the local network.The
OSPF algorithm works as described below.
»Startup.When a router is turned on it sends Hello packets to all of its neighbors, re-ceives their Hello packets in
return, and establishes routing connections by synchroniz-ing databases with adjacent routers that agree to
synchronize.
»»Update.At regular intervals each router sends an update message called its “link state” describing its routing
database to all the other routers, so that all routers have the same description of the topology of the local
network.
»»Shortest path tree.Each router then calculates a mathematical data structure called a “shortest path tree” that
describes the shortest path to each destination address and therefore indicates the closest router to send to for each
communication; in other words -- “open shortest path first”.
What are the differences between TCP and UDP? Explain how TCP handles reliable delivery (explain ACK
mechanism), flow control (explain TCP sender’s / receiver’s window) and congestion control.
TCP (Transmission Control Protocol): TCP is a connection-oriented protocol.A connection can be made from client
to server, and from then on any data can be sent along that connection.
»»Reliable - when you send a message along a TCP socket, you know it will get there unless the connection fails
completely.If it gets lost along the way, the server will re-request the lost part.This means complete integrity;
data will not get corrupted.
»»Ordered - if you send two messages along a connection, one after the other, you know the first message will get
there first.You don’t have to worry about data arriving in the wrong order.
»»Heavyweight - when the low level parts of the TCP “stream” arrive in the wrong order, re send requests have to
be sent.All the out of sequence parts must be put back together, which requires a bit of work.
UDP(User Datagram Protocol): UDP is connectionless protocol.With UDP you send messages (packets) across the
network in chunks.
»»Unreliable - When you send a message, you don’t know if it’ll get there; it could get lost on the way.
»»Not ordered - If you send two messages out, you don’t know what order they’ll arrive in.
»»Lightweight - No ordering of messages, no tracking connections, etc.It’s just fire and forget! This means it’s a lot
quicker, and the network card / OS have to do very little work to translate the data back from the packets.
Explain how TCP handles reliable delivery (explain ACK mechanism), flow control (explain TCP
sender’s/receiver’s window).
For each TCP packet, the receiver of a packet must acknowledge that the packet is received.If there is no
acknowledgement, the packet is sent again.These guarantee that every single packet is delivered.ACK is a packet
used in TCP to acknowledge receipt of a packet.A TCP window is the amount of outstanding (unacknowledged by
the recipient) data a sender can send on a particular connection before it gets an acknowledgment back from the
receiver that it has gotten some of it.
For example, if a pair of hosts are talking over a TCP connection that has a TCP window with a size of 64 KB, the
sender can only send 64 KB of data and then it must wait for an acknowledgment from the receiver that some or all
of the data has been received.If the receiver acknowledges that all the data has been received, then the sender is free
to send another 64 KB.If the sender gets back an acknowledgment from the receiver that it received the first 32 KB
(which could happen if the second 32 KB was still in transit or it could happen if the second 32 KB got lost), then
the sender can only send another additional 32 KB since it can’t have more than 64 KB of unacknowledged data
outstanding (the second 32 KB of data plus the third).
Congestion Control
The TCP uses a network congestion avoidance algorithm that includes various aspects of an additive-increase-
multiplicative-decrease scheme, with other schemes such as slow-start in order to achieve congestion avoidance.
There are different algorithms to solve the problem; Tahoe and Reno are the most well known.To avoid congestion
collapse, TCP uses a multi-faceted congestion control strategy.For each connection, TCP maintains a congestion
window, limiting the total number of unacknowledged packets that may be in transit end-to-end.This is somewhat
analogous to TCP’s sliding window used for flow control.TCP uses a mechanism called slow start to increase the
congestion window after a connection is initialized and after a timeout.It starts with a window of two times the
maximum segment size (MSS).Although the initial rate is low, the rate of increase is very rapid: for every packet
acknowledged, the congestion window increases by 1 MSS so that for every round trip time (RTT), the congestion
window has doubled.When the congestion window exceeds a threshold ssthresh the algorithm enters a new state,
called congestion avoidance.In some implementations (i.e., Linux), the initial ssthresh is large, and so the first slow
start usually ends after a loss.However, ssthresh is updated at the end of each slow start, and will often affect
subsequent slow starts triggered by timeouts.
What’s the difference between a thread and a process?
Processes and threads are related to each other but are fundamentally different.
A process can be thought of as an instance of a program in execution.Each process is an independent entity to which
system resources (CPU time, memory, etc.) are allocated and each process is executed in a separate address
space.One process cannot access the variables and data structures of another process.If you wish to access another
process’ resources, inter-process communications have to be used such as pipes, files, sockets etc.
A thread uses the same stack space of a process.A process can have multiple threads.A key difference between
processes and threads is that multiple threads share parts of their state.Typically, one allows multiple threads to read
and write the same memory (no processes can directly access the memory of another process).However, each thread
still has its own registers and its own stack, but other threads can read and write the stack memory.
A thread is a particular execution path of a process; when one thread modifies a process resource, the change is
immediately visible to sibling threads.
--------------------------------------------------------------
Process is a program in execution where as thread is a seperate path of execution in a program. A process can have
multiple threads.

The major difference between threads and processes is

1.Threads share the address space of the process that created it; processes have their own address.

2.Threads have direct access to the data segment of its process; processes have their own copy of the data segment
of the parent process.

3.Threads can directly communicate with other threads of its process; processes must use interprocess
communication to communicate with sibling processes.

4.Threads have almost no overhead; processes have considerable overhead.

5.New threads are easily created; new processes require duplication of the parent process.

6.Threads can exercise considerable control over threads of the same process; processes can only exercise control
over child processes.

7.Changes to the main thread (cancellation, priority change, etc.) may affect the behavior of the other threads
of the process; changes to the parent process does not affect child processes.

--------------------------------

Overriding is the concept of runtime polymorphism, while,

Overloading is the concept of compile time polymorphism...

The rules for overriding a method are as follows:

The argument list must exactly match that of the overridden method.

The return type must exactly match that of the overridden method.

The access level must not be more restrictive than that of the overridden method.

The access level can be less restrictive than that of the overridden method.

The overriding method must not throw new or broader checked exceptions than those declared by the overridden
method.

The overriding method can throw narrower or fewer exceptions. Just because an overridden method “takes risks”
doesn’t mean that the overriding subclass’ exception takes the same risks. Bottom line: An overriding method
doesn’t have to declare any exceptions that it will never throw, regardless of what the overridden method declares.

You cannot override a method marked final.

If a method can’t be inherited, you cannot override it.

The rules for Overloaded method are as follows:

Overloaded methods must change the argument list.

Overloaded methods can change the return type.

Overloaded methods can change the access modifier.

Overloaded methods can declare new or broader checked exceptions.

A method can be overloaded in the same class or in a subclass.

--------------------------------
1) arrays - I'm talking about C-language and Java-language arrays: fixed-sized, indexed, contiguous structures
whose elements are all of the same type, and whose elements can be accessed in constant time given their indices.

2) vectors - also known as "growable arrays" or ArrayLists. Need to know that they're objects that are backed by a
fixed-size array, and that they resize themselves as necessary.

3) linked lists - lists made of nodes that contain a data item and a pointer/reference to the next (and possibly
previous) node.

4) hashtables - amortized constant-time access data structures that map keys to values, and are backed by a real
array in memory, with some form of collision handling for values that hash to the same location.

5) trees - data structures that consist of nodes with optional data elements and one or more child pointers/references,
and possibly parent pointers, representing a heirarchical or ordered set of data elements.

6) graphs - data structures that represent arbitrary relationships between members of any data set, represented as
networks of nodes and edges.

1) What are some really common data structures, e.g. in java.util?

2) When would you use a linked list vs. a vector?

3) Can you implement a Map with a tree? What about with a list?

4) How do you print out the nodes of a tree in level-order (i.e. first level, then 2nd level, then 3rd level, etc.)

5) What's the worst-case insertion performance of a hashtable? Of a binary tree?

6) What are some options for implementing a priority queue?

What is a balanced tree

C++ ( what is virtual function ? what happens if an error occurs in constructor or destructor. Discussion on error
handling, templates, unique features of C++. What is different in C++, ( compare with unix).

Declare a void pointer : ANS. void *ptr;


What is a volatile variable?
What is the scope of a static function in C ?
What is the difference between "malloc" and "calloc"?

Describe the file system layout in the UNIX OS


ANS. describe boot block, super block, inodes and data layout

In UNIX, are the files allocated contiguous blocks of data

ANS. no, they might be fragmented

How is the fragmented data kept track of

ANS. Describe the direct blocks and indirect blocks in UNIX file system

Networks and Security

1. How do you use RSA for both authentication and secrecy?

2. What is ARP and how does it work?

3. What's the difference between a switch and a router?

4. Name some routing protocols? (RIP,OSPF etc..)

5. How do you do authentication with message digest(MD5)? (Usually MD is used for finding tampering of data)

6. How do you implement a packet filter that distinguishes following cases and selects first case and rejects second
case.

i) A host inside the corporate n/w makes a ftp request to outside host and the outside host sends reply.

ii) A host outside the network sends a ftp request to host inside. for the packet filter in both cases the source and
destination fields will look the same.

7. How does traceroute work? Now how does traceroute make sure that the packet follows the same path that a
previous (with ttl - 1) probe packet went in?

8. Explain Kerberos Protocol ?

9. What are digital signatures and smart cards?

10. Difference between discretionary access control and mandatory access control?

You might also like