By: Nasr Galal / @sniperadmin
An estimate of increasing the runtime of an algo as its input grows
It is used for measuring the efficiency of the algo
number of operations required for a particular algorithm
amount of memory required used by an algorithm
Complexity | Name | Example |
---|---|---|
Constant | Adding element in front of a linked list | |
Logarithmic | Searching an element in a sorted arr / linked list | |
Linear | Searching element in a unsorted array | |
Linear algorithmic | Merge sort algorithm | |
Quadratic | shortest path between 2 points in a graph | |
Cubic | Matrix Multiplication | |
Exponential | Naive solution for the nth Fibonacci problem | |
Factorial | Naive solution for travelling salesman problem |
function (n: number): number {
const a: number = n * n
console.log(a)
return a
}
let nums: object = {1,2,3,4,5,6}
for (let i: number = 0; i < nums.size(); i++) {
console.log(nums[i])
}
const binarySearch = function (nums, target) { // -- T(n)
let left = 0; // --- O(1)
let right = len(nums - 1) // --- O(1)
while (left <= right) { // --- O(n/2)
let mid = left + (right - left) / 2; // --- O(1)
if (nums[mid] == target) { // --- O(1)
return mid; // --- O(1)
}
else if (target > nums[mid]) { // --- O(1)
left = mid + 1; // --- O(1)
}
else { // --- O(1)
right = mid - 1; // --- O(1)
}
}
return -1; // --- O(1)
}
To calculate time complexity
1…….
2…….
3…….
4…….
k…….
After k division length =
TC =>
SC =>
It divides the problem into sub problems and then merges them in n time (Example => Merge & sort algorithm)
function mergeSort(nums, left, right) { // -- T(n)
if (right > left) { // -- O(1)
let mid = left + (right - left) / 2; // -- O(1)
mergeSort(nums, left, mid) // -- T(n/2)
mergeSort(nums, mid + 1, right) // -- T(n/2)
merge(nums, left, mid, right) // --O(n)
}
}
function merge() {// -- O(n)}
**Solving it: **
—-» eq.1
–> Divide n by 2
—-» eq.2
substitute eq.2 in eq.1
substitute n by n/4 in eq.1
—-» eq.3
Pattern will be:
let
after substituting in the discovered pattern
Pattern will be:
TC => O(n*log(n))
SC => O(n)
Bubble sort algorithm takes quadratic time complexity (loop nested inside a loop)
for (let i = 0; i < n; i++) { // o(n)
for (let j = 0; j < n; j++) { // o(n)
// statement here --- O(1)
}
} // -- total O(n^2)
Same principle of
for (let i = 0; i < n; i++) { // o(n)
for (let j = 0; j < n; j++) { // o(n)
// statement here --- O(1)
}
for (let k = 0; k < n; k++) { // o(n)
// statement here --- O(1)
}
} // -- total O(n^3)
Very slow algo as input grows up. If n = 100,000 .. then T(n) would be 21000000. (Example => brute forcing algorithm [Backtracking]) —
Slowest EVER!
for (let i = 0; i < 10; i++) { // -- O(n)
console.log(i) // -- O(1)
}
— Reject any constants —
TC => O(n)
SC => O(1) — This means that we don’t save any data structures in memory
for (let i = 0; i < 10; i += 2) { // -- O(n/2)
console.log(i) // -- O(1)
}
TC => O(n)
SC => O(1) — This means that we don’t save any data structures in memory
for (let i = 0; i < 10; i++) { // -- O(n)
for (let j = 0; j < 10; j++) { // --O(n)
console.log(i) // -- O(1)
}
}
i | j | Execution |
---|---|---|
0 | 0 | |
1 | 0 | 1 |
2 | 0,1 | 2 |
3 | 0,1,2 | 3 |
4 | 0,1,2,3 | 4 |
… | … | … |
n | 1….(n-1) | n |
Pattern will be:
TC =>
SC => O(1) — This means that we don’t save any data structures in memory
let p = 0; // -- O(n)
for (let i = 0; p <= n; i += p) { // -- O(root n)
Do something // -- O(1)
}
i | j |
---|---|
0 | |
1 | 0 + 1 |
2 | 0 + 1 + 2 |
3 | 0 + 1 + 2 + 3 |
4 | 0 + 1 + 2 + 3 + 4 |
… | … |
k | 1…. + k |
Taking the stop condition if p > n
Then,
Then,
Then,
TC => O(n)
SC => O(1) — This means that we don’t save any data structures in memory
for (let i = 0; i < n; i *=2) { // -- O(n)
console.log(i) // -- O(1)
}
i | j |
---|---|
0 | |
1 | |
2 | |
3 | |
4 | |
… | … |
k |
Taking the stop condition if i >= n
Then
Then
Then
TC =>
SC => O(1) — This means that we don’t save any data structures in memory
for (let i = 0; i <= 1; i /=2) { // -- O(n)
console.log(i) // -- O(1)
}
i | j |
---|---|
0 | |
1 | n/ |
2 | n/ |
3 | n/ |
4 | n/ |
… | n/ … |
k | n/ |
Taking the stop condition if i < 1
Then
Then
Then
Then
TC =>
SC => O(1) — This means that we don’t save any data structures in memory
for (let i = 0; i*i < n; i++) { // -- O(n)
console.log(i) // -- O(1)
}
This will stop if –
Then
Then
TC =>
SC => O(1) — This means that we don’t save any data structures in memory
Array is a data structure that contains a group of elements, each is identified by array index.
Each element is represented by a single subscript. Elements are stored in consecutive memory location
Each element is represented by two subscripts. The Two-dimensional array mxn
has m
Rows and n
columns and contains m*n
elements.
for instance 3X4
Array =>
let nums = [1, 2, 3, 4, 5, 6, 7, 8]
let’s assume it will render like this
. | . | . | . | . | . | . | . | . | . | . | . |
---|---|---|---|---|---|---|---|---|---|---|---|
. | . | . | . | . | . | . | . | . | . | . | . |
. | . | . | . | . | . | . | . | . | . | . | . |
. | . | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | . | . |
. | . | . | . | . | . | . | . | . | . | . | . |
. | . | . | . | . | . | . | . | . | . | . | . |
. | . | . | . | . | . | . | . | . | . | . | . |
The key is that each array value will be converted to a binary equivalent and then will be stored in a memory slot
Same concept
let Example: Array<number> // [1, 2, 3, 4]
let Example: Array<string> // ['a', 'b',...]
let example: Array<number> // [1, 2, 3, 4]
example = new Array()
example = new Array()
The Idea of Binary Search is to divide the array into 2 halfs and must be sorted.
if the target number is larger than the mid
value, then we discard the first half and vice versa.
this technique is very powerful.
const nums: Array<number> = new Array(1, 3, 5, 8, 12, 34, 35)
let get = function (arr, index) {
return arr[index];
}
let search = function (arr, target) {
let left: number = 0
let right: number = arr.length - 1;
while (left <= right) {
let mid: number = (left + right) / 2;
if (arr[mid] === target) return mid;
else if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
search(nums, 34)
TC =>
SC => O(1) — This means that we don’t save any data structures in memory
let x: Array<number> = new Array(2);
for (let i = 0; i < x.length; i++) {
x[i] = new Array(2);
}
x[0][0] = 1
x[0][1] = 2
x[1][0] = 3
x[1][1] = 4
This initialization costs only
It is a dynamic data structure where each element (node) is made up of two items [data and Reference (pointer) that points to the next node].
Linked List | Array |
---|---|
Size is not fixed | size is always fixed |
Cannot access a random node | Can access a random node |
Stored in a non-consecutive memory location | Stored in a contiguous memory location |
Because the only ref for each node in the array is the node index itself, the array is stored in memory only in a consecutive memory location.
It could be something like this. Each node is linked to the next one. but they are scattering in different memory slots.
—
It is a data structure used to store a collection of objects in memory.
LIFO => Last In First Out
Stack can be implemented in two ways:
Pros:
Cons
Methods
Pros:
Cons
Methods