一、题目描述------最大子数组之和
给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组是数组中的一个连续部分。
示例 1:
ini
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
ini
输入: nums = [1]
输出: 1
示例 3:
ini
输入: nums = [5,4,-1,7,8]
输出: 23
提示:
1 <= nums.length <= 105
-104 <= nums[i] <= 104
进阶: 如果你已经实现复杂度为 O(n)
的解法,尝试使用更为精妙的 分治法 求解。
二、题解
js
/**
* @param {number[]} nums
* @return {number}
*/
var maxSubArray = function(nums) {
// 初始化最大和和当前和,都设置为数组的第一个元素
let maxSoFar = nums[0];
let currentMax = nums[0];
// 遍历数组,从第二个元素开始
for (let i = 1; i < nums.length; i++) {
// 关键步骤:决定是延续之前的子数组,还是从当前元素开始一个新的子数组
// 如果之前的子数组(currentMax)加上当前元素比当前元素本身还小,
// 说明之前的子数组是负收益,不如直接从当前元素开始。
currentMax = Math.max(nums[i], currentMax + nums[i]);
// 更新全局最大和,取当前最大和与之前最大和的较大值
maxSoFar = Math.max(maxSoFar, currentMax);
}
// 返回找到的最大和
return maxSoFar;
};
核心思想
Kadane 算法的核心思想是使用动态规划,以线性时间复杂度(O(n))解决最大子数组和问题。它通过迭代数组,在每一步维护两个变量:
currentMax
: 表示以当前元素结尾的连续子数组的最大和。maxSoFar
: 表示到目前为止找到的最大子数组和(全局最大和)。
算法的关键在于每一步如何更新 currentMax
。我们要做出一个决定:是以当前元素 nums[i]
重新开始一个新的子数组,还是将当前元素添加到之前的子数组中。 这个决定基于哪个选择能带来更大的和。
详细解析
-
初始化:
maxSoFar = nums[0]
: 初始时,假设最大的子数组就是第一个元素本身。currentMax = nums[0]
: 初始时,也假设以第一个元素结尾的子数组的最大和就是它本身。
-
循环遍历:
-
从数组的第二个元素开始迭代
(i = 1)
。 -
currentMax = Math.max(nums[i], currentMax + nums[i]);
(关键步骤) :nums[i]
: 如果nums[i]
比currentMax + nums[i]
大,这意味着以nums[i]
重新开始一个新的子数组更好。之前的子数组的和是负数(或者 0),拖累了结果,所以放弃。currentMax + nums[i]
: 如果currentMax + nums[i]
比nums[i]
大,这意味着将nums[i]
添加到之前的子数组中是更有利的。之前的子数组至少带来了一些正向的贡献。Math.max()
: 选取两者之间的较大值,更新currentMax
,使其始终表示 以当前元素结尾的最大的子数组和。
-
maxSoFar = Math.max(maxSoFar, currentMax);
:- 更新全局最大和
maxSoFar
。在每一步中,检查当前的currentMax
是否大于之前找到的maxSoFar
。如果大于,则更新maxSoFar
。
- 更新全局最大和
-
-
返回:
- 循环结束后,
maxSoFar
存储的就是整个数组中最大子数组的和。
- 循环结束后,
注意事项
-
空数组或 null 数组: 尽管代码没有显式地检查
nums
是否为null
或空数组,但如果真是这种情况,代码会抛出错误(访问nums[0]
时)。 在实际应用中,建议首先添加一个检查:iniif (!nums || nums.length === 0) { return 0; // 或者抛出错误 }
-
数组全为负数: 算法可以正确处理数组中的所有元素都是负数的情况。 在这种情况下,
maxSoFar
将会是数组中最大的负数(绝对值最小的负数)。 这是因为初始化时maxSoFar = nums[0]
。 -
理解
currentMax
的含义至关重要: 不要将currentMax
视为到目前为止的最大和。 它是 以当前元素结尾的 最大子数组和。 这是算法能够以线性时间复杂度工作的关键。 -
空间复杂度: Kadane 算法具有 O(1) 的空间复杂度,因为它只需要几个常数级的变量(
maxSoFar
,currentMax
,i
)。 -
适用性: Kadane 算法专门用于一维数组的最大子数组和问题。 如果需要解决二维数组或更复杂情景下的最大子数组问题,需要使用其他算法。
实例与展示



HTML动态可交互题解(直接运行即可)
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>最大子数组和可视化</title>
<style>
body {
font-family: 'Microsoft YaHei', sans-serif;
margin: 20px;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
}
.array-container {
display: flex;
margin-bottom: 20px;
}
.array-element {
width: 60px;
height: 60px;
border: 1px solid #333;
display: flex;
justify-content: center;
align-items: center;
margin-right: 5px;
font-weight: bold;
position: relative;
}
.current {
background-color: #ffeb3b;
}
.max-so-far {
background-color: #4caf50;
color: white;
}
.controls {
margin: 20px 0;
}
button {
padding: 8px 16px;
margin: 0 5px;
cursor: pointer;
}
.explanation {
margin-top: 20px;
padding: 15px;
border: 1px solid #ddd;
background-color: #f9f9f9;
max-width: 600px;
}
.variables {
display: flex;
justify-content: center;
margin-bottom: 20px;
}
.variable {
margin: 0 15px;
padding: 10px;
border: 1px solid #333;
min-width: 100px;
text-align: center;
}
.variable-name {
font-weight: bold;
margin-bottom: 5px;
}
.variable-value {
font-size: 18px;
}
</style>
</head>
<body>
<div class="container">
<h1>最大子数组和可视化</h1>
<div class="variables">
<div class="variable">
<div class="variable-name">当前最大值</div>
<div class="variable-value" id="currentMaxVal">0</div>
</div>
<div class="variable">
<div class="variable-name">全局最大值</div>
<div class="variable-value" id="maxSoFarVal">0</div>
</div>
</div>
<div class="array-container" id="arrayContainer"></div>
<div class="controls">
<button id="prevBtn">上一步</button>
<button id="nextBtn">下一步</button>
<button id="resetBtn">重置</button>
<button id="autoBtn">自动运行</button>
</div>
<div class="explanation" id="explanation">
<p><strong>初始化:</strong> 将当前最大值和全局最大值都设为第一个元素 (nums[0] = -2)</p>
</div>
</div>
<script>
// 示例数组 (可以修改)
const nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4];
let currentStep = 0;
let autoInterval = null;
// 初始化数组可视化
function initArray() {
const container = document.getElementById('arrayContainer');
container.innerHTML = '';
nums.forEach((num, index) => {
const element = document.createElement('div');
element.className = 'array-element';
element.textContent = num;
element.id = `element-${index}`;
container.appendChild(element);
});
updateVisualization();
}
// 根据当前步骤更新可视化
function updateVisualization() {
// 重置所有高亮
document.querySelectorAll('.array-element').forEach(el => {
el.classList.remove('current', 'max-so-far');
});
// 更新变量显示
document.getElementById('currentMaxVal').textContent = getCurrentMax();
document.getElementById('maxSoFarVal').textContent = getMaxSoFar();
if (currentStep === 0) {
// 初始状态
document.getElementById('element-0').classList.add('current', 'max-so-far');
document.getElementById('explanation').innerHTML =
'<p><strong>初始化:</strong> 将当前最大值和全局最大值都设为第一个元素 (nums[0] = ' + nums[0] + ')</p>';
} else if (currentStep <= nums.length) {
const i = currentStep - 1;
const element = document.getElementById(`element-${i}`);
element.classList.add('current');
const prevCurrentMax = i > 0 ? getCurrentMaxForStep(i-1) : nums[0];
const newCurrentMax = Math.max(nums[i], prevCurrentMax + nums[i]);
const newMaxSoFar = Math.max(getMaxSoFarForStep(i-1), newCurrentMax);
document.getElementById('explanation').innerHTML = `
<p><strong>第 ${currentStep} 步:</strong> 处理 nums[${i}] = ${nums[i]}</p>
<p>当前最大值 = max(nums[${i}], 当前最大值 + nums[${i}]) = max(${nums[i]}, ${prevCurrentMax} + ${nums[i]}) = ${newCurrentMax}</p>
<p>全局最大值 = max(全局最大值, 当前最大值) = max(${getMaxSoFarForStep(i-1)}, ${newCurrentMax}) = ${newMaxSoFar}</p>
`;
if (newCurrentMax === nums[i]) {
document.getElementById('explanation').innerHTML +=
'<p>从这个元素开始新的子数组,因为它比继续之前的子数组更好</p>';
}
// 高亮当前最大子数组
let start = i;
let sum = 0;
while (start >= 0) {
sum += nums[start];
if (sum === newCurrentMax) {
for (let j = start; j <= i; j++) {
document.getElementById(`element-${j}`).classList.add('max-so-far');
}
break;
}
start--;
}
} else {
document.getElementById('explanation').innerHTML =
'<p><strong>完成:</strong> 最大子数组和是 ' + getMaxSoFar() + '</p>';
}
}
// 辅助函数获取特定步骤的值
function getCurrentMaxForStep(step) {
if (step < 0) return nums[0];
let currentMax = nums[0];
let maxSoFar = nums[0];
for (let i = 1; i <= step; i++) {
currentMax = Math.max(nums[i], currentMax + nums[i]);
maxSoFar = Math.max(maxSoFar, currentMax);
}
return currentMax;
}
function getMaxSoFarForStep(step) {
if (step < 0) return nums[0];
let currentMax = nums[0];
let maxSoFar = nums[0];
for (let i = 1; i <= step; i++) {
currentMax = Math.max(nums[i], currentMax + nums[i]);
maxSoFar = Math.max(maxSoFar, currentMax);
}
return maxSoFar;
}
function getCurrentMax() {
return getCurrentMaxForStep(currentStep - 1);
}
function getMaxSoFar() {
return getMaxSoFarForStep(currentStep - 1);
}
// 事件监听
document.getElementById('nextBtn').addEventListener('click', () => {
if (currentStep < nums.length + 1) {
currentStep++;
updateVisualization();
}
});
document.getElementById('prevBtn').addEventListener('click', () => {
if (currentStep > 0) {
currentStep--;
updateVisualization();
}
});
document.getElementById('resetBtn').addEventListener('click', () => {
currentStep = 0;
if (autoInterval) {
clearInterval(autoInterval);
autoInterval = null;
}
updateVisualization();
});
document.getElementById('autoBtn').addEventListener('click', () => {
if (autoInterval) {
clearInterval(autoInterval);
autoInterval = null;
return;
}
autoInterval = setInterval(() => {
if (currentStep >= nums.length + 1) {
clearInterval(autoInterval);
autoInterval = null;
return;
}
currentStep++;
updateVisualization();
}, 1500);
});
// 初始化
initArray();
</script>
</body>
</html>
三、结语
再见!