Постановка задачи

В очереди стоят N детей, и каждому ребенку присваивается рейтинг. Вы даете этим детям конфеты, следя за тем, чтобы каждый ребенок имел хотя бы одну конфету, а дети с более высоким рейтингом получали больше конфет, чем их соседи. Какой минимум конфет вы должны дать?

Примеры:

  • Ввод: [1, 0, 2] | Выход: 5 {Распространение: [2, 1, 2]}
  • Ввод: [1, 2, 2] | Результат: 4 {Распространение: [2, 1, 1]}
  • Ввод: [0, 2, 0, 6, 6] | Выход: 7 {Распространение: [1, 2, 1, 2, 1]}

Решение 🍬— Сложность времени и пространства O (n)

По сути, каждый ребенок должен оценить обоих соседей (предыдущего ребенка и следующего ребенка), чтобы найти количество конфет, которое он должен получить:

  • Если у ребенка больше, чем у соседа, то ребенок должен получить больше конфет, чем у соседа.
  • Если ребенок имеет рейтинг ниже или равен рейтингу соседа (ей), то соседей можно игнорировать, поскольку это не влияет на количество конфет данного ребенка.

Конкретные шаги алгоритма и его кода следующие:

  • Вперед: начните обход рейтингов с первого ребенка и обновите счетчик конфет следующего ребенка по пути. Если рейтинг текущего ребенка меньше, чем рейтинг следующего ребенка, то количество конфет следующего ребенка должно быть как минимум на на 1 больше, чем количество конфет текущего ребенка.

  • Обратный обход: начните обход рейтингов с последнего ребенка и обновите счетчик конфет предыдущего ребенка по пути. Если рейтинг текущего ребенка меньше, чем рейтинг предыдущего ребенка, то количество конфет предыдущего ребенка должно быть как минимум на на 1 больше, чем количество конфет текущего ребенка.
  • Подсчитайте конфеты: подсчитайте сумму подсчетов конфет для каждого ребенка для окончательного подсчета.
// Language: Java
// Time Complexity: O(n) 3 Linear traversals.
// Space Complexity: O(n) Array of candies.
public int candy(int[] ratings) {
      if (ratings.length < 2) {
        return ratings.length;
      }
        
      int[] candies = new int[ratings.length];
      Arrays.fill(candies, 1);
        
      // ** Step 1: Forward **
      for (int i=0; i<ratings.length-1; i++) {
        if (ratings[i] >= ratings[i+1]) {
          continue;
        }         
        candies[i+1] = candies[i] + 1;    
      }
        
      // ** Step 2: Backward **
      for (int i=ratings.length-1; i>0; i--) {
        if (ratings[i] >= ratings[i-1]) {
          continue;
        }
        candies[i-1] = Math.max(candies[i] + 1, candies[i-1]);   
      }
        
      // ** Step 3: Count Candies **
      int count = 0;
      for (int i=0; i<candies.length; i++) {
        count += candies[i];
      }
        
      return count;     
 }

Это вполне приемлемое решение, но мы можем сэкономить место, оптимизировав алгоритм в следующем разделе.

🍬 Оптимизация пространства - O (n) - ›O (1)

Введение в основы

Представим себе распределение входных данных [0, 4, 9, 25, 8, 6, 5] следующим образом:

Вот несколько важных наблюдений:

  • 📈 Наклон вверх (возрастающие числа) для [0, 4, 9, 25]
    length = 4.
  • Количество конфет в восходящем наклоне составляет 10 = 1 + 2 + 3 + 4, что является суммой последовательно увеличивающихся чисел с n = length = 4 (n * n + 1/2).
  • 📉 Наклон вниз (убывающие числа) для [25, 8, 6, 5] имеет длину = 4.
  • Количество конфет на нисходящем наклоне составляет 10 = 4 + 3 + 2 + 1, что является суммой последовательно увеличивающихся чисел с n = length = 4 (n * n + 1/2).
  • Пик для обоих склонов - средний ребенок с рейтингом 25.

Количество конфет для пика

Равные уклоны
Счетчик конфет для рейтинга 25 (пик) равен 4. Это длина самого длинного склона, на котором он является пиком. В этом примере оба склона с левой и правой стороны совпадают длиной 4.

Это имеет смысл, поскольку более длинный наклон означает больше ударов по 1, чтобы ребенок получил больше конфет, чем его сосед. В конце концов, ребенок на пике склона получит максимальное количество конфет.

Давайте поднимемся по нисходящему склону [0, 4, 9, 25, 8, 6, 5] и выполним несколько конфетных заданий:

  1. Рейтинг 5: 1 конфета; это минимальная сумма.
  2. Рейтинг 6: 2 конфеты ›рейтинг 5.
  3. Рейтинг 8: 3 конфеты ›рейтинг 6.
  4. Рейтинг 25: 4 конфеты ›рейтинг 8.

