LeetCode 青蛙过河(回溯法+贪心策略+剪枝)

一只青蛙想要过河。 假定河流被等分为 x 个单元格,并且在每一个单元格内都有可能放有一石子(也有可能没有)。 青蛙可以跳上石头,但是不可以跳入水中。

给定石子的位置列表(用单元格序号升序表示), 请判定青蛙能否成功过河(即能否在最后一步跳至最后一个石子上)。 开始时, 青蛙默认已站在第一个石子上,并可以假定它第一步只能跳跃一个单位(即只能从单元格1跳至单元格2)。

如果青蛙上一步跳跃了 k 个单位,那么它接下来的跳跃距离只能选择为 k - 1、k 或 k + 1个单位。 另请注意,青蛙只能向前方(终点的方向)跳跃。

请注意:

石子的数量 ≥ 2 且 < 1100;
每一个石子的位置序号都是一个非负整数,且其 < 231;
第一个石子的位置永远是0。
示例 1:

[0,1,3,5,6,8,12,17]
总共有8个石子。
第一个石子处于序号为0的单元格的位置, 第二个石子处于序号为1的单元格的位置,
第三个石子在序号为3的单元格的位置, 以此定义整个数组...
最后一个石子处于序号为17的单元格的位置。
返回 true。即青蛙可以成功过河,按照如下方案跳跃: 
跳1个单位到第2块石子, 然后跳2个单位到第3块石子, 接着 
跳2个单位到第4块石子, 然后跳3个单位到第6块石子, 
跳4个单位到第7块石子, 最后,跳5个单位到第8个石子(即最后一块石子)。

示例 2:

[0,1,2,3,4,8,9,11]
返回 false。青蛙没有办法过河。 
这是因为第5和第6个石子之间的间距太大,没有可选的方案供青蛙跳跃过去。

思路分析:由于当前可跳的步长取决于上一次调到次石头上的步长,所以将上一次可达此石头的步长保存,然后根据上一次的到达此石头的步长集合选择当前可跳的步长。

class Solution {
public:
	bool canCross(vector<int>& stones) {
		//第一步只能跳一个不长
		if (stones[1] != stones[0] + 1) {
			return false;
		}
		int endStone = *(--stones.end());//尾端石头
		set<int> stonesSet(++stones.begin(), stones.end());//将vector容器转换为set容器
		map<int, set<int> > myMap;//myMap[i]标记i可跳的不长
		myMap[*stonesSet.begin()].insert(1);//初始只能跳一步到第二个位置
        //顺序访问所有石头
		for (auto stone : stonesSet) {
            //根据上一次到达此地的步长集合,计算下一步可跳的步长
			for (auto nextStone : myMap[stone]) {
                //步长nextStone - 1
				if (nextStone > 1 && stonesSet.find(stone + nextStone - 1) != stonesSet.end()) {
                    //如果跳nextStone - 1后到达的石头在stonesSet中,说明stone + nextStone - 1这块石头可由不长nextStone - 1到达
					myMap[stone + nextStone - 1].insert(nextStone - 1);
				}
                //步长nextStone
				if (stonesSet.find(stone + nextStone) != stonesSet.end()) {
                    //如果跳nextStone 后到达的石头在stonesSet中,说明stone + nextStone这块石头可由不长nextStone到达
					myMap[stone + nextStone].insert(nextStone);
				}
                //步长nextStone + 1
				if (stonesSet.find(stone + nextStone + 1) != stonesSet.end()) {
                    //如果跳nextStone + 1后到达的石头在stonesSet中,说明stone + nextStone + 1这块石头可由不长nextStone + 1到达
					myMap[stone + nextStone + 1].insert(nextStone + 1);
				}
                //如果已经达到了endStone
				if (myMap.count(endStone) > 0) {
					return true;
				}
			}
		}
		return false;
	}
};

在这里插入图片描述
方法二:回溯法+贪心策略+剪枝。

class Solution {
private:
	set<int> stonesSet;
public:
    //从nowStone开始搜索能否到达last,beforeStep为到达nowStone的步长
	bool jump(int nowStone, int beforeStep, int last) {
        //如果已经达到了终点
		if ((nowStone + beforeStep + 1) == last || (nowStone + beforeStep) == last || (nowStone + beforeStep - 1) == last){
            return true;
        }
        //采取贪心策略,每次都先选择beforeStep + 1步长
		if (stonesSet.find(nowStone + beforeStep + 1) != stonesSet.end() && jump(nowStone + beforeStep + 1, beforeStep + 1, last)) {
			return true;
		}
        //再beforeStep步长
		if (stonesSet.find(nowStone + beforeStep) != stonesSet.end()&& jump(nowStone + beforeStep, beforeStep, last)) {
			return true;
		}
        //最后beforeStep - 1步长
		if (beforeStep > 1 && stonesSet.find(nowStone + beforeStep - 1) != stonesSet.end()&& jump(nowStone + beforeStep - 1, beforeStep - 1, last)) {
			return true;
		}
		return false;
	}
	bool canCross(vector<int>& stones) {
		if (stones[1] != 1) return false;
		int last = stones.back();
		for (int i = 1; i < stones.size(); ++i) {
			if (i > 3 && stones[i - 1] * 2 < stones[i]){//剪枝算法,stones[i - 1]最多是有步长stones[i - 1]到达
                //stones[i - 1] * 2 < stones[i],说明无论如何stones[i]都不可达
                return false;
            }
			stonesSet.insert(stones[i]);
		}
		return jump(1, 1, last);
	}
};

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_41855420/article/details/88668950
今日推荐