Is there a better way of solving this The Leetcode ThreeSum problem? - java

Please I am just curious if there is a better way I can achieve the Leetcode ThreeSum problem.
I came up with this piece of code using Binary Search
// Time Complexity: O(nlogn) + O(nlogn) == O(nlogn)
// Extra Space Complexity: O(1)
public static List<List<Integer>> threeSumOptimalSolution(int[] nums) {
int n = nums.length;
if (n < 3)
return Collections.emptyList();
Arrays.sort(nums);
List<List<Integer>> result = new ArrayList<>();
int left;
int right;
int mid;
int threeSum;
for (int firstValue = 0; firstValue < n - 2; firstValue++) {
if (firstValue == 0 || nums[firstValue] != nums[firstValue - 1]) {
left = firstValue + 1;
right = n - 1;
while (left < right) {
mid = left + (right - left) / 2;
threeSum = nums[firstValue] + nums[left] + nums[right];
if (threeSum < 0) {
if (nums[mid] + nums[right] + nums[firstValue] < 0)
left = mid + 1;
else
left++;
} else if (threeSum > 0) {
if (nums[mid] + nums[left] + nums[firstValue] > 0)
right = mid - 1;
else
right--;
} else {
result.add(List.of(nums[firstValue], nums[left], nums[right]));
left++;
while (nums[left] == nums[left - 1] && left < right)
left++;
while (nums[right] == nums[right - 1] && left < right)
right--;
}
}
}
}
return result;
}
I would appreciate an extensive review from individual/individuals.

The best time complexity in the ThreeNums problem I know is O(n^2)
And I think you want to use Two Pointers to finish the problem.
In your Answer, the part Of Binary Search is wrong, I don't know what's are you doing.
The correct method is to traverse the list outside and use Two Pointers inside.
E.g
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
Arrays.sort(nums);
for(int i = 0; i < nums.length; i++) {
if(nums[i] > 0) break;
if(i > 0 && nums[i] == nums[i-1]) continue;
int left = i+1;
int right = nums.length-1;
while(left < right) {
int sum = nums[i] + nums[left] + nums[right];
if(sum == 0) {
List<Integer> temp = new ArrayList<>();
temp.add(nums[i]);
temp.add(nums[left]);
temp.add(nums[right]);
res.add(temp);
while(right > left && nums[right] == nums[right-1]) right--;
while(right > left && nums[left] == nums[left+1]) left++;
right--;
left++;
} else if(sum > 0) {
right--;
} else {
left++;
}
}
}
return res;
}
}

Related

Tim sort implementation working at low array size but crashing at high

Basically i built this rudementary tim sort built for a simple project and it works with a sub of 32 all the way up to 1000 integers (the next thing i tried was 5000) and then it crashes with a index out of bounds exeception i tried increasing the sub to 64 but it just dosent seem to work i was wondering if anyone could tell me what im doing wrong here.
public static void timSort(List<Comparable> nums) {
int sub = 32;
for (int i = 0; i < nums.size(); i += sub)
{
if((nums.size() -1) < (i + 31)) {
inPlaceInsertion(nums, i, (nums.size() - 1));
}else {
inPlaceInsertion(nums, i, (i + 31));
}
}
for (int size = sub; size < nums.size(); size = 2 * size)
{
for (int left = 0; left < nums.size(); left += 2 * size)
{
int mid = left + size - 1;
int right;
if((nums.size() - 1) < (left + 2 * size - 1)) {
right = nums.size() -1;
}else {
right = left + 2 * size -1;
}
merge(nums, left, mid, right);
}
}
}
public static void inPlaceInsertion(List<Comparable> nums, int first, int last){
for(int i = first; i <= last; i++){
Comparable hold = nums.get(i);
int j;
steps++;
for(j = i; j > first && hold.compareTo(nums.get(j - 1)) < 0; j--) {
nums.set(j, nums.get(j - 1));
steps+=4;
}
nums.set(j, hold);
steps++;
}
}
private static void merge(List<Comparable> nums, int first, int mid, int last){
List<Comparable> newList = new ArrayList<Comparable>();
int loopCountA = 0;
int loopCountB = 0;
while(true) {
if(loopCountB == (last - mid)) {
while(first + loopCountA <= mid) {
newList.add(nums.get(first + loopCountA)); loopCountA++;
steps++;
}
break;
}else if(first + loopCountA > mid) {
while(loopCountB < (last - mid)) {
newList.add(nums.get(mid + (loopCountB + 1))); loopCountB++;
steps++;
}
break;
}else {
if(nums.get(mid + (loopCountB + 1)).compareTo(nums.get(first + loopCountA)) < 0) {
// here is where error is (line above)
newList.add(nums.get(mid + (loopCountB + 1)));
steps += 5;
loopCountB++;
}else {
newList.add(nums.get(first + loopCountA));
steps += 5;
loopCountA++;
}
}
}
for(int i = 0; (i - 1) < (last - first); i++) {
nums.set(first + i, newList.get(i));
steps+=2;
}
}
Here's the error...
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index 5024 out of bounds for length 5000
at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:248)
at java.base/java.util.Objects.checkIndex(Objects.java:372)
at java.base/java.util.ArrayList.get(ArrayList.java:458)
at Sorts.merge(Sorts.java:772)
at Sorts.timSort(Sorts.java:1193)
at Sorts.sortMenu(Sorts.java:255)
at Sorts.main(Sorts.java:33)
Simple main method which demonstrates it not working
int depthLimit
= (int)(2 * Math.floor(Math.log(nums.size()) /
Math.log(2)));
introSort(nums, 0, nums.size() -1, depthLimit);

