考研机试之递归与贪心算法

一、递归

核心思想:将大问题分解成多个小问题,特别注意递归的边界结束条件

1.跳台阶821. 跳台阶 - AcWing题库

斐波那契数列:这一级的台阶数和为上一级的台阶数加上 上上级的台阶数

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int func(int i){
    if(i==1)
        return 1;
    else if(i==2)
        return 2;
    else
        return func(i-1)+func(i-2);
    //
}


int main(){
    int n;
    cin>>n;
    cout<<func(n);
    return 0;
}

二、贪心算法

本质是:由局部最优推导到全局最优----->每个阶段都取最优结果

主要题目

题目1.合并果子P1090 [NOIP 2004 提高组] 合并果子 - 洛谷

利用小根堆 哈夫曼树

1.构建小根堆 ,引入头文件 #include<queue> 定义小根堆 priority<int,vector<int>,greater<int>>q

定义小根堆 的: 类型, vector(底层容器),greater<int> greater为比较器

思想:

1,每次都取两个最小的top元素, 相加 res 进行累加

2.把相加的结果继续push到栈中

cpp 复制代码
#include<iostream>
#include<queue>
#include<algorithm>
#include<vector>
#include <functional>
using namespace std;


int n;
//关键 构建小根堆 //priority_queue默认为大根堆  头文件为:#include<queue>
//构建小根堆定义: 在内置为 类型, 底层容器, greater<int>
priority_queue<int,vector<int>,greater<int>> q;

int main(){
	cin>>n;
	for(int i =0;i<n;i++) {
		int a;
		cin>>a;
		q.push(a);
	}
	
	int res = 0;
	
	while(q.size()>1){
		int a = q.top();
		q.pop();
		int b = q.top();
		q.pop();
		res +=a+b;//结果进行累加
		q.push(a+b);//把每次相加的结果压入堆中
	} 
	printf("%d\n",res);
	return 0;
} 

二、区间问题:

1.区间选点

区间最大重叠数

贪心思想:输入区间 → 按右端点升序排序 → 遍历选不重叠区间 → 统计数量

ed 表示上一个节点的右端点 ,

将当前节点的左端点与上一个节点的右端点进行比较,如果l>ed ,则无重叠,res++

思路:

1.将区间右端点进行排序,从小到大

2.依次遍历,将当前节点的左端点与上一个节点的右端点进行比较,如果l>ed ,则无重叠,res++,更新ed 为当前节点的右端点

适用场景:解决 "最大不重叠区间数""最少点覆盖所有区间" 等经典贪心问题,时间复杂度主要由排序决定(O(nlogn)),效率很高。

cpp 复制代码
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N =100010;

int n;
struct point{
	int l,r;
	
	bool operator< (const point &w)const{
		return r<w.r; //按右端点r升序 
	} //重载小于号 //为什么右端点 
}; 
vector<point> range; 
int main(){
	scanf("%d",&n);
	for(int i = 0;i<n;i++){
		int l,r;
		scanf("%d%d",&l,&r);
		range.push_back({l,r}); 
	}
	sort(range.begin(),range.end());
	
	int res = 0, ed= -2e9;//res 不重叠的区间数,ed上一个选中区间的右端点数,初始值为负无穷 
	for(int i = 0;i<n;i++){
		if(range[i].l>ed){//如果当前区间的左端点数>上一个区间右端点数, 则没有区间重叠 
			res++;
			ed=range[i].r; //更新为其区间右端点 
		} 
	} 
	
	printf("%d\n",res);
	
	return 0;
}

重载运算符

// 重载 <,让 sort 知道按右端点升序比

1.结构体内部 重载

