From 9ca1a0064a74209d83a784811883a13e013c6430 Mon Sep 17 00:00:00 2001 From: Maiklins Date: Fri, 14 Aug 2020 17:41:36 +0200 Subject: [PATCH] CS-378 Find kth smallest element in union of two sorted arrays (#9812) * CS-378 Find the Kth Smallest Element in the Union of Two Sorted Arrays * CS-378 Fix name of unit test * CS-378 Update merge algorithm to operate only on two input arrays Co-authored-by: mikr --- .../algorithms/kthsmallest/KthSmallest.java | 126 ++++++++ .../kthsmallest/KthSmallestUnitTest.java | 288 ++++++++++++++++++ 2 files changed, 414 insertions(+) create mode 100644 algorithms-searching/src/main/java/com/baeldung/algorithms/kthsmallest/KthSmallest.java create mode 100644 algorithms-searching/src/test/java/com/baeldung/algorithms/kthsmallest/KthSmallestUnitTest.java diff --git a/algorithms-searching/src/main/java/com/baeldung/algorithms/kthsmallest/KthSmallest.java b/algorithms-searching/src/main/java/com/baeldung/algorithms/kthsmallest/KthSmallest.java new file mode 100644 index 0000000000..2bd1a20ce0 --- /dev/null +++ b/algorithms-searching/src/main/java/com/baeldung/algorithms/kthsmallest/KthSmallest.java @@ -0,0 +1,126 @@ +package com.baeldung.algorithms.kthsmallest; + +import java.util.Arrays; +import java.util.NoSuchElementException; + +import static java.lang.Math.max; +import static java.lang.Math.min; + +public class KthSmallest { + + public static int findKthSmallestElement(int k, int[] list1, int[] list2) throws NoSuchElementException, IllegalArgumentException { + + checkInput(k, list1, list2); + + // we are looking for the minimum value + if(k == 1) { + return min(list1[0], list2[0]); + } + + // we are looking for the maximum value + if(list1.length + list2.length == k) { + return max(list1[list1.length-1], list2[list2.length-1]); + } + + // swap lists if needed to make sure we take at least one element from list1 + if(k <= list2.length && list2[k-1] < list1[0]) { + int[] list1_ = list1; + list1 = list2; + list2 = list1_; + } + + // correct left boundary if k is bigger than the size of list2 + int left = k < list2.length ? 0 : k - list2.length - 1; + + // the inital right boundary cannot exceed the list1 + int right = min(k-1, list1.length - 1); + + int nElementsList1, nElementsList2; + + // binary search + do { + nElementsList1 = ((left + right) / 2) + 1; + nElementsList2 = k - nElementsList1; + + if(nElementsList2 > 0) { + if (list1[nElementsList1 - 1] > list2[nElementsList2 - 1]) { + right = nElementsList1 - 2; + } else { + left = nElementsList1; + } + } + } while(!kthSmallesElementFound(list1, list2, nElementsList1, nElementsList2)); + + return nElementsList2 == 0 ? list1[nElementsList1-1] : max(list1[nElementsList1-1], list2[nElementsList2-1]); + } + + private static boolean kthSmallesElementFound(int[] list1, int[] list2, int nElementsList1, int nElementsList2) { + + // we do not take any element from the second list + if(nElementsList2 < 1) { + return true; + } + + if(list1[nElementsList1-1] == list2[nElementsList2-1]) { + return true; + } + + if(nElementsList1 == list1.length) { + return list1[nElementsList1-1] <= list2[nElementsList2]; + } + + if(nElementsList2 == list2.length) { + return list2[nElementsList2-1] <= list1[nElementsList1]; + } + + return list1[nElementsList1-1] <= list2[nElementsList2] && list2[nElementsList2-1] <= list1[nElementsList1]; + } + + + private static void checkInput(int k, int[] list1, int[] list2) throws NoSuchElementException, IllegalArgumentException { + + if(list1 == null || list2 == null || k < 1) { + throw new IllegalArgumentException(); + } + + if(list1.length == 0 || list2.length == 0) { + throw new IllegalArgumentException(); + } + + if(k > list1.length + list2.length) { + throw new NoSuchElementException(); + } + } + + public static int getKthElementSorted(int[] list1, int[] list2, int k) { + + int length1 = list1.length, length2 = list2.length; + int[] combinedArray = new int[length1 + length2]; + System.arraycopy(list1, 0, combinedArray, 0, list1.length); + System.arraycopy(list2, 0, combinedArray, list1.length, list2.length); + Arrays.sort(combinedArray); + + return combinedArray[k-1]; + } + + public static int getKthElementMerge(int[] list1, int[] list2, int k) { + + int i1 = 0, i2 = 0; + + while(i1 < list1.length && i2 < list2.length && (i1 + i2) < k) { + if(list1[i1] < list2[i2]) { + i1++; + } else { + i2++; + } + } + + if((i1 + i2) < k) { + return i1 < list1.length ? list1[k - i2 - 1] : list2[k - i1 - 1]; + } else if(i1 > 0 && i2 > 0) { + return Math.max(list1[i1-1], list2[i2-1]); + } else { + return i1 == 0 ? list2[i2-1] : list1[i1-1]; + } + } +} diff --git a/algorithms-searching/src/test/java/com/baeldung/algorithms/kthsmallest/KthSmallestUnitTest.java b/algorithms-searching/src/test/java/com/baeldung/algorithms/kthsmallest/KthSmallestUnitTest.java new file mode 100644 index 0000000000..740e89d8e7 --- /dev/null +++ b/algorithms-searching/src/test/java/com/baeldung/algorithms/kthsmallest/KthSmallestUnitTest.java @@ -0,0 +1,288 @@ +package com.baeldung.algorithms.kthsmallest; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import java.util.*; + +import static com.baeldung.algorithms.kthsmallest.KthSmallest.*; +import static org.junit.jupiter.api.Assertions.*; + +public class KthSmallestUnitTest { + + @Nested + class Exceptions { + + @Test + public void when_at_least_one_list_is_null_then_an_exception_is_thrown() { + + Executable executable1 = () -> findKthSmallestElement(1, null, null); + Executable executable2 = () -> findKthSmallestElement(1, new int[]{2}, null); + Executable executable3 = () -> findKthSmallestElement(1, null, new int[]{2}); + + assertThrows(IllegalArgumentException.class, executable1); + assertThrows(IllegalArgumentException.class, executable2); + assertThrows(IllegalArgumentException.class, executable3); + } + + @Test + public void when_at_least_one_list_is_empty_then_an_exception_is_thrown() { + + Executable executable1 = () -> findKthSmallestElement(1, new int[]{}, new int[]{2}); + Executable executable2 = () -> findKthSmallestElement(1, new int[]{2}, new int[]{}); + Executable executable3 = () -> findKthSmallestElement(1, new int[]{}, new int[]{}); + + assertThrows(IllegalArgumentException.class, executable1); + assertThrows(IllegalArgumentException.class, executable2); + assertThrows(IllegalArgumentException.class, executable3); + } + + @Test + public void when_k_is_smaller_than_0_then_an_exception_is_thrown() { + Executable executable1 = () -> findKthSmallestElement(-1, new int[]{2}, new int[]{2}); + assertThrows(IllegalArgumentException.class, executable1); + } + + @Test + public void when_k_is_smaller_than_1_then_an_exception_is_thrown() { + Executable executable1 = () -> findKthSmallestElement(0, new int[]{2}, new int[]{2}); + assertThrows(IllegalArgumentException.class, executable1); + } + + @Test + public void when_k_bigger_then_the_two_lists_then_an_exception_is_thrown() { + Executable executable1 = () -> findKthSmallestElement(6, new int[]{1, 5, 6}, new int[]{2, 5}); + assertThrows(NoSuchElementException.class, executable1); + } + + } + + @Nested + class K_is_smaller_than_the_size_of_list1_and_the_size_of_list2 { + + @Test + public void when_k_is_1_then_the_smallest_element_is_returned_from_list1() { + int result = findKthSmallestElement(1, new int[]{2, 7}, new int[]{3, 5}); + assertEquals(2, result); + } + + @Test + public void when_k_is_1_then_the_smallest_element_is_returned_list2() { + int result = findKthSmallestElement(1, new int[]{3, 5}, new int[]{2, 7}); + assertEquals(2, result); + } + + @Test + public void when_kth_element_is_smallest_element_and_occurs_in_both_lists() { + int[] list1 = new int[]{1, 2, 3}; + int[] list2 = new int[]{1, 2, 3}; + int result = findKthSmallestElement(1, list1, list2); + assertEquals(1, result); + } + + @Test + public void when_kth_element_is_smallest_element_and_occurs_in_both_lists2() { + int[] list1 = new int[]{1, 2, 3}; + int[] list2 = new int[]{1, 2, 3}; + int result = findKthSmallestElement(2, list1, list2); + assertEquals(1, result); + } + + @Test + public void when_kth_element_is_largest_element_and_occurs_in_both_lists_1() { + int[] list1 = new int[]{1, 2, 3}; + int[] list2 = new int[]{1, 2, 3}; + int result = findKthSmallestElement(5, list1, list2); + assertEquals(3, result); + } + + @Test + public void when_kth_element_is_largest_element_and_occurs_in_both_lists_2() { + int[] list1 = new int[]{1, 2, 3}; + int[] list2 = new int[]{1, 2, 3}; + int result = findKthSmallestElement(6, list1, list2); + assertEquals(3, result); + } + + @Test + public void when_kth_element_and_occurs_in_both_lists() { + int[] list1 = new int[]{1, 2, 3}; + int[] list2 = new int[]{0, 2, 3}; + int result = findKthSmallestElement(3, list1, list2); + assertEquals(2, result); + } + + @Test + public void and_kth_element_is_in_first_list() { + int[] list1 = new int[]{1,2,3,4}; + int[] list2 = new int[]{1,3,4,5}; + int result = findKthSmallestElement(3, list1, list2); + assertEquals(2, result); + } + + @Test + public void and_kth_is_in_second_list() { + int[] list1 = new int[]{1,3,4,4}; + int[] list2 = new int[]{1,2,4,5}; + int result = findKthSmallestElement(3, list1, list2); + assertEquals(2, result); + } + + @Test + public void and_elements_in_first_list_are_all_smaller_than_second_list() { + int[] list1 = new int[]{1,3,7,9}; + int[] list2 = new int[]{11,12,14,15}; + int result = findKthSmallestElement(3, list1, list2); + assertEquals(7, result); + } + + @Test + public void and_elements_in_first_list_are_all_smaller_than_second_list2() { + int[] list1 = new int[]{1,3,7,9}; + int[] list2 = new int[]{11,12,14,15}; + int result = findKthSmallestElement(4, list1, list2); + assertEquals(9, result); + } + + @Test + public void and_only_elements_from_second_list_are_part_of_result() { + int[] list1 = new int[]{11,12,14,15}; + int[] list2 = new int[]{1,3,7,9}; + int result = findKthSmallestElement(3, list1, list2); + assertEquals(7, result); + } + + @Test + public void and_only_elements_from_second_list_are_part_of_result2() { + int[] list1 = new int[]{11,12,14,15}; + int[] list2 = new int[]{1,3,7,9}; + int result = findKthSmallestElement(4, list1, list2); + assertEquals(9, result); + } + } + + @Nested + class K_is_bigger_than_the_size_of_at_least_one_of_the_lists { + + @Test + public void k_is_smaller_than_list1_and_bigger_than_list2() { + int[] list1 = new int[]{1, 2, 3, 4, 7, 9}; + int[] list2 = new int[]{1, 2, 3}; + int result = findKthSmallestElement(5, list1, list2); + assertEquals(3, result); + } + + @Test + public void k_is_bigger_than_list1_and_smaller_than_list2() { + int[] list1 = new int[]{1, 2, 3}; + int[] list2 = new int[]{1, 2, 3, 4, 7, 9}; + int result = findKthSmallestElement(5, list1, list2); + assertEquals(3, result); + } + + @Test + public void when_k_is_bigger_than_the_size_of_both_lists_and_elements_in_second_list_are_all_smaller_than_first_list() { + int[] list1 = new int[]{9, 11, 13, 55}; + int[] list2 = new int[]{1, 2, 3, 7}; + int result = findKthSmallestElement(6, list1, list2); + assertEquals(11, result); + } + + @Test + public void when_k_is_bigger_than_the_size_of_both_lists_and_elements_in_second_list_are_all_bigger_than_first_list() { + int[] list1 = new int[]{1, 2, 3, 7}; + int[] list2 = new int[]{9, 11, 13, 55}; + int result = findKthSmallestElement(6, list1, list2); + assertEquals(11, result); + } + + @Test + public void when_k_is_bigger_than_the_size_of_both_lists() { + int[] list1 = new int[]{3, 7, 9, 11, 55}; + int[] list2 = new int[]{1, 2, 3, 7, 13}; + int result = findKthSmallestElement(7, list1, list2); + assertEquals(9, result); + } + + @Test + public void when_k_is_bigger_than_the_size_of_both_lists_and_list1_has_more_elements_than_list2() { + int[] list1 = new int[]{3, 7, 9, 11, 55, 77, 100, 200}; + int[] list2 = new int[]{1, 2, 3, 7, 13}; + int result = findKthSmallestElement(11, list1, list2); + assertEquals(77, result); + } + + @Test + public void max_test() { + int[] list1 = new int[]{100, 200}; + int[] list2 = new int[]{1, 2, 3}; + int result = findKthSmallestElement(4, list1, list2); + assertEquals(100, result); + } + + @Test + public void max_test2() { + int[] list1 = new int[]{100, 200}; + int[] list2 = new int[]{1, 2, 3}; + int result = findKthSmallestElement(5, list1, list2); + assertEquals(200, result); + } + + @Test + public void when_k_is_smaller_than_the_size_of_both_lists_and_kth_element_in_list2() { + int[] list1 = new int[]{1, 2, 5}; + int[] list2 = new int[]{1, 3, 4, 7}; + int result = findKthSmallestElement(4, list1, list2); + assertEquals(3, result); + } + + @Test + public void when_k_is_smaller_than_the_size_of_both_lists_and_kth_element_is_smallest_in_list2() { + int[] list1 = new int[]{1, 2, 5}; + int[] list2 = new int[]{3, 4, 7}; + int result = findKthSmallestElement(3, list1, list2); + assertEquals(3, result); + } + + @Test + public void when_k_is_smaller_than_the_size_of_both_lists_and_kth_element_is_smallest_in_list23() { + int[] list1 = new int[]{3, 11, 27, 53, 90}; + int[] list2 = new int[]{4, 20, 21, 100}; + int result = findKthSmallestElement(5, list1, list2); + assertEquals(21, result); + } + } + +// @Test +// public void randomTests() { +// IntStream.range(1, 100000).forEach(i -> random()); +// } + + private void random() { + + Random random = new Random(); + int length1 = (Math.abs(random.nextInt())) % 1000 + 1; + int length2 = (Math.abs(random.nextInt())) % 1000 + 1; + + int[] list1 = sortedRandomIntArrayOfLength(length1); + int[] list2 = sortedRandomIntArrayOfLength(length2); + + int k = (Math.abs(random.nextInt()) % (length1 + length2)) + 1 ; + + int result = findKthSmallestElement(k, list1, list2); + + int result2 = getKthElementSorted(list1, list2, k); + + int result3 = getKthElementMerge(list1, list2, k); + + assertEquals(result2, result); + assertEquals(result2, result3); + } + + private int[] sortedRandomIntArrayOfLength(int length) { + int[] intArray = new Random().ints(length).toArray(); + Arrays.sort(intArray); + return intArray; + } +} \ No newline at end of file