3Sum on Leetcode

I'm working on the 3sum problem. I did it by sorting the array in the beginning and then using two pointer method to find all the unique triplets in the array which gives the sum of zero.
My question is that if I comment out the two while loops at the end, the runtime can be improved from 32ms to 26ms. I get a 6 ms speed boost. I think the time complexity is still O(n^2). Does anyone know why commenting out the while loops is faster?
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new LinkedList<List<Integer>>();
if(nums.length < 3){
return result;
}
int left = 0;
int right = nums.length - 1;
int threeSum = 0;
Arrays.sort(nums);
for (int i = 0; i < nums.length - 2 && nums[i] <= 0; i ++) {
right = nums.length - 1;
if (i > 0) {
while (i < right && nums[i - 1] == nums[i])
i ++;
}
left = i + 1;
while (left < right) {
threeSum = nums[i] + nums[left] + nums[right];
if (threeSum == 0) {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
left ++;
right --;
while (left < right && nums[left - 1] == nums[left])
left ++;
while (left < right && nums[right + 1] == nums[right])
right --;
} else if (threeSum < 0) {
// move to the right
left ++;
// Skip duplicate numbers
// Comment this out get 6 ms speed increase
while (left < right && nums[left - 1] == nums[left])
left ++;
} else {
right --;
// Skip duplicate numbers
// Comment this out get 6 ms speed increase
while (left < right && nums[right + 1] == nums[right])
right --;
}
}
}
return result;
}

Binary Search Specifics

I am hoping someone can help me understand this code for binary search. This is from LeetCode, it is "Template 2" under Binary Search.
I was wondering why you set right to nums.length
-Is there a reason why it isn't nums.length - 1 ?
int binarySearch(int[] nums, int target){
if(nums == null || nums.length == 0)
return -1;
int left = 0, right = nums.length;
while(left < right){
// Prevent (left + right) overflow
int mid = left + (right - left) / 2;
if(nums[mid] == target){ return mid; }
else if(nums[mid] < target) { left = mid + 1; }
else { right = mid; }
}
// Post-processing:
// End Condition: left == right
if(left != nums.length && nums[left] == target) return left;
return -1;
}

QuickSort (Java) implementation either overflows or stops short

