前言
看就完了
场景1
某店铺菜单如下
- 醋溜木须 18元/份
- 白菜木耳豆腐 15元/份
- 醋溜土豆丝 12元/份
- 素什锦 10元/份 我们的预期消费是25元,假设只能点两个菜且不能重样,求符合条件的组合.
我们可以建立这样一个函数,通过这个函数来求解
/**
* 输入: nums = [10,12,15,18], target = 25
*/
const solucation = (nums, target) => {
}
方法1:暴力枚举
很容易想到的一个方法,通过排列组合来找出符合条件的组合
const solucation = (nums, target) => {
for(let i = 0; i < nums.length; i++){
for(let j = i + 1; j < nums.length; j++){
if(nums[i] + nums[j] === target){
return [i,j]
}
}
}
return false
}
进阶:能否想到一个时间复杂度小于 O(n2) 的算法吗,说人话就是能不能遍历一次给他算出来。
方法2:哈希表
思路是每当遍历到nums[i]时,判断哈希表中是否存在target-nums[i],如果存在则找到了两个值,如果不存在就把(nums[i],i)存入哈希表中。
const solucation = (nums, target) => {
let map = new Map()
for(let i = 0; i < nums.length; i++){
if(map.has(target - nums[i])){
return [map.get(target - nums[i]),i]
}
map.set(nums[i],i)
}
return false
}
场景2
实际上场景1的情况还是很少发生的,更多的情况是我们有一个心里预算,期望得到一个不超过且最接近预算的组合。所以题目改为:已知预算是25元,求最接近且不超过预算的组合
方法1:暴力枚举
同场景一,不再赘述
方法2:双指针算法
首先我们需要找到所有小于25元的组合,但是哈希表只使用于等于的场景,这时候就需要用到双指针算法了,具体操作为
- 先将菜单价格由便宜到贵排个序
arr=[10,12,15,18] - 建立左右指针
leftIndex,rightIndex - 计算
arr[leftIndex] + arr[rightIndex],如果大于预算就右指针左移,如果小于等于预算就左指针右移并记录结果,直到leftIndex===rightIndex(指针碰撞) 最后再找出最接近预算的组合,代码如下
const solucation = (nums, target) => {
let arr = []
let leftIndex = 0
let rightIndex = nums.length - 1
while (leftIndex < rightIndex) {
if (nums[leftIndex] + nums[rightIndex] > target) {
rightIndex-- // 指针左移
} else {
if (arr.length) {
arr = nums[leftIndex] + nums[rightIndex] > arr[0] + arr[1] ? [leftIndex,rightIndex] : arr
} else{
arr = [leftIndex,rightIndex]
}
leftIndex++ // 指针右移
}
}
return arr
}
场景3
上面两个场景都是假设不能点重样的菜,如果可以点重样的该怎么计算呢。比如,公司加班需要集体订餐,预期消费是50元,求可能的组合。
方法:回溯算法
先看下概念
回溯算法是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
拿这个例子说就是先用50分别减去10,12,15,18,得到四个新的数,再用每个数再分别减去10,12,15,18,如此循环下去,直到满足某个条件为止,本例是出现0为止,如图所示
这里以10,10,15,15这个组合为例,其他省略,画不开了
用代码实现为
const solucation = (nums,target) => {
const ans = []
const dfs = (target, combine) => {
if (target === 0) {
ans.push(combine)
return
}
for(let i = 0; i < nums.length; i++ ){
if (target - nums[i] >= 0) {
dfs(target - nums[i], [...combine, nums[i]])
}
}
}
dfs(target, [])
return ans
}
执行结果
从结果中发现里面有一些重复的组合,所以我们还需要优化一下,需要按钮某种顺序做减法,具体做法是每一次做减法时设置下一轮搜索的起点。即:从每一层的第2个结点开始,都不能再搜索产生同一层结点已经使用过的nums里的元素,如图所示
所以优化之后的代码是
const solucation = (nums,target) => {
const ans = []
const dfs = (target, combine, idx) => {
if (idx === nums.length) {
return;
}
if (target === 0) {
ans.push(combine)
return;
}
dfs(target, combine, idx + 1)
if (target - nums[idx] >= 0) {
dfs(target - nums[idx], [...combine, nums[idx]], idx)
}
}
dfs(target, [], 0)
return ans
}
输出结果