代码随想录算法训练营第三十七天| 860.柠檬水找零 、406.根据身高重建队列、452. 用最少数量的箭引爆气球

860.柠檬水找零

题目链接:860.柠檬水找零

文档讲解:代码随想录/柠檬水找零

视频讲解:视频讲解-柠檬水找零

状态:已完成(1遍)

解题过程 

看到题目的第一想法

这道题我竟然想不到一丝局部最优全局最优的思路。我只知道按照字面意思的机械的对五块钱和十块钱的数量进行计算。收到5的时候,5库存++;收到10的时候,10库存++,5库存--;收到20的时候,要么10和5库存--,要么5库存-=3。

手搓代码如下:

/**
 * @param {number[]} bills
 * @return {boolean}
 */
var lemonadeChange = function (bills) {
    let isCashFive = 0;
    let isCashTen = 0;
    for (let i = 0; i < bills.length; i++) {
        if (bills[i] == 5) {
            isCashFive++;
        } else if (bills[i] == 10) {
            isCashFive--;
            isCashTen++;
        } else {
            if (isCashTen) {
                isCashTen--;
                isCashFive--;
            }else{
                isCashFive-=3;
            }
        }
        if (isCashFive < 0 || isCashTen < 0) return false;
    }
    return true;
};

提交没有问题。让我来看看代码随想录怎么想。

看完代码随想录之后的想法 

我来拷贝一段:

只需要维护三种金额的数量,5,10和20。

有如下三种情况:

  • 情况一:账单是5,直接收下。
  • 情况二:账单是10,消耗一个5,增加一个10
  • 情况三:账单是20,优先消耗一个10和一个5,如果不够,再消耗三个5

此时大家就发现 情况一,情况二,都是固定策略,都不用我们来做分析了,而唯一不确定的其实在情况三。

而情况三逻辑也不复杂甚至感觉纯模拟就可以了,其实情况三这里是有贪心的。

账单是20的情况,为什么要优先消耗一个10和一个5呢?

因为美元10只能给账单20找零,而美元5可以给账单10和账单20找零,美元5更万能!

所以局部最优:遇到账单20,优先消耗美元10,完成本次找零。全局最优:完成全部账单的找零。

局部最优可以推出全局最优,并找不出反例,那么就试试贪心算法!

怪不得说贪心其实很难发现自己用的是贪心,因为大家从小到大生活得来的经验其实就是贪心的本质。

讲解代码如下:

var lemonadeChange = function(bills) {
    let fiveCount = 0
    let tenCount = 0

    for(let i = 0; i < bills.length; i++) {
        let bill = bills[i]
        if(bill === 5) {
            fiveCount += 1
        } else if (bill === 10) {
            if(fiveCount > 0) {
                fiveCount -=1
                tenCount += 1
            } else {
                return false
            }
        } else {
            if(tenCount > 0 && fiveCount > 0) {
                tenCount -= 1
                fiveCount -= 1 
            } else if(fiveCount >= 3) {
                fiveCount -= 3
            } else {
                return false
            }
        } 
    }
    return true
};

总结

这道题一共只有三种情况,全部列出来就很明了了。值得注意的是这里的贪心藏得很深。


 406.根据身高重建队列

题目链接:406.根据身高重建队列

文档讲解:代码随想录/根据身高重建队列

视频讲解:视频讲解-根据身高重建队列

状态:已完成(1遍)

解题过程  

看到题目的第一想法

这题我的想法是将people数组按照身高从矮到高排列,其中身高一样高的按照Ki从小到大排序。

然后对新数组进行遍历,每拿到一个元素,看他的Ki,是多少就放入当前ans中第几个空数组所在的位置。

手搓代码如下:

/**
 * @param {number[][]} people
 * @return {number[][]}
 */
var reconstructQueue = function (people) {
    let len = people.length;
    let ans = Array(len).fill([]);
    let newPeople = people.sort(function (a, b) {
        if (a[0] !== b[0]) {
            return a[0] - b[0]; // 按照第一个元素排序
        } else {
            return a[1] - b[1]; // 第一个元素相等时,按照第二个元素排序
        }
    });
    for(let i = 0;i<len;i++){
        let index = newPeople[i][1];
        //这是排好序的数组的Ki,每个人的Ki代表着当前它应该排在第Ki个ans数组里的空数组所在的位置
        let j = 0;
        //每个元素按照Ki在现在ans里的空数组个数填入,j主要是记录这个位置在ans里的索引
        while(index && j<len+1){
            if(ans[j].length == 0){
                index--;
                //已经经过了一个空数组,让index减1
            }else if(ans[j][0] == newPeople[i][0]){
                //如果遍历到和当前元素的Hi一样的,得算进去,因为身高一样,排序Ki更大的就得在小的后面
                index--;
            }
            j++;
            //j负责记录当前索引
        }
        while(ans[j].length != 0){
            //记录完了之后会出现第j个位置已经有元素了,所以让j往后顺延,直到出现空数组
            if(ans[j][0]<newPeople[i][0]){
                j++;
            }
        }
        ans.splice(j,1,newPeople[i]);
    }
    return ans;
};

