Space complexity of "Number of islands" BFS solution on Leetcode - algorithm

For the Leetcode question "Number of islands," I am trying to determine the SPACE complexity for the Breadth First Search solution.
from collections import deque
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
num_islands = 0
num_row, num_col = len(grid), len(grid[0])
for r in range(num_row):
for c in range(num_col):
if grid[r][c] == '1':
num_islands += 1
self.bfs(r, c, grid)
return num_islands
def bfs(
self,
row: int,
col: int,
grid: List[List[str]]
) -> None:
queue = deque([(row, col)])
while queue:
r, c = queue.popleft()
if grid[r][c] != '1':
continue
grid[r][c] = 'X'
if r+1 <= len(grid)-1 and grid[r+1][c] == '1':
queue.append((r+1, c))
if r-1 >= 0 and grid[r-1][c] == '1':
queue.append((r-1, c))
if c+1 <= len(grid[0])-1 and grid[r][c+1] == '1':
queue.append((r, c+1))
if c-1 >= 0 and grid[r][c-1] == '1':
queue.append((r, c-1))
I am looking at this SO answer, but that example doesn't really elaborate on how exactly worst case BFS space complexity = O(M · N), where M = # of rows and N = number of columns. It states that the queue will have all of the leaf nodes, but how will that be? Also, how are all of the leaf nodes proportional to M · N? I think that diagram makes it hard to conceptualize.
I worked out a few examples, and I definitely see some cases where BFS space complexity > O(Max(M, N)), but can't grasp how it can be O(M·N) worst case. Can someone please explain, preferably with a visual walkthrough?
Example with space complexity = O(Min(M, N)) if you start at top-left corner, but > O(Max(M, N)) if you start in the middle:
[
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1]
]
A visual example like the one above will be easier to digest.

It's not so easy to arrange, but the worst case BFS queue size for an MxM matrix is indeed in Ω(M2)
Lets say we have an MxM block, with M odd, where a BFS starting in the center of one side can reach W cells at the same time, implying a queue size of W.
It's not hard to arrange these into a 2M+3 x 2M+3 block that starts in the center of one side and reaches 4W cells at the same time, like this:
### ###
### ###
#S# #S#
# #
#######
# # #
#S# # #S#
### # ###
### S ###
We can then repeat the procedure with these new blocks, and then repeat again, etc., as long as we like.
As these blocks get bigger and bigger, the extra 3 rows and columns take up a smaller and smaller proportion of the total. In terms of the number of repetitions of this procedure n, the length of the side M is in O(2n),
The maximum BFS queue size, of course is in Ω(4n), which is Ω(M2).
Here's what it looks like if you repeat the procedure a bunch of times. A BFS starting at the top will find all the other end points at the same level:

Related

Calculating the numbers whose binary representation has exactly required number 1's

