LeetCode 837.新21點(diǎn)

題目

新21點(diǎn)
鏈接可能點(diǎn)不進(jìn)去凌彬,所以我把完整的題目寫在了下面。

愛麗絲參與一個(gè)大致基于紙牌游戲 “21點(diǎn)” 規(guī)則的游戲循衰,描述如下:
愛麗絲以 0 分開始铲敛,并在她的得分少于 K 分時(shí)抽取數(shù)字。 抽取時(shí)羹蚣,她從 [1, W] 的范圍中隨機(jī)獲得一個(gè)整數(shù)
作為分?jǐn)?shù)進(jìn)行累計(jì)原探,其中 W 是整數(shù)。 每次抽取都是獨(dú)立的顽素,其結(jié)果具有相同的概率咽弦。
當(dāng)愛麗絲獲得不少于 K 分時(shí),她就停止抽取數(shù)字胁出。 愛麗絲的分?jǐn)?shù)不超過 N 的概率是多少型型?

示例 1:
輸入:N = 10, K = 1, W = 10
輸出:1.00000
說明:愛麗絲得到一張卡,然后停止全蝶。

提示:
0 <= K <= N <= 10000
1 <= W <= 10000
如果答案與正確答案的誤差不超過 10^-5闹蒜,則該答案將被視為正確答案通過寺枉。
此問題的判斷限制時(shí)間已經(jīng)減少。

分析

這道題通過率很低绷落。


題目沒啥歧義姥闪,我們來看看示例1。題目中說了最開始愛麗絲手上是0分砌烁。0<=1(k=1)筐喳,所以這時(shí)候愛麗絲可以從1-10(w=10)中抽一個(gè)整數(shù)并累加到0上。
那么愛麗絲此時(shí)手上就有了以下可能的分?jǐn)?shù):

1 2 3 4 5 6 7 8 9 10

這些數(shù)都>=1函喉,所以愛麗絲游戲結(jié)束避归,那么滿足<=10(n=10)的數(shù)有10個(gè),所以答案為10/10=1管呵。
如果示例1中k=2呢梳毙?那么愛麗絲第一次抽取后得到的分?jǐn)?shù)中的1還需要繼續(xù)抽取。然后不停的進(jìn)行游戲捐下,直到所有可能得到的數(shù)累加后都>=k為止账锹。
首先我們可以知道以下幾點(diǎn):

1:n肯定>=k,不然答案肯定是0蔑担,這個(gè)不難想通牌废,而且條件也給出來了耳鸯;
2:這個(gè)游戲在有限步內(nèi)肯定會停止冰悠,因?yàn)槭菑?-w進(jìn)行抽取未状,即便最開始分?jǐn)?shù)為0,那么抽k次排抬,
每次都抽到1累加起來也是k,而k>=k授段,游戲停止蹲蒲。

遞歸

最開始我們很容易想到一種思路,就是遞歸侵贵,我們抽取一輪后判斷哪些數(shù)還需要繼續(xù)抽取届搁。

    double new21Game(int N, int K, int W) {
        if(K == 0) {
            return 1;
        }
        return currNew21Game(0,N,K,W);
    }
    double currNew21Game(int number,int &N, int &K, int &W) {
        //抽到1-W每一個(gè)數(shù)的概率為1/W
        double everOne = 1.f / W;
        //保存還需要繼續(xù)抽取的數(shù)
        vector<int> numbers;
        //記錄累加后>=k的數(shù)中有多少個(gè)<=n,<=k表示不再繼續(xù)抽取
        int noThanN = 0;
        for (int num = 1; num <= W; num ++) {
            if(num + number < K) {
                numbers.push_back(num + number);
                continue;
            }
            if(num + number <= N) {
                noThanN ++;
            }
        }
        //記錄當(dāng)前抽取的概率
        double resultCount = 0;
        //概率加上everOne*noThanN窍育,這個(gè)不難理解
        resultCount += noThanN * everOne;
        //需要繼續(xù)抽取的數(shù)就是進(jìn)行遞歸卡睦,但是要乘everOne,這個(gè)也很好理解
        for (int index = 0; index < numbers.size(); index ++) {
            resultCount += everOne * currNew21Game(numbers[index],N,K,W);
        }
        return resultCount;
    }

這個(gè)思路是正確的漱抓,但是條件中說了n<=10000表锻,那么直接宣布死亡,這個(gè)肯定是超時(shí)乞娄。

