左神算法中级提升(4) 超级重点:动态规划的空间压缩技巧

news2024/11/25 23:21:05

【案例1】

【题目描述】【以后出现这种的题型 概率很低】

 【案例2】

【题目描述】

【思路解析】

构建两个栈,一个栈存放基本数据,一个栈存放最小值数据。每次加入一个数据时,当前元素和栈顶元素比较,谁小谁进入。 然后弹出时,两个栈同步弹出。

【代码实现】

import java.util.Stack;

/**
 * @ProjectName: study3
 * @FileName: Ex2
 * @author:HWJ
 * @Data: 2023/7/12 8:47
 */
public class Ex2 {
    public static void main(String[] args) {

    }

    public static class MyStack {
        Stack<Integer> stack;
        Stack<Integer> minStack;

        public MyStack() {
            stack = new Stack<>();
            minStack = new Stack<>();
        }

        public void push(int v) {
            stack.push(v);
            if (minStack.isEmpty()) { // 如果最小栈为空就直接加入
                minStack.push(v);
            } else {
                int min = Math.min(minStack.peek(), v);
                minStack.push(min);
            }
        }

        public int pop() {
            if (stack.isEmpty()) {
                System.out.println("栈已经为空,无法弹出有效信息");
                return -1;
            } else {
                int cur = stack.pop(); // 同步弹出
                minStack.pop();
                return cur;
            }
        }

        public int getMin() {
            if (minStack.isEmpty()) {
                System.out.println("栈已经为空,无法弹出有效信息");
                return -1;
            } else {
                return minStack.peek();
            }
        }

    }
}

【案例3】

【题目描述】

【思路解析】

队列实现栈结构,使用两个队列,每次加入时都加在有数据的队列,然后弹出时,将除了最后一个数据全部加载到另一个空队列,然后弹出最后一个元素。

栈实现队列结构,在一个栈中加入数据,弹出时将数据全部加载到另一个空栈里,然后弹出,如果弹出栈不为空,就直接弹出。

【代码实现】

import java.util.LinkedList;
import java.util.Stack;

/**
 * @ProjectName: study3
 * @FileName: Ex3
 * @author:HWJ
 * @Data: 2023/7/12 9:02
 */
public class Ex3 {
    public static void main(String[] args) {
        MyQueen myQueen = new MyQueen();
        myQueen.push(5);
        System.out.println(myQueen.pop());
        myQueen.push(3);
        System.out.println(myQueen.peek());
        System.out.println(myQueen.peek());
        myQueen.push(2);
        myQueen.push(1);
        myQueen.push(0);
        System.out.println(myQueen.pop());
        System.out.println(myQueen.pop());
        System.out.println(myQueen.pop());
        System.out.println(myQueen.pop());

        System.out.println("========");
        MyStack myStack = new MyStack();
        myStack.push(5);
        myStack.push(3);
        myStack.push(2);
        myStack.push(1);
        System.out.println(myStack.pop());
        System.out.println(myStack.pop());


    }

    public static class MyQueen{
        public Stack<Integer> stackPush;
        public Stack<Integer> stackPop;

        public MyQueen(){
            this.stackPush = new Stack<>();
            this.stackPop = new Stack<>();
        }

        public void push(int v){
            stackPush.push(v);
            process();
        }

        public int pop(){
            process();
            if(stackPop.isEmpty()){
                throw new RuntimeException("Queen is empty!!!");
            }
            return stackPop.pop();
        }


        public int peek(){
            process();
            if(stackPop.isEmpty()){
                throw new RuntimeException("Queen is empty!!!");
            }
            return stackPop.peek();
        }

        private void process(){
            if(stackPop.isEmpty()){
                while (!stackPush.isEmpty()){
                    stackPop.push(stackPush.pop());
                }
            }
        }

    }

    public static class MyStack{
        public    LinkedList<Integer> list1;
        public    LinkedList<Integer> list2;
        public int k = 1;  // 指定现在加入数据的列表是那个

        public MyStack(){
            list1 = new LinkedList<>();
            list2 = new LinkedList<>();
        }

