「面試必問」leetcode高頻題精選

image

引言(文末有福利)🏂

算法一直是大廠前端面試常問的一塊,而你們每每準備這方面的面試都是經過leetcode刷題。前端

我特意整理了幾道leetcode中「頗有意思」並且很是「高頻」的算法題目,分別給出了思路分析(帶圖解)和代碼實現。git

認真仔細的閱讀完本文,相信對於你在算法方面的面試必定會有不小的幫助!🤠github

兩數之和 🦊

題目難度 easy,涉及到的算法知識有數組、哈希表

題目描述

給定一個整數數組 nums  和一個目標值 target,請你在該數組中找出和爲目標值的那兩個整數,並返回他們的數組下標。面試

你能夠假設每種輸入只會對應一個答案。可是,數組中同一個元素不能使用兩遍。算法

示例:數組

給定 nums = [2, 7, 11, 15], target = 9

由於 nums[0] + nums[1] = 2 + 7 = 9
因此返回 [0, 1]

思路分析

大多數同窗看到這道題目,心中確定會想:這道題目太簡單了,不就兩層遍歷嘛:兩層循環來遍歷同一個數組;第一層循環遍歷的值記爲a,第二層循環時遍歷的值記爲b;若a+b = 目標值,那麼ab對應的數組下標就是咱們想要的答案。緩存

這種解法沒毛病,但有沒有優化的方案呢?🤔函數

要知道兩層循環不少狀況下都意味着O(n^2) 的複雜度,這個複雜度很是容易致使你的算法超時。即使沒有超時,在明明有一層遍歷解法的狀況下,你寫了兩層遍歷,面試官也會對你的印象分大打折扣。🤒post

其實咱們能夠在遍歷數組的過程當中,增長一個Map結構來存儲已經遍歷過的數字及其對應的索引值。而後每遍歷到一個新數字的時候,都回到Map裏去查詢targetNum與該數的差值是否已經在前面的數字中出現過了。若出現過,那麼答案已然顯現,咱們就沒必要再往下走了。學習

咱們就以本題中的例子結合圖片來講明一下上面提到的這種思路:

  • 這裏用對象diffs來模擬map結構:

    首先遍歷數組第一個元素,此時key爲 2,value爲索引 0

  • 往下遍歷,遇到了 7:

    計算targetNum和 7 的差值爲 2,去diffs中檢索 2 這個key,發現是以前出現過的值。那麼本題的答案就出來了!

代碼實現

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number[]}
 */
const twoSum = function (nums, target) {
  const diffs = {};
  // 緩存數組長度
  const len = nums.length;
  // 遍歷數組
  for (let i = 0; i < len; i++) {
    // 判斷當前值對應的 target 差值是否存在
    if (diffs[target - nums[i]] !== undefined) {
      // 如有對應差值,那麼獲得答案
      return [diffs[target - nums[i]], i];
    }
    // 若沒有對應差值,則記錄當前值
    diffs[nums[i]] = i;
  }
};

三數之和 🦁

題目難度 medium,涉及到的算法知識有數組、雙指針

題目描述

給你一個包含n個整數的數組nums,判斷nums中是否存在三個元素abc ,使得a + b + c = 0。請你找出全部知足條件且不重複的三元組。

注意:答案中不能夠包含重複的三元組。

示例:

給定數組 nums = [-1, 0, 1, 2, -1, -4],

知足要求的三元組集合爲:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

思路分析

和上面的兩數之和同樣,若是不認真思考,最快的方式可能就是多層遍歷了。但有了前車可鑑,咱們一樣能夠把求和問題變爲求差問題:固定其中一個數,在剩下的數中尋找是否有兩個數的和這個固定數相加是等於 0 的。

這裏咱們採用雙指針法來解決問題,相比三層循環,效率會大大提高。

雙指針法的適用範圍比較廣,通常像求和、比大小的均可以用它來解決。可是有一個前提:數組必須有序

所以咱們的第一步就是先將數組進行排序:

// 給 nums 排序
nums = nums.sort((a, b) => {
  return a - b;
});

