1005.K次取反后最大化的数组和
给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。)
以这种方式修改数组后,返回数组可能的最大和。
示例 1:
- 输入:A = [4,2,3], K = 1
- 输出:5
- 解释:选择索引 (1,) ,然后 A 变为 [4,-2,3]。
示例 2:
- 输入:A = [3,-1,0,2], K = 3
- 输出:6
- 解释:选择索引 (1, 2, 2) ,然后 A 变为 [3,1,0,2]。
示例 3:
- 输入:A = [2,-3,-1,5,-4], K = 2
- 输出:13
- 解释:选择索引 (1, 4) ,然后 A 变为 [2,3,-1,5,4]。
提示:
- 1 <= A.length <= 10000
- 1 <= K <= 10000
- -100 <= A[i] <= 100
思路:贪心
这道题目属于贪心简单题,不使用贪心算法也可以算出来。但是为了锻炼贪心算法的思维,这里使用贪心的思路
数组先进行升序排序,
首先对数组元素进行遍历,如果元素为负数,反转为整数,否则不做改动.每次反转将K–。
第一种情况 k <= nums.length
当k == 0 时,返回数组所有元素之和
第二种情况 k > nums.length
此时数组元素均 >= 0,那么找到数组中最小的元素,对最小元素k次转换即可
贪心算法局部最优解:获取当前元素最大值
贪心算法整体最优解:获取数组元素最大和时间复杂度O(nlogn) :快速排序时间复杂度O(nlogn)
空间复杂度: O(logN): java所使用的内置函数用的是快速排序需要 logN 的空间
代码如下
public int largestSumAfterKNegations(int[] nums, int k) {
if (nums == null || nums.length == 0 || k <= 0)
return 0;
Arrays.sort(nums);
int minIndex = 0;// 数组最小元素下标
int minEle = Integer.MAX_VALUE;// 数组最小元素value
for (int i = 0; i < nums.length; i++) {
if (nums[i] < 0) { // 当前元素为负数,转换为整数
nums[i] = -nums[i];
k--;
}
if (k == 0) {// 终止条件
return Arrays.stream(nums).sum();
}
if (nums[i] < minEle) {// 获取反转后数组中,最小的元素和元素下标
minEle = nums[i];
minIndex = i;
}
}
// 此时k > 0,数组元素均大于0。需要找到数组中最小的元素,并反转k次
for (int i = 0; i < k; i++) {
nums[minIndex] = -nums[minIndex];
}
// 返回数组总和
return Arrays.stream(nums).sum();
}
134.加油站
在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。
你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。
如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。
说明:
- 如果题目有解,该答案即为唯一答案。
- 输入数组均为非空数组,且长度相同。
- 输入数组中的元素均为非负数。
示例 1: 输入:
- gas = [1,2,3,4,5]
- cost = [3,4,5,1,2]
输出: 3 解释:
- 从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
- 开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
- 开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
- 开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
- 开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
- 开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
- 因此,3 可为起始索引。
示例 2: 输入:
- gas = [2,3,4]
- cost = [3,4,3]
- 输出: -1
- 解释: 你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油。开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油。开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油。你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。因此,无论怎样,你都不可能绕环路行驶一周。
思路:暴力
虽然这是暴力算法,但我就是按照贪心算法的思路去写出来的
贪心算法局部最优解:到达下一处加油站时,油箱的油 >= 0
贪心算法整体最优解:环路一圈,邮箱的油 >= 0
代码超时了,代码随想录同样方式的暴力方法也超时了时间复杂度: O(n2)
空间复杂度: O(1)
代码如下:
public static int canCompleteCircuit(int[] gas, int[] cost) {
int sum = 0;
for (int i = 0; i < gas.length; i++) {// gas数组元素作为起点,依次遍历。若遍历过程中和遍历结束后,sum均 >= 0,则返回当前索引
int length = gas.length;
int j = i;
sum = gas[j];// 从j号加油站出发
while (length > 0) {
sum = sum - cost[j];// 路径消耗后油量
if (sum < 0)// 若路径小于0,跳出while循环,继续for循环
break;
if (j == gas.length - 1) {// 对数组边缘条件处理
j = 0;
} else {
j++;
}
sum = sum + gas[j];// 到达目的地补充后的油量
length--;
}
if (sum >= 0)
return i;
}
return -1;
}
思路:贪心
暴力算法时间复杂度过高
如果总油量大于总油耗,那么总会有一条路线可以完整走完所有的加油站
如果总油量小于总油耗,那么无法走完所有加油站
单个加油站的剩余油量gas[i] - cost[i]
定义当前剩余油量为curSum,从i = 0开始计算每个加油站的剩余油量
一旦当前剩余油量curSum < 0,那么说明[0,i]区间不适合做为起始位置
因为在这个区间任意加油站出发,中途都会没油
那么起始位置从i+1算起,再从0计算curSum。
如果算完的curSum > 0,注意此时还有一种可能,那就是整个总油量减去总消耗小于0,totalSum = totalSum + gas[i] - cost[i];记录每个加油站的剩余油量
若totalSum < 0,返回-1.
如图所示
局部最优解:一旦curSum < 0,从i+1开始寻找位置
全局最优解:找到适合出发的起始位置时间复杂度: O(n)
空间复杂度: O(1)
代码如下
public static int canCompleteCircuit(int[] gas, int[] cost) {
int startPosition = 0;// 适合出发的起始位置
int curSum = 0;// 当前的剩余油量
int totalSum = 0;// 走过的所有加油站剩余油量
for (int i = 0; i < gas.length; i++) {
curSum = curSum + gas[i] - cost[i];
totalSum = totalSum + gas[i] - cost[i];//
if (curSum < 0) {// 那么说明[0,i]区间不适合做为起始位置
curSum = 0;
startPosition = i + 1;
}
}
if (totalSum < 0) {// 没有合适的路线
return -1;
}
return startPosition;
}
135.分发糖果
老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。
你需要按照以下要求,帮助老师给这些孩子分发糖果:
- 每个孩子至少分配到 1 个糖果。
- 相邻的孩子中,评分高的孩子必须获得更多的糖果。
那么这样下来,老师至少需要准备多少颗糖果呢?
示例 1:
- 输入: [1,0,2]
- 输出: 5
- 解释: 你可以分别给这三个孩子分发 2、1、2 颗糖果。
示例 2:
- 输入: [1,2,2]
- 输出: 4
- 解释: 你可以分别给这三个孩子分发 1、2、1 颗糖果。第三个孩子只得到 1 颗糖果,这已满足上述两个条件。
思路:贪心
题目要求1.每个孩子至少分配到 1 个糖果2.相邻的孩子中,评分高的孩子必须获得更多的糖果。
那么如果我们同时对一个孩子的左右相邻的孩子做比较,部分孩子会得不到应有的糖
需要对孩子的左右相邻孩子分别作比较
1.当前孩子和左孩子做比较,如果比左孩子大,那么当前孩子的糖加1。for循环从前到后
2.当前孩子和右孩子作比较,如果比右孩子大,那么当前孩子的糖为【右孩子糖数量+1和当前孩子糖数量】最大值。for循环从后到前
candy[i]只有取最大的才能既保持对左边candy[i - 1]的糖果多,也比右边candy[i + 1]的糖果多。这里需要注意一个很重要的点,就是For循环遍历的顺序不要搞错
遍历顺序这里有同学可能会有疑问,当前孩子和右孩子作比较,为什么不能从前向后遍历呢?
因为 rating[5]与rating[4]的比较 要利用上 rating[5]与rating[6]的比较结果,所以 要从后向前遍历。
如果从前向后遍历,rating[5]与rating[4]的比较 就不能用上 rating[5]与rating[6]的比较结果了 。如图:
所以确定当前孩子和右孩子作比较的情况一定要从后向前遍历!
同理 当前孩子和左孩子做比较 。for循环不能从后到前遍历for循环从前到后:当前孩子和左孩子做比较
贪心局部最优解:当前孩子比左孩子大,那么就比左孩子的糖果多一个
for循环从后到前:当前孩子和右孩子做比较
贪心局部最优解:当前孩子和右孩子作比较,如果比右孩子大,那么当前孩子的糖为【右孩子糖数量+1和当前孩子糖数量】最大值。
贪心算法整体最优解:得到最少的糖果数量
代码如下
public int candy(int[] ratings) {
if (ratings == null || ratings.length == 0)
return 0;
int candySum = 0;
// 定义数组存储每个孩子的糖果数量。数组默认值为1
int[] candy = new int[ratings.length];
for (int i = 0; i < ratings.length; i++) {
candy[i] = 1;
}
for (int i = 1; i < ratings.length; i++) {// 当前孩子和左孩子做比较,如果比左孩子大,那么当前孩子的糖加1。for循环从前到后
if (ratings[i] > ratings[i - 1]) {
candy[i] = candy[i - 1] + 1;
}
}
for (int i = ratings.length - 2; i >= 0; i--) {// 当前孩子和右孩子作比较,如果比右孩子大,那么当前孩子的糖为【右孩子糖数量+1和当前孩子糖数量】最大值。for循环从后到前
if (ratings[i] > ratings[i + 1]) {
candy[i] = Math.max(candy[i + 1] + 1,candy[i]);
}
}
// 返回糖果总数
for (int i = 0; i < ratings.length; i++) {
candySum = candySum + candy[i];
}
return candySum;
}