当前位置: 首页 > news >正文

代码随想录算法day25 | 贪心算法part03 | 134. 加油站,135. 分发糖果,860.柠檬水找零,406.根据身高重建队列

134. 加油站

本题有点难度,不太好想,推荐大家熟悉一下方法二

力扣题目链接(opens new window)

在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。

说明:

  • 如果题目有解,该答案即为唯一答案。
  • 输入数组均为非空数组,且长度相同。
  • 输入数组中的元素均为非负数。

示例 1: 输入:

  • gas = [1,2,3,4,5]
  • cost = [3,4,5,1,2]

输出: 3 解释:

  • 从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
  • 开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
  • 开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
  • 开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
  • 开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
  • 开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
  • 因此,3 可为起始索引。

示例 2: 输入:

  • gas = [2,3,4]

  • cost = [3,4,3]

  • 输出: -1

  • 解释: 你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油。开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油。开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油。你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。因此,无论怎样,你都不可能绕环路行驶一周。

暴力方法

暴力的方法很明显就是O(n^2)的,遍历每一个加油站为起点的情况,模拟一圈

如果跑了一圈,中途没有断油,而且最后油量大于等于0,说明这个起点是ok的。

暴力的方法思路比较简单,但代码写起来也不是很容易,关键是要模拟跑一圈的过程。

for循环适合模拟从头到尾的遍历,而while循环适合模拟环形遍历,要善于使用while!

Java代码如图所示:

public class Solution {int canCompleteCircuit(int[] gas, int[] cost) {for (int i = 0; i < cost.length; i++) {int rest = gas[i] - cost[i]; // 记录剩余油量int index = (i + 1) % cost.length;while (rest > 0 && index != i) { // 模拟以i为起点行驶一圈(如果有rest==0,那么答案就不唯一了)rest += gas[index] - cost[index];index = (index + 1) % cost.length;}// 如果以i为起点跑一圈,剩余油量>=0,返回该起始位置if (rest >= 0 && index == i) return i;}return -1;}
};
  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)

贪心算法(方法一)

直接从全局进行贪心选择,情况如下:

  • 情况一:如果 gas 的总和小于 cost 总和,那么无论从哪里出发,一定是跑不了一圈的

  • 情况二:rest[i] = gas[i]-cost[i] 为一天剩下的油,i 从 0 开始计算累加到最后一站,如果累加没有出现负数,说明从0出发,油就没有断过(如果< 0,说明有一天亏得油比之前剩的所有油都多;如果 >=0,说明要么没亏过油,要么亏的油之前剩余都能cover),那么0就是起点。

这里其实容易被题目实例解析里面的式子带跑,要对式子进行变形就能得到上面这种简单的思路而不需要纠结 i+1 or i - 1