而後對數組進行遍歷,每遍歷到哪一個數字,就固定當前的數字。同時左指針指向該數字後面的緊鄰的那個數字,右指針指向數組末尾。而後左右指針分別向中間靠攏:

每次指針移動一次位置,就計算一下兩個指針指向數字之和加上固定的那個數以後,是否等於 0。若是是,那麼咱們就獲得了一個目標組合;不然,分兩種狀況來看:

  • 相加之和大於 0,說明右側的數偏大了,右指針左移
  • 相加之和小於 0,說明左側的數偏小了,左指針右移

代碼實現

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
const threeSum = function (nums) {
  // 用於存放結果數組
  let res = [];
  // 目標值爲0
  let sum = 0;
  // 給 nums 排序
  nums = nums.sort((a, b) => {
    return a - b;
  });
  // 緩存數組長度
  const len = nums.length;
  for (let i = 0; i < len - 2; i++) {
    // 左指針 j
    let j = i + 1;
    // 右指針k
    let k = len - 1;
    // 若是遇到重複的數字,則跳過
    if (i > 0 && nums[i] === nums[i - 1]) {
      continue;
    }
    while (j < k) {
      // 三數之和小於0,左指針前進
      if (nums[i] + nums[j] + nums[k] < 0) {
        j++;
        // 處理左指針元素重複的狀況
        while (j < k && nums[j] === nums[j - 1]) {
          j++;
        }
      } else if (nums[i] + nums[j] + nums[k] > 0) {
        // 三數之和大於0,右指針後退
        k--;

        // 處理右指針元素重複的狀況
        while (j < k && nums[k] === nums[k + 1]) {
          k--;
        }
      } else {
        // 獲得目標數字組合,推入結果數組
        res.push([nums[i], nums[j], nums[k]]);

        // 左右指針一塊兒前進
        j++;
        k--;

        // 若左指針元素重複,跳過
        while (j < k && nums[j] === nums[j - 1]) {
          j++;
        }

        // 若右指針元素重複,跳過
        while (j < k && nums[k] === nums[k + 1]) {
          k--;
        }
      }
    }
  }

  // 返回結果數組
  return res;
};

盛最多水的容器 🥃

題目難度 medium,涉及到的算法知識有數組、雙指針

題目描述

給你 n 個非負整數 a1,a2,...,an,每一個數表明座標中的一個點  (i, ai) 。在座標內畫 n 條垂直線,垂直線 i  的兩個端點分別爲  (i, ai) 和 (i, 0)。找出其中的兩條線,使得它們與  x  軸共同構成的容器能夠容納最多的水。

說明:你不能傾斜容器,且  n  的值至少爲 2。

圖中垂直線表明輸入數組[1,8,6,2,5,4,8,3,7]。在此狀況下,容器可以容納水(表示爲藍色部分)的最大值爲 49。

示例:

輸入:[1,8,6,2,5,4,8,3,7]
輸出:49

思路分析

首先,咱們能快速想到的一種方法:兩兩進行求解,計算能夠承載的水量。 而後不斷更新最大值,最後返回最大值便可。

這種解法,須要兩層循環,時間複雜度是O(n^2)。這種相對來講比較暴力,對應就是暴力法

暴力法

/**
 * @param {number[]} height
 * @return {number}
 */
var maxArea = function (height) {
  let max = 0;
  for (let i = 0; i < height.length - 1; i++) {
    for (let j = i + 1; j < height.length; j++) {
      let area = (j - i) * Math.min(height[i], height[j]);
      max = Math.max(max, area);
    }
  }

  return max;
};

那麼有沒有更好的辦法呢?答案是確定有。

其實有點相似雙指針的概念,左指針指向下標 0,右指針指向length-1。而後分別從左右兩側向中間移動,每次取小的那個值(由於水的高度確定是以小的那個爲準)。

若是左側小於右側,則i++,不然j--(這一步其實就是取全部高度中比較高的,咱們知道面積等於長*寬)。對應就是雙指針 動態滑窗

雙指針 動態滑窗

/**
 * @param {number[]} height
 * @return {number}
 */
