第六十七天 --- 力扣-887+88
题目一
思路
为什么用DP:有多阶段决策
我们想,在一个j层楼的房子里,我在第n层(n>=1&&n<=j),共k个鸡蛋处扔鸡蛋,就两种可能,鸡蛋碎或者鸡蛋不碎,因为本题中的f是一个不确定值,所以鸡蛋碎或者不碎都有可能,所以此时这个,在第n层共k个鸡蛋处扔鸡蛋,有多少个操作数的问题就变成了,鸡蛋碎了,和鸡蛋没碎这两种子问题二者方案数取最大值的问题(为了保证无论是哪种情况都能判断出来,所以二者取大值),所以大问题转变成了小问题,产生了多阶段决策,可以尝试DP
状态
看题目,题中给的有鸡蛋个数,楼层高度,因为扔鸡蛋就是从某一层扔到第一层,所以只一个状态即可。综上:两个状态,k是鸡蛋数,n是楼层数
初始值
1、如果我只有一层楼,那么多少个鸡蛋我都只能操作一次
2、我就只有一个鸡蛋,那就只能逐层的实验。
转移方程
1、dp[k][n]代表,第n层楼,有k个鸡蛋。
2、随便在第x层(x>=1&&x<=n),扔鸡蛋,鸡蛋碎了,那么我的状态就变成了dp[k-1][x-1],因为碎了个鸡蛋,所以k-1,因为x层鸡蛋碎了,所以想找f,只能在1到x-1层了。
3、随便在第x层(x>=1&&x<=n),扔鸡蛋,鸡蛋没碎,那么我的状态就变成了dp[k][n-x],因为x层鸡蛋没碎,所以想找f,只能在x+1到n层了,即在n中除了看完的x层之外寻找。
4、因为f未知,所以具体碎不碎不知道,但是为了说应对所有情况,所以操作数取二者中较大的。
5、我在每个楼层都扔一边,因为题中所说要最小值,故转移方程式如下:
找答案
最终答案存于dp[k][n]
优化
1、看咱们原本的状态转移方程,挨个枚举点,来寻找目标,这是最笨的方法,时间复杂度达到了惊人的O(kn^2),放在这里必定超时。
2、所以我们不能用O(N)的复杂度来寻找最小值点,这里就是个常识:一旦要快速寻找,就是往二分法去想。
3、具体分析如下:
dp[k][n] 是一个单增函数,T1=dp[k-1][x-1] T2=dp[k][n-x],二者也同样单调,当x增大,T1单增,T2单减,剩下分析如图所示:
综上:我们就是枚举点,所以用二分检索来代替就好了,把复杂度降低到O(lgN),以上分析有助于我们找二分检索边界,以及T1>T2和T1<T2时候边界怎么移动
代码
class Solution {
public:
int superEggDrop(int k, int n) {
int dp[101][10001];//两个状态,k鸡蛋数,n楼层数
for (int i = 1; i <= n; i++) {
//初始化
dp[1][i] = i;
}
for (int i = 1; i <= k; i++) {
//初始化
dp[i][1] = 1;
}
for (int i = 2; i <= k; i++) {
//鸡蛋数
for (int j = 2; j <= n; j++) {
//楼层数
int left = 1, right = j;//开始二分检索找到最优楼层让当前dp[i][j]最小
while (right - left >= 1) {
//这个是边界条件,上述分析有
int mid = (left + right) / 2;
int tmp2 = dp[i - 1][mid - 1];//单增,是T1(T1,T2,含义看我上面的图
int tmp1 = dp[i][j - mid];//单减,是T2
if (tmp2 > tmp1) {
right = mid - 1;//边界移动方向看上面的图
}
else if (tmp1 > tmp2) {
left = mid + 1;
}
else {
left = right = mid;
}
}
dp[i][j] = 1 + min(max(dp[i - 1][left - 1], dp[i][j - left]), max(dp[i - 1][right - 1], dp[i][j - right]));//将左右迫近的两个点二者取最小就行
}
}
return dp[k][n];
}
};
(所有代码均已在力扣上运行无误)
经测试,该代码运行情况是(经过多次测试所得最短时间):
题目二:
思路
方法一:先合并,再用sort排序
先把nums2合并到nums1里面,再排序。
方法二:从后向前,二者中不断选大的值加入目标数组
1、正常思路就是重开一个临时数组,二者从后向前,按照谁大谁先上的原则,填充就行,最后再把结果复制到nums1。
2、但是这样会浪费空间,为了防止空间浪费,并且nums1已经提前开好了n+m个空间了,所以两个数组选取规则同上,选出来的元素直接加入nums1的尾部就行了,证明略。
代码
方法一:
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int p = m;
for (; p < m + n; p++) {
nums1[p] = nums2[p - m];
}
sort(nums1.begin(), nums1.end());
}
};
(所有代码均已在力扣上运行无误)
经测试,该代码运行情况是(经过多次测试所得最短时间):
时间复杂度:O(N)
方法二
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int point = m + n - 1;//nums1结尾
int p1 = m - 1;//nums1存的数据结尾
int p2 = n - 1;//nums2结尾
while (p1 >= 0 && p2 >= 0) {
if (nums1[p1] < nums2[p2]) {
nums1[point] = nums2[p2];
point--;
p2--;
}
else {
nums1[point] = nums1[p1];
point--;
p1--;
}
}
while (p1 >= 0) {
//把剩下的补上去
nums1[point] = nums1[p1];
point--;
p1--;
}
while (p2 >= 0) {
//把剩下的补上去
nums1[point] = nums2[p2];
point--;
p2--;
}
}
};
(所有代码均已在力扣上运行无误)
经测试,该代码运行情况是(经过多次测试所得最短时间):
时间复杂度:O(N)