Merging two sorted ArrayLists - java

I am supposed to create a method that will merge two given pre-sorted ArrayLists of Strings into one. All of it has to be done in one loop. The way I have gone about it is comparing the two ArrayLists at each index and adding them in alphabetical order based on those comparisons. The problem with this is that if you are given two ArrayLists ["Bob", "Jill"] and ["Watson", "Zane"], the output would be ["Bob", "Watson", "Jill", "Zane"]. This is clearly not sorted.
That being said, I know what the problem is, I just don't know how to implement a fix for it.
Code:
public static ArrayList<String> merge(ArrayList<String> al1, ArrayList<String> al2){
ArrayList<String> al = new ArrayList<String> (al1.size() + al2.size());
for (int i = 0; i < Math.max(al1.size(), al2.size()); i++) { // Loops until max size of the two arraylists is reached
if (i < al1.size() && i < al2.size()) { // Checks if the index is still in range of both arraylists
if (al1.get(i).compareTo(al2.get(i)) < 0) { // Compares the two arraylists at the same index
al.add(al1.get(i));
al.add(al2.get(i));
} else {
al.add(al2.get(i));
al.add(al1.get(i));
}
} else if (i < al1.size() && i > al2.size()) { // Checks if the index is greater than the size of al2
al.add(al1.get(i));
} else { // Anything else, just add al2
al.add(al2.get(i));
}
}
return al;

public static ArrayList<Double> merge(ArrayList<Double> a, ArrayList<Double> b) {
ArrayList<Double> toReturn = new ArrayList<Double>();
int a_ = 0;
int b_ = 0;
for(int j = 0; j < a.size() + b.size(); j++) {
if(a_ == a.size())
toReturn.add(b.get(b_++));
else if(b_ == b.size())
toReturn.add(a.get(a_++));
else if(a.get(a_).compareTo(b.get(b_)) < 0)
toReturn.add(a.get(a_++));
else
toReturn.add(b.get(b_++));
}
return toReturn;
}

You're using one index, i, to index into both a1 and a2, which means that you can't move through the two lists independently. As you've noticed, a pair of lists like [1, 2, 3] and [4, 5, 6] should be merged by taking all three elements from the first list and then taking the elements from the second list. By traversing both lists simultaneously as you're doing that's not possible.
Instead track two separate indicies, say i1 and i2, and increment them independently. At each iteration determine which index is smaller, add that value and increment that index. Once you've reached the end of one list or the other you just drain the remaining list.

You shoud take a look at merge step of merge-sort algo:
https://en.wikipedia.org/wiki/Merge_sort
All in all you should have something like:
int i = 0;
int j = 0;
while (i < a.size() && j < b.size()) {
if (a.get(i).compareTo(b.get(j)) <= 0) {
result.add(a.get(i));
++i;
} else {
result.add(b.get(j));
++j;
}
}
for (; i < a.size(); ++i) {
result.add(a.get(i));
}
for (; j < a.size(); ++j) {
result.add(b.get(j));
}
return result;
Didin't test it, however in result you should have something that looks the same

Related

Rearrange elements in an integer array in a fashion where-you first replace and then move the replaced integer to first part of the array

I came across a Hackerearth coding problem where you have to perform the following tasks over an integer array-
Search for a particular number in the array and replace it's occurrences with 1
Move all the 1s to the first part of the array, maintaining the original order of the array
For example- if we have an integer array {22,1,34,22,16,22,35,1}, here we search for the number "22" (let us assume it is present in the array), replace it with 1 and move all those 1s (including the 1s already present) to the first part of the array and the resultant array should look like {1,1,1,1,1,1,34,16,35} -maintaining the original order of the array, preferably in Java.
I actually have coded a solution and it works fine but is not optimal, can anyone help me find an optimal solution (w.r.t. time-space complexity)?
Below is my solution-
public static void main(String[] args) {
int[] n = rearr(new int[] {22,1,34,22,16,22,1,34,1}, 22);
for(int i=0; i<n.length; i++) {
System.out.print(n[i]+" ");
}
}
static int[] rearr(int[] a, int x) {
int[] temp = new int[a.length];
int j=0, c=0, k=0;
//search and replace
for(int i=0; i<a.length; i++) {
if(a[i] == x) {
a[i] = 1;
}
}
//shift all 1s to first part of array or shift all non-1s to last part of the array
for(int i=0; i<a.length; i++) {
if(a[i] != 1) {
temp[j] = a[i];
j++;
}
if(a[i] == 1) {
c++;
}
}
j=0;
for(int i=0; i<a.length && c>0; i++, c--) {
a[i] = 1;
j++;
}
for(int i=j ;i<a.length; i++) {
a[i] = temp[k];
k++;
}
return a;
}
This can be done in linear time and space complexity, by returning a completely new list instead of modifying the original list.
static int[] rearr(int[] a, int x) {
// allocate the array we'll return
int[] b = new int[a.length];
int fillvalue = 1;
// iterate backwards through the list, and transplant every value OTHER than
// (x or 1) to the last open index in b, which we track with b_idx
int b_idx = b.length - 1;
for (int i = a.length - 1; i >= 0; i--) {
if (a[i] != x && a[i] != fillvalue)) {
b[b_idx] = a[i];
b_idx--;
}
}
// once we've gone through and done that, fill what remains of b with ones
// which are either original or are replacements, we don't care
for (int i = b_idx; i >= 0; i--) {
b[i] = fillvalue;
}
return b;
}
This is linear space complexity because it requires additional space equal to the size of the given list. It's linear time complexity because, in the worst case, it iterates over the size of the list exactly twice.
As a bonus, if we decide we want to leave the original 1s where they were, we can do that without any trouble at all, by simply modifying the if condition. Same if we decide we want to change the fill value to something else.
Doing this with constant space complexity would require O(n^2) list complexity, as it would require swapping elements in a to their proper positions. The easiest way to do that would probably be to do replacements on a first run through the list, and then do something like bubblesort to move all the 1s to the front.
This can be done in a single iteration through the array. We can use 2 pointer approach here where we will use on pointer to iterate through the array and other one to point to the index of 1 in the array.
The code is below:
public static void main(String[] args) {
// input array
int[] arr = { 22, 1, 34, 22, 16, 22, 35, 1, 20, 33, 136 };
// element to be replaced
int x = 22;
int j = -1;
for (int i = arr.length - 1; i >= 0; i--) {
if (arr[i] == 1 || arr[i] == x) {
if (j == -1) {
j = i;
}
// incase arr[i]==x
arr[i] = 1;
} else {
if (j != -1) {
arr[j] = arr[i];
arr[i] = 1;
j--;
}
}
}
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
}
Here we initialise j=-1 since we consider there are no 1's present in the array.
Then we start iterating the array from the end towards the starting of the array as we have to push all the 1's to the starting of the array. Now when we reach to 1 or x (particular number in your case), we check if this is first occurrence of the x or 1, if yes then we initialise the j with this index and change arr[i] = 1 because this could be equal to x then we need to make it 1. If the arr[i] is not 1 or x it means its a number which we need to push at back of the array. We check if we have position of 1 or j=-1. If j=-1 it means this number is already pushed back at end of array else we swap the number at i and j, and decrement j by 1.
At the end of the array we will have the array sorted in a fashion which is required.
Time Complexity: Since we are only iterating the array one, hence the time complexity is O(n).
Space Complexity: Since there are no extra space being used or constant space being used hence the space complexity is O(1)

