HomeBytesheetsRoadmapsUI TemplatesUI ToolsStack Discussion
20 Topics
Chapter•180 Minutes

Easy DSA Questions — Strings, Numbers, and Basic Algorithms

Continue with beginner-friendly problems involving strings, digits, comparisons, transformations, and simple mathematical reasoning.

01. Reverse a String

Difficulty:EasyPattern:String TraversalTime:O(n)Space:O(n)

Reverse a given input string.

How It Works & Algorithmic Design

Strings can be processed using either Built-In Language Methods or an In-Place Two-Pointer Character Array Swap:

  1. Two-Pointer Approach: Convert the string into a mutable character array. Place left = 0 and right = length - 1. Swap characters while incrementing left and decrementing right until they meet in the middle.
  2. Language Idioms: Languages like Python (s[::-1]) or Java (StringBuilder.reverse()) offer optimized native operations.

Why This Approach Works Best

Both methods run in linear time $O(n)$. Understanding the manual pointer-swap mechanism is essential for interviews, especially when working with languages that treat strings as immutable.

Example Walkthrough
Input String: "hello"
5-character string.
Reversal Steps: 'h' <-> 'o' => "oellh" -> 'e' <-> 'l' => "olleh"
Swaps characters from outside inward.
Output String: "olleh"
Reversed string.

Answer: Reverse string implementations

function reverseString(s) {
  return s.split('').reverse().join('');
}
💡 Demonstrates built-in helpers and low-level character array transformations.
Key Takeaways
  • String immutability in languages like Java, JS, and Python requires allocating a new string ($O(n)$ space).
  • The underlying algorithm relies on the converging two-pointer swap pattern.
  • Linear time complexity $O(n)$ is needed to process all $n$ characters.

02. Check Whether a String Is a Palindrome

Difficulty:EasyPattern:Two PointersTime:O(n)Space:O(1)

Determine whether a string reads the same forward and backward.

How It Works & Algorithmic Design

This uses the Symmetric Two-Pointer Check:

  1. Pointer Positions: Set left = 0 and right = s.length - 1.
  2. Symmetry Test: Compare s[left] and s[right]:
    • If s[left] !== s[right], the string is not symmetrical. Immediately return false.
    • Otherwise, advance left++ and retreat right-- toward the middle.
  3. Completion: If pointers cross (left >= right) without a character mismatch, return true.

Why This Approach Works Best

Rather than reversing the entire string (which takes extra space and time), this method checks characters in-place and stops early on the first mismatch. It processes at most $n/2$ comparisons, achieving $O(n)$ time and $O(1)$ extra space.

Example Walkthrough 1 (Palindrome)
Input: "racecar"
Odd-length palindrome string.
Comparisons: 'r'=='r' -> 'a'=='a' -> 'c'=='c' -> Center reached ('e')
All symmetric character pairs match.
Output: true
String is a valid palindrome.
Example Walkthrough 2 (Non-Palindrome)
Input: "hello"
Non-palindrome string.
Comparisons: 'h' != 'o' (Mismatch found instantly)
Terminates on the first comparison.
Output: false
String is not a palindrome.

Answer: Two-pointer palindrome check

function isPalindrome(s) {
  let left = 0, right = s.length - 1;
  while (left < right) {
    if (s[left] !== s[right]) return false;
    left++;
    right--;
  }
  return true;
}
💡 Compare symmetric characters from both ends moving inward toward the center.
Key Takeaways
  • Checks symmetry using two converging pointers.
  • Early termination avoids unnecessary comparisons when a mismatch is found.
  • Operates in $O(n)$ time and $O(1)$ extra space.

03. Count Vowels in a String

Difficulty:EasyPattern:Set Lookup CountingTime:O(n)Space:O(1)

Count the number of vowels (a, e, i, o, u - case insensitive) in a given string.

How It Works & Algorithmic Design

This applies Set-Based Lookups within a Linear Character Pass:

  1. Vowel Set Lookup: Store target vowels (aeiouAEIOU) in a Hash Set or string lookup table. Checking existence in a Hash Set takes constant time ($O(1)$).
  2. Counting Loop: Iterate over every character ch in the string.
  3. Evaluation: If vowelSet.has(ch), increment count++.
  4. Return: Return the accumulated count.

Why This Approach Works Best

Using a set lookup avoids writing long if-else condition chains. The linear traversal processes the string in $O(n)$ time complexity while requiring only $O(1)$ auxiliary space for the fixed 10-character vowel set.

Example Walkthrough
Input String: "Algorithm"
Mixed case string.
Vowel Checks: 'A' (Yes, count=1) -> 'o' (Yes, count=2) -> 'i' (Yes, count=3)
Matches 'A', 'o', and 'i'.
Output Count: 3
Contains 3 vowels.

