【飞桨AI实战】基于PP-OCR和ErnieBot的智能视频问答

前言

        本次分享将带领大家从 0 到 1 完成一个基于 OCR 和 LLM 的视频字幕提取和智能视频问答项目,通过 OCR 实现视频字幕提取,采用 ErnieBot 完成对视频字幕内容的理解,并回答相关问题,最后采用 Gradio 搭建应用。本项目旨在帮助初学者快速搭建入门级 AI 应用,并分享开发过程中遇到的一些坑,希望对感兴趣的同学提供一点帮助。

项目背景和目标

背景:

        光学字符识别(Optical Character Recognition,简称 OCR)是一种将图像中的文字转换为机器编码文本的过程。通常一个 OCR 任务的处理流程如下图所示:

 PP-OCR 是百度面向产业应用提供的 OCR 解决方案,底层采用的是两阶段 OCR 算法,即检测模型+识别模型的组成方式,其处理流程包括如下几个步骤:

而视频字幕提取就是对视频中的每帧图像提取出其中的字幕文字。

        大语言模型(LLM,Large Language Model)是一种先进的自然语言处理技术,当前主流的 LLM 包括 GPTs、百度文心一言、阿里通义千问、字节豆包等,而 ErnieBot 正是基于百度文心一言的智能体框架。基于提取的视频字幕,借助 LLM 强大的语义理解能力,我们可以完成很多有意思的任务,比如让 LLM 帮我们提取视频的关键信息,甚至是基于视频回答我们的问题,减轻当前大模型常见的“幻觉”-胡说八道,比如下面这张图:

目标:

  • 掌握如何用 paddlepaddle 深度学习框架搭建一个文本识别模型;
  • 掌握文本识别模型架构的设计原理以及构建流程;
  • 掌握如何利用已有框架快速搭建应用,满足实际应用需求;

百度 AI Studio 平台

        本次实验将采用 AI Studio 实训平台中的免费 GPU 资源,在平台注册账号后,点击创建项目-选择 NoteBook 任务,然后添加数据集,如下图所示,完成项目创建。启动环境可以自行选择 CPU 资源 or GPU 资源,创建任务每天有 8 点免费算力,推荐大家使用 GPU 资源进行模型训练,这样会大幅减少模型训练时长。

创建项目的方式有两种:

  • 一是在 AI Studio 实训平台参考如下方式,新建项目。

  • 二是直接 fork 一个平台上的已有项目,比如本次实验,可以选择【飞桨 AI 实战】实验 6-基于 PP-OCR 和 ErnieBot 的智能视频问答的最新版本,然后点击 fork,成功后会在自己账户下新建一个项目副本,其中已经挂载了源项目自带的数据集和本次项目用到的核心代码。

为了快速跑通项目流程,建议直接 fork 源项目。

从零开始实战

        1 基础:动手跑通 CRNN 文本识别任务

核心代码在:core/ 文件夹下

背景:CRNN 是较早被提出也是目前工业界应用较多的文本识别方法。本节将详细介绍如何基于 PaddleOCR 完成 CRNN 文本识别模型的搭建、训练、评估和预测。数据集采用 CaptchaDataset 中文本识别部分的 9453 张图像,其中前 8453 张图像在本案例中作为训练集,后 1000 张则作为测试集。

1.1 数据准备

step 1:解压缩数据

# 打开终端
# 解压子集  -d 指定解压缩的路径,会在data0文件夹下生成
unzip data/data57285/OCR_Dataset.zip -d data0/
# 查看文件夹中文件数量
ls data0/OCR_Dataset/|wc -l

step 2: 准备数据部分代码

# 数据读取类在 reader.py, 可以执行如下脚本查看训练数据
python reader.py

可视化结果如下:

1.2 模型构建

本次实验我们将采用最简单的网络架构来搭建 CRNN 网络 并构建损失函数 CTCLoss

step 1: 搭建 CRNN 网络

# 定义模型类
net.py

step 2: 定义损失函数 CTCLoss

