当前位置: 首页 > article >正文

递归,搜索与回溯40道算法题

目录

一递归

1汉诺塔问题

2合并两个有序链表

3反转链表 

 4两两交换链表的节点

5Pow(x,n) 

二二叉树的深搜

1计算布尔二叉树的值

 2求根节点到叶节点数字之和

3二叉树剪枝 

4验证二叉搜索树 

5二叉搜索树中第K小的元素

6二叉树的所有路径

三穷举vs暴搜vs深搜vs回溯vs剪枝 

 1全排列

 2子集

四综合联系

1找出所有子集的异或总和再求和 

2全排列Ⅱ 

3电话号码的字母组合

4括号生成 

 5组合

6目标和

7组合总和

8字母大小写全排列 

9优美的排列 

10N皇后 

11有趣的数独

12解数独

13单词搜索

14黄金矿工

15不同路径Ⅲ

五floodfill算法 

1图像渲染

2岛屿数量

3岛屿的最大面积 

4被环绕的区域

5太平洋大西洋水流问题 

6扫雷游戏

7衣橱整理 

六记忆化搜索 

1斐波那契数

解法1:递归 O(2^N)

解法2:记忆化搜索 O(N)

解法3动态规划 O(N) O(N)

 2不同路径

3最长递增子序列  

4猜数字大小II  

5矩阵中的最长递增路径


一递归

1汉诺塔问题

oj链接:汉诺塔问题

先进行画图分析

d08cdce10b314144b308176b46d05545.png

N=2时,为了最下面的盘子解放,我们直接将(一个)盘子移到B上

而N=3时,为了最下面的盘子解放,(参考N=2)我们需要想办法将2个盘子移到B上,有办法吗?

当然有,借助C间接完成该操作~

然后我们将最大(解放)的盘子移到C上,那下一步呢?

将B上的全部盘子通过A的帮助移到C上,怎么做的问题就转化为:

N=2时将A上的全部盘子通过B的帮助移到C的思路是类似的~(只是柱子对象发生改变了而已)

重复子问题:

设A上盘子数量为N(N>1)

将A上的N-1个盘子通过C的帮助下移到B上(相信递归能帮助我们做到)

剩下的最大的盘子直接移到C上

将B上的N-1个盘子通过A的帮助下移到C上,完成任务~

设计函数头:

要有A,B,C三个vector容器进行数据移动,而且也要有表示当前盘子的数量:

dfs(N,A,B,C)   -> n个盘子全部在A上(开始情况)

dfs(N-1,A,C,B) -> 将A上n-1个盘子借助C全部移到B上(递归后,B=C,C=B)

dfs(N-1,B,A,C) -> 将B上n-1个盘子借助A全部移到C上(递归后,A=B,B=A)

递归出口:

当N==1时,函数参数的A上盘子数量只有一个,直接移到C上即可~

注意:以上在dfs调用时A不一定是我们原来说的那个A,有可能变了B,C

只有调用结束后,返回到原函数时,此时函数的A才是我们说的那个A!

class Solution {
public:
    void dfs(int N,vector<int>& A,vector<int>& B,vector<int>& C)
    {
        if(N==1)
        {
            C.push_back(A.back());
            A.pop_back();
            return;
        }
        //n-1个盘子 A->B
        dfs(N-1,A,C,B);
        //1个盘子 A->C
        C.push_back(A.back());
        A.pop_back();

        //n-1个盘子 B->C
        dfs(N-1,B,A,C);
    }
    void hanota(vector<int>& A, vector<int>& B, vector<int>& C) 
    {
        int N=A.size();
        dfs(N,A,B,C);
    }
};

2合并两个有序链表

oj链接:合并两个有序链表

思路1:通过while循环:判断两个链表那个节点小就把它插入到新定义的链表中(可以是带头节点的,也可以是不带头节点的),然后将该节点对应的链表进行移动..直到其中有一个链表为空就停止循环

将剩下不为空的链表接到新链表中即可~

// no head
class Solution
{
public:
    ListNode *mergeTwoLists(ListNode *list1, ListNode *list2)
    {
        ListNode *ret = nullptr, *cur = nullptr;

        while (list1 && list2)
        {
            if (list1->val < list2->val)
            {
                if (ret == nullptr)
                    ret = cur = list1;
                else
                {
                    cur->next = list1;
                    cur = cur->next;
                }

                list1 = list1->next;
            }
            else
            {
                if (ret == nullptr)
                    ret = cur = list2;
                else
                {
                    cur->next = list2;
                    cur = cur->next;
                }
                list2 = list2->next;
            }
        }
        while (list1)
        {
            if (ret == nullptr)
                ret = cur = list1;
            else
            {
                cur->next = list1;
                cur = cur->next;
            }
            list1 = list1->next;
        }
        while (list2)
        {
            if (ret == nullptr)
                ret = cur = list2;
            else
            {
                cur->next = list2;
                cur = cur->next;
            }
            list2 = list2->next;
        }
        return ret;
    }
};

// head
class Solution
{
public:
    ListNode *mergeTwoLists(ListNode *list1, ListNode *list2)
    {
        ListNode *ret = new ListNode(-1);
        ListNode *cur = ret;

        while (list1 && list2)
        {
            if (list1->val < list2->val)
            {
                cur->next = list1;
                cur = cur->next;
                list1 = list1->next;
            }
            else
            {
                cur->next = list2;
                cur = cur->next;
                list2 = list2->next;
            }
        }
        while (list1)
        {
            cur->next = list1;
            cur = cur->next;
            list1 = list1->next;
        }
        while (list2)
        {
            cur->next = list2;
            cur = cur->next;
            list2 = list2->next;
        }
        ListNode *ans = ret->next;
        delete ret;
        return ans;
    }
};

思路2:递归

重复子问题:

比较两个链表的头节点那个小,那个就是要返回新链表的头节点

接着:头节点小的链表的next与另一个链表合并成一个有序链表

设计函数头:

参数要有两个链表的头节点,而题目正好符合要求,不用在重新写

递归出口: 

当其中一个链表为空时,自己返回另一个链表

class Solution
{
public:
    ListNode *mergeTwoLists(ListNode *list1, ListNode *list2)
    {
        if (list1 == nullptr)
            return list2;
        if (list2 == nullptr)
            return list1;

        if (list1->val < list2->val)
        {
            list1->next = mergeTwoLists(list1->next, list2);
            return list1;
        }
        else
        {
            list2->next = mergeTwoLists(list1, list2->next);
            return list2;
        }
    }
};

3反转链表 

oj链接:反转链表 

思路1:定义新链表:循环进行头插法 

class Solution
{
public:
    ListNode *reverseList(ListNode *head)
    {
        ListNode *ret = new ListNode(-1);
        ListNode *cur = ret;
        while (head)
        {
            ListNode *next = head->next;
            head->next = cur->next;
            cur->next = head;
            head = next;
        }
        ListNode *ans = ret->next;
        delete ret;
        return ans;
    }
};

思路2:递归

重复子问题:

让当前节点后面的链表先逆置,得到逆置完后链表的头节点

将当前节点连接到逆置完后的链表的尾部

返回逆置完后链表的头节点(一层一层往上呈递上去)

