当前位置: 代码迷 >> 综合 >> LeetCode 202次周赛 1553. Minimum Number of Days to Eat N Oranges
  详细解决方案

LeetCode 202次周赛 1553. Minimum Number of Days to Eat N Oranges

热度:57   发布时间:2024-02-11 21:52:49.0

1553. Minimum Number of Days to Eat N Oranges

  • 题目描述
  • 思路分析
    • 弱智动态规划
    • 可行的动态规划
    • 直觉上正确的思路
    • 代码实现
    • 证明正确性
    • 复杂度分析
  • 总结

题目描述

There are n oranges in the kitchen and you decided to eat some of these oranges every day as follows:

  • Eat one orange.
  • I f the number of remaining oranges (n) is divisible by 2 then you can eat n/2 oranges.
  • If the number of remaining oranges (n) is divisible by 3 then you can eat 2*(n/3) oranges.
    You can only choose one of the actions per day.

Return the minimum number of days to eat n oranges.

Example 1:

Input: n = 10
Output: 4
Explanation: You have 10 oranges.
Day 1: Eat 1 orange, 10 - 1 = 9.
Day 2: Eat 6 oranges, 9 - 2*(9/3) = 9 - 6 = 3. (Since 9 is divisible by 3)
Day 3: Eat 2 oranges, 3 - 2*(3/3) = 3 - 2 = 1.
Day 4: Eat the last orange 1 - 1 = 0.
You need at least 4 days to eat the 10 oranges.

Example 2:

Input: n = 6
Output: 3
Explanation: You have 6 oranges.
Day 1: Eat 3 oranges, 6 - 6/2 = 6 - 3 = 3. (Since 6 is divisible by 2).
Day 2: Eat 2 oranges, 3 - 2*(3/3) = 3 - 2 = 1. (Since 3 is divisible by 3)
Day 3: Eat the last orange 1 - 1 = 0.
You need at least 3 days to eat the 6 oranges.

Example 3:

Input: n = 1
Output: 1
Example 4:

Input: n = 56
Output: 6

Constraints:

1 <= n <= 2*109

n个橙子,有3种吃法,
吃一个
如果橙子个数是偶数,可以吃掉一半
如果橙子个数能被3整除,吃掉2/3,剩余1/3
一天只能选择一种方式,问最少几天能把n个橙子吃完

思路分析

弱智动态规划

这题看完以后立马就能想到一个无脑动态规划
dp[i]表示吃完i个橙子最少要几天
对应3种吃法
dp[i] = min(dp[i], 1 + dp[i - 1])
dp[i] = min(dp[i], 1 + dp[i / 2]) if i % 2 == 0
dp[i] = min(dp[i], 1 + dp[i / 3]) if i % 3 == 0

这么算下去,o(n)时间就能求出答案。也容易证明答案是对的,把题目要求种所有的可能性都尝试了一边,等效于无脑暴力破解。

这么写肯定超时了,题目数据量可以给到2*109,20亿的数据量o(n)是不行的。

可行的动态规划

直觉上正确的思路

若当前有n个橙子,根据规则,n只能越吃越少。直觉告诉我们,要想迅速吃完,最好是遇到条件2和条件3,这样能一次吃一半或者吃三分之二。要真的每次都能这么吃,那很快就吃完了。于是乎,我们就想到找个最近的能被2或者3整除的数字,这样就可以在下一步的时候至少能用一个除法来痛快干一轮。

但是到底找最近的2还是3呢?不好说。那就都找找看
如何找到小于等于n最近的能被2整除的数?就是(n/2)*2,因为n = (n/2)*2 + n %2
同理,对于最近的能被3整除的数,就是(n/3)*3,因为n = (n/3)*3 + n % 3
这里的除法都是整数除法

那么dp[n]就可以有从dp[n/2]和dp[n/3]来算了。
dp[n] = min(dp[n], n % 2 + 1 + dp[n / 2])
dp[n] = min(dp[n], n % 3 + 1 + dp[n / 3])
其中n % 3是一个一个吃的,1是做除法的那一轮吃的

代码实现

class Solution {unordered_map<int, int> memo;
public:int minDays(int n) {memo.clear();return dp(n);}int dp(int n) {if ( n <= 2 ) return n;if ( memo.count(n) ) return memo[n];int res = n;res = min(res, 1 + n % 2 + dp(n / 2));res = min(res, 1 + n % 3 + dp(n / 3));memo[n] = res;return res;}
};

证明正确性

看完上面那个思路,有没有觉得有点不服气?这么选择有没有可能漏掉某些情况呢?如果你也有同样的疑虑,说明老哥你上道了,你是个严谨的程序员。我本人在当时做周赛的时候有这么想过,上面贴的代码也不是我当时写的,当时我就觉得这么想好像是对的,但是总是觉得特别心虚。我周赛的时候提交的代码是bfs + memo,是真的考虑了3种情况。虽然也通过了,但是感觉这题目因该不是我当初那么想的。

