1. 程式人生 > >劍指Offer(面試題43~45)

劍指Offer(面試題43~45)

面試題43:n個骰子的點數

題目:把n個骰子仍在地上,所有骰子朝上一面的點數之和為s。輸入n,打印出s的所有可能的值出現的概念。
解法一:基於遞迴求骰子點數,時間效率不夠高
要想求出n個骰子的點數和,可以先把n個骰子分成兩堆:第一堆只有一個,另一堆有n-1個。單獨的那一個有可能從1到6的點數。我們需要計算從1到6的每一種和剩下的n-1個骰子來計算點數和。接下來把剩下的n-1個骰子還是分成兩堆,第一堆只有一個,第二堆有n-2個。我們把上一輪那個單獨骰子的點數和這一輪骰子的點數相加,再和剩下的n-2個骰子來計算點數和。分析到這裡,不難發現這是一種遞迴的思想,遞迴結束條件就是最後只剩下一個骰子。

int g_maxValue = 6;
void PrintProbability(int number)
{
    if(number < 1)
        return;

    int maxSum = number*g_maxValue;
    int* pProbabilities = new int[masSum -number +1];
    for(int i = number;i <= maxSum; ++i)
        pProbabilities[i-number] = 0;

    Probability(number,pProbabilities);

    int
total = pow((double)g_maxValue,number); for(int i = number;i <= maxSum;++ i) { double ratio =(double)pProbabilities[i - number] / total; printf("%d:%e\n",i,ratio); } delete[] pProbabilities; } void Probability(int number,int* pProbabilities) { for(int i = 1;i <= g_maxValue; ++i) Probability(number,number,i,pProbabilities); } void
Probability(int original,int current,int sum,int* pProbabilities) { if(current == 1) { pProbabilities[sum - original]++; } else { for(int i = 1;i <= g_maxValue; ++i) { Probability(original,current - 1,i + sum,pProbabilities); } } }

上述思路很簡潔,實現起來很容易。但由於是基於遞迴的實現,它有很多計算是重複的,從而導致當number變大時效能慢得讓人不能接受。

解法二:基於迴圈求骰子點數,時間效能好
我們可以考慮用兩個陣列來儲存骰子點數的每一個總數出現的次數。在一次迴圈中,第一個陣列中的第n個數字表示骰子和為n出現的次數。在下一迴圈中,我們加上一個新的骰子,此時和為n的骰子出現的次數應該等於上一次迴圈中骰子點數和為n-1、n-2、n-3、n-4、n-5與n-6的次數的總和,所以我們把另一個數組的第n個數字設為前一個數組對應的第n-1、n-2、n-3、n-4、n-5與n-6之和。其程式碼如下:

int g_maxValue = 6;
void PrintProbability(int number)
{
    if(number < 1)
        return;
    int* pProbabilities[2];
    pProbabilities[0] = new int[g_maxValue*number +1];
    pProbabilities[1] = new int[g_maxValue*number +1];
    for(int i = 0;i < g_maxValue * number + 1;++i)
    {
        pProbabilities[0][i] = 0;
        pProbabilities[1][i] = 0;
    }

    int flag = 0;
    for(int i = 1;i <= g_maxValue; ++i)
        pProbabilities[flag][i] = 1;
    for(int k = 2;k <= number; ++k)
    {
        for(int i = 0;i<k;++i)
            pProbabilities[1-flag][i] = 0;

        for(int i=k; i<= g_maxValue*k; ++i)
        {
            pProbabilities[1-flag][i] = 0;
            for(int j=1;j<=i && j<=g_maxValue;++j)
                pProbabilities[1-flag][i]+=pProbabilities[flag][i-j];
        }
        flag = 1-flag;
    }

    double total = pow((double)g_maxValue,number);
    for(int i = number;i <= g_maxValue*number; ++i)
    {
        double ratio =(double)pProbabilities[flag][i] / total;
        printf("%d: %e\n",i,ratio);
    }

    delete[] pProbabilities[0];
    delete[] pProbabilities[1];
}

