Sum Of Two Squares: Where's My Error? - algorithm

I'm trying to calculate the number of ways to write a natural number as the sum of two squares. I'm working from the definition:
So, here is my code. Below where I test it, I find what I think is an error in the result.
sumOfSquares :: Integer -> Int
sumOfSquares k = 4 * (d1 - d3)
where
divs = divisors k
d1 = congruents d1_test divs
d3 = congruents d3_test divs
d1_test n = (n - 1) `mod` 4 == 0
d3_test n = (n - 3) `mod` 4 == 0
congruents :: (Integer -> Bool) -> [Integer] -> Int
congruents f divs = length $ filter f divs
divisors :: Integer -> [Integer]
divisors k = divisors' 2 k
where
divisors' n k' | n*n > k' = [k']
| n*n == k' = [n, k']
| k' `mod` n == 0 = (n:(k' `div` n):result)
| otherwise = result
where result = divisors' (n+1) k'
And when I run it, it generates:
*Main Numbers.SumOfSquares> sumOfSquares 10
4
I calculated that there is only one way to express 10 as a sum of two squares
1^2 + 3^2. Note that the intermediate result (d1 - d3) equals 1.
I'm missing something important but don't know what.

I think you misread the semantics of the formula. The Wikipedia article states the following equation:
There are two important remarks here:
the domain is Z, not N, therefore (-1), (-3), 0, etc. are also valid elements for the squares; and
we count the number of tuples, not sets so the order is important (and (1,2,2) is not equal to (1,2)): if (1,3) is a solution, so is (3,1) and we count these as two separate ones.
Now 10 has the following divisors: 1, 2, 5, 10 (your program forgot about 1 and 10). Two are congruent with 1 modulo 4: 1 and 5. Furthermore there are no divisors congruent with 3 modulo 4. So d1 = 2 and d3 = 0. Therefore there are eight (4×(2-0) = 8) possibilities:
(1,3): 12+32=10
(3,1): 32+12=10
(1,-3): 12+(-3)2=10
(3,-1): 32+(-1)2=10
(-1,3): (-1)2+32=10
(-3,1): (-3)2+12=10
(-1,-3): (-1)2+(-3)2=10
(-3,-1): (-3)2+(-1)2=10
Now we only have to resolve the issue with your program. You simply need to start counting from 1 instead of 2:
divisors :: Integer -> [Integer]
divisors k = divisors' 1
where
divisors' i | i2 > k = []
| i2 == k = [i]
| k `mod` i == 0 = (i:(k `div` i):result)
| otherwise = result
where i2 = i*i
result = divisors' (i+1)
I also simplified the program a bit and solved some other semantical errors. Now it should at least be sound with rk(n).

Related

Count number of subsequences of A such that every element of the subsequence is divisible by its index (starts from 1)

