看完这篇你就知道该怎么点外卖了

164 阅读2分钟

前言

看就完了

场景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为止,如图所示

捕获.PNG

这里以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
}

执行结果

捕获.PNG
从结果中发现里面有一些重复的组合,所以我们还需要优化一下,需要按钮某种顺序做减法,具体做法是每一次做减法时设置下一轮搜索的起点。即:从每一层的第2个结点开始,都不能再搜索产生同一层结点已经使用过的nums里的元素,如图所示

捕获.PNG

所以优化之后的代码是

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
}

输出结果

捕获.PNG