面試題45:圓圈中最後剩下的數字
題目:0,1,…,n-1這n個數字排成一個圓圈,從數字0開始每次從這個圓圈裡刪除第m個數字。求出這個圓圈裡剩下的最後一個數字。
經典的解法,用環形連結串列模擬圓圈

int LastRemaining(unsigned int n,unsigned int m)
{
    if(n < 1 || m < 1)
        return -1;

    unsigned int i = 0;

    list<int> numbers;
    for(i = 0;i < n;++i)
        numbers.push_back(i);
    list<int>::iterator current = numbers.begin();
    while(numbers.size() > 1)
    {
        for(int i =1;i<m;++i)
        {
            current++;
            if(current == numbers.end())
                current = numbers.begin();
        }

        list<int>::iterator next = ++current;
        if(next == numbers.end())
            next = numbers.begin();

        --current;
        numbers.erase(current);
        current = next;
    }
    return *(current);
}

上述程式碼的執行中,我們會發現實際上需要在環形連結串列裡重複遍歷很多遍。重複的遍歷當然對時間效率有負面的影響。這種方法沒刪除一個數字需要m步運算,總共有n個數字,因此總的時間複雜度是O(mn)。同時還需要一個輔助連結串列來模擬圓圈,其空間複雜度是O(n)。

創新的解法:

int LastRemaining(unsigned int n,unsigned int m)
{
    if(n < 1 || m < 1)
        return -1;

    int last = 0;
    for(int i=2;i <= n;i ++)
        last =(last +m) %i;
    return last;
}

參考資料《劍指Offer》

相關推薦

Offer試題43~45

面試題43:n個骰子的點數 題目:把n個骰子仍在地上,所有骰子朝上一面的點數之和為s。輸入n,打印出s的所有可能的值出現的概念。 解法一:基於遞迴求骰子點數,時間效率不夠高 要想求出n個骰子的點數和,可以先把n個骰子分成兩堆:第一堆只有一個,另一堆有n-1

Offer試題33~34

面試題33:把陣列排成最小的數 題目:輸入一個正整數陣列,把數組裡所有數字拼接起來排成一個數,列印能拼接出的所有數字中最小的一個。例如輸入陣列{3,32,321},則打印出這3個數字能排成的最小數字321323。 分析:一個非常直觀的解決大數問題的方法就是把

Offer試題29~30

面試題29:陣列中出現次數超過一半的數字 題目:陣列中有一個數字出現的次數超過陣列長度的一半,請找出這個數字。例如輸入一個長度為9的陣列{1,2,3,2,2,2,5,4,2}。由於數字2在陣列中出現了5次,超過陣列長度的一半,因此輸出2。 bool g_

offer試題43 n個骰子的點數 java

r+ nal ret 次循環 分而治之 源碼 ava 面試 ble 引言:寫這篇文章的初衷只是想做個筆記,因為這道題代碼量有點大,有點抽象,而書上並沒有詳細的註釋。為了加深印象和便於下次復習,做個記錄。 原題:把n個骰子扔到地上,所有骰子朝上一面的點數之後為s. 輸入n,打

offer試題--從尾到頭列印單鏈表

程式碼如下(遞迴實現): /** * public class ListNode { * int val; * ListNode next = null; * * ListNode(int val) { *

offer試題29:順時針列印矩陣

/* * 順時針列印矩陣 * 注意矩陣的維度 */ #include<iostream> using namespace std; //只要當前的起始行號小於終止行號或者起始列號小於終止列號,就可以繼續順時針列印 // 但是隨著順勢怎列印,剩下

offer試題39:陣列中出現次數超過陣列長度一半的數字

方法1: 從數學角度看,所求的數一定的原陣列(有序化)的中位數,那麼,我們可以通過對陣列排序,這樣得到的時間複雜度是O(nlogn)O(nlogn)。而如果能夠優化到在O(n)O(n)的時間內找到,就更好了。基於快速排序的partition階段,如果我們可以

