【题型总结】动态规划之按照某种形式分割数组以获得最值

简介: 【题型总结】动态规划之按照某种形式分割数组以获得最值

按照某种形式分割数组以获得最值

希望看了本文对你有所帮助,参考其他题解写成,取其精华,做以笔记,如有描述不清楚或者错误麻烦指正,不胜感激,不喜勿喷!

最大平均值的和分组【LC813】

You are given an integer array nums and an integer k. You can partition the array into at most k non-empty adjacent subarrays. The score of a partition is the sum of the averages of each subarray.


Note that the partition must use every integer in nums, and that the score is not necessarily an integer.


Return the maximum score you can achieve of all the possible partitions. Answers within 10-6 of the actual answer will be accepted.

给定数组 nums 和一个整数 k 。我们将给定的数组 nums 分成 最多 k 个相邻的非空子数组 。 分数 由每个子数组内的平均值的总和构成。

注意我们必须使用 nums 数组中的每一个数进行分组,并且分数不一定需要是整数。

返回我们所能得到的最大 分数 是多少。答案误差在 10-6 内被视为是正确的。

  • 思路:将 n 个元素划分为k个连续子数组,最大化子数组的平均值之和。
  1. 确定dp数组(dp table)以及下标的含义
    dp[i][j]:将长度为[0,i]的nums分割为j个连续子数组时,得到的最大分数

2.确定递推公式

image.png

image.png

class Solution {
    public double largestSumOfAverages(int[] nums, int k) {
        int len = nums.length;
        double[][] dp = new double[len + 1][k + 1];
        double[] preSum = new double[len + 1];
        for (int i = 1; i <= len; i++){
            preSum[i] = preSum[i-1] + nums[i-1];
        }
        for (int i = 1; i <= len; i++){
            dp[i][1] = preSum[i] / i;
        }
        for (int i = 1; i <= len; i++){
            for (int j = 2; j <= k && j <= i; j++){
                // 自成一组 与start = i 合并
                // dp[i][j] = Math.max(dp[i][j],dp[i-1][j-1] + nums[i-1]);
                // 并入前一组nums[start,i]
                for (int start = 2; start <= i; start++){
                    double ave = (preSum[i] - preSum[start - 1]) / (i - start + 1);
                    dp[i][j] = Math.max(dp[i][j],dp[start - 1][j - 1] + ave);
                }
            }
        }
        return dp[len][k];
    }
}

image.png

class Solution {
    public double largestSumOfAverages(int[] nums, int k) {
        int len = nums.length;
        double[][] dp = new double[len][k + 1];
        double[] preSum = new double[len + 1];
        for (int i = 0; i < len; i++){
            preSum[i+1] = preSum[i] + nums[i];
        }
        for (int i = 0; i < len; i++){
            dp[i][1] = preSum[i + 1] / (i+1);
        }
        for (int i = 0; i < len; i++){
            for (int j = 2; j <= k && j <= i + 1; j++){
                // 自成一组 与start = i 合并
                // dp[i][j] = Math.max(dp[i][j],dp[i-1][j-1] + nums[i-1]);
                // 并入前一组nums[start,i]
                for (int start = 1; start <= i; start++){
                    double ave = (preSum[i + 1] - preSum[start]) / (i - start + 1);
                    dp[i][j] = Math.max(dp[i][j],dp[start - 1][j - 1] + ave);
                }
            }
        }
        return dp[len - 1][k];
    }
}

分隔数组以得到最大和【LC1043】

给你一个整数数组 arr,请你将该数组分隔为长度 最多 为 k 的一些(连续)子数组。分隔完成后,每个子数组的中的所有值都会变为该子数组中的最大值。

返回将数组分隔变换后能够得到的元素最大和。本题所用到的测试用例会确保答案是一个 32 位整数。

  • 思路

image.png

实现

class Solution {
    public int maxSumAfterPartitioning(int[] arr, int k) {
        int n = arr.length;
        int[] dp = new int[n + 1];       
        for (int i = 0; i < n; i++){
            int mx = arr[i];
            for (int j = i; j >= Math.max(0, i - k + 1); j--){
                mx = Math.max(mx, arr[j]);
                dp[i + 1] = Math.max(dp[i + 1], dp[j] + mx * (i - j + 1));
            }
        }
        return dp[n];
    }
}

image.png

填充书架【LC1105】

给定一个数组 books ,其中 books[i] = [thicknessi, heighti] 表示第 i 本书的厚度和高度。你也会得到一个整数 shelfWidth

