π Big O Notation: Writing Efficient Algorithms
Big O Notation is a way to describe the upper bound of an algorithmβs time or space complexity as a function of the input size n. It helps developers measure scalability, performance, and make informed choices.
πΉ O(1) β Constant Time
- The runtime doesn't change as input grows.
- Fastest possible performance.
Examples:
arr[5] // Access element at index
hashMap.put(key, value) // Insert in hash map
β Use Case: Hash table lookup, setting a value in a fixed-size array.
πΉ O(n) β Linear Time
- Runtime grows proportionally with the input size.
Examples:
for (int i = 0; i < n; i++) {
sum += arr[i];
}
β Use Case: Looping through an array or list.
πΉ O(log n) β Logarithmic Time
- Each step reduces the problem size in half.
Examples:
// Binary Search
int binarySearch(int[] arr, int target) {
int low = 0, high = arr.length - 1;
while (low <= high) {
int mid = (low + high) / 2;
if (arr[mid] == target) return mid;
else if (arr[mid] < target) low = mid + 1;
else high = mid - 1;
}
return -1;
}
β Use Case: Binary search, operations on balanced BSTs.
πΉ O(n log n) β Linearithmic Time
- Combines linear and logarithmic growth.
- Most efficient sorting algorithms fall here.
Examples:
Merge Sort, Quick Sort, Heap Sort
β Use Case: Large-scale data sorting.
πΉ O(nΒ²) β Quadratic Time
- Performance drops fast as input grows.
- Typically found in nested loops.
Examples:
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
// Do something
}
}
β Use Case: Simple comparison-based sorting, brute-force string comparison.
πΉ O(nΒ³) β Cubic Time
- Even more nested loops β often in matrix operations.
Examples:
// Naive matrix multiplication
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
for (int k = 0; k < n; k++)
C[i][j] += A[i][k] * B[k][j];
β Use Case: Dense matrix multiplication.
πΉ O(βn) β Square Root Time
- Often seen in mathematical algorithms or optimizations.
Examples:
// Check for prime numbers
for (int i = 2; i <= sqrt(n); i++) {
if (n % i == 0) return false;
}
β Use Case: Sieve of Eratosthenes, divisibility checks.
πΉ O(2βΏ) β Exponential Time
- Performance becomes infeasible quickly.
- Each step creates 2 new subproblems.
Examples:
// Fibonacci Recursive
int fib(int n) {
if (n <= 1) return n;
return fib(n-1) + fib(n-2);
}
β Avoid unless input is very small.
πΉ O(n!) β Factorial Time
- Grows faster than any other β used for permutations or brute-force.
Examples:
// Generating permutations of an array
β Warning: Explodes with small increases in input size.
π Big O Comparison Chart
Big O | Input Size 10 | Input Size 100 |
---|---|---|
O(1) | 1 | 1 |
O(log n) | ~3 | ~7 |
O(n) | 10 | 100 |
O(n log n) | ~30 | ~700 |
O(nΒ²) | 100 | 10,000 |
O(2βΏ) | 1024 | Huge |
O(n!) | 3.6M | Massive |
π§ Final Tips
- Always strive for the lowest time complexity possible.
- Optimize nested loops, recursion, and data structures used.
- Remember: space complexity matters too (Big O for memory).