why does AbstractList.removeRange require quadratic time? - java

I can see in the documentation for:
java.util.AbstractList#removeRange
That it requires quadratic time:
This implementation gets a list iterator positioned before fromIndex,
and repeatedly calls ListIterator.next followed by ListIterator.remove
until the entire range has been removed. Note: if ListIterator.remove
requires linear time, this implementation requires quadratic time.
But why?? The code:
protected void removeRange(int fromIndex, int toIndex) {
ListIterator<E> it = listIterator(fromIndex);
for (int i=0, n=toIndex-fromIndex; i<n; i++) {
it.next();
it.remove();
}
}
Seems for me to be linear... But I have to be wrong as I'm a newbie in this kind of algorithmic stuff. Please help me to understand it.

The important part is "Note: if ListIterator.remove requires linear time, this implementation requires quadratic time." The for loop requires linear time, you're right. But, if you do a linear time step on each iteration, you get n * n = n^2.

The reason is in :
it.remove();
that can be an O(n) operation on a list, which you call within an O(n) loop.
In other words, your real loop would look like this if it is the case (I made it up but you get the idea):
protected void removeRange(int fromIndex, int toIndex) {
ListIterator<E> it = listIterator(fromIndex);
for (int i = 0, n = toIndex - fromIndex; i < n; i++) {
E item = it.next();
//it.remove();
for (int j = ; j < n; j++) {
if (list.get(j).equals(e)) {
list.remove(e);
break;
}
}
}
}

Related

Memory Efficient Way to Remove Kth Node from End of Linked List

This is a known problem with several known solutions, but my current struggle is to try and find the most efficient way to solve it, considering memory usage (and not time-complexity).
The problem: Given a singly-linked-list of unknown (but potentially quite large) size N, remove the Kth member from the end of the list. 0 <= K < N.
If K is 0, remove the last node of the list. If K = N-1, remove the first node in the list.
My initial approach was recursion - it's the simplest to write, and its time complexity is O(N) - going over the list twice, to the end and back.
public int removeKLast(Node<T> node, int k) {
if (node.getNext() == null) {
return k;
} else {
int current = removeKLast(node.getNext(), k);
if (current == 0) {
node.setNext(node.getNext().getNext());
}
return current - 1;
}
}
It had some end-cases that needed solving (like removing the first node from the list) but was otherwise simple enough.
My issue is that this implementation means the entire linked list is stored in memory, with the overhead associated with objects. I wanted to know if a more efficient solution (still in O(N) time, running over the list twice at most) could be found, using at most K primitive ints in memory at any given time.
Start list traversal. After K steps start the second iterator and then walk in parallel. When the first iterator reaches the end, the second one stands on the node to delete.
This approach cannot change O(n) complexity and performs about 2n (2n-k) step operations, but excludes "delay" between end finding and deletion
Consider retrieving the list size first:
public int size(Node<T> node) {
int size = 0;
if(node != null) {
for(; node.getNext() != null; node = node.getNext())
size++;
}
return size;
}
Then you can remove the k-th before last in one go:
public int removeKLast(Node<T> node, int size, int k) {
for(int i = 0; node = node.getNext() && i < size - k - 1; ++i) {}
node.setNext(node.getNext().getNext());
}
Additional memory needed: 1 variable of type int (size).
Time complexity: O(n).
You missed to explain how you store your head node, so this implementation will not work when you try to remove the first node. The modification needed to fix this would be something like:
public void removeKLast(Node<T> node, int size, int k) {
if(k == size - 1) {
node.setHead(node.getHead().getNext());
} else {
for(int i = 0; node = node.getNext() && i < size - k - 1; ++i) {}
node.setNext(node.getNext().getNext());
}
}

Java, First Duplicate value from an array in O(n) time complexity

int firstDuplicate(int[] a) {
Set<Integer> result = new HashSet();
for(int i=0; i<a.length; i++) {
if(result.contains(a[i])) {
return a[i];
} else {
result.add(a[i]);
}
}
return -1;
}
The complexity of the above code is O(n2) because of contains check inside the for loop.
How to achieve this with a complexity of O(n) in java.
Implementation of .contains for ArrayList
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
The implementation you're providing corresponds to the contains(Object) method in the ArrayList class. The method you're using is actually HashSet.contains(Object).
The complexity of HashSet.contains(Object) is O(1). To achieve this, it uses a hash of the value stored to find the element searched for.
In the unlikely event that two objects share the same hash, both elements will be stored under the same hash in a list. This might be the reason that is misleading you to believe that the cost for HashSet.contains(Object) is O(n). Although there is a list, elements are nearly evenly distributed, and thus the list size tends to 1, transforming O(n) to O(1).
As already explained in this answer your algorithm has already O(n) time complexity as HashSet’s lookup methods (includes both contains and add) have O(1) time complexity.
Still, you can improve your method, as there as no reason to perform two lookups for each element:
static int firstDuplicate(int[] a) {
Set<Integer> result = new HashSet<>();
for(int i: a) if(!result.add(i)) return i;
return -1;
}
The contract of Set.add is to only add a value (and return true) if the value is not already contained in the set and return false if it is already contained in the set.
Using this you may end up with half the execution time (it’s still the same time complexity) and have simpler code…
void printRepeating(int a[], int asize){
int i;
System.out.println("The repeating elements are : ");
for (i = 0; i < asize; i++)
{
if (a[Math.abs(a[i])] >= 0){
a[Math.abs(a[i])] = -a[Math.abs(a[i])];
}
else{
System.out.print(Math.abs(a[i]) + " ");
break;
}
}
}

