最近写了一道题,来分享下。
题目
小明衡量自己的工作情况,自己能完成60个单位的工时,现有一组任务,已经按需要花费的工时降序排序,请从中选出n个任务,使得小明工作达到最饱和,输出小明今天需要工作几个工时
如:{20,19,15,13,10,9}
输出:58
说明:20 + 15 + 13 + 10 = 58 最接近60
思路
一开始想到的是贪心算法,因为给定的数组本身就是一个降序排序的正整数数组,只需要从头开始遍历并记录当前的和,如果当前和 + 当前工时 < 目标工时则将当前工时加入,但是这种方法我们最后得到的是20+19+15=54 只能获取到尽可能大的单个任务,无法达到总工时尽可能大。
后来试图穷举,但是如果穷举数组长度为n就需要n个循环来得到每一个解,难以实现。
最后,采用了动态规划来解这道题。
解说
我们可以将这道题划分为两部分——数组中第一个数据一定被选中,数组中第一个数据一定不被选中,在这两部分中取最大值,这两部分又分别可以划分为第二个数据一定被选中和第二个数据一定不被选中两部分取最大值。
公式
即将原问题定义为fn(20,19,15,13,10,9)可进行如下拆分
fn(20,19,15,13,10,9)=max(fn(19,15,13,10,9) + 20,fn(19,15,13,10,9))
图解
后面划分与之前相同,不再做细分。
代码
直接上代码
/**
* @author 大地崩坏苍蝇兽
* @date 2022/11/8 - 19:51
*/
public class Solution {
public static void main(String[] args) {
int[] array = { 20 , 19, 15, 13, 10, 9};
System.out.println(getSumClosestToTarget(array, 0, TARGET));
}
final static int TARGET = 60;
public static int getMaxNotMoreThanTarget(int a, int b, int target) {
//如果两个数都大于目标值,返回0
if (target < a && target < b) {
return 0;
}
//如果一个数大于目标值,一个数小于目标值,返回小于目标值的的数
if (target < a) {
return b;
}
if (target < b) {
return a;
}
//两个数都小于目标值返回较大值
return a > b ? a : b;
}
/**
* 返回数组从start下标开始到数组末尾之和
*
* @param array 数组
* @param start 起始下标
* @return 数组start下标到末尾之和
*/
public static int getSum(int[] array, int start) {
int sum = 0;
for (int i = start; i < array.length; i++) {
sum += array[i];
}
return sum;
}
/**
* @param array 数组
* @param start 起始下标
* @param target 目标值
* @return
*/
public static int getSumClosestToTarget(int[] array, int start, int target) {
//目标值小于0,不可能有这种情况,因为工时都是正整数
if (target < 0) {
return 0;
}
//如果当前数组全部之和小于目标值直接返回
int sum = getSum(array, start);
if (sum <= target) {
return sum;
}
//取两部分较大值
return getMaxNotMoreThanTarget(getSumClosestToTarget(array, start + 1, target - array[start]) + array[start]
, getSumClosestToTarget(array, start + 1, target)
, target);
}
}
看看运行结果
得出答案,欸嘿