Найдите год с наибольшим населением (наиболее эффективное решение) - PullRequest
9 голосов
/ 23 февраля 2020

дано два массива; $births, содержащий список лет рождения, указывающий, когда кто-то родился, и $deaths, содержащий список лет смерти, указывающий, когда кто-то умер, как мы можем найти год, в котором численность населения была самой высокой?

Для В качестве примера приведены следующие массивы:

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

Год, в котором численность населения была самой высокой, должен составлять 1996, поскольку в течение этого года было живо 3 человек, что было самым высоким показателем численности населения за все эти годы. .

Вот примерная математика:

| Birth | Death | Population |
|-------|-------|------------|
| 1981  |       | 1          |
| 1984  |       | 2          |
| 1984  | 1984  | 2          |
| 1991  | 1991  | 2          |
| 1996  |       | 3          |

Допущения

Можно смело предположить, что за год, в который кто-то родился, население может увеличиться на один, а год, в который кто-то умер, население может уменьшиться на один. Таким образом, в этом примере 2 человека родились в 1984 году, а 1 человек умер в 1984 году, что означает, что численность населения за этот год увеличилась на 1.

Можно также с уверенностью предположить, что число смертей никогда не превысит число рождений и смерти не может произойти, когда численность населения равна 0.

Мы также можем с уверенностью предположить, что годы как в $deaths, так и $births никогда не будут отрицательными или значениями с плавающей запятой ( они всегда положительные целые числа больше 0 ).

Мы не можем предположить, что массивы будут отсортированы или что дублирующих значений не будет.

Требования

Мы должны написать функцию, которая будет возвращать год, в котором произошло наибольшее население, учитывая эти два массива в качестве входных данных. Функция может возвращать 0, false, "" или 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 вычислительной сложности на основе существующего алгоритма. Сложность памяти не имеет значения. Также не оптимизация во время выполнения. По крайней мере, это не главная проблема . Любые второстепенные / существенные оптимизации во время выполнения приветствуются, но это не ключевой фактор.

Ответы [ 8 ]

4 голосов
/ 02 марта 2020

Мы можем решить это за линейное время с сортировкой сегментов. Допустим, размер ввода равен n, а диапазон лет равен m.

O(n): Find the min and max year across births and deaths.
O(m): Create an array of size max_yr - min_yr + 1, ints initialized to zero. 
      Treat the first cell of the array as min_yr, the next as min_yr+1, etc...
O(n): Parse the births array, incrementing the appropriate index of the array. 
      arr[birth_yr - min_yr] += 1
O(n): Ditto for deaths, decrementing the appropriate index of the array.
      arr[death_yr - min_yr] -= 1
O(m): Parse your array, keeping track of the cumulative sum and its max value.

Самый большой совокупный максимум - ваш ответ.

Время выполнения O (n + m). ), а необходимое дополнительное пространство - O (m).

Это линейное решение по n, если m равно O (n); то есть, если диапазон лет не растет быстрее, чем число рождений и смертей. Это почти наверняка верно для данных реального мира.

4 голосов
/ 24 февраля 2020

Я думаю, что мы можем получить O(n log n) время с O(1) дополнительным пространством, сначала отсортировав, а затем сохранив текущую совокупность и глобальный максимум во время итерации. Я пытался использовать текущий год в качестве ориентира, но logi c все еще казался немного хитрым, поэтому я не уверен, что он полностью сработал. Надеемся, что это может дать представление о подходе.

JavaScript код (контрпримеры / ошибки приветствуются)

function f(births, deaths){
  births.sort((a, b) => a - b);
  deaths.sort((a, b) => a - b);

  console.log(JSON.stringify(births));
  console.log(JSON.stringify(deaths));
  
  let i = 0;
  let j = 0;
  let year = births[i];
  let curr = 0;
  let max = curr;

  while (deaths[j] < births[0])
    j++;

  while (i < births.length || j < deaths.length){
    while (year == births[i]){
      curr = curr + 1;
      i = i + 1;
    }
    
    if (j == deaths.length || year < deaths[j]){
      max = Math.max(max, curr);
      console.log(`year: ${ year }, max: ${ max }, curr: ${ curr }`);
    
    } else if (j < deaths.length && deaths[j] == year){
      while (deaths[j] == year){
        curr = curr - 1;
        j = j + 1;
      }
      max = Math.max(max, curr);
      console.log(`year: ${ year }, max: ${ max }, curr: ${ curr }`);
    }

    if (j < deaths.length && deaths[j] > year && (i == births.length || deaths[j] < births[i])){
      year = deaths[j];
      while (deaths[j] == year){
        curr = curr - 1;
        j = j + 1;
      }
      console.log(`year: ${ year }, max: ${ max }, curr: ${ curr }`);
    }

    year = births[i];
  }
  
  return max;
}

