Maoyl's blog Maoyl's blog
首页
  • 前端基础

    • HTML
    • CSS
    • CSS动画
    • JavaScript文章
    • stylus
  • 性能优化

    • 《性能优化》笔记
  • 学习笔记

    • 《JavaScript教程》笔记
    • 《JavaScript高级程序设计》笔记
    • 《ES6 教程》笔记
    • 《JavaScript设计模式》笔记
    • 《TypeScript 从零实现 axios》
    • TypeScript笔记
    • JS设计模式总结笔记
  • 前端框架

    • Vue相关
    • React相关
  • 前端监控

    • 前端监控简介
  • 学习笔记

    • 《Vue》笔记
    • 《React》笔记
    • 小程序学习笔记
  • 后端基础

    • Nodejs
  • 学习笔记

    • 数据结构
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • 网页性能
  • 学习笔记

    • 《Git》学习笔记
    • 《Vim》学习笔记
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

maoyln

日日行,不怕千万里
首页
  • 前端基础

    • HTML
    • CSS
    • CSS动画
    • JavaScript文章
    • stylus
  • 性能优化

    • 《性能优化》笔记
  • 学习笔记

    • 《JavaScript教程》笔记
    • 《JavaScript高级程序设计》笔记
    • 《ES6 教程》笔记
    • 《JavaScript设计模式》笔记
    • 《TypeScript 从零实现 axios》
    • TypeScript笔记
    • JS设计模式总结笔记
  • 前端框架

    • Vue相关
    • React相关
  • 前端监控

    • 前端监控简介
  • 学习笔记

    • 《Vue》笔记
    • 《React》笔记
    • 小程序学习笔记
  • 后端基础

    • Nodejs
  • 学习笔记

    • 数据结构
  • 技术文档
  • GitHub技巧
  • 博客搭建
  • 网页性能
  • 学习笔记

    • 《Git》学习笔记
    • 《Vim》学习笔记
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 第一讲 基本概念

    • 1 什么是数据结构
    • 2 什么是算法
    • 3 应用实例:最大子列和问题
      • 解体思路
      • Kadane算法优点:
      • 拓展 在线处理拓展git地址
  • 第二讲 线性结构

  • 第三讲 树(上)

  • 第四讲 树(中)

  • 第五讲 树(下)

  • 第六讲 图(上)

  • 《数据结构》学习笔记
  • 第一讲 基本概念
maoyln
2024-06-18
目录

3 应用实例:最大子列和问题

# 最大子列问题(Maximum Subarray Problem)

最大子列问题(Maximum Subarray Problem)是一个经典的算法问题,它要求找出一个序列中和最大的连续子序列。这个问题可以用不同的方法解决,包括暴力法、分治法、动态规划等,其中动态规划方法最为高效,时间复杂度为O(n)。

问题描述: 给定一个整数数组nums,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:假设我们有数组nums = [-2,1,-3,4,-1,2,1,-5,4],最大子列为[4,-1,2,1],其最大和为6。

# 解体思路

暴力法: git地址 (opens new window)

解体思路

我们需要遍历数组的所有可能子列,并计算它们的和,然后找到最大的那个和。 暴力法的时间复杂度是O(n^3),因为它涉及到三层嵌套循环:外两层循环用来确定子列的起始和结束位置,内层循环用来计算子列的和。

let list = [-2,1,-3,4,-1,2,1,-5,4];

function getMaximum(list) {
  let maximum = 0;
  let listLength = list.length;
  for (let i = 0; i < listLength; i++) {
    for (let j = i; j < listLength; j++) {
      let sum = 0
      for (let k = i; k < j; k++) {
        sum += list[k];
      }
      if (sum > maximum) maximum = sum
    }
  }
  return maximum;
}

const maximum = getMaximum(list)
console.log(maximum);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • 这段代码通过三层循环遍历数组的所有可能子列,计算每个子列的和,并不断更新最大和。
  • 请注意,虽然这种方法可以正确解决问题,但由于其低效的时间复杂度,它不适用于处理大规模数据。
  • 在实际应用中,推荐使用更高效的算法,如Kadane算法,它的时间复杂度为O(n)。

分治法:git地址 (opens new window)

解体思路

