leecode更新

This commit is contained in:
markilue 2022-11-09 15:52:52 +08:00
parent e18305c54a
commit ba62bbc0a2
2 changed files with 315 additions and 1 deletions

View File

@ -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<min){
min=curSum;
}
}
if(curSum<0)return -1; //情况一
if(min>=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;
}
}

View File

@ -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;
}
}