
Algoritmide toimivust uurides puutusin sellega kokku . See mitte ainult ei anna aimu sellest, kuidas suurtes tehnoloogiakorporatsioonides intervjuusid lÀbi viiakse, vaid vÔimaldab ka mÔista, kuidas algoritmilisi probleeme vÔimalikult tÔhusalt lahendatakse.
See artikkel on omamoodi kaas videole. Selles kommenteerin kĂ”iki nĂ€idatud lahendusi ja oma JavaScripti lahendust. Arutatakse ka iga algoritmi nĂŒansse.
Tuletame meelde: kÔigile "Habr" lugejatele - allahindlus 10 000 rubla, kui registreerute mis tahes Skillboxi kursusele, kasutades sooduskoodi "Habr".
Skillbox soovitab: Praktiline kursus .
Probleemi avaldus
Meile antakse jÀrjestatud massiiv ja konkreetne vÀÀrtus. SeejÀrel palutakse luua funktsioon, mis tagastab tÔese vÔi vÀÀra sÔltuvalt sellest, kas massiivi kahe arvu summa vÔib vÔrduda antud vÀÀrtusega.
TeisisÔnu, kas massiivis on kaks tÀisarvu, x ja y, mis kokku liitmisel vÔrdub mÀÀratud vÀÀrtusega?
NĂ€ide A
Kui meile antakse massiiv [1, 2, 4, 9] ja vÀÀrtus on 8, tagastab funktsioon vÀÀrtuse VÀÀr, kuna ĂŒkski massiiv ei saa anda 8-ni.
NĂ€ide B
Aga kui see on massiiv [1, 2, 4, 4] ja vÀÀrtus on 8, peaks funktsioon tagastama vÀÀrtuse tÔene, kuna 4 + 4 = 8.
Lahendus 1: jÔhker jÔud
Aja keerukus: O(NÂČ).
Ruumi keerukus: O(1).
KÔige ilmsem tÀhendus on pesastatud silmuste paari kasutamine.
const findSum = (arr, val) => {
for (let i = 0; i < arr.length; i++) {
for (let j = 0; j < arr.length; j++) {
if (i !== j && arr[i] + arr[j] === val) {
return true;
};
};
};
return false;
};See lahendus ei ole tÔhus, kuna see kontrollib massiivi kahe elemendi iga vÔimalikku summat ja vÔrdleb ka iga indeksi paari kaks korda. (NÀiteks kui i = 1 ja j = 2 on tegelikult sama, mis i = 2 ja j = 1 vÔrdlemine, kuid selles lahenduses proovime mÔlemat varianti).
Kuna meie lahendus kasutab ahelate jaoks pesastatud paari, on see ruutkeskne O(NÂČ) aja keerukusega.
Lahendus 2: binaarne otsing
Aja keerukus: O(Nlog(N)).
Ruumi keerukus: O(1).
Kuna massiivid on jĂ€rjestatud, saame lahendust otsida kahendotsingu abil. See on jĂ€rjestatud massiivide jaoks kĂ”ige tĂ”husam algoritm. Binaarse otsingu enda tööaeg on O(log(N)). Siiski peate siiski kasutama for-tsĂŒklit, et kontrollida iga elementi kĂ”igi teiste vÀÀrtustega.
Siit saate teada, milline vÔib lahendus vÀlja nÀha. Asjade selgeks tegemiseks kasutame binaarse otsingu juhtimiseks eraldi funktsiooni. Ja ka funktsioon removeIndex(), mis tagastab massiivi versiooni miinus antud indeks.
const findSum = (arr, val) => {
for (let i = 0; i < arr.length; i++){
if (binarySearch(removeIndex(arr, i), val - arr[i])) {
return true;
}
};
return false;
};
const removeIndex = (arr, i) => {
return arr.slice(0, i).concat(arr.slice(i + 1, arr.length));
};
const binarySearch = (arr, val) => {
let start = 0;
let end = arr.length - 1;
let pivot = Math.floor(arr.length / 2);
while (start < end) {
if (val < arr[pivot]) {
end = pivot - 1;
} else if (val > arr[pivot]) {
start = pivot + 1;
};
pivot = Math.floor((start + end) / 2);
if (arr[pivot] === val) {
return true;
}
};
return false;
};Algoritm algab indeksist [0]. SeejĂ€rel loob see massiivi versiooni, mis ei sisalda esimest indeksit, ja kasutab binaarset otsingut, et nĂ€ha, kas soovitud summa saamiseks saab massiivile lisada mĂ”nda ĂŒlejÀÀnud vÀÀrtust. Seda toimingut tehakse ĂŒks kord iga massiivi elemendi jaoks.
For-silmuse enda lineaarne ajaline keerukus on O(N), kuid for-tsĂŒkli sees teostame binaarse otsingu, mis annab ĂŒldise aja keerukuse O(Nlog(N)). See lahendus on parem kui eelmine, kuid arenguruumi on veel.
Lahendus 3: lineaarne aeg
Aja keerukus: O(N).
Ruumi keerukus: O(1).
NĂŒĂŒd lahendame probleemi, pidades meeles, et massiiv on sorteeritud. Lahenduseks on vĂ”tta kaks numbrit: ĂŒks alguses ja teine ââlĂ”pus. Kui tulemus erineb nĂ”utavast, siis muutke algus- ja lĂ”pp-punkti.
LÔpuks leiame soovitud vÀÀrtuse ja tagastame tÔese vÔi lÀhte- ja lÔpp-punktid lÀhenevad ja tagastavad vale.
const findSum = (arr, val) => {
let start = 0;
let end = arr.length - 1;
while (start < end) {
let sum = arr[start] + arr[end];
if (sum > val) {
end -= 1;
} else if (sum < val) {
start += 1;
} else {
return true;
};
};
return false;
};NĂŒĂŒd on kĂ”ik korras, lahendus tundub optimaalne. Aga kes saab garanteerida, et massiiv on tellitud?
Mis siis?
Esmapilgul oleksime vĂ”inud lihtsalt massiivi esmalt tellida ja seejĂ€rel kasutada ĂŒlaltoodud lahendust. Aga kuidas see tĂ€itmisaega mĂ”jutab?
Parim algoritm on kiirsortimine ajalise keerukusega O (Nlog (N)). Kui kasutame seda oma optimaalses lahenduses, muudab see oma jÔudlust O(N) asemel O(Nlog(N)). Kas jÀrjestamata massiiviga on vÔimalik leida lineaarne lahendus?
4 lahendus
Aja keerukus: O(N).
Ruumi keerukus: O(N).
Jah, on olemas lineaarne lahendus; selleks peame looma uue massiivi, mis sisaldab otsitavate vastete loendit. Kompromiss on siin suurem mÀlukasutus: see on ainus lahendus paberil, mille ruumi keerukus on suurem kui O(1).
Kui antud massiivi esimene vÀÀrtus on 1 ja otsitav vÀÀrtus on 8, saame massiivile "otsinguvÀÀrtused" lisada vÀÀrtuse 7.
SeejĂ€rel saame massiivi iga elemendi töötlemisel kontrollida "otsinguvÀÀrtuste" massiivi ja nĂ€ha, kas ĂŒks neist on vĂ”rdne meie vÀÀrtusega. Kui jah, tagasta tĂ”ene.
const findSum = (arr, val) => {
let searchValues = [val - arr[0]];
for (let i = 1; i < arr.length; i++) {
let searchVal = val - arr[i];
if (searchValues.includes(arr[i])) {
return true;
} else {
searchValues.push(searchVal);
}
};
return false;
};Lahenduse aluseks on for-silmus, mille lineaarne ajaline keerukus, nagu eespool nÀgime, on O(N).
Funktsiooni teine ââiteratiivne osa on Array.prototype.include(), JavaScripti meetod, mis tagastab tĂ”ene vĂ”i vÀÀr, sĂ”ltuvalt sellest, kas massiiv sisaldab antud vÀÀrtust.
Array.prototype.includes() ajalise keerukuse vĂ€ljaselgitamiseks vĂ”ime vaadata MDN-i pakutavat polĂŒtĂ€itmist (ja kirjutatud JavaScriptis) vĂ”i kasutada meetodit JavaScripti mootori, nĂ€iteks Google V8 (C++) lĂ€htekoodis.
// https://tc39.github.io/ecma262/#sec-array.prototype.includes
if (!Array.prototype.includes) {
Object.defineProperty(Array.prototype, 'includes', {
value: function(valueToFind, fromIndex) {
if (this == null) {
throw new TypeError('"this" is null or not defined');
}
// 1. Let O be ? ToObject(this value).
var o = Object(this);
// 2. Let len be ? ToLength(? Get(O, "length")).
var len = o.length >>> 0;
// 3. If len is 0, return false.
if (len === 0) {
return false;
}
// 4. Let n be ? ToInteger(fromIndex).
// (If fromIndex is undefined, this step produces the value 0.)
var n = fromIndex | 0;
// 5. If n â„ 0, then
// a. Let k be n.
// 6. Else n < 0,
// a. Let k be len + n.
// b. If k < 0, let k be 0.
var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
function sameValueZero(x, y) {
return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
}
// 7. Repeat, while k < len
while (k < len) {
// a. Let elementK be the result of ? Get(O, ! ToString(k)).
// b. If SameValueZero(valueToFind, elementK) is true, return true.
if (sameValueZero(o[k], valueToFind)) {
return true;
}
// c. Increase k by 1.
k++;
}
// 8. Return false
return false;
}
});
}Siin on Array.prototype.include() iteratiivne osa 7. sammus while-tsĂŒkkel, mis (peaaegu) lĂ€bib antud massiivi kogu pikkuse. See tĂ€hendab, et ka selle ajaline keerukus on lineaarne. Noh, kuna see on alati meie pĂ”himassiivist sammu vĂ”rra maas, on ajaline keerukus O (N + (N - 1)). Kasutades suurt O-tĂ€histust, lihtsustame selle vÀÀrtuseks O(N), sest just N-l on sisendi suuruse suurendamisel suurim mĂ”ju.
Ruumilise keerukuse osas on vaja tĂ€iendavat massiivi, mille pikkus peegeldab algset massiivi (miinus ĂŒks, jah, kuid seda vĂ”ib ignoreerida), mille tulemuseks on O(N) ruumiline keerukus. Suurem mĂ€lukasutus tagab algoritmi maksimaalse efektiivsuse.
Loodan, et artikkel on teile kasulik videointervjuu tÀiendusena. See nÀitab, et lihtsat probleemi saab lahendada mitmel erineval viisil, kasutades erinevaid ressursse (aeg, mÀlu).
Skillbox soovitab:
- Rakendatud veebikursus .
- Veebikursus .
- Praktilise aasta kursus .
Allikas: www.habr.com