# 定义 loss, 位于 net.py
class CTCLoss(paddle.nn.Layer):def __init__(self, batch_size):"""定义CTCLoss"""super().__init__()self.batch_size = batch_sizedef forward(self, ipt, label):input_lengths = paddle.full(shape=[self.batch_size],fill_value=LABEL_MAX_LEN + 4,dtype= "int64")label_lengths = paddle.full(shape=[self.batch_size],fill_value=LABEL_MAX_LEN,dtype= "int64")# 按文档要求进行转换dim顺序ipt = paddle.tensor.transpose(ipt, [1, 0, 2])# 计算lossloss = paddle.nn.functional.ctc_loss(ipt, label, input_lengths, label_lengths, blank=10)return loss

1.3 模型训练

编写训练脚本 train.py 如下,主要是定义好数据集、模型,配置训练相关参数:

# 运行训练脚本
python train.py

训练过程如下图所示:

1.4 模型预测

编写预测脚本 predict.py

# 运行预测脚本
python predict.py

调用模型预测函数:得到生成图像的可视化结果

2 进阶:基于PP-OCR和ErnieBot搭建应用

核心代码在:ocr-bot/ 文件夹下

2.1 环境准备

本项目主要用到了以下安装包,可以采用 pip install -r requirements.txt 一键安装。

paddlepaddle
paddleocr==2.7.0
erniebot
moviepy
gradio

2.2 需求分析

本项目主要需要完成两个功能:视频字幕提取 和 智能视频问答

视频字幕提取

  • 中文视频能提取出其中的字幕
  • 英文视频能自动生成中文字幕
  • 生成 SRT 格式的字幕文件
  • 将字幕文件内嵌到视频中去

智能视频问答

  • 提取视频中的关键信息,完成视频摘要
  • 根据字幕信息,回答用户针对视频的提问
  • 根据字幕信息,定位关键信息对应的时间片段

2.2 核心功能实现

2.2.1 基于 PP-OCR 完成字幕提取

采用 opencv 读取视频中的图片,引入 paddleocr 包实现图片中的字幕提取,同时记录时间信息,为了快速完成 demo 展示,这里采用每秒抽取一帧图像,且只用图像底部包含字幕的部分进行文字识别,核心代码如下:

def get_video_ocr(vid_path='/home/aistudio/demo/trim.mp4'):ocr = PaddleOCR(use_angle_cls=True, debug=False)src_video = cv2.VideoCapture(vid_path)fps = int(src_video.get(cv2.CAP_PROP_FPS))total_frame = int(src_video.get(cv2.CAP_PROP_FRAME_COUNT)) # 计算视频总帧数save_text = []for i in tqdm(range(total_frame)):    success, frame = src_video.read()if i % (fps) == 0 and success:result = ocr.ocr(frame[-120:-30, :], cls=True)[0] # 只抽取下半部分图片if len(result)> 0: res = result[0][1][0]start_time = i//fpssave_text.append([start_time, res])# 将数据转换为字典,合并重复的字幕subtitles = {}for (time, text) in save_text:if text in subtitles:subtitles[text].append(time)else:subtitles[text] = [time]subtitle_path = vid_path.replace('.mp4', '.json')print(f"字幕提取完成,结果已保存至{subtitle_path}")with open(subtitle_path, 'w', encoding='utf-8') as f:json.dump(subtitles, f, ensure_ascii=False)return '\n'.join(list(subtitles.keys())), subtitle_path
2.2.2 基于 百度翻译API 完成字幕翻译

为了帮助大家对原版英文视频的理解,可以将原始的英文字幕翻译成中文,这里选择直接调用 百度翻译API,开发者每个月都有一定的免费额度。注意将其中的 API_KEY 和 SECRET_KEY 换成你自己的。

