leecode更新

This commit is contained in:
markilue 2023-02-09 18:58:41 +08:00
parent c893db74ad
commit 5c54b530f4
5 changed files with 361 additions and 2 deletions

View File

@ -0,0 +1,208 @@
package com.markilue.leecode.greedy;
import org.junit.Test;
import java.util.Arrays;
import java.util.stream.IntStream;
/**
*@BelongsProject: Leecode
*@BelongsPackage: com.markilue.leecode.greedy
*@Author: markilue
*@CreateTime: 2023-02-09 10:37
*@Description:
* TODO 力扣1005 K 次取反后最大化的数组和:
* 给你一个整数数组 nums 和一个整数 k 按以下方法修改该数组
* 选择某个下标 i 并将 nums[i] 替换为 -nums[i]
* 重复这个过程恰好 k 可以多次选择同一个下标 i
* 以这种方式修改数组后返回数组 可能的最大和
*@Version: 1.0
*/
public class T07_LargestSumAfterKNegations {
@Test
public void test() {
// int[] nums = {4, 2, 3};
// int[] nums = {3,-1,0,2};
// int[] nums = {2,-3,-1,5,-4};
int[] nums = {-3,-1,-4,2};
// int[] nums = {-2,5,0,2,-2};
System.out.println(largestSumAfterKNegations(nums, 5));
}
/**
* 思路分情况讨论花了很长时间才写出条件判断太复杂
* 1)如果负数数量>=k,那么直接取反负数
* 2如果负数数量<k,分为(k-n)%2两种情况
* 1.如果(k-n)%2==0,那么和1一致
* 2.如果(k-n)%2==1,看看负数最后一个和正数的第一个之和为正还是负
* 速度击败97.8%内存击败5.2% 2ms
* @param nums
* @param k
* @return
*/
public int largestSumAfterKNegations(int[] nums, int k) {
Arrays.sort(nums);
//计算负数的个数
int fu = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] < 0) fu++;
else break;
}
int result = 0;
int index = 0;//需要一直取反的索引
if (k <= fu) {
index = k-1;//减1是因为要从个数变成索引
} else {
if ((k - fu) % 2 == 0 ) {//没有负数
index = fu-1;
} else {
if(fu == 0) {
index = 0;
}else {
//(k-n)%2==1的情况
if (fu < nums.length && nums[fu-1] + nums[fu] < 0) {
index = fu + 1-1;
} else {
index = fu - 1-1;//在减1是变成索引
}
}
}
}
for (int i = 0; i < nums.length; i++) {
result += i <= index ? -nums[i] : nums[i];//负数直接取反,正数不管
}
return result;
}
/**
* TODO 代码随想录思路:思路本质上类似但是如果根据绝对值排序就十分的简洁
* 第一步将数组按照绝对值大小从大到小排序注意要按照绝对值的大小
* 第二步从前向后遍历遇到负数将其变为正数同时K--
* 第三步如果K还大于0那么反复转变数值最小的元素将K用完
* 第四步求和
* 速度击败12.22%内存击败35.4% 11ms
* @param nums
* @param K
* @return
*/
public int largestSumAfterKNegations1(int[] nums, int K) {
// 将数组按照绝对值大小从大到小排序注意要按照绝对值的大小
nums = IntStream.of(nums)
.boxed()
.sorted((o1, o2) -> Math.abs(o2) - Math.abs(o1))
.mapToInt(Integer::intValue).toArray();
int len = nums.length;
for (int i = 0; i < len; i++) {
//从前向后遍历遇到负数将其变为正数同时K--
if (nums[i] < 0 && K > 0) {
nums[i] = -nums[i];
K--;
}
}
// 如果K还大于0那么反复转变数值最小的元素将K用完,把绝对值最小的反过来
if (K % 2 == 1) nums[len - 1] = -nums[len - 1];
return Arrays.stream(nums).sum();
}
/**
* 胆码随想录另一种思路
* 速度击败97.8%内存击败24.17%
* @param A
* @param k
* @return
*/
public int largestSumAfterKNegations2(int[] A, int k) {
if (A.length == 1) return k % 2 == 0 ? A[0] : -A[0];
Arrays.sort(A);
int sum = 0;
int idx = 0;
for (int i = 0; i < k; i++) {
if (i < A.length - 1 && A[idx] < 0) {
A[idx] = -A[idx];
if (A[idx] >= Math.abs(A[idx + 1])) idx++;
continue;
}
A[idx] = -A[idx];
}
for (int i = 0; i < A.length; i++) {
sum += A[i];
}
return sum;
}
/**
* 评论区对于代码随想录的优化:使用两次排序找到最小的值最后进行反转优化
* @param nums
* @param k
* @return
*/
public int largestSumAfterKNegations3(int[] nums, int k) {
// 排序把可能有的负数排到前面
Arrays.sort(nums);
int sum = 0;
for (int i = 0; i < nums.length; i++) {
// 贪心如果是负数而k还有盈余就把负数反过来
if (nums[i] < 0 && k > 0) {
nums[i] = -1 * nums[i];
k--;
}
sum += nums[i];
}
Arrays.sort(nums);
// 如果k没剩那说明能转的负数都转正了已经是最大和返回sum
// 如果k有剩说明负数已经全部转正所以如果k还剩偶数个就自己抵消掉不用删减如果k还剩奇数个就减掉2倍最小正数
return sum - (k % 2 == 0 ? 0 : 2 * nums[0]);
}
/**
* 官方最快:0ms利用count记录数量整体处理如果非要找一个可以根据数值直接索引来找
* 速度击败100%内存击败85.23% 0ms
* @param nums
* @param k
* @return
*/
public int largestSumAfterKNegations4(int[] nums, int k) {
int[] count = new int[201];
int sum = 0;
for(int i : nums) {
++ count[i + 100];
sum += i;
}
// 优先反转负数
for(int i = -100; i < 0 && k > 0; ++ i) {
if(count[100 + i] == 0) continue;
// 有负数
int times = Math.min(k, count[i + 100]);
k -= times;
sum -= (times * 2 * i);
}
// 再选一个必须反转的数
if(k > 0 && (k & 1) == 1) {
for(int i = 100, j = 100; i < 201; ++ i, -- j) {
if(count[i] > 0) {
sum -= (i - 100) << 1;
break;
}
if(count[j] > 0) {
sum += (j - 100) << 1;
break;
}
}
}
return sum;
}
}

