代码中,我们维护一个 cur 表示第 i 层最多可以增加多少个可放置的盒子,如果当前 n \gt \textit{cur,将 n 减去它,然后递增 i,否则当前的 i 就是我们最终要找的 i。每次 i 递增时,首先令 i = i + 1,然后更新 cur} = \textit{cur} + i。
然后我们找 j,维护变量 cur 表示在第 i 层中再添加一个接触地面的盒子时可以增加多少个可放置的盒子,如果当前 n \gt \textit{cur,则将 n 减去它,然后递增 j,否则当前的 j 就是我们最终要找的 j。每次递增 j 时,首先令 j = j + 1, 然后更新 cur} = \textit{cur}+ 1。
最终我们完整的放满了前 i - 1 层,并且在第 i 层放置了 j 个接触地面的盒子,因此答案应该为 1 + 2 + \cdots + (i - 1) + j = (i - 1) \times i}{2} + j。
代码
[sol1-Python3]
1 2 3 4 5 6 7 8 9 10 11 12 13
classSolution: defminimumBoxes(self, n: int) -> int: cur = i = j = 1 while n > cur: n -= cur i += 1 cur += i cur = 1 while n > cur: n -= cur j += 1 cur += 1 return (i - 1) * i // 2 + j
[sol1-C++]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
classSolution { public: intminimumBoxes(int n){ int cur = 1, i = 1, j = 1; while (n > cur) { n -= cur; i++; cur += i; } cur = 1; while (n > cur) { n -= cur; j++; cur++; } return (i - 1) * i / 2 + j; } };
[sol1-Java]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
classSolution { publicintminimumBoxes(int n) { intcur=1, i = 1, j = 1; while (n > cur) { n -= cur; i++; cur += i; } cur = 1; while (n > cur) { n -= cur; j++; cur++; } return (i - 1) * i / 2 + j; } }
[sol1-C#]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
publicclassSolution { publicintMinimumBoxes(int n) { int cur = 1, i = 1, j = 1; while (n > cur) { n -= cur; i++; cur += i; } cur = 1; while (n > cur) { n -= cur; j++; cur++; } return (i - 1) * i / 2 + j; } }
[sol1-Golang]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
funcminimumBoxes(n int)int { cur, i, j := 1, 1, 1 for n > cur { n -= cur i++ cur += i } cur = 1 for n > cur { n -= cur j++ cur++ } return (i-1)*i/2 + j }
[sol1-JavaScript]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
var minimumBoxes = function(n) { let cur = 1, i = 1, j = 1; while (n > cur) { n -= cur; i++; cur += i; } cur = 1; while (n > cur) { n -= cur; j++; cur++; } return (i - 1) * i / 2 + j; };
[sol1-C]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
intminimumBoxes(int n) { int cur = 1, i = 1, j = 1; while (n > cur) { n -= cur; i++; cur += i; } cur = 1; while (n > cur) { n -= cur; j++; cur++; } return (i - 1) * i / 2 + j; }
复杂度分析
时间复杂度:O(\sqrt[3]{n}),其中 n 是盒子数。需要遍历每一层计算盒子数,层数 i 与 n 的关系是 i = O(\sqrt[3]{n})。
空间复杂度:O(1)。
方法二:二分查找
思路与算法
为了方便描述,我们设 f(x) = x \times (x + 1)}{2 表示在同一层中连续放置 x 个接触地面的盒子时,总共可以增加多少个可放置盒子。由于第 i 层最多可以放置 i 个接触地面的盒子,所以第 i 层放满可以增加 f(i) 个可放置盒子。
因此,前 i 层可以放置的盒子总数为 g(i) = \sum_{k=1}^i f(k) = i \times (i + 1) \times (i + 2)}{6。
由于 g(i) 具有单调性,我们可以通过二分查找来找到 i,使得前 i 层的数量足够容纳 n 个盒子。与方法一中描述的一样,我们将 n 分解为完整的 i - 1 层和可能不完整的第 i 层。因此,需要找到一个最小的 i 使得:g(i) \ge n。
var minimumBoxes = function(n) { let i = 0, j = 0; let low = 1, high = Math.min(n, 100000); while (low < high) { const mid = (low + high) >> 1; const sum = mid * (mid + 1) * (mid + 2) / 6; if (sum >= n) { high = mid; } else { low = mid + 1; } } i = low; n -= (i - 1) * i * (i + 1) / 6; low = 1; high = i; while (low < high) { const mid = (low + high) >> 1; const sum = mid * (mid + 1) / 2; if (sum >= n) { high = mid; } else { low = mid + 1; } } j = low; return (i - 1) * i / 2 + j; };
复杂度分析
时间复杂度:O(\log n),其中 n 是盒子数。
空间复杂度:O(1)。
方法三:解方程
思路与算法
由方法二可知,我们要找到最小的 i 满足 g(i) \ge n,其中 g(i) = i \times (i + 1) \times (i + 2)}{6。因为有 i^3 \lt i \times (i + 1) \times (i + 2) \lt (i + 1)^3 成立, 所以有 i^3/6} \lt g(i) \lt (i + 1)^3/6。
因此,我们先求得 i = \lfloor \sqrt[3]{6 \times n} \rfloor,如果 g(i) \lt n,则将 i 加一后就是我们要求的 i。