關注「松寶寫代碼」,精選好文,每日一題
作者:saucxs | songEagle
2020,實「鼠」不易
2021,「牛」轉乾坤
風勁潮涌當揚帆,任重道遠須奮蹄!
一、前言
2020.12.23 立的 flag,每日一題,題目類型不限制,涉及到JavaScript,Node,Vue,React,瀏覽器,http,算法等領域。
本文是:【每日一題】(27題)算法題:如何使用多種解決方案來實現跳一跳游戲?
昨天發了文章【每日一題】(26題)算法題:最長公共前綴?被人一頓吐槽,槽點太多了,比如:
- 能不能給出最優解?
- 不講基礎題目,能不能講一下稍微難點的算法題?
哈哈,我以為沒有人看呢?原來還是有人在看哈,簡單說明一下:
- 每一個問題背后有多種解決方案,我們可以從最初的解決方案出發,逐步優化,找出問題所在。
- 基礎題目有一定的道理,每一個算法可以融入到千變萬化的場景中,從而就有了成千上萬的題目,你需要從場景中抓住重點,提取信息,匹配到合適的算法,最終解決。
[圖片上傳失敗...(image-cb8894-1611071808563)]
二、題目
昨天我們還談了很多基礎問題,我們先來看一下我們說的基礎題目,也就是常說的「爬樓梯問題」或者「跳一跳游戲」。
問題描述:
給定一個非負整數數組,你最初位于數組的第一個位置。數組中的每個元素代表你在該位置可以跳躍的最大長度。判斷你是否能夠到達最后一個位置。
例子1:
輸入: [2,3,1,1,4]
輸出: true
解釋: 我們可以先跳 1 步,從位置 0 到達 位置 1, 然后再從位置 1 跳 3 步到達最后一個位置。
例子2:
輸入: [3,2,1,0,4]
輸出: false
解釋: 無論怎樣,你總會到達索引為 3 的位置。但該位置的最大跳躍長度是 0 , 所以你永遠不可能到達最后一個位置。
三、解決方案
1、回溯方法
這是一個效率低的方案,其中我們嘗試從第一個位置跳到最后一個位置上的跳躍方式。我們第一個位置開始,跳到所有可能達到的步數。我們重復這個過程,直到達到最后一個位置。當達不到的時候我們回溯。
var backtrackingJumpLittle = function (numbers, startIndex = 0, currentJumps = []) {
if (startIndex === numbers.length - 1) {
return true;
}
const maxJumpLength = Math.min(
numbers[startIndex],
numbers.length - 1 - startIndex,
);
for (let jumpLength = maxJumpLength; jumpLength > 0; jumpLength -= 1) {
const nextIndex = startIndex + jumpLength;
currentJumps.push(nextIndex);
const isJumpSuccessful = backtrackingJumpLittle(numbers, nextIndex, currentJumps);
if (isJumpSuccessful) {
return true;
}
currentJumps.pop();
}
return false;
}
我們來分析一下時間復雜度和空間復雜度:
時間復雜度:O(2^n)。從第一個位置到最后一個位置有2的n次方跳躍方式,其中narray的長度為nums。
空間復雜度:O(n)。遞歸需要額外的內存用于堆棧。
2、自頂向下動規方法
自上而下的動態規劃可以被認為是優化的回溯。它依賴于這樣的觀察:一旦我們確定某個臺階是可以達到終點,還是不能達到終點,那么這個結果將永遠不會改變。這意味著我們可以存儲結果,而不必每次都重新計算。
因此,對于數組中的每個位置,我們都記住該位置是好是壞。我們將這個其值設為以下之一:good,bad,unknow。
var dpTopDownJumpLittle = function (
numbers,
startIndex = 0,
currentJumps = [],
cellsGoodness = [], // 用來存當前臺階的狀態
) {
if (startIndex === numbers.length - 1) {
return true;
}
const currentCellsGoodness = [...cellsGoodness];
if (!currentCellsGoodness.length) {
numbers.forEach(() => currentCellsGoodness.push(undefined));
currentCellsGoodness[cellsGoodness.length - 1] = true;
}
const maxJumpLength = Math.min(
numbers[startIndex],
numbers.length - 1 - startIndex,
);
for (let jumpLength = maxJumpLength; jumpLength > 0; jumpLength -= 1) {
const nextIndex = startIndex + jumpLength;
if (currentCellsGoodness[nextIndex] !== false) {
currentJumps.push(nextIndex);
const isJumpSuccessful = dpTopDownJumpLittle(
numbers,
nextIndex,
currentJumps,
currentCellsGoodness,
);
if (isJumpSuccessful) {
return true;
}
currentJumps.pop();
currentCellsGoodness[nextIndex] = false;
}
}
return false;
}
時間復雜度::O(n^2)。對于數組中的每個元素,例如i,我們正在尋找nums[i]其右邊的下一個元素,目的是尋找一個良好的索引。
空間復雜度:O(2 * n) = O(n)。首先n起源于遞歸。其次n來自存儲當前步數狀態的用法。
3、自下向上動規方法
這一步驟為將來的優化打開了可能。通常通過嘗試從上至下的方法顛倒步驟的順序來消除遞歸。
var dpBottomUpJumpLittle = function(numbers) {
const cellsGoodness = Array(numbers.length).fill(undefined);
cellsGoodness[cellsGoodness.length - 1] = true;
for (let cellIndex = numbers.length - 2; cellIndex >= 0; cellIndex -= 1) {
const maxJumpLength = Math.min(
numbers[cellIndex],
numbers.length - 1 - cellIndex,
);
for (let jumpLength = maxJumpLength; jumpLength > 0; jumpLength -= 1) {
const nextIndex = cellIndex + jumpLength;
if (cellsGoodness[nextIndex] === true) {
cellsGoodness[cellIndex] = true;
break;
}
}
}
return cellsGoodness[0] === true;
}
時間復雜度:: O(n^2)
空間復雜度:O(n)
[圖片上傳失敗...(image-a3c869-1611071808563)]
4、貪心算法
一旦我們的代碼處于自下而上的狀態,我們就可以做最后一個重要的觀察。從一個給定的位置,當我們試圖看看是否能跳到一個好的位置時,我們只使用第一個。換句話說,最左邊的一個。如果我們把這個最左邊的好位置作為一個單獨的變量來跟蹤,我們可以避免在數組中搜索它。不僅如此,我們還可以完全停止使用數組。
var greedyJumpLittle = function(numbers) {
let leftGoodPosition = numbers.length - 1;
for (let numberIndex = numbers.length - 2; numberIndex >= 0; numberIndex -= 1) {
const maxCurrentJumpLength = numberIndex + numbers[numberIndex];
if (maxCurrentJumpLength >= leftGoodPosition) {
leftGoodPosition = numberIndex;
}
}
return leftGoodPosition === 0;
}
時間復雜度::O(n)。我們正在遍歷nums數組,因此要遍歷n,其中n的長度是array的長度nums。
空間復雜度:O(1)。我們沒有使用任何額外的內存。
我在leetcode上測試了一下,發現空間占用還是很大,運行時間76ms左右,內存消耗38MB左右。
[圖片上傳失敗...(image-c940fd-1611071808563)]
謝謝支持
1、文章喜歡的話可以「分享,點贊,在看」三連哦。
2、作者昵稱:saucxs,songEagle,松寶寫代碼。「松寶寫代碼」公眾號作者,每日一題,實驗室等。一個愛好折騰,致力于全棧,正在努力成長的字節跳動工程師,星辰大海,未來可期。內推字節跳動各個部門各個崗位。
3、長按下面圖片,關注「松寶寫代碼」,是獲取開發知識體系構建,精選文章,項目實戰,實驗室,每日一道面試題,進階學習,思考職業發展,涉及到JavaScript,Node,Vue,React,瀏覽器,http等領域,希望可以幫助到你,我們一起成長~
[圖片上傳失敗...(image-24253e-1611071808564)]
字節內推福利
- 回復「校招」獲取內推碼
- 回復「社招」獲取內推
- 回復「實習生」獲取內推
后續會有更多福利
學習資料福利
回復「算法」獲取算法學習資料
往期「每日一題」
1、JavaScript && ES6
第 16 題:【每日一題】面試官問:JS中如何全面進行客戶端檢測?
第 15 題:【每日一題】面試官問:JS類型判斷有哪幾種方法?
第 14 題:【每日一題】面試官問:談談你對JS對象的創建和引申
第 12 題[每日一題]面試官問:JS引擎的執行過程(二)
第 11 題[每日一題]面試官問:JS引擎的執行過程(一)
第 10 題[每日一題]面試官問:詳細說一下JS數據類型
2、瀏覽器
3、Vue
4、算法
第 26 道【每日一題】(26題)算法題:最長公共前綴?