Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 5b2f692

Browse files
committed
LeetCode problem: 4. Median of Two Sorted Arrays
1 parent e564846 commit 5b2f692

File tree

3 files changed

+341
-0
lines changed

3 files changed

+341
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# 4. Median of Two Sorted Arrays
2+
3+
Difficulty: `Hard`
4+
Topics: `Array`, `Binary Search`, `Divide and Conquer`
5+
6+
Given two sorted arrays `nums1` and `nums2` of size `m` and `n` respectively, return **the median** of the two sorted arrays.
7+
8+
The overall run time complexity should be `O(log (m+n))`.
9+
10+
**Example 1:**
11+
12+
```text
13+
Input: nums1 = [1,3], nums2 = [2]
14+
Output: 2.00000
15+
Explanation: merged array = [1,2,3] and median is 2.
16+
```
17+
18+
```text
19+
Input: nums1 = [1,2], nums2 = [3,4]
20+
Output: 2.50000
21+
Explanation: merged array = [1,2,3,4] and median is (2 + 3) / 2 = 2.5.
22+
```
23+
24+
**Constraints:**
25+
26+
- `nums1.length == m`
27+
- `nums2.length == n`
28+
- `0 <= m <= 1000`
29+
- `0 <= n <= 1000`
30+
- `1 <= m + n <= 2000`
31+
- `-10^6 <= nums1[i], nums2[i] <= 10^6`
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package com.bl.median_of_two_sorted_arrays;
2+
3+
/**
4+
* This is the solution to the LeetCode problem: 4. Median of Two Sorted Arrays
5+
*
6+
* @author Børre A. Opedal Lunde
7+
* @since 2024.01.25
8+
*/
9+
@SuppressWarnings("ManualMinMaxCalculation")
10+
public class Solution {
11+
12+
// This solution is made with readability in mind. It is not the most
13+
// efficient solution, but it should be understandable. And that's what
14+
// matters more for me.
15+
//
16+
// There are many ways to optimise for performance and memory usage. For
17+
// example by using bitwise operations for division, inlining methods and
18+
// constants, using the original arrays instead of copying them, and
19+
// reducing the number of helper variables and methods, etc.
20+
21+
private static final int HYPOTHETICAL_NEGATIVE_INFINITY = 0x80000000; // Integer minimum value.
22+
private static final int HYPOTHETICAL_POSITIVE_INFINITY = 0x7fffffff; // Integer maximum value.
23+
24+
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
25+
26+
// Determine the smallest and largest array.
27+
final int[] leftArray = getSmallestArrayOf(nums1, nums2);
28+
final int[] rightArray = getLargestArrayOf(nums1, nums2);
29+
30+
// Calculate the sizes of the arrays and their combined length. This
31+
// will be used in the partitioning of the arrays.
32+
final int leftArraySize = leftArray.length;
33+
final int rightArraySize = rightArray.length;
34+
final int combinedArraysLength = leftArraySize + rightArraySize;
35+
36+
// According to the problem description, the arrays should never be
37+
// empty. If they are, then we throw an exception.
38+
if (leftArraySize == 0 && rightArraySize == 0) {
39+
throw new IllegalArgumentException("The arrays are empty.");
40+
}
41+
42+
// Low and high for the binary search.
43+
int low = 0;
44+
int high = leftArraySize;
45+
46+
// Binary search.
47+
while (low <= high) {
48+
49+
// In simple steps, this is what goes on:
50+
//
51+
// 1. Calculate the partition indices for the arrays.
52+
//
53+
// 2. Get the left and right values of the partitions.
54+
//
55+
// 3. Check if the left side of the partition is less than or equal
56+
// to the right side of the partition.
57+
//
58+
// 4. If the left side is less than or equal to the right side,
59+
// then we have found the median.
60+
//
61+
// 5. If the left side is greater than the right side, then we need
62+
// to move the partition to the left.
63+
//
64+
// 6. If the left side is less than the right side, then we need to
65+
// move the partition to the right.
66+
67+
// Calculate the partition indices for the arrays.
68+
final int leftArrayPartitionIndex = (low + high) / 2;
69+
final int rightArrayPartitionIndex = (combinedArraysLength + 1) / 2 - leftArrayPartitionIndex;
70+
71+
// Get values at partition indices.
72+
final int leftArrayLeftValue = getLeftValueOfPartition(leftArray, leftArrayPartitionIndex);
73+
final int leftArrayRightValue = getRightValueOfPartition(leftArray, leftArrayPartitionIndex);
74+
75+
final int rightArrayLeftValue = getLeftValueOfPartition(rightArray, rightArrayPartitionIndex);
76+
final int rightArrayRightValue = getRightValueOfPartition(rightArray, rightArrayPartitionIndex);
77+
78+
// The left side is OK if the left value is less than or equal to
79+
// the right value. Equally, the right side is OK if the right
80+
// value is less than or equal to the left value.
81+
final boolean leftSideIsOk = leftArrayLeftValue <= rightArrayRightValue;
82+
final boolean rightSideIsOk = rightArrayLeftValue <= leftArrayRightValue;
83+
84+
// If both sides are OK, then we can calculate the median.
85+
if (leftSideIsOk && rightSideIsOk) {
86+
87+
// Get the maximum of the left values and the minimum of the
88+
// right values. The maximum and minimum are the closest values
89+
// to the middle.
90+
final int leftValue = getMaximumOf(leftArrayLeftValue, rightArrayLeftValue);
91+
final int rightValue = getMinimumOf(leftArrayRightValue, rightArrayRightValue);
92+
93+
final double median;
94+
if (isEven(combinedArraysLength)) {
95+
// When the combined length is even, we need to calculate
96+
// the average of the two middle values.
97+
median = (leftValue + rightValue) / 2.0;
98+
} else {
99+
// It is easier when the combined length is odd. Then the
100+
// median is the middle (left) value.
101+
median = leftValue;
102+
}
103+
104+
System.out.printf("Returning median: %f%n", median);
105+
return median;
106+
}
107+
108+
// Move the partition to the left if the left side is not OK.
109+
else if (! leftSideIsOk) {
110+
high = leftArrayPartitionIndex - 1;
111+
}
112+
113+
// Vice versa for the right side.
114+
else {
115+
low = leftArrayPartitionIndex + 1;
116+
}
117+
}
118+
119+
// Given the constraints of the problem, we should never reach this
120+
// point. If we reach here regardless, then something is wrong.
121+
throw new UnsupportedOperationException("Something went wrong.");
122+
}
123+
124+
private static int getLeftValueOfPartition(final int[] array, final int index) {
125+
if (index <= 0) {
126+
// There is no left value at this index, so we return the smallest
127+
// possible value. We can then be sure that the value to its right
128+
// is greater or equal to it.
129+
return HYPOTHETICAL_NEGATIVE_INFINITY;
130+
}
131+
return array[index - 1];
132+
}
133+
134+
private static int getRightValueOfPartition(final int[] array, final int index) {
135+
if (index >= array.length) {
136+
// The same goes for the right side, just the other way around.
137+
return HYPOTHETICAL_POSITIVE_INFINITY;
138+
}
139+
return array[index];
140+
}
141+
142+
private static int[] getSmallestArrayOf(int[] a, int[] b) {
143+
return a.length <= b.length ? a : b;
144+
}
145+
146+
private static int[] getLargestArrayOf(int[] a, int[] b) {
147+
return a.length <= b.length ? b : a;
148+
}
149+
150+
private static boolean isEven(final int number) {
151+
return number % 2 == 0;
152+
}
153+
154+
private static int getMaximumOf(final int a, final int b) {
155+
return a >= b ? a : b;
156+
}
157+
158+
private static int getMinimumOf(final int a, final int b) {
159+
return a >= b ? b : a;
160+
}
161+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package com.bl.median_of_two_sorted_arrays;
2+
3+
import org.junit.jupiter.api.DisplayName;
4+
import org.junit.jupiter.api.Test;
5+
6+
import static org.junit.jupiter.api.Assertions.assertEquals;
7+
import static org.junit.jupiter.api.Assertions.assertThrows;
8+
9+
/**
10+
* This is the test to the LeetCode problem: 4. Median of Two Sorted Arrays
11+
*
12+
* @author Børre A. Opedal Lunde
13+
* @since 2024.01.25
14+
*/
15+
@DisplayName("Median of Two Sorted Arrays")
16+
class SolutionTest {
17+
18+
private final Solution solution = new Solution();
19+
20+
@Test
21+
@DisplayName("Example one")
22+
void exampleOne() {
23+
int[] nums1 = {1, 3};
24+
int[] nums2 = {2};
25+
26+
double expected = 2.00000;
27+
double actual = solution.findMedianSortedArrays(nums1, nums2);
28+
29+
assertEquals(expected, actual);
30+
}
31+
32+
@Test
33+
@DisplayName("Example two")
34+
void exampleTwo() {
35+
int[] nums1 = {1, 2};
36+
int[] nums2 = {3, 4};
37+
38+
double expected = 2.50000;
39+
double actual = solution.findMedianSortedArrays(nums1, nums2);
40+
41+
assertEquals(expected, actual);
42+
}
43+
44+
@Test
45+
@DisplayName("Even combined arrays length")
46+
void evenCombinedArraysLength() {
47+
int[] nums1 = {1, 3};
48+
int[] nums2 = {2, 4};
49+
50+
double expected = 2.5;
51+
double actual = solution.findMedianSortedArrays(nums1, nums2);
52+
53+
assertEquals(expected, actual);
54+
}
55+
56+
@Test
57+
@DisplayName("Odd combined arrays length")
58+
void oddCombinedArraysLength() {
59+
int[] nums1 = {1, 3};
60+
int[] nums2 = {2};
61+
62+
double expected = 2.0;
63+
double actual = solution.findMedianSortedArrays(nums1, nums2);
64+
65+
assertEquals(expected, actual);
66+
}
67+
68+
@Test
69+
@DisplayName("One empty array")
70+
void oneEmptyArray() {
71+
int[] nums1 = {};
72+
int[] nums2 = {1, 2, 3, 4, 5};
73+
74+
double expected = 3.0;
75+
double actual = solution.findMedianSortedArrays(nums1, nums2);
76+
77+
assertEquals(expected, actual);
78+
}
79+
80+
@Test
81+
@DisplayName("Both arrays empty")
82+
void bothArraysEmpty() {
83+
int[] nums1 = {};
84+
int[] nums2 = {};
85+
86+
assertThrows(IllegalArgumentException.class,
87+
() -> solution.findMedianSortedArrays(nums1, nums2));
88+
}
89+
90+
@Test
91+
@DisplayName("Arrays with negative numbers")
92+
void arraysWithNegativeNumbers() {
93+
int[] nums1 = {- 5, - 3, - 1};
94+
int[] nums2 = {- 2, - 1, 4, 6};
95+
96+
double expected = - 1.0;
97+
double actual = solution.findMedianSortedArrays(nums1, nums2);
98+
99+
assertEquals(expected, actual);
100+
}
101+
102+
@Test
103+
@DisplayName("First array large and second array small")
104+
void firstArrayLargeAndSecondArraySmall() {
105+
int[] nums1 = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
106+
int[] nums2 = {1, 2, 3};
107+
108+
double expected = 8.0;
109+
double actual = solution.findMedianSortedArrays(nums1, nums2);
110+
111+
assertEquals(expected, actual);
112+
}
113+
114+
@Test
115+
@DisplayName("First array small and second array large")
116+
void firstArraySmallAndSecondArrayLarge() {
117+
int[] nums1 = {1, 2, 3};
118+
int[] nums2 = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
119+
120+
double expected = 8.0;
121+
double actual = solution.findMedianSortedArrays(nums1, nums2);
122+
123+
assertEquals(expected, actual);
124+
}
125+
126+
@Test
127+
@DisplayName("Arrays with a single element each")
128+
void arraysWithASingleElementEach() {
129+
int[] nums1 = {1};
130+
int[] nums2 = {2};
131+
132+
double expected = 1.5;
133+
double actual = solution.findMedianSortedArrays(nums1, nums2);
134+
135+
assertEquals(expected, actual);
136+
}
137+
138+
@Test
139+
@DisplayName("Arrays with identical elements")
140+
void arraysWithIdenticalElements() {
141+
int[] nums1 = {2, 2, 2};
142+
int[] nums2 = {2, 2, 2};
143+
144+
double expected = 2.0;
145+
double actual = solution.findMedianSortedArrays(nums1, nums2);
146+
147+
assertEquals(expected, actual);
148+
}
149+
}

0 commit comments

Comments
 (0)