        public void push(int v){
            if(k == 1){
                list1.add(v);
            }else {
                list2.add(v);
            }
        }

        public int pop(){
            int cur;
            if(k == 1){
                cur = list1.remove();
                while (!list1.isEmpty()){
                    list2.add(cur);
                    cur = list1.remove();
                }
                k = 2;
            }else {
                cur = list2.remove();
                while (!list2.isEmpty()){
                    list1.add(cur);
                    cur = list2.remove();
                }
                k = 1;
            }
            return cur;
        }

    }

}

 【案例4】

【题目描述】

 【思路解析】

【动态规划的空间压缩技巧】【只能减少空间的使用】

如果一个位置只依赖它左边的和上边的数据,则我们可以只用一个数组表示第一行,让这个数组自更新即可,这样就可以得到我们想要的数据。同样,也可以用一个数组表示第一列,来做列的自更新。   有时可以用部分变量来记录某些数据,帮助完成自更新。

 【代码实现】

/**
 * @ProjectName: study3
 * @FileName: Ex4
 * @author:HWJ
 * @Data: 2023/7/12 10:26
 */
public class Ex4 {
    public static void main(String[] args) {
        int[][] matrix = {{2,5,7,9,11},{2,3,4,5,3},{1,5,6,7,9}};
        System.out.println(getMin(matrix));
    }

    public static int getMin(int[][] matrix){
        // 这里假设行和列规模相似,
        // 如果规模差距很大,如n行m列  n >>> m,我们选择行更新,数组大小为m, m >>> n,我们选择列更新,数组大小为n
        int n = matrix.length;
        int m = matrix[0].length;
        int[] arr = new int[m];
        for (int i = 0; i < m; i++) { // 初始化数组
            arr[i] = matrix[0][i];
            if (i > 0){
                arr[i] += arr[i-1];
            }
        }
        for (int i = 1; i < n; i++) { // 自更新
            for (int j = 0; j < m; j++) {
                if (j == 0){
                    arr[j] = arr[j] + matrix[i][j];
                }else {
                    arr[j] = Math.min(arr[j-1], arr[j]) + matrix[i][j];
                }

            }
        }
        return arr[m-1];
    }
}

【案例5】

【题目描述】

 【思路解析】

如果常规思维我们去寻找那些地方有可能有水是非常麻烦的。所以我们去求每个位置的水量来做累加即可。对于一个i位置,它的左边最大值假设为maxL,右边最大值为maxR,容易想到此时的水量为max{min{maxL,maxR}-arr[i],0}。

所以第一种思路使用两个数组分别记录每个位置的左边最大值和右边最大值。空间复杂度为O(N)

 第二种思路,空间复杂度为O(1),记录目前已经遍历的左边最大值maxL和右边最大值maxR

(1)maxL < maxR,

则L索引位置上的桶边界已经确定,因为water = max{min{maxL,maxR}-arr[i],0}。maxR可能更大,但是他并不影响min{maxL,maxR}-arr[i]。

(2)maxL > maxR

则R索引位置上的桶边界已经确定

(3) maxL == maxR

则L和R索引位置上的桶边界都已经确定

 【代码实现】

/**
 * @ProjectName: study3
 * @FileName: Ex5
 * @author:HWJ
 * @Data: 2023/7/12 10:42
 */
public class Ex5 {
    public static void main(String[] args) {
        int[] arr = {3,1,2,5,2,4};
        System.out.println(water1(arr));
        System.out.println(water2(arr));
    }

    public static int water1(int[] arr) {
        // 使用两个数组来记录每个位置的左最大值和右最大值
        int n = arr.length;
        int[] left = new int[n];
        int[] right = new int[n];
        left[1] = arr[0];
        right[n - 2] = arr[n - 1];
        for (int i = 2; i < n - 1; i++) {
            left[i] = Math.max(left[i - 1], arr[i - 1]);
        }
        for (int i = n - 3; i > 0; i--) {
            right[i] = Math.max(right[i + 1], arr[i + 1]);
        }
        int total = 0;
        // 因为1位置和末位置都没有约束,不可能装水
        for (int i = 1; i < n - 1; i++) {
            total += Math.max(Math.min(right[i], left[i]) - arr[i], 0);
        }
        return total;
    }

