LeetCode第26~30题解

CONTENTS

    • LeetCode 26. 删除有序数组中的重复项(简单)
    • LeetCode 27. 移除元素(简单)
    • LeetCode 28. 找出字符串中第一个匹配项的下标(简单)
    • LeetCode 29. 两数相除(中等)
    • LeetCode 30. 串联所有单词的子串(困难)

LeetCode 26. 删除有序数组中的重复项(简单)

【题目描述】

给你一个升序排列的数组 nums,请你原地删除重复出现的元素,使每个元素只出现一次,返回删除后数组的新长度。元素的相对顺序应该保持一致。然后返回 nums 中唯一元素的个数。

考虑 nums 的唯一元素的数量为 k,你需要做以下事情确保你的题解可以被通过:

  • 更改数组 nums,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
  • 返回 k

【示例1】

输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。

【示例2】

输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。

【提示】

1 ≤ n u m s . l e n g t h ≤ 3 ∗ 1 0 4 1\le nums.length\le 3 * 10^4 1nums.length3104
− 1 0 4 ≤ n u m s [ i ] ≤ 1 0 4 -10^4\le nums[i]\le 10^4 104nums[i]104
nums 已按升序排列

【分析】


本题使用 unique 函数一行就能搞定,该函数能够将数组的重复元素移至末端,返回不重复数组的最后一个元素的下一个迭代器,例如原数组为 nums = { 1, 1, 2, 2, 2, 3 },经 unique 处理后即变为 { 1, 2, 3, _, _, _ },返回值为 3 的下一个迭代器,减去 nums.begin() 后即为不重复数组中元素的数量。

我们也可以自己实现,使用两个指针,其中一个指针用来遍历每个数,当遍历到的数与前一个不同时就将该数存入另一个指针指向的位置,同时另一个指针向后移动一位。


【代码】

unique 函数实现】

class Solution {
public:int removeDuplicates(vector<int>& nums) {return unique(nums.begin(), nums.end()) - nums.begin();}
};

【手动实现】

class Solution {
public:int removeDuplicates(vector<int>& nums) {int k = 0;for (int i = 0; i < nums.size(); i++)if (!i || nums[i] != nums[i - 1]) nums[k++] = nums[i];return k;}
};

LeetCode 27. 移除元素(简单)

【题目描述】

给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O ( 1 ) O(1) O(1) 额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

【示例1】

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

【示例2】

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

【提示】

0 ≤ n u m s . l e n g t h ≤ 100 0\le nums.length\le 100 0nums.length100
0 ≤ n u m s [ i ] ≤ 50 0\le nums[i]\le 50 0nums[i]50
0 ≤ v a l ≤ 100 0\le val\le 100 0val100

【分析】


与上一题类似,本题同样可以使用 remove 函数将某个值全部移动到数组末端,并返回删除该数后数组的最后一个元素的下一个迭代器,减去 nums.begin() 即为删除该数后数组的长度。

我们也可以使用两个指针,其中一个遍历所有元素,当该元素不等于 val 时将其保存到另一个指针指向的位置,同时另一个指针向后移动一位。


【代码】

remove 函数实现】

class Solution {
public:int removeElement(vector<int>& nums, int val) {return remove(nums.begin(), nums.end(), val) - nums.begin();}
};

【手动实现】

class Solution {
public:int removeElement(vector<int>& nums, int val) {int k = 0;for (int i = 0; i < nums.size(); i++)if (nums[i] != val) nums[k++] = nums[i];return k;}
};

LeetCode 28. 找出字符串中第一个匹配项的下标(简单)

【题目描述】

给你两个字符串 haystackneedle,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1

【示例1】

输入:haystack = "sadbutsad", needle = "sad"
输出:0
解释:"sad" 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。

【示例2】

输入:haystack = "leetcode", needle = "leeto"
输出:-1
解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1 。

【提示】

1 ≤ h a y s t a c k . l e n g t h , n e e d l e . l e n g t h ≤ 1 0 4 1\le haystack.length, needle.length\le 10^4 1haystack.length,needle.length104
haystackneedle 仅由小写英文字符组成

【分析】


可以直接使用 find 函数查找子串第一次出现的位置,若不匹配则返回 -1

当然我们重点是要讲一下 KMP 算法。假设 S 为模板串(即较长的串),P 为模式串(即较短的串),KMP 中最重要的一个数组为 next[i],表示所有 P[1 ~ i] 的相等的前缀与后缀中长度的最大值(不考虑整个字符串作为前后缀)。KMP 算法流程如下图所示:

