LeetCode题解01_十大排序算法(C/C++实现)

排序算法可以说是算法的基础,很多leetcode题目也会有涉及
为了篇幅不至于太长,排序算法原理和leetcode题目分成2篇笔记进行介绍

  • 1.本篇:针对经典的十大排序算法进行介绍
  • 2.下一篇:介绍排序相关的leetcode题目解法点击查看

00.排序前准备

提示:为了方便介绍,下面默认都是按照升序进行排序的;为了节省篇幅,只列出了核心函数,且将实现一个功能的部分代码合并成一行处理了

根据是否利用额外空间?

排序算法可以分为内部排序(In-place)和外部排序(Out-place)

  • 内部排序:数据记录在内存中进行排序
  • 外部排序:因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存

针对排序后2个相等键值的顺序和排序前顺序是否一致?

对排序算法的稳定性有要求,稳定表示的就是排序前后相对顺序一致

排序算法的简要总结

在这里插入图片描述

其中:n:数据规模;k:"桶"的个数

扩展:公共函数

排序算法中有很多交换2个元素的内容的需求,因此,将几种常见的交换函数实现列举如下:

//指针版本
void swap(int *a, int *b){ 
 int temp = *a;
 *a = *b;
 *b = temp;
}

/* 不用系统函数的话,swap有下面3种常见实现 */
//交换数值         //位运算            //指针
int tmp = s[i];         s[i] ^= s[j];      int temp = *a;
s[i] = s[j];            s[j] ^= s[i];      *a = *b;
s[j] = tmp;             s[i] ^= s[j];      *b = temp;

01.冒泡排序

在这里插入图片描述

原理:比较相邻的元素,如果第一个比第二个大,就交换他们两个

  • 1.对每一对相邻元素作同样操作,从开始第一对到结尾最后一对;结束后,最后的元素是最大
  • 2.针对所有的元素重复以上的步骤,除了最后一个
  • 3.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较

注意:如果是比较相邻2个元素的排序算法,最外层的下标通常到只能取到len - 2,因为只有len - 2

//C
//简单理解:外层for用来规定有几对的数进行比较;内层for从右向左,跑完就会排序好一个数
void bubble_sort(int arr[], int len) {
    for (int i = 0; i < len - 1; i++)				 //i没有参加比较,只是用来规定次数
        for (int j = 0; j < (len - 1) - i; j++) {	 //每次保证循环的最后一个元素有序
            if (arr[j] > arr[j + 1]) swap(&arr[j], &arr[j + 1]); //swap     
        }
}

//C++
template <typename T> 
void bubble_sort(T arr[], int len) {
    for (int i = 0; i < len - 1; i++)
        for (int j = 0; j < len - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) swap(arr[j], arr[j + 1]);     
        }
}

02.选择排序

总结:遍历未排序部分找合适的元素,放在已排序部分末尾

在这里插入图片描述

原理:一句话,未排序中找最小值,放在已排序末尾

  • 1.首先在未排序序列中找到最小元素,存放到排序序列的起始位置

  • 2.再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾

提示:无论什么数据都是 O(n^2) 的时间复杂度,所以用到它时,数据规模越小越好;这个算法时间复杂度很差,且不稳定,除了可以要求,基本不用

//C
void selection_sort(int arr[], int len) {
    for (int i = 0 ; i < len - 1; i++) {    //每次将最小元素放在i的位置上,注:i<len-1
        int min = i;                        //***记录位置,暂时将当前置为最小***
        for (int j = i + 1; j < len; j++){  //遍历未排序的元素,注:j 的范围
            if (arr[j] < arr[min]) min = j;	//找到目前最小值,记录下标
       }
       swap(&arr[min], &arr[i]);            //交换
    }
}
//C++,当函数必须是static函数
template <typename T> 			//整数和浮点数皆可使用,若要使用仿函数时必须设定大于(>)的运算子
void selection_sort(std::vector<T>& arr) {
    for (int i = 0; i < arr.size() - 1; i++) {
        int min = i;
        for (int j = i + 1; j < arr.size(); j++)
            if (arr[j] < arr[min]) min = j;    
        std::swap(arr[i], arr[min]);
    }
}