Answer: Count vowels using set lookup

function countVowels(s) {
  const vowels = new Set('aeiouAEIOU');
  let count = 0;
  for (const ch of s) {
    if (vowels.has(ch)) count++;
  }
  return count;
}
💡 Inspect characters against a fixed vowel set and accumulate matching counts.
Key Takeaways
  • Hash Set lookups take $O(1)$ constant time.
  • Case sensitivity should be handled by including both uppercase and lowercase vowels in the set.
  • Runs in $O(n)$ linear time relative to string length.

04. Find the Frequency of Each Character

Difficulty:EasyPattern:Hash Map Frequency CountingTime:O(n)Space:O(k)

Construct a frequency table mapping each character in a string to its total number of occurrences.

How It Works & Algorithmic Design

This relies on Hash Map Frequency Counting:

  1. Hash Map Setup: Initialize an empty key-value Hash Map freqMap.
  2. Character Scanning: Iterate through each character ch in the string.
  3. Map Update:
    • If ch exists as a key in freqMap, increment its associated value (freqMap[ch] + 1).
    • If ch is not in freqMap, insert it with an initial value of 1 (freqMap[ch] = 1).
  4. Result: Return the populated frequency map.

Why This Approach Works Best

Hash Map insertions and lookups take $O(1)$ average time. Scanning the string of length $n$ takes $O(n)$ time. The space complexity is $O(k)$, where $k$ is the number of unique characters in the alphabet (at most $O(1)$ for fixed character sets like ASCII or lowercase English letters).

Example Walkthrough
Input String: "banana"
6-character string with repeating letters.
Step-by-Step Map Updates: 'b': 1 -> 'a': 1 -> 'n': 1 -> 'a': 2 -> 'n': 2 -> 'a': 3
Updates key counts as characters are scanned.
Output Frequency Table: { 'b': 1, 'a': 3, 'n': 2 }
Final counts for each unique character.

Answer: Build character frequency table

function charFrequency(s) {
  const freq = {};
  for (const ch of s) {
    freq[ch] = (freq[ch] || 0) + 1;
  }
  return freq;
}
💡 Populate a Hash Map by using characters as keys and their counts as values.
Key Takeaways
  • Hash Maps provide average $O(1)$ insertion and lookup times for frequency counting.
  • Frequency maps serve as building blocks for solving complex string problems like Anagram Detection.
  • Time complexity is $O(n)$, and space complexity is bounded by the size of the unique character set ($O(k)$).

05. Check if Two Strings are Anagrams

Difficulty:EasyPattern:Frequency MapTime:O(n)Space:O(1)

Given two strings s and t, return true if t is an anagram of s, and false otherwise.

How It Works & Algorithmic Design

An anagram contains the exact same characters with the exact same frequencies, reordered.

  1. Length Pre-Check: If s.length !== t.length, they cannot be anagrams. Return false immediately.
  2. Frequency Map: Traverse string s and record character counts in a Hash Map (or a fixed 26-element array for lowercase English letters).
  3. Frequency Verification: Traverse string t. For each character, check if it exists in the map with a count $> 0$. Decrement its count in the map. If a character is missing or its count reaches negative, return false.
  4. Completion: If all characters in t match and exhaust the counts in s, return true.

Why This Approach Works Best

Sorting both strings takes $O(n \log n)$ time. Using a frequency map checks anagrams in $O(n)$ time complexity and $O(k)$ space (where $k$ is the alphabet size, $O(1)$ for fixed character sets).

Example Walkthrough 1
Input: s = "listen", t = "silent"
Same letters, different order.
Execution Trace: s map: {l:1, i:1, s:1, t:1, e:1, n:1} -> t matches every key count exactly.
Frequency counts balance out to zero.
Output: true
Valid anagram pair.

Answer: Anagram verification using frequency map

function isAnagram(s, t) {
  if (s.length !== t.length) return false;
  const count = {};
  for (const ch of s) count[ch] = (count[ch] || 0) + 1;
  for (const ch of t) {
    if (!count[ch]) return false;
    count[ch]--;
  }
  return true;
}
💡 Count character occurrences in the first string and verify them against the second string.
Key Takeaways
  • Always check string lengths first for instant early termination.
  • Frequency map eliminates the need for $O(n \log n)$ sorting operations.
  • For lowercase ASCII strings, a fixed 26-element array can replace a dynamic hash table for better performance.

06. First Non-Repeating Character in a String

Difficulty:EasyPattern:Two-Pass Hash MapTime:O(n)Space:O(1)

Find the first non-repeating character in a string and return its index. If it does not exist, return -1.

How It Works & Algorithmic Design