在这里插入图片描述

  1. 假设当前 S 的指针 i i i 指向的字符失配了,P 对应的指针为 j + 1 j+1 j+1,此时我们知道 P[1 ~ j] == S[3 ~ i - 1]
  2. P j j j 处的最长相同前后缀的长度为3(蓝色标注),说明 P[1 ~ 3] == P[j - 2 ~ j]
  3. 根据 P[j - 2 ~ j] 已经和 S[i - 3 ~ i - 1] 匹配了可得 P[1 ~ 3] 也与 S[i - 3 ~ i - 1] 匹配;
  4. 因此我们将 P 向后移,令 j = next[j],此时 j = 3 j=3 j=3
  5. 继续判断 j + 1 j+1 j+1 是否与 i i i 匹配。

【代码】

find 函数实现】

class Solution {
public:int strStr(string haystack, string needle) {return haystack.find(needle);}
};

【手动实现】

class Solution {
public:int strStr(string s, string p) {int n = s.size(), m = p.size();s = ' ' + s, p = ' ' + p;  // 调整为下标从1开始vector<int> next(m + 1);for (int i = 2, j = 0; i <= m; i++)  // next[1]=0{while (j && p[i] != p[j + 1]) j = next[j];if (p[i] == p[j + 1]) j++;next[i] = j;}for (int i = 1, j = 0; i <= n; i++){while (j && s[i] != p[j + 1]) j = next[j];if (s[i] == p[j + 1]) j++;if (j == m) return i - m;  // 由于下标从1开始因此起始位置为i-m}return -1;}
};

LeetCode 29. 两数相除(中等)

【题目描述】

给你两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和取余运算。
整数除法应该向零截断,也就是截去(truncate)其小数部分。例如,8.345 将被截断为 8-2.7335 将被截断至 -2
返回被除数 dividend 除以除数 divisor 得到的
注意:假设我们的环境只能存储 32 位有符号整数,其数值范围是 [ − 2 31 , 2 31 − 1 ] [-2^{31}, 2^{31} - 1] [231,2311]。本题中,如果商严格大于 2 31 − 1 2^{31} - 1 2311,则返回 2 31 − 1 2^{31} - 1 2311;如果商严格小于 − 2 31 -2^{31} 231,则返回 − 2 31 -2^{31} 231

【示例1】

输入: dividend = 10, divisor = 3
输出: 3
解释: 10/3 = 3.33333.. ,向零截断后得到 3 。

【示例2】

输入: dividend = 7, divisor = -3
输出: -2
解释: 7/-3 = -2.33333.. ,向零截断后得到 -2 。

【提示】

− 2 31 ≤ d i v i d e n d , d i v i s o r ≤ 2 31 − 1 -2^{31}\le dividend, divisor\le 2^{31} - 1 231dividend,divisor2311
d i v i s o r ≠ 0 divisor\ne 0 divisor=0

【分析】


假设被除数为 x x x,除数为 y y y,第一个能想到的方法就是用 x x x y y y 直到减不了为止,但是这种做法会超时。

假设 x y = k \frac {x}{y}=k yx=k,我们将 k k k 用二进制表示,假设是 ( 110010 ) 2 (110010)_2 (110010)2,也就是 2 1 + 2 4 + 2 5 2^1+2^4+2^5 21+24+25,即 x = 2 1 y + 2 4 y + 2 5 y x=2^1y+2^4y+2^5y x=21y+24y+25y。那么我们可以先预处理出 2 0 y , 2 1 y , … , 2 31 y 2^0y,2^1y,\dots ,2^{31}y 20y,21y,,231y,然后从大到小枚举,若 x ≥ 2 i y x\ge 2^iy x2iy,那么就将 x x x 减去 2 i y 2^iy 2iy,并将商加上 2 i 2^i 2i

对于正负的判断,我们可以先判断商的正负,然后将两个数统一转换成正数进行运算,最后再改变商的正负号。


【代码】

class Solution {
public:int divide(int x, int y) {vector<long long> exp;  // 表示2^i * ybool minus = (x > 0 && y < 0 || x < 0 && y > 0) ? true : false;  // 商是否为负long long a = abs((long long)x), b = abs((long long)y);for (long long i = b; i <= a; i += i) exp.push_back(i);  // 预处理long long res = 0;for (int i = exp.size() - 1; i >= 0; i--)  // 从大到小枚举if (a >= exp[i]) a -= exp[i], res += 1ll << i;  // 记得加long long,左移可能会越界if (minus) res = -res;if (res > INT_MAX) return INT_MAX;return res;}
};

LeetCode 30. 串联所有单词的子串(困难)

【题目描述】

给定一个字符串 s 和一个字符串数组 wordswords 中所有字符串长度相同
s 中的串联子串是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。
例如,如果 words = ["ab","cd","ef"],那么 "abcdef""abefcd""cdabef""cdefab""efabcd",和 "efcdab" 都是串联子串。"acdbef" 不是串联子串,因为他不是任何 words 排列的连接。
返回所有串联子串在 s 中的开始索引。你可以以任意顺序返回答案。

【示例1】

输入:s = "barfoothefoobarman", words = ["foo","bar"]
输出:[0,9]
解释:因为 words.length == 2 同时 words[i].length == 3,连接的子字符串的长度必须为 6。
子串 "barfoo" 开始位置是 0。它是 words 中以 ["bar","foo"] 顺序排列的连接。
子串 "foobar" 开始位置是 9。它是 words 中以 ["foo","bar"] 顺序排列的连接。
输出顺序无关紧要。返回 [9,0] 也是可以的。

【示例2】

输入:s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]
输出:[]
解释:因为 words.length == 4 并且 words[i].length == 4,所以串联子串的长度必须为 16。
s 中没有子串长度为 16 并且等于 words 的任何顺序排列的连接。
所以我们返回一个空数组。

【示例3】

输入:s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]
输出:[6,9,12]
解释:因为 words.length == 3 并且 words[i].length == 3,所以串联子串的长度必须为 9。
子串 "foobarthe" 开始位置是 6。它是 words 中以 ["foo","bar","the"] 顺序排列的连接。
子串 "barthefoo" 开始位置是 9。它是 words 中以 ["bar","the","foo"] 顺序排列的连接。
子串 "thefoobar" 开始位置是 12。它是 words 中以 ["the","foo","bar"] 顺序排列的连接。