03.插入排序

总结:遍历已排序部分找合适位置给新元素

在这里插入图片描述

原理:一句话,顺序遍历,遇到新元素,找到新元素在前面已排列的合适位置插入

通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入
步骤:

  • 1.待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列

  • 2.从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面)

void insertion_sort(int arr[], int len) {
    int i, j, min;
    for (i = 1; i < len; i++) {   				 	//无序数组,从左到右,默认第一个元素有序
        min = arr[i];             				 	//无序数组中一个元素   
        for (j = i; j > 0 && arr[j-1] > min; j--)	//有序数组,从右到左,数组里一定不是j--
            arr[j] = arr[j-1];                   	//预留出空位j-1
        arr[j] = min;           
    }
}

相关题目:LC315_计算右侧小于当前元素的个数(本题好的地方,逆向的插入 + 二分查找优化已排序部分查找)

题目:一个整数数组 nums ,按要求返回一个新数组 counts (counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量)

  • 插入排序思路(超时)从后向前的插入排序,从右向左看是升序 + 已排序部分用二分法优化查找
  • 不考虑位置,常规思路:升序排序后,新数组的下标就表示有几个元素小于自己
  • 只考虑自己右侧的元素:从后向前,不断将后面元素升序排列,间接的得到想要的结果
  • 插入排序:顺序遍历,遇到新元素,找到新元素在后面已排列的合适位置插入
  • 细节:用一个vector来维护后面已排序的升序列表,便于找下标
vector<int> countSmaller(vector<int>& nums) {
    vector<int> sorted, res(nums.size());           	//插入排序中已排序部分用sorted维护
    for (int i = nums.size() - 1; i >= 0; --i) {    	//从后向前,外层是插入排序
        int left = 0, right = sorted.size();        	//二分的双指针,左闭右开
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (sorted[mid] >= nums[i]) right = mid;
            else left = mid + 1;
        }
        res[i] = right;
        sorted.insert(sorted.begin() + right, nums[i]);	//sort是升序
    }
    return res;
}

04.希尔排序

总结:分段的插入排序,不断缩小排序区间的过程,分-总 的思想

在这里插入图片描述

介绍非稳定排序算法,分段跳跃式的插入排序,是基于插入排序的第2点优化的

  • 1.插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率
  • 2.但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位

原理:分割若干子序列进行插入排序(一次移动多个位置) + 不断缩小子序列个数(gap控制)

步骤

  • 1.对准备排序的数据,根据某一增量分为若干子序列,并对子序列分别进行插入排序

  • 2.逐渐将增量减小,重复步骤1,直至增量为1,此时数据序列基本有序,最后进行插入排序

注意:子序列里的元素不是挨着的,而是间隔为gap大小,跳跃选取的

总结:外层for控制gap缩小,内层for执行插入排序

//C
void shell_sort(int arr[], int len) {  
    int i, j, min, gap;
    //外层for是用来控制gap不断缩小的
    //初始间隔gap为数组一半,每次循环gap减半,跳跃式的插入排序
    for (gap = len >> 1; gap > 0; gap = gap >> 1) { 		//插入排序外面的一层壳
        for (i = gap; i < len; i++) {    					//无序数组;i=gap, 默认第一个有序,从第二个开始
            min = arr[i];                					//无序数组中第一个元素
            for (j = i; j >= gap && arr[j-gap] > min; j -= gap)//有序数组;从右向左
                arr[j] = arr[j - gap];   					//将j-gap这个位置空出来
            arr[j] = min;                					//内层for将空出来的位置更新
        }
    }
}
//C++(另一种实现)
template<typename T>
void shell_sort(T array[], int length) {
    int h = 1;
    while (h < length / 3) h = 3 * h + 1;
    while (h >= 1) {
        for (int i = h; i < length; i++) {
            for (int j = i; j >= h && array[j] < array[j - h]; j -= h) 
                std::swap(array[j], array[j - h]);
        }
        h = h / 3;
    }
}

