Optimal way to index a list of numbers - algorithm

Given a list of non-sequential and unordered numbers is there an optimal means to index the list, where the ith element in the index is the position of the ith element in the indexed list if it were sorted.
So [45,2,6,33] would have index [4,1,2,3]

You can just sort the list and then create a dictionary / associative array mapping each element to its index in the sorted list. You could do this in a loop, or, in Python, even in a single line:
>>> lst = [45, 2, 6, 33]
>>> d = {x: i for (i, x) in enumerate(sorted(lst))}
>>> d
{2: 0, 6: 1, 33: 2, 45: 3}
Now use the values from the dictionary to create your result list:
>>> [d[x] + 1 for x in lst]
[4, 1, 2, 3]
If the list can contain duplicate numbers, and you don't want your result list to contain duplicate indices, you can map the values to lists of indices:
>>> lst = [45, 2, 6, 33, 6]
>>> d = {}
>>> for i, x in enumerate(sorted(lst)):
... d.setdefault(x, []).append(i)
>>> d
{2: [0], 6: [1, 2], 33: [3], 45: [4]}
Now, convert each of those lists to an iterator, and get the next index for each value in the original list.
>>> d2 = {x: iter(d[x]) for x in d}
>>> [next(d2[x]) + 1 for x in lst]
[5, 1, 2, 4, 3]

You can perform radix sort which uses counting sort as a sub-routine and can work in linear time complexity for large numbers as well. Time complexity will be O(n).
Then use a hashmap <element, position when sorted> to record the position of the elements in sorted list. At last print the corresponding position of input. Space complexity is O(n).

It seems that the problem you are facing is that how to keep the original position of element since after sorting the position would be lost. You need to link position in original array with the element and then sort. Following is the pseudo code
list of pair p[] as (element, original position)
for each index i in original input array A
p.push(A[i], i) //adding element and original position
Sort on basis of first element
q = sort(p)
Now q has element in its position after sorting. Second array should have this position on its index in original array
Let new array B is resultant array
for each index i in array q
B[q[i].second] = i //q[i].second is index in original array. so putting index of sorted array in that
Update: Some are not able to understand this algorithm so have put a code in JavaScript.
var input = [45,2,6,33];
var p = [];
for(var i = 0;i < input.length;i++)
{
p.push({first:input[i],second:i});
}
p.sort(function(a,b){
return a.first > b.first;
});
var B = Array(input.length);
for(var i = 0;i < p.length;i++)
{
B[p[i].second] = i;
}
console.log(B);

Related

Failing to print a list in a sorted fashion without actually sorting the list

I am struggling to sort a list in a sorted fashion without actually "sorting" the list.
arr = [3, 2, 1, 4, 5]
count = 0
current = arr[0]
prev = -1
while count < len(arr):
for item in arr:
if current < item > prev:
current = item
prev = current
count = count + 1
print(current)
Output:
5
5
5
5
5
I don't want to sort the list. I am wondering is there a way to not sort the list and not change the original list and print the items in a sorted fashion?
It's pretty unclear what you're trying to do. If you want a sorted copy, you could make a list containing the indices of the the original objects ([0, 1, 2, ..., n]) and then sort these by comparing the original values at those indices, then map this sorted list back to the values from the first list.
But much simpler still is just to sort a shallow clone of the list.
If you read Javascript, here's a demonstration of that idea, using a simple range helper function to create the list of indices:
const arr = [8, 6, 7, 5, 3, 0, 9]
const range = (lo, hi) =>
[...Array (hi - lo)] .map((_, i) => lo + i)
const indexSort = (ns) =>
range (0, ns .length)
.sort ((i, j) => ns [i] - ns [j])
.map (x => ns [x])
console .log ('indexSort:', indexSort (arr))
console .log ('shallow clone:', [...arr] .sort ((a, b) => a - b))
console .log ('no mutation of original array:', arr)
.as-console-wrapper {max-height: 100% !important; top: 0}

Shuffle an int array such that array elements in even indices are smaller than array elements in odd indices

