Complexity of following algorithm for the Three-Sum-Problem - java

Given is a sorted list (a[0],...,a[n-1]) of n integers. I need to find three different indices p, q, r such that the triplet (a[p],a[q],a[r]) satisfies the equation a[p]+a[q]+a[r]=0. Also, the sorted list can contain the same number more than once. The algorithm that is required needs to be quadratic.
I have found a solution (I'm definitely not saying that it is the most efficient one), but I'm quite sure that it's not quadratic (2 for-loops and a while-loop). Here is my code:
public ThreeSumIndices searchTriplet(List<Integer> list) {
for(int i=0; i<list.size()-1; i++){
int w = -list.get(i);
for(int j=i+1; j<list.size(); j++){
int k = 1;
while(j+k<list.size() && list.get(j)+list.get(j+k)!=w){
k++;
}
if(j+k==list.size()){
k = 1;
} else if(list.get(j)+list.get(j+k)==w){
return new ThreeSumIndices(i,j,j+k);
}
}
}
return null; //no indices found.
}
ThreeSumIndices is a seperate class. It returns the indices we're looking for in the form (p,q,r). Constructor parameters are three integer (= three indices).
Example: (-5, 1, 2, 3, 7) --> (0,2,3).
I'm fairly new to complexity analysis, so I was wondering whether my guess of this algorithm not being quadratic was correct or not.
If so, is there a way to get rid of a loop? Or maybe there's an alternative, but more efficient algorithm?
Thanks.

If the array is sorted then all you need to do is the following:
Run loop from i=0 to n-2.
Initialize two index variables l=i+1 and r=n-1
while:l<r, if ((sum = arr[i]+arr[l]+arr[r]) == 0):You got your answer
If sum is less than zero, increment l (l++), otherwise decrement r (r--)
scan all the elements.
for (int i=0; i < n-1; i++) {
int l = i + 1;
int r = n - 1;
int x = arr[i];
while (l < r){
if (x + arr[l] + arr[r] == 0) print()
else if(x + arr[l] + arr[r] < 0) l++;
else r--;
}
}
The time complexity of this code will be O(n^2) with space complexity of O(1)

I had to solve this for a coding interview, here's my python solution which has quadratic time complexity.
from collections import Counter
class Solution(object):
def threeSum(self, nums):
res = []
counts = Counter(nums)
num_counts = sorted(counts.items())
# If we pick 3 of the same nums
if counts[0] >= 3:
res.append([0] * 3)
for i, (first, first_count) in enumerate(num_counts):
# Pick two of these and one greater
if first_count >= 2 and first < 0 and -(first * 2) in counts:
res.append([first, first, -(first * 2)])
# Pick one and two greater
for j in range(i + 1, len(num_counts)):
second, second_count = num_counts[j]
# Pick two of these as second and third num
if second_count >= 2 and -first == 2 * second:
res.append([first, second, second])
# Pick this as second num and third which is greater
third = -(first + second)
if third > second and third in counts:
res.append([first, second, third])
return res
Basically, it counts the occurrences then sorts (number, count) tuples so that
[-1, 0, 1, 2, -1, -4]
becomes
[(-4, 1), (-1, 2), (0, 1), (1, 1), (2, 1)]
We then iterate trying to pick each number twice and third greater if possible and add that to the result. Then, we pick a number once and try to find two greater numbers which sum to zero.

Related

Find all combinations of an array and get top k sum elements

I have an array of numbers say [1,2,3,1,1000] , now I want to get all possible combinations of this array and calculate its sum. Combinations are valid such that two combinations have different subset of elements. Then order all the sum values in descending order and get the top k elements.
Example:
[1,2,3,1,1000]
Combinations:
Duplicates of earlier ones are striked out, for example (3,1) matches the earlier (1,3).
(), (1), (2), (3), (1), (1000), (1,2), (1,3), (1,1), (1,1000), (2,3), (2,1), (2,1000), (3,1), (3,1000), (1,1000), (1,2,3), (1,2,1), (1,2,1000), (1,3,1), (1,3,1000), (1,1,1000), (2,3,1), (2,3,1000), (2,1,1000), (3,1,1000), (1,2,3,1), (1,2,3,1000), (1,2,1,1000), (1,3,1,1000), (2,3,1,1000), (1,2,3,1,1000)
And the corresponding sums:
0, 1, 2, 3, 1, 1000, 3, 4, 2, 1001, 5, 3, 1002, 4, 1003, 1001, 6, 4, 1003, 5, 1004, 1002, 6, 1005, 1003, 1004, 7, 1006, 1004, 1005, 1006, 1007
Getting top k=3, sums = 1007, 1006, 1005
So output is [1007, 1006, 1005].
Constraints:
Array size n = 1 to 105
Array elements -109 to 109
k ranges from 1 to 2000
This is my code, reference taken from here:
static List<Long> printDistSum(int arr[]) {
List<Long> list = new ArrayList<>();
int n = arr.length;
// There are totoal 2^n subsets
long total = (long) Math.pow(2, n);
// Consider all numbers from 0 to 2^n - 1
for (int i = 0; i < total; i++) {
long sum = 0;
// Consider binary representation of
// current i to decide which elements
// to pick.
for (int j = 0; j < n; j++)
if ((i & (1 << j)) != 0)
sum += arr[j];
// Print sum of picked elements.
list.add(sum);
}
return list;
}
This code works for small range of inputs but times out for large range of inputs. How to solve this program.
I probably have solution that should be good enough. It has time complexity O(n * k * log(k)).
First we need to calculate max sum - sum of all positive values.
Next we need to iterate over positive values, from smallest to largest. For each of these values we calculate sums of new combinations (at the start we have one combination with max sum).
New combinations will not contains given value so we need to substract it from sum.
At the end we need to iterate over negative values. These values are not belongs to combinations from previous step so we need to add these values to sums.
In every iteration are needed only k maximum sums. I used the PriorityQueue to store these sums. That class use heap data structure so adding/removing values has logarithmic time.
Code:
private static long[] findSums(int[] array, int k) {
long maxSum = Arrays.stream(array).filter(it -> it >= 0).sum();
int[] positives = Arrays.stream(array).filter(it -> it >= 0).sorted().toArray();
int[] negatives = Arrays.stream(array).filter(it -> it < 0).sorted().toArray();
// sort time complexity is O(n*log(n))
PriorityQueue<Long> sums = new PriorityQueue<>(k); // priority queue is implemented using heap so adding element has time complexity O(log(n))
sums.add(maxSum); // we start with max sum - combination of all positive elements
int previous = Integer.MIN_VALUE;
Long[] previousAddedSums = {};
Long[] sumsToIterate;
// iterate over positive values
for (int i = 0; i < positives.length; i++) {
if (positives[i] == previous) {
sumsToIterate = previousAddedSums;
} else {
sumsToIterate = sums.toArray(new Long[sums.size()]);
}
previousAddedSums = new Long[sumsToIterate.length];
for (int j = 0; j < sumsToIterate.length; j++) {
long newSum = sumsToIterate[j] - positives[i];
// new sum is calculated - value positives[i] is removed from combination (subtracted from sum of that combination)
sums.add(newSum);
previousAddedSums[j] = newSum;
if (sums.size() > k) {
sums.poll(); // only first k maximum sums are needed at the moment
}
}
previous = positives[i];
}
previous = Integer.MAX_VALUE;
// iterate over negative values in reverse order
for (int i = negatives.length - 1; i >= 0; i--) {
if (negatives[i] == previous) {
sumsToIterate = previousAddedSums;
} else {
sumsToIterate = sums.toArray(new Long[sums.size()]);
}
previousAddedSums = new Long[sumsToIterate.length];
for (int j = 0; j < sumsToIterate.length; j++) {
long newSum = sumsToIterate[j] + negatives[i]; // value negatives[i] is added to combination (added to sum of that combination)
sums.add(newSum);
previousAddedSums[j] = newSum;
if (sums.size() > k) {
sums.poll();
}
}
previous = negatives[i];
}
long[] result = new long[sums.size()];
for (int i = sums.size() - 1; i >=0 ; i--) {
result[i] = sums.poll();
}
// get sums from priority queue in proper order
return result;
// this whole method has time complexity O(n * k * log(k))
// k is less than or equal 2000 so it should be good enough ;)
}
Demo: https://ideone.com/yf6POI
Edit: I have fixed my solution. Instead of iterating over distinct values I check if current value is same like previous. In that case I use combinations (sums) created in previous step. This prevents from creating duplicates of combinations.
I'm sorry if I didn't explain this well enough. I don't have experience in describing algorithmic / mathematical things in english.
Pls ignore all previous posts cuz they are all wrong.
Intuitively, we gotta use backtrack to find all desired combos, but it's impossible to backtrack on 10^5 elements.
Constraint 1 <= n <= 10^5 alludes that our algorithm bottlenecked by O(nlogn) sorting
Constraint 1 <= k <= min(2000,2^n) alludes that we can backtrack on k elements since k is less than 11. 2^11=2024/log(2000)=11 -- actually this "2^n" gives away solution :)
My algorithm (nlog(n) + 2^k)
sort the array
Record the highest score combo which is the sum of all positive integers
Find a window in the sorted array of math.min(log(k)--which is less than 11,n) elements -- worst case, this window consists of the
lowest 11 absolute values in the sorted array. Several approaches to
achieve that, since the candidates must be inside 22 elements
window(11 smallest positive values + 11 biggest negative values), we
can use PriorityQueue of size 11 scanning over these 22 elements. or
we can use two pointers to find the sliding window of size 11.
backtrack on this 11 absolute value elements window, find sum of each combo and put them into a size k/k-1 PriorityQueue. (k is for
the case that there's no positive elements)
result is the sum of all positive integers plus (sum deducted by each of k-1 elements in PriorityQueue).
I was also asked the same question yesterday but sadly I was not able to solve it yesterday. I have tried solving it today and think I have the answer today.
First of all I don't think that different subsets mean different costs in a set i.e in array of [1,2,3,1] both subsets are valid => [1,2,3] and [2,3,1] as they both use different 1's. Now here is my solution keeping this in mind. But if you really want to keep distinct elements in set then you can simply remove the multiple elements and do partial_sort then.
Logic
Store sum of all +ve nos. in a variable, say maxsum.
Convert the negative nos. to their absolute values.
Get lowest min(k-1, n) elements in sorted order.
Find all their combinations and subtract them from the maxsum.
While finding all their combinations we only need lowest k-1 combos. So we have to find a way to keep the number of combinations to that. For that use a sorted data structure and limit its size to k and then for every element in the sorted array iterate through the combos and add those combos to the sorted data structure if the end element of that data structure is greater. Also pop the end element after that.
For taking care of the above point I am using 2 vectors since the order already remains sorted.
The proposed solution has time complexity of O(n*log(k) + k^2).
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long int ll;
template <class T>
void print(vector<T> topSumm)
{
for (ll itr : topSumm)
cout << itr << '\t';
cout << '\n';
}
vector<ll> mergeSortedArrays(vector<ll> &minns, vector<ll> &temp)
{
vector<ll> ans(minns.size() + temp.size());
int i{0}, j{0}, k{0};
while (i < minns.size() && j < temp.size())
{
if (temp[j] < minns[i])
ans[k++] = temp[j++];
else
ans[k++] = minns[i++];
}
while (i < minns.size())
ans[k++] = minns[i++];
while (j < temp.size())
ans[k++] = temp[j++];
return ans;
}
vector<ll> topKSum(vector<int> &arr, int k)
{
int n{(int)arr.size()};
ll maxSumm{0};
for (int i{0}; i < n; ++i)
{
if (arr[i] > 0)
maxSumm += arr[i];
else
arr[i] = -arr[i];
}
int nk{min(k - 1, n)};
partial_sort(arr.begin(), arr.begin() + nk, arr.end());
vector<ll> minns{0, maxSumm};
ll summ{};
bool breakOuter{false};
for (int i{0}; i < nk; ++i)
{
vector<ll> temp;
for (ll nums : minns)
{
summ = nums + arr[i];
if (minns.size() + temp.size() < k)
temp.push_back(summ);
else
{
if (minns.back() > summ)
{
minns.pop_back();
temp.push_back(summ);
}
else
{
if (nums == 0)
breakOuter = true;
break;
}
}
}
if (breakOuter)
break;
minns = mergeSortedArrays(minns, temp);
}
vector<ll> ans(k);
int i{0};
for (ll nums : minns)
ans[i++] = maxSumm - nums;
return ans;
}
int main()
{
int t;
cin >> t;
while (t--)
{
int n, k;
cin >> n >> k;
vector<int> arr(n);
ll maxSumm{0};
for (int i{0}; i < n; ++i)
cin >> arr[i];
vector<ll> topSums = topKSum(arr, k);
print<ll>(topSums);
}
return 0;
}

I am stuck at implementing Radix sort recursively

I'm required to implement a programm that sorts numbers ranging from 0 to 99999 recursively (this is basically Radix sort). The process itself is kinda simpel: The user types in an array that contains those numbers in the main method. Then, the main method calls for the sort-method where I create a two-dimensional array named 'space' with 10 rows and 1 column. Then, I divide every number in the array by the digit, which would be 10.000 in the first run. So, for example, 23456 / 10000 = 2,3456 = 2 (in java), hence, the programm puts this number in space[2][0], so in the second row. Then, we take this entire row and extend it, which is done in the putInBucket-method. We do this in order to make sure that we can put another number into the same row.
We do this for every number that is inside the 'numbers'-array. Then, we want to work with these rows and sort them again by the same principle, but now we take a look at the second digit. We want to do this from left to right, not from right to left. So, if our second row would look like this
[23456, 24567],
we'd want to compare the 3 and the 4, which leads to 23456 < 24567.
We do this with the help of the recursive call at the end of the sort method. Now, this is where I am lost. I simply don't know how to manipulate the digit-variable in order to be able to work with the second, third, ... digit of each number. In the first run, as you see, this can be simply done by dividing through 10.000, but I didn't find a way to go further from here.
Please note: Yes, this is a homework question, hence, I'm only allowed to use primitives here. We didn't go through stuff like math.pow(...) yet. Thanks in advance!
public static int[] sort(int[] numbers, int digit) {
if (numbers.length == 0)
return numbers;
int[][]space = new int[10][1];
int i, j = 0;
for (j = 0; j < numbers.length; j++) {
i = numbers[j] / digit;
space[i][0] = numbers[j];
space[i] = putInBucket(space[i], numbers[j]);
}
for (i = 0; i < space[i].length; i++) {
sort(space[i], digit); //not sure how to work with digit here
}
return ... //not sure what to return here
}
private static int[] putInBucket(int[] bucket, int number) {
int[] bucket_new = new int[bucket.length+1];
for (int i = 1; i < bucket_new.length; i++) {
bucket_new[i] = bucket[i-1];
}
return bucket_new;
}
public static void main (String [] argv) {
int[] numbers = IO.readInts("Numbers: ");
int digit = 10000;
int[] bucket = sort(numbers, digit);
}
To extract the last digit, the remainder operator % is your friend:
123 % 10 == 3
if you haven't covered the % operator yet, you can use
123 % 10 == 123 - (123 / 10 * 10) == 3
To extract another digit, you can first move it to the end with /:
123 / 10 == 12
12 % 10 == 2
You can therefore extract an arbitrary digit using
(number / mask) % 10
where mask ∈ {..., 10000, 1000, 100, 10, 1}.
Extra credit
Radix sort is usually implemented in the binary number system instead because a binary digit (or a sequence thereof) can be extracted without performing a division, which is more efficient:
x % 16 == x & 15;
x \ 16 == x >> 4;
Also, if you are implementing this for real, you'd need a more efficient way to grow buckets (your implementation takes O(n) to add a single element to the bucket, adding n elements to the bucket therefore takes O(n^2), which makes your radix sort slower than insertion sort). Dynamic arrays are usually implemented with a more efficient geometric expansion.
This should work:
public static int[] sort(int[] numbers, int digit) {
if (numbers.length == 0 || digit <= 0)
return numbers;
int[][]space = new int[10][10];
int[] len = new int[10];
int i, j = 0;
for (j = 0; j < numbers.length; j++) {
i = (numbers[j] / digit) % 10;
len[i]++;
for (int k = len[i] - 1; k > 0; k--) {
space[i][k] = space[i][k - 1];
}
space[i][0] = numbers[j];
}
for (i = 0; i < 10; i++) {
int[] bucket = new int[len[i]];
for (int k = 0; k < len[i]; k++)
bucket[k] = space[i][k];
space[i] = sort(bucket, digit / 10);
}
int k = 0;
for (i = 0; i < 10; i++) {
for (j = 0; j < len[i]; j++) {
numbers[k] = space[i][j];
k++;
}
}
return numbers;
}
a) Firstly, space is allocated as having only one column. So, space[i] = bucket will not work.
Instead, you could declare it as int[10][10]. (Note: it will only support max of 10 values in one bucket). Or you may allocate new arrays programmatically. Or of course, a List might be better suited.
b) i = (numbers[j] / digit) % 10;
To get the required digit only. For eg: if the number is 12130, and digit = 1000, we want to set i to 2, not 12.
c) putInBucket replaced with an in-place loop.
d) For each bucket of space, we sort it by one digit lower by calling sort recursively.
e) Finally, the result to be returned (numbers), can be created by looping through space from digit 0 to 9.
Note:
This solution could probably be made better.