cpp 复制代码
 // 重载小于号 <,用于sort排序
    bool operator<(const point &w) const {
        return r < w.r;  // 按右端点 r 升序排列
        // 如果想按左端点升序,就写 return l < w.l;
        // 如果想降序,就写 return r > w.r;

参数说明

const point &w

const:保证不会修改传入的 point 对象,符合常量语义 &引用传递,避免拷贝对象,提升效率

末尾的 const 这是常量成员函数的标记,保证这个函数不会修改当前对象,必须写

题目2.最大不相交区间数量

背景tbei'jbeibe112. 雷达设备 - AcWing题库

题解与上一题一样:

题目3:P1803 凌乱的yyy / 线段覆盖 - 洛谷

与上题思路一致

cpp 复制代码
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
struct node{
	int l,r;
	bool operator<(const node &w)const{
		return r<w.r; 
	}
}; 
vector<node> e;
int n,a,b;

int main(){
	scanf("%d",&n);
	for(int i = 0;i<n;i++){
		scanf("%d%d",&a,&b);
		e.push_back({a,b});
	}
	sort(e.begin(),e.end());
	int res = 0, last_time = -1;
	for(int i = 0;i<n;i++){
		if(e[i].l>=last_time) {
			res++;
			last_time = e[i].r;
		}
	}
	
	printf("%d",res);
	return 0;
}

题目3 区间分组(会议室调度问题)

题型:会议室调度

核心要求:要用最少的分组容纳最多的区间,区间之间不重叠

贪心策略:

1.将区间按左端点依次排序(模拟会议开始的时间)

2.资源复用:用小根堆进行维护每个区间的右端点,堆顶元素为:最早结束会议的时间

步骤:

1.按照左端点进行排序

2.遍历处理: 如果小根堆不为空,并且 当前节点的左端点的值大于小根堆的值, 把堆顶弹出队若当前区间的左端点 > 堆顶(最早空闲组的结束时间)→ 复用该组,弹出堆顶并将当前区间右端点入堆

若当前区间的左端点 ≤ 堆顶 → 无法复用,新开一组,将当前区间右端点入堆;(根中节点数增加)---也就是其分组增加

3.输出 堆的大小 即为分组的最小值

背景例题:

111. 畜栏预定 - AcWing题库

cpp 复制代码
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;

int n ;

struct point{
	int l,r;
	bool operator<(const point&w)const{
		return l<w.l;
	}
}; 
//按左端点排序,小根堆记各组结束时间,能复用就更更新,不能就新开,堆大小就是最小组数。
int main(){
	scanf("%d",&n);
	vector<point> range;
	for(int i = 0;i<n;i++){
		int a,b;
		scanf("%d%d",&a,&b);
		range.push_back({a,b}); 
	} 
	sort(range.begin(),range.end()) ;
	
	priority_queue<int,vector<int>,greater<int>> q;//构建小根堆 堆顶元素为上个会议的右端点 
	
	for(int i = 0;i<n;i++){
		if(!q.empty()&&range[i].l>q.top())//如果不为空,且当前区间的左值>堆顶,则复用 
			q.pop();//更新右端点;
		q.push(range[i].r); //把当前区间右端点压入 
	} 
	
	printf("%d\n",q.size());
	
	return 0;
}

题目3 区间覆盖问题

贪心策略;每次选左起点能覆盖当前节点、且右端点是最大的区间

思路:1.先将节点按左端点排序 2.贪心循环每次找其右端点大的

cpp 复制代码
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;

int n, st,ed;

struct node {
	int l,r;
	bool operator<(const node &w)const{
		return l<w.l;
	}
}; 

int main(){
	scanf("%d%d",&st,&ed); 
	scanf("%d",&n);
	vector<node> range;
	for(int i = 0;i<n;i++){
		int a,b;
		cin>>a>>b;
		range.push_back({a,b});
	}
	sort(range.begin(),range.end());
	
	int res = 0;
	bool success = false;
	
	for(int i = 0;i<n;i++){
		int j = i ,r =-2e9; 
		while(j<n&&range[j].l<=st){//找左端小于st,但右端大的节点 ,贪心在于 一直找右侧大的区间 
			r = max(r,range[j].r);
			j++; 
		}
		if(r<st){
			res = -1;
			break;
		}
		
		res++;//区间数加1
		 
		if(r>=ed){
			success =true; 
			break;
		}
			
		st = r;
		i = j-1; //本质:在内层循环处理完一批区间后,通过手动设置 i 的值,配合外层 for 循环的i++           
				//到下一个未处理的区间,避免重复计算,保证算法高效。
				
	}
	if(!success)
	cout<<"-1";
	else
		cout<<res; 
	
	return 0;
}

区间覆盖问题 : P2242 公路维修问题 - 洛谷

贪心策略:优先选取较长的距离/空隙进行求解 ,每步最优,全局最优

题意:

1.区间总长度:拆成m-1段最大间距

计算相邻点的间距,降序排序后,优先拆分最大的m-1个间距,每次拆分能减少(间距-1)的总长度。

cpp 复制代码
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N =20010; 
int n,m,ans;
int a[N];//每个坑的距离
int l[N];//存最大的距离
bool cmp(int k,int h){
	return k>h;//从大到小排序
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i = 1;i<=n;i++){
		scanf("%d",&a[i]);
	}
	for(int i= 1;i<n;i++){
		l[i] = a[i+1]-a[i]; //每个节点的距离
	}
	sort(l+1,l+n,cmp); 
	ans = a[n]-a[1]+1;//原始长度
	for(int i = 1;i<m;i++){
		ans= ans-l[i]+1;//从中间减去不需要的长度
	}
	printf("%d\n",ans);
 	return 0;
} //贪心策略:要总长度最小, 每次优先处理较长的距离/空隙
相关推荐
我能坚持多久1 小时前
链式二叉树OJ问题详解
算法
2401_844221321 小时前
使用PictureBox实现图片缩放与显示的深入探讨
jvm·数据库·python·算法
Java面试题总结2 小时前
Go图像处理基础: image包深度指南
图像处理·算法·golang
C羊驼2 小时前
C 语言:哥德巴赫猜想
c语言·开发语言·人工智能·经验分享·笔记·算法·课程设计
田梓燊2 小时前
算法题学习题单
学习·算法
Sunsets_Red2 小时前
乘法逆元的 exgcd 求法
c++·学习·数学·算法·c#·密码学·信息学竞赛
阿Y加油吧2 小时前
力扣打卡——接雨水、无重复字符的最长子串
算法·leetcode·职场和发展
handler012 小时前
算法:字符串哈希
c语言·数据结构·c++·笔记·算法·哈希算法·散列表