This requires a Two-Pass Algorithm using a Frequency Map:

  1. First Pass (Counting): Iterate through the string from index $0$ to $n-1$ and build a frequency dictionary mapping each character to its occurrence count.
  2. Second Pass (Position Lookup): Iterate through the string a second time from left to right. Look up each character's count in the frequency map. The first character with a count equal to 1 is our answer; return its current loop index.
  3. Fallback: If all characters have counts $> 1$, return -1.

Why This Approach Works Best

Checking every character against the rest of the string using nested loops takes $O(n^2)$ time. The two-pass Hash Map pattern reduces the time complexity to optimal linear $O(n)$ while taking $O(1)$ space for fixed alphabets.

Example Walkthrough
Input: "leetcode"
Target string.
Pass 1 (Counts): { l:1, e:3, t:1, c:1, o:1, d:1 }
Frequency table built.
Pass 2 (Lookup): Index 0 = 'l', count is 1 -> Found!
Returns index 0.
Output: 0
First unique character is 'l' at index 0.

Answer: First unique character search

function firstUniqChar(s) {
  const count = {};
  for (const ch of s) count[ch] = (count[ch] || 0) + 1;
  for (let i = 0; i < s.length; i++) {
    if (count[s[i]] === 1) return i;
  }
  return -1;
}
💡 Build frequency counts first, then locate the first character with a count of 1.
Key Takeaways
  • Two-pass linear scans preserve original order while accumulating global frequencies.
  • Time complexity is $O(2n) = O(n)$.
  • Space complexity remains $O(1)$ when bounded by character set limits (e.g., ASCII/English letters).

07. Two Sum

Difficulty:EasyPattern:Hash Map ComplementTime:O(n)Space:O(n)

Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.

How It Works & Algorithmic Design

This is solved using the Hash Map Complement Pattern:

  1. Mathematical Relation: For any element $x = \text{nums}[i]$, its required complementary pair is $y = \text{target} - x$.
  2. Hash Map Storage: Maintain a Hash Map where key = array element value and value = array index.
  3. Single Pass: Loop through nums using index $i$:
    • Compute complement = target - nums[i].
    • Check if complement exists in the Hash Map.
    • If it exists, return [hashMap.get(complement), i].
    • If not, insert hashMap.put(nums[i], i) and continue.

Why This Approach Works Best

Brute force nested loops take $O(n^2)$ time. Sorting followed by two pointers takes $O(n \log n)$ time and loses original indices. Hash Map lookup gives optimal $O(n)$ runtime and $O(n)$ space complexity.

Example Walkthrough
Input: nums = [2, 7, 11, 15], target = 9
Target sum is 9.
Step 0 (i=0, num=2): Complement = 9 - 2 = 7. Map is empty -> Store {2: 0}
No complement yet.
Step 1 (i=1, num=7): Complement = 9 - 7 = 2. Found 2 in map at index 0!
Match found.
Output: [0, 1]
Indices of values 2 and 7.

Answer: Two Sum using complement Hash Map

function twoSum(nums, target) {
  const map = new Map();
  for (let i = 0; i < nums.length; i++) {
    const complement = target - nums[i];
    if (map.has(complement)) {
      return [map.get(complement), i];
    }
    map.set(nums[i], i);
  }
  return [];
}
💡 Look up the target complement in a Hash Map during a single pass traversal.
Key Takeaways
  • Store visited elements in a map to turn $O(n^2)$ pair searches into $O(1)$ lookups.
  • Calculating complement = target - current avoids nested loops.
  • Optimal single-pass runtime of $O(n)$ time and $O(n)$ space.

08. Find the Missing Number in an Array

Difficulty:EasyPattern:Math / Bit ManipulationTime:O(n)Space:O(1)

Given an array nums containing $n$ distinct numbers in the range $[0, n]$, return the only number in the range that is missing from the array.

How It Works & Algorithmic Design

This problem can be solved mathematically using Gauss's Summation Formula or bit manipulation with XOR:

  1. Mathematical Sum Method:
    • The sum of integers from $0$ to $n$ is calculated using the formula $S_n = \frac{n \cdot (n + 1)}{2}$.
    • Calculate the actual sum of elements in nums.
    • The missing number is simply $S_n - \text{actualSum}$.
  2. Bit Manipulation (XOR Method):
    • XORing a number with itself yields zero ($a \oplus a = 0$).
    • XOR all indices from $0$ to $n$ together with all values in nums. Duplicate numbers cancel out, leaving only the missing number.

Why This Approach Works Best

Both methods run in $O(n)$ time complexity and $O(1)$ space without requiring array sorting ($O(n \log n)$) or extra hash sets ($O(n)$ space).