Однако проблема заключается в общем количестве конфет и не требует от нас поиска назначения конфет / детей, как описано выше.

Помня об этом, давайте спустимся по тому же спуску в [0, 4, 9, 25, 8, 6, 5] и выполним несколько конфетных заданий:

  1. Рейтинг 25: 4 конфеты ›рейтинг 9.
  2. Рейтинг 8: 1 конфета; это минимальная сумма.
    Это никак не повлияет на количество конфет на пике, так как в нем должно быть не менее 1 + 1 = 2 леденца, а у него уже есть 4.
  3. Рейтинг 6: 2 конфеты; нисходящий склон (без пика) имеет 2 детей [8,6]. В идеале с рейтингом 6 должна быть 1 конфета, а с рейтингом 8 - 2 конфеты, чтобы соблюсти ограничение: более высокий рейтинг = больше конфет. Поскольку нас не волнует точная пара «ребенок-леденец», такая замена безвредна.
    Это не влияет на количество леденцов на пике, так как в нем должно быть не менее 2 + 1 = 3 конфеты, а в нем уже 4 .
  4. Рейтинг 5: 3 конфеты; нисходящий наклон имеет 3 детей [8, 6, 5].
    В идеале рейтинг 5 должен иметь 1 конфету, рейтинг 6 должен иметь 2 конфеты, а рейтинг 8 должен иметь 3 конфеты, чтобы сохранить ограничение более высокого рейтинга = больше конфет. Поскольку нас не волнует точная пара «ребенок-леденец», такая замена безвредна.
    Это не влияет на количество леденцов на пике, так как в нем должно быть не менее 3 + 1 = 4 леденца, а у него уже есть 4 .

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

Неравномерные уклоны
Добавим в конец ребенка с рейтингом 4.

В дополнение к шагам, описанным в предыдущем разделе, присвойте 4 леденцам рейтинг 4, потому что нисходящий наклон теперь имеет 4 дочерних элемента [8, 6, 5, 4].

Влияет ли это на пик с рейтингом 25? В нем 4 конфеты. Учитывая наклон длиной 4 перед ним, у него должно быть 5 конфет для поддержания ограничения более высокого рейтинга = больше конфет.

Так как же это исправить?

Мы находим разницу наклона: если размер этого нисходящего наклона больше или равен восходящему наклону (4 ≥ 4), тогда прибавляем 1 (разница наклона) к количеству конфет. С выпуклостью 1, 4 фактически превратится в 5 на пике.

Давайте добавим еще одного ребенка с рейтингом 3 в конце.

В дополнение к шагам, описанным выше, присвойте 5 леденцам рейтинг 3, потому что у нисходящего склона теперь 5 детей [8, 6, 5, 4, 3].

Поскольку размер нисходящего наклона больше, чем восходящего (5≥4), добавьте 1 к общему количеству конфет. С ударом 1, 5 фактически превратится в 6 на пике.

Код алгоритма следующий:

// Language: Java
// Time Complexity: O(n) Linear traversal.
// Space Complexity: O(1) Constant space for variables.
public int candy(int[] ratings) {
      if (ratings.length < 2) {
          return ratings.length;
      }
         
      int count = 1;
      int upSlopeLength = 0;
      int upSlope = 0;
      int downSlope = 0;
         
      for (int i=1; i<ratings.length; i++) {
          int previous = ratings[i-1];
          int me = ratings[i];
          
          // ** Equal Ratings **   
          if (me == previous) {
              upSlope = 0;
              downSlope = 0;
              upSlopeLength = 0;
              count += 1;
          // ** Upward Slope **
          } else if (me > previous) {
              upSlope = upSlope == 0 ? 1 : upSlope;
              upSlope += 1;
              downSlope = 0;
              count += upSlope;
              upSlopeLength = upSlope;
          // ** Downward Slope **
          } else {
              upSlope = 0;
              downSlope += 1;
              int slopeDiff = 
                (downSlope + 1 - upSlopeLength) > 0 ? 1 : 0;
              count += downSlope + slopeDiff;         
          }     
      }
      return count;  
}

Если вы нашли эту статью полезной, пожалуйста, помогите мне привлечь больше учеников-разработчиков!

Покажите, дав «хлопки» 👏 на левом поле страницы (на рабочем столе) или внизу (на мобильном устройстве). Вы можете сделать это до 50 раз, нажимая постоянно!

Анум Малик

Следите за новостями NMTechBytes в Twitter, где я каждый день пишу о технических новинках :)

Специальная благодарность: