算法——递归与递推

典型递推算法

leetcode杨辉三角

在这里插入图片描述
方法一:考虑到每个系数都为C(n, k),然后可以通过制造递推关系式进行计算,这样每个数都可以在线性时间内计算出来。

vector<int> row(rowIndex + 1);
        row[0] = 1;
        for (int i = 1; i <= rowIndex; ++i) {
            row[i] = 1LL * row[i - 1] * (rowIndex - i + 1) / i;//将数字转换成Long Long类型,因为数字太大
        }
        return row;

方法二:(妙呀
递推式C(n, k) = C(n-1, k) + C(n-1, k-1)表明,当前行第 ii 项的计算只与上一行第i−1项及第 i项有关。因此我们可以倒着计算当前行,这样计算到第 i 项时,第 i−1 项仍然是上一行的值。

class Solution {
public:
    vector<int> getRow(int rowIndex) {
        vector<int> row(rowIndex + 1);
        row[0] = 1;
        for (int i = 1; i <= rowIndex; ++i) {
            for (int j = i; j > 0; --j) {
                row[j] += row[j - 1];
            }
        }
        return row;
    }
};

洛谷P3375KMP算法模板

详细内容参考博客

KMP字符串匹配算法中,重点在于next数组的快速求解算法。其过程中的核心思想就是递推(dp动态规划算法)

	string s, p;//s为主串,p为模式串
	next[0] = 0;
	for(int i = 1, now = 0; i < p.size(); ) {
		if(p[now] == p[i]){
			next[i] = now+1;
			i++; now++;
		}
		else if(now) {
			now = next[now-1];//递推关键——状态转移方程
		}
		else {//now已经为零了,表示p[0...(i-1)]这个子段内没有前后缀相同的内容
			next[i] = 0;
			i++;
		}
	}

ac代码:

#include<iostream>
#include<string>
using namespace std;
int ans[1000005];
int main() {
    string s, p;//模式串和子串
    cin >> s >> p;
    int *next = new int[p.size()];
    next[0] = 0;
    for(int x = 1, now = 0; x < p.size(); ) {//动态规划求解next数组
        if(p[now] == p[x]) {
            next[x] = now+1;
            now += 1; x += 1;
        } 
        else if(now) now = next[now-1];
        else {
            next[x] = 0;
            x += 1;
        }
    }
    //下面是KMP字符串匹配算法
    int k = 0;
    for(int i = 0, j = 0; i < s.size(); ) {
        if(s[i] == p[j]) {
            i++; j++;
        } 
        else if(j) {//如果失配点处j不等于零,表示还可以有比零更好的选择,所以使用next数组进行跳转
            j = next[j-1];
        }
        else i++;

        if(j == p.size()) {
            ans[k++] = i-j+1;
            j = next[j-1];
        }
    }
    for(int i = 0; i < k; i++) {
        cout << ans[i] << endl;
    }
    for(int i = 0; i < p.size(); i++) {
        cout << next[i] << " ";
    }
    return 0;
}

典型递归算法

递归算法书写思路——leetcode112

参考博客

在书写递归函数时,一定不能着急动手,需要先理清楚几个要点。

  1. 递归函数是否需要返回值,以及返回值的作用。

递归函数返回值会在函数栈中来回调用,一下列举几种典型情况:

  • 如果需要搜索整颗二叉树且不用处理递归返回值,递归函数就不要返回值。(例如113.路径总和ii)
  • 如果需要搜索整颗二叉树且需要处理递归返回值,递归函数就需要返回值。(例如236. 二叉树的最近公共祖先介绍)
  • 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(例如112,也就是本题)
  1. 递归函数到底什么时候返回,也就是递归终点的问题。

一定要抓住关键变量的特点设计递归终点。例如本题中按照递归是否到达叶子节点为标准进行判断,这样就只需要区分两种情况即可。有时候考虑得太多反而容易丢失重点导致思路混乱!

  1. 确定单层递归逻辑。

这里主要是未到递归终点,后面如何递归的问题。注意:在单层递归逻辑中,尤其要重点考虑递归函数返回值在递归逻辑中的作用,因为递归函数返回值是可以在每一层递归层中传递的。有时候可以将递归函数返回值作为条件进行判断(例如本题所示)

Go语言AC代码:

/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func Traverse(root *TreeNode, targetSum int) bool {
	// 到了叶子节点,同时目标值为零,表示符合要求
	if root.Left == nil && root.Right == nil && targetSum == 0 {
		return true
	} else if root.Left == nil && root.Right == nil { // 到了叶子节点,直接返回
		return false
	}
	if root.Left != nil { // 如果左节点非空,递归
		if Traverse(root.Left, targetSum-root.Left.Val) { // 这里利用函数返回值进行判断输出
			return true
		}
	}
	if root.Right != nil {
		if Traverse(root.Right, targetSum-root.Right.Val) {
			return true
		}
	}
	return false
}
func hasPathSum(root *TreeNode, targetSum int) bool {
	if root == nil {
		return false
	} else {
		return Traverse(root, targetSum-root.Val)
	}
}

洛谷P1322logo语言(读取技巧

一道递归的模板题目,但是需要注意该题目中所用到的关于c++语言的读取技巧。

题目网址

#include<iostream>
#include<string>
#include<cmath>
using namespace std;
int f() {//递归函数
    char c;
    string str;
    int k, ans = 0, v;
    while(cin >> c) { //到行尾自动停止读入
        if( c == ']') break;
        cin >> str >> k;
        if(c == 'R') {
            v = getchar();
            ans += k*f();
            v = getchar();
        } 
        if(c == 'F') {
            ans += k;
        } else if(c == 'B') {
            ans -= k;
        }
    }
    return ans;
}
int main() {
    cout << abs(f());
    return 0;
}
  • 第一:cin 会按照后面的变量的格式来选择性进行读取。但是在遇到enter, space, tab会结束读取。不会管结束标志,结束标志仍然在缓冲区内部,所以如果后面还有其他的不会忽略的读取函数时需要尤其注意(例如:getchar())
  • 如果输入字符串中有形如"150[]"这种数字+符号的组合,可以采用 cin >> k 的方式进行读取其中的数字(此处的k务必为int型)。因为 cin 会按照后面的类型进行读取。不会将 [] 读取到 k 中。

洛谷P3956棋盘问题——如何剪枝

在该问题中,如果使用DFS算法搜索掉所有的情况。将会造成很多时间和空间上的浪费,造成超时(因为这道题目中涉及到使用魔法将空白格子变为有颜色的格子,如果不剪枝将会造成巨大的浪费)

那么该如何剪枝呢?

首先,从剪枝的定义出发,所谓剪枝,就是指保存搜索过程中的一个决策变量(往往是题目中涉及到金钱,金币,能量值等等),然后利用这些决策变量进行条件判断,筛选掉那些不必要的搜索过程。例如本题中,保存每次搜索过程中的最小花费。然后在进行下一次搜索时,进行判断,如果对下一个格子的搜索造成的新的花费没有之前的路径搜索的花费少,那么也就不必要浪费时间对其进行无谓的搜索了。

AC代码:

#include<iostream>
#include<map>
using namespace std;
//DFS加剪枝处理
int m, n;
int minm = 0x7fffffff;
int suc = 0;
int d[1100][1100];//剪枝:保存每个位置的最小花费
map<pair<int, int>, bool> Map;
map<pair<int, int>, bool> temp;
bool HasSearched[105][105] = {0};
int dir[4][4] = {//依次为下、右、上、左
    {1, 0},
    {0, 1},
    {-1, 0},
    {0, -1}
};
//深度优先搜索dfs算法
void dfs(int x, int y, int ans) {
    if(x <= 0 || x > m || y <= 0 || y > m) return ;
    if(x == m && y == m) {
        minm = min(minm, ans);//更新结果
        suc = 1;
        return ;
    }
    HasSearched[x][y] = 1;
    if(Map.find(make_pair(x, y)) == Map.end()) {//表示该节点为空白节点
        for(int i = 0; i < 4; i++) { //如果空白节点旁边全都无法遍历,则递归到尽头
            int dx = x + dir[i][0];
            int dy = y + dir[i][1];
            if(HasSearched[dx][dy] || (!HasSearched[dx][dy] && Map.find(make_pair(dx, dy)) == Map.end()) \
            || dx <= 0 || dx > m || dy <= 0 || dy > m) ;
            else {
                bool tempCol = temp.find(make_pair(x, y))->second;
                bool col = Map.find(make_pair(dx, dy))->second;
                if(col == tempCol) {
                    if(ans < d[dx][dy]) d[dx][dy] = ans, dfs(dx, dy, ans);
                }
                else {
                    if(ans+1 < d[dx][dy]) d[dx][dy] = ans+1, dfs(dx, dy, ans+1);
                }
            }
        }
        HasSearched[x][y] = 0; return;
    }
    bool col = Map.find(make_pair(x, y))->second;//表示该节点的颜色:0->红色;1->黄色
    for(int i = 0; i < 4; i++){
        int dx = x + dir[i][0];
        int dy = y + dir[i][1];
        if(HasSearched[dx][dy] || dx <= 0 || dx > m || dy <= 0 || dy > m) continue;
        if(Map.find(make_pair(dx, dy)) == Map.end()) {
            temp[make_pair(dx, dy)] = col;//暂时将白色格子染成和前一个格子同一样的颜色
            if(ans+2 < d[dx][dy]) d[dx][dy] = ans+2, dfs(dx, dy, ans+2);//施展魔法
            temp.erase(make_pair(dx, dy));
        }
        else if(Map.find(make_pair(dx, dy))->second == col){
            if(ans < d[dx][dy]) d[dx][dy] = ans, dfs(dx, dy, ans);
        }
        else if(Map.find(make_pair(dx, dy))->second != col) {
            if(ans+1 < d[dx][dy]) d[dx][dy] = ans+1, dfs(dx, dy, ans+1);
        }
    }
    HasSearched[x][y] = 0;
    return ;
}

int main() {
    cin >> m >> n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++) d[i][j]=0x7fffffff;
    //读入地图
    for(int i = 0; i < n; i++) {
        int x, y, col;
        cin >> x >> y >> col;
        pair<int, int> tmp(x, y);
        Map[tmp] = col;
    }
    dfs(1, 1, 0);
    if(suc) cout << minm;
    else cout << -1;
    return 0;
}

leetcode二叉树的最近祖先

1)递归算法