按顺序 将这些书摆放到总宽度为 shelfWidth 的书架上。

先选几本书放在书架上(它们的厚度之和小于等于书架的宽度 shelfWidth ),然后再建一层书架。重复这个过程,直到把所有的书都放在书架上。

需要注意的是,在上述过程的每个步骤中,摆放书的顺序与你整理好的顺序相同

  • 例如,如果这里有 5 本书,那么可能的一种摆放情况是:第一和第二本书放在第一层书架上,第三本书放在第二层书架上,第四和第五本书放在最后一层书架上。

每一层所摆放的书的最大高度就是这一层书架的层高,书架整体的高度为各层高之和。

以这种方式布置书架,返回书架整体可能的最小高度。

dfs+记忆化:选或不选
  • 思路:
    感觉递推的形式不是很好写,所以一开始的想法是dfs+记忆化。每遇到一本书,我们可以选择新建一层放置这本书,也可以选择在上一次继续放置这本书,影响结果的有两个因素:

该层已经摆放的书的宽度以及最大高度。因此在dfs过程需要记录这两个值,定义

image.png

class Solution {
    int[][] dp;
    int[][] books;
    int shelfWidth;
    public int minHeightShelves(int[][] books, int shelfWidth) {
        int n = books.length;
        dp = new int[n + 1][shelfWidth + 1];
        this.books = books;
        this.shelfWidth = shelfWidth;
        return dfs(0, 0, 0);
    }
    public int dfs(int i, int width, int maxHeight){
        if (i == books.length) return maxHeight;
        if (dp[i][width] != 0) return dp[i][width];
        // 新建一层
        int res = maxHeight + dfs(i + 1, books[i][0], books[i][1]);
        // 如果宽度满足要求 那么可以继续往后面放
        if (width + books[i][0] <= shelfWidth){
             res = Math.min(res, dfs(i + 1, width +books[i][0], Math.max(maxHeight, books[i][1])));
        }
        dp[i][width] = res;
        return res;
    }
}

image.png

class Solution {
    int[] dp;
    int[][] books;
    int shelfWidth;
    public int minHeightShelves(int[][] books, int shelfWidth) {
        int n = books.length;
        dp = new int[n + 1];
        this.books = books;
        this.shelfWidth = shelfWidth;
        return dfs(0);
    }
    public int dfs(int i){
        if (i == books.length) return 0;
        if (dp[i] != 0) return dp[i];
        int width = 0, maxHeight = 0, res = Integer.MAX_VALUE;
        for (int j = i; j < books.length; j++){
            width += books[j][0];
            if (width > shelfWidth) break;// 无法放书
            maxHeight = Math.max(maxHeight, books[j][1]);
            res = Math.min(res, dfs(j + 1) + maxHeight);
        }     
        dp[i]= res;
        return res;
    }
}

image.png

class Solution {
    public int minHeightShelves(int[][] books, int shelfWidth) {
        int n = books.length;
        int[] dp = new int[n + 1];
        for (int i = 0; i < n; i++){
            int w = books[i][0], h = books[i][1];
            dp[i + 1] = dp[i] + h;// 独立一层
            for (int j = i - 1; j >= 0; j--){// 枚举可以选哪个
                w += books[j][0];
                if (w > shelfWidth) break;
                h = Math.max(h, books[j][1]);
                dp[i + 1] = Math.min(dp[i + 1], dp[j] + h);
            }
        }
        return dp[n];
    }
}

image.png

工作计划的最低难度【LC1335】

你需要制定一份 d 天的工作计划表。工作之间存在依赖,要想执行第 i 项工作,你必须完成全部 j 项工作( 0 <= j < i)。

你每天 至少 需要完成一项任务。工作计划的总难度是这 d 天每一天的难度之和,而一天的工作难度是当天应该完成工作的最大难度。

给你一个整数数组 jobDifficulty 和一个整数 d,分别代表工作难度和需要计划的天数。第 i 项工作的难度是 jobDifficulty[i]

返回整个工作计划的 最小难度 。如果无法制定工作计划,则返回 -1


dfs+记忆化
  • 思路
    题意可以转化为分割成d个子数组,使d个子数组的最大值之和最小。

image.png

