Att lösa ett problem frÄn en Google-intervju i JavaScript: 4 olika sÀtt

Att lösa ett problem frÄn en Google-intervju i JavaScript: 4 olika sÀtt

NÀr jag studerade algoritmernas prestanda stötte jag pÄ detta det hÀr Àr en video av en lÄtsasintervju med Google. Det ger dig inte bara en uppfattning om hur intervjuer genomförs pÄ stora teknikföretag, utan det lÄter dig ocksÄ förstÄ hur algoritmiska problem löses, och pÄ ett sÄ effektivt sÀtt som möjligt.

Den hÀr artikeln Àr ett slags ackompanjemang till videon. I den ger jag kommentarer om alla lösningar som visas, plus min egen version av lösningen i JavaScript. Nyanserna i varje algoritm diskuteras ocksÄ.

PÄminnelse: för alla lÀsare av "Habr" - en rabatt pÄ 10 000 rubel nÀr du anmÀler dig till nÄgon Skillbox-kurs med hjÀlp av "Habr"-kampanjkoden.

Skillbox rekommenderar: Praktisk kurs "Mobilutvecklare PRO".

Problem uttalande

Vi fÄr en ordnad array och ett specifikt vÀrde. De uppmanas sedan att skapa en funktion som returnerar sant eller falskt beroende pÄ om summan av tvÄ valfria tal i matrisen kan vara lika med ett givet vÀrde.

Med andra ord, finns det tvÄ heltal x och y i arrayen som, nÀr de adderas, Àr lika med det angivna vÀrdet?

Exempel A

Om vi ​​fĂ„r en array [1, 2, 4, 9] och vĂ€rdet 8, kommer funktionen att returnera false eftersom inga tvĂ„ tal i arrayen kan lĂ€gga till 8.

Exempel B

Men om det Àr en array [1, 2, 4, 4] och vÀrdet Àr 8, bör funktionen returnera sant eftersom 4 + 4 = 8.

Lösning 1: Brute force

Tidskomplexitet: O(NÂČ).
Rymdkomplexitet: O(1).

Den mest uppenbara betydelsen Àr anvÀndningen av ett par kapslade slingor.

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;
};

Denna lösning Àr inte effektiv eftersom den kontrollerar varje möjlig summa av tvÄ element i arrayen, och jÀmför ocksÄ varje par av index tvÄ gÄnger. (Till exempel, nÀr i = 1 och j = 2 Àr det faktiskt samma sak som att jÀmföra med i = 2 och j = 1, men i den hÀr lösningen försöker vi bÄda alternativen.)

Eftersom vĂ„r lösning anvĂ€nder ett par kapslade för loopar, Ă€r den kvadratisk med tidskomplexiteten O(NÂČ).


Lösning 2. BinÀr sökning

Tidskomplexitet: O(Nlog(N)).
Rymdkomplexitet: O(1)
.

Eftersom arrayerna Àr ordnade kan vi söka efter en lösning med hjÀlp av binÀr sökning. Detta Àr den mest effektiva algoritmen för sorterade arrayer. BinÀr sökning i sig har en körtid pÄ O(log(N)). Du mÄste dock fortfarande anvÀnda en for-loop för att kontrollera varje element mot alla andra vÀrden.

SÄ hÀr kan lösningen se ut. För att göra saker tydligt anvÀnder vi en separat funktion för att styra binÀr sökning. Och Àven funktionen removeIndex(), som returnerar versionen av arrayen minus det givna indexet.

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;
};

Algoritmen utgÄr frÄn index [0]. Den skapar sedan en version av arrayen exklusive det första indexet och anvÀnder binÀr sökning för att se om nÄgot av de ÄterstÄende vÀrdena kan lÀggas till arrayen för att fÄ den önskade summan. Denna ÄtgÀrd utförs en gÄng för varje element i arrayen.

SjÀlva for-slingan kommer att ha en linjÀr tidskomplexitet pÄ O(N), men inuti for-slingan gör vi en binÀr sökning, vilket ger en total tidskomplexitet pÄ O(Nlog(N)). Denna lösning Àr bÀttre Àn den tidigare, men det finns fortfarande utrymme för förbÀttringar.