var input = [
  [[1997, 1997, 1997, 1998, 1999],
  [1998, 1999]],
  [[1, 2, 2, 3, 4],
  [1, 2, 2, 5]],
  [[1984, 1981, 1984, 1991, 1996],
  [1991, 1984, 1997]],
  [[1984, 1981, 1984, 1991, 1996],
  [1991, 1982, 1984, 1997]]
]

for (let [births, deaths] of input)
  console.log(f(births, deaths));

Если диапазон года, m, имеет порядок n, мы могли бы хранить значения для каждого года в диапазоне и иметь время O(n) сложность. Если бы мы хотели получить фантазию, мы могли бы также иметь O(n * log log m) сложность времени, используя Y-fast tr ie, который позволяет искать преемника за O(log log m) время.

3 голосов
/ 03 марта 2020

Я решил эту проблему с требованием памяти O(n+m) [в худшем случае, в лучшем случае O(n)]

и со сложностью времени O(n logn).

Здесь, n & m - это длина массивов births и deaths.

Я не знаю PHP или javascript. Я реализовал это с Java, и лог c очень прост. Но я верю, что моя идея может быть реализована и на этих языках.

Подробности техники:

Я использовал структуру java TreeMap для хранения записей о рождении и смерти .

TreeMap вставляет данные отсортировано ( на основе ключа ) в виде пары (ключ, значение), здесь ключ - год и значение это совокупная сумма рождений и смертей (отрицательная для смертей).

Нам не нужно , чтобы вставить значение смертей, которое произошло после наивысшего года рождения.

После заполнения TreeMap с рождениями и записи о смертях, все кумулятивные суммы обновляются и хранят максимальную численность населения с годами, по мере его развития.

Пример ввода и вывода: 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]

Year counts Births: {1900=2, 1901=1, 1903=1, 1904=1, 1906=1, 1908=3, 1909=1, 1911=2, 1914=1, 1919=2}

Year counts Birth-Deaths combined: {1900=2, 1901=1, 1903=1, 1904=1, 1906=1, 1908=3, 1909=1, 1910=-1, 1911=0, 1912=-1, 1913=-1, 1914=-2, 1915=-2, 1919=2}

Yearwise population: {1900=2, 1901=3, 1903=4, 1904=5, 1906=6, 1908=9, 1909=10, 1910=9, 1911=9, 1912=8, 1913=7, 1914=5, 1915=3, 1919=5}

maxPopulation: 10
yearOfMaxPopulation: 1909

Пример ввода и вывода: 2

Births: [1906, 1901, 1911, 1902, 1905, 1911, 1902, 1905, 1910, 1912, 1900, 1900, 1904, 1913, 1904]

Deaths: [1917, 1908, 1918, 1915, 1907, 1907, 1917, 1917, 1912, 1913, 1905, 1914]

Year counts Births: {1900=2, 1901=1, 1902=2, 1904=2, 1905=2, 1906=1, 1910=1, 1911=2, 1912=1, 1913=1}

Year counts Birth-Deaths combined: {1900=2, 1901=1, 1902=2, 1904=2, 1905=1, 1906=1, 1907=-2, 1908=-1, 1910=1, 1911=2, 1912=0, 1913=0}

Yearwise population: {1900=2, 1901=3, 1902=5, 1904=7, 1905=8, 1906=9, 1907=7, 1908=6, 1910=7, 1911=9, 1912=9, 1913=9}

maxPopulation: 9
yearOfMaxPopulation: 1906

Здесь, смерти произошли (1914 & later) после последнего года рождения 1913, вообще не учитывались, что позволяет избежать ненужных вычислений.

В общей сложности 10 million данных (совокупность рождений и смертей) и более 1000 years range, программе потребовалось около 3 sec. до финиша sh.

Если данные одинакового размера с 100 years range, потребовалось 1.3 sec.

Все входы выбираются случайным образом.

3 голосов
/ 03 марта 2020

Сначала объедините рождения и смерти в карту (year => population change), отсортируйте их по ключу и рассчитайте текущее население по этому.

Это должно быть приблизительно O(2n + n log n), где n - это количество рождений.

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

function highestPopulationYear(array $births, array $deaths): ?int
{
    $indexed = [];

    foreach ($births as $birth) {
        $indexed[$birth] = ($indexed[$birth] ?? 0) + 1;
    }

    foreach ($deaths as $death) {
        $indexed[$death] = ($indexed[$death] ?? 0) - 1;
    }

    ksort($indexed);

    $maxYear = null;
    $max = $current = 0;

    foreach ($indexed as $year => $change) {
        $current += $change;
        if ($current >= $max) {
            $max = $current;
            $maxYear = $year;
        }
    }

    return $maxYear;
}

