This covers standard DSA questions and concepts for revision purposes - Topic wise
Fundamentals
Ascii of
0→ 48 ,A-Z [65 - 90],a - z [97-122],charin c++ is 1 byte or 8 bits so it can take value 0-255 (2^8-1). Now signed char takes values[-128,127], Unsigned char :[0-255]usingfor type aliases.
char c = 200;
unsigned char d = 200;
cout << (int)c; // -56
cout << (int)d; // 200#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ll x;
cin >> x;
cout << x;
}C++ Memory Information (Pointers / references and addresses….)
int a = 5, b = 10;
int *p = &a; // Pointer to 'a' ( p points to memory of a)
// p holds address of where `a` is stored
p = &b; // Now points to 'b'
int &r = a; // Reference to 'a' , no new memory is allocated
// r is alias to a , any operation on r affects a
// r = &b; // ❌ Error: A reference cannot be changed to refer to another variable
r = b; // ✅ Changes the value of 'a' to 10 (not reassigning reference)| Category | Operation | Code | Notes / Headers |
|---|---|---|---|
| Ranges | char (1 byte) int (4 byte) → 32 bits long (4 byte) long long (8 bytes) float (4 byte) double (8 bytes) |
Signed: [-128, 127] int / long : [-2^31, 2^31-1] long long : [−2^63, 2^63 − 1] float : 32 bit single precision double : 64 bit single precision |
unsigned: [0, 255] [0, 2^32 − 1] [0, 2^64 − 1] float : no unsigned double : no unsigned |
| Character | Is it a digit ? Is alphabetic Is alphanumeric |
isdigit(c) isalpha(c) isalnum(c) |
return “1” if true |
| tolower / toupper | char lc = std::tolower(c); char uc = std::toupper(c); |
Cast to unsigned char to avoid UB for negative char | |
| char → int int → char |
int x = c - '0'; char d = char(x + '0'); |
Works if c is '0'..'9' x in 0..9 |
|
| Char → String | char c = ‘s’ , string x = {c} | Converts char to string | |
| String → Char | string str = “a” , char v = str[0] | Converts string to char a[0] | |
| Strings | erase | s.erase(pos, len); | Erase 3 chars from index 2 |
| substr | string s = “ramesh” auto t = s.substr(pos, len); |
Safe if pos ≤ s.size() Ans = “mes” (2,3) |
|
| stoi stof |
int n = stoi(“123”); float f = stof(“4.15”); |
Returns integer 123 Returns float 4.15 |
|
| to_string | auto s = to_string(123); | “123” | |
| reverse | reverse(s.begin(), s.end()); | <algorithm> | |
| Numbers | fixed + precision | cout<<fixed<<setprecision(2)<<y; | prints 5.01 for 5.0123 |
| Arrays | Init 1D (Fixed size) | int arr[5] = {0}; // all elem → 0 int arr[5] = {1, 2, 3, 5, 6}; int arr[26]; // Fixed size array created on stack, values are garbage. |
Fixed size array. Faster allocation on stack. Memory is automatically freed when the variable goes out of scope. |
| Array with new keyword | int* arr = new int[26] int* arr = new int26; //All zero int* arr = new int[5]{1, 2, 3, 4, 5}; |
Dynamic mem. Alloc. Use delete[] to free memory. |
|
| Init 2D | int arr[3][3] = {{1,2,3}, {4,5,6}, {7,8,9} }; int** arr = new int*[rows]; |
Pre-existing values Array of row ptrs. |
|
| fill 1D | fill(a, a+n, -1); | <algorithm> | |
| fill 2D (POD only) | memset(arr, -1, sizeof(arr)); | Only works for -1 or 0 | |
| Vectors | init 1D/2D/3D | vector<int> v(n, -1); vector<vector<int>> dp(r,vector<int>(c, 0)); vector<vector<vector<int>>> dp2(a,vector<vector<int>>(b, vector<int>(c, -1))); |
1-D vector (-1) 2-D vector (r.c -> 0) 3-D vector (a.b.c → -1) |
| Pre-existing | vector<int> v = {1,2,3}; | ||
| copy | vector<int> v2 = v; | Deep copy | |
| insert at ith erase at ith |
v.insert(v.begin()+i, val); v.erase(v.begin()+i); |
O(n) | |
| reverse | reverse(v.begin(), v.end()); | <algorithm> | |
| sort ↑ | sort(v.begin(), v.end()); | <algorithm> | |
| sort ↓ (two ways) | sort(v.begin(), v.end(), greater<>()); | ||
| Binary search | lower_bound | auto it = lower_bound(v.begin(), v.end(), key); int idx = it - v.begin(); int val = *lower_bound(v,v+n,key); |
First >= key v must be sorted |
| upper_bound | auto it = upper_bound(v.begin(), v.end(), key); | First > key v must be sorted |
|
| Priority queue | max-heap (default) | priority_queue<int> pq; | Top is largest |
| min-heap | priority_queue<int, vector<int>, greater<int>> pq; | Top is smallest | |
| MATH | GCD(a,b) | __gcd(a,b) | GCD of a , b in O(Log(N)) |
| Inline sorting By comparator |
sort(s.begin(), s.end(), [](const string &a, const string &b) { if (a.size() != b.size()) return a.size() < b.size(); return a < b; }); |
Sorting strings by Length first then Lexicographically |
|
| BITS | Operators | & -> AND , ^ → XOR , << = left shift | –> OR , >> (right shift) , ~a (invert all bits of a) |
|
| Left and right shift | a << x = a * 2^x a >> x = a / 2^x |
Left and right shift not used for negative nos. | |
| Operations | => if(x & 1) → number is odd => floor(log2(num)) (number of bits) => ~num (1s complement) => ~num + 1 (2s complement) -num (2’s complement) → num | 1 << (k) ⇒ Set kth bit → num & ~(1<<k) ⇒ UnSet kth bit |
||
| Check if Kth Bit set from LSB (starting from index 1) | Number = 500 (111110100) check 3rd bit is set or not → 1 (count from 1) ⇒ if((num >> (k-1)) & 1) → SET |
||
| Get rightmost set bit Unset rightmost set bit |
Eg : num = 12 , o/p => 4 (1100) Ans : num & (~num+1) : num & (-num) => n & (n-1) (unset rightmost set bit) n-1 (flip all bits after rightmost set bit) |
||
| Check power of 2 | => num & (num-1) == 0 | ||
| Range of bit | 32 bit ⇒ [-2^31 to 2^31 - 1] | [INT_MIN to INT_MAX] | |
| Memory Allocation |
class Trie { public: TrieNode* root; Trie() { root = new TrieNode(); } }; |
Dynamic Memory Allocation Trie* trie = new Trie(); trie → root // Allocates memory on Heap // need to use delete trie to delete memory. |
|
| Static Memory Allocation Trie trie; trie.root // Creates Trie object on Stack // Constructor is called automatic |
// Destructor is called when trie goes out of scope // No manual memory allocation |
Questions
⇒ Step1 → Solve this sheet for revision purposes
⇒ Step2 → Striver SDE sheet , Striver A-Z sheet , Leetcode 150
⇒ Step3 → Leetcode company wise and random problems…
⇒ Step4 → Revisit the Additional DSA problems from this sheet → Additional DSA
→ For syntax refer Codes , for solutions and concepts refer Notes…
Math and Geometry
Sieve of Eratosthenes [Ques] → Number of prime numbers up to in
Find all prime factors of N smallest prime factor → ,
Initially my idea was to calculate factors of in , then loop over every factor and check if prime or not using a precomputed sieve.
GFG has a better solution by dividing by spf[i] → per query
Questions
Bulb switcher → Mind Fuck
Min number of operations required to reduce n -> 0 → Beautiful….
Arrays and String
Given array of non negative integers, find number of subarrays with sum b/w B-C O(N)
Merge sorted arrays without extra space O(N) , O(1)
Two Sum 2 - Input Array Sorted - O(N) , O(1)
Subarray with 0 Sum - O(N), O(N)
Triplets with 0 sum - O(N2) , O(1)
Find all anagrams in given string - O(N) , O(1)
Dictionary matching problem - O(N) , O(N)
Find the majority element in the array O(N) O(1)
Count Subarrays with sum = k O(N) O(N)
Subarray with Given XOR O(N) O(N)
Next Permutation O(N) , O(1)
Maximum ones after modification O(N) O(1)
Subarrays with K distinct Integers O(N) O(k)
Count Inversion Problem O(NlogN) , O(N)
Maximum Size Subarray Sum Equals k O(N) , O(N)
Sort Colors O(N), O(1)
Longest subarray with 0 sum O(N), O(N)
Longest consecutive sequence O(N) O(N)
Maximum subarray sum O(N) , O(1)
Maximum subarray size having all subarrays sums less than k O(N) O(1)
Pattern Matching
Stack and Queues
NSL - Nearest smallest left - O(N), O(N)
NSR - Nearest smallest right - O(N), O(N)
Infix to Postfix - O(N), O(N)
Stock Span Problem - O(N), O(N)
Reverse Polish / Prefix Evaluation - O(N) , O(N)
Maximum Rectangle area in Histogram - O(N) , O(N)
Trapping rainwater problem O(N) , O(N)
Sliding window maximum O(N) , O(k)
Max rectangle area in a matrix O(R.C) , O(R.C)
Longest valid parentheses O(N) , O(1)
Sort Stack using recursion O(N2) , O(N)
Max stack in O(1) time (see the solution i have explained the intuition)
Valid Parentheses 2 O(N) , O(N)
Josephus Problem O(N) , O(1)
Linked List
Remove loop in link list O(N) O(1)
Length of cycle in link list O(N) O(1)
Reverse Nodes in K group O(N)/
Sort a K sorted Linked list O(nlogk) , O(k)
Find starting point of the loop O(N), O(1)
Intersection of 2 Linked List O(N) , O(1)
Reverse Alternate K nodes O(N) , O(1)
Clone a Linked list with random pointer O(N), O(N)
Binary Search
Greedy and Sorting
Sorting
Algo that takes minimum number of writes → Selection sort
Quick sort for array and merge sort for link list → O(N) swaps
Inplace(doesn’t need extra space) → Bubble , Insertion, Quick sort , Selection
Stable(2 elements equal value appears in same order as in normal array)
- Bubble sort , Insertion , Merge sort
Best case time complexity
Quick Sort → Best (O(Nlogn) , Worst → O(n2)
Bubble sort → Best (O(N)) , Worst → O(n^2)
Radix Sort ( for fixed numbers in very large datasets )
len(d) < log2(N) → Radix sort makes sense
Sorting in O(N) time → O(n.d) , d is number of digits
Heap
BIT Manipulation
Recursion / BackTracking
———————————————————————————————————————
All the above topics cover basic problems now you come down to topics where most of the companies ask interview questions. So the best way is to solve as many questions as possible
Binary Tree
Left view , Right view , Vertical order traversal , Top view binary tree, Bottom view
Height of BT , Balanced B.T (AVL Red-Black Tree)
Print Root to Node path in BT If exists (check notes)
Binary Search Tree
BST with N keys (Nth catalan number) → see notes…
Graphs
BFS , DFS recursive , Iterative DFS , BFS on 2D matrix , DFS on 2D matrix
Topo Sort BFS , Topo Sort DFS [DAG]
Dijaktras - Single source shortest path (Directed | Undirected) [ No negative edges ]
Single source shortest path in Undirected graph with unit distance
Floyd Warshall - Single source shortest path for all pairs O(V^3)
Other Algos
Strongly Connected Components - O(V+E) / O(V) [ Intuition ] [ Ques ]
Bridges in a graph - Tarjan Algo (O(V+E) / O(V)) [ Intuition ] [ Ques ]
Questions
Word Search (Very basic problem but you always forget to do one step…)
Word Search 2 [ nice application ]
Snakes and Ladders (Very IMP question → BFS vs DFS)
Evaluate Division [ Gaand phat jyga but medium]
Word Ladder 2 [ all possible paths ]
DP
1-D / 2-D DP
KnapSack and String
Partition into K equal subsets - This looks like the above problem but isn’t
The only thing the above problems have taught me is that there is always an easier way to solve a problem and if you don’t know any other way of solving than your own… you’ve probably memorized the solution (even without knowing it 🙂)
Partition DP
Advanced Data Structures and Algorithms
For these refer to Notes / Striver sheet
DSU
Number of Island - 2 [ Nice ]
Tries
Segment Trees
Fenwick Trees
Solutions / Concepts
MATH
Formulas
Modular Arithmetic
The number of digits of an integer in base is
GP sum
Nth term ⇒
Sum up to ⇒
Infinite sum ⇒
Euclidean GCD Sol
If we subtract a smaller number from a larger one (we reduce a larger number), GCD doesn't change. So if we keep subtracting repeatedly the larger of two, we end up with GCD.
int findGCD(int a, int b) {
if (a == 0)
return b;
return findGCD(b % a, a);
}Modular Arithmetic
Recursive
int power(int x, int n, int M) {
if (n == 0)
return 1;
if (n == 1)
return x;
long long res = power(x, n / 2, M);
res = (res * res) % M;
if (n % 2 == 0)
return res % M;
else
return (res * x) % M;
}Iterative
int power(x, n) {
long long res = 1;
while (n > 0) {
if (n % 2 == 0) {
res = res * x;
} else
x = x * x;
n = n / 2;
}
return res;
}Sieve Of Erantosheneus
→ Find all prime numbers upto N
int checkIfPrime(int n) {
vector<int> sieve(n + 1, 1); // initially all are prime
sieve[0] = sieve[1] = 0; // obviously
for (int i = 2; i <= sqrt(n); i++) {
// if number is prime
if (sieve[i] == 1) {
// mark all factors of i as non-prime
// start with i*i as (5,2) will be covered in (2,5)
for (int j = i; i * j <= n; j++) {
sieve[i * j] = 0;
}
}
}
return sieve[n];
}nCr in O(r)
int nCr(int n, int r) {
long long sum = 1;
for (int i = 1; i <= r; i++) {
sum = (sum * (n - i + 1)) / i;
}
return sum;
}LINKED LIST
struct Node {
int data;
Node *next;
Node(int val) {
data = val;
next = nullptr;
}
};
void printLinkList(Node *a) {
Node *head = a;
while (head != nullptr) {
cout << head->data << " ";
head = head->next;
}
}
// main
Node *a = new Node(5);
Node *b = new Node(10);
a->next = b;
print(a);Trees
class Node {
public:
int data;
Node *left;
Node *right;
Node(int val) {
data = val;
left = NULL;
right = NULL;
}
};
// main
Node *root = new Node(10);
Node *leftNode = new Node(11);
Node *rightNode = new Node(9);
root->left = leftNode;
root->right = rightNode;Theory
Inorder traversal , Preorder traversal , Postorder traversal
Inorder (Without recursion) , Iterative Preorder , Iterative Postorder
Max nodes in a binary tree of height ⇒ (complete/perfect binary tree)
Binary tree with nodes and minimum possible height ⇒
Complete B.T → All levels are filled except the last level. The last level filled left to right.
Perfect B.T → All leaf nodes are at the same level, every internal node has two children, and height =
Concepts in Heap
A Heap is a complete binary tree that satisfies the heap property.
Types
Max Heap → Parent ≥ children
Min Heap → Parent ≤ children
Insertion and deletion in Heap - O(log(K)) , K = number of elements in Heap.
Insert : Just insert at the end → Heapify upwards
Delete : delete the top element → copy last element to top → Heapify downwards
Heapify (how does it work) MaxHeap
HeapifyUp - Compare with parent element and swap if parent is smaller.
HeapifyDown - Compare with both children and find the biggest and swap parent with that.
Graphs
Adjacency list is generally represented as :
- vector<int> g[N+1] for (N nodes 1 index) { Array of vectors }
Adjacency matrix
- vector<vector<int>> g(N+1, vector<int>(N+1,0)) { 2-D matrix fixed size }
As you can see for memory usage adjacency list is far better as it only covers nodes with edges and adjacency matrix will take (NxN) space even for sparse graphs
But if you have frequent operations to check if 2 nodes are connected, then adjacency matrix is better O(1) than adjacency list O(N).
Now you’ll say if we represent unordered_set<int> g[N] then we can query if 2 nodes are connected in O(1) right ?
YES but this uses Hashing and hash functions so the overhead is that of hash function and how strong your load factor is to avoid hash collision, this works very well for sparse graphs.
But for dense graphs or or when adjacency queries dominate and you don’t need neighbor iteration, adjacency matrix still beats your hash based adjacency list.
Also iteration in unordered_set<int> has worse cache locality than a flat vector<int>.The vector<int> stores elements sequentially whereas unordered_set use hash tables for storing elements and usually stores 0 or more elements that hash to the same index.
BFS vs DFS
- I tried this problem using DFS and wasted 2 hrs. The thing is DFS explores all the paths to reach the end. I was thinking of kind of DP way exploring all paths and using memoization which will give the answer but will waste a lot of time. Here every cell is a node which is connected to each other if it’s reachable the cost of moving from one cell to another is same. So it’s kind of like a unweighted graph where you have to find min cost to reach at the end and BFS does in O(N) or O(n*n)
DP and recursion usually works when The graph has varying edge weights (so you can’t just rely on first-come-first-serve like BFS).
Or when order matters, like sequences (LCS, LIS, knapsack).
- In this board, every move costs the same (1 dice throw). So BFS explores all nodes in increasing number of moves, layer by layer. That’s why BFS gives the correct minimum steps without any need for recursion or DP.
Could BFS work for other “min cost” DP problems?
All moves have the same cost (or we can model cost by level).
Transitions form a directed acyclic or unweighted graph
Example for BFS
Minimum jumps to reach end , Minimum number of operations to reach X from 1 ,
Minimum path sum in a grid ❌BFS
- costs vary per cell BFS fails because edges have weights; we need Dijkstra or DP.
In BFS the first time you reach a node is always the shortest path. You never need to update it.
Minimum Spanning Tree
A spanning Tree consists of N nodes , N-1 edges. Minimum spanning tree is a spanning tree with the sum of weights of all edges is minimal.
The whole Spanning tree concept is defined for Un-directed graphs. And basically Spanning tree is a tree so it is acyclic and has N-1 edges
Also yeah in MST or any spanning tree every node by definition is mutually reachable.
🌈 Imagine there are 4 cities and you have to connect them with Fibre Optics Cable, in such a way that overall cost is minimum. Cost of Connecting A→B is given as edge in the graph
→ Here your main idea is to find an MST.
Algorithms
Kruskal
Kruskal's idea was that we will always select the minimum edge and include it. But if adding an edge forms a cycle we’ll skip that.
How to know if adding an edge forms a cycle ? → If both the nodes are in the same component.
So we’ll start by sorting the edges in ascending order of weight.
Process edges one by one
If both nodes belong to the same super parent (DSU) → skip it [cycle]
Else Include them in MST Union(u,v)
Prim’s Algorithm
Prim’s idea works on Greedy by maintaining a min-heap of { wt, v, u } for edge u – v
Pick any start vertex s → Add it to min-heap
Continue until heap is empty
Pop the element and check if that node(v) is already visited if yes → skip
Else mark it as visited and include it in MST.
- Push all the un-visited neighbors of this node(v) into Min-heap
Strongly Connected Components
Strongly connected components only exist for Directed Graphs.
{ A, B, C} is a SCC if all nodes are mutually reachable so (A→ B → C→ A) is a SCC as we can reach from any node to any other node.
Before you think this is just cycle detection. Consider this graph { A→B , B→C , C→A , C→D , D→B }. You can easily detect a cycle of 3 nodes. But… This whole graph is an SCC meaning every node in this graph is mutually reachable via every other node. So cycle detection is just one small part of SCC.
Why do we need SCC ?
SCCs are purely about mutual reachability, not shortest paths or distances. Think of it like finding clusters of mutually reachable people within a large set of people. So it can be used in grouping mutually reachable subsets in a large set.
The idea is forming a Condensation Graph.
Take all the SCC components in a graph and reduce it to a single node.
The resulting graph is a DAG. And we all know how useful DAGs are, Used in Topo sorting and network analysis.
Even in Build tools like gradle uses SCC algorithms to first find the SCC in dependency of modules and then reduce it to a condensation graph for performing Topo Sort to decide a valid Build order.
Algorithm
Consider this graph {1,2,3,4} , {5,6,7} forms a SCC. Connected by an Unidirected edge
SCCa → SCCb
Perform DFS and record finish times. Put them in the stack. Here the first to finish would be 7. Stack(top) → [ 1, 2 , 3 ,4, 5 , 6 ,7]
Reverse the edges and and perform dfs again from decreasing order of finish time.
Each DFS traversal will give one SCC.
The whole idea of processing in decreasing order of finish time is because see this
SSCa → SCCb. If we reverse edges and start dfs from any node on SCCb then SCCa is still reachable. The idea was if we reverse edges the DFS traversal should give us SCC.
So when we decompose a SCC graph into a node. We should always start processing from the SCC which finished last here. That would be SCCa so that after reversing the edges we cannot reach the other SCC.
Why we didn’t take insertion time is because that can be different based on where you started your DFS. But the first node to finish will always be in SCCb irrespective of whichever node you start your DFS from.
Bridges in a Graph [ Critical Connection ] [ Single edge of failure ]
A bridge is an edge whose removal splits a connected graph into two disconnected parts.
Generally a concept of un-directed graph, but can be applied to directed graphs as well where connectivity in directed graphs would then refer to SCC
Algorithm
Tarjan Good Explanation - Jenny
The intuition is simple: when doing a DFS, you check if there’s any alternative path back from a subtree to an ancestor. If there isn’t, the edge leading into that subtree is a bridge.
- So suppose we think u – v is a bridge then we have to check if there is an alternate path to v which doesn’t go through u. For this we check low(v) > disc(u) ⇒ u – v is a bridge
- The whole point of low(v) is that can this node v be reached from any other node apart from parent u so if low(v) < disc(u) then yes then u–v is not a bridge, u is parent.
low[u] = x means that starting from u or anywhere in u’s DFS subtree, you can reach the node with discovery time x without using the tree edge that led you into u.
Tarjan
We assign each node a discovery time disc [] , and low[] value
Here insertion time is the time when the node was first discovered in DFS
While doing DFS if you encountered a visited node (which is not parent) update your low for edge u–v low[u] = min(low[u],disc[v]).
Now when the DFS of a node is completed and we backtrack then the parent node will check if u–v is a bridge and if low(v) > disc(u) ⇒ u – v is a bridge.
- Also we will update low(u) = min ( low(u) , low(v) )
Articulation Point [ Single node of failure ]
💅 Imagine Riya being a common link to separate groups A and B. Group A talks to Group B only through riya. A marketing firm wants to advertise their product to masses. They have come up with a strategy targeting riya to advertise their product as riya here is the articulation point connecting the masses. Riya is the Articulation point and she is HOT
- Articulation point is any node in graph whose removal breaks the graph into multiple components
Algorithm
Very much similar to Tarjan in bridges just that the entire node is removed.
Here in u – v , u is an articulation point if from v you cannot reach to any edge above u, so simply in u –v , low(v) >= disc(u) ⇒ u is an articulation point
There is a catch u should not be root or u should have a parent so..
- For u – v ⇒ If ( parent != -1 && low[v] >= disc[u] ) ⇒ u is articulation point
But if u is root node and has multiple children than again u is articulation point so update condition
In the end of DFS of u check if u is root and has > 1 children
- If ( parent == -1 && children > 1) → u is articulation point
DSU
DSU means Disjoint Set Union, also known as Union-Find.
Basically it keeps track of non-overlapping sets (disjoint)
Operation
Union(u,v) → Merge (or unite) two sets into one. O(1)
Find(x) → Find super parent of x, check if 2 elements belong to the same set.
This is efficient as the Time complexity of both these operations is almost constant O(1)
Path compression is used in find(x) gives, super parent of x, it’s like we make every node on the path point directly to the root.
We are storing parent[] , size[] array , parent[x] gives super parent of x and size array is required during union operation to merge smaller size sets to larger sets
Don’t access the parent[] , size[] array , access only from find(x) , union(u,v)
Application ?
It’s a simple way to group elements and to check which group an element belongs to.
Used for finding MST in Kruskal Algorithm
class DSU {
vector<int> parent, rank, size;
public:
DSU(int n) {
parent.resize(n);
rank.resize(n, 0);
size.resize(n, 1);
for (int i = 0; i < n; i++)
parent[i] = i; // 0 indexed
}
// find super parent of x or GODFather of x
// O(4α) ~ O(1) operation
int find(int x) {
if (parent[x] == x) {
return x;
}
// path compression (DP)
return parent[x] = find(parent[x]);
}
// union of 2 nodes by Size
// O(4α) ~ O(1) operation
void unionBySize(int u, int v) {
int superU = find(u);
int superV = find(v);
// if both super parents are equal (belong to same set)
if (superU == superV) {
return;
}
// if size of super parent of u > v then increase size of superParentU by size[superV]
if (size[superU] > size[superV]) {
parent[superV] = superU;
size[superU] += size[superV];
} else {
parent[superU] = superV;
size[superV] += size[superU];
}
}
};
// 6 nodes 0-5
DSU dsu(6);
dsu.unionByRank(1, 2);
dsu.unionByRank(3, 4);
dsu.unionByRank(2, 3);
dsu.unionByRank(0, 5);
// 0 and 3 belong to the same set ?
if (dsu.find(0) == dsu.find(3))
cout << "SAME" << endl;
else
cout << "NOT SAME" << endl;Most of the time DSU questions can be solved directly by using DFS. The whole point of DSU is to solve dynamic graph queries, where we leverage the already connected components..
So mostly the questions revolve around finding components in a graph or to check if 2 nodes belong to the same set.
📝 Finding Components in a Graph with nodes defined ~ O(1)
The idea is to keep a component counter in DSU. It is the max number of components possible in the graph. Usually it’s n where n is the number of nodes.
On each successful Union of 2 nodes (if 2 nodes doesn’t belong to same set decrease component–
If you only need size of a component of a node x → size[find(x)]
If we are told to print distinct components or sizes of them ? →
- Loop through all nodes again and group them by find(i)
unordered_map<int, vector<int>> comp;
for (int i = 1; i <= n; i++) {
comp[find(i)].push_back(i);
}📝 Finding Components in a Graph represented as Matrix ~ O(1)
In a graph representation we can represent each cell as a node by :
- getNode(i,j) → i*C + j Here C is total col.
In order to find components you can use the same previous approach but n = 1’s in the matrix
Tries [ Prefix Tree ]
Tries are essentially great if we are given a dictionary of words and have to do some prefix operations in each of them. Like for example if you are given dictionary of words and you have to find out how many words starts with pre
Each node represents a character and path from root→node represents a prefix.
Trie stores unique prefixes for each dictionary of words so
- Total nodes of Trie == count of unique prefixes
Each operation is O(len(word))

- The best way to represent trie is via edges so
“CANAL” is represented as (C–A–N–A–L) from root
* means End of Word
Trie contains {CANAL,CANDY,THE,THERE}
struct TrieNode {
TrieNode *children[26];
bool isEndOfWord; // marks end of a valid word
int wordCount; // how many words end at this node
int prefixCount; // how many words pass through this node
TrieNode() {
for (int i = 0; i < 26; i++)
children[i] = nullptr;
isEndOfWord = false;
wordCount = 0;
prefixCount = 0;
}
};class Trie {
public:
TrieNode *root;
Trie() { root = new TrieNode(); }
// Insert a word → O(len(word))
void insert(const string &word) {
TrieNode *node = root;
for (char c : word) {
int idx = c - 'a';
if (!node->children[idx])
node->children[idx] = new TrieNode();
node = node->children[idx];
node->prefixCount++; // increment for every node in the path
}
node->wordCount++; // mark end of word
}
// Search for a word O(len(word))
bool search(const string &word) {
TrieNode *node = root;
for (char c : word) {
int idx = c - 'a';
if (!node->children[idx])
return false;
node = node->children[idx];
}
return node->isEndOfWord;
}
// Check if any word starts with a given prefix O(len(word))
bool startsWith(const string &prefix) {
TrieNode *node = root;
for (char c : prefix) {
int idx = c - 'a';
if (!node->children[idx])
return false;
node = node->children[idx];
}
return true;
}
// Count words equal to the given word O(len(word))
int countWordsEqualTo(const string &word) {
TrieNode *node = root;
for (char c : word) {
int idx = c - 'a';
if (!node->children[idx])
return 0;
node = node->children[idx];
}
return node->wordCount;
}
// Count words starting with a given prefix —> O(len(word))
int countWordsStartingWith(const string &prefix) {
TrieNode *node = root;
for (char c : prefix) {
int idx = c - 'a';
if (!node->children[idx])
return 0;
node = node->children[idx];
}
return node->prefixCount;
}
// Erase a word (decrement counts along the path) → O(len(word))
void erase(const string &word) {
if (countWordsEqualTo(word) == 0)
return; // word not present
TrieNode *node = root;
for (char c : word) {
int idx = c - 'a';
TrieNode *next = node->children[idx];
next->prefixCount--;
node = next;
}
node->wordCount--;
}
};For Dynamic Alphabets
When characters can include digits, uppercase, or even Unicode, or when memory is tight
struct TrieNode {
unordered_map<char, TrieNode *> children;
bool isEndOfWord = false;
};Segment Trees
Basic Idea : Suppose you are given an array [ 4 , 5 , 6 , 8 , 2 ], and you have to find sum b/w index → {2,3} ⇒ (6+8 → 14) , you can do it in O(N) , now if i want you to optimize it you can probably think of using prefix array ⇒ [4, 9 , 15, 23, 25] and do pref(3) - pref(1) ⇒ 14
Great this is O(1) but what if i told you i want to update array values and perform the same operation again and again. For each update you will have to calculate prefix sum again so that would be worst case O(N) for updating the prefix array and O(1) for finding sum range.
Is there a better way to do this ?
Turns out yes → Here comes the idea of Segment Trees / Fenwick Trees
If you have lots of
Range queries (find max / min / sum) of given range
Update queries (updating the array)
Then Segment tree solves both problems in O(logN) time where N is the number of elements in the array.
How does it work ?
First step is to build a segment tree.
We take a tree array of size 4*N
We divide the entire array into 2 halves and when size of each half is 1 we simply put tree[node] = arr[left]
Each segment / node stores its operation result, here it’s sum so sum(0,2) = 15
So if someone asks you to find sum(1,4) = sum(1,1) + sum(2,2) + sum(3,4) ⇒ 21
So if I am sitting at root I will say to the left branch give me sum(1,2) and on my right give me sum(3,4) and I will simply add it and output.
We Will store this segment tree in a 4*N array where 0th node is empty so we will start from 1st node. The final Tree Array 4*5 = 20 size → [ 0, 25, 15, 10, 9, 6, 8, 2, 4, 5, 0, 0, 0, ……]
Time complexity to build the tree is O(N)
Remember if you are at ith index
- Parent : floor ( i / 2) , Left child : 2*i , Right child : 2*i + 1
Once you have constructed the tree than it’s easy for Range queries(a,b) you just have to divide the array into 2 equal halves recursively and check for range(a,m) and range(m+1,b). If the given node range is within [a,b] then return node value.
Similarly for Update : We just have to find the index to update , this can be done again recursively to look check if index <= middle → check left subtree else right subtree
Once found update tree[node] = value, In the end re-build tree simultaneously
- tree[node] = tree[2 * node] + tree[2 * node + 1]
Segment tree always starts with node = 1.
In C++ default parameters always comes at the end... remember
class SegmentTree {
int nodes;
vector<int> tree;
public:
SegmentTree(int n) {
nodes = n;
tree.resize(4 * n, 0);
}
void build(vector<int> &arr, int node = 1, int l = 0, int r = -1) {
if (r == -1)
r = nodes - 1;
if (l == r) {
tree[node] = arr[l];
return;
}
int m = (l + r) / 2;
build(arr, 2 * node, l, m);
build(arr, 2 * node + 1, m + 1, r);
// This is the operation = for Range Sum queries addition
// For Range min/max query we can create segment tree with
min / max of both children tree[node] = tree[2 * node] + tree[2 * node + 1];
}
void update(int idx, int val, int node = 1, int l = 0, int r = -1) {
if (r == -1)
r = nodes - 1;
if (l == r) {
tree[node] = val;
return;
}
int m = (l + r) / 2;
if (idx <= m)
update(idx, val, 2 * node, l, m);
else
update(idx, val, 2 * node + 1, m + 1, r);
tree[node] = tree[2 * node] + tree[2 * node + 1];
}
int query(int ql, int qr, int node = 1, int l = 0, int r = -1) {
if (r == -1)
r = nodes - 1;
if (qr < l || ql > r)
return 0;
if (ql <= l && r <= qr)
return tree[node];
int m = (l + r) / 2;
return query(ql, qr, 2 * node, l, m) + query(ql, qr, 2 * node + 1, m + 1, r);
}
};int main() {
vector<int> arr = {4, 5, 6, 8, 2};
SegmentTree st(arr.size());
st.build(arr);
cout << st.query(1, 3) << "\n"; // 5 + 6 + 8 = 19
st.update(2, 10);
cout << st.query(1, 3) << "\n"; // 5 + 10 + 8 = 23
}But why 4*N size is taken ??
If you look closely the size we require is the number of elements present in the tree
For a complete binary tree with height h number of nodes ⇒ 2^(h+1)-1
Here height is ceil (log2N) so number of nodes ⇒ 2^ (ceil(log2N + 1) -1
- We can round off to 4N
Each Range and update operation is O(Log2(N)) as you are accessing exactly LogN elements in each operation
JAVA - Revise
static final int MOD = 1000000007;
static final int INF = Integer.MAX_VALUE;
static final long LINF = Long.MAX_VALUE;
static final int[] dx = {-1, 1, 0, 0}; // 4-directional
static final int[] dy = {0, 0, -1, 1};
static final int[] dx8 = {-1, -1, -1, 0, 0, 1, 1, 1}; // 8-directional
static final int[] dy8 = {-1, 0, 1, -1, 1, -1, 0, 1};Scan Input / Output
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
long x = sc.nextLong();
String s = sc.next();
System.out.println(n + " " + x + " " + s);
}
}- For Fast Input/Output scanning → Use BufferedReader + StringTokenizer
| Category | Operation | Code | Notes / Headers |
|---|---|---|---|
| Ranges | byte (1 byte) char (2 byte) short (2 byte) int (4 byte) long (8 bytes) float (4 byte) double (8 bytes) |
1 byte : 8 bits [−128 to 127] char (16 bits unsigned) : [0 to 65,535] short (16 bits signed) : [−32,768 to 32,767] int (32 bits) : [-2^31, 2^31-1] long : [−2^63, 2^63 − 1] float : 32 bit single precision double : 64 bit double precision |
In Java only char is unsigned. boolean → true / false |
| Character | Is it a digit ? Is alphabetic Is alphanumeric |
Character.isDigit(c) Character.isLetter(c) Character.isLetterOrDigit(c) |
Returns boolean |
| tolower / toupper | Character.toLowerCase(c) Character.toUpperCase(c) |
Returns character | |
| char → int int → char |
int x = c - '0'; char d = (char)(x + '0'); |
Works if c is '0'..'9' x in 0..9 |
|
| String → char Char → String |
char[] c = str.toCharArray(); OR str.charAt(0); String str = String.valueOf(c); |
||
| Strings | Replace length |
s.replace(‘a’, ‘b’); s.replaceAll(“regex”, “replacement”); int len = s.length(); |
Erase => not present in java Strings are immutable |
| SubString | String sub = s.substring(start,end); String sub = s.substring(start); |
end exclusive from start to end |
|
| Convert to integer or Float |
int n = Integer.parseInt(“123”); float f = Float.parseFloat(“4.15"); |
Throws exception if not valid | |
| Convert to String | String s = String.valueOf(123); String s = Integer.toString(123); |
Convert to String | |
| reverse | StringBuilder sb = new StringBuilder(s); String rev = sb.reverse().toString(); |
Strings are immutable | |
| Split | String[] parts = s.split(” “); | Split by delimiter | |
| String Join | String.join(” “, array); | Join with delimiter | |
| Numbers | fixed + precision | System.out.printf(“%.2f”, 5.0123); | prints 5.01 for 5.0123 |
| Arrays | Init 1D | int[] arr = new int[5]; // All 0 int[] arr = {1,2,3,4,5}; |
|
| Init 2D | int[][] arr = new int[3][3]; int[][] arr = {{1,2,3}, {4,5,6}, {7,8,9}}; |
||
| fill 1D fill 2D Array |
Arrays.fill(arr, -1); for(int[] row : arr)Arrays.fill(row, -1); |
import java.util.Arrays | |
| Access | int val = arr[0]; | ||
| Clone array | int[] copy = arr.clone(); int[] copy = Arrays.copyOf(arr, arr.length); |
Shallow copy | |
| Sort and length | Arrays.sort(arr); int len = arr.length; | ||
| ArrayList | List Interface | List<Integer> list = List.of(1,2,3); ArrayList<Integer> list = new ArrayList<>(); List<Integer> list = new ArrayList<>(Arrays.asList(1,2,3)); // Clone of arrayList (shallow copy for 2-D , deep copy for 1-D ) List<Integer> sc = new ArrayList<>(list); list.add(element); int size = list.size() list.add(index, element); int val = list.get(i); list.set(i, newVal); list.remove(i); // by index list.remove(Integer.valueOf(x)); // by value list.contains(element); Collections.sort(list); Collections.sort(list,Collections.reverseOrder()); Integer[] arr = list.toArray(new Integer[0]); |
[1, 2, 3 ] For custom objects use .clone() for deep clone Dynamic -> Static array |
| Stack / Queue | List Interface | Stack<Integer> stack = new Stack<>(); Deque<Integer> stack = new ArrayDeque<>(); stack.push(element); int top = stack.pop(); int top = stack.peek(); stack.isEmpty() Queue<Integer> queue = new LinkedList<>(); Queue<Integer> queue = new ArrayDeque<>(); queue.offer(element); or queue.add(element); int front = queue.poll(); // null if empty int front = queue.remove(); // exception if empty int front = queue.peek(); queue.isEmpty() |
ArrayDeque is faster Preffered... |
| Priority Queue |
PriorityQueue<Integer> pq = new PriorityQueue<>(Collections.reverseOrder()); PriorityQueue<Integer> pq = new PriorityQueue<>(); pq.offer(element); or pq.add(element); int top = pq.poll(); int top = pq.peek(); PriorityQueue<String> pq = new PriorityQueue<>((a,b) -> a.length() - b.length()); |
MaxHeap MinHeap (Default) Custom Ordering.. |
|
| HashMap / HashSet | Map Interface | HashMap<String, Integer> map = new HashMap<>(); map.put(key, value); int val = map.get(key); int val = map.getOrDefault(key, 0); map.containsKey(key); map.containsValue(value); map.remove(key); for(Map.Entry<String, Integer> entry : map.entrySet()) {} for(String key : map.keySet()) {} HashSet<Integer> set = new HashSet<>(); set.add(element); set.remove(element); set.contains(element); |
O(1) on average operations Unique Elements |
| TreeMap / TreeSet |
TreeMap<Integer, String> tmap = new TreeMap<>(); TreeSet<Integer> tset = new TreeSet<>(); tset.lower(key); // largest < key tset.higher(key); // smallest > key tset.floor(key); // largest <= key tset.ceiling(key); // smallest >= key |
Keys sorted, O(log n) Elements sorted, O(log n) range queries Range operations |
|
| MATH | GCD(a,b) | public static int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); } |
Euclidean algorithm |
| int gcd = BigInteger.valueOf(a).gcd(BigInteger.valueOf(b)).intValue(); | import java.math.* BigInteger GCD |
||
| Math.abs(x), Math.max(a,b), Math.min(a,b) Math.pow(base, exp), Math.sqrt(x) |
Common Math operations | ||
| RANDOM | int rand = new Random().nextInt(100); | 0 to 99 | |
| Collections | Lower/ Upper bound | List<Integer> list = Arrays.asList(1,4,4,5,6); int pos = Collections.binarySearch(list, x); → If x exists ⇒ any index of x (in duplicate) → else -(inser_pos) - 1. where inser_pos is the index where the key would be inserted to keep order |
Binary Search in Java Better to use your own Upper Bound / Lower Bound |
| Array Sorting | Integer[] arr = {3, 1, 4, 1, 5}; Arrays.sort(arr, (a, b) -> b - a); // Descending |
Array Sorting | |
| List Sorting | Collections.sort(list, (a, b) -> a - b); // Ascending list.sort((a, b) -> b - a); // Descending | List Sorting | |
| Sort intervals by first element | Arrays.sort(intervals, (a, b) -> a[0] - b[0]); | ||
| String by length then lexicographical | Collections.sort(strings, (a, b) -> { if(a.length() != b.length()) return a.length() - b.length(); return a.compareTo(b); }); |
||
| BITS | Operators | & -> AND , ^ → XOR , << = left shift | –> OR , >> (right shift) , ~a (invert all bits of a) |
|
| SAME OPERATIONS AS IN C++..... | |||
| Number of Bits | Integer.toBinaryString(num).length() Or: 32 - Integer.numberOfLeadingZeros(num) |
Count bits | |
| Count Set bits | Integer.bitCount(num) | ||
| Range of bit numbers | int: -2^31 to 2^31-1 long: -2^63 to 2^63-1 |
Java ranges | |
| Extras | Pair<K,V> in Java | record Pair<U, V>(U first, V second) {} List<Pair<Integer,Integer>> v = new ArrayList<>(); v.add(new Pair<>(123,444)); v.add(new Pair<>(1,4)); sout(v.first()) , sout(v.second()); |
Record is an immutable class (Java 16) means fields are final. |
Leetcode Patterns - TBD
This section contains Leetcode Patterns and the general questions in that section. So if you have completed the above problems comfortably you can feel free to skip this.
3. 2 Pointers
6. BackTracking , BackTracking JAVA
—----------------------------------------
[Extras]
- Counting / radix
Patterns
If input array is sorted then → Binary search , Two pointers
If asked for all permutations/subsets then → Backtracking
If given a tree / graph then → DFS , BFS
If given a linked list then → Two pointers
If recursion is banned then → Stack
If must solve in-place then → Swap corresponding values
Store one or more different values in the same → Two pointer
If asked for maximum/minimum subarray/subset/options then → DP, Sliding window
If asked for top/least K items → Heap , QuickSelect
If asked for common strings then → Map , Trie
- Else
Map/Set for O(1) time & O(n) space
Sort input for O(nlogn) time and O(1) space