ArrayList or several for loops? - java

I know this might be simple, I have a situation where I need to decide between using four for-loops to (two to count and remove null elements, two to add elements) merge two String arrays or use two for-loops with an ArrayList and convert the ArrayList to array using ArrayList.toArray().
Performance wise are there any differences between these two approaches?
EDIT
I had to drop the ArrayList with generics approach because of compatibility issues. But here is the earlier code.
List<String> newList = new ArrayList<String>();
for (String element : array1)
{
if (element != null)
{
newList.add(element);
}
}
for (String element : array2)
{
if (element != null)
{
newList.add(element);
}
}
return newList.toArray(new String[]{});
I wrote a new code with one loop, but I think I might be mentally killing the next one reading this code.
String[] newArr = new String[array1.length + array2.length];
int n = 0;
for (int i = 0; i < newArr.length; i++)
{
if (i < array1.length && array1[i] != null)
{
newArr[n] = array1[i];
n++;
}
if (i >= array1.length)
{
int a = 0;
if (array1.length < array2.length)
{
a = (i - array1.length) + (array2.length - array1.length);
}
else
{
a = i - array1.length;
}
if (array2[a] != null)
{
newArr[n] = array2[a];
n++;
}
}
}
return newArr;
And finally got to know that null element check won't be needed so went ahead with this simple code.
String[] newArr = new String[array1.length + array2.length];
System.arraycopy(array1, 0, newArr, 0, array1.length);
System.arraycopy(array2, 0, newArr, array1.length, array2.length);
return newArr;
I guess from the discussion below the second method is the better performing one.

Assuming 1 for loop has O(n) time complexity, 4 for loops and 2 for loops would have the same time complexity
4*O(n) = O(n)
2*O(n) = O(n)
But using arrays instead of ArrayLists would take less memory. So go with the first alternative.

Try to avoid looping as much as you can because every time u loop with n elements the time and space complexity increases by n.
Please paste your code.

as with second option 2 iterations over collection will be saved, this will definitely improve performance if count of objects in list is high.

Related

Why is my if loop is getting skipped while my condition is true?