设计函数头:

由于返回值要返回头节点,参数有要进行逆置的链表的头节点,题目所给正好符合~

递归出口: 

当节点为空或者节点的下一个节点为空时,返回该节点

581315082cc8416499d1b5546b083176.png

class Solution
{
public:
    ListNode *reverseList(ListNode *head)
    {
        if (head == nullptr || head->next == nullptr)
            return head;
        ListNode *phead = reverseList(head->next);
        ListNode *cur = phead;
        while (cur->next)
            cur = cur->next;
        cur->next = head;
        head->next = nullptr;
        return phead;
    }
};

 4两两交换链表的节点

oj链接:两两交换链表中的节点

思路1:利用while循环进行两两交换并连入新定义的链表 

class Solution
{
public:
    ListNode *swapPairs(ListNode *head)
    {
        ListNode *ret = new ListNode(-1);
        ListNode *cur = ret;
        while (head && head->next)
        {
            ListNode *cur1 = head->next;//储存
            ListNode *next = head->next->next;

            head->next->next = head;
            head->next = nullptr;
            cur->next = cur1;

            cur = cur->next->next;
            head = next;
        }
        if (head)//剩下一个节点
            cur->next = head;
        ListNode *ans = ret->next;
        delete ret;
        return ans;
    }
};

思路2:递归

重复子问题:

让当前节点->next->next的链表先逆置,得到逆置完后的链表的头节点

当前节点->next与逆置完后的链表进行连接即可~

返回两两交换节点的第二个节点(提前储存)

设计函数头:

由于返回值要返回头节点,参数有要进行逆置的链表的头节点,题目所给正好符合~

递归出口: 

当节点为空或者节点的下一个节点为空时,返回该节点

3d05e86957e449959244e03abbcee907.png

class Solution
{
public:
    ListNode *swapPairs(ListNode *head)
    {
        if (head == nullptr || head->next == nullptr)
            return head;

        ListNode *tmp = swapPairs(head->next->next);

        ListNode *ret = head->next; // head->next提前储存
        head->next->next = head;
        head->next = tmp;

        return ret;
    }
};

5Pow(x,n) 

 oj链接:Pow(x, n)

思路1:直接x*x*x..的进行n次相乘,但遇到n太大时会栈溢出,不合适~

class Solution {
public:
    double myPow(double x, int n) 
    {
        if(n==0) return 1;

        if(n>0)
        {
            double tmp=myPow(x,n/2);
            return n%2==0?tmp*tmp:tmp*tmp*x;
        }
        else
        {
            int m=-(long long)n;
            double tmp=myPow(x,m/2);
            return m%2==0?1/(tmp*tmp):1/(tmp*tmp*x);
        }
    }
};

思路2:递归

重复子问题:

