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。

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

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

设第一个满足条件的k是x,那么km=x+3∗m,m=0,1,2...k_m = x + 3*m, m=0, 1,2...km​=x+3∗m,m=0,1,2...
我们选定kl位置
得到了一种计算dp[i]的情况,即candidatel=kl+1+dp[(i−kl)/3]candidate_l=k_l + 1 + dp[(i - k_l)/3]candidatel​=kl​+1+dp[(i−kl​)/3]
如果我们选定kl+1
得到了第二种计算dp[i]的情况,即candidatel+1=kl+1+1+dp[(i−kl+1)/3]candidate_{l+1}=k_{l+1} + 1 + dp[(i - k_{l+1})/3]candidatel+1​=kl+1​+1+dp[(i−kl+1​)/3]
从k的定义我们得出kl+1=kl+3k_{l+1}=k_l+3kl+1​=kl​+3
得出如下
candidatel+1=kl+1+1+dp[(i−kl+1)/3]=kl+3+1+dp[(i−kl)/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]candidatel+1​=kl+1​+1+dp[(i−kl+1​)/3]=kl​+3+1+dp[(i−kl​)/3−1]
又因为(参考前面的弱智结论)
1+dp[(i−kl)/3−1]≥dp[(i−kl)/3]1 + dp[(i-k_l)/3-1]\ge dp[(i - k_l)/3]1+dp[(i−kl​)/3−1]≥dp[(i−kl​)/3]
所以candidatel+1≥2+candidatel>candidatelcandidate_{l+1}\ge 2 + candidate_l> candidate_{l}candidatel+1​≥2+candidatel​>candidatel​

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

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

复杂度分析

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

总结

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

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

LeetCode 202次周赛 1553. Minimum Number of Days to Eat N Oranges相关推荐

  1. LeetCode刷题:871. Minimum Number of Refueling Stops

    LeetCode刷题:871. Minimum Number of Refueling Stops 原题链接:https://leetcode.com/problems/minimum-number- ...

  2. LeetCode——1888. 使二进制字符串字符交替的最少反转次数(Minimum Number of Flips to Make the Binary ...)[中等]——分析及代码(Java)

    LeetCode--1888. 使二进制字符串字符交替的最少反转次数[Minimum Number of Flips to Make the Binary String Alternating][中等 ...

  3. LeetCode 871. Minimum Number of Refueling Stops 最少加油次数

    LeetCode 871. Minimum Number of Refueling Stops 本题是LeetCode 871题,最少加油次数. 题目描述 A car travels from a s ...

  4. LeetCode: 871. Minimum Number of Refueling Stops

    LeetCode: 871. Minimum Number of Refueling Stops 题目描述 A car travels from a starting position to a de ...

  5. LeetCode——5814. 新增的最少台阶数(Add Minimum Number of Rungs)[中等]——分析及代码(Java)

    LeetCode--5814. 新增的最少台阶数[Add Minimum Number of Rungs][中等]--分析及代码[Java] 一.题目 二.分析及代码 1. 模拟 + 贪心 (1)思路 ...

  6. LeetCode 202. Happy Number--Python解法--数学题

    此文首发于我的个人博客:LeetCode 202. Happy Number–Python解法–数学题 - zhang0peter的个人博客 LeetCode题解专栏:LeetCode题解 LeetC ...

  7. LeetCode871.Minimum Number of Refueling Stops

    871.Minimum Number of Refueling Stops(加油站的最小数量) Description Difficulty: hard Example 1: Example 2: E ...

  8. LeetCode刷题记录10——434. Number of Segments in a String(easy)

    LeetCode刷题记录10--434. Number of Segments in a String(easy) 目录 LeetCode刷题记录9--434. Number of Segments ...

  9. 452 Minimum Number of Arrows to Burst Balloons

    452 Minimum Number of Arrows to Burst Balloons 文章目录 452 Minimum Number of Arrows to Burst Balloons 1 ...

最新文章

  1. Develop Smartphone Device use VS.NET 2003
  2. 记录:自制的小说推荐程序(一)
  3. Linux远程软件xshell的使用
  4. 企业网站SEO优化的四大原则
  5. wpfdiagram 学习 教学_李倩、吴欣歆:新高考背景下高中语文教学的三个转变
  6. 数据库oracle 别名不能更新,数据库oracle改成mysql后Hibernate不能使用别名问题
  7. windows 修改nginx端口号_分享Nginx搭建图片服务器简单实现
  8. 网络爬虫-原理篇(一)
  9. 分享一些自用软件,学妹们用了都说好用!
  10. 深度学习系列(一)【人类语言处理--引言】
  11. vb.net 全局键盘钩子
  12. Android实现手机和电脑屏幕共享
  13. paypal注册流程
  14. 计算机病毒的防治方法不包括,计算机病毒的防治方法
  15. catia制作物料明细_CATIA导出装配文件的部件列表BOM清单到Excel文件 | 坐倚北风
  16. 帝国时代2决定版服务器维护,帝国时代2决定版闪退问题解决方法
  17. Python爬虫初学一(爬虫基础)
  18. git remote prune
  19. 最新花椒回放下载方法-马赛克视频助手
  20. 怎样在LaTeX中使用中文

热门文章

  1. 将SQL Server 2000数据库Image类型图片数据显示在页面上
  2. 怎么对人像抠图 在线PS怎么处理图片
  3. 电子围栏在化工厂定位系统中的应用,化工厂精确定位,减少损失-新导智能
  4. 投影机融合的工作报告
  5. Python一个月实训记录
  6. windows和Linux分区相互访问
  7. c# oracle 数据库连接,C#中oracle数据库的连接方法
  8. 云客Drupal源码分析之音译转化Transliteration
  9. 伊朗APT组织的网络间谍工具源代码和攻击者被泄露和曝光
  10. MATLAB在安装时应选择安装的产品组件