Time Complexity Explained
Time Complexity Explained
Example:
dart
dart
1/21
while (left <= right) {
int mid = left + (right - left) ~/ 2;
if (arr[mid] == target) return mid;
if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
dart
dart
2/21
int mid = arr.length ~/ 2;
List<int> left = mergeSort(arr.sublist(0, mid));
List<int> right = mergeSort(arr.sublist(mid));
return merge(left, right);
}
Seen in efficient sorting algorithms like Merge Sort, Quick Sort (average case).
dart
Example:
dart
3/21
void printTriples(List<int> arr) {
for (int i = 0; i < arr.length; i++) {
for (int j = 0; j < arr.length; j++) {
for (int k = 0; k < arr.length; k++) {
print('${arr[i]}, ${arr[j]}, ${arr[k]}');
}
}
}
}
dart
int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
Seen in recursive problems like the Fibonacci sequence, subsets, and backtracking.
dart
4/21
print(chosen);
return;
}
for (int i = 0; i < str.length; i++) {
String newStr = str.substring(0, i) + str.substring(i + 1);
permute(newStr, chosen + str[i]);
}
}
2. Asymptotic Notations
Asymptotic notations describe the upper, lower, and tight bounds of an algorithm’s time
complexity.
Example: O(n²) for Bubble Sort means the worst-case runtime is proportional to n².
Example: Ω(n) for Bubble Sort means the best case (already sorted) takes linear time.
Example: Θ(n log n) for Merge Sort means its time complexity remains the same in all
cases.
3. Summary Table
5/21
Complexity Name Example Algorithm
Constant time (O(1)) and logarithmic time (O(log n)) are the best.
What is an Algorithm?
An algorithm is a step-by-step procedure or a set of well-defined instructions designed to
solve a specific problem or perform a task. It takes an input, processes it, and produces an
output in a finite amount of time.
6/21
Factorial of a number n (denoted as n!) is the product of all positive integers from 1 to n:
n! = n × (n − 1) × (n − 2) × ... × 1
Example:
5! = 5 × 4 × 3 × 2 × 1 = 120
Algorithm (Step-by-Step)
1. Start
3. If n is 0 or 1, return 1 (since 0! = 1! = 1 )
5. Loop from i = 2 to n
7. End
dart
int factorial(int n) {
int fact = 1;
for (int i = 2; i <= n; i++) {
fact *= i;
}
return fact;
}
void main() {
int num = 5;
7/21
print("Factorial of $num is ${factorial(num)}");
}
dart
int factorialRecursive(int n) {
if (n == 0 || n == 1) return 1;
return n * factorialRecursive(n - 1);
}
void main() {
int num = 5;
print("Factorial of $num is ${factorialRecursive(num)}");
}
Let me know if you want an optimized version using memoization or BigInteger for large
numbers! 🚀
what is recursion and make a code of finding factorial ,
fibonacci sequence and tower hanoi using recursion
What is Recursion?
Recursion is a programming technique where a function calls itself to solve a smaller
subproblem of the original problem. It continues calling itself until it reaches a base case,
which stops the recursion.
8/21
1. Factorial Using Recursion
Factorial of a number n! is calculated as:
n! = n × (n − 1)!
0! = 1
int factorial(int n) {
if (n == 0 || n == 1) return 1; // Base case
return n * factorial(n - 1); // Recursive case
}
void main() {
int num = 5;
print("Factorial of $num is ${factorial(num)}");
}
F (n) = F (n − 1) + F (n − 2)
F (0) = 0, F (1) = 1
dart
9/21
int fibonacci(int n) {
if (n == 0) return 0; // Base case
if (n == 1) return 1; // Base case
return fibonacci(n - 1) + fibonacci(n - 2); // Recursive case
}
void main() {
int num = 6;
print("Fibonacci($num) is ${fibonacci(num)}");
}
dart
10/21
print("Move disk $n from $source to $destination");
void main() {
int numDisks = 3;
towerOfHanoi(numDisks, "A", "B", "C");
}
Summary
Time
Problem Base Case Recursive Formula Complexity
Tower of n=1 Move n − 1 disks, then move the last disk, O(2ⁿ)
Hanoi then move n − 1 disks
1. Tail Recursion
A tail recursive function is when the recursive call is the last operation performed
before returning the result.
11/21
Tail recursion can be optimized by the compiler into an iterative approach to save stack
space (Tail Call Optimization, TCO).
dart
void main() {
int num = 5;
print("Factorial of $num is ${factorialTail(num, 1)}");
}
2. Non-Tail Recursion
A non-tail recursive function is when the recursive call is not the last operation before
returning.
Additional operations (like multiplication, addition, etc.) are performed after the
recursive call.
dart
int factorialNonTail(int n) {
if (n == 0 || n == 1) return 1; // Base case
return n * factorialNonTail(n - 1); // Non-tail recursive call (multiplication
happens after recursion)
}
12/21
void main() {
int num = 5;
print("Factorial of $num is ${factorialNonTail(num)}");
}
⚠️ Less efficient because each recursive call remains in memory until all multiplications
are resolved.
⏳ Time Complexity: O(n)
📦 Space Complexity: O(n) (due to recursive stack usage)
Key Differences
Feature Tail Recursion Non-Tail Recursion
Recursive Call Position Last operation before returning Not the last operation
Performance Faster, can be optimized into loops Slower, requires more memory
dart
int fibonacciNonTail(int n) {
if (n == 0) return 0;
if (n == 1) return 1;
return fibonacciNonTail(n - 1) + fibonacciNonTail(n - 2); // Recursive calls
happen before return
}
void main() {
int num = 6;
13/21
print("Fibonacci($num) is ${fibonacciNonTail(num)}");
}
dart
void main() {
int num = 6;
print("Fibonacci($num) is ${fibonacciTail(num, 0, 1)}");
}
Conclusion
Use tail recursion when possible to optimize memory usage.
Non-tail recursion is easier to understand but can lead to higher memory usage.
Some languages optimize tail recursion into loops, but Dart does not support Tail Call
Optimization (TCO) yet.
14/21
Here’s the C implementation of Stack, Queue, and Circular Queue using arrays:
Operations:
Push: Insert element at the top.
#include <stdio.h>
#define MAX 5
// Push operation
void push(int value) {
if (top == MAX - 1) {
printf("Stack Overflow!\n");
return;
}
stack[++top] = value;
printf("%d pushed into stack\n", value);
}
// Pop operation
void pop() {
if (top == -1) {
printf("Stack Underflow!\n");
return;
}
printf("%d popped from stack\n", stack[top--]);
}
15/21
// Peek operation
void peek() {
if (top == -1) {
printf("Stack is empty!\n");
return;
}
printf("Top element: %d\n", stack[top]);
}
// Display stack
void display() {
if (top == -1) {
printf("Stack is empty!\n");
return;
}
printf("Stack elements: ");
for (int i = top; i >= 0; i--) {
printf("%d ", stack[i]);
}
printf("\n");
}
// Main function
int main() {
push(10);
push(20);
push(30);
display();
pop();
display();
peek();
return 0;
}
✅ Time Complexity:
Push, Pop, Peek: O(1)
16/21
2. Queue Implementation Using Array
A queue follows the FIFO (First In, First Out) principle.
Operations:
Enqueue: Insert element at the rear.
#include <stdio.h>
#define MAX 5
// Enqueue operation
void enqueue(int value) {
if (rear == MAX - 1) {
printf("Queue Overflow!\n");
return;
}
if (front == -1) front = 0;
queue[++rear] = value;
printf("%d enqueued into queue\n", value);
}
// Dequeue operation
void dequeue() {
if (front == -1 || front > rear) {
printf("Queue Underflow!\n");
return;
}
printf("%d dequeued from queue\n", queue[front++]);
}
// Display queue
void display() {
if (front == -1 || front > rear) {
17/21
printf("Queue is empty!\n");
return;
}
printf("Queue elements: ");
for (int i = front; i <= rear; i++) {
printf("%d ", queue[i]);
}
printf("\n");
}
// Main function
int main() {
enqueue(10);
enqueue(20);
enqueue(30);
display();
dequeue();
display();
return 0;
}
✅ Time Complexity:
Enqueue, Dequeue: O(1)
Issue: Dequeue operation shifts front but does not reuse space.
Operations:
Enqueue: Insert element at the rear.
18/21
#include <stdio.h>
#define MAX 5
// Enqueue operation
void enqueue(int value) {
if (isFull()) {
printf("Queue Overflow!\n");
return;
}
if (front == -1) front = 0;
rear = (rear + 1) % MAX;
cqueue[rear] = value;
printf("%d enqueued into circular queue\n", value);
}
// Dequeue operation
void dequeue() {
if (isEmpty()) {
printf("Queue Underflow!\n");
return;
}
printf("%d dequeued from circular queue\n", cqueue[front]);
if (front == rear) { // Reset when last element is removed
front = rear = -1;
} else {
front = (front + 1) % MAX;
}
}
19/21
// Display queue
void display() {
if (isEmpty()) {
printf("Queue is empty!\n");
return;
}
printf("Circular Queue elements: ");
int i = front;
while (1) {
printf("%d ", cqueue[i]);
if (i == rear) break;
i = (i + 1) % MAX;
}
printf("\n");
}
// Main function
int main() {
enqueue(10);
enqueue(20);
enqueue(30);
enqueue(40);
enqueue(50);
display();
dequeue();
enqueue(60);
display();
return 0;
}
✅ Time Complexity:
Enqueue, Dequeue: O(1)
Comparison Table
20/21
Data
Structure Principle Pros Cons
Conclusion
Use Stack when you need LIFO operations (e.g., function calls, undo feature).
Use Queue when you need FIFO operations (e.g., scheduling, printing jobs).
21/21