当n为偶数时,将pow(x,n)进行拆分为:两个pow(x,n/2)进行相乘:而pow(x,n/2)可进行拆分为:两个pow(x,n/2/2)进行相乘...(为了保证递归时不栈溢出,可以先求出拆分数值再相乘

但n为奇数时,pow(x,n)拆分是:两个pow(x,n/2)进行相乘后再与x相乘(奇数除二取整)

设计函数头:

返回值要返回计算结果,参数有x和n,题目所给符合~

递归出口: 

当n==0时,返回1即可 

细节问题:

1.n为负数时要单独考虑

2.n==2^31-1转化为正数时有可能溢出

76c24882aee84593b5dcf1f831f5008b.png

class Solution
{
public:
    double myPow(double x, int n)
    {
        if (n == 0)
            return 1;

        if (n > 0)
        {
            double tmp = myPow(x, n / 2);
            return n % 2 == 0 ? tmp * tmp : tmp * tmp * x;
        }
        else
        {
            long long m = -(long long)n; // 数据溢出处理
            double tmp = myPow(x, m / 2);
            return m % 2 == 0 ? 1 / (tmp * tmp) : 1 / (tmp * tmp * x);
        }
    }
};

二二叉树的深搜

1计算布尔二叉树的值

oj链接:计算布尔二叉树的值

重复子问题:

从叶子节点往上进行计算

设计函数头:

题目所给符合

递归出口: 

(题目二叉树是固定的)当root->left==null时,根据val进行返回 

class Solution
{
public:
    bool evaluateTree(TreeNode *root)
    {
        if (root->left == nullptr)
            return root->val == 0 ? false : true; // 二叉树是完整二叉树

        bool left = evaluateTree(root->left);
        bool right = evaluateTree(root->right);
        return root->val == 2 ? left | right : left & right;
    }
};

 2求根节点到叶节点数字之和

oj链接:求根节点到叶节点数字之和

重复子问题:

1.得到这层的数字

2.进行递归,得到左子树最终数字之和

3.进行递归,得到右子树最终数字之和

4.将结果进行返回

设计函数头:

参数root,还要sum表示当前数字值;return 返回结果

递归出口: 

如果节点时叶子节点就自己返回当前数字值

f7d28d11cfa045478178b6e98c26d16d.png

class Solution
{
public:
    int sumNumbers(TreeNode *root)
    {
        return dfs(root, 0);
    }
    int dfs(TreeNode *root, int presum)
    {
        int sum = presum * 10 + root->val;
        // 出口
        if (root->left == nullptr && root->right == nullptr)
            return sum;
        // 左右相加
        int ret = 0;
        if (root->left != nullptr)
            ret += dfs(root->left, sum);
        if (root->right != nullptr)
            ret += dfs(root->right, sum);

        return ret;
    }
};

3二叉树剪枝 

oj链接:二叉树剪枝

重复子问题:

1.进行递归,得到左子树的节点

2.进行递归,得到右子树的节点

3.根据得到的新的root的情况来进行返回

设计函数头:

参数root,return 新二叉树

递归出口: 

如果节点为空返回空

class Solution
{
public:
    TreeNode *pruneTree(TreeNode *root)
    {
        if (root == nullptr)
            return nullptr;
        root->left = pruneTree(root->left);
        root->right = pruneTree(root->right);
        if (root->left == nullptr && root->right == nullptr && root->val == 0)
            return nullptr;
        return root;
    }
};

4验证二叉搜索树 

oj链接:验证二叉搜索树

 二叉搜索树的性质:中序遍历是有序的

重复子问题:

1.进行递归,得到左子树是否是二叉搜索树

2.用一个全局变量(最小值)与当前节点值进行比较,并将变量改为节点值

3.进行递归,得到右子树是否是二叉搜索树

设计函数头:

题目符合要求

递归出口: 

如果节点为空返回true

class Solution
{
public:
    long long prev = LONG_MIN;
    bool isValidBST(TreeNode *root)
    {
        if (root == nullptr)
            return true;
        bool ret = true;

        ret &= isValidBST(root->left);

        if (ret == false)
            return false; // 剪枝

        if (root->val <= prev)
            ret &= false;
        prev = root->val;

        if (ret == false)
            return false; // 剪枝

        ret &= isValidBST(root->right);
        return ret;
    }
};

5二叉搜索树中第K小的元素

oj链接:二叉搜索树中第 K 小的元素

重复子问题:

由于二叉树是二叉搜索树(中序遍历是从小到大),所以找第K小的数只需进行遍历查找

我们用两个全局变量:count记录要查找的第几个数,tmp记录当前位置的数值(如果count==k就说明找到了,往上返回即可)

设计函数头:

参数root return为空

递归出口: 

如果结点为空返回

class Solution
{
public:
    int count = 0, ret = 0;
    int kthSmallest(TreeNode *root, int k)
    {
        if (root == nullptr)
        {
            return 0;
        }
        kthSmallest(root->left, k);
        count++;
        if (count == k)
            ret = root->val;

        kthSmallest(root->right, k);

        return ret;
    }
};

6二叉树的所有路径

oj链接:二叉树的所有路径

重复子问题:

1.要找到二叉树的所有路径,只需进行前序遍历即可;

2.如果考虑都用全局变量:string path记录当前路径,vector<string> ret记录所有路径

问题:path往下递归时每次都要加"val和->":

a.如果递归到空结点时"->"要去掉,添加到ret后往上返回?

b.还是遇到叶子结点添加"val"后停止递归,添加到ret,再将val去掉,最后往上返回?

可见:path作为全局变量很麻烦!

我们考虑path作为局部变量就没有上面的问题了!(每一层的path不会影响另一层的path)

直接:遇到叶子结点加"val",其它情况加"val ->"

设计函数头:

参数:root,path return 为空

递归出口: 

如果结点为空返回

class Solution
{
public:
    vector<string> ret;
    vector<string> binaryTreePaths(TreeNode *root)
    {
        dfs(root, "");
        return ret;
    }
    void dfs(TreeNode *root, string path)
    {
        if (root == nullptr)
            return;

        if (root->left == nullptr && root->right == nullptr)
        {
            path += to_string(root->val);
            ret.push_back(path);
            return;
        }

        path += to_string(root->val);
        path += "->";
        dfs(root->left, path);
        dfs(root->right, path);
    }
};

三穷举vs暴搜vs深搜vs回溯vs剪枝 

关于这类问题,直接画出决策树进行分析问题~

 1全排列

oj链接:全排列

画出决策树:

设计代码:

a全局变量

vector<vector<int>> ret 收集叶子节点

vector<int> path 枚举每层的节点

bool vis[] 往下一层枚举时剪去当前位置的数值(由false变true)

b设计函数

只需关心每一层在干什么

c递归出口

当nums.size()==path.size()(到达叶子节点),将pathpush到ret中后return

回溯

1.干掉path的最后一个元素

2.修改vis数组(有true变false) 

class Solution
{
public:
    bool check[7];
    vector<int> path;
    vector<vector<int>> ret;
    vector<vector<int>> permute(vector<int> &nums)
    {
        dfs(nums);
        return ret;
    }
    void dfs(vector<int> &nums)
    {

        // 出口
        if (nums.size() == path.size())
        {
            ret.push_back(path);

            return;
        }
        for (int i = 0; i < nums.size(); i++)
        {
            // 剪枝
            if (check[i] == false)
            {
                path.push_back(nums[i]);
                check[i] = true;
                dfs(nums);
                // 回溯
                check[i] = false;
                path.pop_back();
            }
        }
    }
};

 2子集

oj链接:子集

思路1 

画出决策树:

设计代码:

a全局变量

vector<vector<int>> ret 收集叶子节点

vector<int> path 枚举每层的节点

b设计函数

dfs(nums,i) i表示当前nums的位置

选 path+=nums[i],dfs(nums,i+1)

不选 dfs(nums,i+1)

c递归出口

当i==nums.size(),将pathpush到ret中后return

回溯

如果有选 干掉path的最后一个元素 

class Solution
{
public:
    vector<vector<int>> ret;
    vector<int> path;

    vector<vector<int>> subsets(vector<int> &nums)
    {
        dfs(nums, 0);
        return ret;
    }
    void dfs(vector<int> &nums, int i)
    {
        if (i == nums.size())
        {
            ret.push_back(path);
            return;
        }
        // 选
        path.push_back(nums[i]);
        dfs(nums, i + 1);
        path.pop_back();
        // 不选
        dfs(nums, i + 1);
    }
};

思路2

画出决策树:

设计代码:

a全局变量

vector<vector<int>> ret 收集叶子节点

vector<int> path 枚举每层的节点

b设计函数

dfs(nums,pos) pos表示下一次枚举的位置

回溯

干掉path的最后一个元素 

在这种思路中由于每个节点都是答案都要收集,所以没有递归出口~

 

class Solution
{
public:
    vector<vector<int>> ret;
    vector<int> path;
    vector<vector<int>> subsets(vector<int> &nums)
    {
        dfs(nums, 0);
        return ret;
    }
    void dfs(vector<int> &nums, int pos)
    {
        // 没有剪枝与出口
        ret.push_back(path);
        for (int i = pos; i < nums.size(); i++)
        {
            path.push_back(nums[i]);
            dfs(nums, i + 1);
            // 回溯
            path.pop_back();
        }
    }
};

四综合联系

1找出所有子集的异或总和再求和 

oj链接:找出所有子集的异或总和再求和 

与求子集的思路一样:回溯时注意异或消消乐(同一个数异或两次抵消)即可 

class Solution
{
public:
    int path = 0, ret = 0;
    int subsetXORSum(vector<int> &nums)
    {
        dfs(nums, 0);
        return ret;
    }
    void dfs(vector<int> &nums, int i)
    {
        if (i == nums.size())
        {
            ret += path;
            return;
        }

        path ^= nums[i];
        dfs(nums, i + 1);
        path ^= nums[i];

        dfs(nums, i + 1);
    }
};

2全排列Ⅱ 

 oj链接:全排列 II

画出决策树:

这里主要是剪枝麻烦~其它与全排列一致

剪枝

1.在同一节点的所有分支中,相同元素只能出现一次 --> 怎么判断?

a.只关心不合法的‘分支’

  i!=0 && nums[i] == nums[i-1] && !vis[i-1] 

b.只关心合法的‘分支’

  i==0 || nums[i] != nums[i-1] || vis[i-1]

  而 vis[i-1] 的意思是:nums[i] ==nums[i-1] 但我是合法的~

2.同一个元素只能使用一次 -> bool vis解决

class Solution
{
public:
    vector<vector<int>> ret;
    vector<int> path;
    bool vis[8] = {false};
    vector<vector<int>> permuteUnique(vector<int> &nums)
    {
        // 先排序
        sort(nums.begin(), nums.end());
        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(!(vis[i]||(i>0&&nums[i]==nums[i-1]&&!vis[i-1]))) 不合法分支
            if (!vis[i] && (i == 0 || nums[i] != nums[i - 1] || vis[i - 1])) // 合法分支
            {
                vis[i] = true;
                path.push_back(nums[i]);
                dfs(nums);
                path.pop_back();
                vis[i] = false;
            }
        }
    }
};

3电话号码的字母组合

oj链接:电话号码的字母组合

画出决策树:

这里主要解决数字与字符串的映射关系  --> 用字符串数组一一映射解决~

设计代码:

a全局变量

vector<vector<int>> ret 收集叶子节点

vector<int> path 枚举每层的节点

vector<string> check 数字与字符串一一映射

b设计函数