Example Walkthrough
Input: [3, 0, 1] (n = 3)
Expected range [0, 1, 2, 3].
Expected Sum Formula: (3 * 4) / 2 = 6
Sum of numbers from 0 to 3.
Actual Sum: 3 + 0 + 1 = 4
Sum of given input array.
Output: 6 - 4 = 2
The missing number is 2.

Answer: Find missing number using Gauss's Sum

function missingNumber(nums) {
  const n = nums.length;
  const expectedSum = (n * (n + 1)) / 2;
  const actualSum = nums.reduce((acc, val) => acc + val, 0);
  return expectedSum - actualSum;
}
💡 Subtract the array sum from the expected arithmetic progression total.
Key Takeaways
  • Gauss's summation formula $n(n+1)/2$ finds expected totals in $O(1)$ arithmetic steps.
  • XOR method prevents potential integer overflow in languages with fixed integer limits.
  • Both strategies achieve optimal $O(n)$ time and $O(1)$ space.

09. Move Zeroes to the End of Array

Difficulty:EasyPattern:Two Pointers / In-Place ShiftTime:O(n)Space:O(1)

Given an integer array nums, move all 0s to the end of it while maintaining the relative order of the non-zero elements in-place.

How It Works & Algorithmic Design

This uses a Two-Pointer Write Boundary approach:

  1. Pointer Definition: Maintain a insertPos pointer initialized to index 0.
  2. Pass 1 (Shift Non-Zeroes): Iterate through the array with pointer $i$. Whenever nums[i] !== 0, place nums[insertPos] = nums[i] and increment insertPos++.
  3. Pass 2 (Fill Zeroes): Once all non-zero elements are moved to the front, loop from insertPos to the end of the array and set nums[i] = 0.

Why This Approach Works Best

This solves the problem in-place without creating a duplicate array ($O(1)$ space). It executes in linear $O(n)$ time complexity.

Example Walkthrough
Input: [0, 1, 0, 3, 12]
Array with scattered zeroes.
After Non-Zero Shift: [1, 3, 12, 3, 12] (insertPos = 3)
Non-zero values shifted to indices 0, 1, 2.
After Zero Fill: [1, 3, 12, 0, 0]
Indices 3 to 4 filled with zeroes.
Output: [1, 3, 12, 0, 0]
Zeroes pushed to end in-place.

Answer: Move zeroes in-place

function moveZeroes(nums) {
  let insertPos = 0;
  for (let i = 0; i < nums.length; i++) {
    if (nums[i] !== 0) {
      nums[insertPos++] = nums[i];
    }
  }
  while (insertPos < nums.length) {
    nums[insertPos++] = 0;
  }
  return nums;
}
💡 Shift non-zero items to the front, then backfill remaining indices with zeroes.
Key Takeaways
  • Maintains relative order of non-zero elements by sequentially writing to insertPos.
  • Modifies original array reference directly ($O(1)$ space).
  • Linear time complexity $O(n)$ with two sequential simple loops.

10. Find Intersection of Two Arrays

Difficulty:EasyPattern:Hash Set LookupTime:O(n + m)Space:O(n)

Given two integer arrays nums1 and nums2, return an array of their unique intersection elements.

How It Works & Algorithmic Design

This problem uses Hash Set Intersection Filtering:

  1. Set Population: Convert nums1 into a Hash Set set1. Hash Sets store only unique elements and provide $O(1)$ membership checks.
  2. Filtering: Initialize a second empty result set resultSet.
  3. Intersection Pass: Iterate through elements in nums2. For every number $x$, if set1.has(x), insert $x$ into resultSet.
  4. Return: Convert resultSet back into an array list.

Why This Approach Works Best

Checking every element of nums2 against nums1 using nested loops takes $O(n \times m)$ time. Using a Hash Set reduces total execution time to linear $O(n + m)$ where $n$ and $m$ are the lengths of the two arrays.

Example Walkthrough
Inputs: nums1 = [1, 2, 2, 1], nums2 = [2, 2]
Arrays with duplicate entries.
Set 1 Construction: { 1, 2 }
Unique values from nums1.
Intersection Check: 2 in Set 1? Yes -> Add 2 to result.
Result set ensures uniqueness.
Output: [2]
Unique common elements.

Answer: Array intersection using Hash Sets

function intersection(nums1, nums2) {
  const set1 = new Set(nums1);
  const result = new Set();
  for (const num of nums2) {
    if (set1.has(num)) result.add(num);
  }
  return Array.from(result);
}
💡 Use Hash Sets to find common elements in linear time.
Key Takeaways
  • Hash Sets eliminate duplicate results automatically.
  • Linear time performance $O(n + m)$ using $O(1)$ set membership checks.
  • Space complexity is proportional to unique elements stored in memory ($O(n)$).