算法一直是大廠前端面試常問的一塊,而你們每每準備這方面的面試都是經過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 = 目標值
,那麼a
和b
對應的數組下標就是咱們想要的答案。緩存
這種解法沒毛病,但有沒有優化的方案呢?🤔函數
要知道兩層循環不少狀況下都意味着O(n^2)
的複雜度,這個複雜度很是容易致使你的算法超時。即使沒有超時,在明明有一層遍歷解法的狀況下,你寫了兩層遍歷,面試官也會對你的印象分大打折扣。🤒post
其實咱們能夠在遍歷數組的過程當中,增長一個Map
結構來存儲已經遍歷過的數字及其對應的索引值。而後每遍歷到一個新數字的時候,都回到Map
裏去查詢targetNum
與該數的差值是否已經在前面的數字中出現過了。若出現過,那麼答案已然顯現,咱們就沒必要再往下走了。學習
咱們就以本題中的例子結合圖片來講明一下上面提到的這種思路:
diffs
來模擬map
結構:首先遍歷數組第一個元素,此時key
爲 2,value
爲索引 0
計算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
中是否存在三個元素a
,b
,c
,使得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。若是是,那麼咱們就獲得了一個目標組合;不然,分兩種狀況來看:
/** * @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
提示:
第一種方法,比較簡單。也是大多數同窗很快就能想到的方法。
/** * @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
,當知足這個條件時,一次遞歸就結束,將對應值放入結果數組中。
這裏有一個潛在的限制條件:有效的
括號組合。對應邏輯就是在往每一個位置去放入「(」或「)」前:
/** * @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; };
題目難度
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。
提示:
其實這道題目思路也比較簡單:
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 種不一樣面值的硬幣。
因此,總金額爲 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.特殊階段,帶好口罩,作好我的防禦。