CodeFights - issues with time limit when writing FirstDuplicate method

I'm trying to solve the problem below from CodeFights. I left my answer in Java after the question. The code works for all the problems, except the last one. Time limit exception is reported. What could I do to make it run below 3000ms (CodeFights requirement)?
Note: Write a solution with O(n) time complexity and O(1) additional space complexity, since this is what you would be asked to do during a real interview.
Given an array a that contains only numbers in the range from 1 to a.length, find the first duplicate number for which the second occurrence has the minimal index. In other words, if there are more than 1 duplicated numbers, return the number for which the second occurrence has a smaller index than the second occurrence of the other number does. If there are no such elements, return -1.
Example
For a = [2, 3, 3, 1, 5, 2], the output should be
firstDuplicate(a) = 3.
There are 2 duplicates: numbers 2 and 3. The second occurrence of 3 has a smaller index than than second occurrence of 2 does, so the answer is 3.
For a = [2, 4, 3, 5, 1], the output should be
firstDuplicate(a) = -1.
Input/Output
[time limit] 3000ms (java)
[input] array.integer a
Guaranteed constraints:
1 ≤ a.length ≤ 105,
1 ≤ a[i] ≤ a.length.
[output] integer
The element in a that occurs in the array more than once and has the minimal index for its second occurrence. If there are no such elements, return -1.
int storedLeastValue = -1;
int indexDistances = Integer.MAX_VALUE;
int indexPosition = Integer.MAX_VALUE;
for (int i = 0; i < a.length; i++)
{
int tempValue = a[i];
for (int j = i+1; j < a.length; j++) {
if(tempValue == a[j])
{
if(Math.abs(i-j) < indexDistances &&
j < indexPosition)
{
storedLeastValue = tempValue;
indexDistances = Math.abs(i-j);
indexPosition = j;
break;
}
}
}
}
return storedLeastValue;
Your solution has two nested for loops which implies O(n^2) while the question explicitly asks for O(n). Since you also have a space restriction you can't use an additional Set (which can provide a simple solution as well).
This question is good for people that have strong algorithms/graph theory background. The solution is sophisticated and includes finding an entry point for a cycle in a directed graph. If you're not familiar with these terms I'd recommend that you'll leave it and move to other questions.
Check this one, it's also O(n) , but without additional array.
int firstDuplicate(int[] a) {
if (a.length <= 1) return -1;
for (int i = 0; i < a.length; i++) {
int pos = Math.abs(a[i]) - 1;
if (a[pos] < 0) return pos + 1;
else a[pos] = -a[pos];
}
return -1;
}
The accepted answer does not work with the task.
It would work if the input array would indeed contain no bigger value than its length.
But it does, eg.: [5,5].
So, we have to define which number is the biggest.
int firstDuplicate(int[] a) {
int size = 0;
for(int i = 0; i < a.length; i++) {
if(a[i] > size) {
size = a[i];
}
}
int[] t = new int[size+1];
for(int i = 0; i < a.length; i++) {
if(t[a[i]] == 0) {
t[a[i]]++;
} else {
return a[i];
}
}
return -1;
}
What about this:
public static void main(String args[]) {
int [] a = new int[] {2, 3, 3, 1, 5, 2};
// Each element of cntarray will hold the number of occurrences of each potential number in the input (cntarray[n] = occurrences of n)
// Default initialization to zero's
int [] cntarray = new int[a.length + 1]; // need +1 in order to prevent index out of bounds errors, cntarray[0] is just an empty element
int min = -1;
for (int i=0;i < a.length ;i++) {
if (cntarray[a[i]] == 0) {
cntarray[a[i]]++;
} else {
min = a[i];
// no need to go further
break;
}
}
System.out.println(min);
}
You can store array values in hashSet. Check if value is already present in hashSet if not present then add it in hashSet else that will be your answer. Below is code which passes all test cases:-
int firstDuplicate(int[] a) {
HashSet<Integer> hashSet = new HashSet<>();
for(int i=0; i<a.length;i++){
if (! hashSet.contains(a[i])) {
hashSet.add(a[i]);
} else {
return a[i];
}
}
return -1;
}
My simple solution with a HashMap
int solution(int[] a) {
HashMap<Integer, Integer> countMap = new HashMap<Integer, Integer>();
int min = -1;
for (int i=0; i < a.length; i++) {
if (!(countMap.containsKey(a[i]))) {
countMap.put(a[i],1);
}
else {
return a[i];
}
}
return min;
}
Solution is very simple:
Create a hashset
keep iterating over the array
if element is already not in the set, add it.
else element will be in the set, then it mean this is minimal index of first/second the duplicate
int solution(int[] a) {
HashSet<Integer> set = new HashSet<>();
for(int i=0; i<a.length; i++){
if(set.contains(a[i])){
// as soon as minimal index duplicate found where first one was already in the set, return it
return a[i];
}
set.add(a[i]);
}
return -1;
}
A good answer for this exercise can be found here - https://forum.thecoders.org/t/an-interesting-coding-problem-in-codefights/163 - Everything is done in-place, and it has O(1) solution.