dfs(nums,pos) pos表示下一次枚举的位置

回溯

干掉path的最后一个元素 

递归出口

当path.size()==digits.size()时:收集叶子结点后返回

class Solution
{
public:
    vector<string> ret;
    string path;
    bool vis[10];
    vector<string> check = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
    vector<string> letterCombinations(string digits)
    {
        if (digits.size() == 0)
            return ret;
        dfs(digits, 0);
        return ret;
    }
    void dfs(string digits, int pos)
    {
        if (path.size() == digits.size())
        {
            ret.push_back(path);
            return;
        }
        string tmp = check[digits[pos] - '0'];
        for (auto &c : tmp)
        {
            path += c;
            dfs(digits, pos + 1);
            path.pop_back();
        }
    }
};

4括号生成 

oj链接:括号生成

做题之前要明白什么是‘有效括号’

1.左括号的数量 = 右括号的数量

2.从头开始的任意子串:左括号数量 >= 右括号数量

画出决策树:

设计代码:

a全局变量

vector<vector<int>> ret 收集叶子节点

vector<int> path 枚举每层的节点

int left,right 表示左,右括号数量

b设计函数

dfs(nums) 

回溯

干掉path的最后一个元素 

递归出口

当右括号数量 == n时 收集叶子节点后返回

class Solution
{
public:
    vector<string> ret;
    string path;
    int left = 0, right = 0;
    vector<string> generateParenthesis(int n)
    {
        dfs(n);
        return ret;
    }
    void dfs(int n)
    {
        if (path.size() == n * 2)
        {
            ret.push_back(path);
            return;
        }
        // 选左括号
        if (left < n)
        {
            path += '(';
            left++;
            dfs(n);
            path.pop_back();
            left--;
        }
        // 选右括号
        if (left > right)
        {
            path += ')';
            right++;
            dfs(n);
            path.pop_back();
            right--;
        }
    }
};

 5组合

oj链接:组合

画出决策树:

设计代码:

a全局变量

vector<vector<int>> ret 收集叶子节点

vector<int> path 枚举每层的节点

b设计函数

dfs(n,k,pos) --> pos记录枚举的位置(往后枚举) 

回溯

干掉path的最后一个元素 

递归出口

当 path.size() == k时收集叶子节点后返回

class Solution
{
public:
    vector<vector<int>> ret;
    vector<int> path;
    vector<vector<int>> combine(int n, int k)
    {
        dfs(n, k, 1);
        return ret;
    }
    void dfs(int n, int k, int pos)
    {
        if (path.size() == k)
        {
            ret.push_back(path);
            return;
        }
        for (int i = pos; i <= n; i++)
        {
            path.push_back(i);
            dfs(n, k, i + 1);
            path.pop_back();
        }
    }
};

6目标和

oj链接:目标和

画出决策树:

设计代码:

a全局变量

int ret 收集符合的情况和

int aim 全局的目标和

b设计函数

dfs(nums,pos,sum) --> pos 记录当前位置 sum当前和

回溯

递归出口

当 nums.size() == pos时看参数是否等于aim来判断ret是否++ 返回

class Solution
{
public:
    int ret = 0, aim = 0;
    int findTargetSumWays(vector<int> &nums, int target)
    {
        aim = target;
        dfs(nums, 0, 0);
        return ret;
    }
    void dfs(vector<int> &nums, int pos, int path)
    {
        if (pos == nums.size())
        {
            if (path == aim)
                ret++;
            return;
        }
        // 加法
        dfs(nums, pos + 1, path + nums[pos]);
        // 减法
        dfs(nums, pos + 1, path - nums[pos]);
    }
};

7组合总和

oj链接:组合总和

思路1 

画出决策树

根据当前位置往后来枚举组合总和

设计代码:

a全局变量

vector<vector<int>> ret 收集叶子结点

vector<int> path 当前每层节点

int aim 目标和

b设计函数

dfs(candidates,pos,sum) --> pos 从当前位置开始  sum 当前位置和

回溯

干掉最后一个元素

递归出口

当sum >=aim时 看sum是否等于aim来决定 ret.push_back(path) 最后往上返回

class Solution
{
public:
    vector<vector<int>> ret;
    vector<int> path;
    int aim;
    vector<vector<int>> combinationSum(vector<int> &candidates, int target)
    {
        aim = target;
        dfs(candidates, 0, 0);
        return ret;
    }
    void dfs(vector<int> &candidates, int pos, int sum)
    {
        if (sum >= aim)
        {
            if (sum == aim)
            {
                ret.push_back(path);
            }
            return;
        }

        for (int i = pos; i < candidates.size(); i++)
        {
            path.push_back(candidates[i]);
            dfs(candidates, i, sum + candidates[i]);
            path.pop_back();
        }
    }
};

思路2

画出决策树

根据当前位置要枚举多少个来进行组合总和

设计代码:

a全局变量

vector<vector<int>> ret 收集叶子结点

vector<int> path 当前每层节点

int aim 目标和

b设计函数

dfs(candidates,pos,sum) --> pos 从当前位置开始  sum 当前位置和

回溯

当前层所有情况枚举完才恢复现场

递归出口

当 sum == aim ret.push_back(path) 往上返回

当 sum >aim || pos == candidates.size() 往上返回

 

class Solution
{
public:
    int aim = 0;
    vector<vector<int>> ret;
    vector<int> path;
    vector<vector<int>> combinationSum(vector<int> &candidates, int target)
    {
        aim = target;
        dfs(candidates, 0, 0);
        return ret;
    }
    void dfs(vector<int> &candidates, int pos, int sum)
    {
        if (sum == aim)
        {
            ret.push_back(path);
            return;
        }
        if (sum > aim || pos == candidates.size())
            return;
        // 枚举个数
        for (int i = 0; sum + i * candidates[pos] <= aim; i++)
        {
            if (i)
                path.push_back(candidates[pos]);
            dfs(candidates, pos + 1, sum + i * candidates[pos]);
        }

        // 所有情况枚举完才回溯
        for (int i = 1; sum + i * candidates[pos] <= aim; i++)
        {
            path.pop_back();
        }
    }
};

8字母大小写全排列 

oj链接:字母大小写全排列

画出决策树

设计代码:

a全局变量

vector<string> ret 收集叶子结点

string path 当前每层节点

b设计函数

dfs(s,pos) --> pos 当前位置 

回溯

干掉最后一个元素

递归出口

当path.size()== pos 收集结果向上返回

class Solution
{
public:
    vector<string> ret;
    string path;
    int n;
    vector<string> letterCasePermutation(string s)
    {
        n = s.size();
        dfs(s, 0);
        return ret;
    }
    void dfs(string s, int pos)
    {
        if (pos == n)
        {
            ret.push_back(path);
            return;
        }

        // 不改
        path += s[pos];
        dfs(s, pos + 1);
        path.pop_back();

        // 改
        if (s[pos] < '1' || s[pos] > '9')
        {
            char tmp = change(s[pos]);
            path.push_back(tmp);
            dfs(s, pos + 1);
            path.pop_back();
        }
    }
    char change(char tmp)
    {
        if (tmp >= 'a' && tmp <= 'z')
            tmp -= 32;
        else
            tmp += 32;
        return tmp;
    }
};