View File

@ -13,7 +13,7 @@ import org.junit.Test;
* 给定两个整数数组 gas cost 如果你可以绕环路行驶一周则返回出发时加油站的编号否则返回 -1 如果存在解 保证 它是 唯一 * 给定两个整数数组 gas cost 如果你可以绕环路行驶一周则返回出发时加油站的编号否则返回 -1 如果存在解 保证 它是 唯一
* @Version: 1.0 * @Version: 1.0
*/ */
public class CanCompleteCircuit { public class T08_CanCompleteCircuit {
@Test @Test
public void test() { public void test() {

View File

@ -17,7 +17,7 @@ import java.util.Arrays;
* 请你给每个孩子分发糖果计算并返回需要准备的 最少糖果数目 * 请你给每个孩子分发糖果计算并返回需要准备的 最少糖果数目
* @Version: 1.0 * @Version: 1.0
*/ */
public class Candy { public class T09_Candy {
@Test @Test
public void test() { public void test() {

View File

@ -0,0 +1,129 @@
package com.markilue.leecode.greedy.second;
import org.junit.Test;
/**
*@BelongsProject: Leecode
*@BelongsPackage: com.markilue.leecode.greedy.second
*@Author: markilue
*@CreateTime: 2023-02-09 12:04
*@Description:
* TODO 力扣134题 加油站:
* 在一条环路上有 n 个加油站其中第 i 个加油站有汽油 gas[i]
* 你有一辆油箱容量无限的的汽车从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 你从其中的一个加油站出发开始时油箱为空
* 给定两个整数数组 gas cost 如果你可以绕环路行驶一周则返回出发时加油站的编号否则返回 -1 如果存在解 保证 它是 唯一
*@Version: 1.0
*/
public class T08_CanCompleteCircuit {
@Test
public void test() {
int[] gas = {2, 3, 4}, cost = {3, 4, 3};
System.out.println(canCompleteCircuit(gas, cost));
}
/**
* 思路:挨个找基本等同于暴力解法,时间超出限制
* @param gas
* @param cost
* @return
*/
public int canCompleteCircuit(int[] gas, int[] cost) {
for (int i = 0; i < gas.length; i++) {
if (gas[i] < cost[i]) continue;
//看看这个位置到不到不得了
int gasSum = 0;
for (int j = 0; j <= gas.length; j++) {
if (j == gas.length) return i;
int left = gas[(j + i) % gas.length] - cost[(j + i) % gas.length];
if (gasSum + left < 0) break;
gasSum += left;
}
}
return -1;//遍历完了都不行返回-1
}
/**
* 思路:对上面思路的优化如果记录下break的位置之后那么之前的则可以全部跳过
* 速度击败25.59%内存击败61.45% 3ms
* @param gas
* @param cost
* @return
*/
public int canCompleteCircuit1(int[] gas, int[] cost) {
for (int i = 0; i < gas.length; i++) {
if (gas[i] < cost[i]) continue;
//看看这个位置到不到不得了
int gasSum = 0;
int j = 0;
for (; j <= gas.length; j++) {
if (j == gas.length) return i;
int left = gas[(j + i) % gas.length] - cost[(j + i) % gas.length];
if (gasSum + left < 0) break;
gasSum += left;
}
i = i + j;//这一段不用再遍历了
}
return -1;//遍历完了都不行返回-1
}
/**
* 以前的思路:直接用totalSum和curSum记录当前的总和如果curSum小于0了那么就证明之前一定到不了
* index一定在最后面接着最后判断totalSum的总和来判断到底到不了的了
* 只用遍历一次
* 速度击败81.71%内存击败81.5% 2ms
* @param gas
* @param cost
* @return
*/
public int canCompleteCircuit2(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;
}
/**
* 官方最快本质上也是贪心找到局部最小值
* 速度击败100%内存击败80.24%
* @param gas
* @param cost
* @return
*/
public int canCompleteCircuit3(int[] gas, int[] cost) {
int n = gas.length;
int gasVal = 0;
int minVal = Integer.MAX_VALUE;
int minIndex = 0;
for (int i = 0; i < n; i++) {
gasVal += gas[i] - cost[i];
if (gasVal < minVal) {
minVal = gasVal;
minIndex = i;
}
}
if (gasVal < 0) return -1;
return (minIndex + 1) % n;//一定在最小的位置的后面1个
}
}

View File

@ -0,0 +1,22 @@
package com.markilue.leecode.greedy.second;
/**
*@BelongsProject: Leecode
*@BelongsPackage: com.markilue.leecode.greedy.second
*@Author: markilue
*@CreateTime: 2023-02-09 13:12
*@Description:
* TODO 力扣135题 分发糖果:
* n 个孩子站成一排给你一个整数数组 ratings 表示每个孩子的评分
* 你需要按照以下要求给这些孩子分发糖果
* 每个孩子至少分配到 1 个糖果
* 相邻两个孩子评分更高的孩子会获得更多的糖果
* 请你给每个孩子分发糖果计算并返回需要准备的 最少糖果数目
*@Version: 1.0
*/
public class T09_Candy {
public int candy(int[] ratings) {
}
}