【提示】

1 ≤ s . l e n g t h ≤ 1 0 4 1\le s.length\le 10^4 1s.length104
1 ≤ w o r d s . l e n g t h ≤ 5000 1\le words.length\le 5000 1words.length5000
1 ≤ w o r d s [ i ] . l e n g t h ≤ 30 1\le words[i].length\le 30 1words[i].length30
words[i]s 由小写英文字母组成

【分析】


根据本题的数据量,如果直接暴力求解时间复杂度特别高,因此需要考虑一些优化。我们先记 n n n s . s i z e ( ) s.size() s.size() m m m w o r d s . s i z e ( ) words.size() words.size() w w w w o r d s [ i ] . s i z e ( ) words[i].size() words[i].size()

枚举 s s s 中所有长度为 m ∗ w m*w mw 的子串,我们按子串的起始位置划分为若干组,例如第一组中每个子串的起始位置分别为: 0 , w , 2 w , … , ( m − 1 ) w 0,w,2w,\dots ,(m-1)w 0,w,2w,,(m1)w,第二组中每个子串的起始位置分别为: 1 , w + 1 , 2 w + 1 , … , ( m − 1 ) w + 1 1,w+1,2w+1,\dots ,(m-1)w+1 1,w+1,2w+1,,(m1)w+1,以此类推,这样同一组里面划分出来的单词都是有规律的,而不是随机分割开的。对于第一组,我们从0开始按顺序枚举所有长度为 w w w 的单词子串,例如对于第一个样例,第一组的枚举顺序就是 bar/foo/the/foo/bar/man

对于每一组,我们需要找到连续的 m m m 个区间,每个区间中的单词都存在于 words 中,因此我们可以用哈希表 window_cnt 维护一个长为 m m m 的滑动窗口中的 m m m 个单词的数量,然后每次都判断一下窗口中的 m m m 个单词是否都和 words 中的一一对应,但是每次我们不能都暴力判断,因此可以用 cnt 表示哈希表中为 words 中的单词的数量,当 cnt == m 时说明一一对应了。那么如何确定 cnt 呢?我们可以再用一个哈希表 words_cnt 表示 words 中每种单词的数量,当窗口新来了一个单词 t 时,window_cnt[t] 会加一,当 window_cnt[t] <= words_cnt[t] 时(表示 twords 中出现过且在窗口中出现的次数还没超过 wordst 出现的次数),即可将 cnt 加一;当窗口移除一个单词 t 时,window_cnt[t] 会减一,当 window_cnt[t] < words_cnt[t] 时(表示 twords 中出现过且当前窗口中的数量比 words 中少),即可将 cnt 减一。