var maxArea = function (height) {
  let max = 0;
  let i = 0;
  let j = height.length - 1;
  while (i < j) {
    let minHeight = Math.min(height[i], height[j]);
    let area = (j - i) * minHeight;
    max = Math.max(max, area);
    if (height[i] < height[j]) {
      i++;
    } else {
      j--;
    }
  }
  return max;
};

爬樓梯 🎢

題目難度 easy,涉及到的算法知識有斐波那契數列、動態規劃。

題目描述

假設你正在爬樓梯。須要 n  階你才能到達樓頂。

每次你能夠爬 1 或 2 個臺階。你有多少種不一樣的方法能夠爬到樓頂呢?

注意:給定 n 是一個正整數。

示例 1:

輸入: 2
輸出: 2
解釋: 有兩種方法能夠爬到樓頂。
1.  1 階 + 1 階
2.  2 階

示例 2:

輸入: 3
輸出: 3
解釋: 有三種方法能夠爬到樓頂。
1.  1 階 + 1 階 + 1 階
2.  1 階 + 2 階
3.  2 階 + 1 階

思路分析

這道題目是一道很是高頻的面試題目,也是一道很是經典的斐波那契數列類型的題目。

解決本道題目咱們會用到動態規劃的算法思想-能夠分紅多個子問題,爬第 n 階樓梯的方法數量,等於 2 部分之和:

  • 爬上n−1階樓梯的方法數量。由於再爬 1 階就能到第 n 階
  • 爬上n−2階樓梯的方法數量,由於再爬 2 階就能到第 n 階

能夠獲得公式:

climbs[n] = climbs[n - 1] + climbs[n - 2];

同時須要作以下初始化:

climbs[0] = 1;
climbs[1] = 1;

代碼實現

/**
 * @param {number} n
 * @return {number}
 */
var climbStairs = function (n) {
  let climbs = [];
  climbs[0] = 1;
  climbs[1] = 1;
  for (let i = 2; i <= n; i++) {
    climbs[i] = climbs[i - 1] + climbs[i - 2];
  }
  return climbs[n];
};

環形鏈表 🍩

題目難度 easy,涉及到的算法知識有鏈表、快慢指針。

題目描述

給定一個鏈表,判斷鏈表中是否有環。

爲了表示給定鏈表中的環,咱們使用整數 pos 來表示鏈表尾鏈接到鏈表中的位置(索引從 0 開始)。 若是 pos 是 -1,則在該鏈表中沒有環。

示例 1:

輸入:head = [3,2,0,-4], pos = 1
輸出:true
解釋:鏈表中有一個環,其尾部鏈接到第二個節點。

示例 2:

輸入:head = [1,2], pos = 0
輸出:true
解釋:鏈表中有一個環,其尾部鏈接到第一個節點。

示例 3:

輸入:head = [1], pos = -1
輸出:false
解釋:鏈表中沒有環。

思路分析

鏈表成環問題也是很是經典的算法問題,在面試中也常常會遇到。

解決這種問題通常有常見的兩種方法:標誌法快慢指針法

標誌法

給每一個已遍歷過的節點加標誌位,遍歷鏈表,當出現下一個節點已被標誌時,則證實單鏈表有環。

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {boolean}
 */
var hasCycle = function (head) {
  while (head) {
    if (head.flag) return true;
    head.flag = true;
    head = head.next;
  }
  return false;
};

快慢指針(雙指針法)

設置快慢兩個指針,遍歷單鏈表,快指針一次走兩步,慢指針一次走一步,若是單鏈表中存在環,則快慢指針終會指向同一個節點,不然直到快指針指向null時,快慢指針都不可能相遇。

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {boolean}
 */
var hasCycle = function (head) {
  if (!head || !head.next) {
    return false;
  }
  let slow = head,
    fast = head.next;
  while (slow !== fast) {
    if (!fast || !fast.next) return false;
    fast = fast.next.next;
    slow = slow.next;
  }
  return true;
};

有效的括號 🍉

題目難度 easy,涉及到的算法知識有棧、哈希表。

題目描述

給定一個只包括'('')''{''}''['']'  的字符串,判斷字符串是否有效。

有效字符串需知足:

一、左括號必須用相同類型的右括號閉合。
二、左括號必須以正確的順序閉合。

注意空字符串可被認爲是有效字符串。

