【探寻C++之旅】C++ 智能指针完全指南:从原理到实战,彻底告别内存泄漏

【探寻C++之旅】C++ 智能指针完全指南:从原理到实战,彻底告别内存泄漏
QQ20251023-230438

前言

作为 C++ 开发者,你是否曾因以下场景头疼不已?函数中new了数组,却因异常抛出导致后续delete没执行,排查半天定位到内存泄漏;多模块共享一块内存,不知道该由谁负责释放,最后要么重复释放崩溃,要么漏释放泄漏;用了auto_ptr后,拷贝对象导致原对象 “悬空”,访问时直接崩溃却找不到原因。

如果你有过这些经历,那智能指针一定是你必须掌握的现代 C++ 工具。它基于 RAII 思想,自动管理动态资源,让你无需手动delete,从根源上减少内存泄漏风险。今天,我们就从 “为什么需要智能指针” 到 “不同智能指针的实战场景”,带你系统掌握这一核心特性。

请君浏览

一、智能指针的诞生:解决手动管理内存的 “千古难题”

在 C++ 中,内存泄漏的核心原因往往是 “资源申请与释放不匹配”—— 尤其是当程序流程被异常、分支跳转打断时,手动编写的delete可能永远不会执行。

**内存泄漏:**内存泄漏指因为疏忽或错误造成程序未能释放已经不再使⽤的内存,⼀般是忘记释放或者发⽣异常释放程序未能执⾏导致的。内存泄漏并不是指内存在物理上的消失,⽽是应⽤程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因⽽造成了内存的浪费。

**危害:**普通程序运⾏⼀会就结束了,出现内存泄漏问题也不大,进程正常结束,⻚表的映射关系解除,物理内存也可以释放。但⻓期运⾏的程序出现内存泄漏影响就很⼤了,如操作系统、后台服务、⻓时间运⾏的客⼾端等等,不断出现内存泄漏会导致可⽤内存不断变少,各种功能响应越来越慢,最终卡死。

1.1 一个典型的内存泄露场景

若函数中存在异常抛出,裸指针会因delete未执行导致泄漏,例如:我们在Func函数中new了两个数组,但如果Divide抛异常,后续的delete会被跳过,导致内存泄漏,如下面代码所示:

