Files
leetcode-master/problems/1005.K次取反后最大化的数组和.md
2025-03-17 10:52:41 +08:00

378 lines
11 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
* [做项目多个C++、Java、Go、测开、前端项目](https://www.programmercarl.com/other/kstar.html)
* [刷算法(两个月高强度学算法)](https://www.programmercarl.com/xunlian/xunlianying.html)
* [背八股40天挑战高频面试题](https://www.programmercarl.com/xunlian/bagu.html)
# 1005.K次取反后最大化的数组和
[力扣题目链接](https://leetcode.cn/problems/maximize-sum-of-array-after-k-negations/)
给定一个整数数组 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
## 算法公开课
**[《代码随想录》算法视频公开课](https://programmercarl.com/other/gongkaike.html)[贪心算法这不就是常识还能叫贪心LeetCode1005.K次取反后最大化的数组和](https://www.bilibili.com/video/BV138411G7LY),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
## 思路
本题思路其实比较好想了,如何可以让数组和最大呢?
贪心的思路,局部最优:让绝对值大的负数变为正数,当前数值达到最大,整体最优:整个数组和达到最大。
局部最优可以推出全局最优。
那么如果将负数都转变为正数了K依然大于0此时的问题是一个有序正整数序列如何转变K次正负让 数组和 达到最大。
那么又是一个贪心:局部最优:只找数值最小的正整数进行反转,当前数值和可以达到最大(例如正整数数组{5, 3, 1}反转1 得到-1 比 反转5得到的-5 大多了),全局最优:整个 数组和 达到最大。
虽然这道题目大家做的时候可能都不会去想什么贪心算法一鼓作气就AC了。
**我这里其实是为了给大家展现出来 经常被大家忽略的贪心思路,这么一道简单题,就用了两次贪心!**
那么本题的解题步骤为:
* 第一步:将数组按照绝对值大小从大到小排序,**注意要按照绝对值的大小**
* 第二步从前向后遍历遇到负数将其变为正数同时K--
* 第三步如果K还大于0那么反复转变数值最小的元素将K用完
* 第四步:求和
对应C++代码如下:
```CPP
class Solution {
static bool cmp(int a, int b) {
return abs(a) > abs(b);
}
public:
int largestSumAfterKNegations(vector<int>& A, int K) {
sort(A.begin(), A.end(), cmp); // 第一步
for (int i = 0; i < A.size(); i++) { // 第二步
if (A[i] < 0 && K > 0) {
A[i] *= -1;
K--;
}
}
if (K % 2 == 1) A[A.size() - 1] *= -1; // 第三步
int result = 0;
for (int a : A) result += a; // 第四步
return result;
}
};
```
* 时间复杂度: O(nlogn)
* 空间复杂度: O(1)
## 总结
贪心的题目如果简单起来,会让人简单到开始怀疑:本来不就应该这么做么?这也算是算法?我认为这不是贪心?
本题其实很简单,不会贪心算法的同学都可以做出来,但是我还是全程用贪心的思路来讲解。
因为贪心的思考方式一定要有!
**如果没有贪心的思考方式(局部最优,全局最优),很容易陷入贪心简单题凭感觉做,贪心难题直接不会做,其实这样就锻炼不了贪心的思考方式了**
所以明知道是贪心简单题,也要靠贪心的思考方式来解题,这样对培养解题感觉很有帮助。
## 其他语言版本
### Java
```java
class Solution {
public int largestSumAfterKNegations(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();
}
}
// 版本二排序数组并贪心地尽可能将负数翻转为正数再根据剩余的k值调整最小元素的符号从而最大化数组的总和。
class Solution {
public int largestSumAfterKNegations(int[] nums, int k) {
if (nums.length == 1) return nums[0];
// 排序:先把负数处理了
Arrays.sort(nums);
for (int i = 0; i < nums.length && k > 0; i++) { // 贪心点, 通过负转正, 消耗尽可能多的k
if (nums[i] < 0) {
nums[i] = -nums[i];
k--;
}
}
// 退出循环, k > 0 || k < 0 (k消耗完了不用讨论)
if (k % 2 == 1) { // k > 0 && k is odd对于负数负-正-负-正
Arrays.sort(nums); // 再次排序得到剩余的负数,或者最小的正数
nums[0] = -nums[0];
}
// k > 0 && k is evenflip数字不会产生影响: 对于负数: 负-正-负;对于正数:正-负-正
int sum = 0;
for (int num : nums) { // 计算最大和
sum += num;
}
return sum;
}
}
```
### Python
贪心
```python
class Solution:
def largestSumAfterKNegations(self, A: List[int], K: int) -> int:
A.sort(key=lambda x: abs(x), reverse=True) # 第一步按照绝对值降序排序数组A
for i in range(len(A)): # 第二步执行K次取反操作
if A[i] < 0 and K > 0:
A[i] *= -1
K -= 1
if K % 2 == 1: # 第三步如果K还有剩余次数将绝对值最小的元素取反
A[-1] *= -1
result = sum(A) # 第四步计算数组A的元素和
return result
```
### Go
```Go
func largestSumAfterKNegations(nums []int, K int) int {
sort.Slice(nums, func(i, j int) bool {
return math.Abs(float64(nums[i])) > math.Abs(float64(nums[j]))
})
for i := 0; i < len(nums); i++ {
if K > 0 && nums[i] < 0 {
nums[i] = -nums[i]
K--
}
}
if K%2 == 1 {
nums[len(nums)-1] = -nums[len(nums)-1]
}
result := 0
for i := 0; i < len(nums); i++ {
result += nums[i]
}
return result
}
```
### JavaScript
```Javascript
var largestSumAfterKNegations = function(nums, k) {
nums.sort((a,b) => Math.abs(b) - Math.abs(a))
for(let i = 0 ;i < nums.length; i++){
if(nums[i] < 0 && k > 0){
nums[i] = - nums[i];
k--;
}
}
// 若k还大于0,则寻找最小的数进行不断取反
while( k > 0 ){
nums[nums.length-1] = - nums[nums.length-1]
k--;
}
// 使用箭头函数的隐式返回值时,需使用简写省略花括号,否则要在 a + b 前加上 return
return nums.reduce((a, b) => a + b)
};
// 版本二 (优化: 一次遍历)
var largestSumAfterKNegations = function(nums, k) {
nums.sort((a, b) => Math.abs(b) - Math.abs(a)); // 排序
let sum = 0;
for(let i = 0; i < nums.length; i++) {
if(nums[i] < 0 && k-- > 0) { // 负数取反k 数量足够时)
nums[i] = -nums[i];
}
sum += nums[i]; // 求和
}
if(k % 2 > 0) { // k 有多余的k若消耗完则应为 -1
sum -= 2 * nums[nums.length - 1]; // 减去两倍的最小值(因为之前加过一次)
}
return sum;
};
```
### Rust
```Rust
impl Solution {
pub fn largest_sum_after_k_negations(mut nums: Vec<i32>, mut k: i32) -> i32 {
nums.sort_by_key(|b| std::cmp::Reverse(b.abs()));
for v in nums.iter_mut() {
if *v < 0 && k > 0 {
*v *= -1;
k -= 1;
}
}
if k % 2 == 1 {
*nums.last_mut().unwrap() *= -1;
}
nums.iter().sum()
}
}
```
### C
```c
#define abs(a) (((a) > 0) ? (a) : (-(a)))
// 对数组求和
int sum(int *nums, int numsSize) {
int sum = 0;
int i;
for(i = 0; i < numsSize; ++i) {
sum += nums[i];
}
return sum;
}
int cmp(const void* v1, const void* v2) {
return abs(*(int*)v2) - abs(*(int*)v1);
}
int largestSumAfterKNegations(int* nums, int numsSize, int k){
qsort(nums, numsSize, sizeof(int), cmp);
int i;
for(i = 0; i < numsSize; ++i) {
// 遍历数组,若当前元素<0则将当前元素转变k--
if(nums[i] < 0 && k > 0) {
nums[i] *= -1;
--k;
}
}
// 若遍历完数组后k还有剩余此时所有元素应均为正则将绝对值最小的元素nums[numsSize - 1]变为负
if(k % 2 == 1)
nums[numsSize - 1] *= -1;
return sum(nums, numsSize);
}
```
### TypeScript
```typescript
function largestSumAfterKNegations(nums: number[], k: number): number {
nums.sort((a, b) => Math.abs(b) - Math.abs(a));
let curIndex: number = 0;
const length = nums.length;
while (curIndex < length && k > 0) {
if (nums[curIndex] < 0) {
nums[curIndex] *= -1;
k--;
}
curIndex++;
}
while (k > 0) {
nums[length - 1] *= -1;
k--;
}
return nums.reduce((pre, cur) => pre + cur, 0);
};
```
### Scala
```scala
object Solution {
def largestSumAfterKNegations(nums: Array[Int], k: Int): Int = {
var num = nums.sortWith(math.abs(_) > math.abs(_))
var kk = k // 因为k是不可变量所以要赋值给一个可变量
for (i <- num.indices) {
if (num(i) < 0 && kk > 0) {
num(i) *= -1 // 取反
kk -= 1
}
}
// kk对2取余结果为0则为偶数不需要取反结果为1为奇数只需要对最后的数字进行反转就可以
if (kk % 2 == 1) num(num.size - 1) *= -1
num.sum // 最后返回数字的和
}
}
```
### C#
```csharp
public class Solution
{
public int LargestSumAfterKNegations(int[] nums, int k)
{
int res = 0;
Array.Sort(nums, (a, b) => Math.Abs(b) - Math.Abs(a));
for (int i = 0; i < nums.Length; i++)
{
if (nums[i] < 0 && k > 0)
{
nums[i] *= -1;
k--;
}
}
if (k % 2 == 1) nums[nums.Length - 1] *= -1;
foreach (var item in nums) res += item;
return res;
}
}
```