A. 牛牛分蛋糕
题目描述
牛牛今天家里要来客人,所以牛牛今天特意做了他最拿手的两种蛋糕,但是他是一个有洁癖的人,所以他在分蛋糕时,有如下几个原则:
他不希望一个盘子里出现两种蛋糕
他希望每个盘子中都有蛋糕
他想让装有最少蛋糕数量的盘子中装有的蛋糕数量尽可能多
备注:
n, a, b(1 ≤ a, b ≤ 10^5, 2 ≤ n ≤ a + b)
第一个参数代表盘子的数量
第二个参数代表第一种蛋糕的数量
第三个参数代表第二种蛋糕的数量。
程序应返回:在所有分法中,蛋糕数量最少的盘子中分到最多的蛋糕数量。
示例1
输入
5, 2, 3
输出
1
说明
只有一种方法把蛋糕分配到盘子里,即所有的盘子上都有一个蛋糕。
示例2
输入
4, 7, 10
输出
3
说明
第一个盘子中装有第一种蛋糕三个,第二个盘子中装有第一种蛋糕四个,第三个、第四个盘子中各装有第二种蛋糕五个。
解法一:暴力搜盘子
思路分析
要想装最少蛋糕数量的盘子中装的蛋糕最多,必定是均匀分配蛋糕。因此可以搜索所有的盘子分配方案,看哪种情况下均匀分配到的蛋糕最多。
时间复杂度:$O(n)$。总共有 $n - 1$ 种盘子分配方案。
空间复杂度:$O(1)$。
代码实现
public int solve (int n, int a, int b) {
int res = 0;
for(int i = 1; i < n; i++) {
int min = Math.min(a / i, b / (n - i));
res = Math.max(res, min);
}
return res;
}
解法二:二分搜蛋糕
思路分析
每个盘子都分配 $c$ 块蛋糕,若 $a / c + b / c \ge n$ 说明这样分配能使 $n$ 个盘子中都有蛋糕。二分搜索寻找最大的 $c$ 即可。
时间复杂度:$O(log(min(a,b)))$。
空间复杂度:$O(1)$
代码实现
public int solve (int n, int a, int b) {
int l = 1, r = Math.min(a, b);
while(l < r){
int mid = (l + r + 1) >> 1;
if(a / mid + b / mid < n) r = mid - 1;
else l = mid;
}
return l;
}
B. 牛牛凑数字
题目描述
牛牛今天逛商店,看到商店里摆着一些很漂亮的数字,牛牛非常喜欢,想买一些数字带回家。
数字一共有九种类型,分别是 1 - 9 这九个数字,每个数字的价钱都不一样,而且每个数字的货源都非常充足。
牛牛是个完美主义者,他希望用自己的能够承受的价格,从这些数字里面购买,并且凑到最大的数字带回家。
备注:
第一个参数为一个整数 n(0 ≤ n ≤ 10^6),代表牛牛所能承受的价格。
第二个参数为 1 - 9 这九个数字的价格数组,a1, a2, ……, a9(1 ≤ ai ≤ 10^5)。
程序应返回:一个数字,代表牛牛能凑到的最大的数字。当然,如果牛牛一个数字都买不起,返回 "-1" 即可。
注意,由于数字可能会很大,所以程序中需要处理成 string 类型进行返回。
示例1
输入
5,[5,4,3,2,1,2,3,4,5]
输出
"55555"
说明
第 5 个数字只需要花费 1,所以买 5 个第 5 个数字可以凑到最大值 55555。
示例2
输入
2,[9,11,1,12,5,8,9,10,6]
输出
"33"
说明
购买 2 个第 3 个数字,可以凑到最大值为 33。
解法:贪心
思路分析
要想数字最大,首先得数字的数量最多。其次,在相同数量的数字时,应尽可能买最大的数字。
如果每一个都买花费最少的数字 $a$,那么数字的数量 $cnt$ 必定是最多的。
找到这个最多的数量后,从大到小搜寻数字,若不减少数量的情况下能买到更大的数字,则用它替换掉 $a$。
时间复杂度:$O(\frac n {min(a_i)})$。最多买 $\frac n {min(a_i)}$ 个数字,因此最多搜寻这么多次。
空间复杂度:$O(1)$。
代码实现
public String solve (int n, int[] a) {
StringBuilder res = new StringBuilder();
int min = Integer.MAX_VALUE;
int minNum = 0;
for(int i = 0; i < 9; i++ ) {
if(a[i] <= min) {
min = a[i];
minNum = i + 1;
}
}
if(n < min) return "-1";
boolean incre = true;
while(incre && n > min) {
incre = false;
for(int i = 8; i >= minNum; i--) {
if(n >= a[i] && ((n - a[i]) / min == n / min - 1)) {
res.append(i + 1);
n -= a[i];
incre = true;
break;
}
}
}
for(int i = 1; i <= n / min; i++) res.append(minNum);
return res.toString();
}
另一种写法(时间复杂度没变,只是降低了代码量)
public String solve (int n, int[] a) {
StringBuilder res = new StringBuilder();
int minCost = Integer.MAX_VALUE;
for(int i = 0; i < 9; i++ ) minCost = Math.min(minCost, a[i]);
if(n < minCost) return "-1";
int cnt = n / minCost;
for(int i = 8; i >= 0 && res.length() < cnt; i--){
while(n - a[i] >= minCost * (cnt - res.length() -1)){
res.append(i + 1);
n -= a[i];
if(res.length() == cnt) break;
}
}
return res.toString();
}
C. 牛妹的野菜
题目描述
书接上回,牛妹组织春游,有一个有趣的项目是挖番薯。聪明的牛妹拿到了一个标明了番薯洞的地图,每个番薯洞中有一定数量的番薯。同时,我们知道番薯洞的连接路径,并规定路径是单向且小序号指向大序号,也无环。可以从任意一处开始挖,然后沿着连接往下挖(仅能选择一条路径),当无连接时,结束。
设计一种挖番薯的方案,使得可以挖到更多的番薯。
输出路径。
备注:
总番薯数量不超过1000000,番薯洞数量不超过250.
示例1
输入
[5,10,20,5,4,5],[[1,2],[1,4],[2,4],[3,4],[4,5],[4,6],[5,6]]
输出
"3-4-5-6"
说明
很明显 先去第三点拿20个番薯,再去第四个点拿5个,再去第五个点拿4个,再去第六个点拿5个。这个方案最优
解法: 回溯
思路分析
(内心 os: 作为成年人,所有番薯我全要。一条路径上的番薯只够牛妹一个人吃!)
当我们处于洞口 $a$ 时,如果我们知道了每一个洞口 $b (b \in next(a))$ 的最优方案,那么可以直接比较集合 $next(a)$ 的各个方案,选择其中最优的作为下一个洞口。
依照这个思路,我们只要从路径的底部往头搜寻,就可以一次遍历得到所有洞口的最优方案。那么如何找到路径的底部呢?答案就是利用回溯。从入口递归的往下搜寻洞口,然后回溯中返回下一个洞口的最优方案。
时间复杂度:$O(n)$。每个洞口最多搜寻一次
空间复杂度:$O(n^2)$。对于 $n$ 个洞口,需要记录它们的下一个洞口。
代码实现
List[] next; // 从 i 出发的下一个洞口
String[] path; // 从 i 出发的最优方案路径
int[] potatoMaxNum; //从 i 出发可以挖到番薯的最大数量
int[] potatoNum;
public String digSum (int[] potatoNum, int[][] connectRoad) {
int n = potatoNum.length;
next = new List[n + 1];
path = new String[n + 1];
potatoMaxNum = new int[n + 1];
this.potatoNum = potatoNum;
for(int i = 1; i <= n; i++) next[i] = new ArrayList(250);
for(int[] path: connectRoad){
next[path[0]].add(path[1]);
}
String res = "";
int maxNum = 0;
for(int i = 1; i <= n; i++) {//遍历 n 个洞口,看看哪个洞口出发得到的番薯最多
dfs(i);
if(potatoMaxNum[i] > maxNum) {
maxNum = potatoMaxNum[i];
res = path[i];
}
}
return res;
}
public int dfs(int d){//返回洞口 d 出发得到的最大番薯数量
if(potatoMaxNum[d] != 0) return potatoMaxNum[d];
if(next[d].isEmpty()) {//最后一个洞口了
path[d] = "" + d;
return potatoMaxNum[d] = potatoNum[d - 1];
}
int maxd = d;
for(Integer nextd: next[d]) {//看看下一步去哪个洞口得到的番薯最多
int num = dfs(nextd);
if(num > potatoMaxNum[maxd]) maxd = nextd;
}
potatoMaxNum[d] = potatoNum[d - 1] + potatoMaxNum[maxd];
path[d] = d + "-" + path[maxd];
return potatoMaxNum[d];
}
写在最后
大家好,我是往西汪,一位坚持原创的新人博主。
如果本文对你有帮助,请动动你的小手指点个赞👍。你的支持是我创作路上的最大动力。谢谢大家!
也欢迎来公众号【往西汪】找我玩耍~