def get_access_token():API_KEY = "j5HodGgjG2iQ87MenXrw2hot"SECRET_KEY = "Ea1AYc1kjzv2MNExEZeMAEwzanDDlsdK"url = "https://aip.baidubce.com/oauth/2.0/token"params = {"grant_type": "client_credentials", "client_id": API_KEY, "client_secret": SECRET_KEY}return str(requests.post(url, params=params).json().get("access_token"))def translation(content, from_lang="en", to_lang="zh"):url = "https://aip.baidubce.com/rpc/2.0/mt/texttrans/v1?access_token=" + get_access_token()payload = json.dumps({"from": from_lang,"to": to_lang,"q": content})headers = {'Content-Type': 'application/json','Accept': 'application/json'}response = requests.request("POST", url, headers=headers, data=payload)i = response.text.find("dst")+6j = response.text.find("src")-3return response.text[i:j]def translate_subtitles(subtitle_path, from_lang="en", to_lang="zh"):new_subtitles = {}subtitles = json.load(open(subtitle_path, 'r'))for text, value in subtitles.items():trans_text = translation(text, from_lang, to_lang)new_subtitles[trans_text] = valuesubtitle_path = subtitle_path.replace('.json', f'_{to_lang}.json')print(f"字幕翻译完成,结果已保存至{subtitle_path}")with open(subtitle_path, 'w', encoding='utf-8') as f:json.dump(new_subtitles, f, ensure_ascii=False)return '\n'.join(list(new_subtitles.keys())), subtitle_path
2.2.3 生成 SRT 格式的字幕文件

视频文件中最简单、最常见的外挂字幕格式是SRT(SubRip Text)。SRT字幕通常以srt作为后缀,作为外挂字幕,多数主流播放器都支持直接加载并显示SRT字幕。通常每个字幕段有四部分构成:

  • 字幕序号:从 1 开始(而不是 0)
  • 字幕显示的起始时间
    • 格式为hour:minute:second,millisecond --> hour:minute:second,millisecond
  • 字幕内容(可多行)
  • 空白行(表示本字幕段的结束)

一个简单的例子如下:

1
0:00:00,000 --> 0:00:02,000
可能没有意识到。
2
0:00:02,000 --> 0:00:03,000
他们怎么会知道我们总有一天

让我们编写代码将提取的字幕改写成 SRT 格式的字幕文件:

def generate_subtitles(subtitle_path, save_path='./subtitles.srt'):srt_content = ''subtitles = json.load(open(subtitle_path, 'r'))for index, (text, times) in enumerate(subtitles.items()):# SRT文件的索引从1开始srt_index = index + 1# 格式化时间戳start_time = "%s,%03d" % (timedelta(seconds=times[0]), 0 * 100)end_time = "%s,%03d" % (timedelta(seconds=times[-1]+1), 0 * 100)time_str = f"{start_time} --> {end_time}"# 将字幕合并为一个字符串,并用逗号分隔# 构建SRT条目srt_entry = f"{srt_index}\n{time_str}\n{text}\n"srt_content += srt_entry# 写入SRT文件with open(save_path, 'w', encoding='utf-8') as file:file.write(srt_content)return srt_content
2.2.4 基于 moviepy 实现视频拼接

注意 moviepy 实现视频拼接需要安装 imagemagick。在 AIStudio 的 Linux 环境中没有 sudo 权限,因此无法安装 imagemagick,如果要实现视频拼接,需要大家移步到自己本地电脑运行。Linux 下一键安装 imagemagick:

sudo apt-get install imagemagick

如果 imagemagick 安装没问题,那么就可以实现将翻译后的中文字幕添加到视频中。这里给出示例代码实现:

def add_subtitles(video_path, subtitle_path, output_path='./video_with_subtitles.mp4'):# 加载视频文件video = VideoFileClip(video_path)width, height = video.w, video.hsubtitles = json.load(open(subtitle_path, 'r'))trans_text = []for text, dura in subtitles.items():start_time = float(dura[0])end_time = float(dura[-1]+1)duration = end_time - start_timetext = TextClip(text, fontsize=20, size=(width-20, 25),align='center', color='white').set_position((10,height-40)).set_duration(duration).set_start(start_time)trans_text.append(text)video = CompositeVideoClip([video, *trans_text])video.write_videofile(output_path)
2.2.5 基于 ErnieBot 实现视频问答

ERNIE Bot 为开发者提供了便捷接口,可以轻松调用文心大模型的文本创作、通用对话、语义向量及AI作图等基础功能。

这里仅使用通用对话接口,你只需要将字幕文件(srt_content)提示词(prompt_content)你的问题(user_content)准备就可以了,示例代码如下:

