【算法】数组分段和最大值最小问题

时间:2021-09-08 11:26:19

描述

题目:给定一个数组,和一个值k,数组分成k段。要求这k段子段和最大值最小。求出这个值。

解析

(1)暴力搜索

                 n                             n-1
M[n, k] = min { max { M[j, k-1], ∑ Ai } }
                j=1                            i=j

n表示数组长度,k表示数组分成几段。初始化条件:

M[1, k] = A0
          n-1
M[n, 1] = ∑ Ai
          i=0

(2)动态规划

递归算法拥有指数时间的复杂度,并且会重复计算一些M值。这类的算法一般可以使用动态规划进行优化。使用数组保存一些已经计算得到的值,采用自底向上进行计算

(3)二分查找

  此题可以想象成把数据按顺序装入桶中,m即是给定的桶数,问桶的容量至少应该为多少才能恰好把这些数装入k个桶中(按顺序装的)。

  首先我们可以知道,桶的容量最少不会小于数组中的最大值,即桶容量的最小值(小于的话,这个数没法装进任何桶中),假设只需要一个桶,那么其容量应该是数组所有元素的和,即桶容量的最大值;其次,桶数量越多,需要的桶的容量就可以越少,即随着桶容量的增加,需要的桶的数量非递增的(二分查找就是利用这点);我们要求的就是在给定的桶数量m的时候,找最小的桶容量就可以把所有的数依次装入k个桶中。在二分查找的过程中,对于当前的桶容量,我们可以计算出需要的最少桶数requiredPainters,如果需要的桶数量大于给定的桶数量k,说明桶容量太小了,只需在后面找对应的最小容量使需要的桶数恰好等于k;如果计算需要的桶数量小于等于k,说明桶容量可能大了(也可能正好是要找的最小桶容量),不管怎样,该桶容量之后的桶容量肯定不用考虑了(肯定大于最小桶容量),这样再次缩小查找的范围,继续循环直到终止,终止时,当前的桶容量既是最小的桶容量。

  对于数组 1 2 3 4 5 6 7,假设k=3,最小桶容量为7(要5个桶),最大桶容量为28(一个桶)

【算法】数组分段和最大值最小问题

第一行表示桶容量,第二行表示需要的桶数,即要求桶数量恰为k的最小桶容量

代码

#include <iostream>
#include <vector>
#include <algorithm>
#include <limits.h>
#include <numeric>
#include "Solution.h"

using namespace std;
//===================方案1==========================
//递归的暴力搜素算法 , 指数时间的复杂度
int partition(vector<int> nums, int len, int k) {
    if (k == 1)
        return accumulate(nums.begin(), nums.begin()+len, 0);
    if (len == 1)
        return nums[0];

    int best = INT_MAX;
    for (int j = 1; j <= len; j++)
        best = min(best, max(partition(nums, j, k-1), accumulate(nums.begin()+j, nums.begin()+len, 0)));

    return best;
}

//==================方案二======================
//改进的动态规划算法
//时间复杂度:O(kN2)  空间复杂度:O(kN)
int DP_findMax(vector<int> nums, int k){
    vector<vector<int>> dp(nums.size()+1,vector<int>(k,0));
    vector<int> sums(nums.size()+1,0);
    for(int i = 1;i<=nums.size();++i){
        sums[i] = sums[i-1] + nums[i];
    }
    for (int i = 1; i <= nums.size(); i++)
        dp[i][1] = sums[i];
    for (int i = 1; i <= k; i++)
        dp[1][i] = nums[0];
    for (int i = 2; i <= k; i++) {
        for (int j = 2; j <= nums.size(); j++) {
            int best = INT_MAX;
            for (int p = 1; p <= j; p++) {
                best = min(best,max(dp[p][i-1],sums[j] - sums[p]));
            }
            dp[j][i] = best;
        }
    }
    return dp[nums.size()][k];
}


int getRequiredPainters(vector<int> nums, int mid) {
    int total = 0, numPainters = 1;
    for (int i = 0; i < nums.size(); i++) {
        total += nums[i];
        if (total > mid) {
            total = nums[i];
            numPainters++;
        }
    }
    return numPainters;
}


//========================方案三========================
// 二分查找算法
//时间复杂度:O(N log ( ∑ Ai )).   空间复杂度:0(1)
int BinarySearch(vector<int> nums, int k) {
    int max_val = INT_MIN;
    for (int i = 0; i < nums.size(); i++) {
        max_val = max(max_val, nums[i]);
    }
    int lo = max_val; //数组中最大元素
    int hi = accumulate(nums.begin(), nums.end(), 0); //数组求和

    while (lo < hi) {  //二分查找
        int mid = (lo + hi)/2;
        int requiredPainters = getRequiredPainters(nums, mid);
        if (requiredPainters <= k)
            hi = mid;
        else
            lo = mid+1;
    }
    return lo;
}


int main() {

    int a[] = {5,1,4,2,3};
    vector<int> va(a, a+5);
    int k = 3;
    cout<<partition(va,va.size(),k)<<endl;
    cout<<DP_findMax(va,k)<<endl;
    cout<<BinarySearch(va,k)<<endl;
    return 0;
}