代码随想录算法Day34 | 1005.K次取反后最大化的数组和 ,134. 加油站 , 135. 分发糖果

1005.K次取反后最大化的数组和

题目链接:1005. K 次取反后最大化的数组和 - 力扣(LeetCode)

思路

贪心思路,局部最优:让绝对值大的负数变为正数,当前数值达到最大,整体最优:整个数组和达到最大。

当数组中的负数全部取反后,K仍然大于0时,这时又是一个贪心:局部最优:只找数值最小的正整数进行反转,当前数值和可以达到最大,全局最优:整个数组和 达到最大。

代码

 1 class Solution {
 2     public int largestSumAfterKNegations(int[] nums, int K) {
 3         // 将数组按照绝对值大小从大到小排序,注意要按照绝对值的大小
 4     nums = IntStream.of(nums)
 5              .boxed()
 6              .sorted((o1, o2) -> Math.abs(o2) - Math.abs(o1))
 7              .mapToInt(Integer::intValue).toArray();
 8     int len = nums.length;        
 9     for (int i = 0; i < len; i++) {
10         //从前向后遍历,遇到负数将其变为正数,同时K--
11         if (nums[i] < 0 && K > 0) {
12             nums[i] = -nums[i];
13             K--;
14         }
15     }
16     // 如果K还大于0,那么反复转变数值最小的元素,将K用完
17 
18     if (K % 2 == 1) nums[len - 1] = -nums[len - 1];
19     return Arrays.stream(nums).sum();
20 
21     }
22 }
 1 class Solution {
 2     public int largestSumAfterKNegations(int[] A, int K) {
 3         if (A.length == 1) return k % 2 == 0 ? A[0] : -A[0];
 4         Arrays.sort(A);
 5         int sum = 0;
 6         int idx = 0;
 7         for (int i = 0; i < K; i++) {
 8             if (i < A.length - 1 && A[idx] < 0) {
 9                 A[idx] = -A[idx];
10                 if (A[idx] >= Math.abs(A[idx + 1])) idx++;
11                 continue;
12             }
13             A[idx] = -A[idx];
14         }
15 
16         for (int i = 0; i < A.length; i++) {
17             sum += A[i];
18         }
19         return sum;
20     }
21 }

134. 加油站

题目链接:134. 加油站 - 力扣(LeetCode)

思路

暴力法

遍历每一个加油站为起点的情况,模拟一圈。

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

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

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

贪心

解法一

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

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

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

解法二

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

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

  • 情况二:rest[i] = gas[i]-cost[i]为一天剩下的油,i从0开始计算累加到最后一站,如果累加没有出现负数,说明从0出发,油就没有断过,那么0就是起点。

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

代码

 1 // 暴力法
 2 public int canCompleteCircuit(int[] gas, int[] cost) {
 3         for (int i = 0; i < gas.length; i++) {
 4             int res = gas[i] - cost[i]; // 记录剩余油量
 5             int index = (i + 1) % cost.length;
 6             while(res > 0 && index != i){ // 模拟以i为起点行驶一圈(如果有res==0,那么答案就不唯一了)
 7                 res += gas[index] - cost[index];
 8                 index = (index + 1) % cost.length;
 9             }
10             // 如果以i为起点跑一圈,剩余油量>=0,返回该起始位置
11             if (res >= 0 && index == i) return i;
12         }
13         return -1;
14     }
 1 // 解法1
 2 class Solution {
 3     public int canCompleteCircuit(int[] gas, int[] cost) {
 4         int curSum = 0;
 5         int totalSum = 0;
 6         int index = 0;
 7         for (int i = 0; i < gas.length; i++) {
 8             curSum += gas[i] - cost[i];
 9             totalSum += gas[i] - cost[i];
10             if (curSum < 0) {
11                 index = (i + 1) % gas.length ; 
12                 curSum = 0;
13             }
14         }
15         if (totalSum < 0) return -1;
16         return index;
17     }
18 }
 1 // 解法2
 2 class Solution {
 3     public int canCompleteCircuit(int[] gas, int[] cost) {
 4         int sum = 0;
 5         int min = 0;
 6         for (int i = 0; i < gas.length; i++) {
 7             sum += (gas[i] - cost[i]);
 8             min = Math.min(sum, min);
 9         }
10 
11         if (sum < 0) return -1;
12         if (min >= 0) return 0;
13 
14         for (int i = gas.length - 1; i > 0; i--) {
15             min += (gas[i] - cost[i]);
16             if (min >= 0) return i;
17         }
18 
19         return -1;
20     }
21 }

 

135. 分发糖果

题目链接:135. 分发糖果 - 力扣(LeetCode)

思路

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

  • 先从前向后遍历数组,确定右边评分大于左边的情况

  • 然后从后往前遍历数组,确定左边评分大于右边的情况

注意:

1、确定右边评分大于左边一定要从前往后遍历,而确定左边评分大于右边一定要从后往前遍历,因为是波浪式更新,前面更新的糖果对后面的比较是有影响的。

2、从后往前遍历从 length - 2 开始。

代码

 1 class Solution {
 2     /**
 3          分两个阶段
 4          1、起点下标1 从左往右,只要 右边 比 左边 大,右边的糖果=左边 + 1
 5          2、起点下标 ratings.length - 2 从右往左, 只要左边 比 右边 大,此时 左边的糖果应该 取本身的糖果数(符合比它左边大) 和 右边糖果数 + 1 二者的最大值,这样才符合 它比它左边的大,也比它右边大
 6     */
 7     public int candy(int[] ratings) {
 8         int len = ratings.length;
 9         int[] candyVec = new int[len];
10         candyVec[0] = 1;
11         for (int i = 1; i < len; i++) {
12             candyVec[i] = (ratings[i] > ratings[i - 1]) ? candyVec[i - 1] + 1 : 1;
13         }
14 
15         for (int i = len - 2; i >= 0; i--) {
16             if (ratings[i] > ratings[i + 1]) {
17                 candyVec[i] = Math.max(candyVec[i], candyVec[i + 1] + 1);
18             }
19         }
20 
21         int ans = 0;
22         for (int num : candyVec) {
23             ans += num;
24         }
25         return ans;
26     }
27 }

 

posted @ 2023-03-07 15:50  颜欢兮  阅读(12)  评论(0)    收藏  举报