offer試題43:n個骰子的點數

第一種思路是,每個骰子的點數從最小到最大,假設為1-6,那麼所有的骰子從最小1開始,我們假設一種從左向右的排列,右邊的最低,索引從最低開始,判斷和的情況。 def setTo1(dices, start, end): for i in range(start, end)

offer試題35:複雜連結串列的複製

/* * 複雜連結串列的複製 * 複雜連結串列是指,結點的指標可能不規則地指向另一個結點 */ #include<iostream> using namespace std; struct ComplexListNode { int v

offer試題38:字串的排列

分析: 求字串中所有字元的全排列,實際上按照全排列的計算公式來理解,第一步是求出所有可能出現在第一個位置的字元,即用首個字元分別後餘下的字元交換;第二步是固定首個字元,餘下字元組成的子串進行第一步的

offer試題21:根據給定條件劃分陣列

/* * 題目 * 輸入一個數組,實現一個函式來調整該陣列中數字的順序,使得所有奇數位於陣列的前半部分 * 偶數位於陣列的後半部分 * 同時考慮程式碼的可擴充套件行 */ #include <i

offer試題31:棧的壓入和彈出序列

/* * 給定兩個序列,一個是棧的壓入序列,一個是彈出序列,判斷彈出序列能否匹配壓入序列 */ #include<iostream> #include<stack> usi

offer試題39 二叉樹的深度java

設計模式 博客 rgs 歷史 存在 復制 pri 取值 今天 摘要: 今天翻到了《劍指offer》面試題39,題目二中的解法二是在函數的參數列表中通過指針的方式進行傳值,而java是沒有指針的,所以函數要進行改造。然而我翻了下別人的java版本(我就想看看有什麽高大上的改造

offer試題571:和為S的數字

題目 輸入一個遞增排序的陣列和一個數字S,在陣列中查詢兩個數,是的他們的和正好是S,如果有多對數字的和等於S,輸出兩個數的乘積最小的。 ps: 對應每個測試案例,輸出兩個數,小的先輸出。 思路

offer試題答案彙總Java版

面試題2:實現Singleton模式 (1)餓漢模式 public class Singleton{ private static Singleton instance = new Singleton(); private Singleton(){}

offer》- 試題3:陣列中重複的數字java實現

題目一:         在一個長度為n的數組裡的所有數字都在0到n-1的範圍內。 陣列中某些數字是重複的,但不知道有幾個數字是重複的。也不知道每個數字重複幾次。請找出陣列中任意一個重複的數字。 例如,如果輸入長度為7的陣列{2,3,1,0,2,5,3},那麼對應的輸出是重複

offer試題322:分行從上到下列印二叉樹

完整程式碼地址 題目 從上到下按層列印二叉樹,同一層結點從左至右輸出。每一層輸出一行。 思路 用佇列來儲存要列印的節點。 同時我們需要兩個變數:一個變量表示在當前層中還沒有列印的節點數

offer試題二:單例模式的實現使用C++語言

#include <iostream> #include <iomanip> #include <cstdio> #include <cstdlib>

offer試題50:字元流中第一個只出現一次的字元【C++版本】

題目:字串中第一個只出現一次的字元。 在字串中找出第一個只出現一次的字元。如輸入"abaccdeff" "abaccdeff",則輸出′ b ′  ′b′。 解題思路: 1.使用雜湊表來記錄每個字元出現的次數,因為字元char char為8位,

offer試題 2. 實現 Singleton模式

模式 試題 枚舉 生成 test hand true 方法 單例模式 面試題 2. 實現 Singleton模式 題目:設計一個類,我們只能生成該類的一個實例。 單例模式:確保一個類只有一個實例,並提供了一個全局訪問點。 Code 1.餓漢模式 //餓漢模式 publ