9优美的排列 

oj链接:优美的排列

画出决策树

设计代码:

a全局变量

int ret 收集叶子结点

vector<int> path 当前每层节点

bool vis[15] 去重

b设计函数

dfs(n,pos) --> pos 当前位置 

回溯

干掉最后一个元素

vis[i]=false

递归出口

当path.size()== n 收集结果向上返回

class Solution
{
public:
    int ret;
    vector<int> path;
    bool vis[15]={false};
    int countArrangement(int n)
    {
        dfs(n, 1);
        return ret;
    }
    void dfs(int n, int pos)
    {
        if (path.size() == n)
        {
            ret++;
            return;
        }
        for (int i = 1; i <= n; i++)
        {
            if (!vis[i] && (i % pos == 0 || pos % i == 0))
            {
                vis[i] = true;
                path.push_back(i);
                dfs(n, pos + 1);
                path.pop_back();
                vis[i] = false;
            }
        }
    }
};

10N皇后 

 oj链接:N 皇后

画出决策树(不重要)

主要:剪枝 + 代码

设计代码:

a全局变量

vector<vector<string>> ret 收集叶子结点

vector<string> path 当前每层节点

bool col[10]  bool diag1[20] bool diag2[20] 剪枝

b设计函数

dfs(row) --> row 当前所在行数

回溯

将‘Q’的位置修改为‘.’

col diag1 diag2 修改为 false

递归出口

当row== n 收集结果向上返回 

class Solution
{
public:
    vector<vector<string>> ret;
    vector<string> path;
    bool col[10] = {false}, diag1[20] = {false}, diag2[20] = {false};
    vector<vector<string>> solveNQueens(int n)
    {
        path.resize(n);
        for (int i = 0; i < n; i++)
        {
            path[i].resize(n, '.');
        }
        dfs(n, 0);
        return ret;
    }
    void dfs(int n, int row)
    {
        if (row == n)
        {
            ret.push_back(path);
            return;
        }
        for (int i = 0; i < n; i++) // 枚举每行的位置
        {
            if (!col[i] && !diag1[i - row + n] && !diag2[i + row + n])
            {
                path[row][i] = 'Q';
                col[i] = diag1[i - row + n] = diag2[i + row + n] = true;
                dfs(n, row + 1);
                path[row][i] = '.';
                col[i] = diag1[i - row + n] = diag2[i + row + n] = false;
            }
        }
    }
};

11有趣的数独

oj链接:有效的数独

 此题严格来说不算是dfs类问题但有点像

全局变量

bool row[9][10] col[9][10] diag[3][3][10] 来进行判断是否出现重复数字即可(空间换时间)

class Solution
{
public:
    bool row[9][10] = {false};
    bool col[9][10] = {false};
    bool grid[3][3][10] = {false};
    bool isValidSudoku(vector<vector<char>> &board)
    {
        for (int i = 0; i < board.size(); i++)
        {
            for (int j = 0; j < board[i].size(); j++)
            {
                if (board[i][j] != '.')
                {
                    int tmp = board[i][j] - '0';
                    if (!row[i][tmp] && !col[j][tmp] && !grid[i / 3][j / 3][tmp])
                    {
                        row[i][tmp] = col[j][tmp] = grid[i / 3][j / 3][tmp] = true;
                    }
                    else
                    {
                        return false;
                    }
                }
            }
        }
        return true;
    }
};

12解数独

oj链接:解数独

借助上题的方法:用三个全局变量判断数独是否重复了

全局变量

bool row[9][10] col[9][10] diag[3][3][10] 

b设计函数

bool dfs(board) --> 每个'.'进行1-9数字尝试,如果出现填不了的情况要告诉上层false了

class Solution
{
public:
    bool row[9][10] = {false}, col[9][10] = {false};
    bool diag[3][3][10] = {false};
    void solveSudoku(vector<vector<char>> &board)
    {
        for (int i = 0; i < board.size(); i++)
        {
            for (int j = 0; j < board[0].size(); j++)
            {
                if (board[i][j] != '.')
                {
                    int tmp = board[i][j] - '0';
                    row[i][tmp] = col[j][tmp] = diag[i / 3][j / 3][tmp] = true;
                }
            }
        }
        dfs(board);
    }

    bool dfs(vector<vector<char>> &board)
    {
        for (int i = 0; i < board.size(); i++)
        {
            for (int j = 0; j < board[0].size(); j++)
            {
                if (board[i][j] == '.')
                {
                    for (int k = 1; k <= 9; k++)
                    {
                        if (!row[i][k] && !col[j][k] && !diag[i / 3][j / 3][k])
                        {
                            board[i][j] = k + '0';
                            row[i][k] = col[j][k] = diag[i / 3][j / 3][k] = true;
                            if (dfs(board))
                                return true;
                            // 恢复现场
                            board[i][j] = '.';
                            row[i][k] = col[j][k] = diag[i / 3][j / 3][k] = false;
                        }
                    }
                    return false; // 1-9到尝试完了
                }
            }
        }
        return true; // 填完了
    }
};

13单词搜索

oj链接:单词搜索

画出决策树

主要:写代码

设计代码:

a全局变量

int dx[4] = {0,0,1,-1} dy[4]={1,-1,0,0} 表示出上下左右四个位置

bool vis[][] 走过的位置进行标记

b设计函数

bool dfs(board,i,j,word,pos) --> pos 当前word的位置

回溯

vis[i][j] = false 返回到上一层进行恢复现场

递归出口

当word.size() == pos 时说明找到了word字符串了,返回true

class Solution
{
public:
    //利用向量方式定义出上下左右四个位置
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    bool vis[7][7] = {false};
    int m, n;
    bool exist(vector<vector<char>> &board, string word)
    {
        m = board.size(), n = board[0].size();
        for (int i = 0; i < m; i++)
        {
            for (int j = 0; j < n; j++)
            {
                if (board[i][j] == word[0])
                {
                    vis[i][j] = true;
                    if (dfs(board, i, j, word, 1))
                        return true;
                    vis[i][j] = false;
                }
            }
        }
        return false;
    }
    bool dfs(vector<vector<char>> &board, int i, int j, string word, int pos)
    {
        if (pos == word.size())
            return true;
        for (int k = 0; k < 4; k++)
        {
            int x = dx[k] + i, y = dy[k] + j;
            if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && board[x][y] == word[pos])
            {
                vis[x][y] = true;
                if (dfs(board, x, y, word, pos + 1))
                    return true;
                vis[x][y] = false;
            }
        }
        return false;
    }
};

14黄金矿工

oj链接:黄金矿工

与上题类似:进行dfs四个方向的深搜:找出最大的一次收益即可 