    public static int water2(int[] arr) {
        // 使用两个数组来记录每个位置的左最大值和右最大值
        int n = arr.length;
        int left = arr[0];
        int right = arr[n - 1];
        int L = 1;
        int R = n - 2;
        int total = 0;
        // 因为1位置和末位置都没有约束,不可能装水
        while (L <= R){
            if (left > right){
                total += Math.max(right - arr[R], 0);
                right = Math.max(right, arr[R--]);
            }else if(right > left){
                total += Math.max(left - arr[L], 0);
                left = Math.max(left, arr[L++]);
            }else {
                total += Math.max(right - arr[R], 0);
                right = Math.max(right, arr[R--]);
                total += Math.max(left - arr[L], 0);
                left = Math.max(left, arr[L++]);
            }
        }
        return total;
    }
}

【案例6】

【题目描述】

【思路解析】

对于每个数组,他都有一个全局最大值,如果全局最大值在左部分,则我们应使右部分的最大值尽可能小,右部分至少包含一个元素arr【n-1】,所以右部分最大值>=arr[n-1],我们要是右部分尽可能小,所以让右部分只包含唯一一个元素。最大值在右部分情况类似。但是如果最大值在左边界,则只能在左部分,如果在右边界只能在右部分。

【代码实现】

/**
 * @ProjectName: study3
 * @FileName: Ex6
 * @author:HWJ
 * @Data: 2023/7/12 11:57
 */
public class Ex6 {
    public static void main(String[] args) {
        
    }
    
    public static int getMax(int[] arr){
        int max = Integer.MIN_VALUE;
        int index = -1;
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] > max){
                max = arr[i];
                index = i;
            }
        }
        int ans;
        if(index == 0){
            ans = max - arr[arr.length - 1];
        } else if (index == arr.length - 1) {
            ans = max - arr[0];
        }else {
            ans = Math.max(max - arr[arr.length - 1], max - arr[0]);
        }
        return ans;
    }
    
}

 【案例7】

【题目描述】

 【思路解析】

当字符串a、b长度不相等时,直接返回false。长度相等时,使a = a + a,然后使用KMP算法查询b是否在a上面。

【KMP算法详解】

详解并查集和KMP算法_Studying~的博客-CSDN博客

【代码实现】

/**
 * @ProjectName: study3
 * @FileName: Ex7
 * @author:HWJ
 * @Data: 2023/7/12 13:23
 */
public class Ex7 {
    public static void main(String[] args) {
        String a = "12345";
        String b = "44123";
        System.out.println(rotateStr(a, b));
    }

    public static boolean rotateStr(String a, String b){
        if (a.length() != b.length()){
            return false;
        }
        a = a + a;
        int[] next = getNext(b);
        int p1 = 0;
        int p2 = 0;
        char[] aChar = a.toCharArray();
        char[] bChar = b.toCharArray();
        while (p1 < aChar.length && p2 < bChar.length){
            if (aChar[p1] == bChar[p2]){
                p1 ++;
                p2++;
            } else if (next[p2] != -1) {
                p2 = next[p2];
            }else {
                p1++;
            }
        }
        return p2 == bChar.length;
    }


    public static int[] getNext(String str){
        if (str.length() == 1){
            return new int[] {-1};
        }
        int n = str.length();
        char[] chars = new char[n];
        int[] next = new int[n];
        next[0] = -1;
        next[1] = 0;
        int i = 2;
        int cn = 0;
        while (i < n) {
            if(chars[cn] == chars[i-1]){
                next[i++] = ++cn;
            } else if (cn > 0) {
                cn = next[cn];
            }else {
                next[i++] = 0;
            }
        }
        return next;
    }


}

【案例8】

【题目描述】 【2019 京东面试题】