分治法是解决最大子列问题的一种有效方法,其基本思想是将一个大问题分解成小问题逐个击破。对于最大子列问题,可以将数组分成两半,分别求解左半部分和右半部分的最大子列和,以及跨越中点的最大子列和,然后取这三者中的最大值作为整个数组的最大子列和。这种方法的时间复杂度是O(n log n)。

function maxCrossingSum(arr, left, mid, right) {
  let sum = 0;
  let leftSum = 0;
  // 包括中点的左半部分的最大子列和
  for (let i = mid; i >= left; i--) {
    sum += arr[i];
    if (sum > leftSum) leftSum = sum;
  }

  sum = 0;
  let rightSum = 0;
  // 包括中点的右半部分的最大子列和
  for (let i = mid + 1; i <= right; i++) {
    sum += arr[i];
    if (sum > rightSum) rightSum = sum;
  }

  // 返回跨越中点的最大子列和
  return leftSum + rightSum;
}

function maxSubArraySum(arr, left, right) {
  // 基本情况
  if (left == right) {
    return arr[left];
  }

  // 找到中点
  let mid = Math.floor((left + right) / 2);

  // 返回以下三者中的最大值:
  // 1. 左半部分的最大子列和
  // 2. 右半部分的最大子列和
  // 3. 跨越中点的最大子列和
  return Math.max(
    maxSubArraySum(arr, left, mid),
    maxSubArraySum(arr, mid + 1, right),
    maxCrossingSum(arr, left, mid, right)
  );
}

// 示例
const list = [-2, 1, -3, 4, -1, 2, 1, -5, 4];
console.log(maxSubArraySum(list, 0, list.length - 1)); // 输出最大子列和,例如上述数组的最大子列和为6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

这段代码首先定义了maxCrossingSum函数,用于计算跨越中点的最大子列和。然后,maxSubArraySum函数通过递归地将数组分成更小的部分,分别求解左半部分和右半部分的最大子列和,以及跨越中点的最大子列和,最后取这三者中的最大值作为结果。这种分而治之的策略使得问题得以有效解决。

在线处理【动态规划(Kadane算法)】:git地址 (opens new window)

解体思路

动态规划是解决最大子列问题的一种高效方法,其中Kadane算法是最著名的实现之一。 Kadane算法的核心思想是遍历数组,同时计算当前最大子列和,如果当前最大子列和变成负数,则从下一个元素重新开始计算。这种方法的时间复杂度是O(n),是解决这个问题的最快方法之一。

基础方法

const list = [-2, 1, -3, 4, -1, 2, 1, -5, 4];
function getMaximum(list) {
  let maximum = 0;
  const l = list.length;
  let sum = 0;
  for (let i = 0; i < l; i++) {
    sum += list[i];
    if (sum < 0) {
      sum = 0;
    }
    if (sum > maximum) {
      maximum = sum;
    }
  }
  return maximum;
}

const maximum = getMaximum(list);
console.log(maximum);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

也可以用下main方法实现

function getMaximum_1(list) {
  let maximum = list[0];
  const l = list.length;
  let sum = list[0];
  for (let i = 0; i < l; i++) {
    sum = Math.max(list[i], sum + list[i]);
    maximum = Math.max(sum, maximum);
  }
  return maximum;
}

const maximum_1 = getMaximum_1(list);
console.log(maximum_1);
1
2
3
4
5
6
7
8
9
10
11
12
13

# Kadane算法优点:

  • 时间效率高:Kadane算法的时间复杂度为O(n),其中n是数组的长度。这意味着算法只需要遍历数组一次就可以找到最大子数组和,这是解决这类问题的最快方法之一。
  • 空间效率高:Kadane算法只需要O(1)的额外空间,因为它在遍历数组的过程中只维护了几个变量来存储中间计算结果,如当前元素之前的最大子数组和和全局最大子数组和。
  • 实现简单:与其他算法相比,Kadane算法的实现相对简单直观。它避免了复杂的数据结构和算法技巧,使得即使是对动态规划不太熟悉的开发者也能轻松掌握。
  • 广泛适用性:尽管Kadane算法最初是为解决最大子数组和问题设计的,但它的思想可以被扩展应用于解决其他类型的动态规划问题,比如寻找数组中的最大子序列乘积等。
  • 优雅的处理负数情况:Kadane算法能够优雅地处理全部为负数的数组。在这种情况下,它会返回单个最大负数,这在数学上是正确的处理方式,因为任何包含多个负数的子数组和都会更小。

