目录
倒水问题
力扣 365. 水壶问题
有两个水壶,容量分别为 jug1Capacity 和 jug2Capacity 升。水的供应是无限的。确定是否有可能使用这两个壶准确得到 targetCapacity 升。
如果可以得到 targetCapacity 升水,最后请用以上水壶中的一或两个来盛放取得的 targetCapacity 升水。
你可以:
装满任意一个水壶
清空任意一个水壶
从一个水壶向另外一个水壶倒水,直到装满或者倒空
示例 1:
输入: jug1Capacity = 3, jug2Capacity = 5, targetCapacity = 4
输出: true
解释:来自著名的 "Die Hard"
示例 2:
输入: jug1Capacity = 2, jug2Capacity = 6, targetCapacity = 5
输出: false
示例 3:
输入: jug1Capacity = 1, jug2Capacity = 2, targetCapacity = 3
输出: true
提示:
1 <= jug1Capacity, jug2Capacity, targetCapacity <= 106
long long gcd(long long a, long long b)
{
if (b == 0)return a;
return gcd(b, a%b);
}
class Solution {
public:
bool canMeasureWater(int a, int b, int s) {
if (s > a + b)return false;
int c = gcd(a, b);
return s % c == 0;
}
};
兑换问题
兑换问题分为最后可以借1个空瓶子,和不能借,2种版本。
2种版本的答案可能一样也可能隔1。
力扣 1518. 换水问题
超市正在促销,你可以用 numExchange
个空水瓶从超市兑换一瓶水。最开始,你一共购入了 numBottles
瓶水。
如果喝掉了水瓶中的水,那么水瓶就会变成空的。
给你两个整数 numBottles
和 numExchange
,返回你 最多 可以喝到多少瓶水。
示例 1:
输入:numBottles = 9, numExchange = 3
输出:13
解释:你可以用 3
个空瓶兑换 1 瓶水。
所以最多能喝到 9 + 3 + 1 = 13 瓶水。
示例 2:
输入:numBottles = 15, numExchange = 4
输出:19
解释:你可以用 4
个空瓶兑换 1 瓶水。
所以最多能喝到 15 + 3 + 1 = 19 瓶水。
提示:
1 <= numBottles <= 100
2 <= numExchange <= 100
class Solution {
public:
int numWaterBottles(int numBottles, int numExchange) {
return numBottles*numExchange/(numExchange-1)-(numBottles%(numExchange-1)==0);
}
};
鸡蛋楼层问题
力扣 1884. 鸡蛋掉落-两枚鸡蛋
给你 2 枚相同 的鸡蛋,和一栋从第 1
层到第 n
层共有 n
层楼的建筑。
已知存在楼层 f
,满足 0 <= f <= n
,任何从 高于 f
的楼层落下的鸡蛋都 会碎 ,从 f
楼层或比它低 的楼层落下的鸡蛋都 不会碎 。
每次操作,你可以取一枚 没有碎 的鸡蛋并把它从任一楼层 x
扔下(满足 1 <= x <= n
)。如果鸡蛋碎了,你就不能再次使用它。如果某枚鸡蛋扔下后没有摔碎,则可以在之后的操作中 重复使用 这枚鸡蛋。
请你计算并返回要确定 f
确切的值 的 最小操作次数 是多少?
示例 1:
输入:n = 2 输出:2 解释:我们可以将第一枚鸡蛋从 1 楼扔下,然后将第二枚从 2 楼扔下。 如果第一枚鸡蛋碎了,可知 f = 0; 如果第二枚鸡蛋碎了,但第一枚没碎,可知 f = 1; 否则,当两个鸡蛋都没碎时,可知 f = 2。
示例 2:
输入:n = 100 输出:14 解释: 一种最优的策略是: - 将第一枚鸡蛋从 9 楼扔下。如果碎了,那么 f 在 0 和 8 之间。将第二枚从 1 楼扔下,然后每扔一次上一层楼,在 8 次内找到 f 。总操作次数 = 1 + 8 = 9 。 - 如果第一枚鸡蛋没有碎,那么再把第一枚鸡蛋从 22 层扔下。如果碎了,那么 f 在 9 和 21 之间。将第二枚鸡蛋从 10 楼扔下,然后每扔一次上一层楼,在 12 次内找到 f 。总操作次数 = 2 + 12 = 14 。 - 如果第一枚鸡蛋没有再次碎掉,则按照类似的方法从 34, 45, 55, 64, 72, 79, 85, 90, 94, 97, 99 和 100 楼分别扔下第一枚鸡蛋。 不管结果如何,最多需要扔 14 次来确定 f 。
提示:
1 <= n <= 1000
思路:dp
f(n)=min{ max(f(n-k)+1,k) | 1<=k<=n}
class Solution {
public:
int twoEggDrop(int n) {
return sqrt(n * 2 + 0.25) + 0.4999;
}
};
力扣 887. 鸡蛋掉落
给你 k 枚相同的鸡蛋,并可以使用一栋从第 1 层到第 n 层共有 n 层楼的建筑。
已知存在楼层 f ,满足 0 <= f <= n ,任何从 高于 f 的楼层落下的鸡蛋都会碎,从 f 楼层或比它低的楼层落下的鸡蛋都不会破。
每次操作,你可以取一枚没有碎的鸡蛋并把它从任一楼层 x 扔下(满足 1 <= x <= n)。如果鸡蛋碎了,你就不能再次使用它。如果某枚鸡蛋扔下后没有摔碎,则可以在之后的操作中 重复使用 这枚鸡蛋。
请你计算并返回要确定 f 确切的值 的 最小操作次数 是多少?
示例 1:
输入:k = 1, n = 2
输出:2
解释:
鸡蛋从 1 楼掉落。如果它碎了,肯定能得出 f = 0 。
否则,鸡蛋从 2 楼掉落。如果它碎了,肯定能得出 f = 1 。
如果它没碎,那么肯定能得出 f = 2 。
因此,在最坏的情况下我们需要移动 2 次以确定 f 是多少。
示例 2:
输入:k = 2, n = 6
输出:3
示例 3:
输入:k = 3, n = 14
输出:4
提示:
1 <= k <= 100
1 <= n <= 104
代码:
class Solution {
public:
int ans[101][10001];
Solution(){
for(int i=0;i<101;i++)for(int j=0;j<10001;j++)ans[i][j]=0;
}
int superEggDrop(int k, int n) {
if(ans[k][n])return ans[k][n];
if(k==1)return n;
if(n==0)return 0;
ans[k][n]=n;
int low=1,high=n;
while(high-low>5){
int m=(low+high)/2;
if(superEggDrop(k-1,m-1)>superEggDrop(k,n-m))high=m;
else low=m;
}
for(int i=low;i<=high;i++)ans[k][n]=min(ans[k][n],1+max(superEggDrop(k-1,i-1),superEggDrop(k,n-i)));
return ans[k][n];
}
};
PS:
需要用二分才能降低时间复杂度,而且不能用三分
点灯问题
一维:
力扣 995. K 连续位的最小翻转次数
给定一个二进制数组 nums
和一个整数 k
。
k位翻转 就是从 nums
中选择一个长度为 k
的 子数组 ,同时把子数组中的每一个 0
都改成 1
,把子数组中的每一个 1
都改成 0
。
返回数组中不存在 0
所需的最小 k位翻转 次数。如果不可能,则返回 -1
。
子数组 是数组的 连续 部分。
示例 1:
输入:nums = [0,1,0], K = 1 输出:2 解释:先翻转 A[0],然后翻转 A[2]。
示例 2:
输入:nums = [1,1,0], K = 2 输出:-1 解释:无论我们怎样翻转大小为 2 的子数组,我们都不能使数组变为 [1,1,1]。
示例 3:
输入:nums = [0,0,0,1,0,1,1,0], K = 3 输出:3 解释: 翻转 A[0],A[1],A[2]: A变成 [1,1,1,1,0,1,1,0] 翻转 A[4],A[5],A[6]: A变成 [1,1,1,1,1,0,0,0] 翻转 A[5],A[6],A[7]: A变成 [1,1,1,1,1,1,1,1]
提示:
1 <= nums.length <= 105
1 <= k <= nums.length
class Solution {
public:
int minKBitFlips(vector<int>& nums, int k) {
vector<int>ans(nums.size(), 0);
int s = ans[0] = 1 - (nums[0] & 1);
for (int i = 1; i < ans.size(); i++) {
ans[i] = nums[i] + nums[i - 1];
if (i >= k)ans[i] += ans[i - k];
s += ans[i] & 1;
if (i > ans.size() - k && (ans[i] & 1))return -1;
}
return s;
}
};
二维及更多变化:
分辨毒药问题
力扣 458. 可怜的小猪
有 buckets
桶液体,其中 正好有一桶 含有毒药,其余装的都是水。它们从外观看起来都一样。为了弄清楚哪只水桶含有毒药,你可以喂一些猪喝,通过观察猪是否会死进行判断。不幸的是,你只有 minutesToTest
分钟时间来确定哪桶液体是有毒的。
喂猪的规则如下:
- 选择若干活猪进行喂养
- 可以允许小猪同时饮用任意数量的桶中的水,并且该过程不需要时间。
- 小猪喝完水后,必须有
minutesToDie
分钟的冷却时间。在这段时间里,你只能观察,而不允许继续喂猪。 - 过了
minutesToDie
分钟后,所有喝到毒药的猪都会死去,其他所有猪都会活下来。 - 重复这一过程,直到时间用完。
给你桶的数目 buckets
,minutesToDie
和 minutesToTest
,返回 在规定时间内判断哪个桶有毒所需的 最小 猪数 。
示例 1:
输入:buckets = 1000, minutesToDie = 15, minutesToTest = 60 输出:5
示例 2:
输入:buckets = 4, minutesToDie = 15, minutesToTest = 15 输出:2
示例 3:
输入:buckets = 4, minutesToDie = 15, minutesToTest = 30 输出:2
提示:
1 <= buckets <= 1000
1 <= minutesToDie <= minutesToTest <= 100
思路:
每只猪可以试错的次数是t=minutesToTest/minutesToDie
当t=1时,每只猪可以提供2种状态,利用二进制对buckets进行编号,每只猪只需要确定一位即可。所以需要的猪的数量是
推广到一般情况,答案就是
class Solution {
public:
int poorPigs(int buckets, int minutesToDie, int minutesToTest) {
int t = minutesToTest / minutesToDie + 1;
int ans = 0, s = 1;
while (s < buckets)s *= t, ans++;
return ans;
}
};