I need to have all the elements in the even indices arr[0],arr[2],arr[4] etc be smaller than the elements with odd indices arr[1],arr[3],arr[5], etc
My approach was to find the MEDIAN and then write out all elements smaller than the median in odd indices and all elements larger than the median in even places.
Question: is there a way to do the array shuffling IN PLACE after finding the median ?
import random
def quickselect(items, item_index):
def select(lst, l, r, index):
# base case
if r == l:
return lst[l]
# choose random pivot
pivot_index = random.randint(l, r)
# move pivot to beginning of list
lst[l], lst[pivot_index] = lst[pivot_index], lst[l]
# partition
i = l
for j in range(l+1, r+1):
if lst[j] < lst[l]:
i += 1
lst[i], lst[j] = lst[j], lst[i]
# move pivot to correct location
lst[i], lst[l] = lst[l], lst[i]
# recursively partition one side only
if index == i:
return lst[i]
elif index < i:
return select(lst, l, i-1, index)
else:
return select(lst, i+1, r, index)
if items is None or len(items) < 1:
return None
if item_index < 0 or item_index > len(items) - 1:
raise IndexError()
return select(items, 0, len(items) - 1, item_index)
def shuffleArray(array, median):
newArray = [0] * len(array)
i = 0
for x in range(0,len(array),2):
newArray[x] = array[i]
i+=1
for y in range(1,len(array),2):
newArray[y] = array[i]
i+=1
return newArray
So here's my interpretation of the question.
Shuffle an array so that all data in even indices are smaller than all data in odd indices.
Eg
[1, 3, 2, 4] would be valid, but [1, 2, 3, 4] wouldn't be.
This stops us just being able to sort the array.
Sort the array, smallest to largest.
Split the array at its mid point (rounding the mid point down).
Shuffle the two arrays together. Such that given array [1, 2, 3] and array [4, 5, 6] it becomes [1, 4, 2, 5, 3, 6].
To elaborate on 3, here's some example code... (using javascript)
let a = [ 1, 2, 3 ];
let b = [ 4, 5, 6 ];
let c = [ ] // this will be the sorted array
for (let i = 0; i < a.length + b.length; i++ ) {
if(i % 2 == 0) c.push( a[Math.floor( i/2 )]);
else c.push( b[Math.floor( i/2 )]);
}
This produces the array [1, 4, 2, 5, 3, 6], which i believe fufils the requirement.

How can the complexity of this function be decreased?

I got this function:
def get_sum_slices(a, sum)
count = 0
a.length.times do |n|
a.length.times do |m|
next if n > m
count += 1 if a[n..m].inject(:+) == sum
end
end
count
end
Given this [-2, 0, 3, 2, -7, 4] array and 2 as sum it will return 2 because two sums of a slice equal 0 - [2] and [3, 2, -7, 4]. Anyone an idea on how to improve this to a O(N*log(N))?
I am not familiar with ruby, but it seems to me you are trying to find how many contiguous subarrays that sums to sum.
Your code is doing a brute force of finding ALL subarrays - O(N^2) of those, summing them - O(N) each, and checking if it matches.
This totals in O(N^3) code.
It can be done more efficiently1:
define a new array sums as follows:
sums[i] = arr[0] + arr[1] + ... + arr[i]
It is easy to calculate the above in O(N) time. Note that with the assumption of non negative numbers - this sums array is sorted.
Now, iterate the sums array, and do a binary search for each element sums[i], if there is some index j such that sums[j]-sums[i] == SUM. If the answer is true, add by one (more simple work is needed if array can contains zero, it does not affect complexity).
Since the search is binary search, and is done in O(logN) per iteration, and you do it for each element - you actually have O(NlogN) algorithm.
Similarly, but adding the elements in sums to a hash-set instead of placing them in a sorted array, you can reach O(N) average case performance, since seeking each element is now O(1) on average.
pseudo code:
input: arr , sum
output: numOccurances - number of contiguous subarrays that sums to sum
currSum = 0
S = new hash set (multiset actually)
for each element x in arr:
currSum += x
add x to S
numOccurances= 0
for each element x in S:
let k = number of occurances of sum-x in the hashset
numOccurances += k
return numOccurances
Note that the hash set variant does not need the restriction of non-negative numbers, and can handle it as well.
(1) Assuming your array contains only non negative numbers.
According to amit's algorithm :
def get_sum_slices3(a, sum)
s = a.inject([]) { |m, e| m << e + m.last.to_i }
s.sort!
s.count { |x| s.bsearch { |y| x - y == sum } }
end
Ruby uses quicksort which is nlogn in most cases
You should detail better what you're trying to achieve here. Anyway computing the number of subarray that have a specific sum could be done like this:
def get_sum_slices(a, sum)
count = 0
(2..a.length).each do |n|
a.combination(n).each do |combination|
if combination.inject(:+) == sum
count += 1
puts combination.inspect
end
end
end
count
end
btw your example should return 6
irb> get_sum_slices [-2, 0, 3, 2, -7, 4], 0
[-2, 2]
[-2, 0, 2]
[3, -7, 4]
[0, 3, -7, 4]
[-2, 3, 2, -7, 4]
[-2, 0, 3, 2, -7, 4]
=> 6

Finding minimum element to the right of an index in an array for all indices

