так вот вопрос ниже, с моим ответом на него. Я знаю, что из-за двойного вложенного цикла for эффективность составляет O (n ^ 2), поэтому мне было интересно, есть ли способ улучшить большой O моего алгоритма / функции.
// Разработка алгоритма и написание кода для удаления повторяющихся символов в строке без использования дополнительного буфера. ПРИМЕЧАНИЕ. Можно использовать одну или две дополнительные переменные. Дополнительной копии массива нет.
function removeDuplicates(str) {
let arrayString = str.split("");
let alphabetArray = [["a", 0],["b",0],["c",0],["d",0],["e",0],["f",0],["g",0],["h",0],["i",0],["j",0],["k",0],["l",0],["m",0],["n",0],["o",0],["p",0],["q",0],["r",0],["s",0],["t",0],["u",0],["v",0],["w",0],["x",0],["y",0],["z",0]]
for (let i=0; i<arrayString.length; i++) {
findCharacter(arrayString[i].toLowerCase(), alphabetArray);
}
removeCharacter(arrayString, alphabetArray);
};
function findCharacter(character, array) {
for (let i=0; i<array.length; i++) {
if (array[i][0] === character) {
array[i][1]++;
}
}
}
function removeCharacter(arrString, arrAlphabet) {
let finalString = "";
for (let i=0; i<arrString.length; i++) {
for (let j=0; j<arrAlphabet.length; j++) {
if (arrAlphabet[j][1] < 2 && arrString[i].toLowerCase() == arrAlphabet[j][0]) {
finalString += arrString[i]
}
}
}
console.info("The string with removed duplicates is:", finalString)
}
removeDuplicates("Hippotamuus")
В вопросе упоминается «строка», а затем «массив»; так это ввод - строка или массив? И удаляем ли мы последовательные повторяющиеся символы или все повторяющиеся символы? Кроме того, я не думаю, что 26-буквенный массив считается «одной или двумя дополнительными переменными»; на самом деле вы используете много дополнительного места.
@ m69 26-буквенный массив по-прежнему остается постоянным пространством, поскольку он не зависит от длины ввода.
@ גלעדבר Это может быть постоянное пространство, но я не думаю, что это то, что подразумевается под "одной или двумя дополнительными переменными". Кроме того, подход с двумя указателями на месте возможен буквально с двумя простыми переменными (при условии, что вы получаете ввод в виде массива символов).
@ m69 вы предлагаете пространство O (1) и лучше, чем время O (n ^ 2) с массивом? Как?
@ גלעדברקן Я не уверен, что теоретический O (n ^ 2) можно улучшить, но это можно сделать намного проще (и, вероятно, быстрее на практике), чем код в вопросе.
@ גלעדברקן На самом деле, поскольку количество символов, которые вы должны проверить на наличие дубликата, никогда не превышает 26, вы можете утверждать, что временная сложность равна O (N), так же, как 26-буквенный массив является постоянным пространством.
@ m69 количество символов может исчисляться десятками тысяч, если мы разрешим Unicode и все языки мира.
@ גלעדברקן Это все еще константа :-)
@ m69 теоретически можно представить себе алфавит O (n).



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Коды символов ASCII / Unicode всех букв одного и того же регистра являются последовательными. Это позволяет провести важную оптимизацию: вы можете найти индекс символа в массиве подсчета символов по его символьному коду ASCII / Unicode. В частности, индекс символа c в массиве счетчика символов будет c.charCodeAt(0) - 'a'.charCodeAt(0). Это позволяет вам искать и изменять количество символов в массиве во времени O(1), что сокращает время выполнения алгоритма до O(n).
Основываясь на вашем описании, я предполагаю, что ввод - это строка (которая неизменна в javascript), и я не уверен, что именно означает «одна или две дополнительные переменные», поэтому, исходя из вашей реализации, я собираюсь предположить, что можно использовать O ( N) пробел. Чтобы улучшить временную сложность, я думаю, что реализации различаются в зависимости от различных требований к выводимой строке.
Предположим, что длина s равна N, в следующей реализации используется пространство O (N) и время O (N).
function removeDuplicates(s) {
const set = new Set(); // use set so that insertion and lookup time is o(1)
let res = "";
for (let i = 0; i < s.length; i++) {
if (!set.has(s[i])) {
set.add(s[i]);
res += s[i];
}
}
return res;
}
Вы можете использовать быстрая сортировка для сортировки на месте, а затем перебрать отсортированный массив, чтобы добавить в результат последний элемент. Обратите внимание, что вам может потребоваться сначала разбить строку на массив. Таким образом, реализация будет использовать пространство O (N), а средняя временная сложность будет O (NlogN)
Следующая реализация использует пространство O (N) и время O (N).
const removeDuplicates = function(s) {
const stack = []; // stack and set are in sync
const set = new Set(); // use set to make lookup faster
const lastPos = getLastPos(s);
let curVal;
let lastOnStack;
for (let i = 0; i < s.length; i++) {
curVal = s[i];
if (!set.has(curVal)) {
while(stack.length > 0 && stack[stack.length - 1] > curVal && lastPos[stack[stack.length - 1]] > i) {
set.delete(stack[stack.length - 1]);
stack.pop();
}
set.add(curVal);
stack.push(curVal);
}
}
return stack.join('');
};
const getLastPos = (s) => {
// get the last index of each unique character
const lastPosMap = {};
for (let i = 0; i < s.length; i++) {
lastPosMap[s[i]] = i;
}
return lastPosMap;
}
Я думаю, правильное предположение состоит в том, что персонажи должны оставаться в исходном порядке, так что «бегемоты» -> «гипотамусы». Кроме того, набор, на самом деле, не квалифицируется как "одна или две переменные" imho.
Спасибо за предложение @ m69. Я соответствующим образом обновил свой ответ.
Есть небольшой трюк «без использования какого-либо дополнительного буфера», хотя я не вижу способа улучшить сложность O(n^2) без использования хэш-карты, чтобы определить, был ли замечен конкретный символ. Уловка состоит в том, чтобы пройти по буферу входной строки (предположим, что это массив JavaScript, поскольку строки в JavaScript неизменяемы) и перезаписать текущий символ следующим уникальным символом, если текущий символ является дубликатом. Наконец, отметьте конец результирующей строки нулевым символом.
Псевдокод:
i = 1
pointer = 1
while string[i]:
if not seen(string[i]):
string[pointer] = string[i]
pointer = pointer + 1
i = i + 1
mark string end at pointer
Функция seen может либо взять время O(n) и пространство O(1), либо время O(1) и пространство O(|alphabet|), если мы используем хэш-карту.
Я не понимал, что имел в виду:
...without using any additional buffer.
Поэтому я подумал, что попробую сделать это за один цикл, и позволю вам сказать мне, если это неправильно.
Я работал на основе того, что предоставленная вами функция дает правильный результат, вы просто искали, чтобы она работала быстрее. Приведенная ниже функция дает правильный результат и работает намного быстрее с любой большой строкой с большим количеством дубликатов, которые я ей добавляю.
function removeDuplicates(originalString) {
let outputString = '';
let lastChar = '';
let lastCharOccurences = 1;
for (let char = 0; char < originalString.length; char++) {
outputString += originalString[char];
if (lastChar === originalString[char]) {
lastCharOccurences++;
continue;
}
if (lastCharOccurences > 1) {
outputString = outputString.slice(0, outputString.length - (lastCharOccurences + 1)) + originalString[char];
lastCharOccurences = 1;
}
lastChar = originalString[char];
}
console.info("The string with removed duplicates is:", outputString)
}
removeDuplicates("Hippotamuus")Опять же, извините, если я неправильно понял сообщение ...
Вы можете сначала отсортировать массив, а затем перебрать элементы, сохраняя символ «последний просмотр» как переменную, удаляя элементы, если они совпадают, и обновляя «последний просмотренный», если они не совпадают. Таким образом, ваш большой O равен (n logn) для сравнительного алгоритма сортировки и (n), если вы реализуете сортировку по основанию и сопоставляете буквы с числами