class Solution
{
public:
    bool vis[16][16] = {false};
    int ret = 0, m, n;
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    int getMaximumGold(vector<vector<int>> &grid)
    {
        m = grid.size(), n = grid[0].size();
        for (int i = 0; i < m; i++)
        {
            for (int j = 0; j < n; j++)
            {
                if (grid[i][j] != 0)
                {
                    vis[i][j] = true;

                    dfs(grid, i, j, 0);
                    vis[i][j] = false;
                }
            }
        }
        return ret;
    }
    void dfs(vector<vector<int>> &grid, int i, int j, int sum)
    {
        ret = max(ret, sum += grid[i][j]);
        for (int k = 0; k < 4; k++)
        {
            int x = dx[k] + i, y = dy[k] + j;
            if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && grid[x][y])
            {
                vis[x][y] = true;
                dfs(grid, x, y, sum);
                vis[x][y] = false;
            }
        }
    }
};

15不同路径Ⅲ

oj链接:不同路径 III  

设计代码:

a全局变量

int dx[4] = {0,0,1,-1} dy[4]={1,-1,0,0} 

int count 统计要走的步数

int step 当前位置已经走的步数

int ret 统计方法数

bool vis[][] 去除当前走过的位置

b设计函数

bool dfs(grid,i,j) ->从当前位置(i,j)进行四个方向的搜索

回溯

step-- vis[][]=false

递归出口

当grid[i][j]==2,说明找到了一种方法进行ret++,return返回上一层

class Solution
{
public:
    int count = 0, step = 0, ret = 0;
    bool vis[20][20];
    int dx[4] = {-1, 1, 0, 0};
    int dy[4] = {0, 0, 1, -1};
    int m, n;
    int uniquePathsIII(vector<vector<int>> &grid)
    {
        m = grid.size(), n = grid[0].size();
        int a = 0, b = 0;
        for (int i = 0; i < m; i++)
        {
            for (int j = 0; j < n; j++)
            {
                if (grid[i][j] == 0)
                    count++;
                if (grid[i][j] == 1)
                    a = i, b = j;
            }
        }
        // 完整路径要加上起始与终止
        count += 2;
        step++;
        vis[a][b] = true;

        dfs(a, b, grid);

        return ret;
    }
    void dfs(int a, int b, vector<vector<int>> &grid)
    {
        if (grid[a][b] == 2)
        {
            if (count == step)
                ret++;
            return;
        }
        for (int k = 0; k < 4; k++)
        {
            int x = dx[k] + a, y = dy[k] + b;
            if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && grid[x][y] != -1)
            {
                step++;
                vis[x][y] = true;

                dfs(x, y, grid);

                step--;
                vis[x][y] = false;
            }
        }
    }
};

五floodfill算法 

暴搜的一种有名字的算法~ 

1图像渲染

oj链接:图像渲染

画出决策树

主要:写代码

设计代码:

a全局变量

int dx[4] = {0,0,1,-1} dy[4]={1,-1,0,0} 表示出上下左右四个位置

int prev 记录起始位置的color

b设计函数

bool dfs(board,sr,sc,color)

回溯

递归出口

class Solution
{
public:
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    int m, n, prev;
    vector<vector<int>> floodFill(vector<vector<int>> &image, int sr, int sc, int color)
    {
        if (image[sr][sc] == color)
            return image; // 防止栈溢出

        m = image.size(), n = image[0].size(), prev = image[sr][sc];
        dfs(image, sr, sc, color);
        return image;
    }
    void dfs(vector<vector<int>> &image, int i, int j, int color)
    {
        image[i][j] = color;
        for (int k = 0; k < 4; k++)
        {
            int x = dx[k] + i, y = dy[k] + j;
            if (x >= 0 && x < m && y >= 0 && y < n && image[x][y] == prev)
            {
                dfs(image, x, y, color);
            }
        }
    }
};

//这种不用判断
class Solution {
public:
    bool vis[50][50]={false};
    int dx[4]={0,0,1,-1};
    int dy[4]={1,-1,0,0};
    int m,n,tmp;
    vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int color) 
    {    
        m=image.size(),n=image[0].size(),tmp=image[sr][sc];
        dfs(image,sr,sc,color);
        return image;
    }
    void dfs(vector<vector<int>>& image,int i,int j,int color)
    {
        cout<<i<<' '<<j<<endl;
        image[i][j]=color;
        vis[i][j]=true;

        for(int k=0;k<4;k++)
        {
            int x=dx[k]+i,y=dy[k]+j;
            if(x>=0&&x<m&&y>=0&&y<n&&!vis[x][y]&&image[x][y]==tmp)
            {
                dfs(image,x,y,color);
            }
        }      
    }
};

2岛屿数量

oj链接:岛屿数量

画出决策树

主要:写代码

设计代码:

a全局变量

int dx[4] = {0,0,1,-1} dy[4]={1,-1,0,0} 表示出上下左右四个位置

int ret 记录岛屿数量

b设计函数

bool dfs(board,i,j)

回溯

递归出口

class Solution
{
public:
    bool vis[300][300] = {false};
    int m, n, ret = 0;
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    int numIslands(vector<vector<char>> &grid)
    {
        m = grid.size(), n = grid[0].size();
        for (int i = 0; i < m; i++)
        {
            for (int j = 0; j < n; j++)
            {
                if (grid[i][j] == 1 + '0' && !vis[i][j])
                {
                    ret++;
                    dfs(grid, i, j);
                }
            }
        }
        return ret;
    }
    void dfs(vector<vector<char>> &grid, int i, int j)
    {
        vis[i][j] = true;
        for (int k = 0; k < 4; k++)
        {
            int x = dx[k] + i, y = dy[k] + j;
            if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && grid[x][y] == 1 + '0')
            {
                dfs(grid, x, y);
            }
        }
    }
};

3岛屿的最大面积 

oj链接:岛屿的最大面积

与上题类似~

class Solution
{
public:
    bool vis[50][50] = {false};
    int m, n, count, ret = 0;
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    int maxAreaOfIsland(vector<vector<int>> &grid)
    {
        m = grid.size(), n = grid[0].size();
        for (int i = 0; i < m; i++)
        {
            for (int j = 0; j < n; j++)
            {
                if (grid[i][j] == 1 && !vis[i][j])
                {
                    count = 0;
                    dfs(grid, i, j);
                    ret = max(ret, count);
                }
            }
        }
        return ret;
    }
    void dfs(vector<vector<int>> &grid, int i, int j)
    {
        count++;
        vis[i][j] = true;
        for (int k = 0; k < 4; k++)
        {
            int x = dx[k] + i, y = dy[k] + j;
            if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && grid[x][y] == 1)
            {
                dfs(grid, x, y);
            }
        }
    }
};

4被环绕的区域

oj链接:被围绕的区域

主要:写代码

题目要把被‘X’包围的‘O’修改为‘X’,直接去求很可能,所以我们正难则反:找不被包围的‘O’

从边界进行找‘O’:从该坐标出进行dfs:把找到的‘O’都进行标记(标记的‘O’就不是题目所求)

再对grid进行遍历:此时没被标记的‘O’就是要被修改成‘X’

设计代码:

a全局变量

int dx[4] = {0,0,1,-1} dy[4]={1,-1,0,0} 

bool vis[][] 进行标记‘O’

b设计函数

bool dfs(grid,i,j) i,j表示‘O’的完整

回溯

递归出口

