代码随想录算法训练营第三十一天 | 455. 分发饼干、376. 摆动序列、53. 最大子数组和

51 阅读4分钟

455. 分发饼干

链接

题目链接

文章链接

第一想法

由于尽可能让每个孩子都能吃饱,所以每个人分配的饼干值应该是大于等于他的胃口值的那堆饼干值的最小值,所以我的想法是先排序,排序之后从小到大依次比较,符合则胃口下标和饼干下标都加一,不符合就饼干下标加一,代码如下:

function findContentChildren(g: number[], s: number[]): number {
    let k: number = 0
    let res: number = 0
    g.sort((a, b) => a - b)
    s.sort((a, b) => a - b)
    for (let i = 0; i < g.length; i++) {
        if (k >= s.length) return res
        if (g[i] > s[k]) {
            i--
        } else res++
        k++
    }
    return res
};

看完文章后的想法

文章的想法核心思想是一样的,但是表达是不一样的,我是从前往后,而文章是从后往前,思想是大饼干优先分给胃口大的孩子,代码如下:

function findContentChildren(g: number[], s: number[]): number {
    let k: number = s.length - 1
    let res: number = 0
    g.sort((a, b) => a - b)
    s.sort((a, b) => a - b)
    for (let i = g.length - 1; i >= 0; i--) {
        if (k < 0) return res
        if (s[k] >= g[i]) {
            res++
            k--
        }
    }
    return res
};

思考

这是一道入门级别的贪心算法的题,不算很难,主要帮助我们理解局部最优推出全局最优的思想.

376. 摆动序列

链接

文章链接

题目链接

第一想法

这道题是寻找摆动序列的最长子序列的长度,所以如何寻找呢?这个就是类似于寻找拐点,例如nums[i-1]是个拐点,而nums[i]>nums[i-1],则nums[i]不一定是拐点,如果nums[i+1]>=nums[i],则说明nums[i]不是拐点,否则nums[i]是拐点

function wiggleMaxLength(nums: number[]): number {
    let res: number = 1 //寻求结果
    for (let i = 1; i < nums.length; i++) {
        if (nums[i] == nums[i - 1]) continue //这个是提交的时候发现这个解法的缺点[3,3,3,2,5]这个要把前面[3,3,3]剔除 否则按照我的想法比正确答案加一 简化版为[0,0]输出的是1而不应该是2
        let index: number = i
        while (nums[i] >= nums[i - 1]) { //上坡
            i++
        }
        if (index != i) { //拐点 因为不知道当前是上坡还是下坡,所以需要用index!=i来判断是否上坡了
            res++
            i-- //因为上坡结束时已经给i++了,所以这里要i--,因为for循环有个i++
            continue
        }
        while (nums[i] <= nums[i - 1]) {//下坡
            i++
        }
        if (index != i) { //拐点 和上面是一样的
            res++
            i--
        }
    }
    return res
};

看完文章的想法

这道题确实比较复杂,因为存在三种情况:

  1. 有平坡

image.png 2. 两端有平坡

image.png

  1. 上/下坡有平坡 image.png 对应这三种情况,如何寻找拐点,文章用了当前两个数的差值currdiff和上一对两个数的差值prediff,如果当前的currdiff>0&&prediff<=0(对应上有平坡[情况3]、上到平坡[情况1]、开始有平坡[情况2])或者currdiff<0&&prediff>=0(对应下有平坡[情况3]、平坡到下[情况1]、结束有平坡[情况2]),代码如下:
function wiggleMaxLength(nums: number[]): number {
    let res: number = 1
    let prediff: number = 0
    let currdiff: number = 0
    for (let i = 1; i < nums.length; i++) {
        currdiff = nums[i] - nums[i - 1]
        if (currdiff > 0 && prediff <= 0 || currdiff < 0 && prediff >= 0) {//发现拐点时
            res++ //结果值加一
            prediff = currdiff //存储当前拐点的差值 这个不能放到if外 否则情况时就不对了 多算了一个拐点
        }
    }
    return res
};

思考

这道题按照自己的想法写了快40分钟,虽然写出来了但是运行的时候还是发现漏洞,最终加了个条件才最终能通过测试用了.之后看了文章的想法,文章的想法和我的一样,都是寻找拐点来解决这个问题,同时文章详细的列举了三个情况,以及当前差值和前一个差值的关系,之所以有=这个条件是因为为了适配开始,因为开始设置prediff是为0的

53. 最大子数组和

链接

题目链接

文章链接

第一想法

这个我第一眼想到的是前缀和加单调栈解决,但是写完后发现了一个缺陷,如果数组每个数都是负数,则获取不到最大值,所以又补了一个if (nums.every(item => item < 0)) return Math.max(...nums)用于解决每个数都是负数的形式,代码如下:

function maxSubArray(nums: number[]): number {
    if (nums.every(item => item < 0)) return Math.max(...nums)//应对每个数都是负数的情况
    let sum: number[] = [0]//求前缀和
    for (let i = 0; i < nums.length; i++) {
        sum[i + 1] = sum[i] + nums[i]
    }
    let max: number = Number.MIN_SAFE_INTEGER
    let stack: number[] = []//单调栈
    for (let i = 0; i < sum.length; i++) {
        while (i > 0 && sum[i] <= stack[stack.length - 1]) {
            stack.pop()
        }
        stack.push(sum[i])
        max = (max > stack[stack.length - 1] - stack[0]) ? max : stack[stack.length - 1] - stack[0]
    }
    return max
};

看完文章后的想法

看完之后,发现我在学习贪心算法的道路上的任务还是很多的,这道题的贪心的思路是,如果当前和是正的,则继续往后加,每加一个数就看一下是否更新最大值,如果当前和是负的,则清零,因为当前是负的话,会减小后面的和,例如这张图:

代码如下:

function maxSubArray(nums: number[]): number {
    let max: number = Number.MIN_SAFE_INTEGER
    let count: number = 0
    for (let i = 0; i < nums.length; i++) {
        count += nums[i]
        if (count > max) max = count
        if (count < 0) count = 0
    }
    return max
};

今日总结

今天耗时3小时,这是初次接触贪心算法,所以耗时比较多,每道题做的时间比较长,三道题很少能一下子想到到底是什么局部最优才能推出总体最优.贪心算法的题得多加练习.