[leetcode]403. Frog Jump

A frog is crossing a river. The river is divided into x units and at each unit there may or may not exist a stone. The frog can jump on a stone, but it must not jump into the water.

Given a list of stones' positions (in units) in sorted ascending order, determine if the frog is able to cross the river by landing on the last stone. Initially, the frog is on the first stone and assume the first jump must be 1 unit.

If the frog's last jump was k units, then its next jump must be either k - 1, k, or k + 1 units. Note that the frog can only jump in the forward direction.

Note:

The number of stones is2 and is < 1,100.
Each stone's position will be a non-negative integer < 231.
The first stone's position is always 0.
Example 1:

[0,1,3,5,6,8,12,17]

There are a total of 8 stones.
The first stone at the 0th unit, second stone at the 1st unit,
third stone at the 3rd unit, and so on...
The last stone at the 17th unit.

Return true. The frog can jump to the last stone by jumping 
1 unit to the 2nd stone, then 2 units to the 3rd stone, then 
2 units to the 4th stone, then 3 units to the 6th stone, 
4 units to the 7th stone, and 5 units to the 8th stone.
Example 2:

[0,1,2,3,4,8,9,11]

Return false. There is no way to jump to the last stone as 
the gap between the 5th and 6th stone is too large.

这是leetcode的一道hard题,其实就是一个明显的dynamic programming的问题,但有一些实现的细节和实现方法的效率问题。
最基本的思路就是基于每个stone都建立一个走到它的步骤,然后看它能走到哪些其他的stone,如果可以走到就在那个stone记下这个步骤大小,依次类推,看能不能走到最后一个。基本代码如下:
迭代写法如下:枚举所有的可能性,复杂度为O(n^2).

bool canCross(vector<int>& stones) {
    vector<vector<int>> res;
    res.push_back({});
    if (stones[1] != 1) return false;
    res.push_back({1});
    int i, j, k;
    for (i = 1; i < stones.size(); i++) {
        for (j = 0; j < res[i].size(); j++) {
            int lastjump = res[i][j];
            for (k = i + 1; k < stones.size(); k++) {
                if (k >= res.size()) res.push_back({});
                if (stones[k]-stones[i] >= lastjump-1 && stones[k]-stones[i] <= lastjump+1) {
                    res[k].push_back(stones[k]-stones[i]);
                }
            }
        }
    }
    if (res[stones.size()-1].size() > 0) return true;
    else return false;
}

也可以写成递归形式,不断尝试所有可能。

bool canCross(vector<int>& stones) {
    int res = 0;
    helper(stones, 0, 1, res);
    return res == stones.size() - 1;
}
void helper(vector<int>& stones, int start, int jump, int& res) {
    int n = stones.size();
    if (res >= n - 1 || start >= n - 1) return;
    for (int i = start + 1; i < n; ++i) {
        int dist = stones[i] - stones[start];
        if (i == 1 && dist != 1) return;
        if (jump == dist + 1 || jump == dist || jump == dist - 1) {
            res = max(res, i);
            helper(stones, i, dist, res);
        } else if (dist > jump+1) break;
    }
}

以上这两种代码都无法通过,都会在最后显示TLE,超时。但可以对第一个稍微调整下:

方法一:

将第一段代码中的vector< vector < int>> 换成hash table的形式,方便查找。同时很重要的就是要这个stone已经超过了之前的最大步长的范围,后面的都没有必须继续搜索了,这样可以将复杂度从实打实的O(n^2)降下来。

bool canCross(vector<int>& stones) {
    unordered_map<int, unordered_set<int>> res;
    res[0].insert(0);
    if (stones[1] != 1) return false;
    res[1].insert(1);
    int i, j, k, maxstep;
    for (i = 1; i < stones.size(); i++) {
        for (k = i + 1; k < stones.size(); k++) {
            int dist = stones[k] - stones[i];
            if (res[i].count(dist-1) || res[i].count(dist) || res[i].count(dist+1)) {
                res[k].insert(dist);
                maxstep = max(dist, maxstep);
            }
            else {
                if ((stones[k]-stones[i]) > maxstep) break;
            }
        }

    }
    if (res[stones.size()-1].size() > 0) return true;
    else return false;
}

方法二:

除了记录每个节点所有可能的步长外,同时建一个一维的dp,记录每个点的最大步长,这个dp[k]帮助我们在搜索的时候确定搜索范围,也就是我们发现某个i点的范围最早被dp[k]+1够到的时候,从这个k点开始检查哈希表里每个点的所有可能步长,一旦成立就终止,然后继续i+1点,这样既控制了搜索范围,也减少了对哈希表的访问,这个实现的效率明显高于前面的算法。因为这个找到即可,找不到再查看其它可能,前面的是找完为止,直到我这点到不了的地方,直接枚举所有可能,所以方法二要更好一些。

bool canCross(vector<int>& stones) {
    unordered_map<int, unordered_set<int>> m;
    vector<int> dp(stones.size(), 0);
    m[0].insert(0);
    int k = 0;
    for (int i = 1; i < stones.size(); i++) {
        while (dp[k]+1 < stones[i]-stones[k]) k++;
        int dist = stones[i] - stones[k];
        for (int j = k; j < i; j++) {
            if (m[j].count(dist) || m[j].count(dist-1) || m[j].count(dist+1)) {
                m[i].insert(dist);
                dp[i] = max(dp[i], dist);
                break;
            }
        }
    }
    return dp.back() > 0;
}

你可能感兴趣的:(leetcode)