DSA
DSA Interview Questions and Answers
1. Find the First Non-Repeating Character in a String
Problem: Given a string s
, find the first non-repeating character and return its index. If none exists, return -1.
JavaScript:
function firstUniqChar(s) {
const freq = {};
for (let char of s) {
freq[char] = (freq[char] || 0) + 1;
}
for (let i = 0; i < s.length; i++) {
if (freq[s[i]] === 1) return i;
}
return -1;
}
Explanation: Count frequency of each character, then iterate to find the first character with a count of 1. Time: O(n), Space: O(1).
2. Two Sum Problem
Problem: Given an array of integers nums
and an integer target
, return indices of the two numbers that add up to the target.
JavaScript:
function twoSum(nums, target) {
const map = new Map();
for (let i = 0; i < nums.length; i++) {
const diff = target - nums[i];
if (map.has(diff)) return [map.get(diff), i];
map.set(nums[i], i);
}
}
Explanation: Hash map for quick lookup. Time: O(n), Space: O(n).
3. Move Zeroes to End
Problem: Move all zeroes to the end of the array while maintaining the relative order of non-zero elements.
JavaScript:
function moveZeroes(nums) {
let lastNonZero = 0;
for (let i = 0; i < nums.length; i++) {
if (nums[i] !== 0) {
[nums[lastNonZero], nums[i]] = [nums[i], nums[lastNonZero]];
lastNonZero++;
}
}
}
Explanation: In-place swapping technique. Time: O(n), Space: O(1).
4. Longest Substring Without Repeating Characters
JavaScript:
function lengthOfLongestSubstring(s) {
let map = new Map();
let maxLen = 0;
let start = 0;
for (let end = 0; end < s.length; end++) {
if (map.has(s[end])) {
start = Math.max(map.get(s[end]) + 1, start);
}
map.set(s[end], end);
maxLen = Math.max(maxLen, end - start + 1);
}
return maxLen;
}
Explanation: Sliding window with hash map. Time: O(n), Space: O(n).
5. Group Anagrams
JavaScript:
function groupAnagrams(strs) {
const map = {};
for (let str of strs) {
const sorted = str.split('').sort().join('');
if (!map[sorted]) map[sorted] = [];
map[sorted].push(str);
}
return Object.values(map);
}
Explanation: Use sorted string as key. Time: O(n * k log k), Space: O(n).
6. Valid Palindrome
JavaScript:
function isPalindrome(s) {
s = s.replace(/[^a-z0-9]/gi, '').toLowerCase();
let left = 0, right = s.length - 1;
while (left < right) {
if (s[left] !== s[right]) return false;
left++;
right--;
}
return true;
}
Explanation: Clean and compare from both ends. Time: O(n), Space: O(1).
7. Longest Common Prefix
JavaScript:
function longestCommonPrefix(strs) {
if (!strs.length) return '';
let prefix = strs[0];
for (let i = 1; i < strs.length; i++) {
while (strs[i].indexOf(prefix) !== 0) {
prefix = prefix.slice(0, -1);
}
}
return prefix;
}
Explanation: Reduce prefix one-by-one. Time: O(n * m), Space: O(1).
8. Rotate Array
JavaScript:
function rotate(nums, k) {
k %= nums.length;
reverse(nums, 0, nums.length - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.length - 1);
}
function reverse(arr, start, end) {
while (start < end) {
[arr[start], arr[end]] = [arr[end], arr[start]];
start++;
end--;
}
}
Explanation: Reverse parts to rotate. Time: O(n), Space: O(1).
9. Maximum Subarray (Kadane’s Algorithm)
JavaScript:
function maxSubArray(nums) {
let max = nums[0], sum = nums[0];
for (let i = 1; i < nums.length; i++) {
sum = Math.max(nums[i], sum + nums[i]);
max = Math.max(max, sum);
}
return max;
}
Explanation: Kadane’s algorithm. Time: O(n), Space: O(1).
10. Contains Duplicate
JavaScript:
function containsDuplicate(nums) {
const set = new Set();
for (let num of nums) {
if (set.has(num)) return true;
set.add(num);
}
return false;
}
Explanation: Use Set for fast lookup. Time: O(n), Space: O(n).
Section 2: Linked Lists
1. Reverse a Linked List
Problem: Given the head of a singly linked list, reverse it.
JavaScript:
function reverseList(head) {
let prev = null, curr = head;
while (curr) {
const next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
Explanation: Iteratively reverse the links. Time: O(n), Space: O(1).
2. Detect Cycle in a Linked List
Problem: Return true if the linked list has a cycle.
JavaScript:
function hasCycle(head) {
let slow = head, fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) return true;
}
return false;
}
Explanation: Floyd’s Tortoise and Hare. Time: O(n), Space: O(1).
3. Merge Two Sorted Lists
Problem: Merge two sorted linked lists and return the merged list.
JavaScript:
function ListNode(val, next = null) {
this.val = val;
this.next = next;
}
function mergeTwoLists(l1, l2) {
let dummy = new ListNode(-1);
let curr = dummy;
while (l1 && l2) {
if (l1.val < l2.val) {
curr.next = l1;
l1 = l1.next;
} else {
curr.next = l2;
l2 = l2.next;
}
curr = curr.next;
}
curr.next = l1 || l2;
return dummy.next;
}
Explanation: Iterative merge. Time: O(n + m), Space: O(1).
More Linked List questions and next sections coming up...
4. Remove N-th Node From End of List
JavaScript:
function removeNthFromEnd(head, n) {
let dummy = new ListNode(0);
dummy.next = head;
let fast = dummy, slow = dummy;
for (let i = 0; i <= n; i++) fast = fast.next;
while (fast) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
}
Explanation: Use two pointers to find the node before the one to be removed. Time: O(n), Space: O(1).
5. Find the Middle of Linked List
JavaScript:
function middleNode(head) {
let slow = head, fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
Explanation: Fast pointer moves twice as fast as slow. Time: O(n), Space: O(1).
6. Palindrome Linked List
JavaScript:
function isPalindrome(head) {
let slow = head, fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
}
let prev = null;
while (slow) {
const next = slow.next;
slow.next = prev;
prev = slow;
slow = next;
}
while (prev) {
if (head.val !== prev.val) return false;
head = head.next;
prev = prev.next;
}
return true;
}
Explanation: Reverse second half and compare with first. Time: O(n), Space: O(1).
7. Intersection of Two Linked Lists
JavaScript:
function getIntersectionNode(headA, headB) {
let a = headA, b = headB;
while (a !== b) {
a = a ? a.next : headB;
b = b ? b.next : headA;
}
return a;
}
Explanation: Equalize path lengths by switching heads. Time: O(n), Space: O(1).
8. Linked List Cycle II (Start of Cycle)
JavaScript:
function detectCycle(head) {
let slow = head, fast = head;
while (fast && fast.next) {
slow = slow.next;
fast = fast.next.next;
if (slow === fast) break;
}
if (!fast || !fast.next) return null;
slow = head;
while (slow !== fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
Explanation: After detecting a cycle, reset slow to head to find cycle start. Time: O(n), Space: O(1).
9. Remove Duplicates from Sorted List
JavaScript:
function deleteDuplicates(head) {
let current = head;
while (current && current.next) {
if (current.val === current.next.val) {
current.next = current.next.next;
} else {
current = current.next;
}
}
return head;
}
Explanation: Skip nodes with duplicate values. Time: O(n), Space: O(1).
10. Add Two Numbers (as Linked Lists)
JavaScript:
function addTwoNumbers(l1, l2) {
let dummy = new ListNode(0);
let curr = dummy;
let carry = 0;
while (l1 || l2 || carry) {
const sum = (l1?.val || 0) + (l2?.val || 0) + carry;
carry = Math.floor(sum / 10);
curr.next = new ListNode(sum % 10);
curr = curr.next;
l1 = l1?.next;
l2 = l2?.next;
}
return dummy.next;
}
Explanation: Simulate addition with carry handling. Time: O(n), Space: O(n).
Section 3: Stacks and Queues
1. Valid Parentheses
JavaScript:
function isValid(s) {
const stack = [];
const map = {
')': '(',
'}': '{',
']': '['
};
for (let char of s) {
if (!map[char]) {
stack.push(char);
} else {
if (stack.pop() !== map[char]) return false;
}
}
return stack.length === 0;
}
Explanation: Use stack to match brackets. Time: O(n), Space: O(n).
2. Min Stack
JavaScript:
class MinStack {
constructor() {
this.stack = [];
this.minStack = [];
}
push(x) {
this.stack.push(x);
const min = this.minStack.length === 0 ? x : Math.min(x, this.getMin());
this.minStack.push(min);
}
pop() {
this.stack.pop();
this.minStack.pop();
}
top() {
return this.stack[this.stack.length - 1];
}
getMin() {
return this.minStack[this.minStack.length - 1];
}
}
Explanation: Track min value in a parallel stack. Time: O(1) operations.
3. Evaluate Reverse Polish Notation
JavaScript:
function evalRPN(tokens) {
const stack = [];
for (let token of tokens) {
if (!isNaN(token)) {
stack.push(Number(token));
} else {
const b = stack.pop();
const a = stack.pop();
if (token === '+') stack.push(a + b);
else if (token === '-') stack.push(a - b);
else if (token === '*') stack.push(a * b);
else if (token === '/') stack.push(Math.trunc(a / b));
}
}
return stack.pop();
}
Explanation: Use a stack to evaluate expressions. Time: O(n), Space: O(n).
4. Implement Queue using Stacks
JavaScript:
class MyQueue {
constructor() {
this.stack1 = [];
this.stack2 = [];
}
push(x) {
this.stack1.push(x);
}
pop() {
if (!this.stack2.length) {
while (this.stack1.length) this.stack2.push(this.stack1.pop());
}
return this.stack2.pop();
}
peek() {
if (!this.stack2.length) {
while (this.stack1.length) this.stack2.push(this.stack1.pop());
}
return this.stack2[this.stack2.length - 1];
}
empty() {
return !this.stack1.length && !this.stack2.length;
}
}
Explanation: Use two stacks to simulate a queue. Time: Amortized O(1).
5. Implement Stack using Queues
JavaScript:
class MyStack {
constructor() {
this.queue = [];
}
push(x) {
this.queue.push(x);
for (let i = 0; i < this.queue.length - 1; i++) {
this.queue.push(this.queue.shift());
}
}
pop() {
return this.queue.shift();
}
top() {
return this.queue[0];
}
empty() {
return this.queue.length === 0;
}
}
Explanation: Rotate the queue to mimic stack LIFO behavior. Time: O(n) for push.
6. Daily Temperatures
JavaScript:
function dailyTemperatures(temperatures) {
const res = Array(temperatures.length).fill(0);
const stack = [];
for (let i = 0; i < temperatures.length; i++) {
while (stack.length && temperatures[i] > temperatures[stack[stack.length - 1]]) {
const prevIndex = stack.pop();
res[prevIndex] = i - prevIndex;
}
stack.push(i);
}
return res;
}
Explanation: Monotonic stack to track next warmer day. Time: O(n), Space: O(n).
7. Next Greater Element
JavaScript:
function nextGreaterElements(nums) {
const stack = [];
const res = new Array(nums.length).fill(-1);
for (let i = 0; i < nums.length * 2; i++) {
const num = nums[i % nums.length];
while (stack.length && nums[stack[stack.length - 1]] < num) {
res[stack.pop()] = num;
}
if (i < nums.length) stack.push(i);
}
return res;
}
Explanation: Circular array handling with a monotonic stack. Time: O(n), Space: O(n).
8. Largest Rectangle in Histogram
JavaScript:
function largestRectangleArea(heights) {
const stack = [], n = heights.length;
let maxArea = 0;
for (let i = 0; i <= n; i++) {
const h = i === n ? 0 : heights[i];
while (stack.length && h < heights[stack[stack.length - 1]]) {
const height = heights[stack.pop()];
const width = stack.length === 0 ? i : i - stack[stack.length - 1] - 1;
maxArea = Math.max(maxArea, height * width);
}
stack.push(i);
}
return maxArea;
}
Explanation: Use stack to manage height indices. Time: O(n), Space: O(n).
9. Sliding Window Maximum
JavaScript:
function maxSlidingWindow(nums, k) {
const deque = [], res = [];
for (let i = 0; i < nums.length; i++) {
if (deque.length && deque[0] <= i - k) deque.shift();
while (deque.length && nums[i] > nums[deque[deque.length - 1]]) deque.pop();
deque.push(i);
if (i >= k - 1) res.push(nums[deque[0]]);
}
return res;
}
Explanation: Maintain deque of max values’ indices. Time: O(n), Space: O(k).
10. Design a Circular Queue
JavaScript:
class MyCircularQueue {
constructor(k) {
this.queue = Array(k);
this.size = k;
this.front = 0;
this.rear = 0;
this.count = 0;
}
enQueue(value) {
if (this.isFull()) return false;
this.queue[this.rear] = value;
this.rear = (this.rear + 1) % this.size;
this.count++;
return true;
}
deQueue() {
if (this.isEmpty()) return false;
this.front = (this.front + 1) % this.size;
this.count--;
return true;
}
Front() {
return this.isEmpty() ? -1 : this.queue[this.front];
}
Rear() {
return this.isEmpty() ? -1 : this.queue[(this.rear - 1 + this.size) % this.size];
}
isEmpty() {
return this.count === 0;
}
isFull() {
return this.count === this.size;
}
}
Explanation: Use array with modulo indexing. Time: O(1) for all ops.
Next up: Section 4 - Trees and Graphs...
Section 4: Trees and Graphs
1. Binary Tree Inorder Traversal (Recursive & Iterative)
JavaScript:
function inorderTraversal(root) {
const res = [];
function dfs(node) {
if (!node) return;
dfs(node.left);
res.push(node.val);
dfs(node.right);
}
dfs(root);
return res;
}
function inorderTraversalIterative(root) {
const res = [], stack = [];
let curr = root;
while (curr || stack.length) {
while (curr) {
stack.push(curr);
curr = curr.left;
}
curr = stack.pop();
res.push(curr.val);
curr = curr.right;
}
return res;
}
Explanation:
-
Inorder traversal (Left → Node → Right) is used to get sorted order in BSTs.
-
Recursive approach uses depth-first traversal and implicit call stack.
-
Iterative approach manually simulates recursion using an explicit stack.
Time Complexity: O(n)
Space Complexity: O(h) (call stack or explicit stack, where h = height of the tree)
2. Maximum Depth of Binary Tree
JavaScript:
function maxDepth(root) {
if (!root) return 0;
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
Explanation:
-
This is a basic DFS problem.
-
At each node, we recursively compute the maximum depth of the left and right subtrees, and add 1.
-
Useful to understand recursive tree traversal and base case design.
Time Complexity: O(n)
Space Complexity: O(h)
3. Invert Binary Tree
JavaScript:
function invertTree(root) {
if (!root) return null;
[root.left, root.right] = [invertTree(root.right), invertTree(root.left)];
return root;
}
Explanation:
-
Recursively swap left and right child of every node.
-
Helps develop intuition about structural transformations on trees.
Time Complexity: O(n)
Space Complexity: O(h)
4. Diameter of Binary Tree
JavaScript:
function diameterOfBinaryTree(root) {
let diameter = 0;
function dfs(node) {
if (!node) return 0;
let left = dfs(node.left);
let right = dfs(node.right);
diameter = Math.max(diameter, left + right);
return 1 + Math.max(left, right);
}
dfs(root);
return diameter;
}
Explanation:
-
Diameter is the length of the longest path between any two nodes in the tree.
-
At each node, compute max depth of left and right subtrees, sum them, and update global maximum.
Time Complexity: O(n)
Space Complexity: O(h)
5. Same Tree Check
JavaScript:
function isSameTree(p, q) {
if (!p && !q) return true;
if (!p || !q || p.val !== q.val) return false;
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
Explanation:
-
Recursively compare structure and node values.
-
A common question to test recursive thinking and base case design.
Time Complexity: O(n)
Space Complexity: O(h)
6. Lowest Common Ancestor (LCA)
JavaScript:
function lowestCommonAncestor(root, p, q) {
if (!root || root === p || root === q) return root;
const left = lowestCommonAncestor(root.left, p, q);
const right = lowestCommonAncestor(root.right, p, q);
return !left ? right : !right ? left : root;
}
Explanation:
-
LCA is the deepest node that has both p and q as descendants.
-
Use post-order DFS. If p and q are found in different subtrees, current node is the LCA.
Time Complexity: O(n)
Space Complexity: O(h)
7. Level Order Traversal (BFS)
JavaScript:
function levelOrder(root) {
const res = [], queue = [];
if (root) queue.push(root);
while (queue.length) {
const level = [], n = queue.length;
for (let i = 0; i < n; i++) {
const node = queue.shift();
level.push(node.val);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
res.push(level);
}
return res;
}
Explanation:
-
Breadth-first search to traverse level by level.
-
Use a queue to store nodes of current level.
Time Complexity: O(n)
Space Complexity: O(n)
8. Right Side View of Binary Tree
JavaScript:
function rightSideView(root) {
const res = [], queue = [];
if (root) queue.push(root);
while (queue.length) {
let size = queue.length;
for (let i = 0; i < size; i++) {
const node = queue.shift();
if (i === size - 1) res.push(node.val);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
}
return res;
}
Explanation:
-
Track the last node of each level in level-order traversal.
-
That last node is what you see from the right.
Time Complexity: O(n)
Space Complexity: O(n)
9. Detect Cycle in Undirected Graph (Union Find)
JavaScript:
function hasCycle(edges, n) {
const parent = Array(n).fill(0).map((_, i) => i);
function find(x) {
if (x !== parent[x]) parent[x] = find(parent[x]);
return parent[x];
}
function union(x, y) {
const rootX = find(x), rootY = find(y);
if (rootX === rootY) return false;
parent[rootX] = rootY;
return true;
}
for (const [u, v] of edges) {
if (!union(u, v)) return true;
}
return false;
}
Explanation:
-
Use Union-Find (Disjoint Set Union) to detect if adding an edge connects two nodes already in the same set.
-
If yes, it forms a cycle.
Time Complexity: O(E * α(N)) where α is inverse Ackermann.
Space Complexity: O(N)
10. Topological Sort (Kahn's Algorithm)
JavaScript:
function topologicalSort(numCourses, prerequisites) {
const graph = Array.from({ length: numCourses }, () => []);
const indegree = Array(numCourses).fill(0);
for (let [u, v] of prerequisites) {
graph[v].push(u);
indegree[u]++;
}
const queue = [], res = [];
for (let i = 0; i < numCourses; i++) {
if (indegree[i] === 0) queue.push(i);
}
while (queue.length) {
const node = queue.shift();
res.push(node);
for (let nei of graph[node]) {
indegree[nei]--;
if (indegree[nei] === 0) queue.push(nei);
}
}
return res.length === numCourses ? res : [];
}
Explanation:
-
Topological sort orders nodes in a Directed Acyclic Graph (DAG) such that for any edge (u → v), u appears before v.
-
Kahn’s algorithm repeatedly removes nodes with zero indegree and updates their neighbors.
Time Complexity: O(V + E)
Space Complexity: O(V + E)