diff --git a/Leecode/src/main/java/com/markilue/leecode/greedy/CanCompleteCircuit.java b/Leecode/src/main/java/com/markilue/leecode/greedy/CanCompleteCircuit.java index 30a7431..9c7a4f7 100644 --- a/Leecode/src/main/java/com/markilue/leecode/greedy/CanCompleteCircuit.java +++ b/Leecode/src/main/java/com/markilue/leecode/greedy/CanCompleteCircuit.java @@ -18,7 +18,7 @@ public class CanCompleteCircuit { @Test public void test() { int[] gas = {1, 2, 3, 4, 5}, cost = {3, 4, 5, 1, 2}; - System.out.println(canCompleteCircuit(gas, cost)); + System.out.println(canCompleteCircuit1(gas, cost)); } @@ -72,4 +72,114 @@ public class CanCompleteCircuit { } } + + + /** + * 官方代码思路:我们首先检查第 0个加油站,并试图判断能否环绕一周;如果不能,就从第一个无法到达的加油站开始继续检查。 + * 速度击败78.24%,内存击败16.6% + * 这种思路理论上来说是O(N),但是实际上可能是O(N^2),因为他遍历以后还需要至少重头开始计算一次, + * 但是实际上可以通过数学推导进行避免,例如本人的思路,当然本人的思路的前提是题目只有唯一解,如果有多个解本人方法是没办法找的,题解方法可以 + * @param gas + * @param cost + * @return + */ + public int canCompleteCircuit1(int[] gas, int[] cost) { + int n = gas.length; + int i = 0; + while (i < n) { + int sumOfGas = 0, sumOfCost = 0; + int cnt = 0; + while (cnt < n) { + //使得一定会转一圈回到原地 + int j = (i + cnt) % n; + sumOfGas += gas[j]; + sumOfCost += cost[j]; + if (sumOfCost > sumOfGas) { + break; + } + cnt++; + } + //判断是否是转了一圈,如果转了一圈,则证明找到了那个唯一解 + if (cnt == n) { + return i; + } else { + //没有转一圈,从头开始找 + i = i + cnt + 1; + } + } + return -1; + } + + + + /** + * 代码随想录贪心算法一思路: + * 情况一:gas总和小于cost,永远到不了 + * 情况二:rest=gas-cost的累加和一直大于0,则从0出发 + * 情况二:rest=gas-cost小于0了,则没办法到下一站,则直接从下一站非0点出发 + * @param gas + * @param cost + * @return + */ + public int canCompleteCircuit2(int[] gas, int[] cost) { + int curSum=0; + int min=Integer.MAX_VALUE; //从起点出发,油箱里的油量的最小值 + + for (int i = 0; i < gas.length; i++) { + int rest=gas[i]-cost[i]; + curSum+=rest; + if(curSum=0)return 0; //情况二 + + //情况三 + //这里开始反向加,如果加完之后大于0了,则证明一定可以转一圈,反之如果怎么加都不能加到则-1 + for (int i =gas.length-1; i > 0; i--) { + int rest=gas[i]-cost[i]; + min+=rest; + + if(min>=0){ + return i; + } + } + return -1; + } + + + /** + * 代码随想录贪心算法二思路: + * 与本人方法类似,不需要使用flag + * 速度击败78.24%,内存击败35.3% + * @param gas + * @param cost + * @return + */ + public int canCompleteCircuit3(int[] gas, int[] cost) { + + int curSum=0; + int totalSum=0; + int start=0; + + for (int i = 0; i < gas.length; i++) { + + curSum+=gas[i]-cost[i]; + totalSum+=gas[i]-cost[i]; + if(curSum<0){ + start=i+1; + curSum=0; + } + } + if(totalSum<0)return -1; + return start; + + } + + + + + } diff --git a/Leecode/src/main/java/com/markilue/leecode/greedy/Candy.java b/Leecode/src/main/java/com/markilue/leecode/greedy/Candy.java new file mode 100644 index 0000000..1e106a5 --- /dev/null +++ b/Leecode/src/main/java/com/markilue/leecode/greedy/Candy.java @@ -0,0 +1,204 @@ +package com.markilue.leecode.greedy; + +import org.junit.Test; + +import java.util.Arrays; + +/** + * @BelongsProject: Leecode + * @BelongsPackage: com.markilue.leecode.greedy + * @Author: markilue + * @CreateTime: 2022-11-09 11:19 + * @Description: TODO 力扣135题 分发糖果: + * n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。 + * 你需要按照以下要求,给这些孩子分发糖果: + * 每个孩子至少分配到 1 个糖果。 + * 相邻两个孩子评分更高的孩子会获得更多的糖果。 + * 请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。 + * @Version: 1.0 + */ +public class Candy { + + @Test + public void test() { + int[] ratings = {1, 0, 2}; + System.out.println(candy(ratings)); + } + + @Test + public void test1() { + int[] ratings = {1, 2, 2}; + System.out.println(candy(ratings)); + } + + @Test + public void test2() { + int[] ratings = {5, 4, 3, 2, 1, 2, 6, 4, 3, 2, 1}; + System.out.println(candy2(ratings)); + } + + @Test + public void test3() { + int[] ratings = {1, 2, 3, 4, 5, 3, 1, 2, 3, 5}; + System.out.println(candy(ratings)); + } + + @Test + public void test4() { + int[] ratings = {1, 3, 2, 2, 1}; + System.out.println(candy(ratings)); //7 + } + + @Test + public void test5() { + int[] ratings = {29, 51, 87, 87, 72, 12}; + System.out.println(candy2(ratings)); //12 + } + + + /** + * 本人思路:要计算当前位置至少需要多少糖果,如果观察局部左右的最小值的位置, + * 因此需要寻找一个单调递减的位置的。然后candy依次递增累加 + * todo 尚且有问题,对于test5不行,问题在于:对于相等值的边界处理,现在是如果相等就跟之前反向,则test5过不去 + * + * @param ratings + * @return + */ + public int candy(int[] ratings) { + + int sum = 0; + int countMin = 1; //记录单调下降区间中的count数 + int countMax = 1; //记录单调上升区间中的count数 + int pre = 1; + boolean flag = true; + + + for (int i = 0; i < ratings.length - 1; i++) { + + int cur = ratings[i + 1] - ratings[i]; + if (cur == 0 && i > 0) { + cur = -(ratings[i] - ratings[i - 1]); + } + + if (cur < 0) { + if (!flag) { + pre = countMax; + countMax = 1; + flag = true; + } + //单调递减区间 + sum += countMin; + countMin = countMin + 1; + } else { + //单调递增区间 + //找到局部最小值的转折点了 + //对于极值点的判断 + if (flag) { + if (countMin > pre) { + sum += countMin; + } else { + sum += pre; + } + countMin = 1; + countMax = 2; + flag = false; + continue; + } + + sum += countMax; + countMax = countMax + 1; + + } + + + } + if (countMax == 1) { + if (countMin > pre) { + sum += countMin; + } else { + sum += pre; + } + } else { + sum += countMax; + } + + + return sum; + } + + + /** + * 代码随想录思路:对于贪心策略的使用,如果在考虑局部最优的时候想兼顾两边,就会顾此失彼 + * 本题则需要采用两次贪心的策略: + * 一次是从左到右的遍历,只比较右边孩子的评分比左边大的情况 + * 另一次是从右到左遍历,只比较左边孩子的评分比右边大的情况 + * 速度击败98.66%,内存击败6.19% + * @param ratings + * @return + */ + public int candy1(int[] ratings) { + + int[] candyVec = new int[ratings.length]; + Arrays.fill(candyVec, 1); + + //从前往后遍历 + for (int i = 1; i < ratings.length; i++) { + if (ratings[i] > ratings[i - 1]) candyVec[i] = candyVec[i - 1] + 1; + } + //从后前遍历 + for (int i = ratings.length - 2; i >= 0; i--) { + //这里是判断从左往右的需求大还是从右往左的需要大 + if (ratings[i] > ratings[i + 1]) candyVec[i] = Math.max(candyVec[i], candyVec[i + 1] + 1); + } + //统计结果 + int result=0; + for (int i = 0; i < candyVec.length; i++) { + result+=candyVec[i]; + } + return result; + + } + + + /** + * 官方常数空间思路: + * 从左到右枚举每一个同学,记前一个同学分得的糖果数量为 pre: + * 如果当前同学比上一个同学评分高,说明我们就在最近的递增序列中,直接分配给该同学 pre+1个糖果即可。 + * 否则我们就在一个递减序列中,我们直接分配给当前同学一个糖果,并把该同学所在的递减序列中所有的同学都再多分配一个糖果,以保证糖果数量还是满足条件。 + * 我们无需显式地额外分配糖果,只需要记录当前的递减序列长度,即可知道需要额外分配的糖果数量。 + * 同时注意当当前的递减序列长度和上一个递增序列等长时,需要把最近的递增序列的最后一个同学也并进递减序列中。 + * 这样,我们只要记录当前递减序列的长度dec,最近的递增序列的长度 inc 和前一个同学分得的糖果数量 pre 即可。 + * 速度击败98.66%,内存击败34.77% + * @param ratings + * @return + */ + public int candy2(int[] ratings) { + + int n = ratings.length; + int ret = 1; + //inc记录上升时,上次的糖果数;dec记录下降时的糖果数 + int inc = 1, dec = 0, pre = 1; + for (int i = 1; i < n; i++) { + if (ratings[i] >= ratings[i - 1]) { + dec = 0; + pre = ratings[i] == ratings[i - 1] ? 1 : pre + 1; + ret += pre; + inc = pre; + } else { + dec++; + //这里是最关键的一点,比较神奇,核心在于dec=inc则左右一样长了,那么最上面的节点也需要加1, + // 这里dec+1,相当于帮助上边的节点完成+1操作;而往后每多一个都需要帮助最上面的节点+1,而当dec+1以后相当于往后每一个都多帮最上边的节点+1, + // 从而巧妙的只需要一次遍历即可完成操作 + if (dec == inc) { + dec++; + } + ret += dec; + pre = 1; + } + } + return ret; + + } + + +}