leetcode-master/problems/0053.最大子序和.md

374 lines
10 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.

<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="https://code-thinking-1253855093.file.myqcloud.com/pics/20210924105952.png" width="1000"/>
</a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
# 53. 最大子序和
[力扣题目链接](https://leetcode.cn/problems/maximum-subarray/)
给定一个整数数组 nums 找到一个具有最大和的连续子数组子数组最少包含一个元素返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大 6。
## 暴力解法
暴力解法的思路第一层for 就是设置起始位置第二层for循环遍历数组寻找最大值
* 时间复杂度O(n^2)
* 空间复杂度O(1)
```CPP
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int result = INT32_MIN;
int count = 0;
for (int i = 0; i < nums.size(); i++) { // 设置起始位置
count = 0;
for (int j = i; j < nums.size(); j++) { // 每次从起始位置i开始遍历寻找最大值
count += nums[j];
result = count > result ? count : result;
}
}
return result;
}
};
```
以上暴力的解法C++勉强可以过,其他语言就不确定了。
## 贪心解法
**贪心贪的是哪里呢?**
如果 -2 1 在一起计算起点的时候一定是从1开始计算因为负数只会拉低总和这就是贪心贪的地方
局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。
全局最优:选取最大“连续和”
**局部最优的情况下,并记录最大的“连续和”,可以推出全局最优**
从代码角度上来讲遍历nums从头开始用count累积如果count一旦加上nums[i]变为负数那么就应该从nums[i+1]开始从0累积count了因为已经变为负数的count只会拖累总和。
**这相当于是暴力解法中的不断调整最大子序和区间的起始位置**
**那有同学问了,区间终止位置不用调整么? 如何才能得到最大“连续和”呢?**
区间的终止位置其实就是如果count取到最大值了及时记录下来了。例如如下代码
```
if (count > result) result = count;
```
**这样相当于是用result记录最大子序和区间和变相的算是调整了终止位置**
如动画所示:
![53.最大子序和](https://code-thinking.cdn.bcebos.com/gifs/53.%E6%9C%80%E5%A4%A7%E5%AD%90%E5%BA%8F%E5%92%8C.gif)
红色的起始位置就是贪心每次取count为正数的时候开始一个区间的统计。
那么不难写出如下C++代码(关键地方已经注释)
```CPP
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int result = INT32_MIN;
int count = 0;
for (int i = 0; i < nums.size(); i++) {
count += nums[i];
if (count > result) { // 取区间累计的最大值(相当于不断确定最大子序终止位置)
result = count;
}
if (count <= 0) count = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和
}
return result;
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
当然题目没有说如果数组为空,应该返回什么,所以数组为空的话返回啥都可以了。
不少同学认为 如果输入用例都是-1或者 都是负数这个贪心算法跑出来的结果是0 这是**又一次证明脑洞模拟不靠谱的经典案例**,建议大家把代码运行一下试一试,就知道了,也会理解 为什么 result 要初始化为最小负数了。
## 动态规划
当然本题还可以用动态规划来做,当前[「代码随想录」](https://img-blog.csdnimg.cn/20201124161234338.png)主要讲解贪心系列后续到动态规划系列的时候会详细讲解本题的dp方法。
那么先给出我的dp代码如下有时间的录友可以提前做一做
```CPP
class Solution {
public:
int maxSubArray(vector<int>& nums) {
if (nums.size() == 0) return 0;
vector<int> dp(nums.size(), 0); // dp[i]表示包括i之前的最大连续子序列和
dp[0] = nums[0];
int result = dp[0];
for (int i = 1; i < nums.size(); i++) {
dp[i] = max(dp[i - 1] + nums[i], nums[i]); // 状态转移公式
if (dp[i] > result) result = dp[i]; // result 保存dp[i]的最大值
}
return result;
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(n)
## 总结
本题的贪心思路其实并不好想,这也进一步验证了,别看贪心理论很直白,有时候看似是常识,但贪心的题目一点都不简单!
后续将介绍的贪心题目都挺难的,哈哈,所以贪心很有意思,别小看贪心!
## 其他语言版本
### Java
```java
class Solution {
public int maxSubArray(int[] nums) {
if (nums.length == 1){
return nums[0];
}
int sum = Integer.MIN_VALUE;
int count = 0;
for (int i = 0; i < nums.length; i++){
count += nums[i];
sum = Math.max(sum, count); // 取区间累计的最大值(相当于不断确定最大子序终止位置)
if (count <= 0){
count = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和
}
}
return sum;
}
}
```
```java
// DP 方法
class Solution {
public int maxSubArray(int[] nums) {
int ans = Integer.MIN_VALUE;
int[] dp = new int[nums.length];
dp[0] = nums[0];
ans = dp[0];
for (int i = 1; i < nums.length; i++){
dp[i] = Math.max(dp[i-1] + nums[i], nums[i]);
ans = Math.max(dp[i], ans);
}
return ans;
}
}
```
### Python
```python
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
result = -float('inf')
count = 0
for i in range(len(nums)):
count += nums[i]
if count > result:
result = count
if count <= 0:
count = 0
return result
```
### Go
```go
func maxSubArray(nums []int) int {
maxSum := nums[0]
for i := 1; i < len(nums); i++ {
if nums[i] + nums[i-1] > nums[i] {
nums[i] += nums[i-1]
}
if nums[i] > maxSum {
maxSum = nums[i]
}
}
return maxSum
}
```
### Rust
```rust
pub fn max_sub_array(nums: Vec<i32>) -> i32 {
let mut max_sum = i32::MIN;
let mut curr = 0;
for n in nums.iter() {
curr += n;
max_sum = max_sum.max(curr);
curr = curr.max(0);
}
max_sum
}
```
### Javascript:
```Javascript
var maxSubArray = function(nums) {
let result = -Infinity
let count = 0
for(let i = 0; i < nums.length; i++) {
count += nums[i]
if(count > result) {
result = count
}
if(count < 0) {
count = 0
}
}
return result
};
```
### C:
贪心:
```c
int maxSubArray(int* nums, int numsSize){
int maxVal = INT_MIN;
int subArrSum = 0;
int i;
for(i = 0; i < numsSize; ++i) {
subArrSum += nums[i];
// 若当前局部和大于之前的最大结果,对结果进行更新
maxVal = subArrSum > maxVal ? subArrSum : maxVal;
// 若当前局部和为负对结果无益。则从nums[i+1]开始应重新计算。
subArrSum = subArrSum < 0 ? 0 : subArrSum;
}
return maxVal;
}
```
动态规划:
```c
/**
* 解题思路:动态规划:
* 1. dp数组dp[i]表示从0到i的子序列中最大序列和的值
* 2. 递推公式dp[i] = max(dp[i-1] + nums[i], nums[i])
若dp[i-1]<0对最后结果无益。dp[i]则为nums[i]。
* 3. dp数组初始化dp[0]的最大子数组和为nums[0]
* 4. 推导顺序:从前往后遍历
*/
#define max(a, b) (((a) > (b)) ? (a) : (b))
int maxSubArray(int* nums, int numsSize){
int dp[numsSize];
// dp[0]最大子数组和为nums[0]
dp[0] = nums[0];
// 若numsSize为1应直接返回nums[0]
int subArrSum = nums[0];
int i;
for(i = 1; i < numsSize; ++i) {
dp[i] = max(dp[i - 1] + nums[i], nums[i]);
// 若dp[i]大于之前记录的最大值,进行更新
if(dp[i] > subArrSum)
subArrSum = dp[i];
}
return subArrSum;
}
```
### TypeScript
**贪心**
```typescript
function maxSubArray(nums: number[]): number {
let curSum: number = 0;
let resMax: number = -Infinity;
for (let i = 0, length = nums.length; i < length; i++) {
curSum += nums[i];
resMax = Math.max(curSum, resMax);
if (curSum < 0) curSum = 0;
}
return resMax;
};
```
**动态规划**
```typescript
// 动态规划
function maxSubArray(nums: number[]): number {
const length = nums.length;
if (length === 0) return 0;
const dp: number[] = [];
dp[0] = nums[0];
let resMax: number = nums[0];
for (let i = 1; i < length; i++) {
dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
resMax = Math.max(resMax, dp[i]);
}
return resMax;
};
```
### Scala
**贪心**
```scala
object Solution {
def maxSubArray(nums: Array[Int]): Int = {
var result = Int.MinValue
var count = 0
for (i <- nums.indices) {
count += nums(i) // count累加
if (count > result) result = count // 记录最大值
if (count <= 0) count = 0 // 一旦count为负则count归0
}
result
}
}
```
**动态规划**
```scala
object Solution {
def maxSubArray(nums: Array[Int]): Int = {
var dp = new Array[Int](nums.length)
var result = nums(0)
dp(0) = nums(0)
for (i <- 1 until nums.length) {
dp(i) = math.max(nums(i), dp(i - 1) + nums(i))
result = math.max(result, dp(i)) // 更新最大值
}
result
}
}
```
-----------------------
<div align="center"><img src=https://code-thinking.cdn.bcebos.com/pics/01二维码一.jpg width=500> </img></div>