class Solution {
    int[] jobDifficulty;
    int[][] memo;
    int res = Integer.MAX_VALUE;
    public int minDifficulty(int[] jobDifficulty, int d) {
        this.jobDifficulty = jobDifficulty;
        // 分割成d个子数组,使d个子数组的最大值之和最小
        // dfs(i,j) j天完成[0,i]项工作所需要的最小难度
        int n = jobDifficulty.length;
        if (n < d){
            return -1;
        }
        memo = new int[d + 1][n];
        for (int i = 0; i <= d; i++){
            Arrays.fill(memo[i], -1);
        }
        return dfs(d, n - 1);
    }
    public int dfs(int i, int j){
        // if (i < 0 || j <= 0) return 0;
        if (memo[i][j] != -1) return memo[i][j];
        // 只有一天
        if (i == 1){
            int mx = 0;
            for (int k = 0; k <= j; k++){
                mx = Math.max(mx, jobDifficulty[k]);
            }
            memo[i][j] = mx;
            return mx;
        }
        int res = Integer.MAX_VALUE;
        int mx = -1;
        // 枚举子数组范围 [i - 1, j] 留下i - 1个元素
        for (int k = j; k >= i - 1; k--){
            mx = Math.max(mx, jobDifficulty[k]);
            res = Math.min(res, mx + dfs(i - 1,k - 1));
        }
        memo[i][j] = res;
        return res;
    }
}

image.png

class Solution {
    public int minDifficulty(int[] a, int d) {
        int n = a.length;
        if (n < d) return -1;
        var f = new int[d][n];
        f[0][0] = a[0];
        for (int i = 1; i < n; i++)
            f[0][i] = Math.max(f[0][i - 1], a[i]);
        for (int i = 1; i < d; i++) {
            for (int j = n - 1; j >= i; j--) {
                f[i][j] = Integer.MAX_VALUE;
                int mx = 0;
                for (int k = j; k >= i; k--) {
                    mx = Math.max(mx, a[k]); // 从 a[k] 到 a[j] 的最大值
                    f[i][j] = Math.min(f[i][j], f[i - 1][k - 1] + mx);
                }
            }
        }
        return f[d - 1][n - 1];
    }
}
作者:灵茶山艾府
链接:https://leetcode.cn/problems/minimum-difficulty-of-a-job-schedule/solutions/2271631/jiao-ni-yi-bu-bu-si-kao-dong-tai-gui-hua-68nx/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

image.png


目录
相关文章
|
6月前
|
算法 测试技术 C++
【动态规划】【前缀和】【数学】2338. 统计理想数组的数目
【动态规划】【前缀和】【数学】2338. 统计理想数组的数目
|
6月前
【每日一题Day146】给定行和列的和求可行矩阵 | 贪心
【每日一题Day146】给定行和列的和求可行矩阵 | 贪心
48 0
|
5月前
【题解】NowCoder [编程题] 数组中两个字符串的最小距离
【题解】NowCoder [编程题] 数组中两个字符串的最小距离
54 7
|
6月前
|
机器学习/深度学习 算法 测试技术
【组合数学 容斥原理 逆向思考】2930. 重新排列后包含指定子字符串的字符串数目
【组合数学 容斥原理 逆向思考】2930. 重新排列后包含指定子字符串的字符串数目
|
5月前
|
算法
【经典LeetCode算法题目专栏分类】【第6期】二分查找系列:x的平方根、有效完全平方数、搜索二位矩阵、寻找旋转排序数组最小值
【经典LeetCode算法题目专栏分类】【第6期】二分查找系列:x的平方根、有效完全平方数、搜索二位矩阵、寻找旋转排序数组最小值
|
6月前
【错题集-编程题】删除相邻数字的最大分数(动态规划 - 线性 dp)
【错题集-编程题】删除相邻数字的最大分数(动态规划 - 线性 dp)
|
6月前
|
算法 测试技术 C++
【组合数学】【动态规划】【前缀和】1735生成乘积数组的方案数
【组合数学】【动态规划】【前缀和】1735生成乘积数组的方案数
|
6月前
代码随想录Day30 贪心05 LeetCode T435无重叠区间 T763划分字母区间 T56 合并区间
代码随想录Day30 贪心05 LeetCode T435无重叠区间 T763划分字母区间 T56 合并区间
51 0
|
6月前
|
存储 算法 Java
【算法训练-数组 二】【元素组合】两数之和、三数之和
【算法训练-数组 二】【元素组合】两数之和、三数之和
52 0
|
6月前
|
存储 算法 程序员
【算法训练-数组 一】【数组子集】:最长无重复子数组
【算法训练-数组 一】【数组子集】:最长无重复子数组
42 0