05.快速排序

总结:类似partition思路,左小右大

在这里插入图片描述

思路:分治法 + 左小右大;每次的小区间都是第一个元素是pivot;最坏运行情况是 O(n^2),比如说顺序数组的快排

分治步骤

  • 1.先从数组中取出一个数作为key值,称为 “基准”(pivot)
  • 2.将比这个数小的数全部放在它的左边
  • 3.对左右两个小数列重复1和2,直至各区间只有1个数

总结:不断挖坑的过程,第1个坑是key位置,这个位置数据要提前保留好,便于后面回填

  • 1.从右到左,找到符合条件的数据,填入第一个坑,而这个数据的位置变成新坑

  • 2.从左到右,找到符合条件的数据(与1的条件相反),填入上面的新坑…

问题:为什么不能执行swap(&arr[i], &arr[j])?

下面体现了最后的元素放置,如果直接swap,导致永远都是i和j位置互换,pivot没有位置放

index    start      i       j
​       arr[j]    pivot   arr[i]
//递归,选取 左闭右闭 区间处理
void quick_sort(int arr[], int start, int end) {
    if (start >= end) return;
    int i = start, j = end - 1;     		//一定要减1,因为会取到arr[j],左闭右闭
    int pivot = arr[start];      		//基准
    while (i < j) {                 		//当前组内寻找一遍,即i和j相遇,只有1个元素结束
        while (i < j && pivot < arr[j]) j--;          //向前寻找
        arr[i] = arr[j];
        while (i < j && pivot >= arr[i]) i++;         //向后寻找
        arr[j] = arr[i];
        //swap(&arr[i], &arr[j]);绝对不能这么用
    }
    arr[i] = pivot;                 		//当组内找完一遍以后就把中间数key回归
    quick_sort(arr, start, i-1);    		//注意:不是所谓的mid,i位置空出来是因为它是pivot
    quick_sort(arr, i+1, end);
}

06.归并排序(需要额外空间)

在这里插入图片描述

思路:分治法(Divide and Conquer);一句话,两个区间"交替"取最小元素放入新区间

有2种实现方法

  • 1.自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法)

  • 2.自下而上的迭代

分治思想:下面一定要体会好先递归后处理的逻辑顺序,先递归体现的是分的思想,递归开始一层层返回时,就会执行到处理逻辑的代码(两个区间排成有序区间),体现的是治的思想

细节:区间的封闭性一定要统一好,下面采用左闭右闭;使用了额外空间

void merge_sort(int arr[], const int len) {
    int tmp[len];
    merge_sort(arr, tmp, 0, len - 1);        //左闭右闭
}
void merge_sort(int arr[], int tmp[], int start, int end) {
    if (start >= end) return;                //终止条件,只有一个元素
    int mid = start + (end - start) / 2;     //防止overflow
    int start1 = start, end1 = mid;          //2段左闭右闭区间
    int start2 = mid + 1, end2 = end;
    
    //分的思想
    merge_sort(arr, tmp, start1, end1);
    merge_sort(arr, tmp, start2, end2);
    
    //合的思想:二合一 (3个while用于合并2个有序数组,用tmp存临时合并结果)
    int k = start;                                  //注:start开始
    while (start1 <= end1 && start2 <= end2)
        tmp[k++] = arr[start1] < arr[start2] ? arr[start1++] : arr[start2++];
    while (start1 <= end1) tmp[k++] = arr[start1++];
    while (start2 <= end2) tmp[k++] = arr[start2++];
    for (k = start; k <= end; k++) arr[k] = tmp[k];	//注:start开始(临时->正式)
}

07.堆排序

示例:设有一个无序序列 { 1, 3, 4, 5, 2, 6, 9, 7, 8, 0 }

在这里插入图片描述

堆排序:不严格的BST,不要求左右精确,只要求根大于结点即可

详细:子结点键值或索引总是 < 或 > 它的父节点;一种利用堆的概念来排序的选择排序,分为2种方法:

  • 大根堆:每个节点的值都 >= 其子节点的值

  • 小根堆:每个节点的值都 <= 其子节点的值

