1. Introduction
In this tutorial, we’ll explore different approaches to finding the second smallest element in an array using Java.
2. Problem Statement
Given an array of integers, the task is to find the second smallest element within the array. This value represents the second-lowest integer present in the array, assuming there are at least two distinct elements.
There are two circumstances where no second smallest element can be found in an array:
- If the input array is empty (length 0) or contains only one element, there’s no second smallest element to identify.
- If all elements in the array are identical, there’s no distinct second smallest element.
In such cases, we’ll return -1 to indicate no second smallest number was found in the given array.
Let’s take a look at the examples below:
Input: [4, 2, 8, 1, 3] Output: 2
Input: [1, 1, 1] Output: -1
Input: [1] Output: -1
Input: [] Output: -1
3. Using Array Sorting
One straightforward approach is to sort the array in the ascending order and then return the second element, which will be the second smallest integer. Here’s the code to find the second smallest integer in an array by sorting the array:
int[] arr = {5, 2, 9, 1, 7};
Arrays.sort(arr);
result = arr[1];
However, this approach has a limitation. If the array contains duplicate elements (e.g., {5, 2, 9, 1, 7, 1}), directly accessing the second element after sorting might not give the second smallest distinct element.
To address duplicates, we can modify the sorting approach:
int usingArraySort(int[] arr) {
Arrays.sort(arr);
int smallest = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] != smallest) {
return arr[i];
}
}
return -1;
}
This modified approach iterates through the sorted array after sorting. It keeps track of the smallest element encountered so far. If it encounters a distinct element (different from the current smallest), it updates the result with that element and breaks the loop. This ensures it finds the second smallest distinct element:
assertEquals(4, usingArraySort(new int[] {5, 3, 8, 9, 6, 8, 4, 4}));
assertEquals(-1, usingArraySort(new int[] {5}));
assertEquals(3, usingArraySort(new int[] {5, 3}));
assertEquals(-1, usingArraySort(new int[] {5, 5, 5, 5, 5}));
Sorting the array is a familiar concept for beginners, and the logic is straightforward. However, Arrays.sort() typically has a time complexity of O(n log n) in the average and worst cases, where n is the number of elements in the array. This can be inefficient for large arrays.
4. Using Single Pass Through
This approach efficiently finds the second smallest element in an array by iterating through it only once. It avoids the overhead of sorting and leverages conditional statements to update potential candidates for the smallest and second smallest elements.
Here’s the code for this approach:
int usingSinglePassThrough(int[] arr) {
int smallest = Integer.MAX_VALUE;
int secondSmallest = Integer.MAX_VALUE;
for (int num : arr) {
if (num < smallest) {
secondSmallest = smallest;
smallest = num;
} else if (num < secondSmallest && num != smallest) {
secondSmallest = num;
}
}
if (secondSmallest == Integer.MAX_VALUE) {
return -1;
} else {
return secondSmallest;
}
}
- smallest: initialized to Integer.MAX_VALUE to keep track of the current smallest element encountered so far
- secondSmallest: initialized to Integer.MAX_VALUE, it potentially holds the second smallest element
The code iterates through the array using a for-each loop. If the current element num is less than the smallest, it becomes the new smallest, and the previous smallest is assigned to secondSmallest. This ensures we track both the smallest and potential second smallest elements.
If num is less than the current secondSmallest but not equal to smallest (to avoid considering the same element twice), it becomes the new secondSmallest. After the loop, if secondSmallest remains Integer.MAX_VALUE, it means that all elements are equal, and no element smaller than the initial smallest was found.
In this case, -1 is returned. Otherwise, the final value stored in secondSmallest is returned as the second smallest element:
assertEquals(4, usingSinglePassThrough(new int[] {5, 3, 8, 9, 6, 8, 4, 4}));
assertEquals(-1, usingSinglePassThrough(new int[] {5}));
assertEquals(3, usingSinglePassThrough(new int[] {5, 3}));
assertEquals(-1, usingSinglePassThrough(new int[] {5, 5, 5, 5, 5}));
In the worst-case scenario, the loop executes for all elements in the array. Therefore, the time complexity of this approach is O(n), which signifies a linear relationship between the number of elements and the execution time. Overall, this approach avoids sorting the entire array, making it potentially faster, especially for larger arrays.
5. Using Min Heap
This approach leverages a min-heap data structure to efficiently find the second smallest element in an array. A min-heap is a priority queue where the element with the minimum value always resides at the root. By strategically manipulating the heap size, we can ensure it holds the smallest and potentially the second smallest elements.
Here’s the code implementing this approach:
int usingMinHeap(int[] arr) {
if (arr.length < 2) {
return -1;
}
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
for (int num : arr) {
if (minHeap.isEmpty() || num != minHeap.peek()) {
minHeap.offer(num);
}
}
// all elements were the same if minHeap size is less than 2
if (minHeap.size() < 2) {
return -1;
}
minHeap.poll(); // Remove the smallest element
return minHeap.peek(); // Second smallest element is at the root
}
We first check if the array length is less than 2. If so, we return -1 as there’s no second smallest element. Next, we create a PriorityQueue object minHeap. By default, PriorityQueue implements a min-heap, so the element with the minimum value is at the root.
We iterate through the array, adding each element to the minHeap using offer(). In each iteration, we consider two conditions:
- If the heap is empty, any element can be added as there’s no smaller element yet.
- If the current element is distinct from the smallest element in the heap, it can be added. This ensures that duplicate elements with the same value as the smallest aren’t added multiple times.
After processing the entire array, we check if the minHeap size is less than 2. This indicates that all elements were the same. In this case, we return -1 as there’s no second smallest.
Otherwise, we remove the smallest element from the min-heap using poll() and return the second smallest element from the root of the min-heap using peek().
Let’s validate our solution using the test cases:
assertEquals(4, usingMinHeap(new int[] {5, 3, 8, 9, 6, 8, 4, 4}));
assertEquals(-1, usingMinHeap(new int[] {5}));
assertEquals(3, usingMinHeap(new int[] {5, 3}));
assertEquals(-1, usingMinHeap(new int[] {5, 5, 5, 5, 5}));
This approach can be more efficient than sorting for larger arrays. However, for very small arrays, the overhead of creating and maintaining the min-heap might be less efficient compared to simpler approaches.
The min-heap approach can be considered to have an average and worst-case time complexity of O(n), which is more efficient than sorting algorithms.
6. Conclusion
In this article, we’ve explored a few approaches to finding the second smallest number in an array. Sorting the entire array might be suitable for smaller datasets as it’s a familiar concept. However, for larger datasets, single pass through or min heap are better solutions.
As always, the source code for the examples is available over on GitHub.