Algorithm for insert/sort into circular priority queue

// gives next index in array which wraps around in a ring; moves clockwise through indices
private int nextSlot(int k) {
return ((k + 1) % A.length);
}
// Insert method
public void insert(int k) {
if( size == A.length)
resize();
A[next] = k;
for(int i = 0; i < next; i = nextSlot(i)) {
if(k < A[i]) {
for( int j = next - 1; j >= i; j--){
A[nextSlot(j)] = A[j];
}
A[i] = k;
break;
}
}
next = nextSlot(next);
size++;
}
I am trying to create an insert/sort method that inserts values into a circular priority queue in ascending order. The problem I'm having is when the next pointer cycles back to the beginning of the array, the items at the front of the queue aren't being sorted. I've been struggling with this for hours now, any help would be greatly appreciated.
Specifically, when next cycles back to the beginning, it's going to be 0, and therefore this for loop:
for(int i = 0; i < next; i = nextSlot(i)) {
will not do anything.
In general, however, I see several problems with your program. First of all, why are you implementing this as a circular array? Circular arrays are useful when you want to be able to quickly add/remove from both the beginning and end of an array. You seem to be inserting into the middle, so there is no reason to complicate your code when you're doing a linear search through the entire list at each insert anyway.
Finally, be aware that when operating on a circular array, you need to take into account that your indices will wrap around and become 0. Therefore, this line:
for( int j = next - 1; j >= i; j--)
is wrong for at least two reasons:
j>=i is not the correct way to find out if j has reached i
j-- is also wrong as j-1 needs to be wrapped

Checking this Big O Analysis

I'm trying to learn Big O analysis, and I was wondering if someone could let me know if I'm doing it right with these two examples (and if I'm not, where did I go wrong?). I got the first one to be O(N^2) and the second to be O(N). My breakdown of how I got them is in the code below.
First example
public void sort(Integer[] v) {
//O(1)
if(v.length == 0)
return;
//O(N)*O(N)*O(1)
for(int i = 0; i < v.length; ++i)
{
for(int j = i + 1; j < v.length; ++j )
{
if(v[j].compareTo(v[i]) < 0)
{
Integer temp = v[i];
v[i] = v[j];
v[j] = v[i];
}
}
}
}
Second example
public void sort(Integer[] v){
TreeSet<Integer> t = new TreeSet<>();
//O(N)
for(int i = 0; i < v.length(); ++i)
{
t.add(v[i]);
}
int i = 0;
//O(N)
for(Integer value : temp)
{
v[i++] = v;
}
}
Thanks for the help!
You are right - the first is O(N^2) because you have one loop nested inside another, and the length of each depends on the input v. If v has length 2, you'll run those swaps 4 times. If v has length 8, they will execute 64 times.
The second is O(N) because you have iterate over your input, and your loop contain any iterative or expensive operations. The second is actually O(n log(n)) - see comments on the original post.
Your first example is O(N^2), you are right.
Your second example is not O(N), so you are not right.
It is O(N) * O(log N) + O(N)
O(N) first loop
O(log N) insert into set
O(N) second loop
Finally you have O(N * log N + N), take the higher value, so answer is O(N*log N)
Edited
By the way, Big O notation does not depend of programming language
It could helps

sorting an arraylist of objects using insertion sort

here's my code that uses selection. i need to use a insertion and do not use temporary arrays or arraylist. i need help of how to do a insertion sort.
public static void sortStudents(ArrayList<Student> list)
{//selection sort
Student tempStudent;
int count1;
int count2;
int largest;
for (count1=0; count1<list.size()-1; count1++)
{
largest = 0;
for (count2=largest+1; count2<list.size()-count1; count2++)
{
if ((list.get(largest)).compareTo(list.get(count2)) < 0)
{
largest = count2;
}
}
tempStudent = list.get(list.size()-1-count1);
list.set(list.size()-1-count1, list.get(largest));
list.set(largest, tempStudent);
}
}
}
Both selection sort and insertion sort work quite similarly, by having a "not yet sorted" part of the list, and an "already sorted" part. In the beginning, the first one is the whole list, and the second part an empty list at the start or end. While sorting the "not yet sorted" part shrinks, while the "already sorted" part grows, by one element per iteration.
The difference between selection sort and insertion sort is this:
For selection sort, you search the minimum (or maximum) element of the "not yet sorted" part, remove it there and then add it to the end (or beginning) of the already sorted part.
For insertion sort, you take the next element of the "not yet sorted" part of the list, find it's insertion point in the "already sorted" part and insert it there.
This should be enough to change your selection sort to insertion sort.
You don't define variables outside the loop if it is only used in the loop. Restricting the lifetime of your variables makes it more easy to reason about the code.
public static void sortStudents (ArrayList<Student> list)
{
int largest;
for (int i=0; i < list.size () - 1; i++)
{
largest = 0;
for (int j=largest + 1; j < list.size () - i; j++)
{
if ((list.get (largest)).compareTo (list.get (j)) < 0)
{
largest = j;
}
}
Student tempStudent = list.get (list.size () - 1 - i);
list.set (list.size () - 1 - i, list.get (largest));
list.set (largest, tempStudent);
}
}
A bit more indentation makes your code more readable. Now what's your concrete error - doesn't it compile, throws it an exception or does it produce the wrong result?
Here is something suspicious in the inner loop:
largest = 0;
for (int j=largest + 1; j < list.size () - i; j++)
If you set largest to 0, then j will be initialized to 0 + 1 => 1. I guess you had another intention. Did you mean j = i + 1;?

Categories

Resources