步骤

  • 1.建堆 (从第一个非叶子节点开始)

  • 2.自动排序 (把堆首(最大值)和堆尾互换 -> 把堆尺寸缩小1,重建堆 -> 直到堆的尺寸为 1)

思想:从第一个非叶子节点向上,先建一个大根堆;然后每次取出大根堆的root就可以了

void heap_sort(int arr[], int len) {		
    //1.create heap(自下向上),i从最后一个非叶子节点开始调整
    for (int i = len / 2 - 1; i >= 0; i--)  //注意:>=0
        adjust_heap(arr, i, len - 1);   	//注意:len-1,闭区间,函数会自动调节成大根堆
    
    //2.sort(自上向下)
    //先将第一个元素和已排好元素前一位做交换,再重新调整,直到排序完毕
    for (int i = len - 1; i > 0; i--) {
        swap(&arr[0], &arr[i]);         	//大根堆,保证i位置存储的是相对的最大值
        adjust_heap(arr, 0, i - 1);     	//把堆的尺寸缩小1,重新调整堆
    }
}
void adjust_heap(int arr[], int start, int end){//大根堆,左闭右闭
    //1.建立父子节点
    int dad = start, son = dad * 2 + 1;
    //2.子节点在范围内才比较(注:<=)
    while(son <= end) {
        //子节点中最大的
        if ((son + 1 <= end) && (arr[son] < arr[son + 1])) son++;
        //如果父节点 > 子节点代表调整完毕,直接跳出函数
        if (arr[dad] > arr[son]) return;
        swap(&arr[dad], &arr[son]); 		//交换节点,向下调整
        dad = son; son = dad * 2 + 1;
    }
}

思考:大根堆的应用场景?

  • 升序:每次为了在大根堆中取出一个元素放在最末尾,因此用大根堆

  • topk小:先让k个元素构成根堆,第k+1个元素要将前k个元素中最大的元素替换掉,因此采用大根堆

实现:stl中大小根堆可以用优先级队列priority_quque(优先级高的在队列前面)实现

前提:使用前要包含#include <quque>,不好记话,就直接记忆成里面的仿函数类与常规使用的sort里的cmp作用相反;

  • 1.升序队列,小根堆 —简单记忆,root最小,后面逐渐增大,因此是升序

    priority_queue <int, vector<int>, greater<int>> q;

  • 2.降序队列,大根堆,---默认是 <,简单记忆就是root最大,后面逐渐减小,因此是降序

    priority_queue <int, vector<int>, less<int>> q;

08.计数排序

计数排序:将输入数据值转化为key存储在额外开辟空间,计数([元素]=出现次数),回填数组;线性时间复杂度

要求:数据必须事先有确定范围

举例:比如 1 2 1 2 2 3,计数([元素] = 出现次数)[1] = 2, [2] = 3, [3] = 1,总次数是2 + 3 + 1 = 6,共6个位置,从后向前先放大数(反向操作也可以),出现次数就放几个元素

特征:不是比较排序,需要额外空间放出现次数信息;速度快于任何比较排序算法

//正常的计数排序局限性:负数不可以用;优点;不用比较
//count_sort修改了负数不可以的情况,下面使用C最原始的方法,C++直接用map更简单
void count_sort(int arr[], int len) {
    int min = arr[0], max = arr[0], i;
    //1.find the max and min element
    for (i = 1; i < len; i++){
        min = arr[i] < min ? arr[i] : min;      //如果允许用库函数,直接min()就可以
        max = arr[i] < max ? max : arr[i];
    }
    int num = max - min + 1;
    int *pcount = (int*)malloc(num * sizeof(int));//省略判断指针nullptr
    memset(count, 0, num * sizeof(int));
    //2.count
    for (i = 0; i < len; i++) pcount[arr[i] - min]++;   //注:len, -min ,可处理负数   
    //3.assemble
    int index = 0;
    for (i = 0; i < num; i++){                          
        while(pcount[i]--) arr[index++] = min + i;      //注:min+是base,回填
    }    
    free(pcount); pcount = NULL;
}