  • 情况三:如果累加的最小值是负数,汽车就要从非0节点出发,从后向前,看哪个节点能把这个负数填平,能把这个负数填平的节点就是出发节点。

Java 代码如下:

class Solution {public int canCompleteCircuit(int[] gas, int[] cost) {int sum = 0;int min = 0;for (int i = 0; i < gas.length; i++) {sum += (gas[i] - cost[i]);min = Math.min(sum, min);}if (sum < 0) return -1;   // 情况一:cost总和大于gas总和if (min >= 0) return 0;  //情况二:油没断过// 情况三:从后往前看,看哪个节点能把这个负数填平for (int i = gas.length - 1; i > 0; i--) {min += (gas[i] - cost[i]);if (min >= 0) return i;}return -1;}
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

有同学可能不认为这种方式是贪心算法,因为没有找出局部最优,而是直接从全局最优的角度上思考问题

但这种解法又说不出是什么方法,这就是一个从全局角度选取最优解的模拟操作。

所以对于本解法是贪心,我持保留意见!

但不管怎么说,解法毕竟还是巧妙的,不用过于执着于其名字称呼。

贪心算法(方法二)

可以换一个思路,首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明各个站点的加油站剩油量 rest[i] 相加一定是大于等于零的。

每个加油站的剩余量 rest[i] 为 gas[i] - cost[i]。

i 从 0 开始累加 rest[i],和记为 curSum,一旦 curSum 小于零,说明 [0, i] 区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从 i+1 算起,再从 0 计算 curSum。

如图:

那么为什么一旦[0,i] 区间和为负数,起始位置就可以是 i+1 呢,i+1 后面就不会出现更大的负数?

如果出现更大的负数,就是更新 i,那么起始位置又变成新的 i+1 了。

那有没有可能 [0,i] 区间选某一个作为起点,累加到 i 这里 curSum 是不会小于零呢? 如图:

如果 curSum<0 说明 区间和1 + 区间和2 < 0, 那么 假设从上图中的位置开始计数curSum不会小于0的话,就是 区间和2>0。

区间和1 + 区间和2 < 0 同时 区间和2>0,只能说明区间和1 < 0, 那么就会从假设的箭头初就开始从新选择其实位置了

那么局部最优:当前累加 rest[i] 的和 curSum 一旦小于 0,起始位置至少要是 i+1,因为从 i 之前开始一定不行。

全局最优:找到可以跑一圈的起始位置

局部最优可以推出全局最优,找不出反例,试试贪心!

Java 代码如下:

class Solution {public int canCompleteCircuit(int[] gas, int[] cost) {int curSum = 0;int totalSum = 0;int index = 0;for (int i = 0; i < gas.length; i++) {curSum += gas[i] - cost[i];totalSum += gas[i] - cost[i];if (curSum < 0) {index = (i + 1) % gas.length ; curSum = 0;}}if (totalSum < 0) return -1;  // cost总和大于gas总和return index;}
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

说这种解法为贪心算法,才是有理有据的,因为全局最优解是根据局部最优推导出来的

总结

对于本题首先给出了暴力解法,暴力解法模拟跑一圈的过程其实比较考验代码技巧的,要对while使用的很熟练。

然后给出了两种贪心算法,对于第一种贪心方法,其实我认为就是一种直接从全局选取最优的模拟操作,思路还是很巧妙的,值得学习一下。

对于第二种贪心方法,才真正体现出贪心的精髓,用局部最优可以推出全局最优,进而求得起始位置。


135. 分发糖果

本题涉及到一个思想,就是想处理好一边再处理另一边,不要两边想着一起兼顾,后面还会有题目用到这个思路

力扣题目链接(opens new window)

老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。

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

  • 每个孩子至少分配到 1 个糖果。
  • 相邻的孩子中,评分高的孩子必须获得更多的糖果。

那么这样下来,老师至少需要准备多少颗糖果呢?

示例 1:

  • 输入: [1,0,2]
  • 输出: 5
  • 解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。

示例 2:

