Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 19

Lesson 1

Interview Question
1.Why do we need pointers?

To use “Dynamic Memory Allocation/DMA”. Because new/ malloc () function return address
and that address has to be stored in a variable so that we can access dynamic memory
further. And that variable has to be a pointer. So pointer is required to store addresses.

Int n=5; int*x=new int[n]; or int*x=(int*)malloc(n*sizeof(int));

2.Can you explain address?

“Address” typically refers to a location in RAM(a huge pool of memory bytes).It represents
the location where a value or data is stored in memory. Memory is a linear sequence of
bytes.

3.What is the difference between a Pointer and a non-Pointer(normal variable)?

They are different from each other in these three aspects.

Declaration, Storage, Accessing data.

Int a=10; a- 10 >

1000 address

Pointer

Accessing Mechanism

Non pointer-by default/value, with & address

Pointer- by default/ address, with * value


Int a,*b;

Cout<<sizeof(a); // 4

Cout<<sizeof(b); // 8 ?

4.What is the significance of data types(int,char…) with respect to pointers?

Data types decide the step size of Pointer. Instead, the step size of a pointer is determined
by the size of the data type the pointer is pointing to. In most programming languages, the
size of a data type is fixed, and it determines how many bytes of memory are needed to
store a value of that data type. The step size of a pointer is the number of bytes it moves
when performing pointer arithmetic. Pointer arithmetic is the manipulation of pointers
using addition and subtraction operations to navigate through memory and access data
stored in memory locations. The size of the step is determined by the data type that the
pointer is pointing to. When you increment or decrement a pointer, the pointer will move
forward or backward in memory by the number of bytes corresponding to the size of the
data type it points to. For example, if you have an int* pointer and you increment it by 1,
the pointer will move forward by the size of an integer, which is usually 4 bytes. If you have
a char* pointer and you increment it by 1, the pointer will move forward by the size of a
character, typically 1 byte.

int* a = new int;


cout << a << endl; // 000001AC5650AEF0
a++;
cout << a; // 000001AC5650AEF4

DMA
As you know we cant declare int a[n] ,bc value of n will be available at run time(cin), but the
allocation of memory location is done at compile time. So we need such memory that can
be allocated at run time. we can provide memory at run time by new and malloc()
functions.

4.How will you classify linear and non linear data structures from each other?

Linear are the data structures in which each element has exactly one
predecessor(նախորդ)

And exactly one successor(հաջորդ)․ except for the first and last elements-array, stack, queue,
link list

Non Linear are the data structures in which each element has multiple
predecessor(նախորդ) and multiple successor(հաջորդ)․-tree, graph

Lesson 2
Linked List FAQ(Frequently Asked Questions)

1.Why do we need LINK LIST?

Data structure-memory arrangement, set of operations, algorithms(programs).

If array was already available then why do we need link list?

