I have 3 lists each containing some integers.
Now I have to find out how many Triangles can I make such that each side of the triangle is from a different list.
A = [3,1,2]
B = [1,5]
C = [2,4,1]
Possible Triangles:
3,5,4
1,1,1
2,1,2
2,5,4
So answer should be 4.
I tried using three loops and using the triangle property where sum of any two sides is always greater than the third. But the time complexity for this O(n^3). I want something more faster.
count = 0
for i in range(len(a)):
for j in range(len(b)):
for k in range(len(c)):
if (a[i]+b[j] > c[k]) and (a[i]+c[k] > b[j]) and (c[k]+b[j] > a[i]):
count += 1
print(count)
What's the O of this code?
import itertools as it
[sides for sides in it.product(A, B, C) if max(sides) < sum(sides) / 2.]
Edit: Jump to the bottom for an O(n^2) solution I found later, but which builds on the ideas in my previous solutions.
Solution 1: use binary search to get O(n^2 log(n))
Pseudo code:
C_s = sort C in ascending order
for a in A:
for b in B:
use binary search to find the highest index c_low in C_s s.t. C_s[c_low] <= |a-b|
use binary search to find lowest index c_high in C_s s.t. C_s[c_high] >= a+b
count += (c_high-c_low-1)
Explanation and demonstration of correctness:
given the pair (a,b):
values of c from C_s[0] to C_s[c_low] are too short to make a triangle
values of c from C_s[c_high] to C_s[end] are too large
there are c_high-c_low-1 values of c between c_high and c_low, excluding the end points themselves, that will make a valid triangle with sides (a,b,c)
I assume the standard binary search behaviour of returning an index just past either end if all values in C_s are either > |a-b| or < a+b, as if C_s[-1] = -infinity and C_s[end+1] = +infinity.
Solution 2: cache values to get best case O(n^2) but worse case O(n^2 log n)
The algorithm above can be improved with a cache for the results of the binary searches. If you cache them in an associative array with insert and lookup both O(1), then this is faster in the best case, and probably also in the average case, depending on the distribution of inputs:
C_s = sort C in ascending order
low_cache = empty associative array
high_cache = empty associative array
for a in A:
for b in B:
if |a-b| in low_cache:
c_low = low_cache{|a-b|}
else:
use binary search to find the highest index c_low in C_s s.t. C_s[c_low] <= |a-b|
set low_cache{|a-b|} = c_low
if a+b in high_cache:
c_high = high_cache{a+b}
else:
use binary search to find lowest index c_high in C_s s.t. C_s[c_high] >= a+b
set high_cache{a+b} = c_high
count += (c_high-c_low-1)
Best case analysis: if A and B are both {1 .. n}, i.e. the n numbers between 1 and n, then the binary search for c_low will be called n times, and the binary search for c_high will be called 2*n-1 times, and the rest of the time the results will be found in the cache.
So the inner loop does 2*n^2 cache lookups with total time O(2*n^2 * 1) = O(n^2), plus 3*n-1 binary searches with total time O((3*n-1) log n) = O(n log n), with an overall best-case time of O(n^2).
Worst case analysis: the cache misses every time, we're back to the original algorithm with its O(n^2 log n).
Average case: this all depends on the distribution of inputs.
More ideas I thought of
You might be able to make it faster by sorting A and B too. EDIT: see solution 3 below for how to get rid of the log n factor by sorting B. I haven't yet found a way to improve it further by sorting A, though.
For Example, if you sort B, as you move to the next b, you can assume the next c_low is greater than the previous if |a-b| is greater, or vice versa. Similar logic applies to c_high. EDIT: the merge idea in solution 3 takes advantage of exactly this fact.
An easy optimization if A, B and C are of different sizes: make C the largest one of the three lists.
Another idea that doesn't actually help, but that I think is worth describing to guide further thought:
calculate all the values of |a-b| and a+b and store them in an array (time: O(n^2))
sort that array (O((n^2)log(n^2))=O(n^2 log n)) -- this is the step that breaks the idea and brings us back to O(n^2 log n),
use a merge algorithm between than sorted array and C_sorted (O(n^2+n)=O(n^2)) to extract the info from the results.
That is very fuzzy pseudo code, I know, but if sorting the array of n^2 elements could somehow be made faster, maybe by constructing that array in a more clever way by sorting A and B first, maybe the whole thing could come down to O(n^2), at the cost of significantly increased complexity. However, thinking about this overnight, I don't see how to get rid of the log n factor here: you could make O(n) sorted lists of O(n) elements and merge them, but merging O(n) lists brings the log n factor right back in - it may be somewhat faster, but only by a constant, the complexity class stays the same.
Solution 3: use merge to bring it down to O(n^2)
Trying to figure out I could how to take advantage of sorting B, I finally realized the idea was simple: use the merging algorithm of merge sort, which can merge two sorted lists of n elements in 2*n - 1 comparisons, but modify it to calculate c_low and c_high (as defined in solution 1) for all elements of B in O(n) operations.
For c_high, this is simple, because {a+b for b in B} sorts in the same order as B, but for c_low it will take two merging passes, since {|a-b| for b in B} is descending as b increases up to b<=a, and ascending again for b>=a.
Pseudo code:
B_s = sort B in ascending order
C_s = sort C in ascending order
C_s_rev = reverse C_s
for a in A:
use binary search to find the highest index mid where B_s[mid] <= a
use an adapted merge over B_s[0..mid] and C_s_rev to calculate c_low_i for i in 0 .. mid
use an adapted merge over B_s[mid+1..end_B] and C_s to calculate c_low_i for i in mid+1 .. end_B
use an adapted merge over B_s and C_s to calculate c_high_i for i in range(len(B_s))
for i in 0 .. end_B:
count += c_high_i - c_low_i - 1
Big-Oh analysis: the body of the loop takes O(log(n) + 2n + 2n + 2n + n) = O(n) and runs n times (over the elements of A) yielding a total complexity class of O(n^2).
Related
two sorted array A, B with length n,m (n <= m), and k which (k >= log n) is given.
with log (nm) we can find k-th smallest number in union of these two array.
I have a solution In problem 2 here, but my challenge is why two given condition "(n <= m)" and "k >= log n" does not affect this algorithm?
First assumption: n <= m is an assumption "without loss of generality". If n >= m, then just swap A and B in your head. They included this assumption even though it was not needed, because they felt it was "free" to make this assumption.
Second assumption: A trivial algorithm to find the kth smallest element is to iterate over A and B simultaneously, advancing in the array that has the smaller element of the two. This would be exactly like running the "Merge" function from mergesort, but stopping once you've merged the first k elements. The complexity would be O(k). They wanted you to find a more complex algorithm, so they "ruled out" this algorithm by stating that k >= log(n), which implies that complexity O(k) is never better than O(log(n)). Technically, if they wanted to thoroughly rule this algorithm out, they should also have stated that k <= n + m - log(n), otherwise you could run the "merge" function from the end: merge the n+m-k largest elements, then return the n+m-kth largest element, which is the same as the kth smallest element.
This question is an extension of my previous question: Fast python algorithm to find all possible partitions from a list of numbers that has subset sums equal to a ratio
. I want to divide a list of numbers so that the ratios of subset sums equal to given values. The difference is now I have a long list of 200 numbers so that a enumeration is infeasible. Note that although there are of course same numbers in the list, every number is distinguishable.
import random
lst = [random.randrange(10) for _ in range(200)]
In this case, I want a function to stochastically sample a certain amount of partitions with subset sums equal or close to the given ratios. This means that the solution can be sub-optimal, but I need the algorithm to be fast enough. I guess a Greedy algorithm will do. With that being said, of course it would be even better if there is a relatively fast algorithm that can give the optimal solution.
For example, I want to sample 100 partitions, all with subset sum ratios of 4 : 3 : 3. Duplicate partitions are allowed but should be very unlikely for such long list. The function should be used like this:
partitions = func(numbers=lst, ratios=[4, 3, 3], num_gen=100)
To test the solution, you can do something like:
from math import isclose
eps = 0.05
assert all([isclose(ratios[i] / sum(ratios), sum(x) / sum(lst), abs_tol=eps)
for part in partitions for i, x in enumerate(part)])
Any suggestions?
You can use a greedy heuristic where you generate each partition from num_gen random permutations of the list. Each random permutation is partitioned into len(ratios) contiguous sublists. The fact that the partition subsets are sublists of a permutation make enforcing the ratio condition very easy to do during sublist generation: as soon as the sum of the sublist we are currently building reaches one of the ratios, we "complete" the sublist, add it to the partition and start creating a new sublist. We can do this in one pass through the entire permutation, giving us the following algorithm of time complexity O(num_gen * len(lst)).
M = 100
N = len(lst)
P = len(ratios)
R = sum(ratios)
S = sum(lst)
for _ in range(M):
# get a new random permutation
random.shuffle(lst)
partition = []
# starting index (in the permutation) of the current sublist
lo = 0
# permutation partial sum
s = 0
# index of sublist we are currently generating (i.e. what ratio we are on)
j = 0
# ratio partial sum
rs = ratios[j]
for i in range(N):
s += lst[i]
# if ratio of permutation partial sum exceeds ratio of ratio partial sum,
# the current sublist is "complete"
if s / S >= rs / R:
partition.append(lst[lo:i + 1])
# start creating new sublist from next element
lo = i + 1
j += 1
if j == P:
# done with partition
# remaining elements will always all be zeroes
# (i.e. assert should never fail)
assert all(x == 0 for x in lst[i+1:])
partition[-1].extend(lst[i+1:])
break
rs += ratios[j]
Note that the outer loop can be redesigned to loop indefinitely until num_gen good partitions are generated (rather than just looping num_gen times) for more robustness. This algorithm is expected to produce M good partitions in O(M) iterations (provided random.shuffle is sufficiently random) if the number of good partitions is not too small compared to the total number of partitions of the same size, so it should perform well for for most inputs. For an (almost) uniformly random list like [random.randrange(10) for _ in range(200)], every iteration produces a good partition with eps = 0.05 as is evident by running the example below. Of course, how well the algorithm performs will also depend on the definition of 'good' -- the stricter the closeness requirement (in other words, the smaller the epsilon), the more iterations it will take to find a good partition. This implementation can be found here, and will work for any input (assuming random.shuffle eventually produces all permutations of the input list).
You can find a runnable version of the code (with asserts to test how "good" the partitions are) here.
Can anyone please help me with the above question.
We have to find combination of elements in the array (a1,a2),(a1,a3),(a1,a4).... so on, and pick those combinations which satisfies the condition (ai*aj) <= max(A) where A is the array and return the number of combinations possible.
Example : input array A = [1,1,2,4,2] and it returns 8 as the combinations are :
(1,1),(1,2),(1,4),(1,2),(1,2),(1,4),(1,2),(2,2).
It's easy to solve this using nested for loops but that would be very time consuming.(O(n^2)).
Naive algorithm:
array = [1,1,2,4,2]
result = []
for i in range(len(array)):
for j in range(len(array)):
if array[i] * array[j] <= max(array):
if (array[j],array[i]) not in result:
result.append((array[i],array[j]))
print(len(result))
What should be the approach when we encounter such problems ?
What I understand reading your problem description is that you want to find the total count of pairs whose multiplication is less than the maximum element in the range between these pairs i.e. ai*aj <= max(ai,ai+1,…aj).
The naive approach suggested by Thomas is easy to understand but it is still of time complexity of O(n^2)). We can optimize this to reduce the time complexity to O(n*log^2n). Lets discuss about it in details.
At first, from each index- i, we can find out the range say {l, r} in which element at index- i will be greater than or equal to all the elements from l to i , as well as it is greater than all elements ranges from i + 1 to r. This can be easily calculated in O(n) time complexity using the idea of histogram data-structure.
Now, lets for each index-i, we find out such range {l, r}, and if we want to traverse over minimum length out of two ranges i.e. min( i - l, r - i ) then overall we will traverse n*logn indices for overall array. While traversing over small length range, if we encounter some element say x, then we somehow have to find out how many elements exists in other range such that values are less than ai / x. This can be solved using offline processing with Fenwick Tree data-structure in O(logn) time complexity for each query. Hence, we can solve above problem with overall complexity of O(n log^2 n) time complexity.
What about sorting the array, then iterating up: for each element, e, binary search the closest element to floor(max(A) / e) that's lower than or equal to e. Add the number of elements to the left of that index. (If there are many duplicates, hash their count, present only two of them in the sorted array, and use prefix sums to return the correct number of items to the left of any index.)
1 1 2 4 2
1 1 2 2 4
0
1
2
3
2
It's easy to solve this using nested for loops but that would be very time consuming.(O(n^2)).
since where i < j we can cut this in half:
for i in range(len(array)):
for j in range(i+1, len(array)):
...
Now let's get rid of this part if (array[j],array[i]) not in result: as it does not reflect your results: (1,1),(1,2),(1,4),(1,2),(1,2),(1,4),(1,2),(2,2) here you have dupes.
The next expensive step we can get rid of is max(array) wich is not only wrong (max(ai,ai+1,…aj) translates to max(array[i:j])) but has to iterate over a whole section of the array in each iteration. Since the Array doesn't change, the only thing that may change this maximum is array[j], the new value you're processing.
Let's store that in a variable:
array = [1,1,2,4,2]
result = []
for i in range(len(array)):
maxValue = array[i]
for j in range(i+1, len(array)):
if array[j] > maxValue:
maxValue = array[j]
if array[i] * array[j] <= maxValue:
result.append((array[i], array[j]))
print(len(result))
Still a naive algorithm, but imo we've made some improvements.
Another thing we could make is not only store the maxValue, but also a pivot = maxValue / array[i] and therefore replace the multiplication by a simple comparison if array[j] <= pivot:. Doing this under the assumption that the multiplication would have been called way more often than the maxValue and therefore the pivot changes.
But since I'm not very experienced in python, I'm not sure wether this would make any difference in python or wether I'm on the road to pointless micro-optimizations with this.
I have a question about the runtime complexity of the standard permutation finding algorithm. Consider a list A, find (and print) all permutations of its elements.
Here's my recursive implementation, where printperm() prints every permutation:
def printperm(A, p):
if len(A) == len(p):
print("".join(p))
return
for i in range(0, len(A)):
if A[i] != 0:
tmp = A[i] # remember ith position
A[i] = 0 # mark character i as used
p.append(tmp) # select character i for this permutation
printperm(A, p) # Solve subproblem, which is smaller because we marked a character in this subproblem as smaller
p.pop() # done with selecting character i for this permutation
A[i] = tmp # restore character i in preparation for selecting the next available character
printperm(['a', 'b', 'c', 'd'], [])
The runtime complexity appears to be O(n!) where n is the size of A. This is because at each recursion level, the amount of work decreases by 1. So, the top recursion level is n amount of work, the next level is n-1, and the next level is n-2, and so on. So the total complexity is n*(n-1)*(n-2)...=n!
Now the problem is the print("".join(p)) statement. Every time this line runs, it iterates through the list, which iterates through the entire list, which is complexity n. There are n! number of permutations of a list of size n. So that means the amount of work done by the print("".join(p)) statement is n!*n.
Does the presence of the print("".join(p)) statement then increases the runtime complexity to O(n * n!)?? But this doesn't seem right, because I'm not running the print statement on every recursion call. Where does my logic for getting O(n * n!) break down?
You're basically right! The possible confusion comes in your "... and the next level is n-2, and so on". "And so on" is glossing over that at the very bottom level of the recursion, you're not doing O(1) work, but rather O(n) work to do the print. So the total complexity is proportional to
n * (n-1) * (n-2) ... * 2 * n
which equals n! * n. Note that the .join() doesn't really matter to this. It would also take O(n) work to simply print(p).
EDIT: But that's not really right, for a different reason. At all levels above the print level, you're doing
for i in range(0, len(A)):
and len(A) doesn't change. So every level is doing O(n) work. To be sure, the deeper the level the more zeroes there are in A, and so the less work the loop does, but it's nevertheless still O(n) merely to iterate over range(n) at all.
Permutations can be generated from combinations using divide and conquer technique to achieve better time complexity. However, generated output is not in order.
Suppose we want to generate permutations for n=8 {0,1,2,3,4,5,6,7}.
Below are instances of permutations
01234567
01234576
03215654
30126754
Note first 4 items in each arrangement are of same set {0,1,2,3} and last 4 items are of same set {4,5,6,7}
This means given set A={0,1,2,3} B={4,5,6,7} permutations from A are 4! and from B are 4!.
It follows that taking one instance of permutations from A and one instance of permutations from B we get a correct permutation instance from universal set {0,1,2,3,4,5,6,7}.
Total permutations given sets A and B such that A union B and A equals universal set and B intersection is null set gives universal set are 2*A!*B! (By swapping A and B order
In this case we get 2*4!*4! = 1156.
So to generate all permutations when n=8, we simply need to generate possible combinations of sets A and B that satisfy conditions explained earlier.
Generating combinations of 8 elements taking 4 at time in lexicographical order is pretty fast. We need to generate the first half combinations each assign to respective set A and set B is found using set operation.
For each pair A and B we apply divide and conquer algorithm.
A and B doesn't need to be of equal sizes and the trick can be applied recursive for higher values of n.
In stead of using 8! = 40320 steps
We can use 8!/(2*4!4!)(4!+4!+1) = 1750 steps with this method. I ignore the cross product operation when joining set A and set B permutations because its straight forward operation.
In real implementation this method runs approximately 20 times faster than some naive algorithm and can take advantage of parallelism. For example different pairs of set A and B can be grouped and process on different threads.
Someone posted this question here a few weeks ago, but it looked awfully like homework without prior research, and the OP promptly removed it after getting a few downvotes.
The question itself was rather interesting though, and I've been thinking about it for a week without finding a satisfying solution. Hopefully someone can help?
The question is as follows: given a list of N integer intervals, whose bounds can take any values from 0 to N³, find the smallest integer i such that i does not belong to any of the input intervals.
For example, if given the list [3,5] [2,8] [0,3] [10,13] (N = 4) , the algorithm should return 9.
The simplest solution that I can think of runs in O(n log(n)), and consists of three steps:
Sort the intervals by increasing lower bound
If the smallest lower bound is > 0, return 0;
Otherwise repeatedly merge the first interval with the second, until the first interval (say [a, b]) does not touch the second (say [c, d]) — that is, until b + 1 < c, or until there is only one interval.
Return b + 1
This simple solution runs in O(n log(n)), but the original poster wrote that the algorithm should run in O(n). That's trivial if the intervals are already sorted, but the example that the OP gave included unsorted intervals. I guess it must have something to do with the N³ bound, but I'm not sure what... Hashing? Linear time sorting? Ideas are welcome.
Here is a rough python implementation for the algorithm described above:
def merge(first, second):
(a, b), (c, d) = first, second
if c <= b + 1:
return (a, max(b, d))
else:
return False
def smallest_available_integer(intervals):
# Sort in reverse order so that push/pop operations are fast
intervals.sort(reverse = True)
if (intervals == [] or intervals[-1][0] > 0):
return 0
while len(intervals) > 1:
first = intervals.pop()
second = intervals.pop()
merged = merge(first, second)
if merged:
print("Merged", first, "with", second, " -> ", merged)
intervals.append(merged)
else:
print(first, "cannot be merged with", second)
return first[1] + 1
print(smallest_available_integer([(3,5), (2,8), (0,3), (10,13)]))
Output:
Merged (0, 3) with (2, 8) -> (0, 8)
Merged (0, 8) with (3, 5) -> (0, 8)
(0, 8) cannot be merged with (10, 13)
9
Elaborating on #mrip's comment: you can do this in O(n) time by using the exact algorithm you've described but changing how the sorting algorithm works.
Typically, radix sort uses base 2: the elements are divvied into two different buckets based on whether their bits are 0 or 1. Each round of radix sort takes time O(n), and there is one round per bit of the largest number. Calling that largest nunber U, this means the time complexity is O(n log U).
However, you can change the base of the radix sort to other bases. Using base b, each round takes time O(n + b), since it takes time O(b) to initialize and iterate over the buckets and O(n) time to distribute elements into the buckets. There are then logb U rounds. This gives a runtime of O((n + b)logb U).
The trick here is that since the maximum number U = n3, you can set b = n and use a base-n radix sort. The number of rounds is now logn U = logn n3 = 3 and each round takes O(n) time, so the total work to sort the numbers is O(n). More generally, you can sort numbers in the range [0, nk) in time O(kn) for any k. If k is a fixed constant, this is O(n) time.
Combined with your original algorithm, this solves the problem in time O(n).
Hope this helps!
Another idea would be use the complement of these intervals somehow. Suppose C() gives you the complement for an interval, for example C([3,5]) would be the integer numbers smaller than 3 and those larger than 5. If the maximum number is N^3, then using modulo N^3+1 you could even represent this as another interval [6,(N^3+1)+2].
If you want a number that does not belong to any of the original intervals, this same number should be present in all of the complements of these intervals. It then comes down to writing a function that can calculate the intersection of any two such 'complement intervals'.
I haven't made an implementation of this idea, since my pen and paper drawings indicated that there were more cases to consider when calculating such an intersection than I first imagined. But I think the idea behind this is valid, and it would result in an O(n) algorithm.
EDIT
On further thought, there is a worst case scenario that makes things more complex than I originally imagined.