2024年C C++最新【C++】算法集锦(3,细数C C++开发者的艰辛历程

img
img

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

需要这份系统化的资料的朋友,可以添加戳这里获取

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

    • 节点设计
  • N叉树的前序遍历

  • 后序遍历

  • 层序遍历

  • 回溯例题精讲

    • 岛屿最大面积
    • 思路
  • 代码实现

  • 八皇后问题

    • 思路
  • 代码实现

  • 括号生成

    • 思路
  • 代码实现

  • 全排列

    • 思路
  • 代码实现

  • 再说两句

  • 解回溯题的一般步骤

  • 电话号码的字母组合

    • 思路
  • 代码实现

  • 子集

    • 思路
  • 代码实现

前言


回溯算法,之前也是写过的,感觉还不错。但是之前分成两篇写了,现在重新整理一下,顺便我自己也回顾一下。

递归


要玩得转回溯算法,递归思想就要融入骨子里。

正好早上我整理了一篇递归相关的,不妨看一下:【C++】算法集锦(2):递归

N叉树的遍历


我们从N叉树的遍历入手,来看一下回溯算法。

思考一下二叉树的回顾一下二叉树的遍历方式:

前序遍历 - 首先访问根节点,然后遍历左子树,最后遍历右子树;

中序遍历 - 首先遍历左子树,然后访问根节点,最后遍历右子树;

后序遍历 - 首先遍历左子树,然后遍历右子树,最后访问根节点;

层序遍历 - 按照从左到右的顺序,逐层遍历各个节点。

N 叉树的中序遍历没有标准定义,中序遍历只有在二叉树中有明确的定义。

我们跳过 N 叉树中序遍历的部分。

在这里插入图片描述

节点设计

class Node {

public:

int val;

vector<Node*> children; //注意,这里不再是左右子节点了

Node() {}

Node(int _val) {

val = _val;

}

Node(int _val, vector<Node*> _children) {

val = _val;

children = _children;

}

};


N叉树的前序遍历

给定一个 N 叉树,返回其节点值的前序遍历。

class Solution {

public:

void preorderDFS(Node* root, int index, vector& ret) {

if (root == NULL)

return;

ret.push_back(root->val);

int sz = (root->children).size();

while (index < sz) {

preorderDFS(root->children[index], 0, ret); //认真捋一下这一步

index += 1;

}

}

vector preorder(Node* root) {

vector ret;

preorderDFS(root, 0, ret);

return ret;

}

};

修改于 2021.8.22:

//经测试,这段代码更简洁:

void preorder(Node* node) {

cout << “value:” << node->val << endl;

for (Node* n : node->children) {

preorder(n);

}

return;

}

后序我就不调了吧,一行代码位置的事情。


后序遍历

给定一个 N 叉树,返回其节点值的后序遍历。

class Solution {

public:

void postorderDFS(Node* root, int index, vector& ret) {

if (root == NULL)

return;

int sz = (root->children).size();

while (index < sz) {

postorderDFS(root->children[index], 0, ret);

index += 1;

}

ret.push_back(root->val);

}

vector postorder(Node* root) {

vector ret;

postorderDFS(root, 0, ret);

return ret;

}

};


层序遍历

给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。

class Solution {

public:

vector<vector> result;

void dfs(Node* root, int dep){

if(!root) return;

if(dep == result.size()) result.emplace_back();

result[dep].push_back(root->val);

auto children = root->children;

for(auto ele:children){

dfs(ele, dep+1);

}

}

vector<vector> levelOrder(Node* root) {

dfs(root, 0);

return result;

}

};


岛屿最大面积、八皇后问题、括号生成感觉比较简单,所以思路讲的就比较简陋,适合入门练手,建议看其他题目的讲解(全排列那题)。


回溯例题精讲


岛屿最大面积

给定一个包含了一些 0 和 1 的非空二维数组 grid 。

一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。

找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。)

示例 1:

[[0,0,1,0,0,0,0,1,0,0,0,0,0],

[0,0,0,0,0,0,0,1,1,1,0,0,0],

[0,1,1,0,1,0,0,0,0,0,0,0,0],

[0,1,0,0,1,1,0,0,1,0,1,0,0],

[0,1,0,0,1,1,0,0,1,1,1,0,0],

[0,0,0,0,0,0,0,0,0,0,1,0,0],

[0,0,0,0,0,0,0,1,1,1,0,0,0],

[0,0,0,0,0,0,0,1,1,0,0,0,0]]

对于上面这个给定矩阵应返回 6。注意答案不应该是 11 ,因为岛屿只能包含水平或垂直的四个方向的 1 。

示例 2:

[[0,0,0,0,0,0,0,0]]

对于上面这个给定的矩阵, 返回 0。

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/max-area-of-island

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

这题其实我自己做出来了,把它放在第一题嘛,我觉得因为它是比较简单的。

从头开始遍历,遇到“1”就展开上下左右搜索,搜空了就回溯,在搜索过程中计数,并将搜索到的位置全部置0,防止二次污染。

代码实现

int checkIsland(vector<vector>& grid, int x, int y) {

int count = 0;

if (grid[x][y] == 0)

return count;

else {

grid[x][y] = 0;

count++;

//上

if (x - 1 >= 0)

count += checkIsland(grid, x - 1, y);

//右

if ((y + 1) < grid[0].size())

count += checkIsland(grid, x, y + 1);

//下

if ((x + 1) < grid.size())

count += checkIsland(grid, x + 1, y);

//左

if ((y - 1) >= 0)

count += checkIsland(grid, x, y - 1);

}

return count;

}

