所用代码 java
K次取反后最大化的数组和 LeetCode 1005
题目链接:K次取反后最大化的数组和 LeetCode 1005 - 简单
思路
先选负数,再选0,再选正数里面晓得 => 从小到大选,0多选。
本题蕴含两次贪心:
- 优先对负数取反,负数里面优先对绝对值最大的取反
- 都是正数取最小值,把k全部消耗掉
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
int sum = 0;
// 按绝对值大小排序 -- 这个太难了
nums = IntStream.of(nums)
.boxed()
.sorted(((o1, o2) -> Math.abs(o2) - Math.abs(o1)))
.mapToInt(Integer::intValue).toArray();
// System.out.println(Arrays.toString(nums));
// 此时数全是正的(k>0) 从大到小排序的
for (int i = 0; i < nums.length; i++) {
if (nums[i] < 0 && k > 0) {
nums[i] = -nums[i];
k--;
}
}
// 若k没翻转完,最后一个数肯定是最小的
// k为奇数就把最小的翻转,偶数则翻转完不变
if (k !=0 && k%2 == 1) {
nums[nums.length - 1] = -nums[nums.length - 1];
}
for (int num : nums) {
sum += num;
}
return sum;
}
}
由于java按绝对值排序使用的int流IntStream有点复杂,所有我用简单的方法,就是两次排序。
- 第一次贪心,从小到大排序,若是负数就取反
- 第二次贪心,再从小到大排序,若还要取反就只用翻第一个数
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
int sum = 0;
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
if (nums[i] < 0 && k > 0) {
nums[i] = -nums[i];
k--;
}
}
// System.out.println(Arrays.toString(nums));
// 两次排序,保证第一个数是最小的
Arrays.sort(nums);
// System.out.println(Arrays.toString(nums));
// 若k没翻转完,第一个数肯定是最小的
// k为奇数就把最小的翻转,偶数则翻转完不变
if (k !=0 && k%2 == 1) {
nums[0] = -nums[0];
}
for (int num : nums) {
sum += num;
}
return sum;
}
}
还有一种一次排序的方法
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
if (nums.length == 1){
return k % 2 == 0 ? nums[0] : -nums[0];
}
Arrays.sort(nums);
int sum = 0;
// 用来对nums计数
int index = 0;
for (int i = 0; i < k; i++) {
// 判断是不是负数,是就取反
if (i < nums.length - 1 && nums[index] < 0){
nums[index] = -nums[index];
// 若后一个数的绝对值比当前的数更小,就证明下一个数是待取反的数
if (nums[index] >= Math.abs(nums[index + 1])){
index ++;
}
continue;
}
nums[index] = -nums[index];
}
for (int num : nums) {
sum += num;
}
return sum;
}
}
总结
本题需两次贪心,每次贪心都是取最小的值判断是否要取反,所有我就进行两次排序来操作。
加油站 LeetCode 134
题目链接:加油站 LeetCode 134 - 中等
思路
无。
局部最优:curSum为正,能往下一个站点跑
全局最优:找到可以跑完一圈的起点
注意: for循环适合模拟从头到尾的遍历,while循环适合模拟环形的遍历
暴力: 但是超时了!!
class Solution {
public int canCompleteCircuit(int[] gas, int[] cost) {
for (int i = 0; i < gas.length; i++) {
// 记录当前剩余的油量
int rest = gas[i] - cost[i];
int start = (i + 1) % gas.length;
// 模拟以 i 为起点行使一圈 rest>0则表示可以到下一个站点
// 所以当剩余油量小于0或是回到起点了就退出
// start == i 就证明回到了起点
while (rest > 0 && start != i){
rest += gas[start] - cost[start];
start = (start + 1) % gas.length;
}
if (rest >=0 && start == i) return start;
}
return -1;
}
}
贪心:
class Solution {
public int canCompleteCircuit(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]);
// 当前可以行驶的路程不至于开往下一个站点,起始位置就是i+1
// (i + 1) % gas.length 为了放在i在末尾的时候没取到第一个数
if (curSum <= 0) {
curSum = 0;
start = (i + 1) % gas.length;
}
}
if (totalSum < 0) return -1;
return start;
}
}
总结
按上述判断所剩的油是否可以开往下一站点,若不能开往下一站点了,起点一定是i+1。但是需要注意的是我们的总油量小于0,肯定是不能环路一周的。
本题还有一种解法:
class Solution {
public int canCompleteCircuit(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;
}
}
// 1、当前油量和小于0,不能绕一圈
if (curSum < 0) return -1;
// 2、从起点出发的最小油量一直保持大于0,则从第一个起点出发肯定能绕一圈
if (min >= 0) return 0;
// 3、min < 0,在中途某个地方不能往下一站开
for (int i = gas.length - 1; i >= 0; i--) {
int rest = gas[i] - cost[i];
// 由于本题是唯一解,我们从后往前遍历,
// 看到哪个位置能把从起点出发的最小油量和给填满,
// 该位置就是出发点
min += rest;
if (min >= 0){
return i;
}
}
return -1;
}
}
分发糖果 LeetCode 135
题目链接:分发糖果 LeetCode 135 - 困难
思路
无。
主要有两种情况,然后取每次的最大值。
- 右边小孩比左边小孩得分高的情况 <
- 左边孩子比右边孩子得分高的情况 >
class Solution {
public int candy(int[] ratings) {
int len = ratings.length;
int[] candyNum = new int[len];
candyNum[0] = 1;
// 左边<右边 的情况 从左到右遍历 -- 从第二个小孩开始考虑
for (int i = 1; i < len; i++) {
if (ratings[i] > ratings[i-1]){
candyNum[i] = candyNum[i-1] + 1;
}else {
candyNum[i] = 1;
}
}
// 左边>右边 的情况 从右到左遍历 -- 从倒数第二个小孩开始考虑
for (int i = len - 2; i >= 0; i--) {
if (ratings[i] > ratings[i + 1]){
candyNum[i] = Math.max(candyNum[i], candyNum[i+1] + 1);
}
}
// 统计最后的糖果数
int sum = 0;
for (int i : candyNum) {
sum += i;
}
return sum;
}
}
总结
这题有点难,题都没看懂,其实是这样的:
若左>右,左就要多一个糖果;同理右>左,右也要多一个糖果。
我们要同时考虑两把的糖果数谁更大。