核心思想:找到对应的子问题:左右子树中是否含有p或者q结点。然后再设计递归出口,最后实现递归函数深度优先遍历解决问题。

class Solution {
public:
    //递归算法关键:寻找核心子问题
    TreeNode* ans;
    bool dfs(TreeNode* T, TreeNode* p, TreeNode* q){//T的左右子树中是否有p或者q
        if(!T) return false;
        bool lson = dfs(T->left, p, q);
        bool rson = dfs(T->right, p, q);
        if((lson && rson) || (((T->val == p->val) || (T->val == q->val)) && (lson || rson))){
            ans = T;
        }
        return lson || rson || (T->val == p->val || T->val == q->val);
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        dfs(root, p, q);
        return ans;
    }
};

2)另解

  • 一旦出现需要判断是否出现过或者出现次数的问题,可以使用散列表。(重点关注下面代码中关于map和set的合理使用
class Solution {
public:
    //建立父节点,从而利用散列表来解决问题
    unordered_map<TreeNode*, TreeNode*> fNode;//fNode表示每个结点(key)的父节点(val)
    unordered_set<TreeNode*> fer;//用来记录p的父节点序列
    void dfs(TreeNode* T){//利用dfs来建立每个结点的父节点
        if(T->left){
            fNode[T->left] = T;
            dfs(T->left);
        }
        if(T->right){
            fNode[T->right] = T;
            dfs(T->right);
        }
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        fNode[root] = nullptr;
        dfs(root);
        //典型处理方式:如何判断两个链表中的相同元素问题?
        while(p){
            fer.insert(p);
            p = fNode[p];
        }
        while(q){
            if(fer.find(q) != fer.end()) return q;
            q = fNode[q];
        }
        return nullptr;
    }
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lingwu_hb

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值