I am coding a small program where I add an an ArrayList of Strings and I have a method that removes every String ending with S, it is working fine for 4 of elements but it seems to skip one.
Minimum reproducible example:
import java.util.ArrayList;
public class sList {
public static void main(String [] args) {
ArrayList<String> sList=new ArrayList<String>();
sList.add("leaf");
sList.add("leaves");
sList.add("box");
sList.add("boxes");
sList.add("phones");
sList.add("phone");
method m=new methods();
System.out.println(m.removePlurals(sList));
}
}
\\ that is my main method
import java.util.ArrayList;
public class method {
ArrayList<String> removePlurals(ArrayList<String> s) {
for (int i = 0; i < s.size(); i++) {
char c = s.get(i).charAt(s.get(i).length() - 1);
if (c == 's') {
s.remove(i);
}
}
return s;
}
}
I am getting as an output: [leaf, box, phones, phone] so it is skipping "phones"
Any help?
Think about what happens when after you removed "boxes" from the list. The index of "phones" decreases by 1. If it is originally the nth item in the list, it is now the (n-1)th item in the list, isn't it? So now "phones" is at the same position in the list as where "boxes" originally was. On the other hand, i increases by 1.
Now you should see the problem, to check for "phones", i should remain the same because after removing "boxes", the index of "phones" decreased by 1.
Solution, just loop from the end of the list to the start, and you won't have this problem:
for (int i = s.size() - 1; i >= 0; i--) {
char c = s.get(i).charAt(s.get(i).length() - 1);
if (c == 's') {
s.remove(i);
}
}
It is because at the moment you remove an item of a list, the list size is less than at the begining and when your counter is incremented, points to the next one element (skips one).
Yous can solve that just looping the list on the other way, as follow:
ArrayList<String> removePlurals(ArrayList<String> s) {
for (int i = s.size()-1; i >= 0; i--) {
char c = s.get(i).charAt(s.get(i).length() - 1);
if (c == 's') {
s.remove(i);
}
}
return s;
}
}
This is happening because the list you are iterating is being updated in the same loop. So when you remove one element is previous iteration it's index is being updated and element is not picked up at all by loop. So the solution should be like:
1) Create new list and return that:
ArrayList<String> removePlurals(final ArrayList<String> s) {
final ArrayList<String> updatedList = new ArrayList<String>();
for (int i = 0; i < s.size(); i++) {
final char c = s.get(i).charAt(s.get(i).length() - 1);
if (c != 's') {
updatedList.add(s.get(i));
}
}
return updatedList;
}
2) Using Iterator:
ArrayList<String> removePlurals(final ArrayList<String> s) {
final Iterator<String> itr = s.iterator();
while (itr.hasNext()) {
final String x = itr.next();
if (x.charAt(x.length() - 1) == 's') {
itr.remove();
}
}
return s;
}
Aside from the issue reported in the question: you have the issue that removing from the middle of an ArrayList one element at a time is really inefficient, because you keep on shifting all of the elements between (i+1) and the end of the list along by one position - and then you do it again for most of them.
Performance should not be your first concern - slow, correct code is always better than fast, incorrect code - but you should keep in the back of your mind issues that some things are worth avoiding: in this case, it is repeated removal from the middle of an ArrayList.
A better solution is not to keep shifting the elements, but rather just to move them once.
Unlike the solutions which iterate the list backwards, this can be done iterating forwards, which feels more natural (to me, at least).
int target = 0;
for (int i = 0; i < s.size(); ++i) {
if (!s.get(i).endsWith("s")) {
s.set(target, s.get(i));
target++;
}
}
This shifts each element that you are going to keep to its new position. All that then remains is to chop off the end of the list:
while (s.size() > target) s.remove(s.size()-1);
Or, in a single operation:
s.subList(target, s.size()).clear();
All together:
int target = 0;
for (int i = 0; i < s.size(); ++i) {
if (!s.get(i).endsWith("s")) {
s.set(target, s.get(i));
target++;
}
}
s.subList(target, s.size()).clear();
Note that this is effectively what is done by ArrayList.removeIf:
s.removeIf(e -> e.endsWith("s"));
(removeIf does a little bit more, in that it does the removal in a failure-atomic way, that is, if the e.endsWith fails for some reason, such as a null element, the list is left untouched rather than partly updated).

Java program to find the duplicate values of an array of integer using simple loop

