Professional Documents
Culture Documents
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:
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:
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;
}
Find and fix the bugs in the following C/C++ function that is supposed to remove the head element from a
singly-linked list:
Ans:
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.
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
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.
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;
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;
}
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.
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.
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
// 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;
}
A deque is a data structure consisting of a list of items, on which the following operations are possible:
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.
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.
};
//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 为最后一个元素的位置.)
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;}
private:
int front, rear;
int maxSize;
vector<Object> elements ;
};
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.
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:
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;
delete tmp;
Suppose that a singly linked list is implemented with both a header and a tail node. Describe O(1) algorithms to
(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.
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.
// 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 );
}
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. 见手写代码
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:
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));
}
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
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 */
}
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
#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)
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?
#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
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.
Write an efficient function in C# that deletes characters from a string. Use the prototype
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.
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.
length = strlen(str);
/* Reverse entire string */
reverseString(str, start, length - 1);
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.
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;
}
if( isNeg )
num *= -1;
return num;
}
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
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.
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
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);
}
}
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.
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.
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"
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
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;
};
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.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.
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.
--------------------------------
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.
--------------------------------
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.
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.)
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).
ANS. Describe the direct blocks and indirect blocks in UNIX file system
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?
10. Difference between discretionary access control and mandatory access control?