class Solution
{
public:
    bool vis[200][200] = {false};
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    int n, m;
    void solve(vector<vector<char>> &board)
    {
        n = board.size(), m = board[0].size();
        for (int i = 0; i < n; i++)
        {
            if (board[i][0] == 'O')
                dfs(board, i, 0);
            if (board[i][m - 1] == 'O')
                dfs(board, i, m - 1);
        }
        for (int j = 0; j < m; j++)
        {
            if (board[0][j] == 'O')
                dfs(board, 0, j);
            if (board[n - 1][j] == 'O')
                dfs(board, n - 1, j);
        }
        for (int i = 0; i < n; i++)
        {
            for (int j = 0; j < m; j++)
            {
                if (!vis[i][j] && board[i][j] == 'O')
                    board[i][j] = 'X';
            }
        }
    }
    void dfs(vector<vector<char>> &board, int i, int j)
    {
        vis[i][j] = true;
        for (int k = 0; k < 4; k++)
        {
            int x = dx[k] + i, y = dy[k] + j;
            if (x >= 0 && x < n && y >= 0 && y < m && !vis[x][y] && board[x][y] == 'O')
            {
                dfs(board, x, y);
            }
        }
    }
};

5太平洋大西洋水流问题 

oj链接:太平洋大西洋水流问题

主要:理解题目

题目要求的是:找出既能流向太平洋,又能流向大西洋的坐标

解法一:直接进行每个点的遍历进行判断(超时)

解法二:正难则反:把能流向太平洋的位置标记;能流向大西洋的位置进行标记

两者都标记的地方就是答案!!

设计代码:

a全局变量

int dx[4] = {0,0,1,-1} dy[4]={1,-1,0,0} 

b设计函数

bool dfs(heights,i,j,vis) i j 表示坐标 vis表示被被洋流流到的位置

回溯

递归出口

class Solution
{
public:
    int n, m;
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};

    void dfs(vector<vector<int>> &heights, int a, int b, vector<vector<bool>> &vis)
    {
        vis[a][b] = true;
        for (int i = 0; i < 4; i++)
        {
            int x = dx[i] + a, y = dy[i] + b;
            if (x >= 0 && x < n && y >= 0 && y < m && !vis[x][y] && heights[x][y] >= heights[a][b])
            {
                dfs(heights, x, y, vis);
            }
        }
    }

    vector<vector<int>> pacificAtlantic(vector<vector<int>> &heights)
    {
        n = heights.size(), m = heights[0].size();
        vector<vector<bool>> pac(n, vector<bool>(m));
        vector<vector<bool>> atl(n, vector<bool>(m));
        // 处理pac
        for (int i = 0; i < m; i++)
            dfs(heights, 0, i, pac);
        for (int j = 0; j < n; j++)
            dfs(heights, j, 0, pac);
        // 处理atl
        for (int i = 0; i < m; i++)
            dfs(heights, n - 1, i, atl);
        for (int j = 0; j < n; j++)
            dfs(heights, j, m - 1, atl);

        vector<vector<int>> ret;
        for (int i = 0; i < n; i++)
        {
            for (int j = 0; j < m; j++)
            {
                if (pac[i][j] && atl[i][j])
                    ret.push_back({i, j});
            }
        }
        return ret;
    }
};

6扫雷游戏

oj链接:扫雷游戏

主要:理解题目+写代码

设计代码:

a全局变量

int dx[4] = {0,0,1,-1,1,1,-1,-1} dy[4]={1,-1,0,0,-1,1,-1,1} 进行8个位置的递归 

b设计函数

bool dfs(board,i,j) i j 表示递归的坐标

回溯

递归出口

该位置的周围雷时,修改位置后return无需再递归

class Solution {
public:
    int m,n;
    int dx[8]={0,0,1,-1,1,1,-1,-1};
    int dy[8]={1,-1,0,0,-1,1,-1,1};
    //bool vis[51][51]={0};
    void dfs(vector<vector<char>>& board,int a,int b)
    {
        //先标记地雷的个数
        int count=0;
        for(int i=0;i<8;i++)
        {
            int x=dx[i]+a,y=dy[i]+b;
            if(x>=0&&x<m&&y>=0&&y<n&&board[x][y]=='M') count++;
        }

        if(count!=0)
        {
            board[a][b]=count+'0';
        }

        else
        {
            board[a][b]='B';
            for(int i=0;i<8;i++)
            {
                int x=dx[i]+a,y=dy[i]+b;
                if(x>=0&&x<m&&y>=0&&y<n&&board[x][y]=='E') dfs(board,x,y);
            }
        }
    }

    vector<vector<char>> updateBoard(vector<vector<char>>& board, vector<int>& click) {
        m=board.size(),n=board[0].size();
        int x=click[0],y=click[1];
        if(board[x][y]=='M')
        {
            board[x][y]='X';
            return board;
        }
        dfs(board,x,y);
        return board;
    }
};

7衣橱整理 

oj链接:衣橱整理

从[0][0]位置开始进行一次dfs统计出格子即可~

class Solution
{
public:
    int dx[2] = {1, 0};
    int dy[2] = {0, 1};
    bool vis[100][100] = {false};
    int ret = 0;
    int wardrobeFinishing(int m, int n, int cnt)
    {
        dfs(m, n, 0, 0, cnt);
        return ret;
    }
    void dfs(int m, int n, int i, int j, int cnt)
    {
        ret++;
        vis[i][j] = true;
        for (int k = 0; k < 2; k++)
        {
            int x = dx[k] + i, y = dy[k] + j;
            if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && check(x, y, cnt))
                dfs(m, n, x, y, cnt);
        }
    }
    bool check(int x, int y, int cnt)
    {
        int sum = 0;
        while (x)
        {
            sum += x % 10;
            x /= 10;
        }
        while (y)
        {
            sum += y % 10;
            y /= 10;
        }
        return sum <= cnt;
    }
};

六记忆化搜索 

1斐波那契数

oj链接:斐波那契数

解法1:递归 O(2^N)

主要:找规律:第n个数 = 第n-1个数 + 第n-2个数

设计代码:

a全局变量

b设计函数

int dfs(n)

回溯

递归出口

但n==0或者n==1时返回n

class Solution
{
public:
    int fib(int n)
    {
        return dfs(n);
    }
    int dfs(int n)
    {
        if (n == 0 || n == 1)
        {
            return n;
        }
        return dfs(n - 1) + dfs(n - 2);
    }
};

解法2:记忆化搜索 O(N)

在这道题中我们会发现:在递归时有出现重复,我们可以把结果存到备忘录中,下次用时玩备忘录看看,有就直接返回结果,不用在往下进行递归啦! 

如何实现记忆化搜索?(四部曲)

1添加一个备忘录(<可变参数,返回值>)

2备忘录初始化,让里面的值不干扰后续使用

3递归每次返回时,将结果放到备忘录里面

4在每次递归进入之前,往备忘录里瞅一瞅

class Solution
{
public:
    int memo[31] = {0}; // 1添加备忘录
    int fib(int n)
    {
        memset(memo, -1, sizeof(memo)); // 2初始化
        return dfs(n);
    }
    int dfs(int n)
    {
        if (memo[n] != -1)
            return memo[n]; // 4往备忘录瞅一瞅
        if (n == 0 || n == 1)
        {
            memo[n] = n;
            return memo[n];
        }
        memo[n] = dfs(n - 1) + dfs(n - 2); // 3递归结果储存
        return memo[n];
    }
};