示例 1:

輸入: "()";
輸出: true;

示例  2:

輸入: "()[]{}";
輸出: true;

示例  3:

輸入: "(]";
輸出: false;

示例  4:

輸入: "([)]";
輸出: false;

示例  5:

輸入: "{[]}";
輸出: true;

思路分析

這道題能夠利用結構。

思路大概是:遇到左括號,一概推入棧中,遇到右括號,將棧頂部元素拿出,若是不匹配則返回 false,若是匹配則繼續循環。

第一種解法是利用switch case

switch case

/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function (s) {
  let arr = [];
  let len = s.length;
  if (len % 2 !== 0) return false;
  for (let i = 0; i < len; i++) {
    let letter = s[i];
    switch (letter) {
      case "(": {
        arr.push(letter);
        break;
      }
      case "{": {
        arr.push(letter);
        break;
      }
      case "[": {
        arr.push(letter);
        break;
      }
      case ")": {
        if (arr.pop() !== "(") return false;
        break;
      }
      case "}": {
        if (arr.pop() !== "{") return false;
        break;
      }
      case "]": {
        if (arr.pop() !== "[") return false;
        break;
      }
    }
  }
  return !arr.length;
};

第二種是維護一個map對象:

哈希表map

/**
 * @param {string} s
 * @return {boolean}
 */
var isValid = function (s) {
  let map = {
    "(": ")",
    "{": "}",
    "[": "]",
  };
  let stack = [];
  let len = s.length;
  if (len % 2 !== 0) return false;
  for (let i of s) {
    if (i in map) {
      stack.push(i);
    } else {
      if (i !== map[stack.pop()]) return false;
    }
  }
  return !stack.length;
};

滑動窗口最大值 ⛵

題目難度 hard,涉及到的算法知識有雙端隊列。

題目描述

給定一個數組 nums,有一個大小爲  k  的滑動窗口從數組的最左側移動到數組的最右側。你只能夠看到在滑動窗口內的 k  個數字。滑動窗口每次只向右移動一位。

返回滑動窗口中的最大值。

進階:你能在線性時間複雜度內解決此題嗎?

示例:

輸入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
輸出: [3,3,5,5,6,7]
解釋:

  滑動窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

提示:

  • 1 <= nums.length <= 10^5
  • -10^4 <= nums[i] <= 10^4
  • 1 <= k <= nums.length

思路分析

暴力求解

第一種方法,比較簡單。也是大多數同窗很快就能想到的方法。

  • 遍歷數組
  • 依次遍歷每一個區間內的最大值,放入數組中
/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
var maxSlidingWindow = function (nums, k) {
  let len = nums.length;
  if (len === 0) return [];
  if (k === 1) return nums;
  let resArr = [];
  for (let i = 0; i <= len - k; i++) {
    let max = Number.MIN_SAFE_INTEGER;
    for (let j = i; j < i + k; j++) {
      max = Math.max(max, nums[j]);
    }
    resArr.push(max);
  }
  return resArr;
};

雙端隊列

這道題還能夠用雙端隊列去解決,核心在於在窗口發生移動時,只根據發生變化的元素對最大值進行更新。

結合上面動圖(圖片來源)咱們梳理下思路:

  • 檢查隊尾元素,看是否是都知足大於等於當前元素的條件。若是是的話,直接將當前元素入隊。不然,將隊尾元素逐個出隊、直到隊尾元素大於等於當前元素爲止。(這一步是爲了維持隊列的遞減性:確保隊頭元素是當前滑動窗口的最大值。這樣咱們每次取最大值時,直接取隊頭元素便可。)
  • 將當前元素入隊
  • 檢查隊頭元素,看隊頭元素是否已經被排除在滑動窗口的範圍以外了。若是是,則將隊頭元素出隊。(這一步是維持隊列的有效性:確保隊列裏全部的元素都在滑動窗口圈定的範圍之內。)
  • 排除掉滑動窗口尚未初始化完成、第一個最大值尚未出現的特殊狀況。
