Complexity of the following code - java

I was given the following task:
Given - 2 list. Size of the first list is N1, size of the second list is N2. Each list don't have the same elements.
Write a code that create a new list with elements from first and second lists. This list also shouldn't have the same elements.
Also estimate the complexity of your code.
I write the followin code:
public class Lists {
static ArrayList<Integer> getNewList(ArrayList<Integer> list1,
ArrayList<Integer> list2) {
ArrayList<Integer> tmp = new ArrayList<>();
for (Integer i : list1) {
tmp.add(i);
}
for (Integer i : list2) {
if (!list1.contains(i))
tmp.add(i);
}
return tmp;
}
public static void main(String[] args) {
Integer[] arr1 = {1, 2, 3, 14, 15, 16};
Integer[] arr2 = {3, 6, 7, 8, 14};
ArrayList<Integer> list1 = new ArrayList<>(Arrays.asList(arr1));
ArrayList<Integer> list2 = new ArrayList<>(Arrays.asList(arr2));
for (Integer i : getNewList(list1, list2)) {
System.out.print(i + " ");
}
}
}
and say that time of execution of getNewList method would be proportional to N1*N2. In reply I receive the following without any explanation - "You are wrong, the complexity of this code is not N1*N2".
So can someone tell what is the right answer? And explain how complexity is determine?

The complexity of your code is O(N1*N2), but you can do much better by using a HashSet to determine which numbers appear in both Lists :
static ArrayList<Integer> getNewList(ArrayList<Integer> list1,
ArrayList<Integer> list2) {
ArrayList<Integer> tmp = new ArrayList<>();
HashSet<Integer> dups = new HashSet<>();
tmp.addAll(list1);
dups.addAll(list1);
for (Integer i : list2) {
if (!dups.contains(i))
tmp.add(i);
}
return tmp;
}
This would give you O(N1+N2) running time, since insertion and lookup take expected O(1) time in HashSet.

Well, the short answer to your question is: complexity is N1 + (N2*N1)/2 + N3 (size of the new list), which should be in O(N1*N2)
Breakdown:
for (Integer i : list1) {
tmp.add(i);
}
-> clearly, this is N1
for (Integer i : list2) {
if (!list1.contains(i))
tmp.add(i);
}
-> list2 iteration => N2
-> for each of this iteration, you call .contain method
which uses sequential search, resulting in N1/2 iterations (on average)
-> So, you get N2*N1/2
In the main you have a loop, iterating from 0 until N3 (which is the length of the new List)
So, overall you get N1 + (N2*N1)/2 + N3, which should be in O(N1*N2)

I'm definitely seeing O(N1 * N2) complexity here too. I'm guessing your professor overlooked the cost of the contains call in the following:
for (Integer i : list2) {
if (!list1.contains(i))
tmp.add(i);
}
contains on ArrayList is O(N) complexity. Since your loop over list2 is also O(N), you end up with O(N1 * N2).

Thanks for #Slanec's explanation, I recheck the implementation of contains(Object obj) in JDK and find it is as below
public boolean contains(Object obj) {
return indexOf(obj) >= 0;
}
public int indexOf(Object obj) {
if (obj == null) {
for (int i = 0; i < size; i++)
if (elementData[i] == null)
return i;
} else {
for (int j = 0; j < size; j++)
if (obj.equals(elementData[j]))
return j;
}
return -1;
}
Obviously, the time complexity of contains(Object obj) is O(n).(I thought it was O(1) at first)
So the time complexity of the code should be O(N1 * N2) but not O(n).

Related

Find sum of subsequent 3 elements of an array

