leetcode:1005. K 次取反后最大化的数组和 - 力扣(LeetCode)
题目
给你一个整数数组 nums
和一个整数 k
,按以下方法修改该数组:
- 选择某个下标
i
并将nums[i]
替换为-nums[i]
。
重复这个过程恰好 k
次。可以多次选择同一个下标 i
。
以这种方式修改数组后,返回数组 可能的最大和 。
示例 1:
输入:nums = [4,2,3], k = 1 输出:5 解释:选择下标 1 ,nums 变为 [4,-2,3] 。
示例 2:
输入:nums = [3,-1,0,2], k = 3 输出:6 解释:选择下标 (1, 2, 2) ,nums 变为 [3,1,0,2] 。
示例 3:
输入:nums = [2,-3,-1,5,-4], k = 2 输出:13 解释:选择下标 (1, 4) ,nums 变为 [2,3,-1,5,4] 。
提示:
1 <= nums.length <= 104
-100 <= nums[i] <= 100
1 <= k <= 104
思路
要求最后的累加和最大,我们需要尽可能地把负数变为正数,如果没有负数了,我们就把最小的那个非负数(可能是0,也可能是正数),一直给他反转,直到k全部用完。
1、力扣评论区老哥给的解法
每sort一次,数组变成了从小到大的排序,然后把nums[0]反转,然后再sort,再反转,一直把k用完,代码如下:
// -------------------------------方法1:k次sort-------------------------------
class Solution
{
public:
int largestSumAfterKNegations(vector<int> &nums, int k)
{
while (k)
{
sort(nums.begin(), nums.end());
nums[0] *= -1;
k--;
}
int sum = 0;
for (int num : nums)
{
sum += num;
}
return sum;
}
};
这用了k次sort,时间复杂度非常高,当然感谢老哥给的暴力思路。
2、我自己做的
整体思路跟老哥一样,但是我只用了两次sort:
我先sort一次,然后尽可能把负数给反转,然后再sort一次,这时候最小的一定是nums[0],然后剩下的次数全部用来反转nums[0]就可以了。
// -------------------------------方法2:2次sort-------------------------------
class Solution
{
public:
int largestSumAfterKNegations(vector<int> &nums, int k)
{
// 对数组进行升序排序,以便优先处理负数
sort(nums.begin(), nums.end());
int index = 0;
// 遍历数组,将负数取反,直到没有负数或取反次数用尽
for (int i = 0; i < nums.size(); i++)
{
if (nums[i] < 0 && k > 0)
{
nums[i] = -nums[i];
k--;
index++;
}
}
// 如果剩余的取反次数为奇数,对数组中最小的元素进行一次取反
if (k % 2 == 1)
{
sort(nums.begin(), nums.end());
nums[0] = -nums[0];
}
// 计算数组元素的总和
int sum = 0;
for (int i = 0; i < nums.size(); i++)
{
sum += nums[i];
}
return sum;
}
};
我这里只用了两次sort,第2个sort是因为前面的负数变正之后,数组里面的大小顺序都变了,我们还要找出其中的最小值。
3、随想录解法
这里是定义了一个比较函数cmp,是按照绝对值的大小进行从大到小的排序,这样不管我们前面怎么反转,nums的最后那个数一定是最小的,如果此时k还有多余的,就一直对最后那个数进行反转(当然代码里面并不是一直*(-1),而是用看剩下的k是奇数还是偶数,这里的优化我之前是没想到的!)
// -------------------------------方法3:绝对值排序-------------------------------
class Solution
{
static bool cmp(int a, int b)
{
return abs(a) > abs(b);
}
public:
int largestSumAfterKNegations(vector<int> &nums, int k)
{
sort(nums.begin(), nums.end(), cmp);
for (int i = 0; i < nums.size(); i++)
{
if (nums[i] < 0 && k > 0)
{
nums[i] *= -1;
k--;
}
}
if (k % 2 == 1)
nums[nums.size() - 1] *= -1;
int result = 0;
for (int num : nums)
{
result += num;
}
return result;
}
};
总结
这个题很容易想到,但是怎么体现出贪心策略呢?
第一次贪心
局部最优:让绝对值大的负数变为正数,当前数值达到最大。
整体最优:整个数组和达到最大。
第二次贪心
局部最优:只找数值最小的正整数进行反转,当前数值和可以达到最大(例如正整数数组{5, 3, 1},反转1 得到-1 比 反转5得到的-5 大多了)。
全局最优:整个 数组和 达到最大。