C++学习笔记之算法模板

news2024/9/23 15:33:05

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 一、双指针
    • 1.1 有序数组的合并
    • 1.2 快慢指针/删除有序数组中的重复项
    • 1.3 求和
  • 二、动态规划
    • 2.1 自底向上和自顶向下(带备忘录)
    • 2.2 带有当前状态
  • 三、二分算法
  • 四、贪心算法
    • 4.1 从最小/最大开始贪心
    • 4.2 从最左/右开始贪心
    • 4.3 区间


一、双指针

  双指针(Two Pointers)是一种常用的算法技巧,主要用于处理数组或字符串中的某些问题。它通过维护两个指针来遍历数据结构,从而高效地解决问题。根据指针的移动方向和位置,双指针技术可以分为同向双指针和对向双指针。

1.1 有序数组的合并

  这里以88.合并两个有序数组为例,这里的思路为先复制的一个数组p1_copy,然后用两个指针分别指向p1_copy和p2,最后依次比较两个数组里的元素大小并填充。
在这里插入图片描述

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int* p1_copy = new int[m];
        std::copy(nums1.begin(), nums1.begin() + m, p1_copy);
        
        int p1 = 0, p2 = 0, cur = 0;
        
        while (p1 < m && p2 < n) {
            if (p1_copy[p1] <= nums2[p2]) nums1[cur++] = p1_copy[p1++];
            else nums1[cur++] = nums2[p2++];
        }

        // 如果 p1_copy 中还有剩余元素,拷贝到 nums1
        while (p1 < m) {
            nums1[cur++] = p1_copy[p1++];
        }

        // 如果 nums2 中还有剩余元素,拷贝到 nums1
        while (p2 < n) {
            nums1[cur++] = nums2[p2++];
        }

        // 释放动态分配的内存
        delete[] p1_copy;
    }
};

1.2 快慢指针/删除有序数组中的重复项

  除了进行插入操作,双指针也可以用作快慢指针,例如26.删除有序数组中的重复项,这里的主要思路是利用两个指针,快指针用于判断每一个元素的值,慢指针用于进行赋值。
在这里插入图片描述

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int n = nums.size();
        if (n == 0) return 0;

        int len = 1; // 因为至少有一个元素不会被移除
        for (int i = 1; i < n; i++) {
            if (nums[i] != nums[len - 1]) {
                nums[len] = nums[i];
                len++;
            }
        }
        return len;
    }
};

1.3 求和

  167.两数之和和15.三数之和是双指针另一种常用方式,二者的本质其实都是对向双指针(有点类似二分法),判断和与目标数的大小关系进行指针的调整。三数之和是在二数的基础上多加了一层遍历,将每次取得的数作为目标数,再转化成二数之和。值得注意的是需要跳过重复数字。

//二数之和
class Solution {
public:
    vector<int> twoSum(vector<int>& numbers, int target) {
        int n = numbers.size();

        int i = n - 1;
        int j = 0;
        while(numbers[i] + numbers[j] != target){
            if(numbers[i] + numbers[j] < target) j++;
            else if(numbers[i] + numbers[j] > target) i--;
            else break;
        }
    return {j + 1, i + 1};
    }
};

//三数之和
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        vector<vector<int>> ans;
        int n = nums.size();
        
        for (int i = 0; i < n - 2; i++) {
            if (i > 0 && nums[i] == nums[i - 1]) continue; // 跳过重复数字
            
            int target = -nums[i];
            int l = i + 1;
            int r = n - 1;
            
            while (l < r) {
                int sum = nums[l] + nums[r];
                if (sum > target) r--;
                else if (sum < target) l++;
                else {
                    ans.push_back({nums[i], nums[l], nums[r]});
                    while (l < r && nums[l] == nums[l + 1]) l++; // 跳过重复数字
                    while (l < r && nums[r] == nums[r - 1]) r--; // 跳过重复数字
                    l++;
                    r--;
                }
            }
        } 
        return ans;
    }
};

二、动态规划

  动态规划(Dynamic Programming,简称 DP)是一种用于解决具有重叠子问题和最优子结构性质的问题的算法设计技术。动态规划通过将问题分解成更小的子问题,并保存这些子问题的解决方案,以避免重复计算,从而提高算法效率。

2.1 自底向上和自顶向下(带备忘录)

  这里以70.爬楼梯为例,我们用 f(x) 表示爬到第 x 级台阶的方案数,考虑最后一步可能跨了一级台阶,也可能跨了两级台阶,所以我们可以列出动态规划的转移方程:f(x)=f(x−1)+f(x−2),即爬到第 x 级台阶的方案数是爬到第 x−1 级台阶的方案数和爬到第 x−2 级台阶的方案数的和。