I need to sum the three consecutive elements of an array when appending numbers to the same array dynamically and return true if the sum is equal to the argument value. I have already written the code below and it all return required output but it fails for some test cases( I don't have the the exact test cases), Can anybody tell me what exact scenario which my programme can be failed?
import java.util.LinkedList;
import java.util.List;
public class Test {
List<Integer> mergeList = new LinkedList<Integer>();
List<List<Integer>> allList = new LinkedList<List<Integer>>();
List<Integer> tail;
int from = 0;
int to = 0;
public void addLast(int[] list) {
allList.removeAll(allList);
for(int i : list) {
mergeList.add(i);
}
if (mergeList.size() > 0) {
int j = 0;
while(to < mergeList.size()){
from = j;
to = j + 3;
tail = mergeList.subList(from, to);
j++;
allList.add(tail);
}
}
}
public boolean containsSum3(int sum) {
boolean retVal = false;
for (List<Integer> sum3List : allList) {
if (sum3List.stream().mapToInt(Integer::intValue).sum() == sum) {
retVal = true;
}
}
return retVal;
}
public static void main(String[] args) {
Test s = new Test();
s.addLast(new int[] { 1, 2, 3 });
System.out.println(s.containsSum3(6));
System.out.println(s.containsSum3(9));
s.addLast(new int[] { 4 });
System.out.println(s.containsSum3(9));
s.addLast(new int[] { 5, 2});
System.out.println(s.containsSum3(11));
s.addLast(new int[] { 0, -1 });
System.out.println(s.containsSum3(7));
System.out.println(s.containsSum3(2));
}
}
Output:
true
false
true
true
true
false
I generated large random collections of integers and couldn't find any obvious cases your code fails for beyond the insufficient elements. Incidentally, the function I wrote to check if a list has any consecutive n elements that sum to a given value was:
public static boolean containsSum(List<Integer> list, int sum, int n) {
return IntStream.range(0, list.size() - n + 1)
.anyMatch(i -> list.subList(i, i + n).stream()
.reduce(0, Integer::sum) == sum);
}
I can't see any reason for your code that keeps all the list of lists: the space / time tradeoff doesn't make a lot of sense. I suggest you could simplify addLast to just add the elements to mergeList. There are a bunch of stylistic issues with your code but I'm sure you'll work those out in your own time.

Getting all combinations from a list using recusion, including combinations that use the same number

I have a list of numbers: [10, 13, 15]. I am trying to find the combinations of numbers in the list that add up to or are less than the target of 28.
Currently I have a recursive method:
public void combinations(ArrayList<Integer>data,int fromIndex, int endIndex)
{
int sum = 0;
int target = 28;
ArrayList<Integer>results = new ArrayList<Integer>();
if(fromIndex == endIndex)
{
return;
}
for(int currentIndex = fromIndex; currentIndex < endIndex; currentIndex++)
{
if(sum + data.get(currentIndex) <= target)
{
results.add(data.get(currentIndex));
sum +=data.get(currentIndex);
}
}
System.out.println(results);
combinations(data, fromIndex + 1, endIndex);
}
Currently this outputs:
[10, 13],[13, 15],[15] which are correct and I understand why I am getting these solutions as my recursive method has a +1. However other solutions such as [10],[13],[10,10] ect are not included and I was wondering how I would go about implementing this, would I need to change my increments in my recursive method?
public static void combinations(ArrayList<Integer> arr, ArrayList<ArrayList<Integer>> ans, int startIndex, int endIndex, int sum) {
if(startIndex > endIndex) {
for(ArrayList<Integer> x : ans) {
System.out.println(x);
}
return;
}
ArrayList<Integer> newTemp;
ArrayList<ArrayList<Integer>> newAns = new ArrayList<ArrayList<Integer>>();
for(ArrayList<Integer> x : ans) {
newAns.add(x);
}
for(ArrayList<Integer> x : ans) {
int s = 0;
newTemp = new ArrayList<Integer>();
for(Integer v : x) {
newTemp.add(v);
s+=v;
}
if(s + arr.get(startIndex) <= sum) {
newTemp.add(arr.get(startIndex));
newAns.add(newTemp);
}
}
if(arr.get(startIndex) <= sum ) {
newTemp = new ArrayList<Integer>();
newTemp.add(arr.get(startIndex));
newAns.add(newTemp);
}
combinations(arr,newAns, startIndex+1, endIndex, sum);
}
I have to rewrite your code as I was unable to think through your code.
Secondly, I have to make a newArrayList all time to avoid ConcurrentModificationException which I have faced the first time and will overcome later after gaining some knowledge about it.
Now, this method should be called as
public static void main (String[] args) {
ArrayList<Integer> arr = new ArrayList<Integer>();
arr.add(10);
arr.add(15);
arr.add(13);
arr.add(-5);
arr.add(28);
combinations(arr, new ArrayList<ArrayList<Integer>>(), 0, 4, 28);
}
Explanation: I have generalized your question's answer to fit any sum in int range.
I have made ArrayList of ArrayList which will print all the combinations at the base case.
I have first added an ArrayList containing single element i.e current element if the current element is the <= sum.
Then with all remaining ArrayList, I have calculated the sum of each and then check whether adding the current element into the previous ArrayList holds the above condition.
At the same time I have made new ArrayList and copied all elements while I was calculating the sum of each ArrayList and then if the second case holds good then I added the current element into temp ArrayList and then added the temp ArrayList to the answer ArrayList of ArrayLists.
Then I called the recursion by incrementing the startIndex by 1.

Determining equivalent arrays in Java

In java, What is the most efficient way to decide whether two arrays contain all of the same elements. The arrays can have duplicates or can be unsorted. Most efficient meaning run time complexity and space complexity.
You can use HashMaps too keep track of the values you have previously seen :
public static void main(String[] args) {
System.out.println(allEquivalents(new String[][] { { "1", "3" }, { "1", "1", "3" } })); // true
System.out.println(allEquivalents(new String[][] { { "1" }, { "1", "1", "1" }, { "1", "1", "2" } })); // false
}
public static boolean allEquivalents(String[][] arrays) {
final HashMap<String, Integer> foundValues = new HashMap<String, Integer>();
for (int i = 0; i < arrays.length; i++) {
for (final String key : arrays[i]) {
// we have a value not seen in the previous array, return false
if (i > 0 && (!foundValues.containsKey(key) || foundValues.get(key) < i - 1)) {
return false;
}
foundValues.put(key, i);
}
}
// check if all the values where in the last array
for (final Integer i : foundValues.values()) {
if (i < arrays.length - 1) {
return false;
}
}
return true;
}
You are iterating only once on each value, and once on the values in the HashMap. So, the complexity is O(n), with n your total number of values.
Unless you are willing to use some additional storage (such as HashSet as mentioned in the comments), you must sort the two arrays prior to finding out if they are equivalent. Then you can do one iteration on the two arrays, and verify that each value found in the first array is also found in the second array.
Sorting the arrays would take you O(n log n), where n is the size of the longer of the two arrays. Iterating over the sorted arrays would take O(n). Therefore the overall time complexity would be O(n log n).
Assuming the arrays are sorted, this is how you find if they are equivalent (hopefully I don't have any bugs, as I didn't test it):
int i = 0;
int j = 0;
int value = -1;
while (i<arr1.length && j<arr2.length) {
int value = arr1[i];
if (!value.equals(arr2[j]))
return false;
do {i++;} while (i<arr1.length && arr1[i].equals(value));
do {j++;} while (j<arr2.length && arr2[j].equals(value));
}
while (i<arr1.length) {
if (!arr1[i].equals(value))
return false;
i++;
}
while (j<arr2.length) {
if (!arr2[j].equals(value))
return false;
j++;
}
return true;
Let's assume the arrays are of the same size, n - if not, return false.
I can think of two approaches...
Sort the arrays -> O(nlogn). Iterate through array a, comparing every element at a[i] to b[i], returning false if they aren't equal.
Use Trie -> O(n.m) , where m is just log of value of the largest integer in your arrays. http://en.wikipedia.org/wiki/Trie . Steps: put all elements of array a into trie, when you put it there for the first time, you set value associated with the key to 1. The next time you are inserting same key, just increment the associated value that is already present. Then iterate through the second array, doing decrement() operation which is the same as add(), only you decrement this time. If you decrement value to 0, remove the element. If you can't find such key in the trie, return false. After successfully iterating second array, check if trie is empty. If it is empty, return true, otherwise false.
Use auxiliary array -> O(n).
This approach applicable only if you know maximum value of the elements in the arrays:
boolean arrayEquality(int[] a, int[] b, int maxValue) {
int[] aux = new int[maxValue];
for (int i = 0; i < a.length; i++) {
int value = a[i];
aux[value]++;
}
for (int i = 0; i < b.length; i++) {
int value = b[i];
aux[value]--;
}
for (int i = 0; i < aux.length; i++) {
if (aux[i] != 0) return false;
}
return true;
}
Or you can use HashSets but you will need to implement your own custom HashSet which will not replace previous key.
This solution is O(n) amortized time, for either solution and supports nested arrays inside array of any/varying levels. e.g. you can compare
[1, [2, [3, 4], [4, 5], [3, 4]]]
Note: [3, 4] is a duplicate.
To use a Hash collection you need to use HashSet is duplicates are ignored and HashMap if duplicates are not ignored. Map is required as you might have duplicates in which case, you need to count them.
public static boolean unorderedEquivalence(Object[] arr1, Object[] arr2) {
return asSet(arr1).equals(asSet(arr2));
}
// if you want to ignore duplicates
private static Set<Object> asSet(Object[] arr1) {
Set<Object> ret = new HashSet<>();
for (Object o : arr1) {
if (o instanceof Object[])
o = asSet((Object[]) o);
ret.add(o);
}
return ret;
}
// if you want to count duplicates.
private static Map<Object, ? extends Number> asSet(Object[] arr1) {
Map<Object, Integer> ret = new HashMap<>();
for (Object o : arr1) {
if (o instanceof Object[])
o = asSet((Object[]) o);
Integer count = ret.get(o);
ret.put(o, count == null ? 1 : count+1);
}
return ret;
}

List of subsets of which their elements add up to n using recursion

I'm writing this function which I want to print all the sublists of a given list with integers. The sum of these integers should be equal to a given number n. There is also a help variable i which starts with value 0. Both the list and each sublist are an ArrayList. So the method looks like this right now:
public static void printSublists(ArrayList numbers, ArrayList sublist, int n,
int i) {
if (sublist.sum() == n) {
System.out.println(sublist.toString());
}
else {
for (int j = 0; j < numbers.size(); j++) {
sublist.add(numbers.get(i));
printSublists(numbers, sublist, n, i + 1);
sublist.remove(numbers.get(i));
}
}
}
Of course I already have the method sum(). The method does this now:
Lets say numbers = [1, 3 , 4] and n == 4, then the method should print [4] and [1 ,3], but it only prints [1, 3] ? I think the for-loop has to do the trick right? I would appreciate it if someone puts me on the right track.
update:
the values I'm giving to the method:
numbers = [1, 3, 4]
n = 4
i = 0
sublist = []
UPDATE 2:
I forgot to say that I want it to be recursive :)
Recursion stops when you see the first sublist with a sum of n. The problem is not (only) the loop but the exit criteria. Your recursive function should stop when the sublist length is 0.
Here I just wrote a working, recursive solution for your problem. It is different but I wasn't able to fix yours. While you start with an empty sublist, I chose to init the recursion with the full list an divide it into smaller sublists. This creates a tree like structure:
[1234]
[123] [234]
[12] [23] [34]
[1][2] [3] [4]
We see immediately, that we have to walk down "right" until we reach the first leaf (1), then we only walk "left". This way we visit all sublists only once.
Here's the idea written in Java:
public static void main (String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(3);
list.add(4);
list.add(0);
printSublists(list, list, 4, true, 0);
}
public static void printSublists(List<Integer> numbers, List<Integer> sublist, int n, boolean walkRight, int level) {
// the test
if (sum(sublist) == n)
System.out.println(sublist);
// the exit criteia (leaf reached)
if (sublist.size() == 1)
return;
// visit the right sublist
if (walkRight)
printSublists(numbers, sublist.subList(0, sublist.size()-1), n, walkRight, level+1);
// we only walk the right path once
walkRight = false;
// visit the left sublist
printSublists(numbers, sublist.subList(1, sublist.size()), n, walkRight, level+1);
}
And that's the output:
[1, 3]
[4]
[4, 0]
#Test
public void test() {
printSublists(new HashSet<Integer>(Arrays.asList(2, 3, 4, 1, 2)), new HashSet<Integer>(), 4);
}
private void printSublists(Set<Integer> numbers, Set<Integer> subList, int expected) {
for (Integer element : numbers) {
subList.add(element);
if (sum(subList) == expected) {
System.out.println("result =" + subList);
}
Set<Integer> listWithoutElement = withoutElement(numbers, element);
printSublists(listWithoutElement, subList, expected);
subList.remove(element);
}
}
private Set<Integer> withoutElement(Set<Integer> numbers, Integer element) {
Set<Integer> newList = new HashSet<Integer>(numbers);
newList.remove(element);
return newList;
}
private int sum(Collection<Integer> sublist) {
int sum = 0;
for (Integer e : sublist) {
sum += e;
}
return sum;
}
Here is your solution. This problem should be for sets, not for list.
If you have set [2,3,4,1,2] the solution should be [3,1] [4] [2,2]. Then the problem has to be recursive. You have to remove duplication of course :)
In your code's for loop:
for (int j = 0; j < numbers.size(); j++) {
sublist.add(numbers.get(i));
printSublists(numbers, sublist, n, i + 1);
sublist.remove(numbers.get(i));
}
The variable j is never used. So you're doing exactly the same thing repeatedly, which I seriously doubt is what you want to do.
probably u will need to do something this way -
public static void printSublists(ArrayList numbers, ArrayList sublist, int n,
int i) {
if (sublist.sum() == n) {
System.out.println(sublist.toString());
sublist.removeAll(sublist);//added remove all here
}
else {
//for (int j = 0; j < numbers.size(); j++) {//commented this line
while(i<number.size()){//need while loop
sublist.add(numbers.get(i));
printSublists(numbers, sublist, n, ++i);//i+1 changed to ++i
//sublist.remove(numbers.get(i));// commented here
}
}
}

