Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions src/main/java/com/thealgorithms/misc/FourSumProblem.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.thealgorithms.misc;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

//In this problem, we are given an array of n integers and a target integer.
//We have to return a list containing all possible quadruplets from the given array which could add to get the target.

public class FourSumProblem {

//Best approach - Sorting and two-pointers
//Time Complexity - O(n^3)
//Space Complexity - O(n)
public List<List<Integer>> fourSum1(int[] nums, int target) {
List<List<Integer>> ans = new ArrayList<>();
if (nums == null || nums.length < 4) return ans;
Arrays.sort(nums);
for(int i = 0; i < nums.length-3; i++) {
if(i > 0 && nums[i] == nums[i-1]) {
continue;
}
for(int j = i+1; j < nums.length - 2; j++) {
if(j > i+1 && nums[j] == nums[j-1]) {
continue;
}
int left = j+1;
int right = nums.length -1;
while(left < right) {
long sum = (long)nums[i] + (long)nums[j] + (long)nums[left] + (long)nums[right];
if(sum == target) {
ans.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
while (left < right && nums[left] == nums[left+1]) {
left++;
}
while(left < right && nums[right] == nums[right-1]) {
right--;
}
left++; right--;
} else if (sum > target) {
right--;
} else {
left++;
}
}
}
}
return ans;
}


//Another approach - Using HashMap
//Time Complexity - O(n^3)
//Space Complexity - O(n^2) (Storing the pair of sums)
public List<List<Integer>> fourSum2(int[] nums, int target) {
List<List<Integer>> ans = new ArrayList<>();
if (nums == null || nums.length < 4) {
return ans;
}
Arrays.sort(nums);
for (int i = 0; i < nums.length - 3; i++) {
if (i > 0 && nums[i] == nums[i-1]) continue;
for (int j = i + 1; j < nums.length - 2; j++) {
if (j > i + 1 && nums[j] == nums[j-1]) continue;
HashMap<Integer, Integer> map = new HashMap<>();
for (int k = j + 1; k < nums.length; k++) {
int complement = target - nums[i] - nums[j] - nums[k];
if (map.containsKey(complement)) {
ans.add(Arrays.asList(nums[i], nums[j], complement, nums[k]));
while (k + 1 < nums.length && nums[k] == nums[k + 1]) k++;
}
map.put(nums[k], k);
}
}
}
return ans;
}
}
187 changes: 187 additions & 0 deletions src/test/java/com/thealgorithms/misc/FourSumProblemTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package com.thealgorithms.misc;

import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import org.junit.jupiter.api.Test;

