一、概述
廣爲人知的跳臺階問題。
輸入一個序列,從第一個開始跳,每次跳的最遠距離是該元素的值,問最少跳多少次出隊列。
這題目,本質上是將具體的跳臺階問題化爲抽象的貪心問題。
我是在BFS→剪枝→找最大值→得到較優解中一步步理解的。
二、分析
我最終版本的代碼和最優代碼思想是一致的,但是我懶得從BFS改成循環了。就只分析我自己的代碼,還可以從中看出我的分析過程。
第一眼看到這個題,我開始想到貪心,怎麼貪心呢?先把最大值找出來,然後找次大值。。。。。。這樣,發現不成,比如說最大值在最後一個,就幾乎不可能用到這個值;再之後找“元素下標加元素值”的最大值,發現也不成,比如全是1,怎麼找,就得一個一個遍歷。
於是想到了BFS。
BFS是個什麼思想呢?建樹,以序列2、3、1、1、4爲例,如下是建出來的樹:
紅色爲跳躍值,藍色爲下標。最開始從0起步,下標爲0的元素值爲2,則可以跳1步或2步,因此有兩個子樹,分別跳到1和2;1的值爲3,因此有三棵子樹,2的值爲1,只有一棵子樹......最後當下標加元素值大於等於序列長-1的時候就找到了結果。結果爲樹的層數。
所以最開始我的代碼就是傳統的BFS:
class Solution {
int jump_next(vector<int>& nums,int nowLoc,int target)
{
queue<int> q;
q.push(nowLoc);
int nowjumps=1;
int last=1;//當前層最後一個元素所在位置
int cnp=1;//進入隊列的節點總數
int cmp=0;//當前位置
while(!q.empty())
{
int tmp=q.front();
q.pop();
cmp++;
if(tmp+nums[tmp]>=target)
return nowjumps;
for(int i=nums[tmp];i>0;i--)
{
q.push(i+tmp);
cnp++;
}
if(cmp==last)
{
last=cnp;
nowjumps++;
}
}
return -1;
}
public:
int jump(vector<int>& nums) {
if(nums.size()==1)
return 0;
int res=jump_next(nums,0,nums.size()-1);
return res;
}
};
由於需要記錄當前層數,所以需要維護多個變量,注意BFS中記錄當前層數的做法:
維護三個變量:last、cnp、cmp。
last是當前層的最後一個節點的位置。cnp是當前入隊元素的總個數。cmp是當前出隊的是第幾個元素。
第m層的第cmp個元素出隊時,它的葉子節點全部入隊,cnp隨之增加,當cmp是m層最後一個元素時,也就是cmp等於當前last的值,cnp的值就是last應該更新的值,此時層數加一。
上面代碼很容易看懂,就是把所有葉子節點全部入隊,沒有任何剪枝操作。很明顯的,超時了。
然後我就開始思考具體的剪枝。
跳臺階本質上是什麼呢?
我把它想象成是一個帝國,每跳一次臺階,帝國的勢力就變大一點。
對,每跳一次臺階,帝國的勢力變大一點。
也就是說,如果該次臺階不能讓帝國的勢力變大,那還不如不跳。
仍然以上面那幅圖舉例:
我們可以發現,在進行第一次跳躍時,帝國有兩種選擇:我這一步跳到1,下一步帝國勢力最遠跳到4;這一步跳到2,下一步帝國勢力最遠跳到3。可能有些人會擔心這樣一種情況:雖然跳到2看起來下一步跳的少,但是可能存在這樣一條路徑,選2比選1更好。
不存在的,因爲跳到2之後的可選項(3)是跳到1後的可選項(2,3,4)的子集,所以跳到1是完全比2好的。也就是說,帝國做的選擇取決於該選擇的潛力,潛力如何量化呢?下標*元素值。
再來看1的三棵子樹,分別能把帝國勢力擴大到2,3,4,意思就是我到1了之後,能把帝國的範圍最多擴大到4。然後看2,3,4三個節點,它們都只有一棵子樹,分別能到3,4,5,也就是說,如果我下一步跳到4,那麼我之後最遠能到5,如果我跳到3,之後我最遠能跳到4,如果我調到2,之後我最遠能跳到3,即2,3,4的潛力分別爲3,4,5,選擇跳到潛力最大的4。
這樣就找到最優路徑了。也就是說,每層只要把“潛力”最大的入隊即可。
優化後的代碼如下:
class Solution {
int jump_next(vector<int>& nums,int nowLoc,int target)
{
queue<int> q;
q.push(nowLoc);
int nowjumps=1;
int last=1;//當前層最後一個元素所在位置
int cnp=1;//進入隊列的節點總數
int cmp=0;//當前位置
while(!q.empty())
{
int tmp=q.front();
q.pop();
++cmp;
if(tmp+nums[tmp]>=target)
return nowjumps;
int max=tmp;//潛力初值
for(int i=nums[tmp];i>0;--i)
if(i+tmp+nums[i+tmp]>max+nums[max])//潛力比較
max=i+tmp;//潛力更新
q.push(max);//潛力最大的入隊
++cnp;
if(cmp==last)
{
last=cnp;
++nowjumps;
}
}
return -1;
}
public:
int jump(vector<int>& nums) {
if(nums.size()==1)
return 0;
int res=jump_next(nums,0,nums.size()-1);
return res;
}
};
這樣就把所有的無用枝都減掉了。
三、總結
最好的做法是DP,但是還沒開始學,所以先不用。次好的方法用的思想就是我這個了。一步一步找到答案的感覺還是很不錯的。