public class ArrayTest{
public static void main(String[] args) {
int array[] = {32,3,3,4,5,6,88,98,9,9,9,9,9,9,1,2,3,4,5,6,4,3,7,7,8,8,88,88};
for(int i= 0;i<array.length-1;i++){
for(int j=i+1;j<array.length;j++){
if((array[i])==(array[j]) && (i != j)){
System.out.println("element occuring twice are:" + array[j]);
}
}
}
}
}
this program work fine but when i compile it, it print the values again and again i want to print the duplicate value once for example if 9 is present 5 times in array so it print 9 once and if 5 is present 6 times or more it simply print 5...and so on....this what i want to be done. but this program not behave like that so what am i missing here.
your help would be highly appreciated.
regards!
Sort the array so you can get all the like values together.
public class ArrayTest{
public static void main(String[] args) {
int array[] = {32,3,3,4,5,6,88,98,9,9,9,9,9,9,1,2,3,4,5,6,4,3,7,7,8,8,88,88};
Arrays.sort(array);
for (int a = 0; a < array.length-1; a++) {
boolean duplicate = false;
while (array[a+1] == array[a]) {
a++;
duplicate = true;
}
if (duplicate) System.out.println("Duplicate is " + array[a]);
}
}
}
The problem statement is not clear, but lets assume you can't sort (otherwise the problem greatly simplifies). Lets also assume the space complexity is constrained, and you can't keep a Map, etc, for counting the frequency.
You can use use lookbehind, but this unnecessarily increases the time complexity.
I think a reasonable approach is to reserve the value -1 to indicate that an array position has been processed. As you process the array, you update each active value with -1. For example, if the first element is 32, then you scan the array for any value 32, and replace with -1. The time complexity does not exceed O(n^2).
This leaves the awkcase case where -1 is an actual value. It would be required to do a O(n) scan for -1 prior to the main code.
If the array must be preserved, then clone it prior to processing. The O(n^2) loop is:
for (int i = 0; i < array.length - 1; i++) {
boolean multiple = false;
for (int j = i + 1; j < array.length && array[i] != -1; j++) {
if (array[i] == array[j]) {
multiple = true;
array[j] = -1;
}
}
if (multiple)
System.out.println("element occuring multiple times is:" + array[i]);
}
What you can do, is use a data structure that only contains unique values, Set. In this case we use a HashSet to store all the duplicates. Then you check if the Set contains your value at index i, if it does not then we loop through the array to try and find a duplicate. If the Set contains that number already, we know it's been found before and we skip the second for loop.
int array[] = {32,3,3,4,5,6,88,98,9,9,9,9,9,9,1,2,3,4,5,6,4,3,7,7,8,8,88,88};
HashSet<Integer> duplicates = new HashSet<>();
for(int i= 0;i<array.length-1;i++)
{
if(!duplicates.contains(array[i]))
for(int j=i+1;j<array.length;j++)
{
if((array[i])==(array[j]) && (i != j)){
duplicates.add(array[i]);
break;
}
}
}
System.out.println(duplicates.toString());
Outputs
[3, 4, 5, 6, 7, 88, 8, 9]
I recommend using a Map to determine whether a value has been duplicated.
Values that have occurred more than once would be considered as duplicates.
P.S. For duplicates, using a set abstract data type would be ideal (HashSet would be the implementation of the ADT), since lookup times are O(1) since it uses a hashing algorithm to map values to array indexes. I am using a map here, since we already have a solution using a set. In essence, apart from the data structure used, the logic is almost identical.
For more information on the map data structure, click here.
Instead of writing nested loops, you can just write two for loops, resulting in a solution with linear time complexity.
public void printDuplicates(int[] array) {
Map<Integer, Integer> numberMap = new HashMap<>();
// Loop through array and mark occurring items
for (int i : array) {
// If key exists, it is a duplicate
if (numberMap.containsKey(i)) {
numberMap.put(i, numberMap.get(i) + 1);
} else {
numberMap.put(i, 1);
}
}
for (Integer key : numberMap.keySet()) {
// anything with more than one occurrence is a duplicate
if (numberMap.get(key) > 1) {
System.out.println(key + " is a reoccurring number that occurs " + numberMap.get(key) + " times");
}
}
}
Assuming that the code is added to ArrayTest class, you could all it like this.
public class ArrayTest {
public static void main(String[] args) {
int array[] = {32,3,3,4,5,6,88,98,9,9,9,9,9,9,1,2,3,4,5,6,4,3,7,7,8,8,88,88};
ArrayTest test = new ArrayTest();
test.printDuplicates(array);
}
}
If you want to change the code above to look for numbers that reoccur exactly twice (not more than once), you can change the following code
if (numberMap.get(key) > 1) to if (numberMap.get(key) == 2)
Note: this solution takes O(n) memory, so if memory is an issue, Ian's solution above would be the right approach (using a nested loop).
// print duplicates
StringBuilder sb = new StringBuilder();
int[] arr = {1, 2, 3, 4, 5, 6, 7, 2, 3, 4};
int l = arr.length;
for (int i = 0; i < l; i++)
{
for (int j = i + 1; j < l; j++)
{
if (arr[i] == arr[j])
{
sb.append(arr[i] + " ");
}
}
}
System.out.println(sb);
Sort the array. Look at the one ahead to see if it is duplicate. Also look at one behind to see if this was already counted as duplicate (except when i == 0, do not look back).
import java.util.Arrays;
public class ArrayTest{
public static void main(String[] args) {
int array[] = {32,32,3,3,4,5,6,88,98,9,9,9,9,9,9,1,2,3,4,5,6,4,3,7,7,8,8,88,88};
Arrays.sort(array);
for(int i= 0;i<array.length-1;i++){
if((array[i])==(array[i+1]) && (i == 0 || (array[i]) != (array[i-1]))){
System.out.println("element occuring twice are:" + array[i]);
}
}
}
}
prints:
element occuring twice are:3
element occuring twice are:4
element occuring twice are:5
element occuring twice are:6
element occuring twice are:7
element occuring twice are:8
element occuring twice are:9
element occuring twice are:32
element occuring twice are:88