Java - Multidimensional Arrays How to test an array for all unique values

I'm trying to test a multidimensional array to see if contains any duplicate values. If it does I would like the method to return false. Otherwise I would like it to return true.
Here is my current code. Where is my logic wrong?
public static boolean isUnique(int[][] array2, int num) {
for (int i = 0; i < array2.length - 1; i++) {
for (int j = i + 1; j < array2.length; j++) {
if (array2[i] == array2[j]) {
return false;
}
}
}
return true;
}
Your current implementation is checking whether two rows are the same (this is a reference based check, rather than a value.) This means that it asks 'Are array2[i] and array2[j] the same address in memory, rather than do they contain the same things.
If you want to see whether the rows are unique you'd use array2[i].equals(array2[j]) instead of array2[i] == array2[j].
If you wanted to check for unique elements (array[i][j] != array2[i+m][j+n] where !(m == n == 0)) you'd need to iterate through both levels in a
for (int i = 0; i < array2.length; i++) {
for (int j = 0; j < array2[i].length; j++) {
// compare array2[i][j] to all other array2[m][n] here.
}
}
Right now your code is checking whether any of the arrays inside array2 are the same. array2[i] and array2[j] are both referring to arrays, because array2 is an array of arrays.
Instead, you want to look at the values inside of each of those arrays. Since you want to fail on any repeated value anywhere in the grid, you're effectively trying to flatten the structure into one collection, and check for duplicates in that.
A HashSet is the best data structure to use in this case. Traverse the entire grid, row-by-row, adding values into your new struture. If you encounter a duplicate, return false:
public static boolean isUnique(int[][] array2) {
Set<Integer> values = new HashSet<>();
for (int i = 0; i < array2.length; i++) {
for (int j = 0; j < array2[i].length; j++) {
if (!values.add(array2[i][j])) {
return false;
}
}
}
return true;
}
Some things to note here:
The set's add method will return false if you attempt to add a duplicate to the collection, so that's wrapped in an if statement for a simple, fail-fast stopping condition.
The sizes of each of the inner arrays are completely independent from the size of the outer array, so you still want to loop from 0 to the length of the array (when you're using <, you don't need the length - 1).