09.桶排序

在这里插入图片描述

桶排序:计数排序的升级版;数据映射到不同的桶中,桶内排序,再合并桶

为了使桶排序更加高效,我们需要做到这2点:

  • 1.在额外空间充足的情况下,尽量增大桶的数量

  • 2.使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中

同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要

//C++(桶是用单链表实现的,类似于解决哈希碰撞的链式法)
class solution {
public:
    void bucket_sort(int arr[], int len) {
        //1.得到BUCKET_NUM个桶,每个桶有一个val=0,next=NULL的节点
        vector<ListNode*> buckets(BUCKET_NUM,(ListNode*)(0));//外层是数组
        //2.元素放入对应桶中(桶内排序好)
        for(int i = 0; i < len; ++i) {
            int index = arr[i] / BUCKET_NUM;             //桶编号
            ListNode *head = buckets.at(index);          //具体哪个桶
            buckets.at(index) = insert(head, arr[i]);    //元素放入桶
        }
        //3.合并桶
        ListNode *head = buckets.at(0);
        for(int i = 1; i < BUCKET_NUM; ++i) head = merge(head, buckets.at(i));
        //4.将所有值全部取出来
        for(int i = 0; i < len; ++i) {
            arr[i] = head->val;head = head->next;  //i++
        }
    }
    
    //insert(元素放入桶时要直接排序好),返回是头节点
    ListNode* insert(ListNode* head, int val){
        //1.新增加一个节点
        ListNode dummy; dummy.next = head;      	//增加一个节点,指向头节点
        //2.遍历
        ListNode *pre, *cur;               //链表:dummy -> head -> ...
        pre = &dummy; cur = head;                
        //遍历链表,找到正确位置,判断的是cur
        while(NULL != cur && cur->val <= val){
            pre = cur; cur = cur->next;
        }
        //3.插入
        ListNode *newNode = new ListNode(val);   //根据val构建一个节点
        newNode->next = cur; pre->next = newNode;
        return dummy.next;
    }
    
    //merge(归并排序也可使用,合并2个链表),返回是头节点
    ListNode* merge(ListNode *head1,ListNode *head2){
        ListNode dummy, *pre = &dummy;
        while(NULL != head1 && NULL != head2) {
            if(head1->val <= head2->val){
                pre->next = head1; head1 = head1->next;    //i++
            }else{
                pre->next = head2; head2 = head2->next;    //i++
            }
            pre = pre->next;
        }
        if(NULL!=head1) pre->next = head1;     //没有处理完的链表要单独处理
        if(NULL!=head2) pre->next = head2;
        return dummy.next;
    }
private:
    const int BUCKET_NUM = 10;              //桶的个数,这个依情况而定
    //单链表节点
    struct ListNode{
        int val;
        ListNode* next;
        explicit ListNode(int i=0):val(i), next(NULL){}
    };
}

10.基数排序

在这里插入图片描述

基数排序:非比较型算法,原理是将整数按位数切割成不同的数字,然后按每个位数分别比较

详细:一句话,根据bit分桶,然后组合桶,一次排序一bit位,这样一直bit0,bit1的下去;这个bit不是二进制bit(是个位、十位、百位...的意思),属于稳定的排序

基数排序 vs 计数排序 vs 桶排序

这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:

  • 计数排序:每个桶只存储单一键值
  • 桶排序 :每个桶存储一定范围的数值
  • 基数排序:根据键值的每位数字来分配桶

简单举例:10个桶,此时处理个位数

1.遍历一遍,就知道每个桶中应该放几个元素了

  • 求桶的index的除数,如798
  • 个位桶index=(798/1)%10=8
  • 十位桶index=(798/10)%10=9
  • 百位桶index=(798/100)%10=7

2.将桶按顺序排好:0号桶,1号桶,…,9号桶,这会形成一个连续的区间,每个桶长度是元素个数

3.重新遍历数组,填充每个桶,因为上一步已经给每个桶留出了合适的长度,填充就完了