给你一个正数数组,每个正数代表一个咖啡机冲一杯咖啡所需要的时间,一个咖啡机同时只能冲一杯咖啡。给你一个正数N,代表有n个人想喝咖啡,每个人只喝一杯。有一个洗咖啡杯的机器,一次只能洗一个,所需时间为a。咖啡杯不洗,也能通过自然挥发变干净所需要的时间b。返回所有人喝完咖啡,并且所有咖啡杯清洗干净所需要的最少时间。

【思路分析】

每个人喝咖啡的时间可以通过小根堆得到,小根堆里面存放一个Coffee结构,如(0,2)第一个数字代表咖啡机能使用的时间,第二个数字代表咖啡机冲一杯咖啡所需的时间。小根堆根据两个数字的和来进行维护。

 得到每个人得到咖啡的时间后,根据洗咖啡杯的两种策略来进行决定。

【代码实现】

import java.util.Comparator;
import java.util.PriorityQueue;

/**
 * @ProjectName: study3
 * @FileName: Ex8
 * @author:HWJ
 * @Data: 2023/7/12 14:10
 */
public class Ex8 {
    public static void main(String[] args) {
        int[] arr = {2, 3, 7};
        System.out.println(minTime(arr, 5, 2, 3));
    }

    public static class Coffee {
        public int time;
        public int need;

        public Coffee(int time, int need) {
            this.time = time;
            this.need = need;
        }
    }

    public static int minTime(int[] arr, int n, int a, int b) {
        int[] drink = drink(arr, n);
        return dpWash(drink, a, b, n);
    }

    public static int[] drink(int[] arr, int n) { // 得到n个人喝到咖啡的时间
        PriorityQueue<Coffee> coffees = new PriorityQueue<>(new Comparator<Coffee>() {
            @Override
            public int compare(Coffee o1, Coffee o2) {
                return (o1.need + o1.time) - (o2.need + o2.time);
            }
        });
        for (int i = 0; i < arr.length; i++) {
            Coffee cur = new Coffee(0, arr[i]);
            coffees.add(cur);
        }
        int[] drink = new int[n];
        for (int i = 0; i < n; i++) {
            Coffee cur = coffees.poll();
            cur.time += cur.need;
            drink[i] = cur.time;
            coffees.add(cur);
        }
        return drink;
    }

    public static int wash(int[] drink, int a, int b, int index, int washTime) {
        // index 代表现在洗到第几个杯子了
        // washTime代表洗咖啡机 在多久可以使用
        // 对每个杯子都有两种策略。 要么洗干净,要么不洗等他挥发干净。 返回两种情况中用时最少的那个
        // a 是洗杯子所用时间, b是自然挥发所用时间

        if (index == drink.length) {
            return 0;
        }

        int wash1 = Math.max(washTime, drink[index]) + a;
        int next1 = wash(drink, a, b, index + 1, wash1);
        int time1 = Math.max(wash1, next1);
        int wash2 = drink[index] + b;
        int next2 = wash(drink, a, b, index + 1, washTime);
        int time2 = Math.max(wash2, next2);
        return Math.min(time1, time2);
    }

    public static int dpWash(int[] drink, int a, int b, int n) {
        if (a >= b) {
            return drink[n - 1] + b;
        }
        int[][] times = new int[n][drink[n - 1] + n * a];
        for (int i = 0; i < times[0].length; i++) {
            times[n-1][i] = Math.min(drink[n - 1] + b, Math.max(i, drink[n-1]) + a);
        }
        for (int i = n - 2; i >= 0; i--) {
            int washTime = drink[i] + (i + 1) * a;
            for (int j = 0; j < washTime; j++) {
                int wash = Math.max(j, drink[i]) + a;
                times[i][j] = Math.min(Math.max(wash, times[i + 1][wash]), Math.max(drink[i] + b, times[i + 1][drink[i] + b]));
            }
        }
        return times[0][0];
    }

}

【案例9】

【题目描述】

【思路解析】

每个数字都一定可以分为奇数(有a个),和只有一个2因子的数(有b个),和含有多个2因子的数(有c个)。

(1) b == 0

a == 1, c >= 1;         a > 1, c >= a - 1

(2) b != 0

c >= a;  if (b == 1) c >= Math.max{1, a};

【代码实现】