遞歸+備忘錄

我們可以在遞歸的思路上加上備忘錄

    double new21Game(int N, int K, int W) {
        if(K == 0) {
            return 1;
        }
        unordered_map<int, double> numberMap;
        return currNew21Game(0,N,K,W,numberMap);
    }
    double currNew21Game(int number,int &N, int &K, int &W,unordered_map<int, double> &numberMap) {
        if(numberMap.count(number)) {
            return numberMap[number];
        }
        //抽到1-W每一個(gè)數(shù)的概率為1/W
        double everOne = 1.f / W;
        //保存還需要繼續(xù)抽取的數(shù)
        vector<int> numbers;
        //記錄累加后>=k的數(shù)中有多少個(gè)<=n
        int noThanN = 0;
        for (int num = 1; num <= W; num ++) {
            if(num + number < K) {
                numbers.push_back(num + number);
                continue;
            }
            if(num + number <= N) {
                noThanN ++;
            }
        }
        //記錄當(dāng)前抽取的概率
        double resultCount = 0;
        //概率加上everOne*noThanN瞬逊,這個(gè)不難理解
        resultCount += noThanN * everOne;
        //需要繼續(xù)抽取的數(shù)就是進(jìn)行遞歸显歧,但是要乘everOne,這個(gè)也很好理解
        for (int index = 0; index < numbers.size(); index ++) {
            resultCount += everOne * currNew21Game(numbers[index],N,K,W,numberMap);
        }
        numberMap[number] = resultCount;
        return resultCount;
    }

雖然可以加快一點(diǎn)速度确镊,但是還是不行士骤,雖然計(jì)算的次數(shù)少了,但是遞歸的次數(shù)實(shí)在太多了蕾域,時(shí)間都耗在這里了敦间。

Accept

在經(jīng)過了一天的堅(jiān)持不懈后,我發(fā)現(xiàn)這道題是在考數(shù)學(xué)歸納法束铭;說的簡單點(diǎn)就是讓我們找規(guī)律廓块。我是這樣思考的。

