数据结构与算法 -- 动态规划子数组问题

一、子数组问题

如果一道题目给定的输入是一个数组,那么满足以下条件的问题就是动归子数组问题:

1、问题符合动归典型特征

2、题目的答案是题设数组的子数组,或者来源于子数组。 

二、回文子串个数 

1、问题描述

给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。 


示例1:

输入:"dp"
输出:2
解释:共有两个回文子串,分别为 "d", "p"。


示例2:

输入:"aaa"
输出:6
解释:共有六个回文子串,分别为 "a", "a", "a", "aa", "aa", "aaa"。注意题设,具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串,因此像 "aa" 和 "aa" 就是两个不同的回文子串。

 2、算法分析

1)初始状态

        从问题的示例就可以看出(当然也很容易想到),单个字符一定是它自己的回文。 

2)状态参数

        由于我们需要在整个字符串(数组)中确定子串(子数组)的位置,因此需要两个变量来约束和确定子串,一个是子串的起始位置,另一个是结束位置。在算法的执行过程中,起始和结束位置是变化的,因此它们是状态参数。

3)状态决策

        一个范围较小的回文子数组 ➕ 额外元素后,再看它是不是回文子数组。更大范围的问题是由前面的子问题 ➕ 当前决策推导出来的,当前的决策就是如果向子问题的两边分别扩充一个元素,那么当前问题是否还是回文。

4)备忘录

        使用二维数组作为动归解法的备忘录。设 DP[i][j],其中 i 是子数组的起始位置,j 是结束位置,而 DP[i][j] 代表以i为起点以j为终点的字符串是否是回文字符串。

3、状态方程

                  

4、算法实现

 

package main

import "fmt"


func DP(str string) int {
	strSlice := []byte(str)
	ans := 0

	// 创建备忘录
	m := len(str)
	n := len(str)
	dp := make([][]bool, m)
	for i := range dp{
		dp[i] = make([]bool, n)
	}

	// 初始化状态
	for i := 0; i < len(str); i++ {
		dp[i][i] = true
		ans++
	}

	for j := 1; j < m; j++ { 	 // 遍历每一个结束位置
		for i := 0; i < j; i++ { // 遍历每一个开始位置
			dp[i][j] = strSlice[i]==strSlice[j] && (j-i <3 || dp[i+1][j-1]);	//状态转移方程
			if dp[i][j] {
				ans++
			}
		}
	}

	return ans;
}

func main() {
	str := "aaa"
	fmt.Println(DP(str))  // 输出答案
}

三、最大子数组之和 

1、问题描述

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


示例:

输入:[-2, 1, -3, 4, -1, 3, -5, 1, 2]
输出:6
解释:连续子数组 [4,-1, 3] 的和最大为 6。

 2、算法分析

1)初始状态

        初始状态即备忘录中以i为终点的子数组和为定义的最小值;

2)状态参数

        状态参数就清晰明了,即 n ;

3)状态决策

        要决策的就是是否要将当前子问题中额外的数字放入整个计算当中,以获得“更大”的子数组之和。 

4)备忘录

        DP[i][j] 对应的值是起始位置为 i 结束位置为 j 构成的最大子的子数组和。那么原问题的答案应该存放在 DP[0][n] 当中。但是这样设计备忘录,问题就复杂了。由于我们要求的只是一个最值,所有子问题最终要规约到从索引 0 到 n,因此没有必要同时记录子数组的起始和结束位置。 将 DP[i][j] 简化成 DP[i]。

3、状态方程

         

 

4、算法实现

package main

import "fmt"


func DP(array []int, length int) int {
	// 创建备忘录
	dp := make([]int, length)

	// 初始化状态
	for i := 1; i < length; i++ {
		dp[i] = -10000
	}
	dp[0] = array[0]

	// 转移方程转换实现
	for j := 1; j < length; j++ {
		if array[j] > dp[j-1]+array[j] {
			dp[j] = array[j]
		} else {
			dp[j] = dp[j-1]+array[j]
		}
	}
	
	//获取结果
	max := -10000
	for i := 0; i < length; i++ {
		if max < dp[i] {
			max = dp[i]
		}
	}
	return max
}