不容易,前后debug了很久。分别是忽视了如果遍历到和当前元素一样高的元素,也得进行相对应的index--;全部遍历完了之后当前ans中 j 所在的位置已经有元素了,那就得往后顺延。

最终是提交成功了。

 看完代码随想录之后的想法 

神奇,代码随想录的思路和我完全相反。他是按照身高从大到小排序,相同身高的时候按照Ki从小到大排序。然后拿到一个人看他的Ki,再不断地往ans中插入。

讲解代码如下:

/**
 * @param {number[][]} people
 * @return {number[][]}
 */
var reconstructQueue = function(people) {
    let queue = []
    people.sort((a, b ) => {
        if(b[0] !== a[0]) {
            return b[0] - a[0]
        } else {
            return a[1] - b[1]
        }
        
    })

    for(let i = 0; i < people.length; i++) {
        queue.splice(people[i][1], 0, people[i])
    }
    return queue
};

总结

按照身高排序之后,优先按身高高的people的k来插入,后序插入节点也不会影响前面已经插入的节点,最终按照k的规则完成了队列。

所以在按照身高从大到小排序后:

局部最优:优先按身高高的people的k来插入。插入操作过后的people满足队列属性

全局最优:最后都做完插入操作,整个队列满足题目队列属性


452. 用最少数量的箭引爆气球 

题目链接:452. 用最少数量的箭引爆气球

文档讲解:代码随想录/用最少数量的箭引爆气球

视频讲解:视频讲解-用最少数量的箭引爆气球

状态:已完成(1遍)

解题过程  

看到题目的第一想法

按照气球坐标的左端进行排序,然后对后面的能一箭射穿的气球进行分类,只要后面的气球的左端坐标小于等于当前气球的右端坐标,就都是能一箭射穿的气球。分为一类之后再重新开始分类,直到最后。这是我的想法,不知道能不能行。

/**
 * @param {number[][]} points
 * @return {number}
 */
var findMinArrowShots = function (points) {
    let arrow = 1;
    let newPoints = points.sort(function (a, b) {
        if (a[0] != b[0]) {
            return a[0] - b[0];
        } else {
            return b[1] - a[1];
        }
    })
    let curArrow = [];
    for (let i = 0; i < newPoints.length; i++) {
        let curLeft = newPoints[i][0],curRight = newPoints[i][1];
        if(curArrow.length == 0){
            //如果一箭射穿数组是空的,也就是刚开始
            curArrow.push(newPoints[i]);
        }else if  (curLeft <= curArrow[0][1] ) {
            // 当前气球左端坐标小于一箭射穿数组的第一个气球的右端坐标
            // curArrow.push(points[i]);
            curArrow[0][0] == curLeft;
            curArrow[0][1] = curRight<=curArrow[0][1]?curRight:curArrow[0][1];
        } else if ( curLeft > curArrow[0][1]) {
            curArrow = [];
            arrow++;
            curArrow.push(newPoints[i]);
        }
    }
    return arrow;
};

哈哈哈提交成功,没有问题。 

 看完代码随想录之后的想法 

确实一开始我对气球排序的时候多考虑了,当气球左端坐标相等时,不用去管右端谁更小,反正后面的判断里会判断。

且只用对新气球的左端坐标和之前的右端最小坐标进行对比就可以了,写的简洁很多。

讲解代码如下:

/**
 * @param {number[][]} points
 * @return {number}
 */
var findMinArrowShots = function(points) {
    points.sort((a, b) => {
        return a[0] - b[0]
    })
    let result = 1
    for(let i = 1; i < points.length; i++) {
        if(points[i][0] > points[i - 1][1]) {
            result++
        } else {
            points[i][1] = Math.min(points[i - 1][1], points[i][1])
        }
    }

    return result
};

总结

我的思路没问题,但是代码写起来过于复杂,确实一个是不用更新一箭射穿数组的左坐标,一个是右坐标更新表达式可以用Math.min替代,i从1开始也可以有效避免我的第一个if判断。

  • 7
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值