力扣-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)

猜你喜欢

转载自blog.csdn.net/qq_45678698/article/details/120335755
今日推荐