//C++
class solution {
public:
    //先处理个位,再处理十位...
    void radix_sort(int data[], int n) {
        int count_bit = maxbit(data, n);    //最外层循环的个数
        int *tmp = new int[n];              //合并桶的临时额外空间
        int *buckets = new int[BUCKET_NUM]; //计数器,桶里存的是元素个数
        int i, index;
        int radix = 1;             //控制处理位数

        while(count_bit--) {                //进行count_bit次排序
            for(i = 0; i < BUCKET_NUM; i++) buckets[i] = 0;//清空桶          
            //1.统计每个桶中的元素个数
            for(i = 0; i < n; i++){
                index = (data[i] / radix) % BUCKET_NUM;    //桶的索引
                buckets[index]++;                          //桶中是元素个数
             }
            //2.合并后,桶的占用的空间
            //每个桶顺序放置,buckets[i]表示第i个桶放完,元素的个数
            //每个桶的数量 = 以前桶数量和 + 自己的数量
            for(i = 1; i < BUCKET_NUM; i++) buckets[i] += buckets[i - 1];
            //3.将所有桶中记录依次收集到tmp中(逆序放置)
            for(i = n - 1; i >= 0; i--){
                index = (data[i] / radix) % BUCKET_NUM;    //桶的编号
                tmp[buckets[index]--] = data[i];           //放元素在相应区间
             }
            //3.将临时数组tmp的内容复制到data中
            for(i = 0; i < n; i++) data[i] = tmp[i];
            radix = radix * BUCKET_NUM;//radix会是1, 10, 100, 1000,....
        }
        delete []tmp; delete []buckets;
    }
    int maxbit(int data[], int n) {    //辅助函数,求所有数据中最大数的位数
        int maxData = data[0];    
        for (int i = 1; i < n; ++i) {
            if (maxData < data[i]) maxData = data[i];
        }
        int count_bit = 1;
        while (maxData / 10) ++count_bit;
        return count_bit;
    }
private:
    const int BUCKET_NUM = 10;        //桶的个数,这个依情况而定
};

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用\[1\]:这段代码是一个解决LeetCode上某个题目的C++实现,具体是一个双指针的解法。该题目是计算一个数组中的积水量。代码中使用了两个指针分别指向数组的左右边界,然后通过比较左右指针所指向的元素的大小,来确定当前位置的积水量。具体的计算方法是,如果左指针所指向的元素小于右指针所指向的元素,则对左指针的左边进行操作,如果左指针所指向的元素大于等于右指针所指向的元素,则对右指针的右边进行操作。在每一次操作中,都会更新左边的最大值和右边的最大值,并计算当前位置的积水量。最后返回总的积水量。\[1\] 引用\[2\]:这段代码是另一个解决LeetCode上某个题目的C++实现,具体是一个深度优先搜索的解法。该题目是计算一个二维网格中从起点到终点的可行路径数量。代码中使用了递归的方式进行深度优先搜索,从起点开始,每次向下或向右移动一步,直到到达终点。在每一步移动中,会判断当前位置是否有障碍物,如果有障碍物则返回0,如果到达终点则返回1,否则继续递归搜索下一步的位置。最后返回总的可行路径数量。\[2\] 引用\[3\]:这段代码是另一个解决LeetCode上某个题目的C++实现,具体是一个动态规划的解法。该题目是计算一个数组中的积水量。代码中使用了动态规划的思想,通过遍历数组中的每个元素,分别计算该元素左边和右边的最大值,并计算当前位置的积水量。最后返回总的积水量。\[3\] 综上所述,这三段代码分别是解决LeetCode上不同题目的C++实现,分别使用了双指针、深度优先搜索和动态规划的方法来解决问题。 #### 引用[.reference_title] - *1* *3* [Leetcode 热题100 42.接雨水(C++ 多种解法,错过可惜)](https://blog.csdn.net/qq_51933234/article/details/124637883)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [[C++]Leetcode 不同路径 || 解题思路及详解](https://blog.csdn.net/weixin_62712365/article/details/123951736)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insert_down28v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值