import erniebot
erniebot.api_type = 'aistudio'
erniebot.access_token = '7d8bcc8494fb95e9059bae34856c3a40daaf8671' # 注意替换成自己的def chat_with_bot(srt_content, prompt_content, user_content):if not srt_content:return "请先点击👂生成srt格式字幕"messages =[{'role': 'user', 'content': f'{srt_content} {prompt_content} {user_content}'}]response = erniebot.ChatCompletion.create(model='ernie-3.5',messages=messages,)res = response.get_result()# print(res)return res

注意这里的erniebot.access_token可以在 AIStudio 的个人中心获取(如下图所示),每个新用户都有免费额度。

2.3 Gradio前端界面实现

本次实验同样还是基于 Gradio 搭建一个简单的前端应用,将上述实现的功能集成进来。具体界面逻辑如下:

def launch():theme = gr.Theme.load("theme.json")with gr.Blocks(theme=theme) as demo:gr.Markdown(top_md)with gr.Row():with gr.Column():video_input = gr.Video(label="视频输入 | Video Input")with gr.Row():gr.Examples(['zh.mp4'],[video_input],label='中文示例视频 | Chinese Demo Video')gr.Examples(['en.mp4'],[video_input],label='英文示例视频 | English Demo Video')with gr.Row():recog_button = gr.Button("👂 识别字幕", variant="primary")recog_button2 = gr.Button("👂英文->中文")srt_button = gr.Button("👂生成srt格式字幕", variant="primary")with gr.Column():with gr.Tab("🤖 PP-OCR视频字幕"):with gr.Row():video_text_ori = gr.Textbox(label="📖 原始字幕内容", lines=8)video_text_tra = gr.Textbox(label="📖 翻译字幕内容", lines=8)video_text_path = gr.Textbox(label="字幕地址", visible=False)video_text_srt = gr.Textbox(label="✏️ SRT字幕内容", lines=8)with gr.Tab("🧠 ErnieBot视频智能问答"):with gr.Column():prompt_head = gr.Textbox(label="Prompt", value=("你是一个视频分析助手,基于输入视频的srt字幕,回答我的问题"))prompt_user = gr.Textbox(label="User", value=("我的问题是:"))llm_button =  gr.Button("Enrie bot推理", variant="primary")llm_result = gr.Textbox(label="Enrie bot 回答", lines=8)recog_button.click(get_video_ocr,inputs=video_input,outputs=[video_text_ori, video_text_path])recog_button2.click(translate_subtitles,inputs=video_text_path,outputs=[video_text_tra, video_text_path])srt_button.click(generate_subtitles,inputs=video_text_path,outputs=video_text_srt)llm_button.click(chat_with_bot,inputs=[video_text_srt, prompt_head, prompt_user],outputs=llm_result)demo.launch(server_name='0.0.0.0', server_port=8080)

在 AIStudio 的云环境中启动应用:

python demo.py

 如果你是在 AIStudio 的 CodeLab 中启动应用的话,本地浏览器中是无法访问这个地址的,那么如何访问这个应用呢?

下面介绍两种方式:

方式一:

参考 AIStudio 的项目服务部署官方文档,采用url拼接的方式:Codelab项目链接/api_serving/<user_port>/

举个例子:比如我的Codelab地址是:

https://aistudio.baidu.com/bd-cpu-01/user/226606/7892508/home#vscode

那么在浏览器中打开如下链接即可访问你启动的 Gradio 应用:

https://aistudio.baidu.com/bd-cpu-01/user/226606/7892508/api_serving/8080/

如果你打开后的界面如下图所示,和本文前面展示的界面相比,不符合预期。什么原因? F12 打开 Chrome 开发者工具,发现是因为加载本地文件失败了(比如这里的前端样式和示例视频),目前还没找到很好的解决方案。 

方式二:

为此,我们选择在 Codelab 的 Notebook 界面中进行前端展示。在Notebook 界面中进行前端展示,需要xx.gradio.py格式的文件,为此可以将demo.py复制一份命名为demo.gradio.py,如下图所示:

这里有几点坑,大家注意避开:

  • 在 demo.launch()中不要指定 8080 端口
  • 如果依然出现上述 css 文件加载不出来,导致界面显示有问题,换一台开发机试试吧,笔者亲测有效。
  • 需要在初始 python 环境中安装项目依赖包:pip install -r requirements.txt,因为xx.gradio.py是在下面这个python环境中启动的:

此外,还可以选择在本地 Linux 环境中运行项目,完美避开上述各种坑。

总结

至此,我们共同走完了一个完整的视频问答项目,从基础的动手跑通 CRNN 文本识别任务,再到应用开发和部署,旨在帮助初学者快速入门 OCR 相关技术并搭建一个简单的应用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/14979.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小猫咪的奇幻冒险:一个简单的Python小游戏

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、游戏简介与演示 二、游戏开发与运行 1. 环境搭建 2. 代码解析 3. 加速机制 三、游戏…

Jeecg | 完成配置后,如何启动整个项目?

前端启动步骤&#xff1a; 1. 以管理员身份打开控制台&#xff0c;切换到前端项目目录。 2. 输入 pnpm install 3. 输入 pnpm dev 4. 等待前端成功运行。 可以看到此时前端已经成功启动。 后端启动步骤&#xff1a; 1. 启动 mysql 服务器。 管理员身份打开控制台&#…

得物小程序逆向+qt可视化(不含sku)

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01;wx a15018601872 本文章未…

Python实现国密GmSSL

Python实现国密GmSSL 前言开始首先安装生成公钥与私钥从用户证书中读取公钥读取公钥生成签名验证签名加密解密 遇到的大坑参考文献 前言 首先我是找得到的gmssl库&#xff0c;经过实操&#xff0c;发现公钥与密钥不能通过pem文件得到&#xff0c;就是缺少导入pem文件的api。这…

迷你手持小风扇到底哪个牌子最好?揭秘迷你手持手持小风扇排行榜

在炎炎夏日&#xff0c;迷你手持小风扇成为了我们不可或缺的清凉伴侣。然而&#xff0c;面对市场上琳琅满目的品牌&#xff0c;迷你手持小风扇到底哪个牌子最好&#xff1f;今天&#xff0c;我将揭秘迷你手持小风扇排行榜&#xff0c;带大家一探各大品牌的魅力&#xff0c;让你…

字节面试:百亿级数据存储,怎么设计?只是分库分表吗?

尼恩&#xff1a;百亿级数据存储架构起源 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;经常性的指导小伙伴们改造简历。 经过尼恩的改造之后&#xff0c;很多小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试机会&#xff0c…

【LeetCode】【5】最长回文子串

文章目录 [toc]题目描述样例输入输出与解释样例1样例2 提示Python实现动态规划 个人主页&#xff1a;丷从心 系列专栏&#xff1a;LeetCode 刷题指南&#xff1a;LeetCode刷题指南 题目描述 给一个字符串s&#xff0c;找到s中最长的回文子串 样例输入输出与解释 样例1 输入…

文件上传安全指南:保护免受不受限制的文件上传攻击

文件上传安全指南&#xff1a;保护免受不受限制的文件上传攻击 在现代应用程序中&#xff0c;文件上传功能是一个常见且重要的部分。然而&#xff0c;这也为攻击者提供了潜在的攻击向量&#xff0c;尤其是不受限制的文件上传攻击。本文将详细介绍如何通过一系列安全措施来保护…

安全设计 | Microsoft 威胁建模工具Threat Modeling Tool安装及使用详解(文末附样例)

1. 概览 微软威胁建模工具&#xff08;Threat Modeling Tool&#xff09;是 Microsoft 安全开发生命周期 (SDL&#xff0c;Security Develop LifeCycle) 的核心要素。 当潜在安全问题处于无需花费过多成本即可相对容易解决的阶段&#xff0c;软件架构师可以使用威胁建模工具提…

linux系统防火墙开放端口命令

目录 linux相关命令参考文章1.开放端口1.1 开发单个端口1.2 一次性开放多个端口 2.保存设置3.查看所有开放的端口4.查看防火墙状态 linux相关命令参考文章 管理、设置防火墙规则&#xff08;firewalld&#xff09;: https://download.csdn.net/blog/column/8489557/137911049 i…

