web-dev-qa-db-ja.com

最も人口の多い年を見つける(最も効率的なソリューション)

2つの配列があるとします。 $birthsには、誰かが生まれた年を示す誕生年のリストが含まれ、$deathsには誰かが死亡した年を示す死亡年のリストが含まれています。人口が最も多かった年をどのように見つけることができますか?

たとえば、次の配列があるとします。

$births = [1984, 1981, 1984, 1991, 1996];
$deaths = [1991, 1984];

人口が最も多かった年は1996である必要があります。これは、その年は3人が生存しており、これはこれらの年の中で最も人口が多いためです。

これについて、実行中の数学は次のとおりです。

 |誕生|死|人口| 
 | ----------- | ------------ | 
 | 1981 | | 1 | 
 | 1984年| 2 | 
 | 1984年1984年2 | 
 | 1991 | 1991 | 2 | 
 | 1996 | | 3 | 

仮定

誰かが生まれた年は人口が1人増える可能性があり、誰かが亡くなった年は1人減少する可能性があると私たちは考えることができます。したがって、この例では、1984年に2人が生まれ、1984年に1人が亡くなりました。つまり、その年に人口は1人増えました。

また、死亡数が出生数を超えることはなく、人口が0の場合は死亡しないと安全に想定できます。

また、$deaths$birthsの両方の年が負または浮動小数点の値になることはないと想定しても安全です(これらは常に0より大きい正の整数です)。

---(できませんただし、配列がソートされるか、値が重複しないことを前提としています。

必要条件

これらの2つの配列を入力として、最大人口が発生した年を返す関数を作成する必要があります。入力配列が次の場合、この関数は0false""、またはNULLの誤った値を受け入れます)を返します。空、または人口が常に0である場合。最高の人口が複数年に発生した場合、関数は最高の人口に達した最初の年またはその後の年を返す場合があります。

例えば:

$births = [1997, 1997, 1997, 1998, 1999];
$deaths = [1998, 1999];

/* The highest population was 3 on 1997, 1998 and 1999, either answer is correct */

さらに、ソリューションのBig Oを含めると役立ちます。


これを行うための私の最善の試みは次のようになります:

function highestPopulationYear(Array $births, Array $deaths): Int {

    sort($births);
    sort($deaths);

    $nextBirthYear = reset($births);
    $nextDeathYear = reset($deaths);

    $years = [];
    if ($nextBirthYear) {
        $years[] = $nextBirthYear;
    }
    if ($nextDeathYear) {
        $years[] = $nextDeathYear;
    }

    if ($years) {
        $currentYear = max(0, ...$years);
    } else {
        $currentYear = 0;
    }

    $maxYear = $maxPopulation = $currentPopulation = 0;

    while(current($births) !== false || current($deaths) !== false || $years) {

        while($currentYear === $nextBirthYear) {
            $currentPopulation++;
            $nextBirthYear = next($births);
        }

        while($currentYear === $nextDeathYear) {
            $currentPopulation--;
            $nextDeathYear = next($deaths);
        }

        if ($currentPopulation >= $maxPopulation) {
            $maxPopulation = $currentPopulation;
            $maxYear = $currentYear;
        }

        $years = [];

        if ($nextBirthYear) {
            $years[] = $nextBirthYear;
        }
        if ($nextDeathYear) {
            $years[] = $nextDeathYear;
        }
        if ($years) {
            $currentYear = min($years);
        } else {
            $currentYear = 0;
        }
    }

    return $maxYear;
}

上記のアルゴリズムは、最悪の場合O(((n log n) * 2) + k)の多項式時間で機能するはずです。ここで、nは各配列からソートされる要素の数であり、kは誕生年の数(kは常にk >= y)であることに注意してください。ここで、yは死亡年数です。ただし、より効率的な解決策があるかどうかはわかりません。

私の興味は、純粋に既存のアルゴリズムの改善された計算の複雑さのBig Oにあります。メモリの複雑さは問題ではありません。ランタイムの最適化も同様です。 少なくともそれは主要な問題ではありません。マイナー/メジャーランタイムの最適化は歓迎されますが、ここで重要な要素ではありません。

9
Sherif

問題に対する最もシンプルで明確なアプローチの1つ。

$births = [1909, 1919, 1904, 1911, 1908, 1908, 1903, 1901, 1914, 1911, 1900, 1919, 1900, 1908, 1906];
$deaths = [1910, 1911, 1912, 1911, 1914, 1914, 1913, 1915, 1914, 1915];

/* for generating 1 million records

for($i=1;$i<=1000000;$i++) {
    $births[] = Rand(1900, 2020);
    $deaths[] = Rand(1900, 2020);
}
*/

function highestPopulationYear(Array $births, Array $deaths): Int {
    $start_time = microtime(true); 
    $population = array_count_values($births);
    $deaths = array_count_values($deaths);

    foreach ($deaths as $year => $death) {
        $population[$year] = ($population[$year] ?? 0) - $death;
    }
    ksort($population, SORT_NUMERIC);
    $cumulativeSum = $maxPopulation = $maxYear = 0;
    foreach ($population as $year => &$number) {
        $cumulativeSum += $number;
        if($maxPopulation < $cumulativeSum) {
            $maxPopulation = $cumulativeSum;
            $maxYear = $year;
        }
    }
    print " Execution time of function = ".((microtime(true) - $start_time)*1000)." milliseconds"; 
    return $maxYear;
}

print highestPopulationYear($births, $deaths);

出力

1909

複雑さ

O(m + log(n))
0
Ronak Dhoot