Как решить проблему с кодом Counting Valleys от HackerRank с помощью JavaScript

Проблема

Задача «Подсчет долин» - подсчитать количество долин, по которым проходит Гэри путешественник:

  • Гэри = Путешественник
  • Уровень моря 0 - Также начальный уровень
  • S = Строка описания, которая представляет собой путь шагов, которые предпринимает путешественник Гэри.
  • U и D - это «Вверх» и «Вниз» соответственно, а также направление шага Гэри.
  • N = количество шагов от 2 до 10⁶ (1000000)
  • AR - это одна строка чисел с интервалом от 1 до 100 - ex 10 11 20 31
  • N - количество значений в шагах на пути от 2 до 1 000 000 (что может оказаться бесполезным, если мы просто вычисляем длину массива).
  • Долина определяется как спускающаяся ниже уровня моря, а затем возвращающаяся к уровню моря.

Пример 1:

N = 8
S = UDDDUDUU
_/\      _
   \    /
    \/\/
Result: 1 Valley

Пример 2:

N = 10
S = UDDDUDUUDU
_/\      _  _
   \    / \/
    \/\/
Result: 2 Valleys

Цель

Напишите функцию или функции, которые возвращают общее количество впадин, найденных путем обхода строкового пути (S) шагов

Покрытие наших баз

Прикрывая наши базы, мы должны убедиться, что:

  • Количество элементов (значений) S находится в диапазоне от 2 до 10⁶.
  • N - целое число от 2 до 10⁶
  • N = общее количество значений S

Подсчет ценностей пути (S)

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

function countingValleys(n, s) {
     // setting the constraints
     const min = 2;
     const max = 1000000;
     
     // if it's a string convert it to an array
     // ex "UDU" = ["U", "D", "U"]    
     s = (typeof ar === "string") ? s.split('') : s;
     // check if s meets the requirements
     if (s.length >= min && s.length <= max) {
          // continue
     }
}

Проверка N

Затем нам нужно убедиться, что N является целым числом и соответствует тому же количеству значений пути (S).

function countingValleys(n, s) {
     // setting the constraints
     const min = 2;
     const max = 1000000;
     
     // if it's a string convert it to an array
     // ex "UDU" = ["U", "D", "U"]    
     s = (typeof s === "string") ? s.split('') : s;
 
     // check if s meets the requirements
     if (s.length >= min
          && s.length <= max
          && n === parseInt(n, 0)
          && n >= min
          && n <= max 
          && n === s.length) {
          // continue
     } 
}

Понимание проблемы

Чтобы получить лучшее представление о направлениях, мы могли бы по-другому взглянуть на вещи: «Вверх» (U) будет +1, а «Вниз» (D) будет -1. Если определение долины - спуститься ниже уровня моря, а затем вернуться к уровню моря, наша цель - найти способ начать отсчет только тогда, когда мы выполним это условие.

Преобразование шагов в целые числа

Преобразование массива в целые числа - это первый шаг к получению указаний.

// Example
// n = 8
// s = "UDDDUDUU"
function countingValleys(n, s) {
     // setting the constraints
     const min = 2;
     const max = 1000000;
     
     // if it's a string convert it to an array
     // ex "UDU" = ["U", "D", "U"]    
     s = (typeof s === "string") ? s.split('') : s;
     // ["U", "D", "D", "D", "U", "D", "U", "U"] 
 
     // check if s meets the requirements
     if (s.length >= min
          && s.length <= max
          && n === parseInt(n, 0)
          && n >= min
          && n <= max 
          && n === s.length) {
          
          // converting the array steps to integers
          s = s.map(steps => ((steps === "U") ? 1 : -1));
          // [1, -1, -1, -1, 1, -1, 1, 1]
     } 
}

Зацикливание на шагах

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

// Example
// n = 8
// s = "UDDDUDUU"
function countingValleys(n, s) {
     // setting the constraints
     const min = 2;
     const max = 1000000;
     
     // if it's a string convert it to an array
     // ex "UDU" = ["U", "D", "U"]    
     s = (typeof s === "string") ? s.split('') : s;
     // ["U", "D", "D", "D", "U", "D", "U", "U"] 
 
     // check if s meets the requirements
     if (s.length >= min
          && s.length <= max
          && n === parseInt(n, 0)
          && n >= min
          && n <= max 
          && n === s.length) {
          
          // converting the array steps to integers
          s = s.map(steps => ((steps === "U") ? 1 : -1));
          // [1, -1, -1, -1, 1, -1, 1, 1]
          
          let path = 0; 
          for(let i in s) {
               path += s[i];
          } 
          //  0 +  1 =  1
          //  1 + -1 =  0
          //  0 + -1 = -1
          // -1 + -1 = -2
          // -2 +  1 = -1
          // -1 + -1 = -2
          // -2 +  1 = -1
          // -1 +  1 =  0
          // initial = 0
          // end = 0 
     } 
}