class Solution {
public:
    int climbStairs(int n) {
        if (n == 1) return 1;
        if (n == 2) return 2;

        int* num = new int[n + 1];
        num[1] = 1;
        num[2] = 2;

        for (int i = 3; i <= n; i++) {
            num[i] = num[i - 1] + num[i - 2];
        }

        int result = num[n];
        delete[] num;

        return result;
    }
};

  118.杨辉三角也是一个很好的自顶向下示例,如下图所示。可以看到其规律为:(1)每一排的第一个数和最后一个数都是 1,即 c[i][0]=c[i][i]=1;(2)其余数字,等于左上方的数,加上正上方的数,即 c[i][j]=c[i−1][j−1]+c[i−1][j]。
在这里插入图片描述
  我们简单地抽象一下这个三角形,即{[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]},那么我们可以将其看作是二维数组,然后每一层比上面一层多一个元素,利用resize()函数扩充一个元素。

class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        vector<vector<int>> dp(numRows); // 正确初始化二维向量
        for(int i = 0; i < numRows; i++){
            dp[i].resize(i + 1, 1);      // 调整第i行的大小为i+1,并填充1
            for(int j = 1; j < i; j++){
                dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; // 计算杨辉三角的值
            }
        }
        return dp;
    }
};

2.2 带有当前状态

三、二分算法

  二分法(Binary Search)是一种在有序数组中查找目标值的高效算法。它通过不断将查找范围减半,从而在每次比较后将查找范围缩小一半,最终找到目标值或确定目标值不存在。其基本思路如下所示:

  1. 初始化: 设定查找范围的左边界 left 和右边界 right。
  2. 计算中间点: 计算中间点 mid = left + (right - left) / 2。
  3. 比较中间点的值与目标值:
    • 如果 nums[mid] == target,则找到目标值,返回 mid。
    • 如果 nums[mid] < target,则目标值在右半部分,调整左边界 left = mid + 1。
    • 如果 nums[mid] > target,则目标值在左半部分,调整右边界 right = mid - 1。
  4. 重复步骤 2 和 3,直到找到目标值或查找范围为空(left > right)。

  这里以34.在排序数组中查找元素的第一个和最后一个位置,这里可以简单改写一下这个问题,将其改写成寻找第一个target的位置和第一个target+1的位置,相当于两次利用二分。

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int begin = findborder(nums, target);
        if (begin == nums.size() || nums[begin] != target) return {-1, -1};
        int end = findborder(nums, target + 1);
        return {begin, end - 1};
    }

    int findborder(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;
        int mid;
        while (left <= right) {
            mid = left + (right - left) / 2;
            if (nums[mid] < target) left = mid + 1;
            else right = mid - 1;
        }
        return left;
    }
};

四、贪心算法

  贪心算法(Greedy Algorithm)是一种在求解优化问题时所采用的方法。贪心算法的核心思想是:在每一步选择中,选择当前状态下最好(即最优)的选择,期望通过局部最优选择达到全局最优。贪心算法通常用于解决一些特定类型的问题,这些问题具有“贪心选择性质”和“最优子结构性质”。贪心算法的基本步骤为:

  1. 建立数学模型来描述问题。
  2. 将问题分解为若干个子问题。
  3. 对每个子问题求解,得到局部最优解。
  4. 将所有子问题的局部最优解合并成一个全局解。

  通常情况下,有两种基本贪心策略:从最小/最大开始贪心,优先考虑最小/最大的数。在此基础上,衍生出了反悔贪心;从最左/最右开始贪心,思考第一个数/最后一个数的贪心策略,把 n 个数的原问题转换成n−1 个数(或更少)的子问题。

4.1 从最小/最大开始贪心

  我们以55.跳跃游戏为例,根据题目要求我们在规定的可选步长(step)内选择合适的长度,最终到达最后一个元素即可,但是实际上如果我们陷入这样的思维就很难继续了,例如对于给出的[2,3,1,1,4],我可以选择分别走2->1->1,也可以走1->3,但是我们可以发现一个现象如果我们在每步都取最大步长,且各个部分的最大步长合起来可以覆盖到终点就可以代表能走到最后,这样就不需要考虑各地方到底是怎么走的了,如图所示。
在这里插入图片描述

class Solution {
public:
    bool canJump(vector<int>& nums) {
        if(nums.size() == 1) return true;
        int step = 0;
        for(int i = 0; i <= step; i++){
            step = max(i + nums[i], step);
            if(step >= nums.size() - 1) return true;
        }
    return false;
    }
};

4.2 从最左/右开始贪心

  这里以134.加油站为例,通过在遍历过程中动态调整起点来找到唯一的可行起点。具体来说,贪心策略体现在每次遇到当前油量不足的情况时,立即放弃从当前起点到当前加油站之间的所有加油站作为起点,因为如果从这些加油站中的任何一个出发,都无法到达当前加油站。

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int current = 0;
        int total = 0;
        int start = 0;
        for(int i = 0; i < gas.size(); i++){
            current = current + (gas[i] - cost[i]);
            total = total  + (gas[i] - cost[i]);
            if(current < 0){
                start = i + 1;
                current = 0;
            }
        }
        if(total < 0) return -1;
        return start;
    }
};

