小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
最近学习动态规划问题,其中有这样的问题,在一个数组中找出一个或几个数,数可以重复选择,将这些选出的数进行求和得到一个我们想要的数,这样问题我们可以将其分解为由简到难,首先看数组中是否存在这样的组合,其实这样组合可能有多个,如果有这样一组数字求和为目标数据,我们在进一步将这样一组数字找出,最后我们要做的是从中选择一个用最少的数字组合成目标数字。
是否存在一组数字求和为目标数字
const canSum = (targetNumber,numbers) => {
if(targetNumber === 0 ) return true;
if(targetNumber < 0) return false;
for(let num of numbers){
const reminder = targetNumber - num
if(canSum(reminder,numbers) === true){
return true
}
}
return false
}
- 首先我们设计函数,这时我们不需要考虑过多,只考虑函数输入也就是参数和函数的返回值。
canSum
函数接受目标值targetNumber
和一个可以在其中找出一组数字这样的数组number
返回值是表示是否存在这样一组数的可能性的布尔值 - 然后我们需要考虑子问题,如果目标数减去数组中某一个数,如果剩下的数值可以由这个数组中一个或者多个可重复数组组合,那么就说明存在一组数字的求和可以表示目标数字。
- 设置边界条件,也就是
if(targetNumber === 0 ) return true;
如果目标函数是 0 说明就是找到了,如果为负数说明没有找到 - 用这个语句将问题进行转换
const reminder = targetNumber - num
通过缓存来降低时间复杂度
这里使用 memoization 方式来优化,这样避免重复计算。在没有优化之前,我们来看 m = 目标数字大小、n 表示数组长度,如果数组都是 1 那么构建树高度就是 m 而且每次都会尝试分支为 n 所以就是 m 个 n 相乘得到时间复杂度为 空间复杂度为 ,优化后时间复杂度变为
const canSumWithMemo = (targetNumber,numbers,memo={}) => {
if(targetNumber in memo) return memo[targetNumber];
if(targetNumber === 0 ) return true;
if(targetNumber < 0) return false;
for(let num of numbers){
const reminder = targetNumber - num
if(canSum(reminder,numbers) === true){
memo[targetNumber] = true;
return true
}
}
memo[targetNumber] = false;
return false
}
const res = canSumWithMemo(300,[7,14]);
console.log(res)