Finding Median of Array with Selection Sort

I'm trying to find the median from an unsorted array in Java. First, I need to use the selection sort technique to sort the array, and I cannot use any Java library methods for sorting (so no Arrays.sort(array)). Also, I cannot sort the entire array either. I can only sort as many elements as necessary to find the median of the array. I suppose for an even array, it would be just half of the elements plus one (then find the average of the last two elements), and for an odd array it would just be half of the elements (the last being the median).
So I'm not sure how to stop the selection sort at just the right time and find the median from the last element or two of the partly sorted array. Below is what I have so far.
import java.util.Arrays;
public class EfficientMedian
{
public static void median(int[] values)
{
int i, j, temp;
double median;
//selection sort below
for (i = 0; i < values.length - 1; i++)
{
for (j = i + 1; j < values.length; j++)
{
if (values[i] > values[j])
{
temp = values[i];
values[i] = values[j];
values[j] = temp;
}
}
}
if (values.length % 2 == 0) //if the array is even
{
median = values[values.length/2]; //just a placeholder
}
else //if the array is odd
{
median = values[values.length/2];
}
System.out.println(Arrays.toString(values));
System.out.println(median);
}
public static void main(String[] args)
{
int[] array1 = {567, 2, 600, 6, 601}, array2 = {45, 300, 46, 49};
median(array1);
median(array2);
}
}
Your first loop selects elements to sort. If you only need median, you only need to sort values.length/2 elements. So you should edit this:
for (i = 0; i < values.length - 1; i++)
{
...
}
to
for (i = 0; i < values.length/2; i++)
{
...
}
and fyi in the "length of the array is odd" case, the convention is to average middle two values.