对于第一个样例,第一组的求解过程如下:

  • [bar],滑动窗口长度还没超过 m m m,直接添加元素;
  • [bar, foo],滑动窗口长度还没超过 m m m,直接添加元素,此时窗口中的内容和 words 一一对应,因此记录下答案;
  • [foo, the],滑动窗口长度超过 m m m,先删除首部元素再添加,此时窗口中的内容与 words 不对应;
  • [the, foo],同上;
  • [foo, bar],此时窗口中的内容和 words 一一对应,记录答案;
  • [bar, man],此时窗口中的内容与 words 不对应。

【代码】

class Solution {
public:vector<int> findSubstring(string s, vector<string>& words) {vector<int> res;int n = s.size(), m = words.size(), w = words[0].size();unordered_map<string, int> word_cnt;  // 记录words中每个单词的数量for (auto t: words) word_cnt[t]++;for (int i = 0; i < w; i++){unordered_map<string, int> window_cnt;  // 滑动窗口中每个单词的数量int cnt = 0;  // 滑动窗口中为words中的单词的数量for (int j = i; j <= n - w; j += w){if (j >= i + m * w)  // 超过滑动窗口的长度{string t = s.substr(j - m * w, w);window_cnt[t]--;if (window_cnt[t] < word_cnt[t]) cnt--;  // words中不存在的单词数量为0}string t = s.substr(j, w);window_cnt[t]++;if (window_cnt[t] <= word_cnt[t]) cnt++;  // 如果某个单词数量超过了也是错的if (cnt == m) res.push_back(j - (m - 1) * w);}}return res;}
};

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

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

相关文章

qt设计界面

widget.h #ifndef WIDGET_H #define WIDGET_H //防止文件重复包含#include <QWidget> //QWidget类所在的头文件&#xff0c;父类头文件 #include<QIcon> #include<QPushButton> …

VS的调试技巧

Visual Studiohttps://visualstudio.microsoft.com/zh-hans/vs/ 目录 1、什么是调试&#xff1f; 2、debug和release 3、调试 3.1、环境 3.2、 快捷键 3.2.1、F10和F11 3.2.2、ctrlF5 3.2.3、F5与F9 3.2.3.1、条件断点 3.3、监视和内存观察 3.3.1、监视 3.3.2、内存 …

[集创赛海云捷讯杯]全国二等奖经验分享

[集创赛海云捷讯杯]全国二等奖经验分享 一.前言二.我们的作品三.小结 一.前言 笔者是研一在校生&#xff0c;从五月份开始和本科生一起卷集创赛&#xff0c;经历初赛&#xff0c;分赛区决赛&#xff0c;全国总决赛&#xff0c;认识了很多一起做比赛的朋友收获颇丰。今年海云杯…

RabbitMQ工作模式-发布订阅模式

Publish/Subscribe&#xff08;发布订阅模式&#xff09; 官方文档&#xff1a; https://www.rabbitmq.com/tutorials/tutorial-three-python.html 使用fanout类型类型的交换器&#xff0c;routingKey忽略。每个消费者定义生成一个队列关绑定到同一个Exchange&#xff0c;每个…

性能测试jmeter连接数据库jdbc(sql server举例)

一、下载第三方工具包驱动数据库 1. 因为JMeter本身没有提供链接数据库的功能&#xff0c;所以我们需要借助第三方的工具包来实现。 &#xff08;有这个jar包之后&#xff0c;jmeter可以发起jdbc请求&#xff0c;没有这个jar包&#xff0c;也有jdbc取样器&#xff0c;但不能发…

外观模式:简化复杂子系统的访问与使用

文章目录 1. 简介2. 外观模式的基本结构3. 外观模式的实现步骤4. 外观模式的应用与实例4.1 图形界面库的外观模式应用4.2 文件压缩与解压缩的外观模式应用4.3 订单处理系统的外观模式应用 5. 外观模式的优缺点5.1 优点5.2 缺点 6. 总结 1. 简介 外观模式是一种结构型设计模式&…

Linux服务器安装部署MongoDB数据库 – 【无公网IP远程连接】

文章目录 前言1.配置Mongodb源2.安装MongoDB数据库3.局域网连接测试4.安装cpolar内网穿透5.配置公网访问地址6.公网远程连接7.固定连接公网地址8.使用固定公网地址连接 前言 MongoDB是一个基于分布式文件存储的数据库。由 C 语言编写&#xff0c;旨在为 WEB 应用提供可扩展的高…

什么是OLAP

一、什么是OLAP OLAP&#xff08;On-line Analytical Processing&#xff0c;联机分析处理&#xff09;是在基于数据仓库多维模型的基础上实现的面向分析的各类操作的集合。可以比较下其与传统的OLTP&#xff08;On-line Transaction Processing&#xff0c;联机事务处理&…

Redisson分布式锁 原理源码 分析

# 基于setnx实现的分布式锁存在的问题&#xff1a; # 为了解决上面的问题&#xff0c;可以用Redisson # Redisson入门 # Redisson可重入锁原理 获取锁的Lua脚本&#xff1a; 释放锁的Lua脚本&#xff1a; # 锁重试原理分析 tryLock&#xff08;&#xff09;底层代码分析 tim…

什么是自动语音识别?

在人工智能发展和全球疫情的双重作用下&#xff0c;企业加强了与客户的线上沟通。企业越发依赖于虚拟助手、聊天机器人以及其他的语音技术&#xff0c;以实现与客户的高效互动。这几类人工智能&#xff0c;都是依赖于自动语音识别技术&#xff0c;简称为ASR。ASR涉及到将语音转…

LLM推理部署(一):LLM七种推理服务框架总结

自从ChatGPT发布以来&#xff0c;国内外的开源大模型如雨后春笋般成长&#xff0c;但是对于很多企业和个人从头训练预训练模型不太现实&#xff0c;即使微调开源大模型也捉襟见肘&#xff0c;那么直接部署这些开源大模型服务于企业业务将会有很大的前景&#xff0c;本文将介绍七…

Effective C++条款16——成对使用new和delete时要采取相同形式(资源管理)

以下动作有什么错? std::string* stringArray new std::string[100]; // ... delete stringArray;每件事看起来都井然有序。使用了new&#xff0c;也搭配了对应的 delete。但还是有某样东西完全错误:你的程序行为不明确&#xff08;未有定义&#xff09;。最低限度&#xff…

PHP竞赛管理系统Dreamweaver开发mysql数据库web结构php编程计算机网页

一、源码特点 PHP 竞赛管理系统是一套完善的web设计系统&#xff0c;对理解php编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 下载地址 https://download.csdn.net/download/qq_41221322/88244066 二、功能介绍 后…

C#_特性反射详解

特性是什么&#xff1f; 为程序元素额外添加声明信息的一种方式。 字面理解&#xff1a;相当于把额外信息写在干胶标签上&#xff0c;然后将其贴在程序集上。 反射是什么&#xff1f; 反射是一种能力&#xff0c;运行时获取程序集中的元数据。 字面理解&#xff1a;程序运行…

一键实现 Oracle 数据整库同步至 Apache Doris

在实时数据仓库建设或迁移的过程中&#xff0c;用户必须考虑如何高效便捷将关系数据库数据同步到实时数仓中来&#xff0c;Apache Doris 用户也面临这样的挑战。而对于从 Oracle 到 Doris 的数据同步&#xff0c;通常会用到以下两种常见的同步方式&#xff1a; OGG/XStream/Lo…

设计模式-桥接模式

核心思想 适配器模式类似&#xff0c;以后也会遇到意思接近一样的设计模式。在开发中一般多个模式混用&#xff0c;且根据不同的场景进行搭配&#xff0c;桥接模式也是结构型模式将抽象的部分和实现的部分分离&#xff0c;使它们都可以独立的变化。通俗来说&#xff0c;就是通…

Linux特殊指令

目录 1.dd命令 2.mkfs格式化 3.df命令 4.mount实现硬盘的挂载 5.unshare 1.dd命令 dd命令可以用来读取转换并输出数据。 示例一&#xff1a; if表示infile&#xff0c;of表示outfile。这里的/dev/zero是一个特殊文件&#xff0c;会不断产生空白数据。 bs表示复制一块的大…

vscode 对模型train、detect脚本进行Debug时配置参数

我们训练yolov5代码时&#xff0c;一般会配置一些参数&#xff0c;比如模型权重文件--weights, 模型的配置文件--cfg, 以及训练的数据--data, 对应的训练脚本为: 训练train python train.py -- weights ./yolov5s.pt --cfg models\yolov5s.yaml --data ./data/coco128.yaml…

9.oracle中sign函数

在Oracle/PLSQL中, sign 函数返回一个数字的正负标志. 语法如下&#xff1a;sign( number ) number 要测试标志的数字. If number < 0, then sign returns -1. If number 0, then sign returns 0. If number > 0, then sign returns 1. 应用于: Oracle 8i, Oracle …

Docker - Docker安装MySql并启动

因为项目需要连接数据库&#xff0c;但是远程服务器上的mysql我不知道账户和密码&#xff0c;这个时候便是docker发挥作用的关键时刻了&#xff01; 目录 docker安装安装gcc卸载老docker&#xff08;如有&#xff09;安装软件包设置镜像仓库更新yum软件包索引安装docker启动doc…