本篇文章是关于算法学习路线中一些极为基础简单的知识点;
同时也是小编系统学习算法后,认为基础且有用的思想;
废话不多说,我们直接进入正题:
枚举:
官方的解释就已经相当准确了,基于已有知识猜测答案的一种问题策略。
通过不断猜测,从所有可能的集合中逐个尝试,然后判断题目条件;
接下来,,我们以一道简单的题来具体分析做法:
例子:一个数组中的每个数都互不相同,求其中和为0的数对的个数
具体做法:
1、分析问题:
通过题目我们得知:需要判断的条件是和为0,且枚举元素是数对,且数对中的两个数都来自于给定数组中;
2、进行枚举设置:
既然知道了枚举类型就将所有符合枚举类型的数对全部枚举出来;
3、结合判断条件编辑整合输出结果:
满足两个数相加为0,结果+1
4、代码编写:
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(a[j]+a[i]==0)sum++;//数组为题目所给数组,sum最终输出值,n为数组大小
}
}
5、优化:
由于题目并没有明确要求有序,我们可以假定有序第一个数要在第二个数前面,最后将有序✖2就是全部
for(int i=0;i<n;i++){
for(int j=0;j<i;j++){
if(a[i]+a[j]==0)sum++;
}
}
sum=sum*2;
还可以进一步分析优化(这里如果不理解,可以只熟悉下思想和代码书写,等后面学习的算法越来越多了,就自然通透了)
在枚举第一数的适合第二个数就已经呈现出一些规律了,那么就是第二个数必须是第一个数的相反数,既然如此就可以使用桶的思想记录,当桶中有这个数数出现第二次时就说明成立,相反数处理采用一个是+一个是 -.
#include<iostream>
define int MAX=10000;
int SUM(int n,int a[]){
bool met[MAX*2+1];//模拟桶
memse(met,0,sizeof(met));//初始化
int sum=0;
for(int i=0;i<n;i++){
if(met[MAX-a[i])++sum;
met[MAX+a[i]]=true;
}
return sum;
}
模拟:
模拟就是使用计算机来模拟题目中要求的操作;
通常需要使用模拟来解决的问题都会给出需要模拟的整体形式;
个人认为模拟题是相对来说好实现的,但是由于模拟题的码量巨大,需要耗费大量的时间,所以需要我们清晰的知道每一步需要做什么在进行模拟:
小编在学习模拟时了解到一些对解这类型题有帮助的技巧如下:
1、在写之前现在草稿本上把具体步骤给罗列出来;
2、代码尽量选择模块化;
3、重复用到的步骤可以统一处理;
来道简单的题方便大家的理解:
青蛙出井,每次只能爬u米,爬完一次需要休息一下,休息时,会掉d米(d<u),井高n米,请求出青蛙需要爬多少次?
1、分析步骤:
青蛙先爬,然后掉,然后爬就这个步骤
2、书写模块化代码:
while(n>0){
n=n-u;
n=n+d;
sum++;
}
return sum;
总结:模拟问题,只要将需要模拟的步骤具体的分析出来然后代码实现即可,注意解题时间;
递归:
定义:在数学和计算机科学中是指在函数的定义中使用函数自身的方法,在计算机科学中还额外指一种通过重复将问题分解为同类的子问题而解决问题的方法。
简单的说就是通过自身调用自身来达到解决问题的目的;
相信这个次在初学C++的时候友友们已经清楚的知道了,那么本文关于递归讨论学习的主要是递归的优化;
递归的优化有两个:搜索优化和记忆化搜索;(这两种优化在后续关于搜索和动态规划篇中详细讲到,留个关注方便后续开快速找到小编)
搜索优化:DFS,BFS........
记忆化搜索:记忆化搜索是一种通过记录已经遍历过的状态的信息,从而避免对同一状态重复遍历的搜索实现方式。记忆化搜索确保了每个状态只访问一次,它也是一种常见的动态规划实现方式。
分治:
定义:分而治之,把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
核心:分而治之;
大致流程步骤可以分为:分解->解决->合并;
1、分解原问题为结构相同的子问题
2、分解到某个容易求解的边界之后,进行递归求解
3、将子问题的解合并成原问题的解
问:如何判断分治问题?
答:分治问题具有以下特征:
1、将问题的规模缩小到一定程度就可以很容易的解决
2、分解后的若干小问题具有最优子结构
3、子问题是相互独立的
例子:
给定一个二叉树,它的每个结点都存放着一个整数值。
找出路径和等于给定数值的路径总数。
路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
二叉树不超过 1000 个节点,且节点数值范围是 [-1000000,1000000] 的整数。
// divide-and-conquer_1.h
// 二叉树结点的定义
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};
解析:
首先明确,既然是求解树中的路径必然设计到要遍历整棵树所以遍历的框架少不了,假定每个节点都可以当作路径的启示节点使用,然后依次总结路径即可
代码实现:
int count(TreeNode *node, int sum) {
if (node == nullptr) return 0;
return (node->val == sum) + count(node->left, sum - node->val) +
count(node->right, sum - node->val);
}
int pathSum(TreeNode *root, int sum) {
if (root == nullptr) return 0;
return count(root, sum) + pathSum(root->left, sum) +
pathSum(root->right, sum);
}
总结:将问题简化成确定一个启示点判断符不符合路径即可;
最后总体总结:
递归和分治,模拟,枚举这些思想是算法长河中比较基础的东西,学习理解使用他们,可以让我们在以后的算法道路上走得更远,最后,路漫漫其修远兮!