Can't find solution to codingbat array challenge

I have been working on codingbat problems for java and have come across a problem in array-2 which I cannot solve using only one loop. The problem is as follows :
Given a non-empty array of ints, return a new array containing the elements from the original array that come before the first 4 in the original array. The original array will contain at least one 4. Note that it is valid in java to create an array of length 0.
I looked at other solutions but they all use two loops, and the Array-2 problem set should be done using only one loop. I am not sure how to approach this, here is my solution with two loops :
public int[] pre4(int[] nums) {
int[] notnums = new int[0];
for(int i = 0; i<nums.length;i++)
if(nums[i]==4){
notnums = new int[i];
for(int j = 0;j<i;j++)
notnums[j] = nums[j];
return notnums;
}
return notnums;
}
As it's a coding challenge, I won't answer with code, instead, here's how you should approach it:
Declare another array
Start iterating first array, element by element
If the element is 4, break out of loop
If the element is not 4, add it into another array
In the end, second array should have all the elements that are present before first 4.
my solution for 1 loop:
public int[] pre4(int[] nums) {
int[] res = new int[0];
for(int i = nums.length - 1; i >= 0; i--){
if (nums[i] == 4)
res = new int[i];
else
if (res.length > 0)
res[i] = nums[i];
}
return res;
}

For N equally sized arrays with integers in ascending order, how can I select the numbers common to arrays?

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)

Java: Finding part of a string in a String Array and making a new array

I have a list in a array, each entry either contains o: or g: in front of a word, like o:word or g:word. I want 2 functions, one to make an array with just the g:'s and one to make an array with just the o:'s
I also want the g:'s and o:'s to be pruned out of the result. I'm not sure how to do this without causing array out of bounds errors and such, so I was wondering if someone could give me an example.
Smells like homework.
String[] array = ...
ArrayList<String> gList = new ArrayList<String>();
ArrayList<String> oList = new ArrayList<String>();
for (String word : array) {
if (word != null)) {
if (word.startsWith("g:"))
gList.add(word.subString(2));
else if (word.startsWith("o:")
oList.add(word.subString(2));
}
}
One way is to use ArrayLists to create the new lists. If you need to have primitive arrays after you've got the two new lists, just use toArray() on them.
You would be better off working with ArrayLists (than arrays), because you don't know in advance what size the result will be. But you can make two passes, one to count the rows with the prefix of interest, and a second to populate the new array.
int n = 0;
for (int i = 0; i < array.size; i++)
if (array[i].startsWith(prefix))
n++;
String[] result = new String[n];
int j = 0;
for (int i = 0; i < array.size; i++)
if (array[i].startsWith(prefix))
result[j++] = array[i].substring(prefix.length());
Untried, but I think that ought to do the job.
Assuming that you can mess up the original array, and that you don't need to preserve the order, this should be quite fast:
String[] array = {"o:1","o:2","g:10","o:3","g:20","o:4","g:30","g:40","o:5"};
int low = 0;
int high = array.length - 1;
while(low <= high) {
if(array[low].startsWith("g:")) {
array[low] = array[low].substring(2);
low ++;
} else if (array[high].startsWith("o:")) {
array[high] = array[high].substring(2);
high --;
} else {
String temp = array[low].substring(2);
array[low] = array[high].substring(2);
array[high] = temp;
low++;
high--;
}
}
String[] gs = new String[low];
System.arraycopy(array, 0, gs, 0, low);
String[] os = new String[array.length - low];
System.arraycopy(array, low, os, 0, array.length - low);

Categories

Resources