Недавно я прошел испытание кода HackerRank под названием Шерлок и действительная строка. Это интересная задача, и я работал над ее решением с JavaScript
. В то время как в моем решении используется Object literal
(хеш-карта), Set
и два for loop
для мускулов, мозг, безусловно, исходит из концептуальной логики, представленной условными выражениями. Элементарно!
Шерлок и верная строка
Постановка задачи:
В постановке задачи Шерлок представлен как главный инспектор string
s. Шерлок должен проверить каждый string
функцией. Данный string
считается действительным, если:
- все символы имеют одинаковую частоту (встречаются одинаковое количество раз)
- можно удалить одиночный символ, чтобы получить
string
, в котором все символы имеют одинаковую частоту
Например:
const s = 'abcc' // this string is valid // a single (1) deletion of character 'c' // would produce a string of which each // character has the same frequency // {'a': 1, 'b': 1, 'c': 1} const s = 'aabbcc' // this string is valid // each character has the same frequency: // {'a': 2, 'b': 2, 'c': 2} const s = 'aaabc' // this string is NOT valid // a single (1) deletion of character 'a' // would produce a string of which each // character does NOT have the same frequency // {'a': 2, 'b': 1, 'c': 1}
Описание функции:
Функция isValid()
должна возвращать string
YES
, если ввод string
действителен, в противном случае она должна возвращать string
NO
.
Входные параметры:
s
:string
, состоящий из букв нижнего регистра (ascii[a–z]
)
Решение:
Во-первых, необходимо объявить функцию. Он примет единственный аргумент string
, представленный здесь как s
. По умолчанию будет возвращена строка NO
:
function isValid(s) { return 'NO' }
Затем необходимо записать частоту каждого символа ввода string
, чтобы была ссылка для проверки на соответствие требованиям. Правильный инструмент для работы - это хеш-карта. Создается экземпляр Object literal
, присвоенный переменной cMap
(c
для символа). Затем for loop
используется для перебора символов ввода string
и загрузки cMap
пар ключ-значение, представляющих каждый символ и его частоту. Это делается с помощью условного оператора, чтобы оценить, содержит ли cMap
символ, оцениваемый на каждой итерации. Если это так, его соответствующее значение увеличивается на 1
. В противном случае соответствующее значение устанавливается на 1
.
function isValid(s) { const cMap = {} for (let c of s) { cMap[c] ? cMap[c]++ : cMap[c] = 1 } console.log(cMap) return 'NO' } //////////////////////////////////////////////////////////////////// const s = 'abcdefghhgfedecba' isValid(s) //=> cMap {a: 2, b: 2, c: 2, d: 2, e: 3, f: 2, g: 2, h: 2}
Далее, требования к достоверности диктуют, что частота каждого символа должна быть одинаковой, или должно быть одно исключение, когда одиночное удаление дает желаемый результат. . Важная информация для оценки - это частоты. Хэш-карта была очень полезна для сбора и подсчета, но теперь значения - частоты - можно извлекать и использовать самостоятельно. Правильный инструмент для работы - это Set
. Создается новый Set
, которому присваивается Array
значений из cMap
, которые компилируются путем передачи cMap
методу Object.values()
. Set
присваивается переменной freqs
(для частот). Set
создает уникальный набор значений. В этом случае freqs
будет содержать только по одному символу каждой частоты, даже если несколько символов имеют одинаковую частоту. Это будет полезно для этой функции, о которой я расскажу в следующем разделе.
function isValid(s) { const cMap = {} for (let c of s) { cMap[c] ? cMap[c]++ : cMap[c] = 1 } const freqs = new Set(Object.values(cMap)) console.log(freqs) return 'NO' } //////////////////////////////////////////////////////////////////// const s = 'abcdefghhgfedecba' isValid(s) //=> Set {2, 3}
Далее, можно применить некоторую логику. Требования к достоверности допускают два сценария. Первый сценарий состоит в том, что каждый символ в строке input
имеет одинаковую частоту. Если это произойдет, значение, связанное с каждым символом в cMap
, будет таким же. Затем, когда Set
будет построен, он будет включать единственное число: единственную текущую частоту. Во втором сценарии существует одно исключение, в котором одиночный символ может быть удален для получения действительного string
. Это будет означать, что есть вторая частота, и ее можно уменьшить на 1
, чтобы равняться первой частоте. Если имеется более двух частот, т. Е. в cMap
более двух значений, потребуется более одного удаления для получения действительного string
, которое не соответствует требования к действительности.
Следовательно, можно определить, что допустимы два случая. Мы можем проверить эти случаи с помощью условных выражений: двух if
операторов. Каждый оператор if
проверяет свойство .size
freqs
. Если размер freqs
равен 1
, что указывает на то, что каждый символ ввода string
имеет одинаковую частоту, ввод string
считается допустимым, и функция может вернуть YES
и завершить работу. Если размер freqs
равен 2
, что указывает на наличие двух частотных символов, будет выполнена дополнительная логика для проверки ввода string
. Если размер freqs
больше, чем 2
, будет выполнен возврат по умолчанию NO
в конце функции.
function isValid(s) { const cMap = {} for (let c of s) { cMap[c] ? cMap[c]++ : cMap[c] = 1 } const freqs = new Set(Object.values(cMap)) if (freqs.size === 1) return 'YES' if (freqs.size === 2) { // additional validation logic } return 'NO' }
Далее необходимо реализовать логику для второго случая. Если есть две частоты символов, можно определить, что есть одно исключение и есть два условия действительности.
Первое условие - если значение одиночного исключения меньше, чем значение основной частоты, и оно равно 1
. Например:
const s = 'aabbc' //=> cMap {'a': 2, 'b': 2, 'c': 1} //=> freqs {2, 1} *note: a Set is constructed with insertion order // the exception is the lesser value of the two frequencies // it is equal to 1, allowing a single deletion to meet // the validity requirements
Второе условие - если значение одиночного исключения больше, чем значение основной частоты, и оно равно значению основной частоты, увеличенному на 1
, т.е. значение исключения минус 1
равно значению основной частоты. Например:
const s = 'aabbbcc' //=> cMap {'a': 2, 'b': 3, 'c': 2} //=> freqs {2, 3} // the exception is the greater value of the two frequencies // it is equal to the value of the majority frequency plus 1 // allowing a single deletion to meet the validity requirements
В случае наличия двух частот необходимо выполнить две необходимые операции.
- количество символов для каждой частоты необходимо определить
- необходимо определить разницу между двумя частотами
Минимальное - меньшее значение - и максимальное - большее значение - необходимо определить для каждого числа в freqs
. Затем, в свою очередь, минимум и максимум можно использовать для вычисления разницы между собой и количеством символов в cMap
, которые соответствуют каждой частоте. Во-первых, минимум и максимум можно вычислить с помощью функций Math.min()
и Math.max()
. Значения из freqs
будут извлечены и переданы каждой функции с помощью оператора распространения (…
), в каждом случае присваивая соответствующий номер переменным min
и max
.
function isValid(s) { const cMap = {} for (let c of s) { cMap[c] ? cMap[c]++ : cMap[c] = 1 } const freqs = new Set(Object.values(cMap)) if (freqs.size === 1) return 'YES' if (freqs.size === 2) { const max = Math.max(...freqs) const min = Math.min(...freqs) console.log('max =', max) console.log('min =', min) } return 'NO' } //////////////////////////////////////////////////////////////////// const s = 'abcdefghhgfedecba' isValid(s) //=> max = 3 //=> min = 2
Затем необходимо определить количество символов в cMap
, которые соответствуют каждой частоте, т.е., сколько раз встречается каждая из двух частот. Это можно определить, перебирая cMap
и увеличивая счетчик для каждой частоты по мере ее появления. Два счетчика могут быть инициализированы значением 0
и назначены двум переменным: maxCt
(Ct
для счетчика) и minCt
. Затем for loop
можно использовать с двумя операторами if
для соответствующего увеличения счетчиков. Если значение, связанное с ключом, оцениваемым на каждой итерации, равно max
, то maxCt
увеличивается на 1
. Если он равен min
, то minCt
увеличивается на 1
.
function isValid(s) { const cMap = {} for (let c of s) { cMap[c] ? cMap[c]++ : cMap[c] = 1 } const freqs = new Set(Object.values(cMap)) if (freqs.size === 1) return 'YES' if (freqs.size === 2) { const max = Math.max(...freqs) const min = Math.min(...freqs) let maxCt = 0 let minCt = 0 for (let c in cMap) { if (cMap[c] === max) maxCt++ if (cMap[c] === min) minCt++ } console.log('maxCt =', maxCt) console.log('minCt =', minCt) } return 'NO' } //////////////////////////////////////////////////////////////////// const s = 'abcdefghhgfedecba' isValid(s) //=> maxCt = 1 //=> minCt = 7
Далее, последний шаг - использовать значения, определенные для max
, min
, maxCt
и minCt
, чтобы определить действительность ввода string
на основе успешного выполнения одного из двух условий для второго случая - двух существующих частоты:
- значение одиночного исключения меньше, чем значение основной частоты, и оно равно
1
- значение одиночного исключения больше, чем значение основной частоты, и равно значению основной частоты, увеличенному на
1
Оператор if
может использоваться со сложным условием, и если оно оценивается как true
, вернуть YES
и завершить функцию. Сложное условие строится как два набора сравниваемых выражений, которые сами сравниваются с оператором Logical OR
(||
), что означает, что только один сравниваемый набор должен оцениваться как true
, чтобы установить оценку оператора if
на true
. Первый набор сравниваемых выражений будет использовать оператор Logical AND
(&&
), чтобы оценить, равно ли minCt
1
и min
равно 1
, т.е. есть одно исключение частоты, которое меньше, чем частота большинства и его можно устранить одним удалением. Второй набор сравниваемых выражений будет использовать оператор Logical AND
, чтобы оценить, равно ли maxCt
1
и max
равно значению основной частоты, увеличенному на 1
, т.е. есть одно исключение частоты, которое больше чем мажоритарная частота, и ее можно приравнять к мажоритарной частоте, вычитая 1
, одно удаление.
Полная функция:
function isValid(s) { const cMap = {} for (let c of s) { cMap[c] ? cMap[c]++ : cMap[c] = 1 } const freqs = new Set(Object.values(cMap)) if (freqs.size === 1) return 'YES' if (freqs.size === 2) { const max = Math.max(...freqs) const min = Math.min(...freqs) let maxCt = 0 let minCt = 0 for (let c in cMap) { if (cMap[c] === max) maxCt++ if (cMap[c] === min) minCt++ } if ( (minCt === 1 && min === 1) || (maxCt === 1 && max === min + 1) ) return 'YES' } return 'NO' } //////////////////////////////////////////////////////////////////// const s = 'abcdefghhgfedecba' isValid(s) //=> YES //////////////////////////////////////////////////////////////////// const s = 'aaaaabc' isValid(s) //=> NO
Проблема требует хорошего логического планирования для подготовки необходимых значений для оценки и определения правильного порядка условных выражений. Вышеупомянутой функции требуется несколько встроенных модулей: Set
, Object.values()
, Math.max()
и Math.min()
, но из-за ограничения 26
символьного алфавита и максимального Set
размера 26
- относительно небольшое число - их производительность не дорогая. Есть два не вложенных цикла, один из которых оценивается, только если в Set
есть две частоты. Это говорит о том, что временная сложность функции линейна: O(n)
, и, учитывая, что хранимые данные представляют собой одну хеш-карту и одну Set
, сложность пространства также составляет O(n)
. Надеюсь, вам понравилась эта статья, представленная в ней проблема кода и решение, которое я изложил. Удачного кодирования.
Github.com/dangrammer
connected.com/in/danieljromans
danromans.com