事后看了大神们的高票答案,直接找最近的2或者3是可行的,但是讨论版本里面也没人给出详细的说明,总是感觉有点虚。我这里就尝试着证明一下为什么这个思路是完备的。

我们首先回顾一下无脑动态规划的思路
无脑的动态规划就是推给n-1,如果当前的n可以被2或者3除,那么就再多考虑这类情况。这么想当然是最接近题目规则的。直觉又告诉我们除法似乎是比减法要好的。我们拓展一下无脑动态规划,
dp[n] =min(dp[n], k + 1 + dp[(n - k) / 2]) 如果n - k可以被2整除
dp[n] =min(dp[n], k + 1 + dp[(n - k) / 3]) 如果n - k可以被3整除
我用一段代码写出求dp[n]过程,看懂下面的代码是理解后面证明的关键

int minDays(int n) {vector<int> dp(n + 1);dp[0] = 0, dp[1] = 1;for ( int i = 2; i <= n; ++i ) {dp[i] = i;for ( int k = 0; k <= i; ++k ) {if ( (i - k) % 2 == 0 ) {dp[i] = min(dp[i], k + 1 + dp[(i - k) / 2]);}if ( (i - k) % 3 == 0 ) {dp[i] = min(dp[i], k + 1 + dp[(i - k) / 3]);}}}return dp[n];
}

这么写复杂度变成o(n2)了,岂不是得不偿失。但是这个思路给我们带来一个很好得启发,我们可以选择下一步做除法的位置,在代码中体现就是满足条件的i - k。

下面我们来进行推导
先给一个简单结论: d p [ i ] d p [ i ? k ] + k dp[i] \le dp[i - k] + k
这个太弱智了,不证了。

不妨设下一步做除法的位置在i - k,除以3,(i - k) % 3 等于0.
我们可以找到好多个满足要求的k。
举个例子:假如i是19,k可以是1,4,7,10,13,16,19

设第一个满足条件的k是x,那么 k m = x + 3 ? m , m = 0 , 1 , 2... k_m = x + 3*m, m=0, 1,2...
我们选定kl位置
得到了一种计算dp[i]的情况,即 c a n d i d a t e l = k l + 1 + d p [ ( i ? k l ) / 3 ] candidate_l=k_l + 1 + dp[(i - k_l)/3]
如果我们选定kl+1
得到了第二种计算dp[i]的情况,即 c a n d i d a t e l + 1 = k l + 1 + 1 + d p [ ( i ? k l + 1 ) / 3 ] candidate_{l+1}=k_{l+1} + 1 + dp[(i - k_{l+1})/3]
从k的定义我们得出 k l + 1 = k l + 3 k_{l+1}=k_l+3
得出如下
c a n d i d a t e l + 1 = k l + 1 + 1 + d p [ ( i ? k l + 1 ) / 3 ] = k l + 3 + 1 + d p [ ( i ? k l ) / 3 ? 1 ] candidate_{l+1}=k_{l+1} + 1 + dp[(i - k_{l+1})/3] = k_l + 3 + 1 + dp[(i-k_l)/3-1]
又因为(参考前面的弱智结论)
1 + d p [ ( i ? k l ) / 3 ? 1 ] d p [ ( i ? k l ) / 3 ] 1 + dp[(i-k_l)/3-1]\ge dp[(i - k_l)/3]
所以 c a n d i d a t e l + 1 2 + c a n d i d a t e l > c a n d i d a t e l candidate_{l+1}\ge 2 + candidate_l> candidate_{l}

结论,同样是找除以3的位置,越往后越亏,找第一个最好
同理,对于除以2的位置,也可以得出同样的结论

经过证明,第二段代码就可以顺理成章改成第一段代码。

复杂度分析

对于数字n,要么推给n/2,要么推给n/3。
计算dp[n]需要的所有状态是 n 2 a ? 3 b \frac{n}{2^a*3^b}
满足条件的a,b组合有多少对呢?
不太好算,但是不会超过log2(n)*log3(n)对
时间复杂度(上界)
o ( l o g 2 ( n ) ? l o g 3 ( n ) ) o ( ( l o g 3 ( n ) ) 2 ) o(log2(n)*log3(n))\le o((log3(n))^2)

总结

这个题目通过率应该很高,但是想弄清楚背后的原理不是那么容易。这个题目向我们展示了同一套规则的两种不用的遍历搜索方法。正所谓条条大路通罗马。
第一种方法对应无脑动态规划。

第二种方法其实更无脑不说,还升了一个维度。但是第二种可以通过数学推导有效减少dp的状态数,实现了指数跳跃。塞翁失马焉知非福啊。

  相关解决方案