1. 程式人生 > >C#LeetCode刷題之#441-排列硬幣(Arranging Coins)

C#LeetCode刷題之#441-排列硬幣(Arranging Coins)

問題

你總共有 n 枚硬幣,你需要將它們擺成一個階梯形狀,第 k 行就必須正好有 k 枚硬幣。

給定一個數字 n,找出可形成完整階梯行的總行數。

n 是一個非負整數,並且在32位有符號整型的範圍內。

n = 5

硬幣可排列成以下幾行: ¤ ¤ ¤ ¤ ¤

因為第三行不完整,所以返回2.

n = 8

硬幣可排列成以下幾行: ¤ ¤ ¤ ¤ ¤ ¤ ¤ ¤

因為第四行不完整,所以返回3.

You have a total of n coins that you want to form in a staircase shape, where every k-th row must have exactly k coins.

Given n, find the total number of full staircase rows that can be formed.

n is a non-negative integer and fits within the range of a 32-bit signed integer.

n = 5

The coins can form the following rows: ¤ ¤ ¤ ¤ ¤

Because the 3rd row is incomplete, we return 2.

n = 8

The coins can form the following rows: ¤ ¤ ¤ ¤ ¤ ¤ ¤ ¤

Because the 4th row is incomplete, we return 3.

示例

public class Program {

    public static void Main(string[] args) {
        var n = 8;
        var res = ArrangeCoins(n);
        Console.WriteLine(res);

        n = 18;
        res = ArrangeCoins2(n);
        Console.WriteLine(res);

        n = 28;
        res = ArrangeCoins3(n);
        Console.WriteLine(res);

        n = 38;
        res = ArrangeCoins4(n);
        Console.WriteLine(res);

        n = 68;
        res = ArrangeCoins5(n);
        Console.WriteLine(res);

        Console.ReadKey();
    }

    private static int ArrangeCoins(int n) {
        //暴力解法,此解法LeetCode超時未AC
        var res = 0;
        var value = (long)0;
        while(n > (value = ComputerT(++res))) { }
        if(n != value) res--;
        return res;
        //以下解法同理,但 LeetCode 可以 AC
        //if(n == 0) return 0;
        //for(long i = 1; ; i++) {
        //    if(n >= (i + 1) * i / 2 && n < (i + 2) * (i + 1) / 2)
        //        return (int)i;
        //}
    }

    private static int ComputerT(int n) {
        //抽出來是為了讓程式結構更清晰
        return (1 + n) * n / 2;
    }

    private static int ArrangeCoins2(int n) {
        //按1、2、3、4、5這樣一直減下去
        //直到n小於或等於0時為止
        var index = 1;
        while((n -= index++) > 0) { }
        //考慮上述迴圈中因為是 index++
        //index最終被多加了一次
        //真實的索引應該是 index - 1
        index--;
        //小於0時,最後一排不完整,所以再減1
        if(n < 0) return index - 1;
        //等於0時,最後一徘正好完整
        return index;
    }

    private static int ArrangeCoins3(int n) {
        /* 推導過程
         * 
         * 說明=>x ^ 2 表示 x 的平方
         * 公式1:a ^ 2 + 2 * a * b + b ^ 2 = (a + b) ^ 2
         * 公式2:公差為1的等差數列的求和公式為 S = (1 + n) * n / 2
         * 
         * 推導=>
         * 設在第 k 行硬幣排完,那麼
         * (1 + k) * k / 2 = n
         * 展開並兩邊同時乘以 2
         * k + k ^ 2 = 2 * n
         * 兩邊同時 + 0.25
         * k ^ 2 + k + 0.25 = 2 * n + 0.25
         * 合併
         * (k + 0.5) ^ 2 = 2 * n + 0.25
         * 兩邊同時開根號
         * k + 0.5 = Math.Sqrt(2 * n + 0.25)
         * 兩邊同時 - 0.5,求出 k 的值
         * k = Math.Sqrt(2 * n + 0.25) - 0.5
        */
        //原來的 n 為int,直接 2 * n + 0.25 會導致值溢位
        //所以用 (long)n 強轉
        return (int)(Math.Sqrt(2 * (long)n + 0.25) - 0.5);
        //以下解法同理,但換了一種求 k 的方式
        //具體推導過程請看以下博文
        //https://blog.csdn.net/zx2015216856/article/details/81950377
        //return (int)((Math.Sqrt(8 * (long)n + 1) - 1) / 2);
    }

    private static int ArrangeCoins4(int n) {
        //解法思路同ArrangeCoins
        //都屬於暴力求解,但此解法效率略高,可AC
        var x = (long)n;
        var temp = (long)n;
        while(x * x + x > 2 * temp) {
            x = (x * x + 2 * temp) / (2 * x + 1);
        }
        return (int)x;
    }

    private static int ArrangeCoins5(int n) {
        //基本思路同ArrangeCoins和ArrangeCoins4
        var k = (int)(Math.Sqrt(2) * Math.Sqrt(n));
        return k * (k + 1) <= 2 * n ? k : k - 1;
    }

}

以上給出5種演算法實現,以下是這個案例的輸出結果:

3
5
7
8
11

分析:

顯而易見,以上2種演算法的時間複雜度均為: O(n)