Given an array, I wish to find the minimum element to the right of the current element at i where 0=<i<n and store the index of the corresponding minimum element in another array.
For example, I have an array A ={1,3,6,7,8}
The result array would contain R={1,2,3,4} .(R array stores indices to min element).
I could only think of an O(N^2) approach.. where for each element in A, I would traverse the remaining elements to right of A and find the minimum.
Is it possible to do this in O(N)? I want to use the solution to solve another problem.
You should be able to do this in O(n) by filling the array from the right hand side and maintaining the index of the current minimum, as per the following pseudo-code:
def genNewArray (oldArray):
newArray = new array[oldArray.size]
saveIndex = -1
for i = newArray.size - 1 down to 0:
newArray[i] = saveIndex
if saveIndex == -1 or oldArray[i] < oldArray[saveIndex]:
saveIndex = i
return newArray
This passes through the array once, giving you the O(n) time complexity. It can do this because, once you've found a minimum beyond element N, it will only change for element N-1 if element N is less than the current minimum.
The following Python code shows this in action:
def genNewArray (oldArray):
newArray = []
saveIndex = -1
for i in range (len (oldArray) - 1, -1, -1):
newArray.insert (0, saveIndex)
if saveIndex == -1 or oldArray[i] < oldArray[saveIndex]:
saveIndex = i
return newArray
oldList = [1,3,6,7,8,2,7,4]
x = genNewArray (oldList)
print "idx", [0,1,2,3,4,5,6,7]
print "old", oldList
print "new", x
The output of this is:
idx [0, 1, 2, 3, 4, 5, 6, 7]
old [1, 3, 6, 7, 8, 2, 7, 4]
new [5, 5, 5, 5, 5, 7, 7, -1]
and you can see that the indexes at each element of the new array (the second one) correctly point to the minimum value to the right of each element in the original (first one).
Note that I've taken one specific definition of "to the right of", meaning it doesn't include the current element. If your definition of "to the right of" includes the current element, just change the order of the insert and if statement within the loop so that the index is updated first:
idx [0, 1, 2, 3, 4, 5, 6, 7]
old [1, 3, 6, 7, 8, 2, 7, 4]
new [0, 5, 5, 5, 5, 5, 7, 7]
The code for that removes the check on saveIndex since you know that the minimum index for the last element can be found at the last element:
def genNewArray (oldArray):
newArray = []
saveIndex = len (oldArray) - 1
for i in range (len (oldArray) - 1, -1, -1):
if oldArray[i] < oldArray[saveIndex]:
saveIndex = i
newArray.insert (0, saveIndex)
return newArray
Looks like HW. Let f(i) denote the index of the minimum element to the right of the element at i. Now consider walking backwards (filling in f(n-1), then f(n-2), f(n-3), ..., f(3), f(2), f(1)) and think about how information of f(i) can give you information of f(i-1).

Find the middle element in merged arrays in O(logn)

We have two sorted arrays of the same size n. Let's call the array a and b.
How to find the middle element in an sorted array merged by a and b?
Example:
n = 4
a = [1, 2, 3, 4]
b = [3, 4, 5, 6]
merged = [1, 2, 3, 3, 4, 4, 5, 6]
mid_element = merged[(0 + merged.length - 1) / 2] = merged[3] = 3
More complicated cases:
Case 1:
a = [1, 2, 3, 4]
b = [3, 4, 5, 6]
Case 2:
a = [1, 2, 3, 4, 8]
b = [3, 4, 5, 6, 7]
Case 3:
a = [1, 2, 3, 4, 8]
b = [0, 4, 5, 6, 7]
Case 4:
a = [1, 3, 5, 7]
b = [2, 4, 6, 8]
Time required: O(log n). Any ideas?
Look at the middle of both the arrays. Let's say one value is smaller and the other is bigger.
Discard the lower half of the array with the smaller value. Discard the upper half of the array with the higher value. Now we are left with half of what we started with.
Rinse and repeat until only one element is left in each array. Return the smaller of those two.
If the two middle values are the same, then pick arbitrarily.
Credits: Bill Li's blog
Quite interesting task. I'm not sure about O(logn), but solution O((logn)^2) is obvious for me.
If you know position of some element in first array then you can find how many elements are smaller in both arrays then this value (you know already how many smaller elements are in first array and you can find count of smaller elements in second array using binary search - so just sum up this two numbers). So if you know that number of smaller elements in both arrays is less than N, you should look in to the upper half in first array, otherwise you should move to the lower half. So you will get general binary search with internal binary search. Overall complexity will be O((logn)^2)
Note: if you will not find median in first array then start initial search in the second array. This will not have impact on complexity
So, having
n = 4 and a = [1, 2, 3, 4] and b = [3, 4, 5, 6]
You know the k-th position in result array in advance based on n, which is equal to n.
The result n-th element could be in first array or second.
Let's first assume that element is in first array then
do binary search taking middle element from [l,r], at the beginning l = 0, r = 3;
So taking middle element you know how many elements in the same array smaller, which is middle - 1.
Knowing that middle-1 element is less and knowing you need n-th element you may have [n - (middle-1)]th element from second array to be smaller, greater. If that's greater and previos element is smaller that it's what you need, if it's greater and previous is also greater we need to L = middle, if it's smaller r = middle.
Than do the same for the second array in case you did not find solution for first.
In total log(n) + log(n)

Resources