doubleDivide(int a,int b){// 当b == 0时抛出异常if(b ==0){throw"Divide by zero condition!";}else{return(double)a /(double)b;}}voidFunc(){int* array1 =newint[10];int* array2 =newint[10];//...int len, time; cin >> len >> time; cout <<Divide(len, time)<< endl;// ... cout <<"delete []"<< endl;delete[] array1;delete[] array2;}intmain(){try{Func();}catch(...){ cout <<"abnormal"<< endl;}return0;}

可以看到当Divide抛出异常时,我们new的两个数组就无法正常释放,导致内存泄漏:

QQ20251028-214941

即使我们加了try-catch,若new array2时本身抛异常,array1也无法释放,代码会变得臃肿且脆弱:

voidFunc(){int* array1 =newint[10];int* array2 =newint[10];//...try{int len, time; cin >> len >> time; cout <<Divide(len, time)<< endl;}catch(...){ cout <<"delete []"<< endl;delete[] array1;delete[] array2;throw;// 异常重新抛出,捕获到什么抛出什么}// ... cout <<"delete []"<< endl;delete[] array1;delete[] array2;}

即便如此,因为new本⾝也可能抛异常,连续的两个new和下⾯的Divide都可能会抛异常,让我们处理起来很麻烦,这种场景下,我们需要一种 “能自动释放资源” 的机制 —— 这就是智能指针的设计初衷。

1.2 智能指针的核心:RAII 思想

在 C++ 中,智能指针是一种封装了裸指针(raw pointer)的模板类,其核心作用是自动管理动态内存,避免因手动调用delete疏忽导致的内存泄漏、重复释放或悬空指针等问题。它基于RAII(资源获取即初始化) 机制:在智能指针构造时获取资源(如动态内存),在析构时自动释放资源,无需手动干预。

智能指针的本质是RAII(Resource Acquisition Is Initialization,资源获取即初始化) 的实践:

  • 资源(如动态内存、文件句柄)在智能指针对象构造时获取,并委托给该对象管理;
  • 智能指针对象析构时自动释放资源,无论程序是正常结束还是异常退出(对象生命周期由作用域管理,析构总会执行);
  • 为了方便使用,智能指针会重载*->[]等运算符,模拟原生指针的行为。

基于此,我们可以先来自己简单粗略的实现一下智能指针,如下面代码所示:

template<classT>classSmartPtr{public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){ cout <<"delete[] "<< _ptr << endl;delete[] _ptr;}// 重载运算符,模拟指针的⾏为,⽅便访问资源 T&operator*(){return*_ptr;} T*operator->(){return _ptr;} T&operator[](size_t i){return _ptr[i];}private: T* _ptr;};

有了智能指针,我们的func函数就可以改成:

voidFunc(){ SmartPtr<int> sp1 =newint[10]; SmartPtr<int> sp2 =newint[10];int len, time; cin >> len >> time; cout <<Divide(len, time)<< endl;}

通过运行结果我们可以看到即便Divide函数抛出异常也不会影响我们new出来两个数组的释放:

QQ20251028-224522

虽然我们上面设计的智能指针是十分粗略的,但是可以看到即便如此也可以帮助我们解决内存泄漏的问题。那么接下来让我们看一看标准库中是如何设计智能指针的。

二、C++ 标准库智能指针:4 种指针的特性与适用场景

C++ 标准库(<memory>头文件)提供了 4 种智能指针,它们分别是auto_ptr、unique_ptr、shared_ptr、weak_ptr。其中除了auto_ptr外的三个都是在C++11中提出的。除weak_ptr外均遵循 RAII,原理上而⾔主要是解决智能指针拷⻉时的思路不同。下面来让我们看一看它们之间的区别,以及在不同场景下该如何选择。

2.1 auto_ptr:被淘汰的 “过渡品”(C++98)

auto_ptr是C++98时设计出来的智能指针,设计思路是 “拷贝时转移资源管理权”—— 但这是一个致命缺陷:拷贝后原对象会 “悬空”(资源指针被置空),后续访问原对象会触发空指针错误。如下面代码所示:

intmain(){ auto_ptr<Date>ap1(new Date);// ap1管理Date对象 auto_ptr<Date>ap2(ap1);// 拷贝:ap2获取管理权,ap1->_ptr被置空// ap1->_year++; // 崩溃!ap1已悬空,访问空指针return0;}

auto_ptr拷贝的原理是将自己的指针赋值给新的auto_ptr,并且使自己的指针置为空,这样我们再去访问这个对象时,就会因为访问空指针而导致报错。可以看到,当我们将ap1拷贝给ap2后,我们就访问ap1时就会报错,因为ap1已经悬空,我们不能去访问空指针。

正因如此,C++11 推出后,auto_ptr被明确标记为 “不推荐使用”,多数公司的编码规范也会直接禁止它。

相关文档auto_ptr

2.2 unique_ptr:不可共享的 “独占指针”(C++11)

unique_ptr(唯⼀指针)的设计思路是禁止拷贝、仅支持移动—— 确保同一时间只有一个unique_ptr管理资源,从根源上避免 “多个指针竞争释放” 的问题。它是 C++11 中最常用的智能指针之一,适用于 “资源无需共享” 的场景。

核心特性:

  1. 不可复制,只能移动:由于是独占所有权,unique_ptr 不支持复制构造或赋值(会编译报错),但可以通过 std::move 转移所有权(转移后原 unique_ptr 会失效,变为空指针)。
  2. 高效轻量:无额外引用计数开销,性能接近裸指针。
intmain(){// 创建unique_ptr(推荐用make_unique,C++14起支持) std::unique_ptr<int> ptr1 = std::make_unique<int>(10); std::cout <<*ptr1 << std::endl;// 输出:10// 转移所有权(ptr1失效,ptr2拥有对象) std::unique_ptr<int> ptr2 = std::move(ptr1);if(ptr1 ==nullptr){ std::cout <<"ptr1已失效"<< std::endl;// 输出:ptr1已失效}// 超出作用域时,ptr2析构,自动释放内存return0;}

适用场景:管理独占资源(如局部动态对象、类的成员变量),作为函数返回值(无需手动释放,避免返回裸指针的风险)。

相关文档unique_ptr

2.3 shared_ptr:可共享的 “计数指针”(C++11)

shared_ptr(共享指针)允许多个 shared_ptr 共同拥有同一个动态对象。。也就是说支持资源共享,其核心是通过 “引用计数” 跟踪管理资源的指针数量:

  • 当新的shared_ptr拷贝或赋值时,引用计数+1
  • shared_ptr析构时,引用计数-1
  • 当引用计数减至0时,代表当前是最后一个管理资源的指针,自动释放资源。

核心特性:

  1. 引用计数透明管理:用户无需手动维护计数,use_count()方法可查看当前计数;
  2. 支持拷贝与移动:拷贝时计数+1,移动时计数不变(原对象悬空);
intmain(){// 创建shared_ptr(推荐用make_shared,更高效) std::shared_ptr<int> ptr1 = std::make_shared<int>(20); std::cout <<"引用计数:"<< ptr1.use_count()<< std::endl;// 输出:1// 复制ptr1,引用计数+1 std::shared_ptr<int> ptr2 = ptr1; std::cout <<"引用计数:"<< ptr1.use_count()<< std::endl;// 输出:2// ptr2超出作用域,引用计数-1{ std::shared_ptr<int> ptr3 = ptr1; std::cout <<"引用计数:"<< ptr1.use_count()<< std::endl;// 输出:3} std::cout <<"引用计数:"<< ptr1.use_count()<< std::endl;// 输出:2// 最后ptr1和ptr2析构,引用计数变为0,内存释放return0;}

shared_ptr的构造有两种方式,除了⽀持⽤指向资源的指针构造,还⽀持 make_shared ⽤初始化资源对象的值直接构造:

  1. shared_ptr<Date> sp(new Date(2024, 10, 1));:两次内存分配(一次给Date对象,一次给引用计数);

auto sp = make_shared<Date>(2024, 10, 1);:一次内存分配(同时存储Date对象和引用计数),效率更高,且避免内存泄漏风险(若new成功但计数分配失败,new的对象无法释放)。

template<classT,class... Args> shared_ptr<T>make_shared(Args&&... args);

对于shared_ptrunique_ptr,我们还需要注意下面几点:

  • shared_ptrunique_ptr 都⽀持了operator bool的类型转换:如果智能指针对象是⼀个空对象没有管理资源,则返回false,否则返回true,意味着我们可以直接把智能指针对象给if判断是否为空。

shared_ptrunique_ptr 的构造函数都使⽤explicit 修饰,防⽌普通指针隐式类型转换成智能指针对象。

// 报错:无法进行隐式类型转换 shared_ptr<Date> sp5 =newDate(2024,9,11); unique_ptr<Date> sp6 =newDate(2024,9,11);
使用shared_ptr还要注意线程安全问题,shared_ptr的引⽤计数对象在堆上,如果多个shared_ptr对象在多个线程中,进⾏shared_ptr的拷贝和析构时会访问修改引⽤计数,就会存在线程安全问题,所以shared_ptr引⽤计数是需要加锁或者原⼦操作保证线程安全的。
相关文档shared_ptr

2.4 weak_ptr:解决循环引用的 “辅助指针”(C++11)

weak_ptr(弱指针)完全不同于上⾯的智能指针,是一个特殊的智能指针。它不⽀持RAII,也就意味着不能⽤它直接管理资源,weak_ptr的产⽣本质是要解决shared_ptr的⼀个循环引⽤导致内存泄漏的问题。

什么是循环引用呢?shared_ptr 的引用计数机制可能导致循环引用问题:两个对象互相持有对方的 shared_ptr,此时它们的引用计数永远不会变为 0,导致内存泄漏。

例如:我们有两个链表结点,把它们分别交给智能指针shared_ptr管理,然后将它们连接起来,如下面代码所示:

structListNode{int _data; shared_ptr<ListNode> _next;// 指向后一个节点 shared_ptr<ListNode> _prev;// 指向前一个节点~ListNode(){ cout <<"~ListNode()"<< endl;}};intmain(){ shared_ptr<ListNode>n1(new ListNode); shared_ptr<ListNode>n2(new ListNode); cout << n1.use_count()<< endl;// 1 cout << n2.use_count()<< endl;// 1 n1->_next = n2;// n2的计数+1 → 2 n2->_prev = n1;// n1的计数+1 → 2// 析构n1和n2:计数各减1 → 1(而非0)// 节点资源永远无法释放,内存泄漏!return0;}

循环引用的逻辑链:n1->_next依赖n2释放,n2->_prev依赖n1释放,最终谁都无法释放。

QQ20251028-235746

那么该如何解决循环引用呢?这时候weak_ptr就派上用场了。weak_ptr 是一种弱引用智能指针,它不拥有对象的所有权,也不会增加引用计数,因此我们修改链表节点为weak_ptr后,循环引用被打破:

structListNode{int _data; weak_ptr<ListNode> _next;// 改为weak_ptr,不增加计数 weak_ptr<ListNode> _prev;~ListNode(){ cout <<"~ListNode()"<< endl;}};intmain(){ shared_ptr<ListNode>n1(new ListNode); shared_ptr<ListNode>n2(new ListNode); n1->_next = n2;// 不增加n2的计数(仍为1) n2->_prev = n1;// 不增加n1的计数(仍为1)// 析构n1和n2:计数各减1 → 0,节点资源正常释放return0;}

weak_ptr不⽀持RAII,也不⽀持访问资源,所以weak_ptr构造时不⽀持绑定到资源,只⽀持绑定到shared_ptr,绑定到shared_ptr时,不增加shared_ptr的引⽤计数,那么就可以解决上述的循环引⽤问题。

QQ20251029-000446

weak_ptr的核心特性是:绑定到shared_ptr时不增加引用计数,仅作为 “观察者” 跟踪资源是否有效。weak_ptr也没有重载operator*operator->等,因为他不参与资源管理 。那么如果它绑定的shared_ptr已经释放了资源,那么它去访问资源就是很危险的,为此它提供了两个关键方法:

  • expired():判断绑定的shared_ptr资源是否已释放(计数为 0);
  • lock():若资源有效,返回一个shared_ptr(计数 + 1,安全访问资源);若无效,返回空shared_ptr
intmain(){ std::shared_ptr<string>sp1(newstring("111111")); std::shared_ptr<string>sp2(sp1); std::weak_ptr<string> wp = sp1; cout << wp.expired()<< endl; cout << wp.use_count()<< endl;// sp1和sp2都指向了其他资源,则weak_ptr就过期了 sp1 = make_shared<string>("222222"); cout << wp.expired()<< endl; cout << wp.use_count()<< endl; sp2 = make_shared<string>("333333"); cout << wp.expired()<< endl; cout << wp.use_count()<< endl; wp = sp1;//std::shared_ptr<string> sp3 = wp.lock();auto sp3 = wp.lock(); cout << wp.expired()<< endl; cout << wp.use_count()<< endl;*sp3 +="###"; cout <<*sp1 << endl;return0;}

weak_ptr仅作为shared_ptr的辅助工具,解决循环引用(如链表、树、图等数据结构的节点引用)。

相关文档weak_ptr

2.5 删除器

智能指针析构时默认是进⾏delete释放资源,这也就意味着如果不是new出来的资源,交给智能指针管理,析构时就会崩溃。**因此当管理非new资源(如new[]、文件指针)时,需自定义删除器。**智能指针⽀持在构造时给⼀个删除器,所谓删除器本质就是⼀个可调⽤对象,在这个可调⽤对象中实现你想要的释放资源的⽅式,当构造智能指针时,给了定制的删除器,在智能指针析构时就会调⽤删除器去释放资源。因为new[]经常使⽤,所以为了简洁,unique_ptrshared_ptr都特化了⼀份[]的版本:

QQ20251029-001954
intmain(){// 这样实现程序会崩溃// unique_ptr<Date> up1(new Date[10]);// shared_ptr<Date> sp1(new Date[10]);// 因为new[]经常使⽤,所以unique_ptr和shared_ptr// 实现了⼀个特化版本,这个特化版本析构时⽤的delete[] unique_ptr<Date[]>up1(new Date[5]); shared_ptr<Date[]>sp1(new Date[5]);return0;}

除此之外,我们还可以自定义删除器,这里我们有三种方式:

lambda表达式

auto delArrOBJ =[](Date* ptr){delete[] ptr;}; unique_ptr<Date,decltype(delArrOBJ)>up4(new Date[5], delArrOBJ); shared_ptr<Date>sp4(new Date[5], delArrOBJ);

函数指针

template<classT>classDeleteArray{public:voidoperator()(T* ptr){delete[] ptr;}} unique_ptr<Date,void(*)(Date*)>up3(new Date[5], DeleteArrayFunc<Date>); shared_ptr<Date>sp3(new Date[5], DeleteArrayFunc<Date>);

仿函数

template<classT>classDeleteArray{public:voidoperator()(T* ptr){delete[] ptr;}}; unique_ptr<Date, DeleteArray<Date>>up2(new Date[5]); shared_ptr<Date>sp2(new Date[5], DeleteArray<Date>());

我们可以看到,使用不同的可调用对象,unique_ptrshared_ptr需要传入的参数也是不同的,这是因为unique_ptrshared_ptr⽀持删除器的⽅式有所不同:

  • unique_ptr是在类模板参数⽀持的;
  • shared_ptr是构造函数参数⽀持的。
这⾥没有使⽤相同的⽅式还是挺不方便,也是标准库中的一点小弊端。

使⽤仿函数unique_ptr可以不在构造函数传递,因为仿函数类型构造的对象直接就可以调用,但是函数指针和lambda表达式的类型是不可以的,所以在传参时不仅要在模板传类型,还要在构造传入相应的对象。

3. shared_ptr的模拟实现

想要加深对智能指针的印象,我们可以自己来模拟实现一下智能指针,在标准库中的四种智能指针中shared_ptr涉猎最广,所以我们来模拟实现一下shared_ptr,当然我们这里只是简单的模拟,标准库中的shared_ptr的实现是极为复杂的。

3.1 原理

实现shared_ptr我们需要搞定两个比较重要的东西,其中之一是引用计数的设计,主要这⾥⼀份资源就需要⼀个引⽤计数,所以引⽤计数采⽤静态成员的⽅式是⽆法实现的,要使⽤堆上动态开辟的⽅式,构造智能指针对象时来⼀份资源,就要new⼀个引⽤计数出来。多个shared_ptr指向资源时就++引⽤计数,shared_ptr对象析构时就--引⽤计数,引⽤计数减到0时代表当前析构的shared_ptr是最后⼀个管理资源的对象,则析构资源。

QQ20251029-005653

其次就是删除器,我们要在构造时传入删除器,但是在析构时才会使用删除器,也就是说我们需要将删除器保存为成员函数,这样才能在析构时去调用,那么我们该如何保存删除器呢?我们知道函数指针、仿函数、lambda表达式这些都可以做删除器,这时候我们就需要用到function包装器(详情点击)了,通过包装器来存储删除器,这样就可以存储不同的删除器了:function<void(T*)> _del = [](T* ptr) {delete ptr; };

3.2 代码

下面让我们来看具体的代码实现:

template<classT>classshared_ptr{public:explicitshared_ptr(T* ptr =nullptr):_ptr(ptr),_pcount(newint(1)){}template<classD>shared_ptr(T* ptr, D del):_ptr(ptr),_pcount(newint(1)),_del(del){}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr),_pcount(sp._pcount),_del(sp._del){++(*_pcount);}voidrelease(){if(--(*_pcount)==0){// 最后⼀个管理的对象,释放资源_del(_ptr);delete _pcount; _ptr =nullptr; _pcount =nullptr;}} shared_ptr<T>&operator=(const shared_ptr<T>& sp){if(_ptr != sp._ptr){release(); _ptr = sp._ptr; _pcount = sp._pcount;++(*_pcount); _del = sp._del;}return*this;}~shared_ptr(){release();}//获得原生指针 T*get()const{return _ptr;}intuse_count()const{return*_pcount;} T&operator*(){return*_ptr;} T*operator->(){return _ptr;}private: T* _ptr;int* _pcount; function<void(T*)> _del =[](T* ptr){delete ptr;};};

需要注意的是我们这⾥实现的shared_ptr是以最简洁的⽅式实现的,只能满⾜基本的功能。感兴趣的可以去查看源代码。

4. 总结:智能指针的最佳实践

掌握智能指针后,我们可以从 “手动管理内存” 的焦虑中解放出来。以下是核心实践原则:

  1. 优先用 unique_ptr:若资源无需共享,unique_ptr是最高效的选择(无引用计数开销);
  2. 共享用 shared_ptr:需多模块共享资源时用shared_ptr,优先用make_shared优化;
  3. 循环引用用 weak_ptr:链表、树等结构中,节点间引用用weak_ptr避免泄漏;
  4. 自定义删除器:管理new[]、文件句柄等非new资源时,务必指定删除器;
  5. 避免裸指针混用:尽量不要用智能指针管理 “已被裸指针管理的资源”,避免重复释放。

最后记住:智能指针不是 “银弹”,但它是现代 C++ 中避免内存泄漏的最有效工具。用好智能指针,让你的代码更安全、更优雅!

尾声

若有纰漏或不足之处欢迎大家在评论区留言或者私信,同时也欢迎各位一起探讨学习。感谢您的观看!

Read more

排序算法指南:选择排序

排序算法指南:选择排序

前言:        选择排序(Selection Sort)是一种基础的排序算法,其核心思路是:在每一轮遍历中,从剩余未排序元素中选出最小(或最大)值,并将其放置在已排序序列的末端。        对于排序算法的实现,由局部到整体的思路,先排序好一趟或一个元素,再排列多趟或全部元素。                一、选择排序的工作原理          以排序升序数组为例,工作原理如下: 初始化:假设当前数组中,前部分是已经排好序的,后部分是未排序的。          寻找最小(或最大)值:遍历未排序的部分,找出其中的最小值(或最大值)。          交换位置:将找到的最小值与当前未排序部分的第一个元素交换。          重复:缩小未排序部分的范围,重复以上步骤,直到整个数组排好序。          如下动图所示:                                    以上述数组为例,假设有一个待排列的数组为:[3,44,38,5,47,15,36,26,27,2,46,4,

By Ne0inhk
【算法通关指南:算法基础篇】二分算法: 1.A-B 数对 2.烦恼的高考志愿

【算法通关指南:算法基础篇】二分算法: 1.A-B 数对 2.烦恼的高考志愿

🔥小龙报:个人主页 🎬作者简介:C++研发,嵌入式,机器人等方向学习者 ❄️个人专栏:《C语言》《【初阶】数据结构与算法》 ✨ 永远相信美好的事情即将发生 文章目录 * 前言 * 一、A-B 数对 * 1.1题目 * 1.2 算法原理 * 1.3代码 * 二、烦恼的高考志愿 * 2.1 题目 * 2.2 算法原理 * 2.3 代码 * 总结与每日励志 前言 本文将通过两道经典二分查找例题 ——A-B 数对与烦恼的高考志愿,带你系统掌握二分查找的核心思想与实用技巧。从排序预处理到lower_bound、upper_bound的灵活运用,再到手动实现二分与边界细节处理,由浅入深讲解算法原理与代码实现,帮助你快速攻克二分查找题型,提升编程思维与解题效率 一、

By Ne0inhk
解密链表环的起点:LeetCode 142 题

解密链表环的起点:LeetCode 142 题

解密链表环的起点:LeetCode 142 题 * 视频地址 * 🌟 引言 * 🔍 问题描述 * 🧠 解题思路回顾 * 快慢指针算法 * 数学原理 * 💻 C++代码实现 * 🛠 代码解析 * 数据结构定义 * 算法实现细节 * 🚀 性能分析 * 🐞 常见问题与调试 * 常见错误 * 调试技巧 * 📊 复杂度对比表 * 🌈 总结 视频地址 因为想更好的为大佬服务,制作了同步视频,这是Bilibili的视频地址 🌟 引言 链表环检测问题在C++中同样是一个经典面试题。本文将用C++实现LeetCode 142题"环形链表II"的解决方案,深入讲解快慢指针算法的原理和实现细节。 🔍 问题描述 给定一个链表的头节点 head,返回链表开始入环的第一个节点。如果链表无环,则返回 nullptr。 🧠 解题思路回顾 快慢指针算法 1. 使用两个指针:slow每次走一步,fast每次走两步 2.

By Ne0inhk
环形房屋如何 “安全劫舍”?动态规划解题逻辑与技巧

环形房屋如何 “安全劫舍”?动态规划解题逻辑与技巧

环形房屋如何 “安全劫舍”?动态规划解题逻辑与技巧 * 1、问题描述 * 2、解题思路 * 3、动态规划解法 * 3.1 辅助函数 * 3.2 主函数 * 4、代码解析 * 5、复杂度分析 * 6、测试用例 * 7、关键点总结 * 8、常见问题解答 🌺The Begin🌺点点关注,收藏不迷路🌺 1、问题描述 你是一个专业的小偷,计划偷窃环形排列的房屋。每间房屋都有一定金额,但如果偷窃相邻的两间房屋就会触发警报。计算在不触发警报的情况下能够偷窃到的最高金额。 2、解题思路 这个问题是经典打家劫舍问题的变种,房屋排列成环形。我们可以将其分解为两个子问题: 1. 不偷第一间房屋 2. 不偷最后一间房屋 然后取这两个子问题的最大值作为最终结果。 3、动态规划解法 3.1

By Ne0inhk