Okay so the problem is finding a positive integer n such that there are exactly m numbers in n+1 to 2n (both inclusive) whose binary representation has exactly k 1s.
Constraints: m<=10^18 and k<=64. Also answer is less than 10^18.
Now I can't think of an efficient way of solving this instead of going through each integer and calculating the binary 1 count in the required interval for each of them, but that would take too long. So is there any other way to go about with this?
You're correct to suspect that there's a more efficient way.
Let's start with a slightly simpler subproblem. Absent some really clever
insights, we're going to need to be able to find the number of integers in
[n+1, 2n] that have exactly k bits set in their binary representation. To
keep things short, let's call such integers "weight-k" integers (for motivation for this terminology, look up Hamming weight). We can
immediately simplify our counting problem: if we can count all weight-k integers in [0, 2n]
and we can count all weight-k integers in [0, n], we can subtract one count
from the other to get the number of weight-k integers in [n+1, 2n].
So an obvious subproblem is to count how many weight-k integers there are
in the interval [0, n], for given nonnegative integers k and n.
A standard technique for a problem of this kind is to look for a way to break
it down into smaller subproblems of the same kind; this is one aspect of
what's often called dynamic programming. In this case, there's an easy way of
doing so: consider the even numbers in [0, n] and the odd numbers in [0, n]
separately. Every even number m in [0, n] has exactly the same weight as
m/2 (because by dividing by two, all we do is remove a single zero
bit). Similarly, every odd number m has weight exactly one more than the
weight of (m-1)/2. With some thought about the appropriate base cases, this
leads to the following recursive algorithm (in this case implemented in Python,
but it should translate easily to any other mainstream language).
def count_weights(n, k):
"""
Return number of weight-k integers in [0, n] (for n >= 0, k >= 0)
"""
if k == 0:
return 1 # 0 is the only weight-0 value
elif n == 0:
return 0 # only considering 0, which doesn't have positive weight
else:
from_even = count_weights(n//2, k)
from_odd = count_weights((n-1)//2, k-1)
return from_even + from_odd
There's plenty of scope for mistakes here, so let's test our fancy recursive
algorithm against something less efficient but more direct (and, I hope, more
obviously correct):
def weight(n):
"""
Number of 1 bits in the binary representation of n (for n >= 0).
"""
return bin(n).count('1')
def count_weights_slow(n, k):
"""
Return number of weight-k integers in [0, n] (for n >= 0, k >= 0)
"""
return sum(weight(m) == k for m in range(n+1))
The results of comparing the two algorithms look convincing:
>>> count_weights(100, 5)
11
>>> count_weights_slow(100, 5)
11
>>> all(count_weights(n, k) == count_weights_slow(n, k)
... for n in range(1000) for k in range(10))
True
However, our supposedly fast count_weights function doesn't scale well to the
size numbers you need:
>>> count_weights(2**64, 5) # takes a few seconds on my machine
7624512
>>> count_weights(2**64, 6) # minutes ...
74974368
>>> count_weights(2**64, 10) # gave up waiting ...
But here's where a second key idea of dynamic programming comes in: memoize!
That is, keep a record of the results of previous calls, in case we need to use
them again. It turns out that the chain of recursive calls made will tend to
repeat lots of calls, so there's value in memoizing. In Python, this is
trivially easy to do, via the functools.lru_cache decorator. Here's our new
version of count_weights. All that's changed is the extra line at the top:
#lru_cache(maxsize=None)
def count_weights(n, k):
"""
Return number of weight-k integers in [0, n] (for n >= 0, k >= 0)
"""
if k == 0:
return 1 # 0 is the only weight-0 value
elif n == 0:
return 0 # only considering 0, which doesn't have positive weight
else:
from_even = count_weights(n//2, k)
from_odd = count_weights((n-1)//2, k-1)
return from_even + from_odd
Now testing on those larger examples again, we get results much more quickly,
without any noticeable delay.
>>> count_weights(2**64, 10)
151473214816
>>> count_weights(2**64, 32)
1832624140942590534
>>> count_weights(5853459801720308837, 27)
356506415596813420
So now we have an efficient way to count, we've got an inverse problem to
solve: given k and m, find an n such that count_weights(2*n, k) -
count_weights(n, k) == m. This one turns out to be especially easy, since the
quantity count_weights(2*n, k) - count_weights(n, k) is monotonically
increasing with n (for fixed k), and more specifically increases by either
0 or 1 every time n increases by 1. I'll leave the proofs of those
facts to you, but here's a demo:
>>> for n in range(10, 30): print(n, count_weights(n, 3))
...
10 1
11 2
12 2
13 3
14 4
15 4
16 4
17 4
18 4
19 5
20 5
21 6
22 7
23 7
24 7
25 8
26 9
27 9
28 10
29 10
This means that we're guaranteed to be able to find a solution. There may be multiple solutions, so we'll aim to find the smallest one (though it would be equally easy to find the largest one). Bisection search gives us a crude but effective way to do this. Here's the code:
def solve(m, k):
"""
Find the smallest n >= 0 such that [n+1, 2n] contains exactly
m weight-k integers.
Assumes that m >= 1 (for m = 0, the answer is trivially n = 0).
"""
def big_enough(n):
"""
Target function for our bisection search solver.
"""
diff = count_weights(2*n, k) - count_weights(n, k)
return diff >= m
low = 0
assert not big_enough(low)
# Initial phase: expand interval to identify an upper bound.
high = 1
while not big_enough(high):
high *= 2
# Bisection phase.
# Loop invariant: big_enough(high) is True and big_enough(low) is False
while high - low > 1:
mid = (high + low) // 2
if big_enough(mid):
high = mid
else:
low = mid
return high
Testing the solution:
>>> n = solve(5853459801720308837, 27)
>>> n
407324170440003813446
Let's double check that n:
>>> count_weights(2*n, 27) - count_weights(n, 27)
5853459801720308837
Looks good. And if we got our search right, this should be the smallest
n that works:
>>> count_weights(2*(n-1), 27) - count_weights(n-1, 27)
5853459801720308836
There are plenty of other opportunities for optimizations and cleanups in the
above code, and other ways to tackle the problem, but I hope this gives you a
starting point.
The OP commented that they needed to do this in C, where memoization isn't immediately available without using an external library. Here's a variant of count_weights that doesn't need memoization. It's achieved by (a) tweaking the recursion in count_weights so that the same n is used in both recursive calls, and then (b) returning, for a given n, the values of count_weights(n, k) for all k for which the answer is nonzero. In effect, we're just moving the memoization into an explicit list.
Note: as written, the code below needs Python 3.
def count_all_weights(n):
"""
Return frequencies of weights of all integers in [0, n],
as a list. The kth entry in the list gives the count
of weight-k integers in [0, n].
Example
-------
>>> count_all_weights(16)
[1, 5, 6, 4, 1]
"""
if n == 0:
return [1]
else:
wm = count_all_weights((n-1)//2)
weights = [wm[0], *(wm[i]+wm[i+1] for i in range(len(wm)-1)), wm[-1]]
if n % 2 == 0:
weights[bin(n).count('1')] += 1
return weights
An example call:
>>> count_all_weights(7590)
[1, 13, 78, 286, 714, 1278, 1679, 1624, 1139, 559, 182, 35, 3]
This function should be good enough even for larger n: count_all_weights(10**18) takes less than a half a millisecond on my machine.
Now the bisection search will work as before, replacing the call to count_weights(n, k) with count_all_weights(n)[k] (and similarly for count_weights(2*n, k)).
Finally, another possibility is to break up the interval [0, n] into a succession of smaller and smaller subintervals, where each subinterval has length a power of two. For example, we'd break the interval [0, 101] into [0, 63], [64, 95], [96, 99] and [100, 101]. The advantage of this is that we can easily compute how many weight-k integers there are in any one of these subintervals by counting combinations. For example, in [0, 63] we have all possible 6-bit combinations, so if we're after weight-3 integers, we know there must be exactly 6-choose-3 (i.e., 20) of them. And in [64, 95], we know each integer starts with a 1-bit, and then after excluding that 1-bit we have all possible 5-bit combinations, so again we know how many integers there are in this interval with any given weight.
Applying this idea, here's a complete, fast, all-in-one function that solves your original problem. It has no recursion and no memoization.
def solve(m, k):
"""
Given nonnegative integers m and k, find the smallest
nonnegative integer n such that the closed interval
[n+1, 2*n] contains exactly m weight-k integers.
Note that for k small there may be no solution:
if k == 0 then we have no solution unless m == 0,
and if k == 1 we have no solution unless m is 0 or 1.
"""
# Deal with edge cases.
if k < 2 and k < m:
raise ValueError("No solution")
elif k == 0 or m == 0:
return 0
k -= 1
# Find upper bound on n, and generate a subset of
# Pascal's triangle as we go.
rows = []
high, row = 1, [1] + [0] * k
while row[k] < m:
rows.append((high, row))
high, row = high * 2, [1, *(row[i]+row[i+1] for i in range(k))]
# Bisect to find first n that works.
low = mlow = weight = 0
while rows:
high, row = rows.pop()
mmid = mlow + row[k - weight]
if mmid < m:
low, mlow, weight = low + high, mmid, weight + 1
return low + 1

choose k from n with a probability

I've a list of n elements (e_i). For each i, e_i have a probability p_i to be selected.
I want to write an algorithm to pick k elements from theses n, but I have to respect the probabilities of each element when I choose them. I've no idea how to do that, I didn't know any algorithm which do that :/
Can you direct my reflection?
Let's say you have 3 possible values: A, B, C and:
P(A) = 0.2, P(B) = 0.3, P(C) = 0.5. Then you will put the cumulative probabilities in an array p = [0.2, 0.5, 1]. In each pick you will generate a random number in the range [0, 1] (using the built in library of the language you use). Based on that number, you will return as a response the smallest number which is greater or equal to the randomly generated number (actually the class which corresponds to that number A, B or C).
Hint: that class can be obtained in O(logN) time, if the optimal approach is used.
Here is an example:
if you generate value of 0.4, then you will return B, because 0.5 is the smallest number >= 0.4. If you generate 0.01 you will return A.
That's the idea, I'll let you try implement that. If you need more help, I could write some (pseudo)code too.
Assuming that you want k distinct elements, you could do the following: keep track of the total remaining probability of the non-selected elements. Repeatedly (k times) pick a random number, r, in the range [0,remaining]. Scan over the probabilities, accumulating the probabilities until the sum exceeds r. Pick the corresponding element. Then -- reduce remaining by this probability and then zero the probability of that element so that it won't be picked again.
Here is a Python implementation:
from random import random
def choose(probs,k):
choices = []
remaining = 1
p = probs[:] #create a local copy
for i in range(k):
r = remaining * random()
i = 0
s = p[i]
while s < r:
i += 1
s += p[i]
choices.append(i)
remaining -= p[i]
p[i] = 0 #so won't be chosen again
return choices
#test:
dist = [0.2, 0.4, 0.1, 0.1, 0.1, 0.05, 0.05]
for i in range(10):
print(choose(dist,4))
Typical output:
[2, 5, 1, 3]
[1, 0, 6, 4]
[0, 4, 1, 6]
[1, 2, 3, 0]
[1, 5, 2, 4]
[3, 1, 0, 2]
[1, 2, 0, 4]
[1, 2, 0, 4]
[2, 5, 1, 4]
[1, 2, 0, 3]
Note how 0 and 1 are frequently chosen but 5 and 6 are comparatively rare.
As an implementation detail: the above algorithm should always work in principle, but it is possible that round-off error and a value of r which is extremely close to remaining could lead to a subscript out of range error. For some use cases this should be so rare that you need not worry about it, but you could add error-trapping to e.g. pick the element with the last non-zero probability in the cases that the sum of all non-zero probabilities rounds to just below remaining and the r chosen happens to fall in that narrow gap.
So element ix can be expressed as (e_ix, p_ix), as those are its two components. You apparently already know what values to fill in for all of these. I am going to come up with an example though, so I can show you how to do this without doing it for you:
(A, 1) (B, 2) (C, 3)
What you need to do is assign each value to a range. I'll do it an easy way and just go left to right, starting at zero.
So, we need 1 slot for A, 2 for B, 3 for C. Our possible indices will be 0, 1, 2, 3, 4, and 5.
0->A
1->B
2->B
3->C
4->C
5->C
This is a basic example, and your weights might be floating point, but it should give you a start.
Edit: Floating point example
(D, 2) (E, .5123) (F, 1)
D < 2
2 <= E < 2.5123
2.5123 <= F < 3.5123
Necessary assumption
By linearity of the expectation, it is easy to show that if you pick elements among the n elements 0, 1, 2, ..., n-1 such that each element i has probability p_i of being selected, then the expectation of the number of picked elements is exactly sum p_i. This holds no matter the algorithm used to pick the elements.
You are looking for such an algorithm, but with the added constraint that the number of picked elements is always k. It follows that a necessary assumption is:
sum p_i = k
Fortunately, it turns out that this assumption is also sufficient.
Algorithm
Assume now that sum p_i = k. The following algorithm will select exactly k elements, such that each element i in 0,1,...,n-1 has probability p_i of being chosen.
Compute the cumulative sums:
c_0 = 0
c_1 = p_0
...
c_i = p_0 + p_1 + ... + p_(i-1)
...
c_n = k
Pick a number x uniformly at random in [0,1[
For every number y in the list x, 1+x, 2+x, 3+x, ..., k-1+x:
Choose element i such that c_i <= y < c_(i+1)
It is easy to verify that exactly k elements are chosen, and that every element i has probability p_i of being chosen.
Reference
The previous algorithm is the subject of a research paper from the 80s or 90s, which I can't put my hands on at this exact moment; I will edit this post with a reference if I can find it again.

Evenly pruning an ordered set

Given an ordered set of n elements (a1, a2, ..., an), what algorithm can I use to pick M of these elements (M < n) such that the sampling from the original set is as spread out as possible?
For example, if the starting set is the numbers from 1 to 9 (i.e. n=9), and I want to evenly sample it so I end up with only 5 of those numbers (i.e. M=9), I'd select 1, 3, 5, 7, 9. To get 3 of the original 9, I'd go with 1, 5, 9, and so on. But what would the pseudo-code for picking the elements look like for any n and M?
The mathematical formulation for this problem would be as follows: given M < n, find the set q(1), q(2), ..., q(M) such that 1 <= q(k) < q(k+1) <= M for any k:[1, M-1], and the sum of q(k+1)-q(k) for k: [1, M-1] is the maximum possible.
Following Nico's suggestion of maximizing the minimum difference (the form of the objective function is fairly flexible), there's a simple O(n^2 M)-time dynamic program that goes something like this.
def prune(a, M):
n = len(a)
# The [i][j] entry is the max min gap
# for a[0], ..., a[i] choose j + 1 (a[0] is always chosen).
table = [[float('inf')] * M for i in range(n)]
for i in range(1, n):
for j in range(M):
table[i][j] = max(min(table[k][j], a[i] - a[k])
for k in range(i))
# Trace back the sequence of argmaxes to recover the chosen indexes.
This can be improved to O(n M) time using total monotonicity. The idea is that, as i increases, the proper value for k can only increase too, and as soon as the objective decreases when we increase k, we can move on to the next i.
Both of the above algorithms handle fairly general objectives. If max is OK, then there's an O(n log n)-time algorithm that uses binary search to guess the minimum gap and then check whether it's feasible. I'll wait for you to update the question.

Permutation At A Railway Track(Stack Implementation)

Engines numbered 1, 2, ..., n are on the line at the left, and it is desired to rearrange(permute) the engines as they leave on the right-hand track. An engine that is on the spur track can be left there or sent on its way down the right track, but it can never be sent back to the incoming track. For example if n = 3, and we ha the engines numbered 1,2,3 on the left track, then 3 first goes to the spur track. We could then send 2 to the spur, then on the way to its right,then send 3 on the way, then 1, obtaining the new order 1,3,2.
We have to find all the possible permutations for specific n.
For n=1, answer = 1;
For n=2 answer = 2;
For n=3 answer = 5;
Didn't find any generality.
Using Stack Implementation would be very helpful.
But any solution is welcomed.
p.s. This is not a homework question, as I am a self taught person.
Here's my attempt at a recursive solution (see comments in Java code):
private static int result = 0;
private static int n = 3;
public static void g(int right,int spur){
if (right == n) // all trains are on the right track
result++;
else if (right + spur < n) // some trains are still on the left track
g(right,spur + 1); // a train moved from the left track to the spur
if (spur > 0) // at least one train is on the spur
g(right + 1,spur - 1); // a train moved from the spur to the right track
// (also counts trains moving directly from the left to right track)
}
public static void main (String[] args){
g(0,0);
System.out.println(result); // 5
}
The recursive solution above actually counts each possibility. For a combinatorial solution we consider all combinations of n movements on and out of the spur, where adjacent such movements are equivalent to movements directly from the left to right track. There are 2n choose n such combinations. Now let's count the invalid ones:
Consider all combinations of (n - 1) ins and (n + 1) outs of the spur. All these include a point, p, where a train is counted as leaving the spur when no trains are on it. Let's say that p has k ins and (k + 1) outs preceding it - then the number of remaining ins is (n - 1 - k); and remaining outs, (n + 1) - (k + 1) = (n - k).
Now reverse the ins and outs for each one of these combinations starting after p so that in becomes out and out in. Each one of the reversed sections has necessarily (n - k) ins and (n - 1 - k) outs. But now if we total the number of ins and outs before and after p we get k + (n - k) = n ins, and (k + 1) + (n - 1 - k) = n outs. We have just counted the number of combinations of n ins and n outs that are invalid. If we assume that one such combination may not have been counted, theoretically reverse that combination after its p and you will find a combination of (n - 1) ins and (n + 1) outs that was not counted. But by definition we counted them all already so our assumed extra combination could not exist.
Total valid combinations then are 2n choose n - 2n choose (n + 1), the Catalan numbers.
(Adapted from an explanation by Tom Davis here: http://mathcircle.berkeley.edu/BMC6/pdf0607/catalan.pdf)
First, note that you may ignore the possibility of moving the train from incoming directly to outgoing: such a move can be done by moving a train to the spur and then out again.
Denote a train move from incoming to spur as ( and a train move from spur to outgoing as ), and you get a bijection between permutations of the trains and strings of n pairs of correctly balanced parenthesis. That statement needs proving, but the only hard part of the proof is proving that no two strings of balanced parentheses correspond to the same permutation. The number of such strings is the n'th Catalan number, or choose(2n, n)/(n+1), where choose(n, k) is the number of ways of choosing k items from n.
Here's code to compute the solution:
def perms(n):
r = 1
for i in xrange(1, n+1):
r *= (n + i)
r //= i
return r // (n + 1)
You can generate all the permutations with this code, which also exposes the Catalan nature of the solution.
def perms(n, i=0):
if n == 0:
yield []
for k in xrange(n):
for s in perms(k, i+1):
for t in perms(n-k-1, i+k+1):
yield s + [i] + t
print list(perms(4))
Output:
[[0, 1, 2, 3], [0, 1, 3, 2], [0, 2, 1, 3], [0, 2, 3, 1],
[0, 3, 2, 1], [1, 0, 2, 3], [1, 0, 3, 2], [1, 2, 0, 3],
[2, 1, 0, 3], [1, 2, 3, 0], [1, 3, 2, 0], [2, 1, 3, 0],
[2, 3, 1, 0], [3, 2, 1, 0]]
The status of the system can be described by giving the 3 (ordered!) lists of engines, in the left, spur and right tracks. Given the status, it is possible to calculate all the possible moves. This creates a tree of possibilities: the root of the tree is the initial status, and every move corresponds to a branch which leads to a new status. The final status at the end of a branch (a leaf) is your final position in the right track.
So, you have to build and explore all the tree, and at the end you have to count all the leaves. And the tree is a common data structure.
Just to clarify, the tree in this case wouldn't replace the stacks. The stacks are used to store your data (the position of the engines); the tree is used to track the progress of the algorithm. Every time you have a status (a node of the tree) you have to analyse your data (=the content of the stacks) and find the possible moves. Every move is a branch in the tree of the algorithm, and it leads to a new status of the stacks (because the engine has moved). So basically you will have one "configuration" of the 3 stacks for each node of the tree.

Sum of continuous sequences

Given an array A with N elements, I want to find the sum of minimum elements in all the possible contiguous sub-sequences of A. I know if N is small we can look for all possible sub sequences but as N is upto 10^5 what can be best way to find this sum?
Example: Let N=3 and A[1,2,3] then ans is 10 as Possible contiguous sub sequences {(1),(2),(3),(1,2),(1,2,3),(2,3)} so Sum of minimum elements = 1 + 2 + 3 + 1 + 1 + 2 = 10
Let's fix one element(a[i]). We want to know the position of the rightmost element smaller than this one located to the left from i(L). We also need to know the position of the leftmost element smaller than this one located to the right from i(R).
If we know L and R, we should add (i - L) * (R - i) * a[i] to the answer.
It is possible to precompute L and R for all i in linear time using a stack. Pseudo code:
s = new Stack
L = new int[n]
fill(L, -1)
for i <- 0 ... n - 1:
while !s.isEmpty() && s.top().first > a[i]:
s.pop()
if !s.isEmpty():
L[i] = s.top().second
s.push(pair(a[i], i))
We can reverse the array and run the same algorithm to find R.
How to deal with equal elements? Let's assume that a[i] is a pair <a[i], i>. All elements are distinct now.
The time complexity is O(n).
Here is a full pseudo code(I assume that int can hold any integer value here, you should
choose a feasible type to avoid an overflow in a real code. I also assume that all elements are distinct):
int[] getLeftSmallerElementPositions(int[] a):
s = new Stack
L = new int[n]
fill(L, -1)
for i <- 0 ... n - 1:
while !s.isEmpty() && s.top().first > a[i]:
s.pop()
if !s.isEmpty():
L[i] = s.top().second
s.push(pair(a[i], i))
return L
int[] getRightSmallerElementPositions(int[] a):
R = getLeftSmallerElementPositions(reversed(a))
for i <- 0 ... n - 1:
R[i] = n - 1 - R[i]
return reversed(R)
int findSum(int[] a):
L = getLeftSmallerElementPositions(a)
R = getRightSmallerElementPositions(a)
int res = 0
for i <- 0 ... n - 1:
res += (i - L[i]) * (R[i] - i) * a[i]
return res
If the list is sorted, you can consider all subsets for size 1, then 2, then 3, to N. The algorithm is initially somewhat inefficient, but an optimized version is below. Here's some pseudocode.
let A = {1, 2, 3}
let total_sum = 0
for set_size <- 1 to N
total_sum += sum(A[1:N-(set_size-1)])
First, sets with one element:{{1}, {2}, {3}}: sum each of the elements.
Then, sets of two element {{1, 2}, {2, 3}}: sum each element but the last.
Then, sets of three elements {{1, 2, 3}}: sum each element but the last two.
But this algorithm is inefficient. To optimize to O(n), multiply each ith element by N-i and sum (indexing from zero here). The intuition is that the first element is the minimum of N sets, the second element is the minimum of N-1 sets, etc.
I know it's not a python question, but sometimes code helps:
A = [1, 2, 3]
# This is [3, 2, 1]
scale = range(len(A), 0, -1)
# Take the element-wise product of the vectors, and sum
sum(a*b for (a,b) in zip(A, scale))
# Or just use the dot product
np.dot(A, scale)

Resources