How to merge two arraylists into one in ascending order

I need to merge two lists into one, in ascending order, not duplicates, and I think my code is really close, I'm just missing something and I can't figure it out. As of now, my code is not working properly in my merge method. I think it has something to do with my loops, but I just can't work around it. My current method prints the new list, but it is not in perfect increasing order. I would appreciate any assistance in figuring out how to make this method print my merged list with ascending order using the contents of l1 and l2.
**Note: I cannot use any built-in array sorting methods.
Thanks!
import java.util.ArrayList;
import java.util.Random;
public class MergeLists {
public static ArrayList<Integer> merge(ArrayList<Integer> l1, ArrayList<Integer> l2){
ArrayList<Integer> mergedList = new ArrayList();
for (int j = 0; j < l1.size(); j++) {
if (l1.get(j) < l2.get(j)) {
mergedList.add(l1.get(j));
mergedList.add(l2.get(j));
} else {
mergedList.add(l2.get(j));
mergedList.add(l1.get(j));
}
}
for (int i = l2.size() - l1.size(); i < l2.size(); i++) {
mergedList.add(l2.get(i));
}
return mergedList;
}
public static ArrayList<Integer> makeRandomIncreasingList(int length) {
ArrayList<Integer> randomList = new ArrayList();
Random rand = new Random();
int inList = rand.nextInt(9) + 1;
int inList2 = rand.nextInt(9) + 1;
for (int i = 0; i < length; i++) {
randomList.add(inList);
inList = inList + inList2;
}
return randomList;
}
public static void doMergeTest() {
ArrayList<Integer> list1 = makeRandomIncreasingList(10);
ArrayList<Integer> list2 = makeRandomIncreasingList(20);
ArrayList<Integer> mergedList = merge(list1, list2);
System.out.println("List 1:" + list1);
System.out.println("List 2:" + list2);
System.out.println("Merged list:" + mergedList);
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println("Performing merge test #" + (i + 1) + ":");
doMergeTest();
}
}
}
Remove duplicates
arrayList1.remove(arrayList2);
Then merge two arrayList:
arrayList1.addAll(arrayList2);
And Lastly sort the last
collections.sort(arrayList1);
Another way is to use SET: Set doesnt allow duplicates
(HashSet is faster depending on the List implementation class)
Set setmerge = new HashSet(list1);
setmerge.addAll(list2);
list1.clear();
list1.addAll(setmerge);
The first part of your merge() method seems ok, if you modify it a little bit. You need to be going through both lists in parallel, something like
int i = 0, j = 0;
for (; i < l1.size() && j < l2.size();)
And compare individual items and increment indices independently, as in
if (l1.get(i) < l2.get(j)) {
...
i++;
} else
...
j++;
}
The way you were doing it you were literally going in parallel, which is not always correct (think of lists [1 2 2] and [1 1 1] => your merge would look like [1 1 1 2 1 2])
Then, after your "parallel" for-loop (the one where you're iterating through both lists), one of your indices is always going to break your loop because it's at the end of its list. For in-order merging, I usually declare i, j outside the loop (you'll need then after your first for-loop, like above) and then do something like (in your notation):
for (int i1 = i; i1 < l1.size(); i1++) {
mergeList.add(l1.get(i1));
}
for (int i2 = j; i2 < l2.size(); i2++) {
mergeList.add(l2.get(i2));
}
After your first for-loop, you get to the end of exactly one of the lists (someone's going to break the loop), so exactly one of the above loops is going to get executed, and that will contain the remaining items, in order.
Edit: your last for-loop of the merge() method is not correct for your purpose.
You have assumed l2 items are always bigger than l1 items, since you are adding remainder of l2 items in the end of the list. You need to compare them with mergedList items and add them accordingly.

Categories