4.3 区间

  在计算机中区间和数学中一样,均表示一个范围,通常情况下会写成字符串数组的形式。以例57.插入区间为例,这里我们就可以使用贪心算法,其体现在两个方面:不重叠的区间直接添加;发现当前区间 x 与 newInterval 重叠则选择合并。

class Solution {
public:
    vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) {
        // 如果 intervals 为空,直接返回 newInterval 作为唯一的结果
        if(intervals.empty()) return {newInterval};       
        
        vector<vector<int>> res;
        bool inserted = false;

        for(auto x : intervals){
            if(x[1] < newInterval[0]){
                // 当前区间在 newInterval 左侧,无重叠
                res.push_back(x);
            }
            else if(newInterval[1] < x[0]){
                // 当前区间在 newInterval 右侧,无重叠
                if(!inserted) {
                    res.push_back(newInterval);
                    inserted = true;
                }
                res.push_back(x);
            }
            else{
                // 当前区间与 newInterval 有重叠,合并
                newInterval[0] = min(newInterval[0], x[0]);
                newInterval[1] = max(newInterval[1], x[1]);
            }
        }

        // 如果 newInterval 还未插入,则插入它
        if(!inserted) {
            res.push_back(newInterval);
        }

        return res;
    }
};       

  值得注意的是if (!inserted) 语句的作用是在处理所有的区间后,确保新区间 newInterval 被正确地插入到结果中。假设 intervals 中的所有区间都在 newInterval 的左侧且不重叠,循环中每次都会走到 if (x[1] < newInterval[0]) 分支,将所有的原始区间直接添加到 res,但 newInterval 并没有被插入;另一种情况是 newInterval 小于所有区间且不重叠,在这种情况下,newInterval 应该在最前面插入。但如果不检查 !inserted,newInterval 可能会被忽略,因此在遍历完成后,newInterval 需要被手动添加。

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

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

相关文章

浅谈哈希长度扩展攻击

攻击原理&#xff1a; 我们首先需要了解一下Message Authentication codes (MACs) &#xff0c;称为消息验证码&#xff0c;一般用于服务器验证消息的真实性。服务器把密钥和消息连接起来&#xff0c;用摘要算法获取摘要&#xff0c;对于H&#xff08;secret data&#xff09…

RabbitMq的基本理解

MQ概念及同步异步&#xff1a; 同步调用&#xff1a; 是一种编程模型&#xff0c;其中调用者发送请求并等待响应。在同步调用中&#xff0c;调用者会阻塞&#xff0c;直到被调用的方法返回结果。 异步调用: 是一种编程模型&#xff0c;其中调用者发送请求后立即返回&#x…

09结构型设计模式——组合模式

一、组合模式的简介 组合模式&#xff08;Composite Pattern&#xff09;是一种结构型设计模式&#xff0c;主要用于处理树形结构中的对象组合问题。它允许你将对象组合成树形结构&#xff0c;以表示部分-整体层次结构。组合模式使得客户端能够统一地对待单个对象和对象组合&a…

SEREN MC2电源匹配器控制器Matching Network Controller手侧

SEREN MC2电源匹配器控制器Matching Network Controller手侧

NC 反转链表

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 给定一个单链…

亲测解决Bundler HTTPError Could not fetch specs from

这个问题源于ruby的网站连接不上&#xff0c;解决方法是修改网页地址或者网络配置。 环境 win11 ruby 问题原文 Retrying fetcher due to error (2/4): Bundler::HTTPError Could not fetch specs from https://rubygems.org/ due to underlying error <IO::TimeoutEr…

C# 将Dll嵌入exe中发布

一、制作模版Dll 二、在exe工程中添加Dll 1、添加上述“创建Dll”&#xff0c;并修改属性为&#xff1a;不复制到输出目录的嵌入资源 2、引用“Resource”中的dll文件&#xff0c;并修改属性&#xff1a;不复制到本地 三、添加重载Dll代码 1、添加以下代码 class DependentFi…

Modbus 通信协议详解

目录 一、概述二、Modbus 的作用三、Modbus 的工作原理1、四种数据类型2、三种工作模式3、三类功能码3.1 标志功能码3.2 Modbus 封装接口3.3 异常 4、Modbus 协议层4.1 协议数据单元4.2 访问数据4.3 数据模型寻址4.3.1 数据寻址范围4.3.2 数据地址起始值 4.4 大数据类型4.4.1 位…

频率检测计