Dynamic programming with Combination sum inner loop and outer loop interchangeable?

I am a little confuse about the dynamic programming solution for combination sum, that you are given a list of numbers and a target total, and you want to count how many ways you can sum up to this target sum. Numbers can be reused multiple times. I am confused about the inner loop and outer loop that whether they are interchangeable or not. Can some explain the difference between the following two, and in what case we would use one but not the other, or they are the same.
int [] counts = new int[total];
counts[0] = 1;
// (1)
for(int i = 0; i <= total; i++) {
for(int j = 0; j < nums.length; j++) {
if(i >= nums[j])
counts[i] += counts[i - nums[j]];
}
}
// (2)
for(int j = 0; j < nums.length; j++)
for(int i = nums[j]; i <= total; i++) {
counts[i] += counts[i - nums[j]];
}
}
The two versions are indeed different, yielding different results.
I'll use nums = {2, 3} for all examples below.
Version 1 finds the number of combinations with ordering of elements from nums whose sum is total. It does so by iterating through all "subtotals" and counting how many combinations have the right sum, but it doesn't keep track of the elements. For example, the count for 5 will be 2. This is the result of using the first element (with value 2) and finding 1 combination in nums[3] and another combination for the second element (value 3) with the 1 combination in nums[2]. You should pay attention that both combinations use a single 2 and a single 3, but they represent the 2 different ordered lists [2, 3] & [3, 2].
Version 2 on the other hand find the number of combinations without ordering of elements from nums whose sum is total. It does so by counting how many combinations have the right sum (fur each subtotal), but contrary to version 1, it "uses" each element completely before moving on to the next element thus avoiding different orderings of the same group. When counting subtotals with the first element (2), all counts will initially be 0 (except the 0 sum sentinel), and any even subtotal will get the new count of 1. When the next element used, it is as if it's coming after all 2's are already in the group, so, contrary to version 1, only [2, 3] is counted, and not [3, 2].
By the way, the order of elements in nums doesn't affect the results, as can be understood by the logic explained.
Dynamic programming works by filling out entries in a table assuming that previous entries in the table have been fully completed.
In this case, we have counts[i] is dependent on counts[i - nums[j]]; for every entry j in nums.
In this code snippet
// (1)
for(int i = 0; i < total; i++) {
for(int j = 0; j < nums.length; j++) {
if(i >= nums[j])
counts1[i] += counts1[i - nums[j]];
}
}
We fill the table in order from 0 to total in that order. This is the action of the outer loop. The inner loop goes through our different nums and updates the current entry in our table based on the previous values, which are all assumed to be completed.
Now look at this snippet
// (2)
for(int j = 0; j < nums.length; j++){
for(int i = nums[j]; i < total; i++) {
counts2[i] += counts2[i - nums[j]];
}
}
Here we are iterating through our list of different counts and updating our totals. This breaks the concept of dynamic programming. None of our entries can ever be assumed to be complete until we are completely finished with our table.
Are they the same? The answer is no they are not. The following code illustrates the fact:
public class dyn {
public static void main(String[] args) {
int total = 50;
int[] nums = new int[]{1, 5, 10};
int [] counts1 = new int[total];
int [] counts2 = new int[total];
counts1[0] = 1;
counts2[0] = 1;
// (1)
for(int i = 0; i < total; i++) {
for(int j = 0; j < nums.length; j++) {
if(i >= nums[j])
counts1[i] += counts1[i - nums[j]];
}
}
// (2)
for(int j = 0; j < nums.length; j++){
for(int i = nums[j]; i < total; i++) {
counts2[i] += counts2[i - nums[j]];
}
}
for(int k = 0; k < total; k++){
System.out.print(counts1[k] + ",");
}
System.out.println("");
for(int k = 0; k < total; k++){
System.out.print(counts2[k] + ",");
}
}
}
This will output 2 different lists.
They are different because we are updating our counts[i] with incomplete information from earlier in the table. counts[6] assumes you have the entry for counts[5] and counts[1], which in turn assume you have the entries for counts[4], counts[3], counts[2], and counts[0]. Thus, each entry is dependent on (in the worst case all of) the previous entries in the table.
Addendum:
Interesting (perhaps obvious) side-note:
The two methods produce the same list up until the smallest pairwise sum of entries in nums.
Why?
This is when the information from previous entries becomes incomplete (with respect to the first loop). That is, if we have int[] nums = new int[]{3, 6}, then counts[3+6] will not be computed correctly, because either
count[3] will not be right or count[6] will not align with the result obtained using the first loop, depending on which stage of the computation we have done yet.
In light of criticism of my previous answer, I thought I'd take a more mathematical approach.
As in #Amit 's answer, I will use nums = {2, 3} in examples.
Recurrence Relations
The first loop computes
S(n) = S(n-3) + S(n-2)
Or, more generally, for some set {x_1, x_2, x_3, ... ,x_k}:
S(n) = S(n- x_1) + S(n- x_2) + ... + S(n- x_k)
It should be clear that each S(n) is dependent on (possibly all) previous values, and so we must start on 0 and populate the table upwards to our desired total.
The second loop computes a recurrence S_2(n) with the following definitions:
S_1(n) = S_1(n-2)
S_2(n) = S_1(n) + S_2(n-3)
More generally, for some set {x_1, x_2, x_3, ... ,x_k}:
S_1(n) = S_1(n- x_1)
S_2(n) = S_1(n) + S_2(n- x_2)
...
S_k(n) = S_{k-1}(n) + S_k(n- x_k)
Each entry in this sequence is like those from the first loop; it is dependent on the previous entries. But unlike the first loop, it is also dependent on earlier sequences.
Put perhaps more concretely:
S_2 is dependent on not only (possibly all) previous entries of S_2, but also on previous entries of S_1.
Thus, when we want to compute the first recurrence, we begin at 0 and compute each entry, for each number in our nums.
When we want to compute the second recurrence, we compute each intermediate recurrence one at a time, each time storing the result in counts.
In Plain English
What do these two recurrences compute? As #Amit 's answer explains, they compute the number of combinations that sum to total, with and without preserving order. It's easy to see why, again using our example of nums = {2, 3}:
Note my use of the word list to denote something ordered, and the word set to denote something unordered.
I use append to mean adding to the former, and add to denote adding to the latter.
If you know
how many lists of numbers add to 2,
and how many add to 3,
and I ask you
how many add to 5?
You can append a 3 to every one of the former lists, and a 2 to every one of the latter lists.
Thus (how many add to 5) = (how many add to 3) + (how many add to 2)
Which is our first recurrence.
For the second recurrence,
If you know
how many sets of just 2's add to 5 (0)
how many sets of just 2's and 3's add to 2 (1)
You can just take all of the first number, and you can add a 3 to all the sets in the second number.
Note how "sets of just 2's" is a special case of "sets of just 2's and 3's". "sets of just 2's and 3's" depends on "sets of just 2's", just like in our recurrence!
Recursive functions written in java
The following recursive function computes the values for the first loop, with example values 3 and 2.
public static int r(int n){
if(n < 0)
return 0;
if(n == 0)
return 1;
return r(n-2) + r(n-3);
}
The following set of recursive functions computes the values for the second loop, with example values 3 and 2.
public static int r1(int n){
if(n < 0)
return 0;
if(n == 0)
return 1;
return r1(n-2);
}
public static int r2(int n){
if(n < 0){
return 0;
}
return r1(n) + r2(n-3);
}
I have checked them up to 10 and they appear to be correct.

