硬幣排成線I:算法
題目描述:數組
有 n
個硬幣排成一條線。兩個參賽者輪流從右邊依次拿走 1 或 2 個硬幣,直到沒有硬幣爲止。拿到最後一枚硬幣的人獲勝。spa
請斷定第一個玩家是輸仍是贏?code
樣例:blog
n = 1
, 返回 true
.遞歸
n = 2
, 返回 true
.it
n = 3
, 返回 false
.io
n = 4
, 返回 true
.class
n = 5
, 返回 true
.遍歷
挑戰:
O(1) 時間複雜度且O(1) 存儲。
算法分析:
public class Solution { /* * @param n: An integer * @return: A boolean which equals to true if the first player will win */ public boolean firstWillWin(int n) { // write your code here return n%3!=0; } }
硬幣排成線II:
題目描述:
有 n
個不一樣價值的硬幣排成一條線。兩個參賽者輪流從左邊依次拿走 1 或 2 個硬幣,直到沒有硬幣爲止。計算兩我的分別拿到的硬幣總價值,價值高的人獲勝。
請斷定第一個玩家是輸仍是贏?
樣例:
給定數組 A = [1,2,2]
, 返回 true
.
給定數組 A = [1,2,4]
, 返回 false
.
算法描述:
對於本題,其判斷輸贏的標準是拿到硬幣總價值的高低,有兩種解法:動態規劃或者遞歸遍歷出全部狀況並判斷每種狀況。然而,對於遞歸實現我以爲是比較無腦且費時的,並且對於本題必定會TLE。
這裏介紹一下動態規劃的解法:
①要判斷第一個玩家是否可以贏得比賽,咱們就須要對玩家的不一樣拿法進行分析與判斷,獲得利潤最大的那種拿法。由於針對玩家1的每一種拿法,玩家2會想出本身的拿法讓玩家1接下去的獲利最小,而對玩家1,就是要保證當前的拿法可以使剩下的硬幣獲利最大,所以咱們須要從後往前經過記錄後續拿法的最優解來進行動態規劃。
②創建數組DP[i]表示拿第i個硬幣到第n個硬幣所能得到的最大利潤;
③對於玩家拿到第i個硬幣時,玩家拿硬幣還是從左往右拿。所以他有兩種選擇,僅拿第i個硬幣或者拿第i個與第i+1個硬幣,這兩種拿法均會產生一個價值做爲DP[i]的值;由於咱們是從後往前進行動態規劃,咱們知道後續的結果,所以咱們知道怎樣的拿法會使得最終結果更好;
④第二個玩家在咱們作出選擇後也會對第一個玩家的利益進行限制,例如當第一個玩家拿第i個硬幣時,他能夠拿第i+1個或拿第i+一、i+2個硬幣,可是他會對的結果進行判斷,保證第一個玩家的獲利達到最小,即選擇DP[i+2]和DP[i+3]中的最小值來決定本身是拿一個仍是拿兩個;
綜上,動態規劃表達式爲:DP[i] = max{nums[i]+min{DP[i+2],DP[i+3]} , nums[i]+nums[i+1]+min{DP[i+3],DP[i+4]} };
代碼:
public class Solution { /* * @param values: a vector of integers * @return: a boolean which equals to true if the first player will win */ public boolean firstWillWin(int[] values) { // write your code here int n = values.length; if(values==null||n==0){ return false; } if(n<=2){ return true; } int[] DP = new int[n+1]; DP[n]=0; DP[n-1]=values[n-1]; DP[n-2]=values[n-1]+values[n-2]; DP[n-3]=values[n-2]+values[n-3]; for(int i=n-4;i>=0;i--){ //咱們取一個數或取兩個數 DP[i]=values[i]+Math.min(DP[i+2],DP[i+3]); DP[i]=Math.max(DP[i],values[i]+values[i+1]+Math.min(DP[i+3],DP[i+4])); } int sum=0; for(int i:values){ sum+=i; } return DP[0]*2>sum; } }