打造AI虚拟伴侣 - 优化方案

第一部分:框架优化概述 1、精确定位: 构建一个高度灵活且用户友好的平台,旨在通过无缝集成多种大型语言模型(LLMs)后端,为用户创造沉浸式的角色交互体验。不仅适配电脑端,还特别优化移动端体验,满足二次元AI虚拟伴侣市场的特定需求。 2、核心功能强化: 增强后端兼容…

每日练习之深度优先搜索——最大的湖

最大的湖 题目描述 运行代码 #include<iostream> using namespace std; bool mp[102][102]; int sum,num; int N,M,K; int dfs(int x,int y ) {if( mp[x][y] ){mp[x][y]0;sum;dfs(x1,y);dfs(x-1,y);dfs(x,y1);dfs(x,y-1);}return 0; } int main() {cin>>N>>…

【每日一题】52.20个机器学习问题 2 (模型部署、实践流程和应用问题)

在上一篇《20个机器学习问答题》中&#xff0c;问题主要围绕机器学习的基础概念和理论知识。 这次&#xff0c;本篇内容针对机器学习的实践和应用继续提出了20个不同的问题。【点击跳转原文】 在实际应用中&#xff0c;机器学习模型的建立流程是怎样的&#xff1f; 机器学习模…

使用delphi11编写一个基于xls作为数据库的照片展示程序

1、创建xls文档可以参考前一篇博客&#xff0c;并使用wps将文档保存为2003格式xls后缀。 2、在form上面放置adoconnection、adotable、datasource、spinedit、timer、checkbox、image、4个button组件。 image的设置&#xff1a; Image1.Align : alClient; Image1.Center : Tr…

如何找到docker的run(启动命令)

使用python三方库进行 需要安装python解释器 安装runlike安装包 pip3 install runlike 运行命令 runlike -p <container_name> # 后面可以是容器名和容器id&#xff0c;-p参数是显示自动换行实验 使用docker启动一个jenkins 启动命令为 docker run -d \ -p 9002:80…

无线领夹麦克风哪个品牌音质最好,揭秘无线领夹麦哪个牌子好用

​随着社交媒体和内容创作的兴起&#xff0c;清晰可靠的音频捕捉已成为打造高品质作品的关键要素。无线领夹麦克风因其轻巧设计和用户友好的接口而受到青睐&#xff0c;它能够确保你的声音在任何环境下都能被完美捕捉。经过精心测试和对比&#xff0c;以下几款无线领夹麦克风是…

大数据学习之安装并配置maven环境

什么是Maven Maven字面意&#xff1a;专家、内行Maven是一款自动化构建工具&#xff0c;专注服务于Java平台的项目构建和依赖管理。依赖管理&#xff1a;jar之间的依赖关系&#xff0c;jar包管理问题统称为依赖管理项目构建&#xff1a;项目构建不等同于项目创建 项目构建是一…

C语言——⾼位优先与低位优先的不同之处是什么?

一、问题 C语⾔的最⼤特⾊就是可移植性好。根据机器类型的不同&#xff0c;⾼位优先与低位优先也不同。那么&#xff0c;最好的可移植的 C 程序应该同时适⽤这两种类型的计算机。下⾯了解⼀下⾼位优先与低位优先的不同之处。 二、解答 所谓的⾼位优先&#xff0c;就是最低的地…

使用docker-compose部署时序数据库InfluxDB1.8.4

背景 如今 InfluxDB 已经更新到了 2.x &#xff0c; InfluxDB 1.x 和 2.x 版本之间有几个主要的区别&#xff1a; 数据模型&#xff1a; 1.x&#xff1a;使用数据库和保留策略来组织数据。 2.x&#xff1a;引入了组织&#xff08;organizations&#xff09;和存储桶&#xff…

牛客NC236 最大差值【simple 动态规划 Java/Go/PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/a01abbdc52ba4d5f8777fb5dae91b204 思路 不难看出该题可以使用动态规划的方式解题。 在循环数组的过程中&#xff0c;记录截止到当前位置-1的最小值&#xff0c; 然后用当前的值去计算最大的差值。Java代码 im…