Arrays: Types and Essential Algorithmic Techniques
Arrays are fundamental data structures in computer programming, serving as the building blocks for numerous algorithms and applications. Whether you're a beginner programmer or an experienced developer preparing for coding interviews, a solid understanding of arrays and their associated techniques is crucial. In this post, we'll explore the types of arrays and delve into some powerful algorithmic techniques that leverage arrays to solve complex problems efficiently.
Types of Arrays
Arrays come in different dimensions, but we'll focus on the two most common types: one-dimensional and two-dimensional arrays.
a) One-Dimensional Arrays (1D Arrays)
A one-dimensional array is a linear collection of elements, each identified by an index or a key. It's the simplest form of an array and is often visualized as a row of boxes, each containing a value.
String[] fruits = {"apple", "banana", "cherry", "date"};
Key characteristics:
Elements are stored in contiguous memory locations.
Accessing elements is done using their index (usually starting from 0).
Insertion and deletion at the end take O(1) time, while at an arbitrary position it's O(n).
Searching in an unsorted array takes O(n) time.
b) Two-Dimensional Arrays (2D Arrays)
A two-dimensional array is essentially an array of arrays, often represented as a matrix or a table with rows and columns.
int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
Key characteristics:
Useful for representing grids, game boards, or mathematical matrices.
Accessing elements requires two indices: row and column.
Can be implemented as a single block of memory or as an array of pointers to arrays.
Time complexity for basic operations is generally O(1), but can be O(n) or O(n^2) for certain operations involving all elements.
Essential Array Techniques and Algorithms
Now that we've covered the types of arrays, let's explore some powerful techniques and algorithms that are frequently used with arrays.
a) Kadane's Algorithm
Kadane's algorithm is used to find the contiguous subarray within a one-dimensional array of numbers that has the largest sum.
How it works:
Initialization:
maxSum
starts with the first array element to handle all-negative arrays.curSum
starts at 0, representing the current subarray sum.
Iterative Process: For each element in the array:
Reset
curSum
to 0 if it's negative (abandoning unproductive subarrays).Add the current element to
curSum
.Update
maxSum
ifcurSum
is larger, tracking the best subarray sum.
Result: After processing all elements,
maxSum
contains the maximum subarray sum, which is then returned.public static int kadanes(int[] nums) { int maxSum = nums[0]; int curSum = 0; for (int n : nums) { curSum = Math.max(curSum, 0); curSum += n; maxSum = Math.max(maxSum, curSum); } return maxSum; }
Time complexity: O(n)
b) Sliding Window Technique
The sliding window technique is used to process arrays or strings by maintaining a "window" of elements and sliding it through the data structure.
i) Fixed-Size Sliding Window
In a fixed-size sliding window, the size of the window remains constant throughout the traversal.
Useful for problems involving subarrays or substrings of a fixed length.
Time complexity is often O(n), where n is the size of the array or string.
How it works:
Sliding Window Setup:
Use a HashSet (
window
) to track unique elements within a window of sizek
.Slide this window through the array using two pointers,
L
(left) andR
(right).
Window Management and Duplicate Check:
For each new element (at
R
):If the window size exceeds
k
, remove the leftmost element and moveL
.Check if the current element is already in the window. If so, return
true
.Otherwise, add the current element to the window.
Result:
- If no duplicates are found after processing all elements, return
false
.
- If no duplicates are found after processing all elements, return
// Check if array contains a pair of duplicate values,
public class SlidingWindowFixedSize {
public static boolean closeDuplicates(int[] nums, int k) {
HashSet<Integer> window = new HashSet<>(); // Cur window of size <= k
int L = 0;
for (int R = 0; R < nums.length; R++) {
if (R - L + 1 > k) {
window.remove(nums[L]);
L++;
}
if (window.contains(nums[R])) {
return true;
}
window.add(nums[R]);
}
return false;
}
}
ii) Variable-Size Sliding Window
In a variable-size sliding window, the size of the window can change during the traversal.
Useful for problems where the size of the subarray or substring is not fixed.
Often involves expanding and contracting the window based on certain conditions.
How it works:
Sliding Window Expansion:
Initialize a sliding window with left pointer
L
and right pointerR
.Expand the window by moving
R
to the right, adding each new element to thetotal
sum.
Window Contraction and Length Update:
When
total
becomes greater than or equal to thetarget
:Update
length
to the minimum of currentlength
and window size (R - L + 1
).Contract the window from the left by subtracting
nums[L]
fromtotal
and incrementingL
.Continue this contraction while
total
remains greater than or equal totarget
.
Result Handling:
After processing all elements, if
length
remainsInteger.MAX_VALUE
, return 0 (no valid subarray found).Otherwise, return the
length
of the shortest subarray meeting the condition.
// Find length of minimum size subarray where the sum is
// greater than or equal to the target: O(n)
public static int shortestSubarray(int[] nums, int target) {
int L = 0, total = 0;
int length = Integer.MAX_VALUE;
for (int R = 0; R < nums.length; R++) {
total += nums[R];
while (total >= target) {
length = Math.min(R - L + 1, length);
total -= nums[L];
L++;
}
}
if (length == Integer.MAX_VALUE) {
return 0;
}
return length;
}
}
d) 2 Pointers
The two pointers technique involves using two pointers to traverse an array or string, often moving in opposite directions or at different speeds.
Often used to solve problems with O(n) time complexity and O(1) space complexity.
Useful for problems involving searching, reversing, or finding pairs in sorted arrays.
How it works:
Initial Setup:
Two pointers are initialized:
L
at the start of the array (index 0) andR
at the end (last index).The input array
nums
is assumed to be sorted in ascending order.
Two-Pointer Iteration:
The function enters a while loop that continues as long as
L
is less thanR
.In each iteration, it calculates the sum of the elements at positions
L
andR
.
Sum Comparison and Pointer Adjustment:
If the sum is greater than the target,
R
is decremented to reduce the sum.If the sum is less than the target,
L
is incremented to increase the sum.If the sum exactly matches the target, the function immediately returns the current indices
[L, R]
.
Result Handling:
If the loop completes without finding a match (which shouldn't happen if there's guaranteed to be one solution), the function returns
null
.This approach efficiently finds the solution in O(n) time complexity by leveraging the sorted nature of the input array.
// Given a sorted array of integers, return the indices // of two elements (in different positions) that sum up to // the target value. Assume there is exactly one solution. // O(n) public class TwoPointer { public static int[] targetSum(int[] nums, int target) { int L = 0, R = nums.length - 1; while (L < R) { if (nums[L] + nums[R] > target) { R--; } else if (nums[L] + nums[R] < target) { L++; } else { return new int[] {L, R}; } } return null; }
c) Prefix Sum
Prefix sum is a technique where you precompute cumulative sums of array elements to efficiently answer range sum queries.
How it works:
Constructor and Prefix Sum Calculation:
The constructor takes an array of integers (
nums
) as input.It initializes an ArrayList
prefix
to store cumulative sums.It iterates through
nums
, calculating a running total and adding each cumulative sum toprefix
.
Prefix Sum Array Structure:
After construction,
prefix
contains cumulative sums whereprefix.get(i)
represents the sum of all elements from index 0 to i in the original array.This precomputation allows for efficient range sum queries.
Range Sum Calculation Method:
The
rangeSum
method calculates the sum of elements between indicesleft
andright
(inclusive).It uses the formula: sum(left to right) = prefix[right] - prefix[left-1]
A special case is handled when
left
is 0, avoiding index out of bounds.
Efficiency:
Construction time is O(n), where n is the length of the input array.
Each
rangeSum
query takes O(1) time, as it involves only simple arithmetic operations.
public class PrefixSum {
List<Integer> prefix;
public PrefixSum(int[] nums) {
prefix = new ArrayList<>();
int total = 0;
for (int n : nums) {
total += n;
prefix.add(total);
}
}
public int rangeSum(int left, int right) {
int preRight = prefix.get(right);
int preLeft = left > 0 ? prefix.get(left - 1) : 0;
return (preRight - preLeft);
}
}
Subscribe to my newsletter
Read articles from Sambhav Rana directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by