2024年C C++最新算法总结篇 —— DFS(搜索、递归、回溯,2024年最新2024年字节跳动+京东+美团面试总结

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

有些事情本来很遥远,你争取,它就会离你越来越近

概念 + 名词解释

递归是指在一个函数的定义中调用自身的过程,有些复杂问题可以划分为多个相同子问题的话,就可以大概率可以使用递归来解决。经典算法如暴搜回溯深度优先遍历(dfs)FloodFile算法记忆化搜索等本质上都是使用递归来解决,换句话来说,这些本质上来说都是一种问题。

如何理解递归

初学者可以在做递归简单问题的时候,画递归展开图来帮助理解递归,如斐波那契数列问题 f(n) = f(n - 1) + f(n - 2),要求出f(4),就得先递归去求解 f(3)和 f(2);

当学习了数据结构之后呢,可以做一些二叉树相关的 OJ 题目去理解递归,但二叉树刚开始做其实都是非常迷的,但慢慢理解后,好像确实就只是这么个事哈哈。

当做了一些简单递归题目和二叉树后,就可以尝试从宏观上理解递归了,可以将递归问题分为三步:

  • 不细想递归展开图,越复杂的问题越不能画递归展开图,递归展开图只适用于在简单题中理解递归
  • 把递归的函数想象成一个黑盒,你让这个黑盒去完成一个任务,并信任这个黑盒一定能完成。只需要设计好这个黑盒即可!
  • 将复杂问题的主逻辑写出来,只在乎递归的结果,并将这个问题解决

那么问题就变为了,如何设计递归函数,也就是如何设计一个这样的黑盒?
首先,找到题目中的多个相同子问题——递归函数头的设计
只关心其中一个子问题是如何解决的——递归函数体的设计
根据经验或题目要求再确定一下递归的出口即可

单看比较抽象,使用一个例子来解释一下上面的步骤:
面试题——汉诺塔

汉诺塔问题,大家都听过吧,将多个圆盘从一个柱A 借助 柱B 移动到 柱C,前提是不小的盘子必须放在大的盘子的上面,问一共有多少种解法?
在这里插入图片描述
当然汉诺塔问题是有公式解决的的,但这没有任何意义,我们的目的是从宏观上理解递归。
当有n个圆盘的时候,我们可以先将(n-1)个圆盘移动到 塔2 上面,然后再将塔1的最后一个大圆盘移至 塔3,再用同样的方法将塔2上的(n-1)个元素借助塔1移至塔3,要将这 n - 1 个移动,就要先移动 n - 2,如此往复…

找到了重复子问题:要移动 n 个,就要先解决 n - 1 个怎么搞,要解决 n - 1 个,就要先解决 n - 2 个怎么搞…,并且函数头需要将这几个柱子全部传过去

在这里插入图片描述

那么其中一个子问题需要怎么解决呢?
假设有 3个柱子ABC吗,n 个盘子,那么需要先将 n - 1 个盘子借助 C 先移动到 B,再将地下那个最大的移动到 C ,然后A 空了,再借助 A 将 B 上的 n - 1 个盘子移动到 C上,就完成了任务。说明在 n 减到 1 之前,都需要重复上面的步骤,先将 n -1 个盘子借助一个柱子移动到另一个上,然后将第 n 个移动。那么函数体的设计:
在这里插入图片描述

在这里插入图片描述
那么,在 n > 1 的时候就要一直递归,直到 n = 1 时才进行第一次返回,所以递归出口就是 n == 1,递归返回前,需要将盘子从 A 移动到 C,然后返回。
在这里插入图片描述

汉诺塔力扣代码:

class Solution {
public:
    void \_haota(vector<int>& A, vector<int>& B, vector<int>& C, int n)
    {
        if(n == 1)
        {
            C.push\_back(A.back());
            A.pop\_back();
            return;
        }
        \_haota(A, C, B, n - 1);
        C.push\_back(A.back());
        A.pop\_back(); 
        \_haota(B, A, C, n - 1);
    }
    void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {
        int n = A.size(); // 需要移动的盘子数量
        \_haota(A, B, C, n);
    }
};

其实我也不知道自己到底说没说明白…

dfs 回溯类问题

步骤:
先使用决策树画出所有情况,如对 [1, 2, 3] 进行全排列
在这里插入图片描述
使用 全局变量path 来记录某条路径上的结果,在决策树画到底部的时候,判断 path 中的结果是否符合结果,然后需要“向上返回”,并且顺便“恢复现场”,如上图中,path 中在底部变成了 123,恢复现场后就成了 12;如果没有可以继续走的路,就继续“向上返回”,再恢复现场,然后判断是否有其他路径可以走,遍历完决策树中所有情况后,就暴搜出了所有情况。
剪枝
即剪去不合理的分支,当全排列的时候,某个数字被列举了之后,就不能再使用了,因此要剪去这个分支!一般我们可以使用 bool 类型的check[] 数组来实现剪枝。

全排列力扣代码

class Solution {
public:
    vector<vector<int>> ret;
    vector<int> path;
    bool check[7] = { false };
    vector<vector<int>> permute(vector<int>& nums) {
        dfs(nums);
        return ret;
    }
    void dfs(vector<int>& nums)
    {
        if(path.size() == nums.size())
        {
            ret.push\_back(path); // 将合理路径放入最终结果
            return;
        }
        for(int i = 0; i < nums.size(); i++)
        {
            if(!check[i])
            {
                check[i] = true;
                path.push\_back(nums[i]);
                dfs(nums);
                check[i] = false; // 恢复现场
                path.pop\_back(); // 恢复现场
            }
        }
    }
};

蓝桥杯——飞机降落

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这道题的数据要求不算高,我们就可以考虑将所有情况都枚举一遍,也就是暴搜!

#include<bits/stdc++.h>
using namespace std; 
// 飞机降落 
struct plane
{
	int a; // 到达时间 
	int b; // 可以盘旋的时间 
	int c; // 降落所需的时间 
};

int T; // 组数 
int N; // 每组的飞机数 
plane vp[12];
bool check[12];
bool dfs(int num, int last)//num 为第几个, last 为上一架的降落时间 
{
	if(num == N)
	{
		return true;// 能走到这一步,说明符合题意且结束了 
	}
	for(int i = 0; i < N; i++)
	{
		if(!check[i] && vp[i].a + vp[i].b >= last)// 说明可以降落 
		{
			check[i] = true;
			if(dfs(num + 1, max(last, vp[i].a) + vp[i].c)) return true;
			check[i] = false;
		}
	}
	return false;// 全部都遍历, 如果没有满足情况的话, 说明不行, 就返回 
}
int main()
{
	cin >> T; // 组数
	for(int i = 0; i < T; i++)
	{
		cin >> N;
		for(int i = 0; i < N; i++)
		{
			cin >> vp[i].a >> vp[i].b >> vp[i].c;
		}
		if(dfs(0, 0)) cout << "YES" << endl;
		else cout << "NO" << endl;
	}
	return 0;
}

dfs 迷宫搜索类问题

搜索类问题一般都是在二维矩阵中进行,以某个坐标为起点进行一次/多次深度优先遍历,直到遇到某个位置停止。
在二维矩阵中搜索,一般可以定义两个向量数组

    // 当题目要求只能上下左右四个方向走时
    int dx[4] = { 0, 0, 1, -1 };
    int dy[4] = { 1, -1, 0, 0 };
    // 如当前的位置坐标为 (i, j), 那么可以走的位置有:(矩阵 m 行 n 列)
    for(int k = 0; k < 4; k++)
    {
    	int x = dx[k] + i, int y = dy[k] + j;
    	if(x >= 0 && x < m && y >= 0 && y < n && !visit[x][y] && /\* 题目要求条件\*/)
    	{
    		visit[x][y] = true;
    		dfs(x, y, /\*\*/);
    	}
    }

    // 当题目要求可以上下左右、斜左上、斜右上、斜左下、斜右下这8个方向走时
    int dx[4] = { 0, 0, 1, -1 , 1, 1, -1, -1};
    int dy[4] = { 1, -1, 0, 0 , -1, 1, -1, 1};
    for(int k = 0; k < 8; k++)
    {
    	int x = dx[k] + i, int y = dy[k] + j;
    	if(x >= 0 && x < m && y >= 0 && y < n && !visit[x][y] && /\* 题目要求条件\*/)
    	{


![img](https://img-blog.csdnimg.cn/img_convert/067afcb284aed4d24402b4c2cf74c20a.png)
![img](https://img-blog.csdnimg.cn/img_convert/2b83630e7ad99c2f6253f0179b4cbdd7.png)

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

g-17N42p8V-1715565385134)]

**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**[需要这份系统化的资料的朋友,可以添加戳这里获取](https://bbs.csdn.net/topics/618668825)**


**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 19
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值