Java: Interleaving multiple arrays into a single array

I found similar question about interleaving two arraylists into one, but its in PHP. I was asked this question in interview as well but could'nt solve it, came back to SO to look if it was addressed already, but i could only find this paper
So any pointers to pseudo code or method definition ?
Big(O) restrictions : O(n) - time cost and O(1) - space cost
Example:
a[]= a1, a2, ..., an
b[]= b1, b2, ..., bn
Rearrange the arraylist to a1, b1, a2, b2, ..., an, bn
Editv1.0 : Arraylists a[] and b[] are of same size
Editv2.0 : What if the question is extended to rearrange in one of given two arrays, but not create a new array ?
For simplicity, assume that the arrays are the same length, and are int arrays.
int[] merge(int[] a, int[] b)
{
assert (a.length == b.length);
int[] result = new int[a.length + b.length];
for (int i=0; i<a.length; i++)
{
result[i*2] = a[i];
result[i*2+1] = b[i];
}
return result;
}
I think this is not doable with your given constraints (O(n) time and O(1) space, i.e. no additional space) for an array or array-based list. (Assuming of course, that we can't simply create a new List object delegating to the original ones.)
If you have two linked lists, this is doable - if we assume the garbage collector is fast enough, i.e. deleting an element from one list and adding it to another list does not violate the space limitation.
public <X> void interleaveLists(List<X> first, List<X> second)
{
ListIterator<X> firstIt = first.listIterator();
ListIterator<X> secondIt = second.listIterator();
while(secondIt.hasNext()) {
fistIt.next();
firstIt.add(secondIt.next());
secondIt.remove();
}
}
This method works for any pair of lists, but is only O(n) for linked lists.
For a custom linked list where we can modify the pointers, we don't have to rely on the garbage collector, we would simply change the nodes. Here for a singly-linked list:
public void interleaveLinkedLists(Node<X> firstList, Node<X> secondList) {
while(secondList != null) {
Node<X> nextFirst = firstList.next;
Node<X> nextSecond = secondList.next;
firstList.next = secondList;
secondList.next = nextFirst;
firstList = nextFirst;
secondList = nextSecond;
}
}
For a doubly-linked list, we would also have to adapt the prev-pointers.
Here the wrapping variant mentioned in the first paragraph:
public List<X> interleaveLists(final List<X> first, final List<X> second)
{
if (first.size() != second.size())
throw new IllegalArgumentException();
return new AbstractList<X>() {
public int size() {
return 2 * first.size();
}
public X get(int index) {
return index % 2 == 0 ? first.get(index / 2) : second.get(index / 2);
}
// if necessary, add a similar set() method. add/remove are not sensible here.
};
}
This is actually O(1) in time, too.
I've done up a small solution going on the assumption that you are talking about using the ArrayList (see my comment on the question). I may be oversimplifying the problem based on some of the responses here, but here goes anyway.
The below example takes a and b both of type ArrayList<Integer> and interleaves them by inserting b[0] after a[0], b[1] after a[1] etc. This snippet of course naively assumes that a and b are of the same size as per your Edit v1.0. It also does not create a new ArrayList as per your Edit v2.0.
//a and b are of type ArrayList<Integer>
for (int i = a.size(); i > 0; i--)
{
a.add(i, b.get(i - 1));
}
No matter what happens if you are combining the ArrayLists you're going to have twice the size.
I believe the mod (%) operations in Matt's answer are incorrect. Under the same assumption (that the arrays are the same length), I'd propose the following solution instead:
static int[] merge(final int[] a, final int[] b)
{
final int[] result = new int[a.length * 2];
for (int i=0; i < a.length; i++)
{
result[i << 1] = a[i];
result[(i << 1) + 1] = b[i];
}
return result;
}
I tested (very briefly), and it appears to work, but of course makes no attempt to handle error conditions such as null arguments or input arrays mismatched in size.
in the meantime lambda was introduced
O(n) time complexity
O(1) space complexity
int[] merge(int[] a, int[] b) {
return( IntStream.range( 0, a.length ).flatMap(
n -> IntStream.of( a[n], b[n] ) ).toArray() );
}
The lists don't have to be the same size:
public class InterleaveTwoLists<X> {
public List<X> interleaveLists(final List<X> first, final List<X> second) {
return new AbstractList<X>() {
private int minSize;
private int combinedMinSize;
private int size;
private List<X>largerList;
{{
minSize = Math.min(first.size(), second.size());
combinedMinSize = minSize*2;
size = first.size() + second.size();
largerList = first.size() > minSize ? first : second;
}}
public int size() {
return size;
}
public X get(int index) {
if (index < combinedMinSize) {
return index % 2 == 0
? first.get(index / 2)
: second.get(index / 2);
}
else {
return largerList.get(index-minSize);
}
}
};
}
}
To test this:
public class InterleaveTwoListsTest {
private static final Logger log =
LoggerFactory.getLogger(InterleaveTwoListsTest.class);
List<String> first = new ArrayList<String>() {
{
add("one"); add("three"); add("five");
add("seven"); add("eight"); add("nine");
}};
List<String> second = new ArrayList<String>() {
{
add("two"); add("four"); add("six");
}};
private InterleaveTwoLists<String> interleaveTwoLists;
#Before
public void setUp() throws Exception {
interleaveTwoLists = new InterleaveTwoLists<>();
}
#Test
public void test() {
List<String> combinedList = interleaveTwoLists.interleaveLists(first, second);
for( int i = 0; i < first.size() + second.size(); i++) {
log.debug("{}: {}", i, combinedList.get(i));
}
}
}

Categories