B is a subsequence of A if and only if we can turn A to B by removing zero or more element(s).
A = [1,2,3,4]
B = [1,4] is a subsequence of A.(Just remove 2 and 4).
B = [4,1] is not a subsequence of A.
Count all subsequences of A that satisfy this condition : A[i]%i = 0
Note that i starts from 1 not 0.
Example :
Input :
5
2 2 1 22 14
Output:
13
All of these 13 subsequences satisfy B[i]%i = 0 condition.
{2},{2,2},{2,22},{2,14},{2},{2,22},{2,14},{1},{1,22},{1,14},{22},{22,14},{14}
My attempt :
The only solution that I could came up with has O(n^2) complexity.
Assuming the maximum element in A is C, the following is an algorithm with time complexity O(n * sqrt(C)):
For every element x in A, find all divisors of x.
For every i from 1 to n, find every j such that A[j] is a multiple of i, using the result of step 1.
For every i from 1 to n and j such that A[j] is a multiple of i (using the result of step 2), find the number of B that has i elements and the last element is A[j] (dynamic programming).
def find_factors(x):
"""Returns all factors of x"""
for i in range(1, int(x ** 0.5) + 1):
if x % i == 0:
yield i
if i != x // i:
yield x // i
def solve(a):
"""Returns the answer for a"""
n = len(a)
# b[i] contains every j such that a[j] is a multiple of i+1.
b = [[] for i in range(n)]
for i, x in enumerate(a):
for factor in find_factors(x):
if factor <= n:
b[factor - 1].append(i)
# There are dp[i][j] sub arrays of A of length (i+1) ending at b[i][j]
dp = [[] for i in range(n)]
dp[0] = [1] * n
for i in range(1, n):
k = x = 0
for j in b[i]:
while k < len(b[i - 1]) and b[i - 1][k] < j:
x += dp[i - 1][k]
k += 1
dp[i].append(x)
return sum(sum(dpi) for dpi in dp)
For every divisor d of A[i], where d is greater than 1 and at most i+1, A[i] can be the dth element of the number of subsequences already counted for d-1.
JavaScript code:
function getDivisors(n, max){
let m = 1;
const left = [];
const right = [];
while (m*m <= n && m <= max){
if (n % m == 0){
left.push(m);
const l = n / m;
if (l != m && l <= max)
right.push(l);
}
m += 1;
}
return right.concat(left.reverse());
}
function f(A){
const dp = [1, ...new Array(A.length).fill(0)];
let result = 0;
for (let i=0; i<A.length; i++){
for (d of getDivisors(A[i], i+1)){
result += dp[d-1];
dp[d] += dp[d-1];
}
}
return result;
}
var A = [2, 2, 1, 22, 14];
console.log(JSON.stringify(A));
console.log(f(A));
I believe that for the general case we can't provably find an algorithm with complexity less than O(n^2).
First, an intuitive explanation:
Let's indicate the elements of the array by a1, a2, a3, ..., a_n.
If the element a1 appears in a subarray, it must be element no. 1.
If the element a2 appears in a subarray, it can be element no. 1 or 2.
If the element a3 appears in a subarray, it can be element no. 1, 2 or 3.
...
If the element a_n appears in a subarray, it can be element no. 1, 2, 3, ..., n.
So to take all the possibilities into account, we have to perform the following tests:
Check if a1 is divisible by 1 (trivial, of course)
Check if a2 is divisible by 1 or 2
Check if a3 is divisible by 1, 2 or 3
...
Check if a_n is divisible by 1, 2, 3, ..., n
All in all we have to perform 1+ 2 + 3 + ... + n = n(n - 1) / 2 tests, which gives a complexity of O(n^2).
Note that the above is somewhat inaccurate, because not all the tests are strictly necessary. For example, if a_i is divisible by 2 and 3 then it must be divisible by 6. Nevertheless, I think this gives a good intuition.
Now for a more formal argument:
Define an array like so:
a1 = 1
a2 = 1× 2
a3 = 1× 2 × 3
...
a_n = 1 × 2 × 3 × ... × n
By the definition, every subarray is valid.
Now let (m, p) be such that m <= n and p <= n and change a_mtoa_m / p`. We can now choose one of two paths:
If we restrict p to be prime, then each tuple (m, p) represents a mandatory test, because the corresponding change in the value of a_m changes the number of valid subarrays. But that requires prime factorization of each number between 1 and n. By the known methods, I don't think we can get here a complexity less than O(n^2).
If we omit the above restriction, then we clearly perform n(n - 1) / 2 tests, which gives a complexity of O(n^2).

Can I ignore the last k while expanding (a + b) % k?

Today I was trying to solve a problem that involved modular arithmetic. I was not able to solve it. So I looked it up on Geeks for Geeks
The above image shows what the author did. I know modular addition for two numbers
(a + b) % m = (a % m + b % m) % m
This works for any positive values of a and b
When I consider the equation the author used in the image.
a % k + b % k = 0
I substituted some random values for a , b and k to see if it really works. It turns out it fails for the input values a = 2, b = 5 and k = 7.
2 % 7 + 5 % 7 = 7 ≠ 0
When I considered the last equation. It worked.
b % k = (k - a % k) % k
(5 % 7) = (7 - 2 % 7) % 7
5 % 7 = 5 % 7
(a + b) % k = c
When I solved the above equation with the same idea as the author, I got
(a + b) % k = c
a % k + b % k = c
b % k = (c - a % k + k) % k
It works for any positive values of a, b, c and k
In the equation,
(a + b) % k = (a % k + b % k) % k
Can I just ignore the last k and proceed while expanding (a + b) % k ?. I wonder how the absence of the last k doesn't affect the final result
No, a = b = 0 is a counterexample.
Indeed, the final formula is incorrect, assuming that % denotes the remainder of truncating division. Let a = 1 and b = -1. (In Python, or for nonnegative integers, it's OK.)
This is why mathematicians prefer to deal in equivalence mod K, which avoids the issue of where to put the mod operator.

Can someone explain to me the runtime of why Perfect Squares is O(sqrt(n))?

Problem
Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 16, ...) which sum to n.
Example 1:
Input: n = 12
Output: 3
Explanation: 12 = 4 + 4 + 4
Example 2:
Input: n = 13
Output: 2
Explanation: 13 = 4 + 9.
Suggested Solution (BFS)
def numSquares(self, n):
if n < 2:
return n
lst = []
i = 1
while i * i <= n:
lst.append( i * i )
i += 1
cnt = 0
toCheck = {n}
while toCheck:
cnt += 1
temp = set()
for x in toCheck:
for y in lst:
if x == y:
return cnt
if x < y:
break
temp.add(x-y)
toCheck = temp
return cnt
How does this particular BFS run in O(sqrt(n))? Because what I am thinking is finding squares take O(sqrt(n)). Because there are 2 for loops, (for y in lst1 takes O(sqrt(n)), for x in toCheck takes O(sqrt(n)), shouldn't it be O(n)??
The running time is actually Theta(n^(3/2)). According to Legendre's three-square theorem, any integer of the form 4^a (8b + 7) for integers a and b can be written as the sum of four squares but not three. Let n be an integer of this kind. There are Omega(n) numbers less than n that can be written as the sum of three squares, so in the final iteration of the while loop, toCheck has Theta(n) elements, and lst has Theta(n^(1/2)).

First appearance in Stern's Diatomic Sequence

You get an integer n and you need to find the index of its first appearance in Stern's Diatomic Sequence.
The sequence is defined like this:
a[0] = 0
a[1] = 1
a[2*i] = a[i]
a[2*i+1] = a[i] + a[i+1]
See MathWorld.
Because n can be up to 400000, it's not a good idea to brute-force it, especially since the time limit is 4000 ms.
The sequence is pretty odd: first occurrence of 8 is 21, but first occurrence of 6 is 33.
Any ideas how to solve this?
Maybe this might help: OEIS
We can easily solve for the first occurrence of a number in the range of 400000 in under four seconds:
Prelude Diatomic> firstDiatomic 400000
363490989
(0.03 secs, 26265328 bytes)
Prelude Diatomic> map firstDiatomic [400000 .. 400100]
[363490989,323659475,580472163,362981813,349334091,355685483,346478235,355707595
,291165867,346344083,347155797,316314293,576398643,315265835,313171245,355183267
,315444051,315970205,575509833,311741035,340569429,313223987,565355925,296441165
,361911645,312104147,557145429,317106853,323637939,324425077,610613547,311579309
,316037811,311744107,342436533,348992869,313382235,325406123,355818699,312128723
,347230875,324752171,313178421,312841811,313215645,321754459,576114987,325793195
,313148763,558545581,355294101,359224397,345462093,307583675,355677549,312120731
,341404245,316298389,581506779,345401947,312109779,316315061,315987123,313447771
,361540179,313878107,304788843,325765547,316036275,313731751,355635795,312035947
,346756533,313873883,349358379,357393763,559244877,313317739,325364139,312128107
,580201947,358182323,314944173,357403987,584291115,312158827,347448723,363246413
,315935571,349386085,315929427,312137323,357247725,313207657,320121429,356954923
,557139285,296392013,576042123,311726765,296408397]
(2.45 secs, 3201358192 bytes)
The key to it is the Calkin-Wilf tree.
Starting from the fraction 1/1, it is built by the rule that for a node with the fraction a/b, its left child carries the fraction a/(a+b), and its right child the fraction (a+b)/b.
1/1
/ \
/ \
/ \
1/2 2/1
/ \ / \
1/3 3/2 2/3 3/1
etc. The diatomic sequence (starting at index 1) is the sequence of numerators of the fractions in the Calkin-Wilf tree, when that is traversed level by level, each level from left to right.
If we look at the tree of indices
1
/ \
/ \
/ \
2 3
/ \ / \
4 5 6 7
/ \
8 9 ...
we can easily verify that the node at index k in the Calkin-Wilf tree carries the fraction a[k]/a[k+1] by induction.
That is obviously true for k = 1 (a[1] = a[2] = 1), and from then on,
for k = 2*j we have the left child of the node with index j, so the fraction is a[j]/(a[j]+a[j+1]) and a[k] = a[j] and a[k+1] = a[j] + a[j+1] are the defining equations of the sequence.
for k = 2*j+1 we have the right child of the node with index j, so the fraction is (a[j]+a[j+1])/a[j+1] and that is a[k]/a[k+1] again by the defining equations.
All positive reduced fractions occur exactly once in the Calkin-Wilf tree (left as an exercise for the reader), hence all positive integers occur in the diatomic sequence.
We can find the node in the Calkin-Wilf tree from the index by following the binary representation of the index, from the most significant bit to the least, for a 1-bit we go to the right child and for a 0-bit to the left. (For that, it is nice to augment the Calkin-Wilf tree with a node 0/1 whose right child is the 1/1 node, so that we need have a step for the most significant set bit of the index.)
Now, that doesn't yet help very much to solve the problem at hand.
But, let us first solve a related problem: For a reduced fraction p/q, determine its index.
Suppose that p > q. Then we know that p/q is a right child, and its parent is (p-q)/q. If also p-q > q, we have again a right child, whose parent is (p - 2*q)/q. Continuing, if
p = a*q + b, 1 <= b < q
then we reach the p/q node from the b/q node by going to the right child a times.
Now we need to find a node whose numerator is smaller than its denominator. That is of course the left child of its parent. The parent of b/q is b/(q-b) then. If
q = c*b + d, 1 <= d < b
we have to go to the left child c times from the node b/d to reach b/q.
And so on.
We can find the way from the root (1/1) to the p/q node using the continued fraction (I consider only simple continued fractions here) expansion of p/q. Let p > q and
p/q = [a_0, a_1, ..., a_r,1]
the continued fraction expansion of p/q ending in 1.
If r is even, then go to the right child a_r times, then to the left a_(r-1) times, then to the right child ... then a_1 times to the left child, and finally a_0 times to the right.
If r is odd, then first go to the left child a_r times, then a_(r-1) times to the right ... then a_1 times to the left child, and finally a_0 times to the right.
For p < q, we must end going to the left, hence start going to the left for even r and start going to the right for odd r.
We have thus found a close connection between the binary representation of the index and the continued fraction expansion of the fraction carried by the node via the path from the root to the node.
Let the run-length-encoding of the index k be
[c_1, c_2, ..., c_j] (all c_i > 0)
i.e. the binary representation of k starts with c_1 ones, followed by c_2 zeros, then c_3 ones etc., and ending with c_j
ones, if k is odd - hence j is also odd;
zeros, if k is even - hence j is also even.
Then [c_j, c_(j-1), ..., c_2, c_1] is the continued fraction expansion of a[k]/a[k+1] whose length has the same parity as k (every rational has exactly two continued fraction expansions, one with odd length, the other with even length).
The RLE gives the path from the 0/1 node above 1/1 to a[k]/a[k+1]. The length of the path is
the number of bits necessary to represent k, and
the sum of the partial quotients in the continued fraction expansion.
Now, to find the index of the first occurrence of n > 0 in the diatomic sequence, we first observe that the smallest index must necessarily be odd, since a[k] = a[k/2] for even k. Let the smallest index be k = 2*j+1. Then
the length of the RLE of k is odd,
the fraction at the node with index k is a[2*j+1]/a[2*j+2] = (a[j] + a[j+1])/a[j+1], hence it is a right child.
So the smallest index k with a[k] = n corresponds to the left-most ending of all the shortest paths to a node with numerator n.
The shortest paths correspond to the continued fraction expansions of n/m, where 0 < m <= n is coprime to n [the fraction must be reduced] with the smallest sum of the partial quotients.
What kind of length do we need to expect? Given a continued fraction p/q = [a_0, a_1, ..., a_r] with a_0 > 0 and sum
s = a_0 + ... + a_r
the numerator p is bounded by F(s+1) and the denominator q by F(s), where F(j) is the j-th Fibonacci number. The bounds are sharp, for a_0 = a_1 = ... = a_r = 1 the fraction is F(s+1)/F(s).
So if F(t) < n <= F(t+1), the sum of the partial quotients of the continued fraction expansion (either of the two) is >= t. Often there is an m such that the sum of the partial quotients of the continued fraction expansion of n/m is exactly t, but not always:
F(5) = 5 < 6 <= F(6) = 8
and the continued fraction expansions of the two reduced fractions 6/m with 0 < m <= 6 are
6/1 = [6] (alternatively [5,1])
6/5 = [1,4,1] (alternatively [1,5])
with sum of the partial quotients 6. However, the smallest possible sum of partial quotients is never much larger (the largest I'm aware of is t+2).
The continued fraction expansions of n/m and n/(n-m) are closely related. Let's assume that m < n/2, and let
n/m = [a_0, a_1, ..., a_r]
Then a_0 >= 2,
(n-m)/m = [a_0 - 1, a_1, ..., a_r]
and since
n/(n-m) = 1 + m/(n-m) = 1 + 1/((n-m)/m)
the continued fraction expansion of n/(n-m) is
n/(n-m) = [1, a_0 - 1, a_1, ..., a_r]
In particular, the sum of the partial quotients is the same for both.
Unfortunately, I'm not aware of a way to find the m with the smallest sum of partial quotients without brute force, so the algorithm is (I assume n > 2
for 0 < m < n/2 coprime to n, find the continued fraction expansion of n/m, collecting the ones with the smallest sum of the partial quotients (the usual algorithm produces expansions whose last partial quotient is > 1, we assume that).
Adjust the found continued fraction expansions [those are not large in number] it the following way:
if the CF [a_0, a_1, ..., a_r] has even length, convert it to [a_0, a_1, ..., a_(r-1), a_r - 1, 1]
otherwise, use [1, a_0 - 1, a_1, ..., a_(r-1), a_r - 1, 1]
(that chooses the one between n/m and n/(n-m) leading to the smaller index)
reverse the continued fractions to obtain the run-length-encodings of the corresponding indices
choose the smallest among them.
In step 1, it is useful to use the smallest sum found so far to short-cut.
Code (Haskell, since that's easiest):
module Diatomic (diatomic, firstDiatomic, fuscs) where
import Data.List
strip :: Int -> Int -> Int
strip p = go
where
go n = case n `quotRem` p of
(q,r) | r == 0 -> go q
| otherwise -> n
primeFactors :: Int -> [Int]
primeFactors n
| n < 1 = error "primeFactors: non-positive argument"
| n == 1 = []
| n `rem` 2 == 0 = 2 : go (strip 2 (n `quot` 2)) 3
| otherwise = go n 3
where
go 1 _ = []
go m p
| m < p*p = [m]
| r == 0 = p : go (strip p q) (p+2)
| otherwise = go m (p+2)
where
(q,r) = m `quotRem` p
contFracLim :: Int -> Int -> Int -> Maybe [Int]
contFracLim = go []
where
go acc lim n d = case n `quotRem` d of
(a,b) | lim < a -> Nothing
| b == 0 -> Just (a:acc)
| otherwise -> go (a:acc) (lim - a) d b
fixUpCF :: [Int] -> [Int]
fixUpCF [a]
| a < 3 = [a]
| otherwise = [1,a-2,1]
fixUpCF xs
| even (length xs) = case xs of
(1:_) -> fixEnd xs
(a:bs) -> 1 : (a-1) : bs
| otherwise = case xs of
(1:_) -> xs
(a:bs) -> 1 : fixEnd ((a-1):bs)
fixEnd :: [Int] -> [Int]
fixEnd [a,1] = [a+1]
fixEnd [a] = [a-1,1]
fixEnd (a:bs) = a : fixEnd bs
fixEnd _ = error "Shouldn't have called fixEnd with an empty list"
cfCompare :: [Int] -> [Int] -> Ordering
cfCompare (a:bs) (c:ds) = case compare a c of
EQ -> cfCompare ds bs
cp -> cp
fibs :: [Integer]
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
toNumber :: [Int] -> Integer
toNumber = foldl' ((+) . (*2)) 0 . concat . (flip (zipWith replicate) $ cycle [1,0])
fuscs :: Integer -> (Integer, Integer)
fuscs 0 = (0,1)
fuscs 1 = (1,1)
fuscs n = case n `quotRem` 2 of
(q,r) -> let (a,b) = fuscs q
in if r == 0
then (a,a+b)
else (a+b,b)
diatomic :: Integer -> Integer
diatomic = fst . fuscs
firstDiatomic :: Int -> Integer
firstDiatomic n
| n < 0 = error "Diatomic sequence has no negative terms"
| n < 2 = fromIntegral n
| n == 2 = 3
| otherwise = toNumber $ bestCF n
bestCF :: Int -> [Int]
bestCF n = check [] estimate start
where
pfs = primeFactors n
(step,ops) = case pfs of
(2:xs) -> (2,xs)
_ -> (1,pfs)
start0 = (n-1) `quot` 2
start | even n && even start0 = start0 - 1
| otherwise = start0
eligible k = all ((/= 0) . (k `rem`)) ops
estimate = length (takeWhile (<= fromIntegral n) fibs) + 2
check candidates lim k
| k < 1 || n `quot` k >= lim = if null candidates
then check [] (2*lim) start
else minimumBy cfCompare candidates
| eligible k = case contFracLim lim n k of
Nothing -> check candidates lim (k-step)
Just cf -> let s = sum cf
in if s < lim
then check [fixUpCF cf] s (k - step)
else check (fixUpCF cf : candidates) lim (k-step)
| otherwise = check candidates lim (k-step)
I would recommend you read this letter from Dijkstra which explains an alternative way of computing this function via:
n, a, b := N, 1, 0;
do n ≠ 0 and even(n) → a, n:= a + b, n/2
odd(n) → b, n:= b + a, (n-1)/2
od {b = fusc(N)}
This starts with a,b=1,0 and effectively uses successive bits of N (from least to most significant) to increase a and b, the final result being the value of b.
The index of the first appearance of a particular value for b can therefore be computed via finding the smallest n for which this iteration will result in that value of b.
One method for finding this smallest n is to use A* search where the cost is the value of n. The efficiency of the algorithm will be determined by your choice of heuristic.
For the heuristic, I would recommend noting that:
the final value will always be a multiple of the gcd(a,b) (this can be used to rule out some nodes that can never produce the target)
b always increases
there is a maximum (exponential) rate at which b can increase (the rate depends on the current value of a)
EDIT
Here is some example Python code to illustrate the A* approach.
from heapq import *
def gcd(a,b):
while a:
a,b=b%a,a
return b
def heuristic(node,goal):
"""Estimate least n required to make b==goal"""
n,a,b,k = node
if b==goal: return n
# Otherwise needs to have at least one more bit set
# Improve this heuristic to make the algorithm faster
return n+(1<<k)
def diatomic(goal):
"""Return index of first appearance of n in Stern's Diatomic sequence"""
start=0,1,0,0
f_score=[] # This is used as a heap
heappush(f_score, (0,start) )
while 1:
s,node = heappop(f_score)
n,a,b,k = node
if b==goal:
return n
for node in [ (n,a+b,b,k+1),(n+(1<<k),a,b+a,k+1) ]:
n2,a2,b2,k2 = node
if b2<=goal and (goal%gcd(a2,b2))==0:
heappush(f_score,(heuristic(node,goal),node))
print [diatomic(n) for n in xrange(1,10)]

Building an expression with maximum value

Given n integers, is there an O(n) or O(n log n) algorithm that can compute the maximum value of a mathematical expression that can be obtained by inserting the operators -, +, * and parentheses between the given numbers? Assume only binary variants of the operators, so no unary minus, except before the first element if needed.
For example, given -3 -4 5, we can build the expression (-3) * (-4) * 5, whose value is 60, and maximum possible.
Background:
I stumbled upon this problem some time ago when studying genetic algorithms, and learned that it can be solved pretty simply with a classical genetic algorithm. This runs slowly however, and it's only simple in theory, as the code gets rather ugly in practice (evaluate the expression, check for correct placement of brackets etc.). What's more, we're not guaranteed to find the absolute maximum either.
All these shortcomings of genetic algorithms got me wondering: since we can don't have to worry about division, is there a way to do this efficiently with a more classic approach, such as dynamic programming or a greedy strategy?
Update:
Here's an F# program that implements the DP solution proposed by #Keith Randall together with my improvement, which I wrote in a comment to his post. This is very inefficient, but I maintain that it's polynomial and has cubic complexity. It runs in a few seconds for ~50 element arrays. It would probably be faster if written in a fully imperative manner, as a lot of time is probably wasted on building and traversing lists.
open System
open System.IO
open System.Collections.Generic
let Solve (arr : int array) =
let memo = new Dictionary<int * int * int, int>()
let rec Inner st dr last =
if st = dr then
arr.[st]
else
if memo.ContainsKey(st, dr, last) then
memo.Item(st, dr, last)
else
match last with
| 0 -> memo.Add((st, dr, last),
[
for i in [st .. dr - 1] do
for j in 0 .. 2 do
for k in 0 .. 2 do
yield (Inner st i j) * (Inner (i + 1) dr k)
] |> List.max)
memo.Item(st, dr, last)
| 1 -> memo.Add((st, dr, last),
[
for i in [st .. dr - 1] do
for j in 0 .. 2 do
for k in 0 .. 2 do
yield (Inner st i j) + (Inner (i + 1) dr k)
] |> List.max)
memo.Item(st, dr, last)
| 2 -> memo.Add((st, dr, last),
[
for i in [st .. dr - 1] do
for j in 0 .. 2 do
for k in 0 .. 2 do
yield (Inner st i j) - (Inner (i + 1) dr k)
] |> List.max)
memo.Item(st, dr, last)
let noFirst = [ for i in 0 .. 2 do yield Inner 0 (arr.Length - 1) i ] |> List.max
arr.[0] <- -1 * arr.[0]
memo.Clear()
let yesFirst = [ for i in 0 .. 2 do yield Inner 0 (arr.Length - 1) i ] |> List.max
[noFirst; yesFirst] |> List.max
let _ =
printfn "%d" <| Solve [|-10; 10; -10|]
printfn "%d" <| Solve [|2; -2; -1|]
printfn "%d" <| Solve [|-5; -3; -2; 0; 1; -1; -1; 6|]
printfn "%d" <| Solve [|-5; -3; -2; 0; 1; -1; -1; 6; -5; -3; -2; 0; 1; -1; -1; 6; -5; -3; -2; 0; 1; -1; -1; 6; -5; -3; -2; 0; 1; -1; -1; 6; -5; -3; -2; 0; 1; -1; -1; 6; -5; -3; -2; 0; 1; -1; -1; 6;|]
Results:
1000
6
540
2147376354
The last one is most likely an error due to overflow, I'm just trying to show that a relatively big test runs too fast for this to be exponential.
Here's a proposed solution:
def max_result(a_):
memo = {}
a = list(a_)
a.insert(0, 0)
return min_and_max(a, 0, len(a)-1, memo)[1]
def min_and_max(a, i, j, memo):
if (i, j) in memo:
return memo[i, j]
if i == j:
return (a[i], a[i])
min_val = max_val = None
for k in range(i, j):
left = min_and_max(a, i, k, memo)
right = min_and_max(a, k+1, j, memo)
for op in "*-+":
for x in left:
for y in right:
val = apply(x, y, op)
if min_val == None or val < min_val: min_val = val
if max_val == None or val > max_val: max_val = val
ret = (min_val, max_val)
memo[i, j] = ret
return ret
def apply(x, y, op):
if op == '*': return x*y
if op == '+': return x+y
return x-y
max_result is the main function, and min_and_max is auxiliary. The latter returns the minimum and maximum results that can be achieved by sub-sequence a[i..j].
It assumes that maximum and minimum results of sequences are composed by maximum and minimum results of sub-sequences. Under this assumption, the problem has optimal substructure and can be solved with dynamic programming (or memoization). Run time is O(n^3).
I haven't proved correctness, but I have verified its output against a brute force solution with thousands of small randomly generated inputs.
It handles the possibility of a leading unary minus by inserting a zero at the beginning of the sequence.
EDIT
Been thinking a bit more about this problem, and I believe it can be reduced to a simpler problem in which all values are (strictly) positive and only operators * and + are allowed.
Just remove all zeroes from the sequence and replace negative numbers by their absolute value.
Furthermore, if there are no ones in the resulting sequence, the result is simply the product of all numbers.
After this reduction, the simple dynamic programming algorithm would work.
EDIT 2
Based on the previous insights I think I found a linear solution:
def reduce(a):
return filter(lambda x: x > 0, map(abs, a))
def max_result(a):
b = reduce(a)
if len(b) == 0: return 0
return max_result_aux(b)
def max_result_aux(b):
best = [1] * (len(b) + 1)
for i in range(len(b)):
j = i
sum = 0
while j >= 0 and i-j <= 2:
sum += b[j]
best[i+1] = max(best[i+1], best[j] * sum)
j -= 1
return best[len(b)]
best[i] is the maximum result that can be achieved by sub-sequence b[0..(i-1)].
EDIT 3
Here's an argument in favor of the O(n) algorithm based on the following assumption:
You can always achieve the maximum result with an expression of the form
+/- (a_1 +/- ... +/- a_i) * ... * (a_j +/- ... +/- a_n)
That is: a product of factors composed of an algebraic sum of terms (including the case of only one factor).
I will also use the following lemmas which are easy to prove:
Lemma 1: x*y >= x+y for all x,y such that x,y >= 2
Lemma 2: abs(x_1) + ... + abs(x_n) >= abs(x_1 +/- ... +/- x_n)
Here it goes.
The sign of each factor doesn't matter, since you can always make the product positive by using the leading unary minus. Hence, to maximize the product we need to maximize the absolute value of each factor.
Setting aside the trivial case in which all numbers are zeroes, in an optimal solution no factor will be composed only of zeroes. Therefore, since zeroes have no effect inside each sum of terms, and each factor will have at least one non-zero number, we can remove all zeroes. From now on, let's assume there are no zeroes.
Let's concentrate in each sum of terms separately:
(x_1 +/- x_2 +/- ... +/- x_n)
By Lemma 2, the maximum absolute value each factor can achieve is the sum of the absolute values of each term. This can be achieved in the following way:
If x_1 is positive, add all positive terms and subtract all negative terms. If x_1 is negative, subtract all positive terms and add all negative terms.
This implies that the sign of each term does not matter, we can consider the absolute value of each number and only use operator + inside factors. From now on, let's consider all numbers are positive.
The crucial step, that leads to an O(n) algorithm, is to prove that the maximum result can always be achieved with factors that have at most 3 terms.
Suppose we have a factor of more than 3 terms, by Lemma 1 we can break it into two smaller factors of 2 or more terms each (hence, each add up to 2 or more), without reducing the total result. We can break it down repeatedly until no factors of more than 3 terms are left.
That completes the argument. I still haven't found a complete justification of the initial assumption. But I tested my code with millions of randomly generated cases and couldn't break it.
A reasonable big value can be found in O(N). Consider this a greedy algorithm.
Find all positive numbers ≥ 2. Store the result as A.
Count all "-1"s . Store the result as B.
Find all negative numbers ≤ -2. Store the result as C.
Count all "1"s. Store the result as D.
Initialize Product to 1.
If A is not empty, multiply Product by the product of A.
If C is not empty and has even count, multiply Product by the product of C.
If C is has odd count, take the smallest number in magnitude of C away (store it as x), and multiply Product by the product of the rest of C.
If x is set and B is nonzero, compare Product × -x with Product − x + 1.
If the former is strictly larger, decrease B by 1 and multiply Product by -x, then remove x.
If the latter is larger, do nothing.
Set Result to 0. If Product ≠ 1, add it to Result.
Add D to Result, representing addition of D "1"s.
Add B to Result, representing subtraction of B "-1"s.
If x is set, substract x from Result.
The time complexities are:
1. O(N), 2. O(N), 3. O(N), 4. O(N), 5. O(1), 6. O(N), 7. O(N), 8. O(N), 9. O(1), 10. O(1), 11. O(1), 12. O(1), 13. O(1),
so the whole algorithm runs in O(N) time.
An example session:
-3 -4 5
A = [5]
B = 0
C = [-3, -4]
D = 1
Product = 1
A is not empty, so Product = 5.
C is even, so Product = 5 × -3 × -4 = 60
-
-
Product ≠ 1, so Result = 60.
-
-
-
5 × -3 × -4 = 60
-5 -3 -2 0 1 -1 -1 6
A = [6]
B = 2
C = [-5, -3, -2]
D = 1
Product = 1
A is not empty, so Product = 6
-
C is odd, so x = -2, and Product = 6 × -5 × -3 = 90.
x is set and B is nonzero. Compare Product × -x = 180 and Product − x + 1 = 93. Since the former is larger, we reset B to 1, Product to 180 and remove x.
Result = 180.
Result = 180 + 1 = 181
Result = 181 + 1 = 182
-
6 × -5 × -3 × -2 × -1 + 1 − (-1) + 0 = 182
2 -2 -1
A = [2]
B = 1
C = [-2]
D = 0
Product = 1
Product = 2
-
x = -2, Product is unchanged.
B is nonzero. Compare Product × -x = 4 and Product − x + 1 = 5. Since the latter is larger, we do nothing.
Result = 2
-
Result = 2 + 1 = 3
Result = 3 − (-2) = 5.
2 − (-1) − (-2) = 5.
You should be able to do this with dynamic programming. Let x_i be your input numbers. Then let M(a,b) be the maximum value you can get with the subsequence x_a through x_b. You can then compute:
M(a,a) = x_a
M(a,b) = max_i(max(M(a,i)*M(i+1,b), M(a,i)+M(i+1,b), M(a,i)-M(i+1,b))
edit:
I think you need to compute both the max and min computable value using each subsequence. So
Max(a,a) = Min(a,a) = x_a
Max(a,b) = max_i(max(Max(a,i)*Max(i+1,b),
Max(a,i)*Min(i+1,b),
Min(a,i)*Max(i+1,b),
Min(a,i)*Min(i+1,b),
Max(a,i)+Max(i+1,b),
Max(a,i)-Min(i+1,b))
...similarly for Min(a,b)...
Work this in reverse polish - that way you don't have to deal with parentheses. Next put a - in front of every -ve number (thereby making it positive). Finally multiply them all together. Not sure about the complexity, probably about O(N).
EDIT: forgot about 0. If it occurs in your input set, add it to the result.
This feels NP Complete to me, though I haven't yet figured out how to do a reduction. If I'm right, then I could say
Nobody in the world knows if any polynomial algorithm exists, let alone O(n log n), but most computer scientists suspect there isn't.
There are poly time algorithms to estimate the answer, such as the genetic algorithm you describe.
In fact, I think the question you mean to ask is, "Is there a reasonably useful O(n) or O(n log n) algorithm to estimate the maximum value?"
This is my first post on stackoverflow, so I apologize in advance for missing any preliminary etiquette. Also, in the interest of full disclosure, Dave brought this problem to my attention.
Here's an O(N^2logN) solution, mostly because of the the repeated sorting step in the for loop.
Absolute values: Remove zero elements and sort by absolute value. Since you are allowed to place a negative sign in front of your final result, it does not matter whether your answer is negative or positive. Only the absolute values of all numbers in the set matter.
Multiplication only for numbers > 1: We make the observation that for any set of positive integers greater than 1, (e.g. {2,3,4}), the largest result comes from a multiplication. This can be shown by an enumerative technique or a contradiction argument over permitted operations + and -. e.g. (2+3)*4 = 2*4 + 3*4 < 3*4 + 3*4 = 2*(3*4). In other words, multiplication is the most "powerful" operation (except for the 1s).
Addition of the 1s to the smallest non-1 numbers: For the 1s, since multiplication is a useless operation, we are better off adding. Here again we show a complete ordering on the result of an addition. For rhetoric sake, consider again the set {2,3,4}. We note that: 2*3*(4+1) <= 2*(3+1)*4 <= (2+1)*3*4. In other words, we get the most "mileage" from a 1 by adding it to the smallest existing non-1 element in the set. Given a sorted set, this can be done in O(N^2logN).
Here's what the pseudo-code looks like:
S = input set of integers;
S.absolute();
S.sort();
//delete all the 0 elements
S.removeZeros();
//remove all 1 elements from the sorted list, and store them
ones = S.removeOnes();
//now S contains only integers > 1, in ascending order S[0] ... S[end]
for each 1 in ones:
S[0] = S[0] + 1;
S.sort();
end
max_result = Product(S);
I know I'm late to the party, but I took this on as a challenge to myself. Here is the solution I came up with.
type Operation =
| Add
| Sub
| Mult
type 'a Expr =
| Op of 'a Expr * Operation * 'a Expr
| Value of 'a
let rec eval = function
| Op (a, Add, b) -> (eval a) + (eval b)
| Op (a, Sub, b) -> (eval a) - (eval b)
| Op (a, Mult, b) -> (eval a) * (eval b)
| Value x -> x
let rec toString : int Expr -> string = function
| Op (a, Add, b) -> (toString a) + " + " + (toString b)
| Op (a, Sub, b) -> (toString a) + " - " + (toString b)
| Op (a, Mult, b) -> (toString a) + " * " + (toString b)
| Value x -> string x
let appendExpr (a:'a Expr) (o:Operation) (v:'a) =
match o, a with
| Mult, Op(x, o2, y) -> Op(x, o2, Op(y, o, Value v))
| _ -> Op(a, o, Value v)
let genExprs (xs:'a list) : 'a Expr seq =
let rec permute xs e =
match xs with
| x::xs ->
[Add; Sub; Mult]
|> Seq.map (fun o -> appendExpr e o x)
|> Seq.map (permute xs)
|> Seq.concat
| [] -> seq [e]
match xs with
| x::xs -> permute xs (Value x)
| [] -> Seq.empty
let findBest xs =
let best,result =
genExprs xs
|> Seq.map (fun e -> e,eval e)
|> Seq.maxBy snd
toString best + " = " + string result
findBest [-3; -4; 5]
returns "-3 * -4 * 5 = 60"
findBest [0; 10; -4; 0; 52; -2; -40]
returns "0 - 10 * -4 + 0 + 52 * -2 * -40 = 4200"
It should work with any type supporting comparison and the basic mathmatical operators, but FSI will constrain it to ints.

Resources