我們拿n=5,k=4,w=5舉例契沫。
題目中有一個(gè)條件是誤導(dǎo)我們的:起始分?jǐn)?shù)為0带猴,那么我們就會很容易先入為主的認(rèn)為我們應(yīng)該從0開始找規(guī)律。
但是分?jǐn)?shù)為0會涉及到深層遞歸的情況懈万,因?yàn)榈谝淮纬槿『笪覀儠玫? 2 3 4 5拴清,其中的1 2 3我們需要遞歸;
然后假如我們第一輪得到1会通,這時(shí)我們起始分?jǐn)?shù)為1口予,第二輪抽取我們就會得到2 3 4 5,其中2 3我們又要進(jìn)行遞歸涕侈;這樣的話我們肯定就繞進(jìn)去了沪停。所以我們應(yīng)該逆向思考。
當(dāng)我們從起始分?jǐn)?shù)為k-1=3時(shí)情況就不需要遞歸裳涛,因?yàn)槲覀兂槿∫淮蔚玫降姆謹(jǐn)?shù)為4 5 6 7 8木张,哇,兄die端三?是不是很輕松的就得到了概率舷礼。因?yàn)樗袛?shù)都>=k了,游戲終止郊闯;這時(shí)候我們可以直接得到概率為2/5=0.4妻献。
然后我們來推k-2=2時(shí)的情況,我們會得到3 4 5 6 7团赁,相對于4 5 6 7 8
我們只需要減去8的概率育拨,再加上3的概率即可,因?yàn)?需要遞歸然痊;但是這個(gè)3你發(fā)現(xiàn)沒有至朗,我們剛才才算過3啊,是不是發(fā)現(xiàn)了新大陸剧浸?因?yàn)槲覀冇?jì)算k-2時(shí)所有的條件都是已知的了锹引。不要說8不知道矗钟。。嫌变。
你看到這里的時(shí)候你可以自己想一想遞歸公式了吨艇;最后答案就是我們推到0的時(shí)候的概率。為什么是0腾啥?因?yàn)轭}目說了是從0分開始玩游戲嘛东涡。
    double new21Game(int N, int K, int W) {
        if(K == 0) {
            return 1;
        }
        //我們拿3 2 3舉例,可以得到下面的規(guī)律
        //假設(shè)開始我們的分?jǐn)?shù)為K-1=1倘待,我們可以拿1 2 3疮跑,得到的分?jǐn)?shù)為2 3 4,為什么要假設(shè)分?jǐn)?shù)為K-1呢凸舵,因?yàn)檫@時(shí)候沒有遞歸的情況出現(xiàn)祖娘,因?yàn)榭隙ǘ?gt;=K了
        //1-W每個(gè)選擇的概率
        double everOne = 1.f / W;
        //其中滿足<=N的數(shù)量是
        int lessNCount = N - K + 1;
        //我們記錄算出來的所有開始分?jǐn)?shù)的概率
        double probability[K];
        probability[K - 1] = lessNCount * everOne;
        
        //當(dāng)開始分?jǐn)?shù)為K-2時(shí),我們得到的是1 2 3啊奄,
        //這時(shí)候就有兩部分了渐苏,一部分是>=K的2和3,另一部分是<k的1
        for (int currK = K - 2; currK >= 0; currK --) {
            double currValue = probability[currK + 1];
            //對于5 4 5來說
            //3時(shí)得到的是:4 5 6 7 8
            //2時(shí)得到的是:3 4 5 6 7
            //也就是說currK是在currK-1的基礎(chǔ)上減去currK+W+1菇夸,加上currK+1琼富,其中currK+1或者currK+W+1都是我們已經(jīng)計(jì)算過的
            currValue += (everOne * currValue);
            //滿足這個(gè)條件說明減去的數(shù)是在<=n范圍內(nèi),>n的數(shù)不會加到結(jié)果概率中的庄新,內(nèi)部為什么要乘everOne?因?yàn)槟氵x到這個(gè)數(shù)的概率就是這么多鞠眉,我們當(dāng)前需要乘了。
            if(currK + W + 1 <= N) {
                //滿足這個(gè)條件說明減去的數(shù)是遞歸獲得的摄咆,因?yàn)榉謹(jǐn)?shù)<k會繼續(xù)抽取
                if(currK + W + 1 < K) {
                    currValue -= everOne * probability[currK + W + 1];
                }
                //否則就是>=k凡蚜,也就是不需要繼續(xù)抽取的情況人断,那么概率是一個(gè)固定值
                else {
                    currValue -= everOne * 1;
                }
            }
            probability[currK] = currValue;
        }
        return probability[0];
    }

后記

至此吭从,我LeetCode上的題就只剩下困難難度的了,這道題也算是一個(gè)好的結(jié)尾并且是一個(gè)全新的開始吧恶迈,因?yàn)榇蟛糠掷щy難度的題確實(shí)比較燒腦涩金。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市暇仲,隨后出現(xiàn)的幾起案子步做,更是在濱河造成了極大的恐慌,老刑警劉巖奈附,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件全度,死亡現(xiàn)場離奇詭異,居然都是意外死亡斥滤,警方通過查閱死者的電腦和手機(jī)将鸵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門勉盅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人顶掉,你說我怎么就攤上這事草娜。” “怎么了痒筒?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵宰闰,是天一觀的道長。 經(jīng)常有香客問我簿透,道長移袍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任老充,我火速辦了婚禮咐容,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蚂维。我一直安慰自己戳粒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布虫啥。 她就那樣靜靜地躺著蔚约,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涂籽。 梳的紋絲不亂的頭發(fā)上苹祟,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機(jī)與錄音评雌,去河邊找鬼树枫。 笑死,一個(gè)胖子當(dāng)著我的面吹牛景东,可吹牛的內(nèi)容都是我干的砂轻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼斤吐,長吁一口氣:“原來是場噩夢啊……” “哼搔涝!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起和措,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤庄呈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后派阱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诬留,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了文兑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片傀广。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖彩届,靈堂內(nèi)的尸體忽然破棺而出伪冰,到底是詐尸還是另有隱情,我是刑警寧澤樟蠕,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布贮聂,位于F島的核電站,受9級特大地震影響寨辩,放射性物質(zhì)發(fā)生泄漏吓懈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一靡狞、第九天 我趴在偏房一處隱蔽的房頂上張望耻警。 院中可真熱鬧,春花似錦甸怕、人聲如沸甘穿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽温兼。三九已至,卻和暖如春武契,著一層夾襖步出監(jiān)牢的瞬間募判,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工咒唆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留届垫,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓全释,卻偏偏與公主長得像装处,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子恨溜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359