力扣刷题--813.最大平均值和的分组

113 阅读3分钟

题目链接

813. 最大平均值和的分组

心路历程

时间

(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次取得的最大分组平均值之和,而难理解的是递推公式。

image.png

            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];
    }