func main() {
	array := []int{-2, 1, -3, 4, -1, 3, -5, 1, 2}
	length := len(array)
	fmt.Println(DP(array, length))  // 输出答案
}

声明:本文参考极客时间《动态规划面试宝典》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何具体存储。例如,数组的连续存储,链表的动态分配节点,树和图的邻接矩阵或邻接表表示等。 基本操作:针对每种数据结构,定义了一系列基本的操作,包括但不限于插入、删除、查找、更新、遍历等,并分析这些操作的时间复杂度和空间复杂度。 算法算法设计:研究如何将解决问题的步骤形式化为一系列指令,使得计算机可以执行以求解问题算法特性:包括输入、输出、有穷性、确定性和可行性。即一个有效的算法必须能在有限步骤内结束,并且对于给定的输入产生唯一的确定输出。 算法分类:排序算法(如冒泡排序、快速排序、归并排序),查找算法(如顺序查找、二分查找、哈希查找),图论算法(如Dijkstra最短路径算法、Floyd-Warshall算法、Prim最小生成树算法),动态规划,贪心算法,回溯法,分支限界法等。 算法分析:通过数学方法分析算法的时间复杂度(运行时间随数据规模增长的速度)和空间复杂度(所需内存大小)来评估其效率。 学习算法数据结构不仅有助于理解程序的内部工作原理,更能帮助开发人员编写出高效、稳定和易于维护的软件系统。
逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何具体存储。例如,数组的连续存储,链表的动态分配节点,树和图的邻接矩阵或邻接表表示等。 基本操作:针对每种数据结构,定义了一系列基本的操作,包括但不限于插入、删除、查找、更新、遍历等,并分析这些操作的时间复杂度和空间复杂度。 算法算法设计:研究如何将解决问题的步骤形式化为一系列指令,使得计算机可以执行以求解问题算法特性:包括输入、输出、有穷性、确定性和可行性。即一个有效的算法必须能在有限步骤内结束,并且对于给定的输入产生唯一的确定输出。 算法分类:排序算法(如冒泡排序、快速排序、归并排序),查找算法(如顺序查找、二分查找、哈希查找),图论算法(如Dijkstra最短路径算法、Floyd-Warshall算法、Prim最小生成树算法),动态规划,贪心算法,回溯法,分支限界法等。 算法分析:通过数学方法分析算法的时间复杂度(运行时间随数据规模增长的速度)和空间复杂度(所需内存大小)来评估其效率。 学习算法数据结构不仅有助于理解程序的内部工作原理,更能帮助开发人员编写出高效、稳定和易于维护的软件系统。
算法数据结构它们分别涵盖了以下主要内容: 数据结构(Data Structures): 逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何具体存储。例如,数组的连续存储,链表的动态分配节点,树和图的邻接矩阵或邻接表表示等。 基本操作:针对每种数据结构,定义了一系列基本的操作,包括但不限于插入、删除、查找、更新、遍历等,并分析这些操作的时间复杂度和空间复杂度。 算法算法设计:研究如何将解决问题的步骤形式化为一系列指令,使得计算机可以执行以求解问题算法特性:包括输入、输出、有穷性、确定性和可行性。即一个有效的算法必须能在有限步骤内结束,并且对于给定的输入产生唯一的确定输出。 算法分类:排序算法(如冒泡排序、快速排序、归并排序),查找算法(如顺序查找、二分查找、哈希查找),图论算法(如Dijkstra最短路径算法、Floyd-Warshall算法、Prim最小生成树算法),动态规划,贪心算法,回溯法,分支限界法等。 算法分析:通过数学方法分析算法的时间复杂度(运行时间随数据规模增长的速度)和空间复杂度(所需内存大小)来评估其效率。 学习算法数据结构不仅有助于理解程序的内部工作原理,更能帮助开发人员编写出高效、稳定和易于维护的软件系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值