Определение начальных условий для путей

Далее нам нужно обработать условие соответствия требованиям долины.

  • Ниже уровня моря (‹0)
  • Вернуться на уровень моря (= 0)
// Example
// n = 8
// s = "UDDDUDUU"
function countingValleys(n, s) {
     // setting the constraints
     const min = 2;
     const max = 1000000;
     let valleys = 0;
 
     // if it's a string convert it to an array
     // ex "UDU" = ["U", "D", "U"]    
     s = (typeof s === "string") ? s.split('') : s;
     // ["U", "D", "D", "D", "U", "D", "U", "U"] 
 
     // check if s meets the requirements
     if (s.length >= min
          && s.length <= max
          && n === parseInt(n, 0)
          && n >= min
          && n <= max 
          && n === s.length) {
          
          // converting the array steps to integers
          s = s.map(steps => ((steps === "U") ? 1 : -1));
          // [1, -1, -1, -1, 1, -1, 1, 1]
          
          let path = 0;
          for(let i in s) {
               path += s[i];
               if (path < 0) {
                    // start of a valley 
               }
               if (path == 0) {
                    // end of valley, increase count
               }    
          } 
          //  0 +  1 =  1 (Moved up = valley not started)
          //  1 + -1 =  0 (Back to sea level = valley not started)
          //  0 + -1 = -1 (Below sea level = valley started)
          // -1 + -1 = -2 (Moved lower = still in valley
          // -2 +  1 = -1 (Moved up = still in valley)
          // -1 + -1 = -2 (Moved lower = still in valley)
          // -2 +  1 = -1 (Moved up = still in valley)
          // -1 +  1 =  0 (Back to sea level = 1 valley)
          // initial = 0
          // end = 0 
     } 
}

Учет того, что все еще нахожусь в долине

Теперь, когда мы определили начало и конец долины, нам также нужно учитывать, чтобы не считать долины, пока мы все еще находимся в долине.

// Example
// n = 8
// s = "UDDDUDUU"
function countingValleys(n, s) {
     // setting the constraints
     const min = 2;
     const max = 1000000;
     let valleys = 0;
     let isInValley = false;  
     
     // if it's a string convert it to an array
     // ex "UDU" = ["U", "D", "U"]    
     s = (typeof s === "string") ? s.split('') : s;
     // ["U", "D", "D", "D", "U", "D", "U", "U"] 
 
     // check if s meets the requirements
     if (s.length >= min
          && s.length <= max
          && n === parseInt(n, 0)
          && n >= min
          && n <= max 
          && n === s.length) {
          
          // converting the array steps to integers
          s = s.map(steps => ((steps === "U") ? 1 : -1));
          // [1, -1, -1, -1, 1, -1, 1, 1]
          
          let path = 0;
          for(let i in s) {
               path += s[i];
               if (path < 0 && !isInValley) {
                    // to check that we're not already in a valley 
                    // start of a valley
                    isInValley = true; 
               }
               if (path == 0 && isInValley) {
                    // to check if we're just coming out of a valley
                    // end of valley, increase count
                    valleys++; // increase count
                    isInValley = false; // reset isInValley
               }    
          } 
          //  0 +  1 =  1 (Moved up = valley not started)
          //  1 + -1 =  0 (Back to sea level = valley not started)
          //  0 + -1 = -1 (Below sea level = valley started)
          // -1 + -1 = -2 (Moved lower = still in valley
          // -2 +  1 = -1 (Moved up = still in valley)
          // -1 + -1 = -2 (Moved lower = still in valley)
          // -2 +  1 = -1 (Moved up = still in valley)
          // -1 +  1 =  0 (Back to sea level = 1 valley)
          // initial = 0
          // end = 0
     }
     
     // to make sure we return even when the req. are not met
     return valleys;
}

Давайте снова запустим те же значения сверху:

// Example 1
// n = 8
// s = "UDDDUDUU"
countingValleys(8, "UDDDUDUU");
// path = 1
// isInValley = false
// valleys = 0 
// path = 0
// isInValley = false
// valleys = 0
// path = -1
// isInValley = true
// valleys = 0
// path = -2
// isInValley = true
// valleys = 0
// path = -1
// isInValley = true
// valleys = 0
// path = -2
// isInValley = true
// valleys = 0
// path = -1
// isInValley = true
// valleys = 0
// path = 0
// isInValley = false
// valleys = 1
// Solution = 1

Вот пример 2 (почти такой же):

// Example 2
// n = 10
// s = UDDDUDUUDU
// ...
// path = 0
// isInValley = false
// valleys = 1
// path = -1
// isInValley = true
// valleys = 1
// path = 0
// isInValley = false
// valleys = 2
// Solution = 2

Рефакторинг для повышения производительности

Теперь, когда у нас есть решение, давайте реорганизуем цикл for с помощью более эффективных методов, таких как map и reduce .

До

function countingValleys(n, s) {
     const min = 2;
     const max = 1000000;
     let valleys = 0;
     let isInValley = false; 
     
     s = (typeof s === "string") ? s.split('') : s;
 
     if (s.length >= min
          && s.length <= max
          && n === parseInt(n, 0)
          && n >= min
          && n <= max 
          && n === s.length) {
          
          s = s.map(steps => ((steps === "U") ? 1 : -1));
          
          let path = 0;
          for(let i in s) {
               path += s[i];
               if (path < 0 && !isInValley) {
                    isInValley = true; 
               }
               if (path == 0 && isInValley) {
                    valleys++;
                    isInValley = false;
               }    
          } 
     }
      
     return valleys; 
}

После

function countingValleys(n, s) {
     const min = 2;
     const max = 1000000;
     let valleys = 0;
     let isInValley = false;
     s = (typeof s === "string") ? s.split('') : s;
 
     if (s.length >= min
          && s.length <= max
          && n === parseInt(n, 0)
          && n >= min
          && n <= max 
          && n === s.length) {
          
          // remove s = s.map because we're already iterating 
          s.map(steps => ((steps === "U") ? 1 : -1))
               .reduce((prev, next) => {
                    if (prev < 0 && !isInValley) {
                         isInValley = true;
                    }
                    if ((prev + next) === 0 && isInValley) {
                         valleys++;
                         isInValley = false;
                    }
                    // continue incrementing by adding
                    return prev + next;    
               });
     }
 
     return valleys;
}

Решение

И вот полное решение:

function countingValleys(n, s) {
     const min = 2;
     const max = 1000000;
     let isInValley = false;
     let valleys = 0;
     s = (typeof s === "string") ? s.split('') : s;
 
     if (s.length >= min
          && s.length <= max
          && n === parseInt(n, 0)
          && n >= min
          && n <= max 
          && n === s.length) {
          
          s.map(steps => ((steps === "U") ? 1 : -1))
               .reduce((prev, next) => {
                    if (prev < 0 && !isInValley) {
                         isInValley = true;
                    }
                    if ((prev + next) === 0 && isInValley) {
                         valleys++;
                         isInValley = false;
                    }

                    return prev + next;    
               });
     } 
     
     return valleys; 
}

Тестовые кейсы

// N = 8, S = "UDDDUDUU", Expected 1
// N = 12, S = "DDUUDDUDUUUD", Expected 2
// N = 1, S = "DU", Expected 0
// N = 2, S = "DU", Expected 1
// N = 3, S = "DDU", Expected 0
// N = 1000001, S = "DDU", Expected 0
// N = 20, S = "DDUUDDUUDDUUDDUUDDUU", Expected 5
// N = 10, S = "UUUUUDUUUU", Expected 0 
countingValleys(8, "UDDDUDUU"); // 1 ✅
countingValleys(12, "DDUUDDUDUUUD"); // 2 ✅
countingValleys(1, "DU"); // 0 ✅
countingValleys(2, " DU"); // 1 ✅
countingValleys(3, "DDU"); // 0 ✅
countingValleys(100001, "DDU"); // 0 ✅
countingValleys(20, "DDUUDDUUDDUUDDUUDDUU"); // 5 ✅
countingValleys(10, "UUUUUDUUUU"); // 0 ✅

Обратная связь?

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

Если вы получили от этого пользу, поделитесь им в Twitter 🐦 или других социальных сетях. Еще раз спасибо за чтение. 🙏

Также подпишитесь на меня в twitter: @codingwithmanny и instagram на @codingwithmanny.