public class FourSumProblemTest {

@Test
public void testFourSum1_basicCase() {
FourSumProblem solver = new FourSumProblem();

// Case 1: Standard case with multiple quadruplets
int[] nums1 = {1, 0, -1, 0, -2, 2};
int target1 = 0;
List<List<Integer>> result1 = solver.fourSum1(nums1, target1);
assertEquals(3, result1.size()); // Expect 3 quadruplets
assertTrue(result1.contains(List.of(-2, -1, 1, 2)));
assertTrue(result1.contains(List.of(-2, 0, 0, 2)));
assertTrue(result1.contains(List.of(-1, 0, 0, 1)));
}

@Test
public void testFourSum1_edgeCase_emptyArray() {
FourSumProblem solver = new FourSumProblem();

// Case 2: Empty array
int[] nums2 = {};
int target2 = 0;
List<List<Integer>> result2 = solver.fourSum1(nums2, target2);
assertEquals(0, result2.size()); // Expect no quadruplets
}

@Test
public void testFourSum1_edgeCase_noQuadruplet() {
FourSumProblem solver = new FourSumProblem();

// Case 3: No valid quadruplets
int[] nums3 = {1, 2, 3, 4, 5};
int target3 = 100;
List<List<Integer>> result3 = solver.fourSum1(nums3, target3);
assertEquals(0, result3.size()); // Expect no quadruplets
}

@Test
public void testFourSum2_basicCase() {
FourSumProblem solver = new FourSumProblem();

// Case 4: Standard case with multiple quadruplets using HashMap approach
int[] nums1 = {1, 0, -1, 0, -2, 2};
int target1 = 0;
List<List<Integer>> result1 = solver.fourSum2(nums1, target1);
assertEquals(3, result1.size()); // Expect 3 quadruplets
assertTrue(result1.contains(List.of(-2, -1, 1, 2)));
assertTrue(result1.contains(List.of(-2, 0, 0, 2)));
assertTrue(result1.contains(List.of(-1, 0, 0, 1)));
}

@Test
public void testFourSum2_edgeCase_emptyArray() {
FourSumProblem solver = new FourSumProblem();

// Case 5: Empty array
int[] nums2 = {};
int target2 = 0;
List<List<Integer>> result2 = solver.fourSum2(nums2, target2);
assertEquals(0, result2.size()); // Expect no quadruplets
}

@Test
public void testFourSum2_edgeCase_noQuadruplet() {
FourSumProblem solver = new FourSumProblem();

// Case 6: No valid quadruplets
int[] nums3 = {1, 2, 3, 4, 5};
int target3 = 100;
List<List<Integer>> result3 = solver.fourSum2(nums3, target3);
assertEquals(0, result3.size()); // Expect no quadruplets
}

@Test
public void testFourSum2_singleQuadruplet() {
FourSumProblem solver = new FourSumProblem();

// Case 7: Single quadruplet in the array
int[] nums4 = {2, 2, 2, 2, 2};
int target4 = 8;
List<List<Integer>> result4 = solver.fourSum2(nums4, target4);
assertEquals(1, result4.size()); // Expect only one quadruplet
assertTrue(result4.contains(List.of(2, 2, 2, 2)));
}

@Test
public void testFourSum1_withDuplicateFirstIndex() {
FourSumProblem solver = new FourSumProblem();
int[] nums = {1, 1, 0, 0, -1, -1, 2, 2};
int target = 0;
List<List<Integer>> result = solver.fourSum1(nums, target);
assertTrue(!result.isEmpty()); // Expect some quadruplets
}

@Test
public void testFourSum1_withDuplicateSecondIndex() {
FourSumProblem solver = new FourSumProblem();
int[] nums = {2, 2, 2, 2, 2, 2, 0, 0};
int target = 4;
List<List<Integer>> result = solver.fourSum1(nums, target);
assertEquals(1, result.size()); // Expect only one quadruplet
assertTrue(result.contains(List.of(0, 0, 2, 2)));
}

@Test
public void testFourSum1_withSuccessfulSum() {
FourSumProblem solver = new FourSumProblem();
int[] nums = {1, 0, -1, 0, -2, 2};
int target = 0;
List<List<Integer>> result = solver.fourSum1(nums, target);
assertEquals(3, result.size()); // Expect 3 quadruplets
assertTrue(result.contains(List.of(-2, -1, 1, 2)));
assertTrue(result.contains(List.of(-2, 0, 0, 2)));
assertTrue(result.contains(List.of(-1, 0, 0, 1)));
}

@Test
public void testFourSum2_withDuplicateSecondIndex() {
FourSumProblem solver = new FourSumProblem();

// Case with duplicates affecting the second loop index (j)
int[] nums = {2, 2, 2, 2, 2, 0, 0};
int target = 4;
List<List<Integer>> result = solver.fourSum2(nums, target);

assertEquals(1, result.size()); // Expect only one quadruplet
assertTrue(result.contains(List.of(0, 0, 2, 2))); // The only valid quadruplet
}

@Test
public void testFourSum1_withNullArray() {
FourSumProblem solver = new FourSumProblem();

// Case with null input array
int[] nums = null;
int target = 0;
List<List<Integer>> result = solver.fourSum1(nums, target);

assertEquals(0, result.size()); // Expect no quadruplets
}

@Test
public void testFourSum2_withNullArray() {
FourSumProblem solver = new FourSumProblem();

// Case with null input array
int[] nums = null;
int target = 0;
List<List<Integer>> result = solver.fourSum2(nums, target);

assertEquals(0, result.size()); // Expect no quadruplets
}

@Test
public void testFourSum1_withLessThanFourElements() {
FourSumProblem solver = new FourSumProblem();

// Case with fewer than four elements
int[] nums = {1, 2, 3}; // Only 3 elements
int target = 0;
List<List<Integer>> result = solver.fourSum1(nums, target);

assertEquals(0, result.size()); // Expect no quadruplets
}

@Test
public void testFourSum2_withLessThanFourElements() {
FourSumProblem solver = new FourSumProblem();

// Case with fewer than four elements
int[] nums = {1, 2, 3}; // Only 3 elements
int target = 0;
List<List<Integer>> result = solver.fourSum2(nums, target);

assertEquals(0, result.size()); // Expect no quadruplets
}

}
Loading