I have an array containing duplicates in the following format:
arr[]={ 2,9,1,5,1,4,9,7,2,1,4 }
I want to sort the array in place such that all the duplicate elements are moved towards the end and sorted in different sub arrays like following:
arr[]={ 1,2,4,5,7,9, 1,2,4,9, 1 }
There is no range for Integers for the array specified.
Following is the code which i tried. This code recursively sorts the sub-arrays and then move duplicates towards the end. But Complexity wise this is not optimal solution.
Please suggest if it can be solve in O(n) or O(nlogn). Entire code is as follows:
public static int sortDuplicates(int a[],int start,int end){
int i, k,temp;
if(start==end)
return 1;
Arrays.sort(a, start, end);
k = start;
for (i = start+1; i < end; i++) {
if (a[k] != a[i] && a[k]<a[i])
{
temp=a[k+1];
a[k+1] = a[i];
a[i]=temp;
k++;
}
}
return sortDuplicates(a,k+1,a.length);
}
I would approach it as follows:
Put all elements and their counts in a hashmap:
int[] arr={ 2,9,1,5,1,4,9,7,2,1,4 };
Map<Integer, Integer> map = new HashMap<>();
for (int elt : arr) {
int val = 1;
if (map.containsKey(elt))
val = map.get(elt) + 1;
map.put(elt, val);
}
Fetch the batches one by one:
List<Integer> result = new ArrayList<>();
while (map.size() > 0) {
List<Integer> subList = new ArrayList<>(); // for the current segment
Iterator<Integer> it = map.keySet().iterator();
while (it.hasNext()) {
int elt = it.next();
subList.add(elt);
if (map.get(elt) == 1) { // remove all elements with occurence = 1
it.remove();
} else { // reduce occurence by 1
map.put(elt, map.get(elt)-1);
}
}
Collections.sort(subList); // sort this segment
result.addAll(subList); // append to result
}
for (int i : result) {
System.out.print(i + " ");
}
This prints
1 2 4 5 7 9 1 2 4 9 1
And if I'm not mistaken, it runs in O(n log n).
I would approach it as follows.
Put all the elements in a balanced binary search tree with their counts.
Now traverse the tree in an inorder way and keep putting the elements(with count > 0) in the array and decreasing their count by 1.
time complexity for creating the tree is O(nlgn) and for putting elements from tree is O(n) and space complexity is O(n).
Problem is if none of elements is being repeated then we are creating the tree in vain.
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed last year.
Improve this question
I'm trying to implement a code to mergeSort a list. I was told to use getFirst() and addAll() but I don't know how so this is what I came up with:
Also, I can't use a void for mergeSort method - because it gives me an error so I have to return something.
Any suggestions will be very appreciate
You were close in terms of getting your approach working. Here's what I came up with. I changed the logic to create new lists to contain the results at each level rather than creating new lists when you break up a list. There's no reason to copy a list just to keep it in the same order. It's the list in the new order that needs to be fresh storage. This limits the number of copies and I think makes the logic easier, since you can then just append onto the end of each result list instead of having to have logic to set the right position in each list.
import java.util.Arrays;
import java.util.LinkedList;
class Test {
public static LinkedList<Integer> mergeSort(LinkedList<Integer> list, int start, int count) {
if (count < 2) {
LinkedList<Integer> result = new LinkedList<>();
result.add(list.get(start));
return result;
}
int size1 = count / 2;
int size2 = count - size1;
LinkedList<Integer> list_1 = mergeSort(list, start, size1);
LinkedList<Integer> list_2 = mergeSort(list, start + size1, size2);
return merge(list_1, list_2);
}
public static LinkedList<Integer> merge(LinkedList<Integer> list_1, LinkedList<Integer> list_2) {
LinkedList<Integer> result = new LinkedList<>();
int i = 0, j = 0;
while (i < list_1.size() && j < list_2.size())
if (list_1.get(i) < list_2.get(j))
result.add(list_1.get(i++));
else
result.add(list_2.get(j++));
while (i < list_1.size())
result.add(list_1.get(i++));
while (j < list_2.size())
result.add(list_2.get(j++));
return result;
}
public static LinkedList<Integer> mergeSort(LinkedList<Integer> list) {
return mergeSort(list, 0, list.size());
}
public static void main(String[] args) {
LinkedList<Integer> myList = new LinkedList<>(Arrays.asList(22, 45, 1234, 77, 4, 1111, 999, 12, 88, 44, 7777, 22, 33, 44, 666));
LinkedList<Integer> sorted = mergeSort(myList);
System.out.println(sorted);
}
}
Result:
[4, 12, 22, 22, 33, 44, 44, 45, 77, 88, 666, 999, 1111, 1234, 7777]
UPDATE: As #rcgldr points out, the above solution doesn't meet the stated guidelines. With this new version of the merge() method, the guidelines are met. Note that this doesn't make the algorithm much more efficient (both versions create lots of lists and are quite INEFFICIENT), but it does save a bunch of walking to random positions in lists in favor of working from the front each list:
public static LinkedList<Integer> merge(LinkedList<Integer> list_1, LinkedList<Integer> list_2) {
LinkedList<Integer> result = new LinkedList<>();
while (list_1.size() > 0 && list_2.size() > 0)
if (list_1.peek() < list_2.peek()) {
result.add(list_1.getFirst());
list_1.remove();
}
else {
result.add(list_2.getFirst());
list_2.remove();
}
result.addAll(list_1);
result.addAll(list_2);
return result;
}
Also note that calling getFirst() and then remove() is redundant. remmove() can be called by itself to both grab the next element and remove it from the list. I went with calling both methods to meet the requirements.
Although there is an accepted answer, this code uses getFirst() and addAll() as mentioned in the original post, in what I assume is the intended purpose of this exercise. It uses a bottom up merge sort, which avoids the top down issue having to scan lists to find mid points to split lists.
The basic concept of a bottom up merge sort is two 1 node lists are merged to create a sorted 2 node list, two sorted 2 node lists are merged to create a sorted 4 node list, two sorted 4 node lists are merged to create a sorted 8 node list, and so on, similar to bottom up merge sort for arrays.
A bottom up merge sort uses a small array of lists (java), pointers to nodes (C), or iterators to nodes (C++) to hold the lists. For this example code, I use an array of 32 lists. For each member of array, the member is either null or refers to a list where the number of nodes for the list for array[i] has 2^i nodes: array[0] 1 node, array[1] 2 nodes, array[2] 4 nodes, ..., array[30] 2^30 = 1 billion nodes. The last member will have 2^31 = 2 billion nodes or more if the total number of nodes is >= 2^32 (4 billion) nodes.
Nodes are removed from the front of the source list one at a time and each node from the source list is used to create a list mm with 1 node, then mm is merged into the array, stopping at the first null encountered:
while(source_list not empty){
mm = source_list.removeNode(() // mm = next node from source list
for(i = 0; i < 32; i++){ // merge mm into array
if(array[i] != null){
mm = merge(array[i], mm)
array[i] = null
} else {
break
}
}
if(i == 32) // if i == 32, there is no array[32], so
i = 31 // use array[31] instead
array[i] = mm
}
The pattern looks like this:
mm = next node from source list // 1 node list
array[0] = mm
mm = next node from source list // 1 node list
mm = merge(array[0], mm) // 2 node list
array[0] = null
array[1] = mm
mm = next node from source list // 1 node list
array[0] = mm
mm = next node from source list // 1 node list
mm = merge(array[0], mm) // 2 node list
array[0] = null
mm = merge(array[1], mm) // 4 node list
array[1] = null
array[2] = mm
Once the last node is merged into the array, all the lists in the array are merged to form a single sorted list:
mm = empty list
for(i = 0; i < 32; i++){
if(all[i] != null){
mm = merge(all[i], mm);
all[i] = null; // (for garbage collection)
}
}
Java's native double linked list class doesn't provide a means to move nodes within a list or between lists, so a node has to be deleted when an element is retrieved from the front of a list, and a node has to be created when an element is appended to the back of a list, an extra overhead. In the case of C++ standard double link list class std::list, std::list::splice() can be used to move nodes within a list or between lists, reducing the overhead, such as dst.splice(dst.end(), src, src.begin()), which moves the beginning node (first node) from src to the ending node (last node) of dst.
public static LinkedList<Integer> merge(LinkedList<Integer> ll,
LinkedList<Integer> rr)
{
if(ll.isEmpty())
return rr;
if(rr.isEmpty())
return ll;
LinkedList<Integer> mm = new LinkedList<>();
while(true){
if(ll.getFirst().compareTo(rr.getFirst()) <= 0){
mm.add(ll.removeFirst());
if(!ll.isEmpty())
continue;
mm.addAll(rr);
break;
} else {
mm.add(rr.removeFirst());
if(!rr.isEmpty())
continue;
mm.addAll(ll);
break;
}
}
return mm;
}
public static LinkedList<Integer> mergeSort(LinkedList<Integer> ll)
{
if(ll == null || ll.size() < 2)
return ll;
int i;
final int ASZ = 32; // create array (of nulls)
LinkedList<Integer>[] all= new LinkedList[ASZ];
// merge nodes into array
LinkedList<Integer> mm;
do{
mm = new LinkedList<>(); // mm = next node
mm.add(ll.removeFirst());
// merge mm into array
for(i = 0; (i < ASZ) && (all[i] != null); i++){
mm = merge(all[i], mm);
all[i] = null;
}
if(i == ASZ) // don't go past end of array
i--;
all[i] = mm;
}while(!ll.isEmpty());
// merge array into single list
mm = new LinkedList<>();
for(i = 0; i < ASZ; i++){
if(all[i] != null){
mm = merge(all[i], mm);
all[i] = null;
}
}
return (mm);
}
test code, takes about 3 to 5 seconds to sort 4 million (2^22) nodes.
public static void main(String[] args) {
final int COUNT = 4*1024*1024;
LinkedList<Integer> ll = new LinkedList<>();
Random r = new Random();
for(int i = 0; i < COUNT; i++)
ll.addLast(r.nextInt());
long bgn, end;
bgn = System.currentTimeMillis();
ll = mergeSort(ll);
end = System.currentTimeMillis();
System.out.println("milliseconds " + (end-bgn));
// check sort
int i;
i = ll.removeFirst();
int j = i;
while(!ll.isEmpty()){
j = ll.removeFirst();
if(i > j)
break;
i = j;
}
if(i == j)
System.out.println("passed");
else
System.out.println("failed");
}
In the merge code, I'm getting faster and more consistent run times using a loop instead of addAll, from 3 to 5 seconds down to 2.8 to 3.2 seconds, but the assignment states to use addAll.
// mm.addAll(rr); // replace with loop is faster
do
mm.add(rr.removeFirst());
while(!rr.isEmpty());
I have a list with values [1,2,2,8,7,8]. I would like to find the indexes of the biggest number. Here the biggest number is 8. 8 is repeated twice. So the answer should be [3,5] i.e. index of both the 8's.
I spent a lot of time. I am able to find the biggest number. I am not able to find a clean and easy solution to find the indexes.
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<Integer> parts = new ArrayList<>();
parts.add(1);
parts.add(2);
parts.add(2);
parts.add(8);
parts.add(7);
parts.add(8);
Test ob = new Test();
System.out.println("Indexs with max value:" + ob.getIndex(parts));
}
public List<Integer> getIndex(List<Integer> parts) {
int big = parts.get(0);
List<Integer> indexes = new ArrayList<>();
for (int i = 1; i < parts.size(); i++) {
if (big <= parts.get(i)) {
big = parts.get(i);
indexes.add(i);
}
}
System.out.println("Biggest Number:" + big);
return indexes;
}
}
The above code prints the biggest number and prints wrong indexes i.e. I am adding 'i' value to the index whenever it goes inside the if loop. I need to filter so that it adds only if the value is big. I am looking for a solution without two for loops. Any help is appreciated.
You were almost there, you just forgot to clean your list when you had found a bigger number:
for (int i = 0; i < parts.size(); i++) {
if (big <= parts.get(i)) {
if (big < parts.get(i)) {
indexes.clear();
}
big = parts.get(i);
indexes.add(i);
}
}
A more elegant, but also a little bit slower solution would be to use Streams:
public List<Integer> getIndex(List<Integer> parts)
if (!parts.isEmpty()) {
int max = parts.stream().max(Integer::compare).get();
return IntStream.range(0, parts.size())
.filter(i -> parts.get(i) == max)
.boxed()
.collect(Collectors.toList());
}
return Collections.emptyList();
}
You’re almost there; you just need to clear the list when you come across a new “biggest” number. You can do this by adding to your if statement like so :
if (big <= parts.get(i)) {
// new biggest number, so previous indexes no longer apply
if (big < parts.get(i)) {
indexes.clear();
}
big = parts.get(i);
indexes.add(i);
}
Recreate the indexes list when you encounter a new "biggest" number, adding that index to the new list (or clear the list and add the new index). If the number is equal to the current "biggest" number, add the index to the list.
public List<Integer> getIndex(List<Integer> parts) {
int big = parts.get(0);
List<Integer> indexes = new ArrayList<>();
for (int i = 1; i < parts.size(); i++) {
if (big < parts.get(i)) {
// new biggest, create new list starting with this index
big = parts.get(i);
indexes = new ArrayList<>();
indexes.add(i);
} else if (big == parts.get(i)) {
// new instance, add index
indexes.add(i);
} // else do nothing, not bigger or instance of current biggest
}
System.out.println("Biggest Number:" + big);
return indexes;
}
Result:
Biggest Number:8
Indices with max value:[3, 5]
you need to clear you indexes list every time you find value grather than big.
you need to add index to indexes if big and parts.get(i) == big.
you need to check if parts is empty or not.
you need to add index 0 to indexes before loop because parts can be with just one element.
this the code :
public List<Integer> getIndex(List<Integer> parts) {
List<Integer> indexes = new ArrayList<>();
if (!parts.isEmpty()) {
int big = parts.get(0);
indexes.add(0);
for (int i = 1; i < parts.size(); i++) {
if (big < parts.get(i)) {
big = parts.get(i);
indexes.clear();
indexes.add(i);
} else if (big == parts.get(i)) {
indexes.add(i);
}
}
}
return indexes;
}
output ,in your example :
Indexs with max value:[3, 5]
It could be resolved using a sorted map with a key - element of the input list, and value - the list of indexes.
TreeMap<Integer, List<Integer>> valueIndexes = new TreeMap<>();
The list of indexes could be created/populated in different ways:
using
Map::computeIfAbsent:
List<Integer> list = Arrays.asList(1, 2, 2, 8, 7, 8);
for (int i = 0; i < list.size(); i++) {
List<Integer> indexes = valueIndexes.computeIfAbsent(list.get(i), (x) -> new ArrayList<>());
indexes.add(i);
}
System.out.println("valueIndexes: " + valueIndexes.lastEntry().getValue()); // lastEntry may be null for empty input list
Map::merge is less convenient because many intermediate lists are created:
for (int i = 0; i < list.size(); i++) {
valueIndexes.merge(
list.get(i), // key: element of input
new ArrayList<>(Arrays.asList(i)), // store index into extendable list
(l1, l2) -> { l1.addAll(l2); return l1; } // merge function
);
}
System.out.println("merge valueIndexes: " + valueIndexes.lastEntry().getValue());
Similarly, the maximum element could be selected from an unsorted map created using Collectors.groupingBy + Collectors.mapping followed by selecting the max key and related list of indexes:
List<Integer> maxIndexes = IntStream.range(0, list.size())
.boxed()
.collect(Collectors.groupingBy(i -> list.get(i),
Collectors.mapping(i -> i, Collectors.toList()) // build list of indexes
))
.entrySet()
.stream()
.collect(Collectors.maxBy(Map.Entry.comparingByKey()))
.orElse(Map.entry(-1, Collections.emptyList())).getValue();
System.out.println(maxIndexes);
These approaches could be joined to get the last entry from the sorted map retrieved by collector:
List<Integer> maxIndexes2 = IntStream.range(0, list.size())
.boxed()
.collect(Collectors.groupingBy(i -> list.get(i), TreeMap::new,
Collectors.mapping(i -> i, Collectors.toList())
))
.lastEntry().getValue();
System.out.println(maxIndexes2);
I want to split a list into a given number n sublists in all possible ways in Java.
For example [1, 2, 3, 4] where n = 3 would include the following lists (but would not be a complete solution - complete would require much more space):
([], [], [1,2,3,4])
([],[1],[2,3,4])
([],[1,2],[3,4])
([],[1,2,3],[4])
([],[1,2,3,4],[])
([1],[2,3,4], [])
([1],[2,3],[4])
([2,3],[4],[1])
([4],[],[1,2,3])
...
etc
I adapted a solution from another similar question (Split a list into two sublists in all possible ways) however it only works for creating lists of 2 sublists and I am struggling to grasp how to implement it for a flexible rather than hardcoded number of sublists.
Here is my code:
public List<List<EGroup>> permutation(List<E> list) {
List<List<E>> sublists = new ArrayList<List<E>>();
for (int i = 0; i <= list.size(); i++) {
permutationSplit(list, sublists, i, new ArrayList<E>(), 0);
}
List<List<EGroup>> listOfEGroupPairs = new ArrayList<List<EGroup>>();
for (List<E> subList : sublists) {
List<E> listCopy = new ArrayList<E>(list);
listCopy.removeAll(subList);
EGroup e1 = new EGroup(subList);
EGroup e2 = new EGroup(listCopy);
List<EGroup> egr = new ArrayList<EGroup>();
egr.add(e1);
egr.add(e2);
listOfEGroupPairs.add(egr);
}
return listOfEGroupPairs;
}
public void permutationSplit(List<E> list, List<List<E>> subLists, int sublistSize, List<E> currentSubList,
int startIndex) {
if (sublistSize == 0) {
subLists.add(currentSubList);
} else {
sublistSize--;
for (int i = startIndex; i < list.size(); i++) {
List<E> newSubList = new ArrayList<E>(currentSubList);
newSubList.add(list.get(i));
permutationSplit(list, subLists, sublistSize, newSubList, i + 1);
}
}
}
I need to create n number of EGroup objects to add to listOfEGroupPairs rather than the hardcoded 2, but how to always get the right number (n) of sublists of varied size each loop?
You have K elements and each might fall into any of N lists.
So there are N^K variants and we can just map integer values from 0 to N^K-1 to distributions like N-ary numeral system.
Another approach - recursively insert every element into N lists.
I can demonstrate approaches with Python code recursive, N-ary and hope it might be translated to Java
def recdistr(K, N, level, ls):
if level == K:
print(ls)
else:
for i in range(N):
ls[i].append(level)
recdistr(K, N, level + 1, ls) #recursive call with changed list
ls[i].pop() #remove last added element to revert list to previous state
K = 4
N = 3
lst = [[] for _ in range(N)]
recdistr(K, N, 0, lst)
def mapdistr(K, N):
for x in range(N**K):
t = x
l = [[] for _ in range(N)]
for i in range(K):
id = t % N
t = t // N #integer division
l[id].append(i)
print(l)
mapdistr(K, N)
If I understand the question correctly, each element of the original list can end up in any of the n sublists. That means, there are n^s possible sublists (s being the number of elements in the original list), which can be enumerated in a simple loop. With a bit of modulo and integer division you can then get the proper "bucket" for each element and prepare the results accordingly.
public <T> List<List<List<T>>> partition(List<T> lst, int n) {
var result = new ArrayList<List<List<T>>>();
// k = SUM ( pos of lst[i] * n^i )
for (int k = 0; k < Math.pow(n, lst.size()); k++) {
// initialize result
List<List<T>> res = IntStream.range(0, n)
.mapToObj(i -> new ArrayList<T>())
.collect(Collectors.toList());
// distribute elements to sub-lists
int k2 = k;
for (int i = 0; i < lst.size(); i++) {
res.get(k2 % n).add(lst.get(i));
k2 /= n;
}
result.add(res);
}
return result;
}
Use recursion.
For n equal to 0 or negative the task is impossible. Either throw an exception or return an empty lists of lists of sublists. Corner case: if n is 0 and the list is empty, you may argue that the empty list of sublists is a valid response.
If n is 1, the only solution is trivially the entire list.
For n > 1:
If the length of the list is 4 (for example [1, 2, 3, 4]), there are 5 possible first lists. In general there are list.length + 1 possible first sublists. Find them. For each such sublist make a recursive call passing the remainder of the list and n - 1 as arguments to find all possible combinations of sublists made from the remainder of the list. Combine each first sublist with each combination of remaining sublists to form a full solution.
PS The solution as sketched will only produce sublists in the order they come in the original list. So the solution will include ([],[1],[2,3,4]) and ([1],[2,3,4], []), but not ([4],[],[1,2,3]). To regard the last one as a separate solution, you will additionally need to find all permutations of each solution, in turn taking into account that some sublists may be equal and hence swapping them won’t make a distinct solution.
I was wondering what could be a better solution that could produce less complexity than O(n^2) when printing unique items from two arrays. Any ideas?
int[] a = {1,2,4,5,8};
int[] b = {3,2,5,7,8};
ArrayList unMatch = new ArrayList() ;
for(int i=0; i<a.length; i++){
boolean contains = false;
innerloop:
for(int k =0; k<b.length; k++){
if(a[i]==b[k]){
contains = true;
break innerloop;
}
}
if(!contains){
unMatch.add(a[i]);
}
}
for(int i=0; i<b.length; i++){
boolean contains = false;
innerloop:
for(int k =0; k<a.length; k++){
if(b[i]==a[k]){
contains = true;
break innerloop;
}
}
if(!contains){
unMatch.add(b[i]);
}
}
Output: [1,4,3,7]
I think this sort of solution will be better, if you can use other data structures:
First we will fill up a HashMap<Integer, Integer> with the items and their frequencies:
public static Set<Entry<Integer, Integer>> fillMap(int[] a, int[] b) {
HashMap<Integer, Integer> entries = new HashMap<>();
for (Integer i : a)
entries.put(i, entries.get(i) == null ? 1 : entries.get(i) + 1);
for (Integer i : b)
entries.put(i, entries.get(i) == null ? 1 : entries.get(i) + 1);
return entries.entrySet();
}
And then print the unique items (the ones with value = 1):
for (Entry<Integer, Integer> entry: fillMap(a, b))
if (entry.getValue() == 1)
System.out.println("This value is unique: " + entry.getKey() );
If I'm not mistaken this should run in O(n+m) (or just O(n) if the arrays are the same length always).
convert array to array list
List<Integer> c = Array.asList(a);
List<Integer> d = Array.asList<b>;
c.removeAll(d);
c.addAll(d);
c.froEach(System.out::println);
I did this in java using lambdas it is only O(n)
Hope this code answers your question
Use of Set can reduce your complexity. Sets don't allow duplicates. Sets can be:
HashSet - HashSet is implemented using a hash table. Elements are not ordered. The add, remove, and contains methods have constant time complexity O(1).
LinkedHashSet - uses Trees (RB-Trees). Elements are not ordered. Complexity for the same methods is O(log n)
TreeSet - uses a hash table with a linked list running through it. Elements are ordered. The time complexity of the same methods is O(1).
E.g.
HashSet<Integer> set = new HashSet<>();
for(int n : a) {
set.add(n);
}
for (int n : b) {
set.add(n);
}
So, it provides a linear order here - O(n+m).
I was asked an algorithmic question today in an interview and i would love to get SO members' input on the same. The question was as follows;
Given equally sized N arrays with integers in ascending order, how would you select the numbers common to all N arrays.
At first my thought was to iterate over elements starting from the first array trickling down to the rest of the arrays. But then that would result in N power N iterations if i am right. So then i came up with a solution to add the count to a map by keeping the element as the key and the value as the counter. This way i believe the time complexity is just N. Following is the implementation in Java of my approach
public static void main(String[] args) {
int[] arr1 = { 1, 4, 6, 8,11,15 };
int[] arr2 = { 3, 4, 6, 9, 10,16 };
int[] arr3 = { 1, 4, 6, 13,15,16 };
System.out.println(commonNumbers(arr1, arr2, arr3));
}
public static List<Integer> commonNumbers(int[] arr1, int[] arr2, int[] arr3) {
Map<Integer, Integer>countMap = new HashMap<Integer, Integer>();
for(int element:arr1)
{
countMap.put(element, 1);
}
for(int element:arr2)
{
if(countMap.containsKey(element))
{
countMap.put(element,countMap.get(element)+1);
}
}
for(int element:arr3)
{
if(countMap.containsKey(element))
{
countMap.put(element,countMap.get(element)+1);
}
}
List<Integer>toReturn = new LinkedList<Integer>();
for(int key:countMap.keySet())
{
int count = countMap.get(key);
if(count==3)toReturn.add(key);
}
return toReturn;
}
I just did this for three arrays to see how it will work. Question talks about N Arrays though i think this would still hold.
My question is, is there a better approach to solve this problem with time complexity in mind?
Treat as 3 queues. While values are different, "remove" (by incrementing the array index) the smallest. When they match, "remove" (and record) the matches.
int i1 = 0;
int i2 = 0;
int i3 = 0;
while (i1 < array1.size && i2 < array2.size && i3 < array3.size) {
int next1 = array1[i1];
int next2 = array2[i2];
int next3 = array3[i3];
if (next1 == next2 && next1 == next3) {
recordMatch(next1);
i1++;
i2++;
i3++;
}
else if (next1 < next2 && next1 < next3) {
i1++;
}
else if (next2 < next1 && next2 < next3) {
i2++;
}
else {
i3++;
}
}
Easily generalized to N arrays, though with N large you'd want to optimize the compares somehow (NPE's "heap").
I think this can be solved with a single parallel iteration over the N arrays, and an N-element min-heap. In the heap you would keep the current element from each of the N input arrays.
The idea is that at each step you'd advance along the array whose element is at the top of the heap (i.e. is the smallest).
You'll need to be able to detect when the heap consists entirely of identical values. This can be done in constant time as long as you keep track of the largest element you've added to the heap.
If each array contains M elements, the worst-case time complexity of the would be O(M*N*log(N)) and it would require O(N) memory.
try
public static Set<Integer> commonNumbers(int[] arr1, int[] arr2, int[] arr3) {
Set<Integer> s1 = createSet(arr1);
Set<Integer> s2 = createSet(arr2);
Set<Integer> s3 = createSet(arr3);
s1.retainAll(s2);
s1.retainAll(s3);
return s1;
}
private static Set<Integer> createSet(int[] arr) {
Set<Integer> s = new HashSet<Integer>();
for (int e : arr) {
s.add(e);
}
return s;
}
This is how I learned to do it in an algorithms class. Not sure if it's "better", but it uses less memory and less overhead because it iterates straight through the arrays instead of building a map first.
public static List<Integer> commonNumbers(int[] arr1, int[] arr2, int[] arr3, ... , int[] arrN) {
List<Integer>toReturn = new LinkedList<Integer>();
int len = arr1.length;
int j = 0, k = 0, ... , counterN = 0;
for (int i = 0; i < len; i++) {
while (arr2[j] < arr1[i] && j < len) j++;
while (arr3[k] < arr1[i] && k < len) k++;
...
while (arrN[counterN] < arr1[i] && counterN < len) counterN++;
if (arr1[i] == arr2[j] && arr2[j] == arr3[k] && ... && arr1[i] == arrN[counterN]) {
toReturn.add(arr1[i]);
}
}
return toReturn;
}
This may be solved in O(M * N) with M being the length of arrays.
Let's see what happens for N = 2, this would be a sorted-list intersection problem, which has a classic merge-like solution running in O(l1 + l2) time. (l1 = length of first array, l2 = length of second array). (Find out more about Merge Algorithms.)
Now, let's re-iterate the algorithm N times in an inductive matter. (e.g. i-th time we will have the i-th array, and the intersection result of previous step). This would result in an overall O(M * N) algorithm.
You may also observe that this worst case upper-bound is the best achievable, since all the numbers must be taken into account for any valid algorithm. So, no deterministic algorithm with a tighter upper-bound may be founded.
Okay - maybe a bit naive here, but I think the clue is that the arrays are in ascending order. My java is rusty, but here is some pseduocode. I haven't tested it, so it's probably not perfect, but it should be a fast way to do this:
I = 1
J = 1
K = 1
While I <= Array1Count and J <= Array2Count and K <= Array3Count
If Array1(I) = Array2(J)
If Array1(I) = Array3(K)
=== Found Match
I++
J++
K++
else
if Array1(I) < Array3(K)
I++
end if
end if
else
If Array1(I) < Array2(J)
I++
else
if Array2(J) < Array3(K)
J++
else
K++
end if
end if
end if
Wend
This is Option Base 1 - you'd have to recode to do option base 0 (like java and other languages have)
I think another approach is to do similar thing to what we do in Mergesort: walk through all the arrays at the same time, getting identical numbers. This would take advantage of the fact that the arrays are in sorted order, and would use no additional space other than the output array. If you just need to print the common numbers, no extra space is used.
public static List<Integer> commonNumbers(int[] arrA, int[] arrB, int[] arrC) {
int[] idx = {0, 0, 0};
while (idxA<arrA.length && idxB<arrB.length && idxC<arrC.length) {
if ( arrA[idx[0]]==arrB[idx[1]] && arrB[idx[1]]==arrC[idx[2]] ) {
// Same number
System.out.print("Common number %d\n", arrA[idx[0]]);
for (int i=0;i<3;i++)
idx[i]++;
} else {
// Increase the index of the lowest number
int idxLowest = 0; int nLowest = arrA[idx[0]];
if (arrB[idx[1]] < nLowest) {
idxLowest = 1;
nLowest = arrB[idx[1]];
}
if (arrC[idx[2]] < nLowest) {
idxLowest = 2;
}
idx[idxLowest]++;
}
}
}
To make this more general you may want to take an arrays of arrays of ints, this will let you make the code more pretty. The array indeces must be stored in an array, otherwise it is hard to code the "increment the index that points to the lowest number" code.
public static List<Integer> getCommon(List<List<Integer>> list){
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
int c=0;
for (List<Integer> is : list) {
c++;
for (int i : is) {
if(map.containsKey(i)){
map.put(i, map.get(i)+1);
}else{
map.put(i, 1);
}
}
}
List<Integer>toReturn = new LinkedList<Integer>();
for(int key:map.keySet())
{
int count = map.get(key);
if(count==c)toReturn.add(key);
}
return toReturn;
}
Your solution is acceptable, but it uses NxM space. You can do it with O(N) space (where N is the number of arrays), or in O(1) space.
Solution #1 (By Luigi Mendoza)
Assuming there are many small arrays (M << N), this can be useful, resulting in O(M*N*Log M) time, and constant space (excluding the output list).
Solution #2
Scan the arrays in ascending order, maintaining a min-heap of size N, containing the latest visited values (and indices) of the arrays. Whenever the heap contains N copies of the same value, add the value to the output collection. Otherwise, remove the min value and advance with the corresponding list.
The time complexity of this solution is O(M*N*Log N)