解法3动态规划 O(N) O(N)

1状态表示  -->  dfs函数的含义

dp[i]表示:第i个斐波那契数

2状态转移方程  -->  dfs函数的函数体

dp[i] = dp[i-1] + dp[i-2]

3初始化  -->  dfs函数的出口

dp[0]=0,dp[1]=1

4确定填表顺序  -->  填写备忘录的顺序

从左向右

5返回值  -->  主函数是如何调用dfs的

dp[n]

class Solution
{
public:
    int dp[31] = {0};
    int fib(int n)
    {
        if (n == 0 || n == 1)
        {
            return n;
        }
        dp[1] = 1;
        for (int i = 2; i <= n; i++)
        {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
};

 问题:

1所有的递归(暴搜,深搜)都能改成记忆化搜索吗?

不是的,只有在递归过程中,出现大量相同的问题时,才能用记忆化搜索优化

2带备忘录的递归 vs 带备忘录的动态规划 vs 记忆化搜索

都是一回事

3自顶向下 vs 自底向上

思考的方向不同:递归是自顶向下,动态规划时自底向上

4我们在做题中能这么做: 暴搜 -> 记忆化搜索 -> 动态规划?

80%的题能够往这方向上去思考解决,但不是绝对:但这能为我们的状态表示提供方向

 2不同路径

oj链接:不同路径

设计代码:

a全局变量

int memo[][]

b设计函数

int dfs(i,j) 

回溯

递归出口

当i==1 && j==1时 返回1(此时递归到开始处,只有一种路径)

当i==0 || j==0时 返回1(此时坐标不合法)

class Solution
{
public:
    int memo[101][101];
    int uniquePaths(int m, int n)
    {
        return dfs(m, n);
    }
    int dfs(int i, int j)
    {
        if (memo[i][j] != 0)
            return memo[i][j];
        if (i == 0 || j == 0)
            return 0;
        if (i == 1 && j == 1)
        {
            memo[i][j] = 1;
            return memo[i][j];
        }
        memo[i][j] = dfs(i - 1, j) + dfs(i, j - 1);
        return memo[i][j];
    }
};

3最长递增子序列  

oj链接:最长递增子序列 

设计代码:

a全局变量

int memo[][]

b设计函数

int dfs(pos,nums,memo) -->从pos位置开始往下进行遍历 

回溯

递归出口

class Solution
{
public:
    int lengthOfLIS(vector<int> &nums)
    {
        int n = nums.size();
        vector<int> memo(n);
        int ret = 1;
        for (int i = 0; i < n; i++)
        {
            ret = max(ret, dfs(i, nums, memo));
        }
        return ret;
    }

    int dfs(int pos, vector<int> &nums, vector<int> &memo)
    {
        if (memo[pos] != 0)
            return memo[pos];
        int ret = 1;
        for (int i = pos + 1; i < nums.size(); i++)
        {
            if (nums[i] > nums[pos])
            {
                ret = max(ret, dfs(i, nums, memo) + 1);
            }
        }
        memo[pos] = ret;
        return ret;
    }
};

4猜数字大小II  

 oj链接:猜数字大小 II

主要理解

找所有策略中的最小现金数(每个策略找出猜到数字时最大的现金数)

设计代码:

a全局变量

int memo[][]

b设计函数

int dfs(a,b) -->在[a,b]区间内进行猜数字

回溯

递归出口

a>=b是,返回0

 

class Solution
{
public:
    int memo[201][201];
    int dfs(int a, int b)
    {
        if (a >= b)
            return 0; // 猜到了
        if (memo[a][b] != 0)
            return memo[a][b];
        int ret = INT_MAX;
        for (int i = a; i <= b; i++)
        {
            ret = min(ret, max(dfs(a, i - 1), dfs(i + 1, b)) + i);
        }
        memo[a][b] = ret;
        return memo[a][b];
    }
    int getMoneyAmount(int n)
    {
        return dfs(1, n);
    }
};

5矩阵中的最长递增路径

oj链接:矩阵中的最长递增路径 

设计代码:

a全局变量

int memo[][]

b设计函数

int dfs(i,j,matrix) --> 从该点出发找到最长递增路径

回溯

递归出口

class Solution
{
public:
    int n, m, ret;
    int dx[4] = {0, 0, 1, -1};
    int dy[4] = {1, -1, 0, 0};
    int memo[201][201] = {0};

    int dfs(int a, int b, vector<vector<int>> &matrix)
    {
        if (memo[a][b] != 0)
            return memo[a][b];
        int ret = 1;
        for (int i = 0; i < 4; i++)
        {
            int x = dx[i] + a, y = dy[i] + b;
            if (x >= 0 && x < n && y >= 0 && y < m && matrix[x][y] > matrix[a][b])
            {
                ret = max(ret, dfs(x, y, matrix) + 1); // 所有路径下的最长路径
            }
        }
        memo[a][b] = ret;
        return memo[a][b];
    }

    int longestIncreasingPath(vector<vector<int>> &matrix)
    {
        n = matrix.size(), m = matrix[0].size(), ret = 1;
        for (int i = 0; i < n; i++)
        {
            for (int j = 0; j < m; j++)
            {
                ret = max(ret, dfs(i, j, matrix)); // 每个数进行遍历,找出最长路径
            }
        }
        return ret;
    }
};

以上便是40道递归算法题,有错误欢迎在评论区指正,感谢您的观看! 


http://www.kler.cn/a/323550.html

相关文章:

  • 【机器学习】机器学习中用到的高等数学知识-1.线性代数 (Linear Algebra)
  • 信奥学习规划(CSP-J/S)
  • 【2025最新计算机毕业设计】基于SpringBoot+Vue电脑在线装机指南教程网站【源码+文档】
  • 网络原理-网络层和数据链路层
  • C# Winform--SerialPort串口通讯(ASCII码发送)
  • 企业如何提高招聘能力?
  • Android 保存本地图片
  • 深度学习(入门)03:监督学习
  • 9.24 C++ 常成员,运算符重载
  • 人工智能-机器学习-深度学习-分类与算法梳理
  • qt 模仿简易的软狗实现
  • Java NIO 全面详解:掌握 `Path` 和 `Files` 的一切
  • Keysight 下载信源 Visa 指令
  • 蓝桥杯模块二:数码管的静态、动态实现
  • 电脑录屏怎么录视频和声音?苹果macOS、windows10都可以用的原神录屏工具来啦
  • 【JAVA】算法笔记
  • Linux用户管理
  • 面试遇到的质量体系10个问题(深度思考)
  • 论文阅读 | 可证安全隐写(网络空间安全科学学报 2023)
  • 神经网络(二):卷积神经网络
  • Vscode 远程切换Python虚拟环境
  • 解决Android中使用jdk 9以上中的某个类(AbstractProcessor)但是无法导入的问题
  • Java中通过方法上注解实现入参校验
  • 计算机毕业设计 在线问诊系统的设计与实现 Java实战项目 附源码+文档+视频讲解
  • C++那些你不得不知道的(2)
  • .NET 控制台应用程序连接 MySQL 数据库实现增删改查