/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */
var maxSlidingWindow = function (nums, k) {
  // 緩存數組的長度
  const len = nums.length;
  const res = [];
  const deque = [];
  for (let i = 0; i < len; i++) {
    // 隊尾元素小於當前元素
    while (deque.length && nums[deque[deque.length - 1]] < nums[i]) {
      deque.pop();
    }
    deque.push(i);

    // 當隊頭元素的索引已經被排除在滑動窗口以外時
    while (deque.length && deque[0] <= i - k) {
      // 隊頭元素出對
      deque.shift();
    }
    if (i >= k - 1) {
      res.push(nums[deque[0]]);
    }
  }
  return res;
};

每日溫度 🌡

題目難度 medium,涉及到的算法知識有棧。

題目描述

根據每日氣溫列表,請從新生成一個列表,對應位置的輸出是須要再等待多久溫度纔會升高超過該日的天數。若是以後都不會升高,請在該位置用  0 來代替。

例如,給定一個列表  temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的輸出應該是  [1, 1, 4, 2, 1, 1, 0, 0]。

提示:氣溫列表長度的範圍是  [1, 30000]。每一個氣溫的值的均爲華氏度,都是在  [30, 100]  範圍內的整數。

思路分析

看到這道題,你們很容易就會想到暴力遍歷法:直接兩層遍歷,第一層定位一個溫度,第二層定位離這個溫度最近的一次升溫是哪天,而後求出兩個溫度對應索引的差值便可。

然而這種解法須要兩層遍歷,時間複雜度是O(n^2),顯然不是最優解法。

本道題目能夠採用棧去作一個優化。

大概思路就是:維護一個遞減棧。當遍歷過的溫度,維持的是一個單調遞減的態勢時,咱們就對這些溫度的索引下標執行入棧操做;只要出現了一個數字,它打破了這種單調遞減的趨勢,也就是說它比前一個溫度值高,這時咱們就對先後兩個溫度的索引下標求差,得出前一個溫度距離第一次升溫的目標差值。

代碼實現

/**
 * @param {number[]} T
 * @return {number[]}
 */
var dailyTemperatures = function (T) {
  const len = T.length;
  const stack = [];
  const res = new Array(len).fill(0);
  for (let i = 0; i < len; i++) {
    while (stack.length && T[i] > T[stack[stack.length - 1]]) {
      const top = stack.pop();
      res[top] = i - top;
    }
    stack.push(i);
  }
  return res;
};

括號生成 🎯

題目難度 medium,涉及到的算法知識有遞歸、回溯。

題目描述

數字 n 表明生成括號的對數,請你設計一個函數,用於可以生成全部可能的而且 有效的 括號組合。

示例:

輸入:n = 3
輸出:[
       "((()))",
       "(()())",
       "(())()",
       "()(())",
       "()()()"
     ]

思路分析

這道題目經過遞歸去實現。

由於左右括號須要匹配、閉合。因此對應「(」和「)」的數量都是n,當知足這個條件時,一次遞歸就結束,將對應值放入結果數組中。

這裏有一個潛在的限制條件:有效的括號組合。對應邏輯就是在往每一個位置去放入「(」或「)」前:

  • 須要判斷「(」的數量是否小於 n
  • 「)」的數量是否小於「(」

代碼實現

/**
 * @param {number} n
 * @return {string[]}
 */
var generateParenthesis = function (n) {
  let res = [];
  const generate = (cur, left, right) => {
    if (left === n && right === n) {
      res.push(cur);
      return;
    }
    if (left < n) {
      generate(cur + "(", left + 1, right);
    }
    if (right < left) {
      generate(cur + ")", left, right + 1);
    }
  };
  generate("", 0, 0);
  return res;
};

電話號碼的字母組合 🎨

題目難度 medium,涉及到的算法知識有遞歸、回溯。

題目描述

給定一個僅包含數字 2-9 的字符串,返回全部它能表示的字母組合。

給出數字到字母的映射以下(與電話按鍵相同)。注意 1 不對應任何字母。

示例:

輸入:"23"
輸出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].

思路分析

首先用一個對象map存儲數字與字母的映射關係,接下來遍歷對應的字符串,第一次將字符串存在結果數組result中,第二次及之後的就雙層遍歷生成新的字符串數組。

代碼實現

