Remove entries from array to sort it and maximize sum of elements
Haskell, \$O(n \log n)\$ time, \$O(n)\$ space
{-# LANGUAGE MultiParamTypeClasses #-}
import qualified Data.FingerTree as F
data S = S
{ sSum :: Int
, sArr :: [Int]
} deriving (Show)
instance Monoid S where
mempty = S 0 []
mappend _ s = s
instance F.Measured S S where
measure = id
bestSubarrays :: [Int] -> F.FingerTree S S
bestSubarrays [] = F.empty
bestSubarrays (x:xs) = left F.>< sNew F.<| right'
where
(left, right) = F.split (\s -> sArr s > [x]) (bestSubarrays xs)
sLeft = F.measure left
sNew = S (x + sSum sLeft) (x : sArr sLeft)
right' = F.dropUntil (\s -> sSum s > sSum sNew) right
bestSubarray :: [Int] -> [Int]
bestSubarray = sArr . F.measure . bestSubarrays
How it works
bestSubarrays xs
is the sequence of subarrays of xs
that are on the efficient frontier of {largest sum, smallest first element}, ordered from left to right by increasing sum and increasing first element.
To go from bestSubarrays xs
to bestSubarrays (x:xs)
, we
- split the sequence into a left side with first elements less than
x
, and a right side with first elements greater thanx
, - find a new subarray by prepending
x
to the rightmost subarray on the left side, - drop the prefix of subarrays from the right side with smaller sum than the new subarray,
- concatenate the left side, the new subarray, and the remainder of the right side.
A finger tree supports all these operations in \$O(\log n)\$ time.
Perl
This should be O(n^2) in time and O(n) in space
Give numbers separated by space on one line to STDIN
#!/usr/bin/perl -a
use strict;
use warnings;
# use Data::Dumper;
use constant {
INFINITY => 9**9**9,
DEBUG => 0,
};
# Recover sequence from the 'how' linked list
sub how {
my @z;
for (my $h = shift->{how}; $h; $h = $h->[1]) {
push @z, $h->[0];
}
pop @z;
return join " ", reverse @z;
}
use constant MINIMUM => {
how => [-INFINITY, [INFINITY]],
sum => -INFINITY,
next => undef,
};
# Candidates is a linked list of subsequences under consideration
# A given final element will only appear once in the list of candidates
# in combination with the best sum that can be achieved with that final element
# The list of candidates is reverse sorted by final element
my $candidates = {
# 'how' will represent the sequence that adds up to the given sum as a
# reversed lisp style list.
# so e.g. "1, 5, 8" will be represented as [8, [5, [1, INFINITY]]]
# So the final element will be at the front of 'how'
how => [INFINITY],
# The highest sum that can be reached with any subsequence with the same
# final element
sum => 0,
# 'next' points to the next candidate
next => MINIMUM, # Dummy terminator to simplify program logic
};
for my $num (@F) {
# Among the candidates on which an extension with $num is valid
# find the highest sum
my $max_sum = MINIMUM;
my $c = \$candidates;
while ($num < $$c->{how}[0]) {
if ($$c->{sum} > $max_sum->{sum}) {
$max_sum = $$c;
$c = \$$c->{next};
} else {
# Remove pointless candidate
$$c = $$c->{next};
}
}
my $new_sum = $max_sum->{sum} + $num;
if ($$c->{how}[0] != $num) {
# Insert a new candidate with a never before seen end element
# Due to the unique element rule this branch will always be taken
$$c = { next => $$c };
} elsif ($new_sum <= $$c->{sum}) {
# An already known end element but the sum is no improvement
next;
}
$$c->{sum} = $new_sum;
$$c->{how} = [$num, $max_sum->{how}];
# print(Dumper($candidates));
if (DEBUG) {
print "Adding $num\n";
for (my $c = $candidates; $c; $c = $c->{next}) {
printf "sum(%s) = %s\n", how($c), $c->{sum};
}
print "------\n";
}
}
# Find the sequence with the highest sum among the candidates
my $max_sum = MINIMUM;
for (my $c = $candidates; $c; $c = $c->{next}) {
$max_sum = $c if $c->{sum} > $max_sum->{sum};
}
# And finally print the result
print how($max_sum), "\n";