/**
 * @ProjectName: study3
 * @FileName: Ex9
 * @author:HWJ
 * @Data: 2023/7/12 16:29
 */
public class Ex9 {
    public static void main(String[] args) {

    }

    public static boolean arr4(int[] arr) {
        if (arr.length == 0) {
            return false;
        }
        int a = 0;
        int b = 0;
        int c = 0;
        for (int i = 0; i < arr.length; i++) {
            if (arr[i] % 2 == 0) {
                if (arr[i] % 4 == 0) {
                    c++;
                } else {
                    b++;
                }
            } else {
                a++;
            }
        }
        if (b == 0) {
            if (a == 0) {
                return true;
            } else if (a == 1) {
                return c >= 1;
            } else {
                return c >= a - 1;
            }
        } else {
            if (b == 1) {
                return c >= Math.max(1, a);
            } else {
                return c >= a;
            }
        }
    }
}

 

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

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

相关文章

Hugging News #0710: 体验 MusicGen、Diffusers 库发布一周年、我们的内容政策更新

每一周&#xff0c;我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新&#xff0c;包括我们的产品和平台更新、社区活动、学习资源和内容更新、开源库和模型更新等&#xff0c;我们将其称之为「Hugging News」。本期 Hugging News 有哪些有趣的消息&#xff0…

[PyTorch][chapter 44][时间序列表示方法4]

前言&#xff1a; 训练复杂度 OE*T*Q 参数 全称 E 迭代次数 Number of the training epochs T数据集大小 Number of the words in the training set Q 模型计算复杂度 Model computational complexity E,T 一般都认为相同&#xff0c;所以这里面主要讨论Q&#xff0c;模…

maven-依赖管理-下

依赖冲突 特殊优先 特殊优先∶当同级配置了相同资源的不同版本&#xff0c;后配置的覆盖先配置的(提醒&#xff1a;要尽量避免这种没有意义的冲突)修改D:\java_projects\maven_A\pom.xml, 引入mysql5.1 <?xml version"1.0" encoding"UTF-8"?> &…

什么是用电信息采集系统?

用电信息采集系统是一种用于收集、处理和分析电力用户用电信息的系统&#xff0c;旨在提高电力系统的运行效率、可靠性和安全性。该系统主要通过对电力用户的用电数据进行实时监测、分析和处理&#xff0c;为电力公司、政府部门和用户提供有用的信息&#xff0c;以帮助他们更好…

PyTorch 1.13简介

# 1.  PyTorch 1.13 据官方介绍&#xff0c;PyTorch 1.13 中包括了 BetterTransformer 的稳定版&#xff0c;且不再支持 CUDA 10.2 及 11.3&#xff0c;并完成了向 CUDA 11.6 及 11.7 的迁移。此外 Beta 版还增加了对 Apple M1 芯片及 functorch 的支持。 1.1 主要更新 Be…

JavaWeb 前后端分离

AJax 1. 前端视图 ajax\src\main\webapp\ajax-register.html <html><head><meta charset"UTF-8"> </head><body><form class"form-horizontal" role"form"><div><tr><td>账号</td&…

数据库主从同步

目录 一、准备工作1.1 安装或关闭以下服务1.2 本次安装环境 2、主数据库配置2.1主数据库配置2.2创建用户2.3查看信息 三、从主数据库配置3.1从数据库配置3.2连接主服务器3.3测试 4、其他4.1连接完毕后发现Slave_IO_Running值异常&#xff0c;4.2报错Error connecting to source…

第一阶段-第七章 Python的函数进阶

目录 一、函数多返回值  1.学习目标  2.多个返回值的语法  3.本节的代码演示 二、函数多种传参方式  1.学习目标  2.函数参数种类&#xff08;位置参数、关键字参数、缺省参数、不定长参数&#xff08;位置传递、关键字传递&#xff09;&#xff09;  3.本节的代码…

一条命令解决端口占用,开启mysql

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录 端口占用开启mysql 端口占用 如果发现 8080 端口被占用可以使用命令 sudo lsof -t -i:8080 | sudo xargs kill -9 查找并杀死相应的进程。 开启mysql 打开命令提示符或终端。如果您已经安装了MySQL&…

