Mastering Array Data Structures in JavaScript: A Deep Dive into DSA Arrays
Arrays are an important part of programming. They are used to store and organize data in a specific order. In JavaScript, arrays are easy to use and come with many tools to help work with them. An array is a list where each item is automatically given a number, called an index, starting from zero. You can create an array using brackets []
and fill it with items separated by commas. You can look at any item in the array using its index or even change the value at a specific index. This makes arrays very useful and flexible for many tasks.
What is an Array?
An array is an ordered collection of elements, where each element is identified by a numerical index. Arrays are widely used for storing lists of data because of their simplicity and versatility.
Key Characteristics of Arrays:
- Indexing: Arrays use zero-based indexing.
- Order: The order of elements is maintained.
- Dynamic Size: In JavaScript, arrays can grow or shrink dynamically.
- Homogeneous and Heterogeneous: Arrays can store data of the same or mixed types.
Custom Implementation of an Array
Below is an example of a custom MyArray
class that mimics the behavior of JavaScript's native arrays. This helps understand the internal mechanics of arrays.
class MyArray {
constructor() {
this.length = 0;
this.data = {};
}
// Add an item to the end
push(item) {
this.data[this.length] = item;
this.length++;
return this.length;
}
// Access an item at a specific index
get(index) {
return this.data[index];
}
// Remove the last item
pop() {
if (this.length === 0) return undefined;
const lastItem = this.data[this.length - 1];
delete this.data[this.length - 1];
this.length--;
return lastItem;
}
// Remove the first item
shift() {
if (this.length === 0) return undefined;
const firstItem = this.data[0];
for (let i = 0; i < this.length - 1; i++) {
this.data[i] = this.data[i + 1];
}
delete this.data[this.length - 1];
this.length--;
return firstItem;
}
// Delete an item at a specific index
delete(index) {
if (index < 0 || index >= this.length) return undefined;
const item = this.data[index];
for (let i = index; i < this.length - 1; i++) {
this.data[i] = this.data[i + 1];
}
delete this.data[this.length - 1];
this.length--;
return item;
}
}
// Example Usage
const myArray = new MyArray();
myArray.push(10);
myArray.push(20);
myArray.push(30);
console.log(myArray.pop()); // 30
console.log(myArray.shift()); // 10
Common DSA Problems with Arrays
1. Reverse a String
Problem: Reverse the characters of a string.
const str = 'apple banana orange';
let rev = '';
for (let i = str.length - 1; i >= 0; i--) {
rev += str[i];
}
console.log(rev); // "egnaro ananab elppa"
2. Reverse an Array
Problem: Reverse the elements of an array.
Iterative Approach
const reverseArray = (arr) => {
let left = 0, right = arr.length - 1;
while (left < right) {
[arr[left], arr[right]] = [arr[right], arr[left]];
left++;
right--;
}
return arr;
};
console.log(reverseArray([1, 2, 3, 4])); // [4, 3, 2, 1]
Iterative Two-Pointer Approach (for loop)
const numArr = [10, 7, 0, 5, 0, 9, 16, 91, 22, 22, 0]
for (let i = 0; i < numArr.length / 2; i++) {
let temp = numArr[i];
numArr[i] = numArr[numArr.length - 1 - i];
numArr[numArr.length - 1 - i] = temp;
}
console.log(numArr);
Iterative Two-Pointer Approach (while loop)
const numArr = [10, 7, 0, 5, 0, 9, 16, 91, 22, 22, 0]
let left = 0;
let right = numArr.length - 1;
while (left < right) {
let temp = numArr[left];
numArr[left] = numArr[right];
numArr[right] = temp;
left++;
right--;
}
console.log(numArr);
Destructuring Assignment (for loop)
const numArr = [10, 7, 0, 5, 0, 9, 16, 91, 22, 22, 0]
for (let i = 0, j = numArr.length - 1; i < numArr.length / 2; i++, j--) {
[numArr[i], numArr[j]] = [numArr[j], numArr[i]];
}
console.log(numArr);
Recursive Approach
const reverseArrayRecursive = (arr, start, end) => {
if (start >= end) return arr;
[arr[start], arr[end]] = [arr[end], arr[start]];
return reverseArrayRecursive(arr, start + 1, end - 1);
};
const array = [1, 2, 3, 4, 5];
console.log(reverseArrayRecursive(array, 0, array.length - 1)); // [5, 4, 3, 2, 1]
3. Check if a String is a Palindrome
Problem: Check whether a given string reads the same forward and backward.
const isPalindrome = (str) => {
let reversed = '';
for (let i = str.length - 1; i >= 0; i--) {
reversed += str[i];
}
return str === reversed;
};
console.log(isPalindrome("cddc")); // true
4. Capitalize the First Letter of Each Word in a Sentence
Problem: Convert each word's first letter to uppercase.
const capitalizeWords = (sentence) => {
const words = sentence.split(' ');
return words.map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
};
console.log(capitalizeWords("pratap das")); // "Pratap Das"
5. Move All Zeroes to the End
Problem: Move all zeroes in an array to the end while maintaining the order of non-zero elements.
const moveZeroesToEnd = (arr) => {
let nonZeroIndex = 0;
for (let i = 0; i < arr.length; i++) {
if (arr[i] !== 0) {
arr[nonZeroIndex] = arr[i];
nonZeroIndex++;
}
}
for (let i = nonZeroIndex; i < arr.length; i++) {
arr[i] = 0;
}
return arr;
};
console.log(moveZeroesToEnd([1, 0, 2, 0, 3, 0])); // [1, 2, 3, 0, 0, 0]
6. Find the Second Largest Element
Problem: Find the second-largest element in an array.
const findSecondLargest = (arr) => {
let largest = -Infinity, secondLargest = -Infinity;
for (const num of arr) {
if (num > largest) {
secondLargest = largest;
largest = num;
} else if (num > secondLargest && num !== largest) {
secondLargest = num;
}
}
return secondLargest;
};
console.log(findSecondLargest([10, 20, 30, 40])); // 30
7. Rotate an Array
Problem: Rotate an array by d
positions to the left.
Iterative Approach
const rotateArray = (arr, d) => {
d %= arr.length;
return [...arr.slice(d), ...arr.slice(0, d)];
};
console.log(rotateArray([1, 2, 3, 4, 5], 2)); // [3, 4, 5, 1, 2]
Slicing and Concatenation Approach
function rotatedArray(array, position) {
// Handle cases where position > array.length
position = position % array.length;
const first = [];
const second = [];
// Collect elements before the position
for (let i = 0; i < position; i++) {
first.push(array[i]);
}
// Collect elements from the position to the end
for (let j = position; j < array.length; j++) {
second.push(array[j]);
}
// Return the concatenated rotated array
return [...second, ...first];
}
console.log(rotatedArray([7, 3, 9, 1], position=9))
Recursive Approach
const rotateArrayRecursive = (arr, d) => {
d %= arr.length;
if (d === 0) return arr;
const first = arr.shift();
arr.push(first);
return rotateArrayRecursive(arr, d - 1);
};
console.log(rotateArrayRecursive([2, 4, 6, 8, 10], 3)); // [8, 10, 2, 4, 6]
Types of Array Algorithms and Techniques in DSA
When working with arrays in Data Structures and Algorithms (DSA), mastering the various algorithmic techniques is crucial. Here's a breakdown of commonly used array algorithms and the techniques behind them:
Searching Algorithms
- Purpose: Locate an element within an array.
- Examples:
- Linear Search: Sequentially checks each element.
- Binary Search: Efficiently searches a sorted array by dividing the search range.
- Techniques:
- Linear: Iterate over all elements (
O(n)
). - Binary: Use divide-and-conquer (
O(log n)
).
- Linear: Iterate over all elements (
Sorting Algorithms
- Purpose: Arrange array elements in a specific order.
- Examples:
- Bubble Sort: Repeatedly swap adjacent elements if out of order.
- Quick Sort: Partition around a pivot.
- Merge Sort: Divide the array and merge sorted halves.
- Techniques:
- Comparison-based or divide-and-conquer methods.
- Complexity: Bubble (
O(n^2)
), Quick/Merge (O(n log n)
).
Two Pointer Technique
- Purpose: Solve problems efficiently by maintaining two pointers.
- Examples:
- Finding pairs with a given sum.
- Removing duplicates from a sorted array.
- Technique:
- Move two indices towards or away from each other based on conditions.
Sliding Window Technique
- Purpose: Handle problems involving subarrays.
- Examples:
- Maximum sum of a subarray of size
k
. - Longest substring without repeating characters.
- Maximum sum of a subarray of size
- Technique:
- Use a fixed or dynamic window to reduce redundant calculations (
O(n)
).
- Use a fixed or dynamic window to reduce redundant calculations (
Kadane's Algorithm
- Purpose: Find the maximum sum subarray.
- Technique:
- Maintain the maximum sum at each step, updating the global maximum (
O(n)
).
- Maintain the maximum sum at each step, updating the global maximum (
Prefix Sum / Cumulative Sum
- Purpose: Precompute sums for fast range queries.
- Examples:
- Subarray sum.
- Range queries.
- Technique:
- Use a prefix sum array and subtraction for constant-time sum retrieval.
Divide and Conquer
- Purpose: Solve problems by breaking them into subproblems.
- Examples:
- Maximum subarray problem.
- Merge Sort.
- Technique:
- Recursively divide the array and combine results.
Dynamic Programming on Arrays
- Purpose: Solve optimization problems using previously computed results.
- Examples:
- Longest Increasing Subsequence.
- Maximum sum of non-adjacent elements.
- Technique:
- Store results in a DP table to prevent redundant computation.
Greedy Algorithms
- Purpose: Solve problems by making the optimal local choice.
- Examples:
- Minimum jumps to reach the end.
- Interval scheduling.
- Technique:
- Use iterative steps to maximize or minimize results.
Matrix Manipulation (2D Arrays)
- Purpose: Handle multidimensional array problems.
- Examples:
- Rotate a matrix.
- Search in a sorted matrix.
- Technique:
- Traverse rows, columns, or diagonals, applying logic specific to the problem.
Hashing-Based Approaches
- Purpose: Use hash tables for fast lookups.
- Examples:
- Find duplicates.
- Subarray with zero sum.
- Technique:
- Use hash maps to store frequencies, prefix sums, or indices (
O(1)
average lookup).
- Use hash maps to store frequencies, prefix sums, or indices (
Conclusion
Understanding array algorithms and techniques not only deepens your knowledge of arrays in JavaScript but also equips you to solve a wide range of DSA problems efficiently. By mastering arrays and creating custom functions for them, you can tackle advanced coding challenges and address real-life applications effectively. Whether it's sorting, searching, or optimizing, arrays form the foundation for solving complex programming problems with confidence.