哈希映射 逐層遍歷

/**
 * @param {string} digits
 * @return {string[]}
 */
var letterCombinations = function (digits) {
  let res = [];
  if (digits.length === 0) return [];
  let map = {
    2: "abc",
    3: "def",
    4: "ghi",
    5: "jkl",
    6: "mno",
    7: "pqrs",
    8: "tuv",
    9: "wxyz",
  };
  for (let num of digits) {
    let chars = map[num];
    if (res.length > 0) {
      let temp = [];
      for (let char of chars) {
        for (let oldStr of res) {
          temp.push(oldStr + char);
        }
      }
      res = temp;
    } else {
      res.push(...chars);
    }
  }
  return res;
};

遞歸

/**
 * @param {string} digits
 * @return {string[]}
 */
var letterCombinations = function (digits) {
  let res = [];
  if (!digits) return [];
  let map = {
    2: "abc",
    3: "def",
    4: "ghi",
    5: "jkl",
    6: "mno",
    7: "pqrs",
    8: "tuv",
    9: "wxyz",
  };
  function generate(i, str) {
    let len = digits.length;
    if (i === len) {
      res.push(str);
      return;
    }
    let chars = map[digits[i]];
    for (let j = 0; j < chars.length; j++) {
      generate(i + 1, str + chars[j]);
    }
  }
  generate(0, "");
  return res;
};

島嶼數量 🏝

題目難度 medium,涉及到的算法知識有 DFS(深度優先搜索)。

題目描述

給你一個由  '1'(陸地)和 '0'(水)組成的的二維網格,請你計算網格中島嶼的數量。

島嶼老是被水包圍,而且每座島嶼只能由水平方向或豎直方向上相鄰的陸地鏈接造成。

此外,你能夠假設該網格的四條邊均被水包圍。

示例 1:

輸入: 11110;
11010;
11000;
00000;
輸出: 1;

示例  2:

輸入:
11000
11000
00100
00011
輸出: 3
解釋: 每座島嶼只能由水平和/或豎直方向上相鄰的陸地鏈接而成。

思路分析

如上圖,咱們須要計算的就是圖中相連(只能是水平和/或豎直方向上相鄰)的綠色島嶼的數量。

這道題目一個經典的作法是沉島,大體思路是:採用DFS(深度優先搜索),遇到 1 的就將當前的 1 變爲 0,並將當前座標的上下左右都執行 dfs,並計數。

終止條件是:超出二維數組的邊界或者是遇到 0 ,直接返回。

代碼實現

/**
 * @param {character[][]} grid
 * @return {number}
 */
var numIslands = function (grid) {
  const rows = grid.length;
  if (rows === 0) return 0;
  const cols = grid[0].length;
  let res = 0;
  for (let i = 0; i < rows; i++) {
    for (let j = 0; j < cols; j++) {
      if (grid[i][j] === "1") {
        helper(grid, i, j, rows, cols);
        res++;
      }
    }
  }
  return res;
};
function helper(grid, i, j, rows, cols) {
  if (i < 0 || j < 0 || i > rows - 1 || j > cols - 1 || grid[i][j] === "0")
    return;

  grid[i][j] = "0";

  helper(grid, i + 1, j, rows, cols);
  helper(grid, i, j + 1, rows, cols);
  helper(grid, i - 1, j, rows, cols);
  helper(grid, i, j - 1, rows, cols);
}

分發餅乾 🍪

題目難度 easy,涉及到的算法知識有貪心算法。

題目描述

假設你是一位很棒的家長,想要給你的孩子們一些小餅乾。可是,每一個孩子最多隻能給一塊餅乾。對每一個孩子 i ,都有一個胃口值  gi ,這是能讓孩子們知足胃口的餅乾的最小尺寸;而且每塊餅乾 j ,都有一個尺寸 sj 。若是 sj >= gi ,咱們能夠將這個餅乾 j 分配給孩子 i ,這個孩子會獲得知足。你的目標是儘量知足越多數量的孩子,並輸出這個最大數值。

注意:

你能夠假設胃口值爲正。
一個小朋友最多隻能擁有一塊餅乾。

示例  1:

輸入: [1,2,3], [1,1]