Finding two non-subsequent elements in array which sum is minimal

Intro: As far as I could search, this question wasn't asked in SO yet.
This is an interview question.
I am not even specifically looking for a code solution, any algorithm/pseudocode will work.
The problem: Given an integer array int[] A and its size N, find 2 non-subsequent (can't be adjacent in the array) elements with minimal sum. Also the answer must not contain the first or last elements (index 0 and n-1). Also the solution should be in O(n) time and space complexity.
E.g. when A = [5, 2, 4, 6, 3, 7] the answer is 5, since 2+3=5.
When A = [1, 2, 3, 3, 2, 1] the answer is 4, since 2+2=4 and you can't choose either of the 1's since the are at the ends of the array.
Attempt: At first I thought that one of the numbers in the solution must be the smallest one in the array (besides the first and last), but this was refuted quickly with the counter-example A = [4, 2, 1, 2, 4] -> 4 (2+2)
Then I thought that if I find the 2 smallest numbers (besides the first and last) in the array, the solution will be those two. This obviously quickly failed because I can't choose 2 adjacent numbers, and if I have to choose non-adjacent numbers then this is the very definition of the question :).
Finally I thought, well, I will just find the 3 smallest numbers (besides the first and last) in the array, and the solution will have to be two of those, since two of those have to not be adjacent to each other.
This also failed due to A = [2, 2, 1, 2, 4, 2, 6] -> 2+1=3 , which seems to work because I will find 2, 1, 2, but assuming I am finding the 2, 1, 2 in indexes 1, 2, 3 this won't necessarily work (it would if I found specifically the 2 in index 5 but I can't guarantee that unfortunately).
Question:
Now I'm stumped, can anyone come up with a solution/idea that works?
Here is a live javascript implementation of an algorithm that:
finds the 4 smallest elements (excluding first/last element from search)
finds the pairs of these 4 elements that are not adjacent in original array
finds from these pairs the one with the minimal sum
function findMinNonAdjacentPair(a) {
var mins = [];
// quick exits:
if (a.length < 5) return {error: "no solution, too few elements."};
if (a.some(isNaN)) return {error: "non-numeric values given."};
// collect 4 smallest values by their indexes
for (var i = 1; i < a.length - 1; i++) { // O(n)
if (mins.length < 4 || a[i] < a[mins[3]]) {
// need to keep record of this element in sorted list of 4 elements
for (var j = Math.min(mins.length - 1, 2); j >= 0; j--) { // O(1)
if (a[i] >= a[mins[j]]) break;
mins[j+1] = mins[j];
}
mins[j+1] = i;
}
}
// mins now has the indexes to the 4 smallest values
// Find the smallest sum
var result = {
sum: a[mins[mins.length-1]]*2+1 // large enough
}
for (var j = 0; j < mins.length-1; j++) { // O(1)
for (var k = j + 1; k < mins.length; k++) {
if (Math.abs(mins[j] - mins[k]) > 1) { // not adjacent
if (result.sum > a[mins[j]]+a[mins[k]]) {
result.sum = a[mins[j]]+a[mins[k]];
result.index1 = mins[j];
result.index2 = mins[k];
};
if (k < j + 3) return result; // cannot be improved
break; // exit inner loop: it cannot bring improvement
}
}
}
return result;
}
// Get I/O elements
var input = document.getElementById('in');
var output = document.getElementById('out');
var select = document.getElementById('pre');
function process() {
// translate input to array of numbers
var a = input.value.split(',').map(Number);
// call main function and display returned value
output.textContent = JSON.stringify(findMinNonAdjacentPair(a), null, 4);
}
// respond to selection from list
select.onchange = function() {
input.value = select.value;
process();
}
// respond to change in input box
input.oninput = process;
// and produce result upon load:
process();
Type comma-separated list of values (or select one):</br>
<input id="in" value="2, 2, 1, 2, 4, 2, 6"> <=
<select id="pre">
<option value="5, 2, 4, 6, 3, 7">5, 2, 4, 6, 3, 7</option>
<option value="1, 2, 3, 3, 2, 1">1, 2, 3, 3, 2, 1</option>
<option value="4, 2, 1, 2, 4">4, 2, 1, 2, 4</option>
<option value="2, 2, 1, 2, 4, 2, 6" selected>2, 2, 1, 2, 4, 2, 6</option>
</select>
</br>
Output:</br>
<pre id="out"></pre>
The algorithm has a few loops with following big-O complexities:
find 4 smallest values: O(n), as the inner loop runs at most 3 times, which is O(1)
find the smallest sum of non-adjacent pairs has a double loop: in total the body will run at most 4 times = O(1). NB: The number of possible pairs is 6, but the execution is guaranteed to break out of the loops sooner.
So the algorithm runs in O(n).
Find the smallest number beside the first and the last.
Find the second smallest that is not a neighbour of the first one and not the first or last one in the array. Then build the sum.
If the first element is the second or the penultimate element you already have the solution.
Otherwise calculate the sum of both neighbours of the first number. check if its smaller then the first sum
if not: take the first sum
otherwise take the second one
This will always work because if the first sum is not the answer that means the first number cannot be part of the solution. And that on the other hand means, the solution can just be the second sum.
This problem can be solved with about 10 lines of Java code.
You can start with an obvious but inefficient (O(N^2)) solution:
public class Main {
int solve(int[] array) {
int answer = Integer.MAX_VALUE;
for (int i = 3; i < array.length - 1; i++) {
for (int j = 1; j < i - 1; j++) {
if (array[i] + array[j] < answer) {
answer = array[i] + array[j];
}
}
}
return answer;
}
}
But then you can notice that you actually do not need the internal for loop because you can just preserve the minimum and update it with every new element if necessary, which is faster than finding the minimum anew every time. Therefore the final O(N) solution looks like this:
public class Main {
int solve(int[] array) {
int answer = Integer.MAX_VALUE;
int min = array[1];
for (int i = 3; i < array.length - 1; i++) {
min = Math.min(min, array[i - 2]);
if (array[i] + min < answer) {
answer = array[i] + min;
}
}
return answer;
}
}
Find the four smallest and consider all possibilities among those four. The smallest is nonadjacent to at least one of the second, third, or fourth smallest; the only other possibility that could be better is the second and third smallest (assuming that they are nonadjacent).
I think this does not need any deep reasoning, and can be solved in a single pass, keeping the optimal solution of the array elements processed so far:
public static int[] minimumSumOfNonAcjacentElements(int[] a) {
// the result for the sequence a[1:i]
int minSum = Integer.MAX_VALUE;
int minSumElement1 = Integer.MAX_VALUE;
int minSumElement2 = Integer.MAX_VALUE;
// the minimum element eligible for joining with a[i], i.e. from a[1 : i-2]
int minElement = a[1];
int prevElement = a[2]; // a[i - 1]
for (int i = 3; i + 1 < a.length; i++) {
int sum = minElement + a[i];
if (sum < minSum) {
minSum = sum;
minSumElement1 = minElement;
minSumElement2 = a[i];
}
if (prevElement < minElement) {
minElement = prevElement;
}
prevElement = a[i];
}
return new int[] {minSumElement1, minSumElement2};
}
Here's some test code, with the corner cases from OP's question:
private static void test(int minSumIndex1, int minSumIndex2, int... input) {
int[] result = minimumSumOfNonAcjacentElements(input);
if (result[0] == minSumIndex1 && result[1] == minSumIndex2) {
// ok
} else {
throw new AssertionError("Expected: " + minSumIndex1 + ", " + minSumIndex2 + ". Actual=" + Arrays.toString(result));
}
}
public static void main(String[] args) throws Exception {
test(2, 2, 4, 2, 1, 2, 4);
test(1, 2, 2, 2, 1, 2, 4, 2, 6);
test(1, 2, 0, 2, 1, 2, 4, 2, 0);
System.out.println("All tests passed.");
}
Use dynamic programming.
Remove or disregard the first and last elements of your array. Since they cannot participate in the solution, they are not important. Once you've done this, you can also ignore the "must not be the first or last element" constraint since we've already accounted for it.
Find the solution for the first three elements of (what's left of) the array (and without considering the "no first/last element" rule). There is only one solution in this case (array[0] + array[2]), so this is a trivial step.
Memoize the minimal element which is not the last element (i.e. min(array[0], array[1])).
Find the solution for the first four elements. We don't have to redo the whole problem; instead we just have to ask whether introducing the fourth element allows us to produce a smaller solution. We can do this by adding the fourth element to the minimal element we memoized in the previous step, and comparing the sum to the solution we found in the second step.
Update the memoized minimal element so that it is the minimum of the first three elements.
Continue widening and updating in this fashion until we have considered the entire array.
The whole algorithm is O(n), since both widening and updating are constant-time operations. The algorithm can be proved correct by simple induction. O(n) is also a lower bound since we have to consider every element of the array, so this algorithm is optimal.
Algorithm:
Find the minimum, avoiding the end indices. (1 O(n) pass)
Find the minimum, avoiding the end indices and the index of (1) and adjacent indices. (1 O(n) pass)
Find the minimum, avoiding the end indices and the index of (1) (1 O(n) pass)
Find the minimum, avoiding the end indices and the index of (3) and adjacent indices. (1 O(n) pass)
Return the minimum of the sums (1) + (2), (3) + (4), if they exist.
Passes 3 and 4 are meant to pass the case [4, 2, 1, 2, 4] = 4 by finding both 2s.
public static int minSumNonAdjNonEnd(int[] array)
{
// 1. Find minimum
int minIdx1 = -1;
int minValue1 = Integer.MAX_VALUE;
for (int i = 1; i < array.length - 1; i++)
{
if (array[i] < minValue1)
{
minIdx1 = i;
minValue1 = array[i];
}
}
// 2. Find minimum not among (1) or adjacents.
int minIdx2 = -1;
int minValue2 = Integer.MAX_VALUE;
for (int i = 1; i < array.length - 1; i++)
{
if ((i < minIdx1 - 1 || i > minIdx1 + 1) && (array[i] < minValue2))
{
minIdx2 = i;
minValue2 = array[i];
}
}
boolean sum1Exists = (minIdx1 > -1 && minIdx2 > -1);
int sum1 = minValue1 + minValue2;
// 3. Find minimum not among (1).
int minIdx3 = -1;
int minValue3 = Integer.MAX_VALUE;
for (int i = 1; i < array.length - 1; i++)
{
if ((i != minIdx1) && (array[i] < minValue3))
{
minIdx3 = i;
minValue3 = array[i];
}
}
// 4. Find minimum not among(3) or adjacents.
int minIdx4 = -1;
int minValue4 = Integer.MAX_VALUE;
for (int i = 1; i < array.length - 1; i++)
{
if ((i < minIdx3 - 1 || i > minIdx3 + 1) && (array[i] < minValue4))
{
minIdx4 = i;
minValue4 = array[i];
}
}
boolean sum2Exists = (minIdx3 > -1 && minIdx4 > -1);
int sum2 = minValue3 + minValue4;
if (sum1Exists)
{
if (sum2Exists)
return Math.min(sum1, sum2);
else
return sum1;
}
else
{
if (sum2Exists)
return sum2;
else
throw new IllegalArgumentException("impossible");
}
}
This performs 4 linear searches, for a complexity of O(n).
Test cases:
System.out.println(minSumNonAdjNonEnd(new int[] {5, 2, 4, 6, 3, 7}));
System.out.println(minSumNonAdjNonEnd(new int[] {1, 2, 3, 3, 2, 1}));
System.out.println(minSumNonAdjNonEnd(new int[] {4, 2, 1, 2, 4}));
System.out.println(minSumNonAdjNonEnd(new int[] {2, 2, 1, 2, 4, 2, 6}));
System.out.println(minSumNonAdjNonEnd(new int[] {2, 2, 3, 2}));
5
4
4
3
Exception in thread "main" java.lang.IllegalArgumentException: impossible
I don't know if my solution is correct because I just tested it with the data in the OP, and I don't even know if this is better or worse than the other ideas but I wanted to try it.
static void printMinimalSum(int[] A) {
// Looking for mins so we init this with max value
int[] mins = new int[]{Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE};
// Indices, used just to print the solution
int[] indices = new int[]{-1, -1, -1};
// If the array has length 5 then there's only one solution with the 2nd and 4th elements
if (A.length == 5) {
mins[0] = A[1];
indices[0] = 1;
mins[1] = A[3];
indices[1] = 3;
} else {
// Loop on the array without considering the first and the last element
for (int i = 1; i < A.length - 1; i++) {
// We consider each element which is smaller than its neighbours
if ((i == 1 && A[i] < A[i + 1]) // 1: first element, compare it with the second one
|| (i == A.length - 2 && A[i] < A[i - 1]) // 2: last element, compare it with the previous one
|| (A[i] < A[i + 1] && A[i] < A[i - 1])) { // 3: mid element, compare it with both neighbors
// If the element is "legal" then we see if it's smaller than the 3 already saved
if (A[i] < mins[0]) {
mins[0] = A[i];
indices[0] = i;
} else if (A[i] < mins[1]) {
mins[1] = A[i];
indices[1] = i;
} else if (A[i] < mins[2]) {
mins[2] = A[i];
indices[2] = i;
}
}
}
}
// Compute the 3 sums between those 3 elements
int[] sums = new int[]{Math.abs(mins[0]+mins[1]), Math.abs(mins[0]+mins[2]), Math.abs(mins[1]+mins[2])};
// Find the smaller sum and print it
if (sums[0] < sums[1] || sums[0] < sums[2]){
System.out.println("Sum = " + sums[0] + " (elements = {" + mins[0] + "," + mins[1] + "}, indices = {" + indices[0] + "," + indices[1] + "}");
} else if (sums[1] < sums[0] || sums[1] < sums[2]){
System.out.println("Sum = " + sums[1] + " (elements = {" + mins[0] + "," + mins[2] + "}, indices = {" + indices[0] + "," + indices[2] + "}");
} else {
System.out.println("Sum = " + sums[2] + " (elements = {" + mins[1] + "," + mins[2] + "}, indices = {" + indices[1] + "," + indices[2] + "}");
}
}
public static void main(String[] args) {
printMinimalSum(new int[]{5, 2, 4, 6, 3, 7});
printMinimalSum(new int[]{1, 2, 3, 3, 2, 1});
printMinimalSum(new int[]{4, 2, 1, 2, 4});
printMinimalSum(new int[]{2, 2, 1, 2, 4, 2, 6});
}
Output is:
Sum = 5 (elements = {2,3}, indices = {1,4}
Sum = 4 (elements = {2,2}, indices = {1,4}
Sum = 4 (elements = {2,2}, indices = {1,3}
Sum = 3 (elements = {1,2}, indices = {2,5}
which seems fine.
edit: you're right, I completely ignored the adjacency constraint.
luckily I've thought of a solution.
The algorithm goes like this:
You run once over the array to find the smallest (O(n))
You run a second time to find the second smallest (O(n))
If second smallest is not adjacent to smallest we're done(O(1) - just an index check)
Otherwise run a third time to find third smallest (still O(n))
If not adjacent to smallest return smallest and third smallest
otherwise return second and third smallest
Elaborating on the above answer, you'd need a modified insertion-sort to track the smallest four values and the corresponding indexes (an array of 4 elements for each).
Once found the solution would be the first pair whose difference in indexes would be more than 1 and whose sum is the least.
The solution being one of (0,1) or (0,2) or (0,3) or (1,2) or (1,3) or (2,3) where the values indicate the indexes of the array that in turn tracks the position of the actual elements in the array.
Also you'd need to handle the special case for array-length 5 (arr\[1]+arr[3]) and an error for those arrays less than 5.
I think this should work:
Find the minimum 3 element and their indices. Since all of them can't be adjacent choose 2 among them.
If all of them are adjacent and the minimum number is in the middle of them, iterate through all elements, find the forth minimum element, choose minimum of min1+min4, min2+min3, whichever is smaller.
You can do this in one iteration too.
I have used dynamic programming to solve it.
Idea is to first create the array which tracks the minimum found till now as below:
Input array = [1, 3, 0, 5, 6]
Minimum array = [1, 1, 0, 0, 0]
Now using the minimum array and the input array we can use below:
DP[i] = min(DP[i-1], min(first_data, second_data))
where DP[i] means the minimum found till now which is sum of two previous alternate elements.
first_data = sum of current element in input array + sum of current-2 element in minimum array
second_data = sum of current-1 element in input array + sum of current-3 element in minimum array
import random
def get_min(numbers):
#disregard the first and last element
numbers = numbers[1:len(numbers)-1]
#for remembering the past results
DP = [0]*len(numbers)
#for keeping track of minimum till now found
table = [0]*len(numbers)
high_number = 1 << 30
min_number = numbers[0]
table[0] = min_number
for i in range(0, len(numbers)):
DP[i] = high_number
for i in range(1, len(numbers)):
if numbers[i] < min_number:
min_number = numbers[i]
table[i] = numbers[i]
else:
table[i] = min_number
for i in range(0, len(numbers)):
min_first, min_second = high_number, high_number
if i >= 2:
min_first = numbers[i] + table[i-2]
if i >= 3:
min_second = numbers[i-1] + table[i-3]
if i >= 1:
DP[i] = min(min(DP[i-1], min_first), min_second)
return DP[len(numbers)-1]
input = random.sample(range(100), 10)
print(input)
print(get_min(input))
How about that: you find k smallest numbers (or more precisely their indices) (k big enough, let say 10). It is sure, that the wanted pair is between them. Now you just check the possible 50 pairs and select the best which satisfies the constraints.
You don't need 10, less would do - but more than 3 :)
Edit: finding k smallest numbers is O(n), because you just keep the best 10 for example in a heap (add new element, delete maximum O(k*logk)=O(1) operations).
Then there will be a pair which satisfy the constraints (not next to each other). It is also clear that, if you build the sum with an element not from those k, it would be bigger than the best pair chosen from those k elements.
Checking at most k*k pairs is also O(1), thus the whole running time is O(n).
Here is the python implementation in O(N) time complexity
import math
def minSum(array):
_min = array[1]
result = math.inf
for i in range(3, len(array) - 1):
_min = min(_min, array[i-2])
if (_min + array[i]) < result:
result = _min + array[i]
return result
As we only need to track the minimum sum of two no adjacent values, we could do it by iterating over the array excluding the first and last element and keeping the track of min values and minimum sum. current min value will the two index before current value. For example if we are checking the current index i then minValue is the minimum value from index 1 to i-2.
Code:
int minSum(int[] A){
int minSum=Integer.MAX_VALUE;
int min= Integer.MAX_VALUE;
for(int i=3; i<A.length-1; i++){
min= Math.min(A[i-2], min);
minSum = Math.min(min+A[i], minSum);
}
return minSum;
}

How many times will merge and mergesort be run in mergesort algorithm?

These are homework questions, but I would like to understand the concepts behind them, not just get the answers.
I know that the running time of MergeSort is O(nlogn). It seems that the merge method would have to run n times (because it has to merge all of the arrays, and there ends up being n arrays). Therefore, I thought I could deduce that the MergeSort() method would be called logn times. I also thought that made sense because it is dividing the array, so it would keep dividing itself by 2, so logn.
Therefore, I feel that the answers are C and A, respectively. But I am a bit skeptical because of the note saying the questions are asking how many times the method is called, not the running time. I would appreciate some advice and an explanation of counting vs running time. Thank you.
The questions are as follows:
18.
We defined a recursive method MergeSort() to divide the input array in the middle and recursively sort each part.
Suppose we have an array of length n, and we apply this merge sort algorithm. How many times will the MergeSort() method be called?
A. O(n)
B. O(n2)
C. O( log(n) )
D. O( n log(n) )
[[[
Note that this and the next question ask to count how many times the methods are called. This is NOT about the running time; it is about the counting.
]]]
19.
Suppose we have an array of length n, and we apply this merge sort algorithm. How many times will the merge() method be called?
A. O(n)
B. O(n2)
C. O( log(n) )
D. O( n log(n) )
Source code:
import java.util.Arrays;
public class MergeSort
{
public static void merge(int[] data, int first, int last)
{
int[] temp = new int[last - first + 1]; // A new array to hold the merged result
int mid = (first + last) / 2;
int i = first, j = mid + 1, k = 0;
// Copy smaller item from each subarray into temp until one
// of the subarrays is exhausted
while (i <= mid && j <= last)
{
if (data[i] < data[j])
temp[k++] = data[i++];
else
temp[k++] = data[j++];
}
// Copy remaining elements from first subarray, if any
while (i <= mid)
temp[k++] = data[i++];
// Copy remaining elements from second subarray, if any
while (j <= last)
temp[k++] = data[j++];
// Copy merged data back into original array
for (i = first; i <= last; i++)
data[i] = temp[i - first];
}
public static void merge2(int[] data, int first, int last)
{
int mid = (first + last) / 2;
int i = first, j = mid + 1;
int len = last - first + 1;
int[] temp = new int[len];
for (int k = 0; k < len; k++)
{
if (i == mid + 1) // The left part is done
temp[k] = data[j++];
else if (j == last + 1) // The right part is done
temp[k] = data[i++];
else if (data[i] < data[j]) // Get one from the left
temp[k] = data[i++];
else // Get one from the right
temp[k] = data[j++];
}
// Copy merged part back into the original array
for (i = first; i <= last; i++)
data[i] = temp[i - first];
}
public static void mergeSort(int[] data, int first, int last)
{
// intermediate result
System.out.println(Arrays.toString(Arrays.copyOfRange(data, first, last + 1)));
if (first >= last)
return;
int mid = (first + last) / 2;
mergeSort(data, first, mid);
System.out.println("testingMerge");
mergeSort(data, mid + 1, last);
System.out.println("testingMerge");
// merge two sorted parts
merge(data, first, last); //merge2(data, first, last);
// intermediate result
}
public static void main(String[] args)
{
int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
System.out.println("begin with: \n" + Arrays.toString(array));
System.out.println("------------------");
mergeSort(array, 0, array.length - 1);
System.out.println("------------------");
System.out.println("end with: \n" + Arrays.toString(array));
}
}
Your answer appears to be correct.
We defined a recursive method MergeSort() to divide the input array in the middle and recursively sort each part.
So we expect to call MergeSort log n times. Since each recursive step is one half the length of n.
Since we know that merge sort is O(n log n) could stop here as MergeSort is called log n times, the merge must be called n times. But we can also reason that we must subdivide the n items until each input consists of one element. Clearly we must merge n such one item lists to arrive at a final out consisting of n items.

Categories