贪心算法入门(一)

news2024/11/7 22:43:44

1.什么是贪心算法?

        贪心算法是一种解决问题的策略,它将复杂的问题分解为若干个步骤,并在每一步都选择当前最优的解决方案,最终希望能得到全局最优解。这种策略的核心在于“最优”二字,意味着我们追求的是以最少的时间和精力,快速获得正确的结果。

        然而,“希望得到全局最优解”就表示贪心算法并不意味着一定能得到全局最优解。实际上,并不是所有问题都可以通过贪心策略解决。为了确保贪心策略的有效性,需要对其进行严格的证明。而且,不同的问题往往需要采用不同的贪心策略。

        如果你觉得这一点仍然比较抽象,接下来我将通过五道具体的例题来详细说明贪心算法的应用及其背后的思路。

2.柠檬水找零

860. 柠檬水找零 - 力扣(LeetCode)

通过示例和问题描述,理解这个问题并不难,所以我就不过多阐述了。就是看能不能给bills数组里面的每个顾客正确的找零,并且一开始我们手上是没有零钱的,也就意味着如果bills[0] > 5, 那么我们是没有办法给顾客找零的,因为此时我们还没有任何零钱。以此类推,当检索完整个数组都能正确找零时返回true,否则返回false。

在理清问题后,不要急于去想出一个贪心策略,也不要急于去想用代码如何实现。而是首先在你脑子里面去想示例里面的答案是如何一步一步得到的。

 示例1中,第1位顾客用5美元买柠檬水,不用找零,与此同时我们手上多了一张5元的零钱。以此类推,直到第4位顾客付了10美元,我们需要找零5美元,此时我们的零钱库中有3张5美元,可以正确找零,此时零钱库变为2张5美元,1张10美元。最后一位顾客,我们只需找1张10美元和1张5美元,故可以实现正确找零,返回true。

再把以上思路,变现为代码即可。但其实还有一个细节问题需要我们考虑,就是如果顾客支付20元,而此时我们的零钱库中有3张5美元和1张10美元,那么我们应该选择3张5美元还是1张10美元1张5美元进行找补呢?这时可能会想到用递归的方式将两种方式递归下去,只要有一种的结果为true那么就为true。但同时递归这种方式时间复杂度就会大大提升。此时能不能有一种贪心策略去简化呢?

3张5美元和1张10美元1张5美元,应该更优先选择1张10美元1张5美元进行找补。这就是我们贪心的地方,因为5美元的用处比10美元更大,当顾客支付10美元时,5美元可以找补,同时5美元也可以为20找补。所以我们应该留着用处更大的5美元,以备不时之需。

那么这种策略对不对呢,这个贪心策略得到的结果是不是正确的,需要证明。我们可以反推假设,假设在最优解中,我们在某一次找补20美元的选择中,并没有选择1张10美元和1张5美元,而是选择了3张5美元。而我们的贪心策略是要选择1张10美元和1张5美元的,我们把贪心的选择往假设的最优解中进行替换,看是否会影响后续结果。

代码实现

class Solution {
    public boolean lemonadeChange(int[] bills) {
        int five = 0, ten = 0; // five表示零钱库中5美元的张数,ten表示10美元的张数
        for(int i = 0; i < bills.length; i++){ 
            if(bills[i] == 5) five++; // 更新零钱库
            else if(bills[i] == 10){
                if(five == 0) return false; // 如果零钱库没有5美元返回false
                five--;ten++; // 更新零钱库
            }
            else{
                if(ten > 0 && five > 0) {ten--;five--;} // 优先选择用10美元和5美元找补
                else if(five >= 3) five -= 3; // 次选3张5美元(在没有10美元的情况下)
                else return false; // 上述条件都不满足则没有合适的零钱找补返回false
            }
        }
        return true;
    }
}

3. 将数组和减半的最少操作次数

2208. 将数组和减半的最少操作次数 - 力扣(LeetCode)

这道题的贪心策略并不难想,要让操作次数最少,只需每次都挑出数组最大的数进行减半,但是需要注意的是,减半后的数也需要放回原数组,然后挑出最大值。所以不能直接将数组用sort方法进行排序,因为还需要将减半后的数重新放回数组进行比较。采取使用大根堆的数据结构即可。

代码实现

class Solution {
    public int halveArray(int[] nums) {
        PriorityQueue<Double> q = new PriorityQueue<>((a, b) -> Double.compare(b, a));
        int ret = 0;
        double sum = 0;
        for(int x : nums){sum += x;q.add((double) x);} // 原数组总和
        sum /= 2.0; // 计算总和的一半
        while(sum > 0){
            double t = q.poll() / 2.0; // 拿出堆中最大的数
            sum -= t;
            ret++;
            q.offer(t);// 减半后的数重新放回大根堆
        }
        return ret;
    }
}

4.最大数

179. 最大数 - 力扣(LeetCode)

以示例1为例,10和2我们很容易看出来,应该让2在高位,10放在2后面。所以实现的逻辑就是,把两个数从高位到低位的数字挨个比较,直至数字不同,并把数字大的放在前面。比如10和2的比较逻辑就是,10的第一位数字1和2比较,2比1大所以放前面。再比如23和22, 23的第一位是2,22的第一位也是2,所以继续向后比较就3和2比较,所以23和22组成的最大值应该是2322。

但还存在一种情况就是,2和20,这种情况应该把谁放在前面呢,肉眼观察最大值应该是220,所以应该把2放前面20放后面。但是这个结论我们应该用逻辑推敲出来而不是凭空想。难道遇到这种情况就是把位数少的放前面吗?那2和24就显然不是这种情况,因为224比242要小。但是从这两个例子就可以看出端倪了,如果多出的数字比首位大那么就是位数多的排前面,否则排后面。2和20中0比2小,所以20应该排在后面,而2和24中4比2大,所以24应该排前面。

所以我们只要按照这种方法对数组进行降序排序,再依次遍历数组就可以得到最终结果值。其实还有一种更简单的比较方法,就是将数a和数b都转换为字符串,然后比较 a + b(a在前,b在后)大还是(b + a)大即可。

代码实现

class Solution {
    public String largestNumber(int[] nums) {
        String[] s = new String[nums.length];
        for(int i = 0; i < nums.length; i++) s[i] = "" + nums[i]; // 将数转换为字符串存入字符数组
        Arrays.sort(s, (a, b) -> (b + a).compareTo(a + b)); // 按照比较方法对字符数组进行降序排序
        if(Objects.equals(s[0],"0")) return "0"; // 判断特殊情况,如果排序后的最大值是0就返回0
        StringBuilder ret = new StringBuilder();
        for(String t : s) ret.append(t); // 依次添加
        return ret.toString();
    }
} 

5.摆动序列

376. 摆动序列 - 力扣(LeetCode)

摆动序列其实就是一种波浪型曲线,要你找的这个子序列必须是先升再降再升再...,或者先降再升再降...。而且子序列就表示中间可以不用是连续的,也就表明你可以在数组里面跳着挑满足摆动的数。最终找出最长的子序列即可。

我先说一下我一开始看到这道题的思路,就是用动态规划。但其实这道题用贪心的思路是更简单的,但是动态规划和贪心的时间复杂度是一样的。我在力扣上跑的结果所花的时间也是差不多的。只是贪心的逻辑会更简单,但是贪心的策略是比较扩散性思维才能想到的,但是也不难。这里想讲一下动态规划的思路是因为是比较容易想和常规的。如果想直接看贪心策略的可以直接跳到贪心的思路。

动态规划思路:

首先明确状态表示,最开始想的是dp[i]表示以i位置为结尾的摆动最长子序列长度,但是很快被否定了,因为这样我没有办法写状态转移方程,因为我不知道以i位置结尾的是上升趋势还是下降趋势,就没有办法知道后续那个数可以加在这个状态之上。所以用两个数组表示不同的状态。

 int[] f = new int[n]; // 以i结尾并且为上升趋势的摆动序列最长长度
 int[] g = new int[n]; // 以i结尾并且为下降趋势的摆动序列最长长度

 当nums[ j ] < nums[ i ] (j 表示在 i 之前的所有索引值),为上升趋势,所以应该连上为下降趋势的序列组成新的摆动序列,即更新 f[ i ] = g[ i ] + 1,  当nums[ j ] > nums[ i ] 时, 更新g[ i ] = f[ i ] + 1。需要注意的是在更新i位置的值时,需要把i之前的所有的f[ j ] 和 g[ j ]都遍历比较一遍,看接在哪个位置后才能组成最长的摆动子序列。还有就是初始值问题,每个数自己也能单独组成摆渡序列,所以f [ i ] 和 g [ i ] 的初始值都应该为1。

动规代码实现:

class Solution {
    public int wiggleMaxLength(int[] nums) {
        int n = nums.length;
        int[] f = new int[n]; // 以i结尾并且为上升趋势
        int[] g = new int[n]; // 以i结尾并且为下降趋势
        for(int i = 0; i < n; i++) f[i] = g[i] = 1;
        int ret = 1;
        for(int i = 1; i < n; i++){
            for(int j = 0; j < i; j++){
                if(nums[j] > nums[i]) g[i] = Math.max(g[i], f[j] + 1);
                if(nums[j] < nums[i]) f[i] = Math.max(f[i], g[j] + 1);
            }
            ret = Math.max(ret, Math.max(f[i], g[i]));
        }
        return  ret;
    }
}

以上实现思路时间复杂度为n², 因为多了一个找出最合适j位置的循环。所以我的优化思路就是想把这个寻找j位置的循环省掉。如果我的f 和 g函数表示在0 - i区间内最长摆动子序列的值,我就不用去找j位置了,直接用这个值往上加即可。但是这样的话我的判断条件又会缺少值,因为不知道最长子序列的最后一位数是什么,就没有办法判断哪些数可以接在后面,所以又新开一个二维数组hash,hash[i][0]表示在0-i区间内最长摆动序列且结尾为下降趋势的最后一位数的值,hash[i][1]表示在0-i区间内最长摆动序列且结尾为上升趋势的最后一位数的值。

所以条件判断变成了,nums[ i ] 和 hash [ i - 1] [ 0 ] 、 hash [ i - 1] [ 1 ] 进行比较,如果nums[ i ]  >  hash [ i - 1] [ 0 ], 表示当前位置的值大于在0 - i - 1区间内结尾为下降趋势的最长摆动序列的最后一位数,满足条件则更新f[ i ] 的值,并且hash [ i ] [ 1 ] 也要更新为当前数。

f[i] = g[i - 1] + 1; hash[i][1] = nums[i];

 如果不满足,f[ i ] 的值应该延续 f[ i - 1] 的值,并且hash [ i ] [ 1 ] 也应该延续hash [ i - 1] [ 1 ] 的值。

g[i]同理。

优化后的代码:

class Solution {
    public int wiggleMaxLength(int[] nums) {
        int n = nums.length;
        int[] f = new int[n]; // 在0-i区间内 结尾为上升趋势
        int[] g = new int[n]; // 在0-i区间内 结尾并且为下降趋势
        for(int i = 0; i < n; i++) f[i] = g[i] = 1;
        int[][] hash = new int[n][2];
        hash[0][0] = hash[0][1] = nums[0];
        for(int i = 1; i < n; i++){
           if(nums[i] > hash[i - 1][0]) { f[i] = g[i - 1] + 1; hash[i][1] = nums[i]; }
           else { f[i] = f[i - 1];  hash[i][1] = hash[i - 1][1];}
           if(nums[i] < hash[i - 1][1]) { g[i] = f[i - 1] + 1; hash[i][0] = nums[i]; }
           else { g[i] = g[i - 1];  hash[i][0] = hash[i - 1][0];}
        }
        return  Math.max(f[n -1], g[n - 1]);
    } 
}

 贪心思路:

把数组用折线表示,下图是我随便画的一种情况,你可以随便用折线画你假设的数组,从图中可以看出我们要要找的摆动序列长度的最大值就是一共有多少个拐点,但是连续的相同拐点只能算一个。这些拐点也叫极点,判断是不是极点的方式:该点和左区间值的差值left和和右区间的差值right相乘是否小于0,小于0则为极点,大于0说明一直都是上升趋势或下降趋势,等于0则说明是连续相等的区间。

贪心代码实现

class Solution {
    public int wiggleMaxLength(int[] nums) {
        int n = nums.length;
        if (n < 2) return n;
        
        int ret = 0, left = 0, right = 0;
        for(int i = 0; i < n - 1; i++){ // 注意没有考虑最后一个
            right = nums[i + 1] - nums[i]; 
            if(right == 0) continue; // 先将右区间更新到拐点位置
            if(right * left <= 0) ret++; // 条件中等于0是为了第一次循环左区间为0的特殊情况
            left = right;
        }
        return ret + 1; // 要加上最后一个位置,因为最后一个位置必为极点
    }
}

6.最长递增子序列

300. 最长递增子序列 - 力扣(LeetCode)

 这道题依然会讲解动态规划和贪心两种方法思路,再在动态规划的基础上去优化得到贪心。

动态规划思路:

dp[ i ] 表示以i位置为结尾的最长递增子序列, 状态转移方程为j < i && nums[ j ] < nums[ i ] ,dp [ i ] = max( dp [ j ] + 1) 。

动态规划代码实现

class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n]; // 表示以dp[i]位置结尾的最长递增子序列长度
        Arrays.fill(dp,1);
        int ret = 1;
        for(int i = 1; i < n; i++){
            for(int j = 0; j < i; j++){
                if(nums[i] > nums[j]) dp[i] = Math.max(dp[i], dp[j] + 1); // 满足条件表示i位置的值可以跟在j位置后形成递增序列
            }
            ret = Math.max(ret, dp[i]); // 更新最大值
        }
        return ret;
    }
}

时间复杂度是n²。因为多了找最合适的j位置的循环,需要想办法把这个循环省掉。根据上一题给的思路,依然可以把状态表示dp[ i ] 重新定义为在0 - i区间的最长递增子序列长度。但是依然需要考虑判断条件是跟谁比较,由于这里定义为区间内并不知道结尾的数是多少,所以新开一个数组key[i]表示在0 - i区间内最长递增子序列的最后一个数。看似很合理但是实际上状态转移方程还是写不出来的。

key[0] = nums[0];
        for(int i = 1; i < n; i++){
           if(nums[i] > key[i - 1]) { dp[i] = dp[i - 1] + 1; key[i] = nums[i]; }
           else (nums[i] < key[i - 1]) { dp[i] = dp[i - 1]; key[i] = key[i - 1]; }
        }

nums[ i ] > key[i - 1] 这种情况是可以正确写出状态转移方程的, 只需在上一个区间的最长序列长度加1即可,并且更新结尾值为当前i位置的值。但如果是小于的情况,dp[ i ] = dp[ i - 1]继续延续上一个区间的状态没错,可是 key[ i ] 的值却不能简单的用延续处理。例如数组为【10, 1 ,3】这种情况,当i = 1 的时候如果简单的把key[ i ] 的值延续上一次的状态为10,那么最终肯定是不能得到正确答案长度为2的序列1,3的。那么key[ i ] 的值在这种情况下应该怎么更新呢?你会发现需要处理很多种情况,并且只记录递增子序列最后的数根本不能应对这些情况,因为有可能最长递增子序列需要以另外一个位置为起点开头,或者说在中间把一些点替换掉。例如【9,10,1,2,3】,【1,2,11,12,5,6,7】。

贪心思路

贪心策略就是在以上思路发散扩展得来的。以示例1为例:

从左至右扫描数组,10可以看成一个单独的递增序列,长度为1。再扫描到9, 9 比 10 小,此时也能单独看成长度为1的递增序列。这时我们把10这个单独递增序列剔除掉,保留9的序列。因为在长度同样的情况下,保留越小的数,能够加在该数后形成递增的情况越多。扫描到2同理,保留2的单独序列,扫描到5,比2大,直接让长度加1,扫描到3,比2大比5小,此时可以把最长递增序列的第二位换成3,最长长度依然为2。以此类推。

通过扫描数组不断地更新每个位置的数,和添加新位置,最终得到最长递增子序列长度。

添加位置的时机:只要比最后一个位置的数大就添加位置。

更新某个位置的时机:从第一个位置依次往后比较,找到一个小于等于该数的位置,在该位置更新,更新的值为当前扫描的数。

 贪心代码实现

class Solution {
    public int lengthOfLIS(int[] nums) {
        List<Integer> list = new ArrayList<>();
        for(int x : nums){
            if(list.size() == 0) list.add(x); // 第一个数直接放入list
            else{
                if(x > list.get(list.size() - 1)) list.add(x); // 如果大于最后一个位置,直接添加放到新位置
                else{ // 否则从第一个位置开始依次比较找到放入的位置
                    for(int i = 0; i < list.size(); i++){
                        if(x <= list.get(i)) { list.set(i,x); break;} // 找到第一个小于等于的位置更新该位置的值
                    }
                }
            }
        }
        return list.size();// list的大小表示在过程中最多添加了多少位置
    }
}

优化:
在更新某个位置的数时,可以使用二分查找,快速找到该位置进行更新。需要注意的是,找出的位置是严格大于上个位置的数的,在更新left和right的时候需要注意这个细节。

class Solution {
    public int lengthOfLIS(int[] nums) {
        List<Integer> list = new ArrayList<>();
        for(int x : nums){
            if(list.size() == 0) list.add(x);
            else{
                if(x > list.get(list.size() - 1)) list.add(x);
                else{
                    int left = 0, right = list.size() - 1, mid = (left + right) / 2;
                    while(left < right){
                        if(x > list.get(mid)) left = mid + 1;
                        else if(x < list.get(mid)) right = mid;
                        else break;
                        mid = (left + right) / 2;
                    }
                    list.set(mid,x);
                }
            }
        }
        return list.size();
    }
}

结语

欢迎大家在评论区讨论留言,有不理解的地方可以私信或者评论。

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

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

相关文章

MATLAB发票识别系统

课题介绍 该课题为基于MATLAB的发票识别系统。主要识别发票的编号。可定做发票的日期&#xff0c;金额等字段的识别。通过输入图片&#xff0c;校正&#xff0c;定位目标区域&#xff0c;分割&#xff0c;字符分割&#xff0c;模板匹配识别&#xff0c;得出结果。整个设计包含…

spark==windows启动spark集群

下载hadoop3.0.0 https://archive.apache.org/dist/hadoop/core/hadoop-3.0.0/ 下载spark3.5.3 Index of /dist/spark/spark-3.5.0 添加环境变量 HADOOP_HOME SPARK_HOME PATH中添加%HADOOP_HOME%\bin,%HADOOP_HOME%\sbin, %SPARK_HOME%\bin,%SPARK_HOME%\sbin, 启动mast…

(实战)WebApi第10讲:Swagger配置、RESTful与路由重载

一、Swagger配置 1、导入SwashBuckle.AspNetCore包 2、在.NET Core 5框架里的startup.cs文件里配置swagger 3、在.NET Core 6框架里的Program.cs文件里配置swagger 二、RESTful风格&#xff1a;路由重载&#xff0c;HttpGet()括号中加参数 &#xff08;1&#xff09;原则&…

【春秋云镜】CVE-2023-27179

CVE-2023-27179 CVE-2023-27179 是一个影响 Apache Doris 的漏洞。Apache Doris 是一款用于交互式分析的高性能数据库&#xff0c;特别适用于处理大规模的结构化数据。该漏洞属于权限提升漏洞&#xff0c;允许未授权用户以管理员身份执行敏感操作。 具体细节 漏洞类型&#…

chrome编辑替换js文件的图文教程

一、找到要修改替换的js文件 二、将文件保存到本地 三、在本地新建一个文件 路径最好跟你要替换的文件的路径保持一致&#xff0c; 四、选中js文件替换 回到原文件右击选择保存并覆盖 点击完保存并覆盖之后回到替换的新文件中&#xff0c;在自动生成的webpack文件中对文件进…

​CSS之三

CSS三大特性 CSS 有三个非常重要的三个特性:层圣性、继承性、优先级 层叠性 相同选择器给设置相同的样式&#xff0c;此时一个样式就会覆盖(层曼)另一个冲突的样式。层曼性主要解决样式冲突的问题 层叠性原则: - 样式冲突&#xff0c;遵循的原则是就近原则&#xff0c;哪个…

Generating /run/initramfs/rdsosreport.txt

Linux中遇到Generating /run/initramfs/rdsosreport.txt 第一步&#xff1a;首先输入 ls /dev/mapper 第二步&#xff1a;输入 xfs_repair /dev/mapper/centos-root -L 第三步&#xff1a;重启reboot 不说原因了&#xff0c;直接上解决方式&#xff1a; 第一步&#xff1a;首先…

Spark,Anconda在虚拟机实现本地模式部署

如果想要了解模式的概念部分&#xff0c;以及作用请看&#xff1a; Spark学习-CSDN博客 一.在虚拟机安装spark cd /opt/modules 把Anconda和Spark安装包拖拽进去&#xff1a; 解压&#xff1a; tar -zxf spark-3.1.2-bin-hadoop3.2.tgz -C /opt/installs 重命名&#x…

云原生开源开发者沙龙丨AI 应用工程化专场杭州站邀您参会

云原生开源开发者沙龙 AI 原生应用架构专场&#xff0c;邀您一起交流&#xff0c;探索 AI 原生应用的工程化落地&#xff01; 活动简介 AI 驱动的应用程序开发、部署和运维&#xff0c;给应用带来了新的生命力和想象空间。但大部分开发者对 AI 应用的编程框架、可观测体系、网…

OpenCV基本操作(python开发)——(5)轮廓处理

OpenCV基本操作&#xff08;python开发&#xff09;——&#xff08;1&#xff09; 读取图像、保存图像 OpenCV基本操作&#xff08;python开发&#xff09;——&#xff08;2&#xff09;图像色彩操作 OpenCV基本操作&#xff08;python开发&#xff09;——&#xff08;3&…

常用的查询mysql配置命令

1. 查看数据库版本信息 SELECT VERSION();2. 查看数据库所有变量和值 SHOW VARIABLES3. 查询数据库是否区分大小写 SHOW VARIABLES LIKE lower_case_table_names;查询数据库是否支持大小写lower_case_table_names 被设置为 1&#xff0c;即表名不区分大小写。如果值为 1&…

企业数据泄露安全演练(分享)

该文章主要分享作者在XXX企业内部做的一次【数据泄露安全演练】&#xff0c;涉及演练背景、目的、演练流程、剧本设定、预期行为、结果等等。 以下是完整的演练方案&#xff0c;有不足的地方希望大家指出&#xff01;&#xff01; 需要原版方案电子版的可以联系作者获取。 演练…

品牌控价的执行技巧

在当今竞争激烈的商业世界中&#xff0c;品牌的发展犹如在波涛汹涌的大海中航行&#xff0c;而价格管控&#xff0c;无疑是那保驾护航的关键舵手&#xff0c;简称控价。这一举措&#xff0c;绝非仅仅着眼于品牌自身的狭隘利益&#xff0c;实则肩负着更为深远的使命&#xff0c;…

一文详解精细化工行业持续增长的策略与路径解析

随着全球经济的快速发展和科技的不断进步&#xff0c;精细化工行业正面临着前所未有的挑战和机遇。在这个过程中&#xff0c;数字化转型已成为推动行业持续增长的关键因素。精细化工行业&#xff0c;作为化学工业的一个重要分支&#xff0c;其产品广泛应用于医药、农药、涂料、…

akshare股票涨跌幅自定义范围查询:A股、港股、美股

参看&#xff1a;https://stock.hexun.com/2024-10-31/215251914.html 涨幅计算公式&#xff1a;&#xff08;当前价格 - 上一个交易日收盘价&#xff09; 上一个交易日收盘价 100% 。 跌幅计算公式&#xff1a;&#xff08;上一个交易日收盘价 - 当前价格&#xff09; 上一个…

基于Pycharm和Django模型技术的数据迁移

1.配置数据库 在trip_server/settings.py中修改配置&#xff1a; 其格式可访问官网&#xff1a;Settings | Django documentation | Django 1.1 配置数据库 文件地址&#xff1a;trip_server/settings.py 配置前需要创建&#xff08;NaviCat&#xff09;个人数据库 "…

linux命令行的艺术

文章目录 前言基础日常使用文件及数据处理系统调试单行脚本冷门但有用仅限 OS X 系统仅限 Windows 系统在 Windows 下获取 Unix 工具实用 Windows 命令行工具Cygwin 技巧 更多资源免责声明 熟练使用命令行是一种常常被忽视&#xff0c;或被认为难以掌握的技能&#xff0c;但实际…

C++学习路线(数据库部分)四

表的插入 插入数据记录是常见的数据操作&#xff0c;可以显示向表中增加的新的数据记录。在MySQL中可以通过“INSERT INTO”语句来实现插入数据记录&#xff0c;该SQL语句可以通过如下4种方式使用&#xff1a;插入完整数据记录、插入部分数据记录、插入多条数据记录和插入JSON…

供应商图纸外发:如何做到既安全又高效?

供应商跟合作伙伴、客户之间会涉及到图纸外发的场景&#xff0c;这是一个涉及数据安全、效率及合规性的重要环节。供应商图纸发送流程一般如下&#xff1a; 1.申请与审批 采购人员根据需要提出发放图纸的申请并提交审批&#xff1b; 采购部负责人审批发放申请&#xff0c;确…

代码随想录之链表刷题总结

目录 1.链表理论基础 2.移除链表元素 3.设计链表 4.翻转链表 5.两两交换链表中的节点 6.删除链表中的第N个节点 7.链表相交 8.环形链表 1.链表理论基础 链表是一种通过指针串联在一起的线性结构&#xff0c;每一个节点由两部分组成&#xff0c;一个是数据域一个是指针域…