輸出: 1

解釋:
你有三個孩子和兩塊小餅乾,3個孩子的胃口值分別是:1,2,3。
雖然你有兩塊小餅乾,因爲他們的尺寸都是1,你只能讓胃口值是1的孩子知足。
因此你應該輸出1。

示例  2:

輸入: [1,2], [1,2,3]

輸出: 2

解釋:
你有兩個孩子和三塊小餅乾,2個孩子的胃口值分別是1,2。
你擁有的餅乾數量和尺寸都足以讓全部孩子知足。
因此你應該輸出2.

思路分析

這道題目是一道典型的貪心算法類。解題思路大概以下:

  • 優先知足胃口小的小朋友的需求
  • 設最大可知足的孩子數量爲maxNum = 0
  • 胃口小的拿小的,胃口大的拿大的
  • 兩邊升序,而後一一對比

    • 餅乾j >= 胃口i 時,i++j++maxNum++
    • 餅乾j < 胃口i時,說明餅乾不夠吃,換更大的,j++
  • 到邊界後中止

代碼實現

/**
 * @param {number[]} g
 * @param {number[]} s
 * @return {number}
 */
var findContentChildren = function (g, s) {
  g = g.sort((a, b) => a - b);
  s = s.sort((a, b) => a - b);
  let gLen = g.length,
    sLen = s.length,
    i = 0,
    j = 0,
    maxNum = 0;
  while (i < gLen && j < sLen) {
    if (s[j] >= g[i]) {
      i++;
      maxNum++;
    }
    j++;
  }
  return maxNum;
};

買賣股票的最佳時機 II 🚁

題目難度 easy,涉及到的算法知識有動態規劃、貪心算法。

題目描述

給定一個數組,它的第  i 個元素是一支給定股票第 i 天的價格。

設計一個算法來計算你所能獲取的最大利潤。你能夠儘量地完成更多的交易(屢次買賣一支股票)。

注意:你不能同時參與多筆交易(你必須在再次購買前出售掉以前的股票)。

示例 1:

輸入: [7,1,5,3,6,4]
輸出: 7
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 3 天(股票價格 = 5)的時候賣出, 這筆交易所能得到利潤 = 5-1 = 4 。
     隨後,在第 4 天(股票價格 = 3)的時候買入,在第 5 天(股票價格 = 6)的時候賣出, 這筆交易所能得到利潤 = 6-3 = 3 。

示例 2:

輸入: [1,2,3,4,5]
輸出: 4
解釋: 在第 1 天(股票價格 = 1)的時候買入,在第 5 天 (股票價格 = 5)的時候賣出, 這筆交易所能得到利潤 = 5-1 = 4 。
     注意你不能在第 1 天和第 2 天接連購買股票,以後再將它們賣出。
     由於這樣屬於同時參與了多筆交易,你必須在再次購買前出售掉以前的股票。

示例  3:

輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種狀況下, 沒有交易完成, 因此最大利潤爲 0。

提示:

  • 1 <= prices.length <= 3 * 10 ^ 4
  • 0 <= prices[i] <= 10 ^ 4

思路分析

其實這道題目思路也比較簡單:

  • 維護一個變量profit用來存儲利潤
  • 由於能夠屢次買賣,那麼就要後面的價格比前面的大,那麼就能夠進行買賣
  • 所以,只要prices[i+1] > prices[i],那麼就去疊加profit
  • 遍歷完成獲得的profit就是獲取的最大利潤

代碼實現

/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function (prices) {
  let profit = 0;
  for (let i = 0; i < prices.length - 1; i++) {
    if (prices[i + 1] > prices[i]) profit += prices[i + 1] - prices[i];
  }
  return profit;
};

不一樣路徑 🛣

題目難度 medium,涉及到的算法知識有動態規劃。

題目描述

一個機器人位於一個 m x n 網格的左上角 (起始點在下圖中標記爲「Start」 )。

機器人每次只能向下或者向右移動一步。機器人試圖達到網格的右下角(在下圖中標記爲「Finish」)。

問總共有多少條不一樣的路徑?

例如,上圖是一個 7 x 3 的網格。有多少可能的路徑?

示例  1:

輸入: m = 3, n = 2
輸出: 3
解釋:
從左上角開始,總共有 3 條路徑能夠到達右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右

示例  2:

輸入: (m = 7), (n = 3);
輸出: 28;

思路分析

由題可知:機器人只能向右或向下移動一步,那麼從左上角到右下角的走法 = 從右邊開始走的路徑總數+從下邊開始走的路徑總數。

因此可推出動態方程爲:dp[i][j] = dp[i-1][j]+dp[i][j-1]

代碼實現

這裏採用 Array(m).fill(Array(n).fill(1))進行了初始化,由於每一格至少有一種走法。
/**
 * @param {number} m
 * @param {number} n
 * @return {number}
 */
var uniquePaths = function (m, n) {
  let dp = Array(m).fill(Array(n).fill(1));
  for (let i = 1; i < m; i++) {
    for (let j = 1; j < n; j++) {
      dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
    }
  }
  return dp[m - 1][n - 1];
};

零錢兌換 💰

題目難度 medium,涉及到的算法知識有動態規劃。

題目描述

給定不一樣面額的硬幣 coins 和一個總金額 amount。編寫一個函數來計算能夠湊成總金額所需的最少的硬幣個數。若是沒有任何一種硬幣組合能組成總金額,返回  -1。

示例  1:

輸入: (coins = [1, 2, 5]), (amount = 11);
輸出: 3;
解釋: 11 = 5 + 5 + 1;

示例 2:

輸入: (coins = [2]), (amount = 3);
輸出: -1;

說明:
你能夠認爲每種硬幣的數量是無限的。

思路分析

這道題目咱們一樣採用動態規劃來解決。

假設給出的不一樣面額的硬幣是[1, 2, 5],目標是 60,問最少須要的硬幣個數?

咱們須要先分解子問題,分層級找最優子結構。

dp[i]: 表示總金額爲 i 的時候最優解法的硬幣數

咱們想一下:求總金額 60 有幾種方法?一共有 3 種方式,由於咱們有 3 種不一樣面值的硬幣。

  • 拿一枚面值爲 1 的硬幣 + 總金額爲 59 的最優解法的硬幣數量。即:dp[59] + 1
  • 拿一枚面值爲 2 的硬幣 + 總金額爲 58 的最優解法的硬幣數。即:dp[58] + 1
  • 拿一枚面值爲 5 的硬幣 + 總金額爲 55 的最優解法的硬幣數。即:dp[55] + 1

因此,總金額爲 60 的最優解法就是上面這三種解法中最優的一種,也就是硬幣數最少的一種,咱們下面用代碼來表示一下:

dp[60] = Math.min(dp[59] + 1, dp[58] + 1, dp[55] + 1);

推導出狀態轉移方程

dp[i] = Math.min(dp[i - coin] + 1, dp[i - coin] + 1, ...)
其中 coin 有多少種可能,咱們就須要比較多少次,遍歷 coins 數組,分別去對比便可

代碼實現

/**
 * @param {number[]} coins
 * @param {number} amount
 * @return {number}
 */
var coinChange = function (coins, amount) {
  let dp = new Array(amount + 1).fill(Infinity);
  dp[0] = 0;
  for (let i = 0; i <= amount; i++) {
    for (let coin of coins) {
      if (i - coin >= 0) {
        dp[i] = Math.min(dp[i], dp[i - coin] + 1);
      }
    }
  }
  return dp[amount] === Infinity ? -1 : dp[amount];
};

福利

大多數前端同窗對於算法的系統學習,實際上是比較茫然的,這裏我整理了一張思惟導圖,算是比較全面的歸納了前端算法體系。

另外我還維護了一個github倉庫:https://github.com/Cosen95/js_algorithm,裏面包含了大量的leetcode題解,而且還在不斷更新中,感受不錯的給個star哈!🤗

❤️ 愛心三連擊

1.若是以爲這篇文章還不錯,來個分享、點贊、在看三連吧,讓更多的人也看到~

2.關注公衆號前端森林,按期爲你推送新鮮乾貨好文。

3.特殊階段,帶好口罩,作好我的防禦。

相關文章
相關標籤/搜索