I have been reading through all of the QuickSort questions on SO, but I cannot resolve this specific problem. By referencing the other questions and comparing my faults to theirs I have gotten to a specific point, that I cannot find the answer to, even in Debug mode.
I was repeatedly getting out of bounds -1, so I added a conditional check for
if(pivot > 0)
and that stopped the overflow, but since I am using 0 as my partition, It partitions once and then terminates. The first partition is correct, but if I change that number to include 0, the I get infinite recursion again. If I completely take the line out, I get index out of bounds errors that I cannot seem to tackle.
Here's where I am so far:
public class QuickSort {
int[] array;
public static void main(String[] args) {
QuickSort qs = new QuickSort();
qs.array = new int[] {35, 82, 2, 24, 57, 17};
qs.quickSort(qs.array, 0, qs.array.length - 1);
for(int i = 0; i < qs.array.length; i++) {
System.out.println(qs.array[i]);
}
}
public void quickSort(int[] array, int left, int right) {
if(array.length == 1) {
return;
}
if(left < right) {
int pivot = partition(array, left, right);
quickSort(array, left, pivot - 1);
quickSort(array, pivot + 1, right);
}
}
public int partition(int[] array, int left, int right) {
if(array.length == 1) {
return right;
}
int pivot = array[0];
int pivotIndex = 0;
int leftPointer = left - 1;
int rightPointer = right + 1;
while(pivotIndex < right) {
if(leftPointer > rightPointer) {
break;
}
leftPointer++;
while(leftPointer < array.length - 1 && array[leftPointer] <= pivot) {
leftPointer++;
}
rightPointer--;
while(rightPointer > leftPointer && array[rightPointer] > pivot) {
rightPointer--;
}
if(leftPointer < rightPointer) {
int temp = array[leftPointer];
array[leftPointer] = array[rightPointer];
array[rightPointer] = temp;
} else {
int temp = array[rightPointer];
array[rightPointer] = array[pivotIndex];
array[pivotIndex] = temp;
}
}
return rightPointer;
}
EDIT: After a few more alterations, I can now get it to always return an array without overflow, but it still only partitions once.
I'm pretty sure I fixed it now. You were increasing the left and right pointers within the partition method before you wanted to (outside of the "checks"). Change your partition method as follows:
public static int partition(int[] array, int left, int right) {
if(array.length == 1)
return right;
int pivot = array[0];
int pivotIndex = 0;
int leftPointer = left; // Remove the +1
int rightPointer = right; // Remove the +1
while(pivotIndex < right) {
if(leftPointer > rightPointer) {
break;
}
//leftPointer++;
while((leftPointer < array.length - 1) && (array[leftPointer] <= pivot)) {
leftPointer++;
}
//rightPointer--;
while((rightPointer > leftPointer) && (array[rightPointer] > pivot)) {
rightPointer--;
}
if(leftPointer < rightPointer) {
int temp = array[leftPointer];
array[leftPointer] = array[rightPointer];
array[rightPointer] = temp;
}
else {
int temp = array[rightPointer];
array[rightPointer] = array[pivotIndex];
array[pivotIndex] = temp;
}
}
return rightPointer;
}
You're returning "0" from partition whenever the array length is not 1, and setting that to the pivot. The if(pivot >= 0) will always be hit in that case, or it will iterate once if you used if(pivot > 0), which I think is the problem. If that's right, then correcting your return from partition (to "left" ?) should fix the problem.
I think you should change
if(leftPointer > rightPointer) {
break;
}
to
if(leftPointer >= rightPointer) {
break;
}
inside the while loop.
Also, I think you should compare leftPointer with rightPointer after either is changed,
// move to #### to perform compare after possible change
// if(leftPointer > rightPointer) break;
//leftPointer++;
while(leftPointer < array.length - 1 && array[leftPointer] <= pivot) leftPointer++;
//rightPointer--;
while(rightPointer > leftPointer && array[rightPointer] > pivot) rightPointer--;
//####
if(leftPointer > rightPointer) break;

Java multithreaded merge sort speed issue

I'm trying to implement multithreaded merge sort in Java. The idea is to recursively call to new threads on every iteration. Everything works properly, but problem is that regular single-thread version appears to be much more faster. Please, help fixing it.
I've tried to play with .join(), but it haven't brought any success.
My code:
public class MergeThread implements Runnable {
private final int begin;
private final int end;
public MergeThread(int b, int e) {
this.begin = b;
this.end = e;
}
#Override
public void run() {
try {
MergeSort.mergesort(begin, end);
} catch (InterruptedException ex) {
Logger.getLogger(MergeThread.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
public class MergeSort {
private static volatile int[] numbers;
private static volatile int[] helper;
private int number;
public void sort(int[] values) throws InterruptedException {
MergeSort.numbers = values;
number = values.length;
MergeSort.helper = new int[number];
mergesort(0, number - 1);
}
public static void mergesort(int low, int high) throws InterruptedException {
// check if low is smaller than high, if not then the array
// is sorted
if (low < high) {
// Get the index of the element which is in the middle
int middle = low + (high - low) / 2;
// Sort the left side of the array
Thread left = new Thread(new MergeThread(low, middle));
Thread right = new Thread(new MergeThread(middle+1, high));
left.start();
right.start();
left.join();
right.join();
// combine the sides
merge(low, middle, high);
}
}
private static void merge(int low, int middle, int high) {
// Copy both parts into the helper array
for (int i = low; i <= high; i++) {
helper[i] = numbers[i];
}
int i = low;
int j = middle + 1;
int k = low;
// Copy the smallest value from either the left or right side
// back to the original array
while (i <= middle && j <= high) {
if (helper[i] <= helper[j]) {
numbers[k] = helper[i];
i++;
} else {
numbers[k] = helper[j];
j++;
}
k++;
}
// Copy the rest of the left side of the array
while (i <= middle) {
numbers[k] = helper[i];
k++;
i++;
}
}
public static void main(String[] args) throws InterruptedException {
int[] array = new int[1000];
for(int pos = 0; pos<1000; pos++) {
array[pos] = 1000-pos;
}
long start = System.currentTimeMillis();
new MergeSort().sort(array);
long finish = System.currentTimeMillis();
for(int i = 0; i<array.length; i++) {
System.out.print(array[i]+" ");
}
System.out.println();
System.out.println(finish-start);
}
}
There are several factors here. First of all, you are spawning too many threads. A lot more than the number of cores your processor has. If I understand your algorithm correctly you are doing something like log2(n) at the bottom level of your tree.
Given that you're doing processor intensive computations, not involving I/O, once you pass the number of cores with your thread count, the performance starts degrading pretty fast. Hitting something like several thousand threads will slow and in the end crash the VM.
If you want to actually benefit from having a multi-core processor in this computation you should try to use a fixed size thread-pool (upper bounded on the number of cores or thereabout) or an equivalent thread reuse policy.
Second point, if you want to do a valid comparison you should try with computations that last longer (sorting 100 numbers doesn't qualify). If not, you are taking a significant relative hit from the cost of creating threads.
Provide a threadcount in begining as number of cores or less.
Below link has performance analysis too.
Here if a good example https://courses.cs.washington.edu/courses/cse373/13wi/lectures/03-13/MergeSort.java
Below is the iterative serial version of MergeSort which is indeed faster than the recursive version and also doesnot involve calculation of middle so avoids the overflow error for it. However overflow errors can occur for other integers as well. You can try for parallelizing it if you are interested.
protected static int[] ASC(int input_array[]) // Sorts in ascending order
{
int num = input_array.length;
int[] temp_array = new int[num];
int temp_indx;
int left;
int mid,j;
int right;
int[] swap;
int LIMIT = 1;
while (LIMIT < num)
{
left = 0;
mid = LIMIT ; // The mid point
right = LIMIT << 1;
while (mid < num)
{
if (right > num){ right = num; }
temp_indx = left;
j = mid;
while ((left < mid) && (j < right))
{
if (input_array[left] < input_array[j]){ temp_array[temp_indx++] = input_array[left++]; }
else{ temp_array[temp_indx++] = input_array[j++]; }
}
while (left < mid){ temp_array[temp_indx++] = input_array[left++]; }
while (j < right){ temp_array[temp_indx++] = input_array[j++]; }
// Do not copy back the elements to input_array
left = right;
mid = left + LIMIT;
right = mid + LIMIT;
}
// Instead of copying back in previous loop, copy remaining elements to temp_array, then swap the array pointers
while (left < num){ temp_array[left] = input_array[left++]; }
swap = input_array;
input_array = temp_array;
temp_array = swap;
LIMIT <<= 1;
}
return input_array ;
}
Use the java executor service, thats a lot faster, even with threads exceeding the number of cores ( you can build scalable multithreaded applications with it ), I have a code that uses only threads but its very very slow, and Im new to executors so cant help much, but its an interesting area to explore.
Also there is a cost for parallelism, because thread management is a big deal, so go for parallelism at high N, if you are looking for a serial alternative to merge sort, I suggest the Dual-Pivot-QuickSort or 3-Partition-Quick-Sort as they are known to beat merge sort often. Reason is that they have low constant factors than MergeSort and the worst case time complexity has the probability of occuring only 1/(n!). If N is large, the worst case probability becomes very small paving way for increased probability of average case. You could multithread both and see which one among the 4 programs ( 1 serial and 1 multithreaded for each : DPQ and 3PQ ) runs the fastest.
But Dual-Pivot-QuickSort works best when there are no, or almost no duplicate keys and 3-Partition-Quick-Sort works best when there are many duplicate keys. I have never seen 3-Partition-Quick-Sort beat the Dual-Pivot-QuickSort when there are none or very few duplicate keys, but I have seen Dual-Pivot-QuickSort beat 3-Partition-Quick-Sort a very small number of times in case of many duplicate keys. In case you are interested, DPQ serial code is below( both ascending and descending)
protected static void ASC(int[]a, int left, int right, int div)
{
int len = 1 + right - left;
if (len < 27)
{
// insertion sort for small array
int P1 = left + 1;
int P2 = left;
while ( P1 <= right )
{
div = a[P1];
while(( P2 >= left )&&( a[P2] > div ))
{
a[P2 + 1] = a[P2];
P2--;
}
a[P2 + 1] = div;
P2 = P1;
P1++;
}
return;
}
int third = len / div;
// "medians"
int P1 = left + third;
int P2 = right - third;
if (P1 <= left)
{
P1 = left + 1;
}
if (P2 >= right)
{
P2 = right - 1;
}
int temp;
if (a[P1] < a[P2])
{
temp = a[P1]; a[P1] = a[left]; a[left] = temp;
temp = a[P2]; a[P2] = a[right]; a[right] = temp;
}
else
{
temp = a[P1]; a[P1] = a[right]; a[right] = temp;
temp = a[P2]; a[P2] = a[left]; a[left] = temp;
}
// pivots
int pivot1 = a[left];
int pivot2 = a[right];
// pointers
int less = left + 1;
int great = right - 1;
// sorting
for (int k = less; k <= great; k++)
{
if (a[k] < pivot1)
{
temp = a[k]; a[k] = a[less]; a[less] = temp;
less++;
}
else if (a[k] > pivot2)
{
while (k < great && a[great] > pivot2)
{
great--;
}
temp = a[k]; a[k] = a[great]; a[great] = temp;
great--;
if (a[k] < pivot1)
{
temp = a[k]; a[k] = a[less]; a[less] = temp;
less++;
}
}
}
int dist = great - less;
if (dist < 13)
{
div++;
}
temp = a[less-1]; a[less-1] = a[left]; a[left] = temp;
temp = a[great+1]; a[great+1] = a[right]; a[right] = temp;
// subarrays
ASC(a, left, less - 2, div);
ASC(a, great + 2, right, div);
// equal elements
if (dist > len - 13 && pivot1 != pivot2)
{
for (int k = less; k <= great; k++)
{
if (a[k] == pivot1)
{
temp = a[k]; a[k] = a[less]; a[less] = temp;
less++;
}
else if (a[k] == pivot2)
{
temp = a[k]; a[k] = a[great]; a[great] = temp;
great--;
if (a[k] == pivot1)
{
temp = a[k]; a[k] = a[less]; a[less] = temp;
less++;
}
}
}
}
// subarray
if (pivot1 < pivot2)
{
ASC(a, less, great, div);
}
}
protected static void DSC(int[]a, int left, int right, int div)
{
int len = 1 + right - left;
if (len < 27)
{
// insertion sort for large array
int P1 = left + 1;
int P2 = left;
while ( P1 <= right )
{
div = a[P1];
while(( P2 >= left )&&( a[P2] < div ))
{
a[P2 + 1] = a[P2];
P2--;
}
a[P2 + 1] = div;
P2 = P1;
P1++;
}
return;
}
int third = len / div;
// "medians"
int P1 = left + third;
int P2 = right - third;
if (P1 >= left)
{
P1 = left + 1;
}
if (P2 <= right)
{
P2 = right - 1;
}
int temp;
if (a[P1] > a[P2])
{
temp = a[P1]; a[P1] = a[left]; a[left] = temp;
temp = a[P2]; a[P2] = a[right]; a[right] = temp;
}
else
{
temp = a[P1]; a[P1] = a[right]; a[right] = temp;
temp = a[P2]; a[P2] = a[left]; a[left] = temp;
}
// pivots
int pivot1 = a[left];
int pivot2 = a[right];
// pointers
int less = left + 1;
int great = right - 1;
// sorting
for (int k = less; k <= great; k++)
{
if (a[k] > pivot1)
{
temp = a[k]; a[k] = a[less]; a[less] = temp;
less++;
}
else if (a[k] < pivot2)
{
while (k < great && a[great] < pivot2)
{
great--;
}
temp = a[k]; a[k] = a[great]; a[great] = temp;
great--;
if (a[k] > pivot1)
{
temp = a[k]; a[k] = a[less]; a[less] = temp;
less++;
}
}
}
int dist = great - less;
if (dist < 13)
{
div++;
}
temp = a[less-1]; a[less-1] = a[left]; a[left] = temp;
temp = a[great+1]; a[great+1] = a[right]; a[right] = temp;
// subarrays
DSC(a, left, less - 2, div);
DSC(a, great + 2, right, div);
// equal elements
if (dist > len - 13 && pivot1 != pivot2)
{
for (int k = less; k <= great; k++)
{
if (a[k] == pivot1)
{
temp = a[k]; a[k] = a[less]; a[less] = temp;
less++;
}
else if (a[k] == pivot2)
{
temp = a[k]; a[k] = a[great]; a[great] = temp;
great--;
if (a[k] == pivot1)
{
temp = a[k]; a[k] = a[less]; a[less] = temp;
less++;
}
}
}
}
// subarray
if (pivot1 > pivot2)
{
DSC(a, less, great, div);
}
}

Categories