题目链接
心路历程
时间
(2022年11月27日--2022年11月30日) 每天大概2h
回溯法
前段时间,用了两个月的时间,参加了代码随想录的训练营。每天花费了3个小时左右的时间,基本上每天新学一题,以及复习前面的题目。
这两天学校因疫情原因让回家,在酒店住下了,便打算继续开始刷题目。想着试一试每日一题看看。自己独立思考了这一题,首先想到了这是一个分割的问题,可以使用回溯法把所有分组都记录下来。然后再去算各种情况的最大平均值,取一个最大的。想法没啥问题,但是自己实现起来还是有点困难的,遇到了一些问题。最先想到了自己做过的一个题目--分割回文串。
//用回溯法分割回文串
void backtracking(string& s, int startIndex){
if(startIndex == s.size()){
result.push_back(path);
return;
}
for(int i = startIndex; i < s.size(); i++){
if(isPalindrome(s, startIndex, i)){
string str = s.substr(startIndex, i - startIndex + 1);
path.push_back(str);
}
else
continue;
backtracking(s, i + 1);
path.pop_back();
}
}
里面是用回溯法将字符串进行了分割,但是这个分割的结束条件是分割到字符串的最后,不用管分割了多少次。而此题是要求有固定分割次数的,那如何才能实现按照规定的次数进行分割呢?这里我陷入了沉思,想了大概几十分钟也没想出来,之后就去休息了。过了一段时间,又接着想,这次想到了解决办法。假设一共切割k次,前k-1次正常处理,即每次收集开始(startIndex)到结束(i)的整个数字序列。最后需要单独进行处理,加个判断语句,为k次时收集起始位置到最后的数字序列。
void backtracking(vector<int>& nums, int k, int delimeter, int index){
if(index == k){
result.push_back(path);
return;
}
for(int i = delimeter; i < nums.size(); i++){
if(index == k - 1)
i = nums.size() - 1;
vector<int> tmp(nums.begin() + delimeter, nums.begin() + i + 1);
path.push_back(tmp);
backtracking(nums, k, i + 1, index + 1);
path.pop_back();
}
}
就是这几行简单的代码,不过自己写的时候还是很吃力的。想了好几次,才想到解决办法。
动态规划法
动态规划的解法,我自己想半天没有想出来怎么做。就去翻看题解,发现很多题解看不是太懂,官方题解的数学语言让我不想去看,一些题解直接给出代码和大概思路,没怎么看懂。最后有一个图解算法的题解吸引我了,就过去看了。一开始看了半天也不是很理解,没接触过过,现在搞完了之后感觉好像很简单。
看了好几次没看懂,终于在一次休息完后认真看了几十分钟后,明白了。打通的点在于,举例子理解dp数组的含义。
dp[i][j]的意思是i个元素分j次取得的最大分组平均值之和,而难理解的是递推公式。
for(int j = 2; j <= k; j++){
for(int i = j; i <= nums.size(); i++)
for(int x = j - 1; x < i; x++){
// x -- x 个物品分 j - 1份 -- x >= j - 1
dp[i][j] = max(dp[i][j], dp[x][j - 1] +
(prefix[i] -prefix[x]) / (i - x));
}
思考之后,我的理解:首先新的一次划分是在原有的基础上进行划分的。考虑一直划分k-1次的情况,那么如何划分k次的情况呢? 如果我们知道k次的所有划分,取到一个最大的值是不是就可以了呢? 而dp数组已经记录了K-1次时候所有的划分情况,那么k次不就是在前面划分的情况下,再多一次划分么。
问题是怎么表示出最后一次划分的所有情况呢?
dp[x][j-1]表示x个物品划分了j-1份,那么x--i之间的物品不就自动分成一份了么,而且平均值也可以表示出来。
所以所有划分都可以表示出来,取最大的即可。
double largestSumOfAverages(vector<int>& nums, int k){
//前缀数组 方便计算 prefix[i]表示 0 -- i元素之和
int sum = 0;
vector<double> prefix(nums.size() + 1, 0.0);
for(int i = 0; i < nums.size(); i++){
sum += nums[i];
prefix[i + 1] = sum;
}
vector<vector<double>> dp(nums.size() + 1, vector<double>(k + 1, 0.0));
for(int i = 1; i <= nums.size(); i++){
dp[i][1] = prefix[i] / i;
}
//错误思路
// for(int i = 1; i <= nums.size(); i++){
// for(int j = 2; j <= k && j <= i; j++)
// for(int x = j - 1; x < i; x++){ // x -- x 个物品分 j - 1份 -- x >= j - 1
// dp[i][j] = max(dp[i][j], dp[x][j - 1] + (prefix[i] - prefix[x]) / (i - x));
// cout << "i=" << i << "," << "j=" << j << "," << "x=" <<x << " " << "dp[i][j]=" << dp[i][j] << endl;
// cout << "dp[x][j - 1]=" << dp[x][j - 1] << " prefix[i]=" << prefix[i] << endl;
// cout << " prefix[x]=" << prefix[x] <<" (prefix[i] - x)/(i - x)=" << (prefix[i] - prefix[x]) / (i - x)<< endl;
// cout << endl;
// }
// }
for(int j = 2; j <= k; j++){
for(int i = j; i <= nums.size(); i++)
for(int x = j - 1; x < i; x++){ // x -- x 个物品分 j - 1份 -- x >= j - 1
dp[i][j] = max(dp[i][j], dp[x][j - 1] + (prefix[i] - prefix[x]) / (i - x));
}
}
return dp[nums.size()][k];
}