Array needs contiguous memory. The drawback of arrays is EXTERNAL FRAGMENTATION(a


type of memory wastage(վատնում)․ Arrays may result in memory wastage when their size is
larger than the number of elements they currently hold. If you allocate an array of size 10 but only
use 5 elements, the remaining 5 elements are unused, leading to wasted memory.

Solution to this problem is link list(a linear but non-continuous data structure).Its a collection of
memory nodes where each node contains two fields. DATA , NEXT(pointer,address).

Data field contains the value of the current node, and a next or an address field contains address of
the next node in the link list. Data structure-memory arrangement, set of operations,
algorithms(programs).

Operations-Traversal(it’s the process of accessing each element exactly once),Insertion(the


process of inserting a node into a linked list), Deletion(the process of deleting a node from
a linked list)

Difference between singly linked list and circular linked list


Single Linked List:
 In a single linked list, each node has a data element and a single pointer (usually
called next or nextNode) that points to the next node in the list.
 The last node of a single linked list points to nullptr (null in C++), indicating the
end of the list.
 Traversing a single linked list involves moving from the head (first node) to
subsequent nodes through the next pointers until the end is reached

Circular Linked List:


 In a circular linked list, each node has a data element and a pointer (usually called
next or nextNode) that points to the next node in the list.
 The last node of a circular linked list points back to the first node (head) of the list,
forming a circular loop.
 Traversing a circular linked list involves moving from the head (first node) to
subsequent nodes through the next pointers, and when you reach the last node,
you loop back to the head to continue traversing.
10->20->30->nullptr 10->20->30->back to head

Doubly linked list contains three field. First field stores the address of the previous node , the
second field stores the value of the current node and the third field stores the address of the next
node.

Doubly Circular Linked List:


 In a doubly circular linked list, each node has a data element, a pointer that points to
the next node (usually called next or nextNode), and another pointer that points to
the previous node (usually called prev or prevNode).
 The last node points back to the head, and the head's previous node points to the
last node, forming a circular loop in both directions.
 Traversing a doubly circular linked list can be done in both forward and backward
directions by following the next and prev pointers, respectively.

(back to last) < -10 < -> 20 < -> 30 < -> 40 -> (back to first)
https://www.geeksforgeeks.org/learn-data-structures-and-algorithms-dsa-tutorial/?ref=lbp
LESSON 3 / THREE

1.How are doubly link list and tree different?

A tree data structure is a hierarchical structure that is used to represent and organize
data in a way that is easy to navigate (traverse) and search. It is a collection of nodes
that are connected by edges and has a hierarchical relationship between the nodes.
The topmost node of the tree is called the root, and the nodes below it are called the
child nodes. Each node can have multiple child nodes, and these child nodes can also
have their own child nodes, forming a recursive structure
Basic Terminologies In Tree Data Structure:
Node: A node is a fundamental element of a tree. It represents a single element in the tree. Each
node can have zero or more child nodes, except for the root node, which is the topmost node and
has no parent

Root: The root is the topmost node of the tree. It is the starting point of the tree and has no parent.
In a tree, there can be only one root.
Internal node: A node with at least one child is called Internal Node
Subtree: Any node of the tree along with its descendant.
Leaf Node or External Node: The nodes which do not have any child nodes
Siblings: Siblings are nodes that are at the same level in the tree and have the same parent.
Height of the Tree: The height of a tree is the length of the longest path from the root of
the tree to a leaf node of the tree.
Height of a node: The height of a node is the length of the longest path from the node to
a leaf node of the tree.
Level of the node: The length of the path (The count of edges on the path from the root
node to that node.) from the root to that node.
Number of edges: An edge can be defined as the connection between two nodes. If a
tree has N nodes then it will have (N-1) edges.
Descendant: Any successor node on the path from the leaf node to that node.
Ancestor of a Node: Any predecessor nodes on the path of the root to that node are
called Ancestors of that node.
The degree of a node: The number of children that a particular node has. The degree of a
leaf node must be 0. The degree of a tree is the maximum degree of a node among all
the nodes in the tree.
Depth of a node: the distance from the root to that node.

Terminology summary
 Root is the topmost node of the tree
 Edge is the link between two nodes
 Child is a node that has a parent node
 Parent is a node that has an edge to a child node
 Leaf is a node that does not have a child node in the tree
 Height is the length of the longest path to a leaf
 Depth is the length of the path to its root

Why Tree is considered a non-linear data structure ?


The data in a tree are not stored in a sequential manner i.e., they are not stored linearly.
Instead, they are arranged on multiple levels or we can say it is a hierarchical structure.

Binary Tree
Each node has at most 2 (0,1,2) child nodes. We name them the left and the right child node.
Each node of a Binary tree contains the following parts.
1.Data 2.Pointer to the left child 3.Pointer to the right child
Binary Tree-Each node can have either 0,1 or 2 child.
Strict Binary Tree-Each node can have either 0 or 2 child.
Complete Binary Tree-A tree has to be strict binary tree + all leaf nodes should be at the same
level.
Binary Search Tree ( BST ) is an extension of Binary Tree with added constraints. In BST the
value of the left child node is less than the value of the parent node and the value of the right
child node is always larger than the value of the parent node. This property makes finding a
node very fast cause we have a pretty good idea of where it would be, at each node we can
decide accurately whether the value will be in the left or right node. Therefore, it is called a
Search Tree. Trees can be traversed in different ways.
1. Depth First Search ( DFS)
1.Preorder traversal -Root Left Right
2.Inorder traversal- Left Root Right
3.Postorder traversal- Left Right Root

struct Node {
int data;
Node* right;
Node* left;

};

Node* createNode(int data) {


Node* newNode = new Node();
newNode->data = data;
newNode->right = newNode->left = NULL;
return newNode;
}

void printPreOrder(Node* root) { //preOrder data left right


if (root == NULL) return;
cout << root->data << endl;
printTree(root->left);
printTree(root->right);}
//if in l case its null return to r, when in r case its null return } when both null return one step back

int main(){
//level 1
Node* root = createNode(1);
//level 2
root->left = createNode(2);
root->right = createNode(3);
//level 3
root->left->left = createNode(4);
root->left->right = createNode(5);
root->right->right = createNode(7);
root->right -> left = createNode(6);
//level 4
root->left->right->right = createNode(9);
root->right->right->left = createNode(15);

printPreOrder(root);
}

Interview Special

The core purpose to design tree was to reduce search tree complexity from O(n) to
O(log2n). But in all previous types of trees it wasn’t possible. So a new tree was created
called AVL (Balanced Binary Search Tree)Tree. Named after its inventors Adelson-Velsky
and Landis, is a self-balancing binary search tree. It gives always order of O(log2n).

AVL= BST+Balanced Balanced Factor=H(LS)-H(RS) OR H(RS)-H(LS)

B.F.=Absolute(H(LS)-H(RS))<=1(at any node it must at most 1)

The difference between heights of left and right subtrees cant be more than 1 for all nodes.

In this example its AVL, LS-RS=3-2=1

root

Unbalanced TREE

While maintaining this balanced factor few problems can happen. LC

L-L Problem-left subtree of left child is creating a problem. Problem arises

during insertion when a new node is added to the left subtree of the left
LS
child. Solution is L-L rotation, it simply rotates this branch. LC goes up and Root goes
down
LC

LS root

R-R problem- Problem arises during insertion when a new node is added to the right
subtree of the right child.
LESSON 4 / Programming with Tree Mon
FAQs in interviews…

1.You can be asked to write a code section to insert a node in an existing BST.

2.You can be given a code segment and will be asked what the code segment is

gonna perform.

3.You can be asked to write a code to delete(remove) a specific node from an

existing tree.

LESSON 5/ Time Complexity Calculation

Time computation of an algorithm defines the time taken ( in terms of size of input ) by an
algorithm to solve a problem.

Space computation is the total amount of memory consumed by the algorithm to solve a
problem except input parameters given to it.( add( int a,int b) except for a and b).

COA –Computer Organization and Architecture s

An algorithm contains different types of operations. It takes time during performance of


operations ( arithmetic, logical operations, comparisons, input/output statements, function
calls, memory declaration, assignments, looping statements). // negligible-չնչին
Comparison is the only operation that takes much significant time which worth your consideration.
Apart from comparison all other operations takes a constant amount of time which doesn’t worth
your consideration. Let’s say there are two algorithms A and B. A takes 100 operations ( other than
comparison) and B takes only one comparison than you should be clear for the fact that time taken
by algorithm B is more than A. Conclusively, only comparison operation ( >,<,=, !=) deserves your
attention while calculating time complexity (time taken) by an algorithm.

Minimum number of comparisons 2 sec, maximum 21 sec. Best Case is the situation when your
algorithm is taking minimum time to compute the result. Worst Case is 21 seconds, when its taking
maximum time. From 2 to 21 all these cases are Average Case. This example contains only 10 values
that’s why you can define the number of comparisons, but when you have n inputs it will be
n+n+1=2n+1. But the first for loop takes n amount of time, then the second if takes extra +1, then
it will be n+2n+1+1=3n+2. In general case we can have the formal computation a*n+b where a is
Multiplicative (բազմապատկիչ) Constant and b is Additive (հավելում) Constant, while
computing Time Complexity a and b will be neglected, therefore time complexity will be either O(n)
or Omega(n) or Theta(n).

Important Facts during Complexity Calculation

Always consider only higher order terms if you get a computation like n^2+n+k where n is the size
of input and k is constant than complexity will be n^2 only ( O(n^2) ). Remember to neglect
constants additive and multiplicative constants. If your program has a single loop, in that case
complexity is O(n). When your program( փրոգրամ) contains two loops nested with each other ( 2
nested loops), in that case time complexity of the program is O(n^2).
If your program contains 3 loops nested with each other it’s time complexity becomes n^3 and so
on

When the loop variable is growing by the factor of 2, we have order of log2 n time complexity.For
example

Void main(){ By the factor of 3 , then log 3n

Int i;

For(i=2;i<16;i=i*2){

Void main(){ I is decreasing, then time complexity is exponential 2^n

Int i;

For(i=2;i<16;i=i/2){

}
Asymptotic Notation

Big O Notation- (is used for) Upper Bound

Omega Notation-Lower bound / Ω

Theta Notation-Tight bound / θ

Big O Notation

The formal definition is as follows:

Let f(n) and g(n) be two positive functions. The function f(n)=O(g(n)) if there exist a
positive constant c and n₀ such that for all n>= n₀ the following inequality holds
f(n)<=c*g(n).

Example. F(n)=2n+4 ; 2n+4<= 12 n where n>=1(n₀), 12 is c and g(n)=n then f(n)=O(n)

2n+4<=2n+4n so it is 2n+4<=6n n>=1 then f(n)=O(n)

2n+4<=2n^2+4n^2 so it is 2n+4<=6n^2 then f(n)=O(n^2) but we cant say that


f(n)=O(log2n), because 2n+4<=6 log2n

So if f(n)=2n+4, then f(n)=O(n), O(n^2), O(n^3)…

Omega Notation

Let f(n) and g(n) be two positive functions. The function f(n)= Ω (g(n)) if there exist a
positive constant c and n₀ such that for all n>= n₀ the following inequality holds f(n) >=
c*g(n).

Example. F(n)=2n+4; 2n+4 >= 1n for all n>=1(n₀) , 1 is c and g(n)=n so f(n)= Ω(n)

2n+4 >= log2n so f(n)= Ω(log2n)

2n+4 >=2 so f(n)=Ω(2)

But f(n)=Ω(n^2) is false because 2n+4>=n^2


Theta Notation(Θ)

Let f(n) and g(n) be two positive functions. The function f(n)= Θ(g(n)) if there exist positive
constants c1,c2 and n₀ such that for all n>= n₀ the following inequality holds

C1*g(n) <= f(n) <= c2*g(n)

Example. F(n)=2n+4 ; 1*n <= 2n+4 <= 6*n ; c1=1, c2=6, g(n)=n so f(n)= Θ(n)

F(n)= Θ(log2n) or f(n)= Θ(n^2) are false.

It’s a MYTH that Worst case computation is related to big O notation and Best case to
Omega notation.

LESSON 6/ Merge Sort Technique

Divide and Conquer Process ( DAC ) - divide a big size problem into a small size problem.
Find the solutions of the small size problems and merge their solutions to create the
solution for the final problem. But how to find out whether the problem is big or small?
Whenever we have to perform a lot of operations in order to solve a particular problem in
that case the problem becomes big. For example 5! = 5*4*3*2*1 , in this case we have to
perform a lot of multiplication to find the solution , so it is big. Whenever no operations are
required to perform that problem is small, for example 1! = 1. When we have a sorting
problem, the operation will be the comparison.

The above array contains 6 elements together ,it has been broken down into the 6
different arrays where each array stores only one element. We can say that each array is
sorted cause we don’t have to perform any operation, there is just one element.
Merge sort is a recursive sorting algorithm which continuously divides an array into
subarrays(halves) until it can’t be further divided i.e., (that is,in other words) the array has
only one element left (an array with one element is always sorted ), sorts each subarray and
merges the sorted subarrays back together to form the final sorted array. It’s based on the
divide-and-conquer strategy.

To sort the array using merge sort includes two processes.

1.Splitting of array into two arrays on every step

2. To merge two sorted arrays into a big third array in such a way that the third array is
also supposed to be sorted.

First compare 1 with only 3, comparisons with 2,4,6 isn’t required, cause we already know
that that array has already been sorted. We don’t need to compare 1 with 5,7,8 as well. The
third array will have 1 as its element .Then i points to the index 1 / value 2, k also increases
but j stays the same. Next compare 2 with 3. Then if condition is true it is inserted into the
third array. Then compare 4 with 3 but the condition is false this time we need to pick up 3
and insert it into the array, in this case j increases. Every time it doesn’t matter you choose
element from L or R k must increase. This process is repeated many times. We do this
comparison until both arrays are available. Next we will compare 4 with 5 and insert 4. I and
k increases. Compare 6 with 5 ,cause it is false we insert 5 and j and k increases then 6 will
be inserted. I=4 so L array is finished first. Now make the remaining elements become the
part of the third array, simply copy them into the third one cause there is no need to do
comparison. Now we have to check two conditions separetly. If L array is still available we
will simply copy element from L to final one increasing I and k. The same for R.
Time Complexity
Space Complexity of Merge Sort is O(n), Time Complexity of Merge Sort is O(n*log2n) in
all three situations( best, average,worst).
In order to find out time complexity we have 2 functions, find out it for MergeSort()-x and
Merge()-y function, and then x+y will be the time complexity for the complete process. We
are performing merging ,we have n->n/2->n/4->n/8->…->1 levels
8-4-2-1 we have 3 levels, in every level we have on comparison( if(n<2) )
Drawbacks of Merge Sort:
 Space complexity: Merge sort requires additional memory to store the merged sub-
arrays during the sorting process.
 Not in-place: Merge sort is not an in-place sorting algorithm, which means it requires
additional memory to store the sorted data. This can be a disadvantage in applications
where memory usage is a concern.
 Not always optimal for small datasets: For small datasets, Merge sort has a higher
time complexity than some other sorting algorithms, such as insertion sort. This can
result in slower performance for very small datasets.

1. Divide Step: The input array is divided into two halves repeatedly until each subarray
contains a single element. This step takes O(log n) time, as the array is halved at each level
of recursion.
2. Merge Step: After the division, the merge step involves merging the sorted subarrays. The
merging process takes linear time O(n) because each element is compared and placed in
the correct position.
Merge() is used for merging two halves. MergeSort() recursively calls itself to divide the
array till size becomes one.
First call merge sort on the initial array. 1.ms(arr,0,4). Check the condition 0<4 so it’s true.
This condition is for checking whether we have arrived an subarray which consists of one
element it means that l=r l,r are both 0 in this case the condition will become false and
recursive nature will stop. Now we go into the function and calculate the midpoint. It will be
(4+0)/2=2. In step 2 we call merge sort again this is a recursive call and the first merge sort
call was over, now call the second one on the leftmost area ms(arr,0,2) with the new
midpoint. ms(arr,0,2) is given for the left side of the subarray. We divided 9,7,3 and 6,2 into
2 subarrays basically what we did is we are using the array we aren’t creating two separate
arrays. We just apply merge sort on a part of the same array. the first morge sort will go
into a pause state

Quick Sort
Best case is that the array is already sorted. Worst case is when array is reversely sorted,
it means the array in in descending order. For quick sort worst case doesn’t happen.
FAQs
1.Best, Average and Worst case time complexity of QS.
2.Explain partition procedure (բաժանման կարգը).
3.What is pivot element?
4.Why is QS most suitable practical sorting algorithm?
5. Why do most of the companies use QS for sorting data?
6. What is the difference between QS and RQS?
QS entirely depends on one element which is pivot, this can be any of the elements. There
are 2 versions of QS, one is Randomized QS, in which we can choose anyone of them at
every iteration, at every step we can change the pivot element, but the normal version we
fix the position and all the iteration the element remains pivot. Once the first pass is over
the pivot is supposed to be at its correct place. In that case all the elements coming before
the pivot are always less that the pivot, and all the elements after the pivot are always
greater than the pivot. That correct place is called the partition index.
The drawback of merge sort is the huge space complexity. QS works on 2 processes. One
is QS() and QS process will use another process which is partition process. Partition()
gives the partition index.
Choose the pivot and move it to the end of the array.1,a go from left to right and find the
first element which is greater than the pivot. 1,b go from right to left and find the first
element which is less than the pivot. 2 interchange their positions. When index of
itemFromLeft<index of itemFromRight 3.(j<i) 4.stop the procedure, 5.swap itemFromLeft
and pivot ( replace itemFromLeft with pivot).Pivot has to be replaced and that position is
called partition index. //If we use the elements twice it doesn’t matter.

Now apply the same process in the left part of the array. Then apply in the right part.

You might also like