Probleemi lahendamine Google'i intervjuust JavaScriptis: 4 erinevat viisi

Probleemi lahendamine Google'i intervjuust JavaScriptis: 4 erinevat viisi

Algoritmide toimivust uurides puutusin sellega kokku See on video Google'i nÀidisintervjuust.. 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 "Mobile Developer PRO".

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:

Allikas: www.habr.com

Ostke DDoS-kaitsega saitide jaoks usaldusvÀÀrne hostimine, VPS VDS-serverid đŸ”„ Osta usaldusvÀÀrne veebimajutus DDoS-kaitsega, VPS VDS serverid | ProHoster