【雕爷学编程】Arduino动手做(161)---16路PWM舵机驱动板PCA9685

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

HTML语法

文章目录 前言HTML 文件基本结构常见标签标签种类特殊符号图片链接a链接 双标签链接 列表表格 &#xff1a;表单多行文本域: 前言 HTML是有标签组成的 <body>hello</body>大部分标签成对出现. 为开始标签, 为结束标签. 少数标签只有开始标签, 称为 “单标签”. 开…

第二章:类和对象(中)

系列文章目录 文章目录 系列文章目录前言类的6个默认成员函数构造函数概念特性 析构函数概念特性 拷贝构造函数概念特征 赋值运算符重载运算符重载赋值运算符重载赋值运算符重载格式赋值运算符只能重载成类的成员函数不能重载成全局函数用户没有显式实现时&#xff0c;编译器会…

【物理】模拟粒子在电场和磁场中的轨迹研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Mysql的分库分表策略

一.水平切分 水平切分又称为 Sharding 策略&#xff0c;它是将同一个表中的记录拆分到多个结构相同的表中。 当一个表的数据不断增多时&#xff0c;Sharding 是必然的选择&#xff0c;它可以将数据分布到集群的不同节点上&#xff0c;从而缓存单个数据库的压力。 Sharding 策…

后端(五):JVM

目录 JVM 中的内存区域划分 JVM 的类加载机制 1. 加载 2. 验证 3. 准备 4. 解析 5. 初始化 JVM 中的垃圾回收策略 找&#xff0c;确认垃圾 1. 引用计数 2. 可达行分析 释放”垃圾“对象 1. 标记清除 2. 复制算法 3. 标记整理 分代算法 JVM也就是我们俗称的八股…

Bootstrap编写一个兼容主流浏览器的受众巨幕式风格页面

Bootstrap编写一个兼容主流浏览器的受众巨幕式风格页面 虽然说IE6除了部分要求苛刻的需求以外已经被可以不考虑了&#xff0c;但是WIN7自带的浏览器IE8还是需要支持的。 本文这个方法主要的优点&#xff0c;个人觉得就是准备少&#xff0c;不需要上网寻找大量的图片做素材&…

2003-Can‘t connect to Mysql server on ‘xxx‘ (10060 “Unknown error“)

Navicat连接 阿里云 服务器MySQL5.7数据库报错 解决办法&#xff1a; 进入数据库执行以下sql 1.允许root用户远程连接 GRANT ALL PRIVILEGES ON *.* TO root% IDENTIFIED BY 数据库密码 WITH GRANT OPTION; 2.刷新权限 FLUSH PRIVILEGES;3.执行quit退出数据库 quit; 4.…

PVE虚拟化平台之安装Ubuntu Desktop系统

PVE虚拟化平台之安装Ubuntu Desktop系统 一、Ubuntu介绍1.1 Ubuntu简介1.2 Ubuntu版本1.3 ubuntu命名规则 二、上传镜像到PVE2.1 检查PVE环境2.2 上传镜像到PVE 三、新建虚拟机3.1 设置虚拟机名称3.2 操作系统设置3.3 系统设置3.4 磁盘设置3.5 cpu设置3.6 内存设置3.7 网络设置…

sprinboot摄影跟拍预定管理系统

摄影跟拍预定管理方面的任务繁琐,以至于每年都在摄影跟拍预定管理这方面投入较多的精力却效果甚微,摄影跟拍预定管理系统的目标就是为了能够缓解摄影跟拍预定管理工作方面面临的压力,让摄影跟拍预定管理方面的工作变得更加高效准确。 本项目在开发和设计过程中涉及到原理和技术…

怎么给pdf文件加密?pdf文档如何加密

在数字化时代&#xff0c;保护个人和机密信息的重要性越来越受到关注。PDF&#xff08;Portable Document Format&#xff09;是一种广泛使用的文件格式&#xff0c;用于共享和存储各种类型的文档。然而&#xff0c;由于其易于编辑和复制的特性&#xff0c;保护PDF文件中的敏感…