前言 频率计是一种用于测量信号频率的仪器。它可以准确地确定电子信号的频率&#xff0c;广泛应用于电子设备的测试和维护中。频率计的工作原理通常包括对输入信号进行采样&#xff0c;并通过内部电路计算信号的周期&#xff0c;从而得到频率值。现代频率计通常具有高精度、高稳…

无线通信代码搬运/复现系列(1) : 重新审视具有每天线功率约束的 MIMO 容量:固定点迭代和交替优化

无线通信代码搬运/复现系列(1) “Revisiting the MIMO Capacity with Per-antenna Power Constraint: Fixed-point Iteration and Alternating Optimization,” IEEE Trans. Wireless Commun., vol. 18, no. 1, pp. 388-401, Jan. 2019 by T. M. Pham, R. Farrell, and L.-N. …

C++入门——05STL

STL&#xff08;Standard Template Library&#xff0c;标准模板库&#xff09;是C标准库的重要组成部分&#xff0c;是一个通用的数据结构和算法库。STL提供了一组经过精心设计的模板类和函数&#xff0c;用于处理各种常见的数据结构&#xff08;如容器&#xff09;和算法&…

六. 部署分类器-preprocess-speed-compare

目录 前言0. 简述1. 案例运行2. 代码分析2.1 main.cpp2.2 preprocess.cpp 3. 补充说明结语下载链接参考 前言 自动驾驶之心推出的 《CUDA与TensorRT部署实战课程》&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考 本次课程我们来学习课程第六章—部署分类器&…

嵌入式面经篇八——进程线程

文章目录 前言一、进程&线程1、异步 IO 和同步 IO 区别&#xff1f;2、进程间通信方式&#xff1f;3、进程的地址空间模型&#xff1f;4、进程的五种状态分别是?5、子进程从父进程继承的资源有哪些&#xff1f;6、什么是进程上下文、中断上下文&#xff1f;7、如何防止僵尸…

写了一个分页 sql,因为粗心出了 bug 造成了 OOM!

大家好&#xff0c;我是君哥。 最近上完线后&#xff0c;凌晨收到一个生产告警&#xff0c;一个 OOM 异常导致了服务重启。今天来分享一下这个事故。 1.事故现场 事故的代码逻辑并不复杂&#xff0c;从一个大概有 8 万数据的表里面查出数据&#xff0c;汇总后对数据做处理。…

高校宣讲会管理系统--论文pf

TOC springboot370高校宣讲会管理系统--论文pf 第1章 绪论 1.1选题动因 当前的网络技术&#xff0c;软件技术等都具备成熟的理论基础&#xff0c;市场上也出现各种技术开发的软件&#xff0c;这些软件都被用于各个领域&#xff0c;包括生活和工作的领域。随着电脑和笔记本的…

【myz_tools】Python库 myz_tools:Python算法及文档自动化生成工具 - 0.2.0版更新

文章目录 0.2.0 更新内容如下函数generate_2d_combinations_iter函数generate_row_permutations函数calculate_total_permutations函数display_combinations函数evaluate_list_similarity函数check_unique 写在前面关于库库使用库内所有函数目录文件名称: common_maths.py函数部…

ZooKeeper分布式协调系统介绍

1. ZooKeeper概述 1.1 ZooKeeper介绍 ZooKeeper 是 Apache 软件基金会的一个项目&#xff0c;它确实提供了一种非常有用的服务&#xff0c;用于维护分布式系统中的配置信息、命名、提供分布式同步和提供组服务等。它的核心是原子广播和大约一致性模型&#xff0c;这使得它能够…

CCF-GESP五级考级——初等数论,全网最精简的求最大公约数gcd和最小公倍数lcm方法(100%好使)

&#x1f451;一、约数和因数的区别 约数必须在整除的前提下才存在&#xff0c;而因数是从乘积的角度来提出的。如果数与数相乘的积是数&#xff0c;是的因数。 1.约数只能对在整数范围内而言&#xff0c;而因数就不限于整数的范围。 举个栗子&#xff1a;。2和8是16的…

中仕公考:国考往年招录情况对比

2025年国考预计10月中旬启动&#xff0c;11月进行笔试。中仕为大家总结了往年的国考招录情况&#xff0c;希望能给大家一些参考。 2024年计划招录3.96万人。截止到考试结束&#xff0c;共有225.2万人参加了考试&#xff0c;参加考试人数与录用计划数之比约为57:1&#xff0c;2…

CSP-J 2023真题一轮

选择题 阅读题 第1题 第2题 第3题 完善程序 第1题 第2题 答案&#xff1a; 一、单选题 1-5 BDAAC 6-10 BCADA 11-15 ABBAD 二、阅读程序 1&#xff09; 16. √ 17. √ 18. ⅹ 19.A 20.B 2&#xff09; 21. √ 22. ⅹ 23. √ 24. D 25.B 26.D 3&#xff09; 27. √ 28. √ 29…