  • 输入: [1,2,2]
  • 输出: 4
  • 解释: 你可以分别给这三个孩子分发 1、2、1 颗糖果。第三个孩子只得到 1 颗糖果,这已满足上述两个条件。

这道题目一定是要确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边,如果两边一起考虑一定会顾此失彼

先确定右边评分大于左边的情况(也就是从前向后遍历)

此时局部最优:只要右边评分比左边大,右边的孩子就多一个糖果,全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果

局部最优可以推出全局最优。

如果 ratings[i] > ratings[i - 1] 那么 [i] 的糖 一定要比 [i - 1] 的糖多一个,所以贪心:candyVec[i] = candyVec[i - 1] + 1

代码如下:

// 从前向后
for (int i = 1; i < ratings.length; i++) {if (ratings[i] > ratings[i - 1]) candyVec[i] = candyVec[i - 1] + 1;
}

如图:

135.分发糖果

再确定左孩子大于右孩子的情况(从后向前遍历)

遍历顺序这里有同学可能会有疑问,为什么不能从前向后遍历呢?

因为 rating[5] 与 rating[4] 的比较 要利用上 rating[5] 与 rating[6] 的比较结果,所以 要从后向前遍历。

如果从前向后遍历,rating[5]与rating[4]的比较 就不能用上 rating[5]与rating[6]的比较结果了 。如图:

所以确定左孩子大于右孩子的情况一定要从后向前遍历!

如果 ratings[i] > ratings[i + 1],此时 candyVec[i](第i个小孩的糖果数量)就有两个选择了,一个是 candyVec[i + 1] + 1(从右边这个加1得到的糖果数量),一个是 candyVec[i](之前比较右孩子大于左孩子得到的糖果数量)。

那么又要贪心了,局部最优:取 candyVec[i + 1] + 1 和 candyVec[i] 最大的糖果数量,保证第i个小孩的糖果数量既大于左边的也大于右边的。全局最优:相邻的孩子中,评分高的孩子获得更多的糖果。

局部最优可以推出全局最优。

所以就取 candyVec[i + 1] + 1 和 candyVec[i] 最大的糖果数量,candyVec[i] 只有取最大的才能既保持对左边 candyVec[i - 1] 的糖果多,也比右边 candyVec[i + 1] 的糖果多

如图:

135.分发糖果1

所以该过程代码如下:

// 从后向前
for (int i = ratings.length - 2; i >= 0; i--) {if (ratings[i] > ratings[i + 1] ) {candyVec[i] = max(candyVec[i], candyVec[i + 1] + 1);}
}

整体代码如下:

class Solution {/**分两个阶段1、起点下标1 从左往右,只要 右边 比 左边 大,右边的糖果=左边 + 12、起点下标 ratings.length - 2 从右往左, 只要左边 比 右边 大,此时 左边的糖果应该 取本身的糖果数(符合比它左边大) 和 右边糖果数 + 1 二者的最大值,这样才符合 它比它左边的大,也比它右边大*/public int candy(int[] ratings) {int len = ratings.length;int[] candyVec = new int[len];candyVec[0] = 1;for (int i = 1; i < len; i++) {candyVec[i] = (ratings[i] > ratings[i - 1]) ? candyVec[i - 1] + 1 : 1;}for (int i = len - 2; i >= 0; i--) {if (ratings[i] > ratings[i + 1]) {candyVec[i] = Math.max(candyVec[i], candyVec[i + 1] + 1);}}int ans = 0;for (int num : candyVec) {ans += num;}return ans;}
}
  • 时间复杂度: O(n)
  • 空间复杂度: O(n)

总结

这在leetcode上是一道困难的题目,其难点就在于贪心的策略,如果在考虑局部的时候想两边兼顾,就会顾此失彼。

那么本题我采用了两次贪心的策略:

  • 一次是从左到右遍历,只比较右边孩子评分比左边大的情况。
  • 一次是从右到左遍历,只比较左边孩子评分比右边大的情况。

这样从局部最优推出了全局最优,即:相邻的孩子中,评分高的孩子获得更多的糖果。


860.柠檬水找零

本题看上好像挺难,其实很简单,大家先尝试自己做一做。

力扣题目链接(opens new window)

在柠檬水摊上,每一杯柠檬水的售价为 5 美元。

顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。

每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。

注意,一开始你手头没有任何零钱。

如果你能给每位顾客正确找零,返回 true ,否则返回 false 。

示例 1:

  • 输入:[5,5,5,10,20]
  • 输出:true
  • 解释:
    • 前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
    • 第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
    • 第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
    • 由于所有客户都得到了正确的找零,所以我们输出 true。

示例 2:

  • 输入:[5,5,10]
  • 输出:true

示例 3:

  • 输入:[10,10]
  • 输出:false

示例 4:

  • 输入:[5,5,10,10,20]
  • 输出:false
  • 解释:
    • 前 2 位顾客那里,我们按顺序收取 2 张 5 美元的钞票。
    • 对于接下来的 2 位顾客,我们收取一张 10 美元的钞票,然后返还 5 美元。
    • 对于最后一位顾客,我们无法退回 15 美元,因为我们现在只有两张 10 美元的钞票。
    • 由于不是每位顾客都得到了正确的找零,所以答案是 false。

提示:

  • 0 <= bills.length <= 10000
  • bills[i] 不是 5 就是 10 或是 20

这是前几天的leetcode每日一题,感觉不错,给大家讲一下。

这道题目刚一看,可能会有点懵,这要怎么找零才能保证完成全部账单的找零呢?

但仔细一琢磨就会发现,可供我们做判断的空间非常少!

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

有如下三种情况:

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

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

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

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

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

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

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

Java代码如下:

class Solution {public boolean lemonadeChange(int[] bills) {int five = 0;int ten = 0;for (int i = 0; i < bills.length; i++) {if (bills[i] == 5) {five++;} else if (bills[i] == 10) {five--;ten++;} else if (bills[i] == 20) {if (ten > 0) {ten--;five--;} else {five -= 3;}}if (five < 0 || ten < 0) return false;}return true;}
}
  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

总结

咋眼一看好像很复杂,分析清楚之后,会发现逻辑其实非常固定。

这道题目可以告诉大家,遇到感觉没有思路的题目,可以静下心来把能遇到的情况分析一下,只要分析到具体情况了,一下子就豁然开朗了。

如果一直陷入想从整体上寻找找零方案,就会把自己陷进去,各种情况一交叉,只会越想越复杂了。


406.根据身高重建队列

本题有点难度,和分发糖果类似,不要两头兼顾,处理好一边再处理另一边

力扣题目链接(opens new window)

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。

请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。

示例 1:

  • 输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
  • 输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
  • 解释:
    • 编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
    • 编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
    • 编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
    • 编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
    • 编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。
    • 编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
    • 因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。

示例 2:

  • 输入:people = [[6,0],[5,0],[4,0],[3,2],[2,2],[1,4]]
  • 输出:[[4,0],[5,0],[2,2],[3,2],[1,4],[6,0]]

提示:

  • 1 <= people.length <= 2000
  • 0 <= hi <= 10^6
  • 0 <= ki < people.length

题目数据确保队列可以被重建

本题有两个维度,h 和 k,看到这种题目一定要想如何确定一个维度,然后再按照另一个维度重新排列

其实如果大家认真做了 135.分发糖果,就会发现和此题有点点的像。

135.分发糖果 强调过一次,遇到两个维度权衡的时候,一定要先确定一个维度,再确定另一个维度。

如果两个维度一起考虑一定会顾此失彼

对于本题相信大家困惑的点是先确定 k 还是先确定 h 呢,也就是究竟先按 h 排序呢,还是先按照 k 排序呢?

如果按照 k 来从小到大排序,排完之后,会发现 k 的排列并不符合条件,身高也不符合条件,两个维度哪一个都没确定下来。

那么按照身高 h 来排序呢,身高一定是从大到小排(身高相同的话则k小的站前面),让高个子在前面。

此时我们可以确定一个维度了,就是身高,前面的节点一定都比本节点高!

那么只需要按照 k 为下标重新插入队列就可以了,为什么呢?

以图中{5,2} 为例:

406.根据身高重建队列

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

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

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

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

局部最优可推出全局最优,找不出反例,那就试试贪心。

刷题或者面试的时候,手动模拟一下感觉可以局部最优推出整体最优,而且想不到反例,那么就试一试贪心,至于严格的数学证明,就不在讨论范围内了。

回归本题,整个插入过程如下:

排序完的people: [[7,0], [7,1], [6,1], [5,0], [5,2], [4,4]]

插入的过程:

  • 插入[7,0]:[[7,0]]
  • 插入[7,1]:[[7,0],[7,1]]
  • 插入[6,1]:[[7,0],[6,1],[7,1]]
  • 插入[5,0]:[[5,0],[7,0],[6,1],[7,1]]
  • 插入[5,2]:[[5,0],[7,0],[5,2],[6,1],[7,1]]
  • 插入[4,4]:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]

此时就按照题目的要求完成了重新排列。

Java 代码如下:

class Solution {public int[][] reconstructQueue(int[][] people) {// 身高从大到小排(身高相同k小的站前面)Arrays.sort(people, (a, b) -> {if (a[0] == b[0]) return a[1] - b[1];   // a - b 是升序排列,故在a[0] == b[0]的狀況下,會根據k值升序排列return b[0] - a[0];   //b - a 是降序排列,在a[0] != b[0],的狀況會根據h值降序排列});LinkedList<int[]> que = new LinkedList<>();for (int[] p : people) {que.add(p[1],p);   //Linkedlist.add(index, value),會將value插入到指定index裡。}return que.toArray(new int[people.length][]);}
}
  • 时间复杂度:O(nlog n + n^2)
  • 空间复杂度:O(n)

总结

关于出现两个维度一起考虑的情况,我们已经做过两道题目了,另一道就是 135.分发糖果

其技巧都是确定一边然后贪心另一边,两边一起考虑,就会顾此失彼

这道题目可以说比​​​​​​​ 135.分发糖果 难不少,其贪心的策略也是比较巧妙。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 算法学习-基础数据结构
  • 【python】懂车帝字体反爬逐层解密案例(附完整代码)
  • 中秋佳节,数码好礼伴团圆:中秋节五大数码礼品指南
  • docker compose用法详解
  • 深度确定问题中的树森林操作:分析与实现
  • OpenCV+Python识别机读卡
  • 盘点国内外最好用的12款源代码加密软件:总有一款适合你
  • Python爬虫,爬取某网站小说
  • Nvidia财报前夕:市场预期股价波动创纪录,AI芯片巨头引领市场热潮
  • DNS劫持问题
  • ArcGIS Pro技术应用
  • 【计算机网络】电路交换、报文交换、分组交换
  • 云计算实训37——Dockerfile的应用+私有仓库的创建与管理
  • 第三届环境工程与可持续能源国际会议(EESE 2024)
  • 【Liunx入门】Liunx软件包管理器
  • 0基础学习移动端适配
  • es6
  • Java IO学习笔记一
  • LeetCode算法系列_0891_子序列宽度之和
  • Nacos系列:Nacos的Java SDK使用
  • Spring Boot MyBatis配置多种数据库
  • 类orAPI - 收藏集 - 掘金
  • 通过几道题目学习二叉搜索树
  • 一些基于React、Vue、Node.js、MongoDB技术栈的实践项目
  • 原生 js 实现移动端 Touch 滑动反弹
  • ionic异常记录
  • 选择阿里云数据库HBase版十大理由
  • ​DB-Engines 12月数据库排名: PostgreSQL有望获得「2020年度数据库」荣誉?
  • # 利刃出鞘_Tomcat 核心原理解析(二)
  • #define
  • #HarmonyOS:软件安装window和mac预览Hello World
  • (CPU/GPU)粒子继承贴图颜色发射
  • (LeetCode C++)盛最多水的容器
  • (vue)el-checkbox 实现展示区分 label 和 value(展示值与选中获取值需不同)
  • (三)Honghu Cloud云架构一定时调度平台
  • (十五)devops持续集成开发——jenkins流水线构建策略配置及触发器的使用
  • (一)pytest自动化测试框架之生成测试报告(mac系统)
  • (一)基于IDEA的JAVA基础1
  • (原)本想说脏话,奈何已放下
  • (源码版)2024美国大学生数学建模E题财产保险的可持续模型详解思路+具体代码季节性时序预测SARIMA天气预测建模
  • (转载)从 Java 代码到 Java 堆
  • ******IT公司面试题汇总+优秀技术博客汇总
  • .NET Framework 服务实现监控可观测性最佳实践
  • .NET/C# 如何获取当前进程的 CPU 和内存占用?如何获取全局 CPU 和内存占用?
  • .NET下的多线程编程—1-线程机制概述
  • []常用AT命令解释()
  • [023-2].第2节:SpringBoot中接收参数相关注解
  • [2024-06]-[大模型]-[Ollama] 0-相关命令
  • [BT]BUUCTF刷题第4天(3.22)
  • [C#]DataTable常用操作总结【转】
  • [C++ 从入门到精通] 12.重载运算符、赋值运算符重载、析构函数
  • [Cesium学习]
  • [Flex] PopUpButton系列 —— 控制弹出菜单的透明度、可用、可选择状态
  • [Flexbox] Using order to rearrange flexbox children
  • [Godot] 3D拾取