面向前端的算法——贪心算法

123 阅读2分钟

面向前端的算法——贪心算法

贪心算法是一种求局部最优解的算法。因此,应用方面,贪心算法只是追求某个范围内的最优解。

贪心算法思想

例如有若干1元,5元,10元的零钱。将手中的N兑换成以上1,5,10的零钱,怎么样的兑换次数最少?

这里假设N是24

很显然,210 + 41 = 24 这样兑换的次数最少。

从上面可以看出,上面的问题就是先用局部最优的10元零钱去兑换也就是N对10做除法然后余数对5做除法(此时的局部最优就是5),在然后余数对1做除法(此时的局部最优是1)

从上面就可以看出贪心算法的一般步骤:

  1. 建立数学模型描述问题
  2. 把求解的问题分成若干个子问题
  3. 对每个子问题进行求解,得到子问题的局部最优解
  4. 把子问题的局部最优解合并成原来解问题的一个解就是其结果

实战体验

1. 盛最多水的容器

本题是LeetCode的11题

第一种解法: 看到这个题,我首先想到就是枚举法,将他们的结果全部枚举比较选出最大的,实现如下:

function solution(nums: number[]){
    let value = 0
    const _nums:number[] = [...nums];
    let shiftLg = 0
    nums.forEach( (item,index) =>{
        _nums.shift()
        shiftLg ++;
        _nums.forEach( (its,idx)=>{
            const height =item>its? its:item;
            const width = (idx + shiftLg)- index;
            const result = height * width
            value = result > value ? result :value
        })
    })
    return value
}

其算法缺陷就是时间复杂度为N*N,因此测试用例的时候会出现超出时间限制

第二种解法: 贪心思维下的双指针法

思路概述:

  1. 第一步给两个指针一个指向数组第一个成员,一个指向数组的最后一个成员
  2. 其容器的高就是两个指针指的元素值最小的那个
  3. 其容器的宽就是两个指针的位置之差的绝对值
  4. 然后这两个指针谁指的值小谁就向前/向后移动一位
  5. 循环2,3,4当两个指针指向同一个元素时结束循环
function solution(nums: number[]){
    if(nums.length === 0 || nums.length === 1) {
        return 0
    }
    let value = 0;
    let start = 0;
    let end = nums.length -1 
    while (start!==end){
        const height = Math.min(nums[start],nums[end]);
        const width = end - start;
        const res = height * width;
        value = Math.max(res,value)
        // 贪心思维体现就在于指针的移动,总是最小的去移动,以期望遇到大的
        if(nums[start]<nums[end]){
            start ++
        }else{
            end --
        }
    }
    return value
}

2. 分发糖果

n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。

你需要按照以下要求,给这些孩子分发糖果:

每个孩子至少分配到 1 个糖果。 相邻两个孩子评分更高的孩子会获得更多的糖果。 请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。

来源:力扣(LeetCode) 链接:

const candy = function(ratings) {
    let total = 0
    const record = ratings.map((item,index)=>{
        return {
            index:index,
            score: item,
            candy:1,
        }
    }) 
    let _pre = record[0]
    record.forEach( item =>{
        if(item.index !==0){
            // 前一个小孩的得分小于当前小孩的得分
            if(_pre.score < item.score){
                item.candy = _pre.candy +1
            }else if(_pre.score > item.score){
            // 前一个小孩得分大于当前小孩,
                // 比较,1 前一小孩的糖果大于当前小孩的糖果,不做变动
                // 2. 前一小孩的糖果小于等于当前小孩的糖果,做出变动
                if(_pre.candy <= item.candy){
                    // 同时向前搜索
                    let _point = item.index
                    while(_point!==0 && record[_point -1].score > record[_point].score){
                        if(record[_point-1].candy <=record[_point].candy){
                            record[_point-1].candy = record[_point-1].candy +1
                        }
                        _point --
                    }
                }
            }
        }
        _pre = item
    })
    record.forEach( item =>{
        total +=item.candy
    })
    return total
};