第三十六天
1231 分享巧克力
你有一大块巧克力,它由一些甜度不完全相同的小块组成。我们用数组 sweetness
来表示每一小块的甜度。
你打算和 K
名朋友一起分享这块巧克力,所以你需要将切割 K
次才能得到K+1
块,每一块都由一些 连续 的小块组成。
为了表现出你的慷慨,你将会吃掉 总甜度最小 的一块,并将其余几块分给你的朋友们。
请找出一个最佳的切割策略,使得你所分得的巧克力 总甜度最大,并返回这个最大总甜度。
方法
由于所有的巧克力必须连续分,我们可以使用二分搜索来确定答案,然后再通过具体相应判断算法来判断某一个答案是否合法。本题可以使用二分搜索答案的关键点在于,如果res
是答案,那么比res
大的一定不行,而比res
小的一定也可以,它具有二段性。
因此,我们使用二分来找这个res
,然后判断res
是否可达,如果发现res
可达,我们就尽可能的让res
大,否则让res
变小。直到我们找到答案为止。
对于一个答案是否可达,我们可以通过模拟切巧克力的过程,让切出来的大小在超过res
并尽可能的靠近res
,然后我们统计一下所切的巧克力的块数,如果超过了k+1
块(分给k
个朋友,自己也需要一块,所以需要k+1
块)说明这个res
是可以实现的,即可以分出k+1
块,让每一块的大小不小于res
。
然后我们需要确定搜索的范围,容易知道搜索的左边界是整个巧克力数组的最小值,对于搜索的右边界,我们知道最极端的情况是k+1
所分到的巧克力相同,因此右边界是所有巧克力总和除以k+1
。
class Solution {
public int maximizeSweetness(int[] sweetness, int k) {
int l = 1000000;
int[] sumI = new int[sweetness.length + 1];
for (int i = 0; i < sweetness.length; ++i){
sumI[i + 1] = sweetness[i] + sumI[i];
l = Math.min(l, sweetness[i]);//搜索的左边界
}
int r = sumI[sumI.length - 1] / (k + 1);//搜索的右边界
//二分搜索答案
while (l <= r){
int mid = (l + r) >> 1;
if (isOk(sumI, mid, k + 1)) l = mid + 1;//如果能够满足 我们尝试调大
else r = mid - 1;//不能满足 调小一点
}
return r;//返回能够满足的最大值
}
public boolean isOk(int[] sum, int target, int nums){
int count = 0;
int s = 0, e = 1;
//模拟切巧克力
while (e < sum.length){
if (sum[e] - sum[s] < target) e++;
else{
count++; //超过了target 切下一块 统计块数
s = e;
e++;
}
}
return count >= nums; //能不能切出k+1块
}
}
1182 与目标颜色的距离
给你一个数组 colors
,里面有 1
、2
、 3
三种颜色。
我们需要在 colors
上进行一些查询操作 queries
,其中每个待查项都由两个整数 i
和 c
组成。
现在请你帮忙设计一个算法,查找从索引 i
到具有目标颜色 c
的元素之间的最短距离。
如果不存在解决方案,请返回 -1
。
方法
和上一题的思路大致相同,我们可以二分搜索这个最短距离,因为这个最短距离是具有二段性的,如果最短距离是distance
,那么小于distance
的一定不行,而大于等于distance
的一定可以。我们使用二分去搜索这个最短距离,注意距离是双向的,因此左右都要找。
class Solution {
public List<Integer> shortestDistanceColor(int[] colors, int[][] queries) {
ArrayList<Integer> res = new ArrayList<>();
Map<Point, Integer> map = new HashMap<>();
for (int[] query : queries){
if (map.containsKey(new Point(query))){
res.add(map.get(new Point(query)));
continue;
}
int l = 0, r = Math.max(colors.length - query[0] - 1, query[0]);//确定搜索范围 左边界是0 右边界是到数组开头 和 尾部两者距离中的最大值
boolean flag = false;
while (l <= r){
int mid = (l + r) >> 1;
if (isOk(colors, mid, query[0], query[1])) {r = mid - 1;flag = true;}
else l = mid + 1;
}
int ans = flag ? l : -1;
res.add(ans);
map.put(new Point(query), ans);
}
return res;
}
//判断在所给的distance内能不能找到答案
public boolean isOk(int[] colors, int distance, int start, int target) {
for (int i = 0; i <= distance; ++i){
int count = 0;
if (start + i < colors.length){
if (colors[start + i] == target) return true;
count++;
}
if (start - i >= 0){
if (colors[start - i] == target) return true;
count++;
}
if (count == 0) break;
}
return false;
}
}
class Point{
int x;int y;
Point(int[] a){x = a[0]; y = a[1];}
@Override public boolean equals(Object p){
Point cmp = (Point) p;
return cmp.x == x && cmp.y == y;
}
@Override public int hashCode(){return x | y;}
}