Lösning 3. LinjÀr tid

Tidskomplexitet: O(N).
Rymdkomplexitet: O(1).

Nu ska vi lösa problemet och komma ihÄg att arrayen Àr sorterad. Lösningen Àr att ta tvÄ siffror: ett i början och ett i slutet. Om resultatet skiljer sig frÄn det som krÀvs, Àndrar vi start- och slutpunkterna.

SÄ smÄningom kommer vi antingen att möta det önskade vÀrdet och returnera sant, eller sÄ kommer start- och slutpunkterna att konvergera och returnera falskt.

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;
};


Nu Àr allt bra, lösningen verkar vara optimal. Men vem kommer att garantera att arrayen var bestÀlld?

Vad hÀnder dÄ?

Vid första anblicken kunde vi helt enkelt sortera arrayen först och sedan anvÀnda lösningen ovan. Men hur kommer detta att pÄverka genomförandetiden?

Den bĂ€sta algoritmen Ă€r quicksort med tidskomplexitet O(Nlog(N)). Om vi ​​anvĂ€nder den i vĂ„r optimala lösning kommer den att Ă€ndra dess prestanda frĂ„n O(N) till O(Nlog(N)). Är det möjligt att hitta en linjĂ€r lösning med en oordnad array?

Lösning 4

Tidskomplexitet: O(N).
Utrymmeskomplexitet: O(N).

Ja, det finns en linjÀr lösning, den krÀver att man skapar en ny array som innehÄller listan över matchningar vi letar efter. AvvÀgningen hÀr Àr mer minnesanvÀndning: detta Àr den enda lösningen i tidningen med rymdkomplexitet större Àn O(1).

Om det första vÀrdet i den givna arrayen Àr 1 och sökvÀrdet Àr 8, kan vi lÀgga till vÀrdet 7 till arrayen "search values".

Sedan, nÀr vi bearbetar varje element i arrayen, kan vi kontrollera arrayen av "sökvÀrden" och se om ett av dem Àr lika med vÄrt vÀrde. Om ja, returnera sant.

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;
};

KÀrnan i lösningen Àr for-loopen, som, som vi sÄg ovan, har linjÀr tidskomplexitet O(N).

Den andra iterationsdelen av vÄr funktion Àr Array.prototype.include() , en JavaScript-metod som returnerar true eller false beroende pÄ om arrayen innehÄller det givna vÀrdet.

För att ta reda pÄ tidskomplexiteten för Array.prototype.includes() kan vi övervÀga polyfillen som tillhandahÄlls av MDN (och skriven i JavaScript), eller anvÀnda metoden i kÀllkoden för en JavaScript-motor som Google V8 (C++).

// 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;
    }
  });
}

HÀr Àr iterationsdelen av Array.prototype.include() while-loopen i steg 7, som (nÀstan) korsar hela lÀngden av den givna arrayen. Detta innebÀr att dess tidskomplexitet ocksÄ Àr linjÀr. Tja, eftersom det alltid Àr ett steg bakom vÄr huvudmatris, Àr tidskomplexiteten O(N + (N - 1)). Med hjÀlp av Big O Notation förenklar vi det till O(N) - eftersom det Àr N som har störst inverkan nÀr indatastorleken ökar.

NÀr det gÀller rymdkomplexitet behövs en extra matris vars lÀngd speglar den ursprungliga matrisen (minus ett, ja, men det kan ignoreras), vilket resulterar i en rymdkomplexitet pÄ O(N). Tja, ökad minnesanvÀndning sÀkerstÀller maximal effektivitet hos algoritmen.


Jag hoppas att du tycker att den hÀr artikeln Àr anvÀndbar som ett tillÀgg till din videointervju. Den visar att ett enkelt problem kan lösas pÄ flera olika sÀtt med olika mÀngd resurser som anvÀnds (tid, minne).

Skillbox rekommenderar:

KĂ€lla: will.com

Köp pĂ„litlig hosting för webbplatser med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster