【代码随想录刷题记录】LeetCode27移除元素

news2024/7/6 20:31:35

题目地址

1. 思路

1.1 基本思路及代码的初步实现

基本思路大体上和卡尔老师的想法是一致的,详见代码随想录:数组:移除元素,暴力法大家都能想到,我这里写一下算法时间复杂度为 O ( n ) O(n) O(n)时候的思路(全文都是我思考的过程,不太喜欢照着答案解算法,还是有一个自己的思考过程印象深刻):

  • slow是慢指针,用来记录删除该元素后,每个元素对应的新下标slow
  • fast是快指针,用来记录删除该元素后,新数组的元素对应旧的数组的下标nums[fast]
  • i是循环迭代的变量,从0开始到nums.size()-1,也就是数组的全部元素对应的下标

每一次循环,当目标值val和数组nums[i]的值一致的时候,fast应该继续往下走,即fast++,而slow则要保持在原地,因为slow记录的是新数组的下标,如果删除一个数值的话,当前位置的下标因为删除了一个元素,其对应的是该删除的元素的下一个元素,也就是nums[slow]=nums[fast],fast指向的是新元素的值,slow的值则是fast所指向的新元素的值的下标(相当于向前移动元素);
当目标值val和数组nums[i]的值不一样的时候,不删除当前元素,fast和slow都继续向前走,但是在向前走之前,还要执行nums[slow]=nums[fast],因为可能之前还删除过元素,所以还要继续向前移动元素,而且假设在fast和slow都向前走之后才执行nums[slow]=nums[fast],假如此时fast++后,超出了数组的范围,则fast的值就是不合理的下标值,此时会报错提示越界,所以要在slow++和fast++之前执行nums[slow]=nums[fast]。
而谈到这里,我们还注意到,当i遍历到数组中最后一个元素下标的时候,假如此时nums[i]与val是相等的,则说明最后一个元素也要删除,删除末尾元素,fast不需要再一次移动,因为fast再一次移动就越界了,所以还要在nums[i]与val相等的条件下再加一个判断条件,判断fast是否越界,也就是fast == nums.size()这个条件,然后我们还要返回新数组的长度,如果i遍历到最后,nums[i]与val不相等,则slow和fast都向前移动,slow刚好比新数组的最后元素下标多1(越界),则slow是新数组长度;如果nums[i]与val相等,说明要删除这最后一个元素,此时slow正好指向这个最后删除的元素,也就是新数组越界后的第一个内存位置(对应旧数组的最后一个元素位置),而fast在旧数组的越界1个的位置(nums.size()),此时返回slow也是新数组的长度;假设fast遍历到最后没能遍历到旧数组越界的位置且val与nums[i]相等(比如:[1,2,3,3,3]删去3),我们删除的元素对应的是slow所指向的元素,那删除后,slow自然也是指向新数组越界一个内存位置的地方,也是新数组的长度。
看了这么一大段文字描述,接下来看看代码:

class Solution {
public:
    // 引用传递,直接改nums,是改其本身,不是拷贝
    int removeElement(vector<int>& nums, int val) {
        int slow = 0; //慢指针,用来记录删除该元素后,每个元素对应的新下标slow
        int fast = 0; //快指针,用来记录删除该元素后,新数组的元素对应旧的数组的下标nums[fast]
        //每次循环,如果在数组中遇到目标值val,则slow停一下,fast继续走
        //slow停一下是因为,我们删除一个元素后,slow就不能继续再往下遍历,因为删除了一个元素,后面的下标都要减1
        //所以slow停一次,fast继续向前走
        //删几次,slow就停几次,然后把对应的新元素nums[fast]赋值过来
        //slow是每个元素对应的新下标,所以nums[slow] = nums[fast],这样就能够将新数组的元素和新数组的下标对应起来
        //slow遍历到最后,会在新数组的最后一个元素的下标的基础上slow++
        for(int i = 0; i < nums.size(); i++)
        {
            //当前元素不是要删除的元素,nums[slow] = nums[fast]
            //因为新数组还是旧数组(没删除元素),所以[slow]对应的还是nums[fast]
            //就算之前slow停止了一次,删除了元素,但是到本元素nums[i]的时候
            //由于当前的元素不是要删除的元素,所以新数组和旧数组(之前删过元素的数组)一样
            if(val != nums[i])
            {
                //先执行是防止fast越界(针对旧数组)
                nums[slow] = nums[fast];
                slow++;
                fast++;
            }
            //当前是要删除的元素,slow要停一下,fast继续走
            else
            {
                /*当i遍历到数组中最后一个元素下标的时候,
                假如此时nums[i]与val是相等的,
                则说明最后一个元素也要删除,
                删除末尾元素,fast不需要再一次移动,
                因为fast再一次移动就越界了,
                所以还要在nums[i]与val相等的条件下再加一个判断条件,
                判断fast是否越界,也就是fast == nums.size()这个条件*/
                fast++;
                if(fast == nums.size())
                {
                    /*当i遍历到数组中最后一个元素下标的时候,
                    如果nums[i]与val相等,说明要删除这最后一个元素,
                    此时slow正好指向这个最后删除的元素,
                    也就是新数组越界后的第一个内存位置
                    (对应旧数组的最后一个元素位置),
                    而fast在旧数组的越界1个的位置(nums.size()),
                    此时返回slow也是新数组的长度*/
                    return slow;
                }
                else
                {
                    nums[slow] = nums[fast];
                }
            }
            
        }
        /*返回新数组的长度,如果i遍历到最后,
        nums[i]与val不相等,则slow和fast都向前移动
        slow刚好比新数组的最后元素下标多1(越界),
        则slow是新数组长度*/
        /*假设fast遍历到最后没能遍历到旧数组越界的位置且val与nums[i]相等
        (比如:[1,2,3,3,3]删去3),
        我们删除的元素对应的是slow所指向的元素,
        那删除后,slow自然也是指向新数组越界一个内存位置的地方,
        也是新数组的长度。*/
        return slow;
    }
};

1.2 三种基本情况

这个算法能遇到的情况如下:

    1. 要移除的值在数组的中且不在末尾
    1. 要移除的值在数组末尾(逻辑上不用移动,直接舍弃末尾)
    1. 要移除的值不在数组中

下面根据这三种基本情况,我们来模拟一下其运行过程

1.3 模拟运行过程

1.3.1 要移除的值在数组的中且不在末尾

假设有这样一个数组nums[1, 2, 2, 3],val=2:
i=0
由于 nums [ i ] = nums [ 0 ] = 1 ≠ val = 2 \text{nums}[i]=\text{nums}[0]=1\ne \text{val}=2 nums[i]=nums[0]=1=val=2,则

i=1
由于 nums [ i ] = nums [ 1 ] = 2 = val = 2 \text{nums}[i]=\text{nums}[1]=2= \text{val}=2 nums[i]=nums[1]=2=val=2,则

又由于 fast = 2 ≠ nums.size() = 4 \text{fast}=2\ne\text{nums.size()}=4 fast=2=nums.size()=4,则

注意到,slow在此处停止移动一次,因为找到了要删除的元素
i=2
由于 nums [ i ] = nums [ 2 ] = 2 = val = 2 \text{nums}[i]=\text{nums}[2]=2= \text{val}=2 nums[i]=nums[2]=2=val=2,则

又由于 fast = 3 ≠ nums.size() = 4 \text{fast}=3\ne\text{nums.size()}=4 fast=3=nums.size()=4,则

注意到,slow在此处停止移动一次,因为找到了要删除的元素

i=3
由于 nums [ i ] = nums [ 3 ] = 3 ≠ val = 2 \text{nums}[i]=\text{nums}[3]=3\ne \text{val}=2 nums[i]=nums[3]=3=val=2,则



此时循环结束,可以看到slow恰好是在新数组的越界一个单元的位置,也就是新数组的长度,即return slow,示意图如下:

1.3.1 要移除的值在数组末尾

假设有这样一个数组nums[1, 2, 2, 3],val=3:
i=0
由于 nums [ i ] = nums [ 0 ] = 1 ≠ val = 3 \text{nums}[i]=\text{nums}[0]=1\ne \text{val}=3 nums[i]=nums[0]=1=val=3,则

i=1
由于 nums [ i ] = nums [ 1 ] = 2 ≠ val = 3 \text{nums}[i]=\text{nums}[1]=2\ne \text{val}=3 nums[i]=nums[1]=2=val=3,则

i=2
由于 nums [ i ] = nums [ 2 ] = 2 ≠ val = 3 \text{nums}[i]=\text{nums}[2]=2\ne \text{val}=3 nums[i]=nums[2]=2=val=3,则

i=3
由于 nums [ i ] = nums [ 3 ] = 3 = val = 3 \text{nums}[i]=\text{nums}[3]=3= \text{val}=3 nums[i]=nums[3]=3=val=3,则

又由于 fast = 3 ≠ nums.size() = 4 \text{fast}=3\ne\text{nums.size()}=4 fast=3=nums.size()=4,则return slow;

1.3.2 要移除的值不在数组中

假设有这样一个数组nums[1, 2, 2, 3],val=4:
i=0
由于 nums [ i ] = nums [ 0 ] = 1 ≠ val = 4 \text{nums}[i]=\text{nums}[0]=1\ne \text{val}=4 nums[i]=nums[0]=1=val=4,则

i=1
由于 nums [ i ] = nums [ 1 ] = 2 ≠ val = 4 \text{nums}[i]=\text{nums}[1]=2\ne \text{val}=4 nums[i]=nums[1]=2=val=4,则

i=2
由于 nums [ i ] = nums [ 2 ] = 2 ≠ val = 4 \text{nums}[i]=\text{nums}[2]=2\ne \text{val}=4 nums[i]=nums[2]=2=val=4,则

i=3
由于 nums [ i ] = nums [ 3 ] = 3 ≠ val = 4 \text{nums}[i]=\text{nums}[3]=3\ne \text{val}=4 nums[i]=nums[3]=3=val=4,则

1.4 代码进一步优化

至此,算法的解的三种情况我们都举了例子论证完了,但是注意观察,每次i自增的时候,fast在if和else两个条件里都自增,似乎fast可以放到if else条件外面自增,我们还注意到,if条件下,我们先执行的nums[slow]=nums[fast]赋值语句,然后才进行的fast++语句,这就说明,我们不能简单地将fast放在if else条件之外,我们思考一下,我们的if(val!=nums[i])的条件里,是先执行的nums[slow]=nums[fast]赋值语句而后进行的slow和fast向前移动一个单位,这是因为,如果我们先移动,再赋值,当fast移动到数组越界处的时候,nums[slow]=nums[fast]这条语句就会报越界的错误,所以我们需要判断一下fast是否越界,如果越界直接返回slow,并且将else的条件下的代码稍微变动,这样就变成了如下的等价形式(也是可以通过的):

class Solution {
public:
    // 引用传递,直接改nums,是改其本身,不是拷贝
    int removeElement(vector<int>& nums, int val) {
        int slow = 0; //慢指针,用来记录删除该元素后,每个元素对应的新下标slow
        int fast = 0; //快指针,用来记录删除该元素后,新数组的元素对应旧的数组的下标nums[fast]
        for(int i = 0; i < nums.size(); i++)
        {
            if(val != nums[i])
            {
                slow++;
                fast++;
                //先移动slow和fast,再判断其是否越界,这样就算fast越界,也能直接返回结果
                if(fast == nums.size())
                {
                    return slow;
                }
                nums[slow] = nums[fast];
            }
            else
            {
                fast++;
                if(fast == nums.size())
                {
                    return slow;
                }
                nums[slow] = nums[fast];
            }
            
        }
        return slow;
    }
};

此时,fast++在两个条件里都有,可以单独拿到if else外面(也是可以通过的):

class Solution {
public:
    // 引用传递,直接改nums,是改其本身,不是拷贝
    int removeElement(vector<int>& nums, int val) {
        int slow = 0; //慢指针,用来记录删除该元素后,每个元素对应的新下标slow
        int fast = 0; //快指针,用来记录删除该元素后,新数组的元素对应旧的数组的下标nums[fast]
        for(int i = 0; i < nums.size(); i++)
        {
            fast++;
            if(val != nums[i])
            {
                slow++;
                //先移动slow和fast,再判断其是否越界,这样就算fast越界,也能直接返回结果
                if(fast == nums.size())
                {
                    return slow;
                }
                nums[slow] = nums[fast];
            }
            else
            {
                if(fast == nums.size())
                {
                    return slow;
                }
                nums[slow] = nums[fast];
            }
            
        }
        return slow;
    }
};

然后我们又发现,每次i自增的时候,fast必然自增,是不是可以将fast 和 i等效,直接用fast来进行迭代,而且使用fast进行循环迭代,保证不能越界,因为我们最后返回的是slow,之前的情况下,当i=3的时候,如果要删除的元素不在数组之中,fast++会导致越界,但是我们已经证明slow所指才是我们需要的新数组的长度,于是我们尝试将fast等效i在循环中的迭代作用,我们思考一下,for循环的迭代变量一般是等到for循环中的代码执行后,再进行自增,而我们既然选择fast作为迭代变量进行自增,那么我们就要想方设法先将最上面的fast++语句放到for循环内的最后执行,所以我们还是要看一下之前的代码:

class Solution {
public:
    // 引用传递,直接改nums,是改其本身,不是拷贝
    int removeElement(vector<int>& nums, int val) {
        int slow = 0; //慢指针,用来记录删除该元素后,每个元素对应的新下标slow
        int fast = 0; //快指针,用来记录删除该元素后,新数组的元素对应旧的数组的下标nums[fast]
        for(int i = 0; i < nums.size(); i++)
        {
            if(val != nums[i])
            {
                nums[slow] = nums[fast];
                slow++;
                fast++;
            }
            else
            {
                fast++;
                if(fast == nums.size())
                {
                    return slow;
                }
                else
                {
                    nums[slow] = nums[fast];
                }
            }
            
        }
        return slow;
    }
};

我们原来的代码中,在if(val != nums[i])条件里,nums[slow] = nums[fast];先执行的初衷是为了防止fast和slow越界,而else条件里加上了防止fast越界的情况,那我们也把else条件里的赋值语句nums[slow] = nums[fast];放到fast++;语句前面来执行,得到了如下代码(顺利通过):

class Solution {
public:
    // 引用传递,直接改nums,是改其本身,不是拷贝
    int removeElement(vector<int>& nums, int val) {
        int slow = 0; //慢指针,用来记录删除该元素后,每个元素对应的新下标slow
        int fast = 0; //快指针,用来记录删除该元素后,新数组的元素对应旧的数组的下标nums[fast]
        for(int i = 0; i < nums.size(); i++)
        {
            if(val != nums[i])
            {
                nums[slow] = nums[fast];
                slow++;
                fast++;
            }
            else
            {
                nums[slow] = nums[fast];
                fast++;
            }
            
        }
        return slow;
    }
};

我们再进行一下等效变换,将两个if else的条件中的重复部分取出来,nums[slow] = nums[fast];语句在判断条件前,fast++;语句在判断条件后,得到如下代码(顺利通过):

class Solution {
public:
    // 引用传递,直接改nums,是改其本身,不是拷贝
    int removeElement(vector<int>& nums, int val) {
        int slow = 0; //慢指针,用来记录删除该元素后,每个元素对应的新下标slow
        int fast = 0; //快指针,用来记录删除该元素后,新数组的元素对应旧的数组的下标nums[fast]
        for(int i = 0; i < nums.size(); i++)
        {
            nums[slow] = nums[fast];
            if(val != nums[i])
            {
                slow++;
            }
            fast++;
            
        }
        return slow;
    }
};

此时我们观察,fast自增操作已在循环体内部的最后部分了,那就可以让fast代替i做迭代变量了,修改代码为(顺利通过):

class Solution {
public:
    // 引用传递,直接改nums,是改其本身,不是拷贝
    int removeElement(vector<int>& nums, int val) {
        int slow = 0; //慢指针,用来记录删除该元素后,每个元素对应的新下标slow
        for(int fast = 0; fast < nums.size(); fast++)
        {
            nums[slow] = nums[fast];
            if(val != nums[fast])
            {
                slow++;
            }       
        }
        return slow;
    }
};

但是修改到这个时候,它的实际复杂度并不好,我们继续思考,有些时候,其实nums[slow] = nums[fast];赋值是没必要的,考虑刚才的例子:
假设有这样一个数组nums[1, 2, 2, 3],val=2:
i=1
由于 nums [ i ] = nums [ 1 ] = 2 = val = 2 \text{nums}[i]=\text{nums}[1]=2= \text{val}=2 nums[i]=nums[1]=2=val=2,则

又由于 fast = 2 ≠ nums.size() = 4 \text{fast}=2\ne\text{nums.size()}=4 fast=2=nums.size()=4,则

我们先是移动了fast,然后才进行nums[slow]=nums[fast]的赋值操作,但是我们修改后的代码在此处会这样操作:
先进行一次赋值,这个赋值其实是无效的,在nums[fast]和val相等的时候,我们只需要让slow原地等待一次,fast向前走一次,此时没必要进行赋值一次,只有在不等的时候,赋值是有效的,赋值相当于逻辑上从后向前移动

然后再进行指针的移动:

它将真正从后向前赋值的过程放到了val != nums[fast]中执行,所以下一次循环必须先执行赋值操作再进行slow的自增,我们完全可以把赋值过程放到val != nums[fast]条件中执行,因为我们只需要在不等的情况下移动元素。

1.5 代码最终实现

有了上面这些思考,我们发现,其实还是最开始的思路,相等的情况下,slow等一下(不自增),fast自增,不相等的情况下,slow和fast一起自增,并且还要执行赋值过程,而且要注意是先赋值再让slow自增,原因刚才已经提到了,代码最终优化为(顺利通过):

class Solution {
public:
    // 引用传递,直接改nums,是改其本身,不是拷贝
    int removeElement(vector<int>& nums, int val) {
        int slow = 0; //慢指针,用来记录删除该元素后,每个元素对应的新下标slow
        for(int fast = 0; fast < nums.size(); fast++)
        {
            if(val != nums[fast])
            {
                // 下一次循环必须先执行赋值操作再进行slow的自增(对应上文)
                nums[slow] = nums[fast];
                slow++;
            }       
        }
        return slow;
    }
};

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

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

相关文章

短视频矩阵营销系统 poihuoqu 任意文件读取漏洞复现

0x01 产品简介 短视频矩阵营销系统是由北京华益云数据科技有限公司开发的一款产品,这家公司专注于抖音短视频矩阵营销系统的研发,致力于为企业提供全方位的短视频营销解决方案。华益云抖销短视频矩阵系统可以帮助企业快速搭建多个短视频账号,实现内容的批量制作和发布,提高…

ShardingSphere 5.x 系列【25】 数据分片原理之 SQL 解析

有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot 版本 3.1.0 本系列ShardingSphere 版本 5.4.0 源码地址:https://gitee.com/pearl-organization/study-sharding-sphere-demo 文章目录 1. 分片执行流程1.1 Simple Push Down1.2 SQL Federation2. SQL 解析2.1 解析…

SystemUI KeyButtonView setDarkIntensity 解析

继承自 ImageView KeyButtonDrawable intensity为0时按键颜色为白色。 intensity为1时黑色为的调用堆栈&#xff1a; java.lang.NullPointerException: Attempt to invoke virtual method int java.lang.String.length() on a null object referenceat com.android.systemui.…

嵌入式学习Day18

一、输入两个数&#xff0c;实现排序 代码&#xff1a; #!/bin/bashread -p "please enter n m:" n m if [ $n -gt $m ] thentemp$nn$mm$temp fi echo $n $m运行结果 二、输入一个数判断是否水仙花数 代码&#xff1a; echo narcissistic number read -p "p…

店匠科技技术产品闪耀,引领新质生产力发展

在科技飞速发展的今天,新质生产力正成为推动社会进步和经济高质量发展的核心力量。店匠科技,作为一家致力于为全球B2C电商提供产品和技术解决方案的领先企业,其技术产品不仅体现了新质生产力的创新特质,更在推动电商行业转型升级中发挥了重要作用。 新质生产力,以创新为主导,摆…

Java设计模式 _创建型模式_原型模式(Cloneable)

一、原型模式 1、原型模式&#xff08;Prototype Pattern&#xff09;是用于创建重复的对象&#xff0c;同时又能保证性能比较好。一般对付出较大代价获取到的实体对象进行克隆操作&#xff0c;可以提升性能。 2、实现思路&#xff1a; &#xff08;1&#xff09;、需要克隆的…

macOS 一些系统图标的存放位置 icns

macOS 一些系统图标的存放位置 icns macOS 中有很多好看的图标&#xff0c;有时候就想用一下它&#xff0c;我来告诉你他们的具体位置。 系统图标位置&#xff0c;像各种通用文件类型的图标都在这里面&#xff0c;里面好多高清的系统图标 /System/Library/CoreServices/Core…

济宁市中考报名照片要求及手机拍照采集证件照方法

随着中考报名季的到来&#xff0c;并且进入了中考报名演练阶段&#xff0c;济宁市的广大考生和家长都开始忙碌起来。报名过程中&#xff0c;上传一张符合要求的证件照是必不可少的环节。本文将详细介绍济宁市中考报名照片的具体要求&#xff0c;并提供一些实用的手机拍照采集证…

flutter开发实战-build apk名称及指令abiFilters常用gradle设置

flutter开发实战-build apk名称及指令abiFilters常用gradle设置 最近通过打包flutter build apk lib/main.dart --release&#xff0c;发现apk命名规则需要在build.gradle设置。这里记录一下。 一、apk命名规则 在android/app/build.gradle中需要设置 android.applicationVa…

TinyML之Hello world----基于Arduino Nano 33 BLE Sense Rev2的呼吸灯

早期版本的Hello World 这应该是一个逼格比较高的呼吸灯了&#xff0c;用ML来实现呼吸灯功能&#xff0c;之前已经有大佬发过类似的文章&#xff1a;https://blog.csdn.net/weixin_45116099/article/details/126310816 当前版本的Hello World 这是一个ML的入门例程&#xff…

vue3【详解】vue3 比 vue2 升级了哪些重要的功能?

改用 createApp 初始化实例 vue2 使用 new Vue() 初始化实例 vue3 使用 Vue.createApp() 初始化实例 新增 emits 选项 vue3 选项式API中新增了emits 选项&#xff0c;用于显示声明组件中的自定义事件&#xff0c;自定义事件的名称&#xff0c;需用 on 开头。 export default {…

arcgis js 4.x加载SceneLayer并实现基于属性查询定位及高亮

一、代码 <!DOCTYPE html> <html> <head><meta charset"utf-8" /><meta name"viewport" content"widthdevice-width, initial-scale1,maximum-scale1,user-scalableno"><title></title><link rel…

表情识别 | LBP+SVM实现脸部动态特征的人脸表情识别程序(Matlab)

表情识别 | LBPSVM实现脸部动态特征的人脸表情识别程序&#xff08;Matlab&#xff09; 目录 表情识别 | LBPSVM实现脸部动态特征的人脸表情识别程序&#xff08;Matlab&#xff09;预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1 运行环境 程序运行在Windows系统下&am…

PyQt5中QTablewidget生成右键菜单

QTablewidget生成右键菜单&#xff0c;需要自定义一个QTablewidget类 import sys from PyQt5.QtWidgets import QApplication, QTableWidget, QTableWidgetItem, QMenu, QAction, QDialog from PyQt5.QtCore import Qt from PyQt5 import QtCoreclass CustomTableWidget(QTabl…

数字文旅重塑旅游发展新格局:以数字化转型为突破口,提升旅游服务的智能化水平,为游客带来全新的旅游体验

随着信息技术的迅猛发展&#xff0c;数字化已成为推动各行各业创新发展的重要力量。在旅游业领域&#xff0c;数字文旅的兴起正以其强大的驱动力&#xff0c;重塑旅游发展的新格局。数字文旅以数字化转型为突破口&#xff0c;通过提升旅游服务的智能化水平&#xff0c;为游客带…

AIGC:开启内容创作新纪元,我们如何看待它的影响与前景?

AIGC的概念 AIGC&#xff08;Artificial Intelligence Generated Content&#xff09;的概念主要是指人工智能生成内容。 这是一种新的人工智能技术&#xff0c;它利用人工智能模型&#xff0c;根据给定的主题、关键词、格式、风格等条件&#xff0c;自动生成各种类型的文本、图…

目标检测与追踪AI算法模型及边缘计算智能分析网关V4的算法应用

目标检测与追踪是计算机视觉领域中的一个重要任务&#xff0c;主要用于识别图像或视频中的目标&#xff0c;并跟踪它们的运动轨迹。针对这一任务&#xff0c;有许多先进的AI算法模型&#xff0c;例如&#xff1a; YOLO&#xff08;You Only Look Once&#xff09;&#xff1a;…

SpringCloudAlibaba:2.1nacos

概述 概述 简介 Nacos是阿里巴巴开源的服务注册中心以及配置中心 Nacos注册中心Eureka 服务配置Config 服务总线Bus 官网 Nacos官网 | Nacos 官方社区 | Nacos 下载 | Nacos 名字由来 Naming&#xff1a;名字 Configurations&#xff1a;配置 Service&#xff1a;服务 功能…

使用Keil移植工程时修改单片机型号参数

系列文章目录 STM32单片机系列专栏 C语言术语和结构总结专栏 当使用Keil对STM32系列单片机开发时&#xff0c;如果使用的是库函数&#xff0c;那么不同型号单片机的工程项目文件是可以直接移植的。只需要按照下面的步骤修改对应的芯片&#xff0c;就可以直接将工程移植过去&a…

Git学习路线

1.看书 把这本书看懂就可以了&#xff1b;这个是比较专业的一本书&#xff1b;比较系统&#xff1b;没有书的可以私信我 2.理解Git多个分区和多个分支 多个分区包括&#xff1a;工作区、暂存区、本地仓、本地的远端仓信息、远端仓 多个分区的状态 分支及其变化 3.记住常用命令…