var_dump(highestPopulationYear($births, $deaths));
1 голос
/ 02 марта 2020
$births = [1984, 1981, 1984, 1991, 1996];
$deaths = [1991, 1984];
$years = array_unique(array_merge($births, $deaths));
sort($years);

$increaseByYear = array_count_values($births);
$decreaseByYear = array_count_values($deaths);
$populationByYear = array();

foreach ($years as $year) {
    $increase = $increaseByYear[$year] ?? 0;
    $decrease = $decreaseByYear[$year] ?? 0;
    $previousPopulationTally = end($populationByYear);
    $populationByYear[$year] = $previousPopulationTally + $increase - $decrease;
}

$maxPopulation = max($populationByYear);
$maxPopulationYears = array_keys($populationByYear, $maxPopulation);

$maxPopulationByYear = array_fill_keys($maxPopulationYears, $maxPopulation);
print_r($maxPopulationByYear);

Это будет учитывать возможность связанного года, а также, если год чьей-либо смерти не соответствует чьему-либо рождению.

0 голосов
/ 08 марта 2020

Один из самых простых и понятных подходов к вашей проблеме.

$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);

output :

1909

сложность :

O(m + log(n))
0 голосов
/ 07 марта 2020

Я заполняю очень удобно это решение, сложность Big O составляет n + m

<?php
function getHighestPopulation($births, $deaths){
    $max = [];
    $currentMax = 0;
    $tmpArray = [];

    foreach($deaths as $key => $death){
        if(!isset($tmpArray[$death])){
            $tmpArray[$death] = 0;    
        }
        $tmpArray[$death]--;
    }
    foreach($births as $k => $birth){
        if(!isset($tmpArray[$birth])){
            $tmpArray[$birth] = 0;
        }
        $tmpArray[$birth]++;
        if($tmpArray[$birth] > $currentMax){
            $max = [$birth];
            $currentMax = $tmpArray[$birth];
        } else if ($tmpArray[$birth] == $currentMax) {
            $max[] = $birth;
        }
    }

    return [$currentMax, $max];
}

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

print_r (getHighestPopulation($births, $deaths));
?>
0 голосов
/ 05 марта 2020

Память имеет смысл сохранять currentPopulation и currentYear. Начать с сортировки массивов $births и $deaths - это очень хороший момент, потому что сортировка пузырьков не такая уж тяжелая задача, но позволяет срезать некоторые углы:

<?php

$births = [1997, 1999, 2000];
$deaths = [2000, 2001, 2001];

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

    // sort takes time, but is neccesary for futher optimizations
    sort($births);
    sort($deaths);

    // first death year is a first year where population might decrase 
    // sorfar max population
    $currentYearComputing = $deaths[0];

    // year before first death has potential of having the biggest population
    $maxY = $currentYearComputing-1;

    // calculating population at the begining of the year of first death, start maxPopulation
    $population = $maxPop = count(array_splice($births, 0, array_search($deaths[0], $births)));

    // instead of every time empty checks: `while(!empty($deaths) || !empty($births))`
    // we can control a target time. It reserves a memory, but this slot is decreased
    // every iteration.
    $iterations = count($deaths) + count($births);

    while($iterations > 0) {
        while(current($births) === $currentYearComputing) {
            $population++;
            $iterations--;
            array_shift($births); // decreasing memory usage
        }

        while(current($deaths) === $currentYearComputing) {
            $population--;
            $iterations--;
            array_shift($deaths); // decreasing memory usage
        }

        if ($population > $maxPop) {
            $maxPop = $population;
            $maxY = $currentYearComputing;
        }

        // In $iterations we have a sum of birth/death events left. Assuming all 
        // are births, if this number added to currentPopulation will never exceed
        // current maxPoint, we can break the loop and save some time at cost of
        // some memory.
        if ($maxPop >= ($population+$iterations)) {
            break;
        }

        $currentYearComputing++;
    }

    return $maxY;
}

echo highestPopulationYear($births, $deaths);

не очень-то увлечен погружением в Большой O вещь, оставленная вам.

Кроме того, если вы заново открываете currentYearComputing каждый l oop, вы можете изменить циклы на if операторы и оставить только один l oop.

    while($iterations > 0) {

        $changed = false;

        if(current($births) === $currentYearComputing) {
            // ...
            $changed = array_shift($births); // decreasing memory usage
        }

        if(current($deaths) === $currentYearComputing) {
            // ...
            $changed = array_shift($deaths); // decreasing memory usage
        }

        if ($changed === false) {
            $currentYearComputing++;
            continue;
        }
...