int maxAreaOfIsland(vector<vector>& grid) {

if (grid.size() == 0)

return 0;

int max = 0;

for (int i = 0; i < grid.size(); i++) {

for (int j = 0; j < grid[0].size(); j++) {

int temp = checkIsland(grid, i, j);

if (temp > max)

max = temp;

}

}

}

return max;

}


八皇后问题

八皇后问题是一个古老的问题,于1848年由一位国际象棋棋手提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,如何求解?在这里插入图片描述

思路

将每一个棋子放在当前列的最前端,再放下一个棋子。

如果下一个棋子没地方放了,就回溯到前一个棋子,将其往后一个适宜位置挪动。

具体思路都在代码里了。

代码实现

#include

using namespace std;

#define MAX_NUM 8 //皇后数量

int queen[MAX_NUM][MAX_NUM] = { 0 };

bool check(int x, int y) { //检查一个坐标是否可以放置

for (int i = 0; i < y; i++) {

if (queen[x][i] == 1) { //这一行是否可以存在

return false;

}

if (x - 1 - i > 0 && queen[x - 1 - i][y - 1 - i] == 1) { //检查左斜列

return false;

}

if (x + 1 + i < MAX_NUM && queen[x + 1 + i][y + 1 + i] == 1) { //检查右斜列

return false;

}

}

queen[x][y] = 1;

return true;

}

void showQueen() {

for (int i = 0; i < MAX_NUM; i++) {

for (int j = 0; j < MAX_NUM; j++) {

cout << queen[i][j] << " ";

}

cout << endl;

}

cout << endl;

}

bool settleQueen(int x) {

if (x == MAX_NUM) { //遍历完毕,找到答案

return true;

}

for (int i = 0; i < MAX_NUM; i++) {

for (int y = 0; y < MAX_NUM; y++) {

queen[y][x] = 0; //清空当前列,省的回溯的时候被打扰

}

if (check(i,x)) { //如果这行找着了

queen[i][x] = 1;

showQueen(); //直观测试结果

if (settleQueen(x + 1)) { //是时候往左了

return true; //一路往左

}

}

}

return false; //如果不行,就退回来

}


括号生成

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例:

输入:n = 3

输出:[

“((()))”,

“(()())”,

“(())()”,

“()(())”,

“()()()”

]

来源:力扣(LeetCode)

链接:https://leetcode-cn.com/problems/generate-parentheses

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

如果左括号数量不大于 n,我们可以放一个左括号。如果右括号数量小于左括号的数量,我们可以放一个右括号。

代码实现

vector generateParenthesis(int n) {

vector res;

generateParenthesisDFS(n, n, “”, res);

return res;

}

void generateParenthesisDFS(int left, int right, string out, vector &res) {

if (left > right) return;

if (left == 0 && right == 0) res.push_back(out);

else {

if (left > 0) generateParenthesisDFS(left - 1, right, out + ‘(’, res);

if (right > 0) generateParenthesisDFS(left, right - 1, out + ‘)’, res);

}

}


全排列

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

输入: [1,2,3]

输出:

[

[1,2,3],

[1,3,2],

[2,1,3],

[2,3,1],

[3,1,2],

[3,2,1]

]

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/permutations

著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

怎么用,是不是感觉千头万绪,但是又捋不出一个头绪来,很难受。

思路

“全排列”就是一个非常经典的“回溯”算法的应用。我们知道,N 个数字的全排列一共有 N! 这么多个。

大家可以尝试一下在纸上写 3 个数字、4 个数字、5 个数字的全排列,相信不难找到这样的方法。

以数组 [1, 2, 3] 的全排列为例。

我们先写以 1 开头的全排列,它们是:[1, 2, 3], [1, 3, 2];

再写以 2 开头的全排列,它们是:[2, 1, 3], [2, 3, 1];

最后写以 3 开头的全排列,它们是:[3, 1, 2], [3, 2, 1]。

我们只需要按顺序枚举每一位可能出现的情况,已经选择的数字在接下来要确定的数字中不能出现。按照这种策略选取就能够做到不重不漏,把可能的全排列都枚举出来。

在枚举第一位的时候,有 3 种情况。

在枚举第二位的时候,前面已经出现过的数字就不能再被选取了;

在枚举第三位的时候,前面 2 个已经选择过的数字就不能再被选取了。

使用编程的方法得到全排列,就是在这样的一个树形结构中进行编程,具体来说,就是执行一次深度优先遍历,从树的根结点到叶子结点形成的路径就是一个全排列。

在这里插入图片描述

img
img

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

需要这份系统化的资料的朋友,可以添加戳这里获取

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

3, 2];

再写以 2 开头的全排列,它们是:[2, 1, 3], [2, 3, 1];

最后写以 3 开头的全排列,它们是:[3, 1, 2], [3, 2, 1]。

我们只需要按顺序枚举每一位可能出现的情况,已经选择的数字在接下来要确定的数字中不能出现。按照这种策略选取就能够做到不重不漏,把可能的全排列都枚举出来。

在枚举第一位的时候,有 3 种情况。

在枚举第二位的时候,前面已经出现过的数字就不能再被选取了;

在枚举第三位的时候,前面 2 个已经选择过的数字就不能再被选取了。

使用编程的方法得到全排列,就是在这样的一个树形结构中进行编程,具体来说,就是执行一次深度优先遍历,从树的根结点到叶子结点形成的路径就是一个全排列。

在这里插入图片描述

[外链图片转存中…(img-8tANeuXZ-1715556153936)]
[外链图片转存中…(img-FTbH6VEQ-1715556153937)]

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

需要这份系统化的资料的朋友,可以添加戳这里获取

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值