Jump Game 是一道有意思的題目。題意很簡單,給你一個數組,數組的每個元素表示你能前進的最大步數,最開始時你在第一個元素所在的位置,之后你可以前進,問能不能到達最后一個元素位置。
比如:
A = [2, 3, 1, 1, 4], return true.
一種走法是 0 - 2 - 3 - 4
,還有一種走法是 0 - 1 - 4
O(n ^ 2) 解法###
一個很顯然,幾乎不用動腦的解法。
設置一個布爾數組f
,f[0] === true
表示 index === 0
這個位置能夠到達,模擬每個位置的前進,最后判斷 f[lastIndex]
的值。
/**
* @param {number[]} nums
* @return {boolean}
*/
var canJump = function(nums) {
var f = [];
f[0] = true;
nums.forEach(function(item, index, array) {
if (f[index]) {
var tmp = Math.min(array.length - 1, index + item)
for (var i = index + 1; i <= tmp; i++)
f[i] = true;
}
});
return f[nums.length - 1] ? false : true;
};
但是返回了無情的TLE,給出了一組TLE的數據,數組長度達到了25000,也就是復雜度達到了O(25000 ^ 2),雖然leetcode數據普遍很弱,但是這組TLE也是讓我心服口服。
解法1,跪...
O(nlogn) 解法###
方法總比困難多,聯想到了樹狀數組中的染色問題。
我們可以把jump的過程看成是染色,還是從左到右枚舉位置,比如枚舉到 index=0
位置時,nums[0]=5
,也就是說從 index=0
的位置一直可以走到 index=5
的位置,那么我們可以把1~5這一段進行染色。當枚舉到 index=1
時,如何判斷能不能走到這一步呢?只需求該點被染色的次數,如果大於0,那么就是能到達,然后從該點向后繼續染色,最后判斷最后一點有沒有被染色即可。復雜度 O(nlongn)
。
/**
* @param {number[]} nums
* @return {boolean}
*/
var sum, n;
function lowbit(x) {
return x & (-x);
}
function update(index, val) {
while (index) {
sum[index] += val;
index -= lowbit(index);
}
}
function getAns(index) {
var ans = 0;
while (index <= n) {
ans += sum[index];
index += lowbit(index);
}
return ans;
}
var canJump = function(nums) {
sum = [];
sum[1] = 1;
n = nums.reduce(function(pre, item, index) {
return Math.max(pre, item + index + 1);
}, 0);
for (var i = 2; i <= n; i++)
sum[i] = 0;
for (var i = 0, len = nums.length; i < len; i++) {
var isPainted = getAns(i + 1); // 是否被染色
if (!isPainted) continue;
update(i + 1 + nums[i], 1);
update(i, -1);
}
return Boolean(getAns(len));
};
O(n) 解法###
用樹狀數組顯然大材小用了,樹狀數組可以求得被染色的次數,但是本題只需要判斷是否被染色即可;而且本題每次染色都是一次。
進一步思考,我們枚舉每一位時都在判斷是否被染色過(從而決定是否能夠到達該點且能否繼續往前走),假設在某一瞬間,index=m
的位置已經被染色了,那么 index=n (n<=m)
的位置肯定已經被染色過了,我們維護一個最右邊被染色的點,如果當前枚舉點在該點的左側,那么當前點已經被染色,否則即可停止遍歷(因為右邊的點再也不可能被染色到了)。
/**
* @param {number[]} nums
* @return {boolean}
*/
var canJump = function(nums) {
var rightMost = 1;
for (var i = 0, len = nums.length; i < len; i++) {
if (rightMost < i + 1) break;
rightMost = Math.max(rightMost, i + 1 + nums[i]);
}
return rightMost >= len;
};