# 拓展 在线处理拓展git地址 (opens new window)

获取最大子列和、最大子列的第一个数字和最大子列最后一个数字、最大子列

let list = [-2, 11, -4, 13, -5, -2, 12,3,4,5,-12,3,12,-13,12,4,2];

// 输出最大子列和、最大子列开始和结束的数字、最大子列
function getMaximum(list) {
  let maximum = 0;
  const l = list.length;
  let sum = 0;
  let allList = [];
  for (let i = 0; i < l; i++) {
    sum += list[i];
    if (sum < 0) {
      sum = 0;
      allList = [];
    } else {
      if (sum > maximum) {
        maximum = sum;
      }
      allList = [...allList, list[i]]
    }
  }
  const startEndList = [allList[0], allList[allList.length - 1]]

  // 输出
  return {maximum, startEndList, allList};
}

console.log(getMaximum(list));
// 结果:
// {
//  maximum: 45,
//  startEndList: [ 11, 2 ],
//  allList: [
//     11, -4, 13,  -5, -2, 12,
//      3,  4,  5, -12,  3, 12,
//    -13, 12,  4,   2
//  ]
//}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

获取最大子列和、最大子列的第一个数字和最大子列最后一个数字该方法存在问题

// 输出最大子列和,最大子列开始的数字和最大子列结束的数字
function findMaxSubsequence(list) {
  let maxSum = 0;
  let tempSum = 0;
  let start = 0;
  let tempStart = 0;
  let end = list.length - 1;

  let allNegative = true; // 假设所有数字都是负数

  // 遍历数组,寻找最大子序列和
  for (let i = 0; i < list.length; i++) {
    tempSum += list[i];

    // 检查是否所有数字都是负数
    if (list[i] > 0) {
      allNegative = false;
    }

    if (tempSum > maxSum) {
      maxSum = tempSum;
      start = tempStart;
      end = i;
    } else if (tempSum < 0) {
      tempSum = 0;
      tempStart = i + 1;
    }
  }

  // 如果所有数字都是负数,最大子序列和为0,返回整个序列的第一个和最后一个数字
  if (allNegative) {
    maxSum = 0;
    start = 0;
    end = list.length - 1;
  }

  return [maxSum, list[start], list[end]];
}

// 测试用例
console.log(findMaxSubsequence(list));
// 结果:[ 45, 11, 2 ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
正确的返回结果应该是: [0, 0, 0]
下面是代码
1
2
function isAllNonPositiveAndContainsZero(arr) {
  let containsZero = false;
  // 遍历数组中的每个元素
  for (let i = 0; i < arr.length; i++) {
    // 如果找到一个大于0的元素,返回false
    if (arr[i] > 0) {
      return false;
    }
    // 检查是否至少有一个元素是0
    if (arr[i] === 0) {
      containsZero = true;
    }
  }
  // 确保数组至少包含一个0
  return containsZero;
}

function getMaximum(list) {
  let maxSum = 0;
  let tempSum = 0;
  let start = 0;
  let tempStart = 0;
  let end = list.length - 1;

  let allNegative = true; // 假设所有数字都是负数
  if (isAllNonPositiveAndContainsZero(list)) {
    return [0, 0, 0];
  }

  // 遍历数组,寻找最大子序列和
  for (let i = 0; i < list.length; i++) {
    tempSum += list[i];

    // 检查是否所有数字都是负数
    if (list[i] > 0) {
      allNegative = false;
    }

    if (tempSum > maxSum) {
      maxSum = tempSum;
      start = tempStart;
      end = i;
    } else if (tempSum < 0) {
      tempSum = 0;
      tempStart = i + 1;
    }
  }

  // 如果所有数字都是负数,最大子序列和为0,返回整个序列的第一个和最后一个数字
  if (allNegative) {
    maxSum = 0;
    start = 0;
    end = list.length - 1;
  }

  return maxSum, list[start], list[end];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
编辑 (opens new window)
#数据结构
上次更新: 2025/04/18, 01:42:12
2 什么是算法
1 线性表及其实现

← 2 什么是算法 1 线性表及其实现→

最近更新
01
GSAP动画库——如何高效写动画
04-17
02
自适应方案PxToRem
09-10
03
性能优化-requestAnimationFrame
08-10
更多文章>
Theme by Vdoing | Copyright © 2019-2025 备案号:京ICP备19058102号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式