Perl O(n log n)
#!/usr/bin/env perl
use strict;
$^W=1;
# calculate_f ( <lower bound>, <upper bound>, <array reference> )
sub calculate_f ($$$) {
my $L = shift;
my $U = shift;
my $A = shift;
my $n = @$A;
# Catch trivial cases:
# * Upper bound is lower than lower bound,
# * array is empty.
# In both cases the result array is empty and the value
# of f does not matter. Therefore we can return the
# "optimum" value 1.
return 1 if $U < $L or $n == 0;
# The array is sorted. accordign to the manual page, Perl since 5.7
# uses a "stable mergesort algorithm whose worst-case behaviour
# is O(N log N).
my @A = sort {$a <=> $b} @$A;
# Array @R stores the length of ranges:
# $R[$i] is the number of elements that form a valid
# range of input numbers ($A[$i], ..., $A[$i + $R[$i] - 1])
my @R;
my $lu = $L / $U;
my $ul = $U / $L;
# find ( <upper value>, <left position>, <right position> )
# The function looks for the largest input value below or equal
# <upper value>. Invariant: The input value at <left position>
# in the sorted array belongs to the range below <upper value>,
# the input value at <right position> is larger than <upper value>.
# It is a binary search: O(log n)
sub find ($$$) {
my $u = shift;
my $left = shift;
my $right = shift;
return $left if $right <= $left + 1;
my $next = int(($left + $right)/2);
return find($u, $next, $right) if $A[$next] <= $u;
return find($u, $left, $next);
}
for (my $i = 0; $i < $n; $i++) {
my $a = $A[$i];
my $u = $a * $ul;
if ($A[$n-1] <= $u) {
$R[$i] = $n - $i;
next;
}
$R[$i] = find($u, $i, $n - 1) - $i + 1;
}
# Complexity is n times the loop body with the binary search: O(n log n).
# In the next step the maximum number of a valid range is
# obtained: O(n)
my $max = 0;
for (my $i = 0; $i < $n; $i++) {
my $a = $R[$i];
$max = $a if $a > $max;
}
print "(Debug) maximal number of elements in a valid range: $max\n";
# Now each range is checked to find the optimum f
my $f = 0;
for (my $i = 0; $i < $n; $i++) {
# ignore ranges with fewer elements
next unless $R[$i] == $max;
# print current range:
print sprintf "(Debug) A[%d .. %d] = (%g .. %g)\n",
$i, $i + $max - 1,
$A[$i], $A[$i + $max - 1];
# calculate f values based on the smallest/largest element
my $f_lower = $L / $A[$i];
my $f_upper = $U / $A[$i + $max - 1];
# if 1 is in the middle of the f values, then return
# optimal value
return 1 if $f_lower <= 1 and $f_upper >= 1;
# Now $f_lower and $f_upper are both smaller or both greater than 1.
# A candidate for $f is selected from the current range
# by using the value that is closer to 1.
my $f_candidate = $f_lower < 1 ? $f_upper : $f_lower;
print sprintf "(Debug) f_candidate = %g from (%g .. %g)\n",
$f_candidate, $f_lower, $f_upper;
# Compare the candidate with the current f
if (not $f or abs($f_candidate - 1) < abs($f - 1)) {
$f = $f_candidate;
}
}
# Complexity is O(n), because the loop body is O(1).
return $f;
}
my $f = calculate_f(1, 5, [
0.01, 0.02, 0.5, 1 , 5, 9.5, 9.8, 15, 18, 20, 50, 100
]);
print "(Result) f = $f\n";
__END__
Example output:
(Debug) maximal number of elements in a valid range: 6
(Debug) A[4 .. 9] = (5 .. 20)
(Debug) f_candidate = 0.25 from (0.2 .. 0.25)
(Result) f = 0.25
Algorithm is explained in the comments. The input numbers are sorted with O(n log n). Then for each number its valid range is obtained, O(n log n), then the maximum number of elements in the range is found, O(n) and the best fitting f value is calculated, O(n).
Complexity: O(n log n)
Older version with O(n2)
#!/usr/bin/env perl
use strict;
$^W=1;
# calculate_f ( <lower bound>, <upper bound>, <array reference> )
sub calculate_f ($$$) {
my $L = shift;
my $U = shift;
my $A = shift;
my $n = @$A;
# Catch trivial cases:
# * Upper bound is lower than lower bound,
# * array is empty.
# In both cases the result array is empty and the value
# of f does not matter. Therefore we can return the
# "optimum" value 1.
return 1 if $U < $L or $n == 0;
# The array is sorted. according to the manual page, Perl since 5.7
# uses a "stable mergesort algorithm whose worst-case behaviour
# is O(N log N)".
my @A = sort {$a <=> $b} @$A;
# Array @R stores the length of ranges for each element with
# position $i in the sorted array, if the range starts with this
# element:
# $R[$i] is the number of elements that form a valid
# range of input numbers ($A[$i], ..., $A[$i + $R[$i] - 1])
my @R;
my $lu = $L / $U;
for (my $i = 0; $i < $n; $i++) {
my $a = $A[$i];
# Each input number is part of its own range.
$R[$i]++;
# Now we assume, that the current input number $A[$i] is
# the largest element of the range. The minimum lower bound in
# the input number space is calculated and the previous
# input number are checked, if the current input number
# can be added to their ranges.
my $l = $a * $lu;
for (my $j = $i - 1; $j >= 0; $j--) {
last if $A[$j] < $l;
$R[$j]++;
}
}
# In the worst case, all input numbers are part of the final
# output range. Then we would have to look up all smaller input
# numbers for each input number: O(n^2).
# In the best case, the final output range is 1, e.g. if the lower
# bound equals the upper bound. Then the going back is limited to
# 1 and the complexity is O(n).
# In the next step the maximum number of a valid range is
# obtained: O(n)
my $max = 0;
for (my $i = 0; $i < $n; $i++) {
my $a = $R[$i];
$max = $a if $a > $max;
}
print "(Debug) maximal number of elements in a valid range: $max\n";
# Now each range is checked to find the optimum f
my $f = 0;
for (my $i = 0; $i < $n; $i++) {
# ignore ranges with fewer elements
next unless $R[$i] == $max;
# print current range:
print sprintf "(Debug) A[%d .. %d] = (%g .. %g)\n",
$i, $i + $max - 1,
$A[$i], $A[$i + $max - 1];
# calculate f values based on the smallest/largest element
my $f_lower = $L / $A[$i];
my $f_upper = $U / $A[$i + $max - 1];
# if 1 is in the middle of the f values, then return
# optimal value
return 1 if $f_lower <= 1 and $f_upper >= 1;
# Now $f_lower and $f_upper are both smaller or both greater than 1.
# A candidate for $f is selected from the current range
# by using the value that is closer to 1.
my $f_candidate = $f_lower < 1 ? $f_upper : $f_lower;
print sprintf "(Debug) f_candidate = %g from (%g .. %g)\n",
$f_candidate, $f_lower, $f_upper;
# Compare the candidate with the current f
if (not $f or abs($f_candidate - 1) < abs($f - 1)) {
$f = $f_candidate;
}
}
# Complexity is O(n), because the loop body is O(1).
return $f;
}
my $f = calculate_f(1, 5, [
0.01, 0.02, 0.5, 1 , 5, 9.5, 9.8, 15, 18, 20, 50, 100
]);
print "(Result) f = $f\n";
__END__
The algorithm is explained in the comments. The complexity in the worst case is O(n2), in the best case O(n log n) because of the sorting.
Example output:
(Debug) maximal number of elements in a valid range: 6
(Debug) A[4 .. 9] = (5 .. 20)
(Debug) f_candidate = 0.25 from (0.2 .. 0.25)
(Result) f = 0.25
I looked at the picture and thought you were asking a creative fractal drawing question! Unfortunately your actual question is somewhat less interesting. – Level River St – 2014-03-09T21:07:51.477
Well, that could be the next challenge! But not for the moment. – Mikaël Mayer – 2014-03-09T21:16:24.503
I would have expected the result to be 0.25. Then the same numbers of
A
(5
up to20
) are inside the bounds and 0.25 is closer to 1 than 0.2. – Heiko Oberdiek – 2014-03-09T21:20:25.633How accurate need it be? I calculated it to be .2499. – None – 2014-03-09T21:48:15.723