算法篇:递归、搜索与回溯算法

一、递归、深搜、穷举vs暴搜vs深搜vs回溯vs剪枝:

01、 面试题 08.06. 汉诺塔问题

cpp 复制代码
class Solution 
{
public:
    void hanota(vector<int>& a, vector<int>& b, vector<int>& c) 
    {
        dfs(a, b, c, a.size());
    }

    void dfs(vector<int>& a, vector<int>& b, vector<int>& c, int n)
    {
        if(n == 1) 
        {
            c.push_back(a.back());
            a.pop_back();
            return;
        }

        dfs(a, c, b, n - 1);
        c.push_back(a.back());
        a.pop_back();
        dfs(b, a, c, n - 1);
    }
};

02、 21. 合并两个有序链表

cpp 复制代码
class Solution 
{
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) 
    {
        if(l1 == nullptr) return l2;
        if(l2 == nullptr) return l1;

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

总结:

①循环(迭代)vs递归:分支越多,递归越爽;分支又少又长,则用循环(否则容易栈溢出)。

②递归的展开图,其实就是对一棵树做一次深度优先遍历(dfs)。

03、 206. 反转链表

cpp 复制代码
class Solution 
{
public:
    ListNode* reverseList(ListNode* head) 
    {
        if(head == nullptr || head -> next == nullptr) return head;
        ListNode* newhead = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return newhead;
    }
};

04、 24. 两两交换链表中的节点

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

        auto tmp = swapPairs(head->next->next);
        auto ret = head->next;
        head->next->next = head;
        head->next = tmp;
        
        return ret;
    }
};

05、 50. Pow(x, n)

解法快速幂 O(N):

cpp 复制代码
class Solution 
{
public:
    double myPow(double x, int n) 
    {
        return n < 0 ? 1.0 / pow(x, -(long long)n) : pow(x, n);
    }

    double pow(double x, long long n)
    {
        if(n == 0) return 1.0;
        auto tmp = pow(x, n / 2);
        return n % 2 == 0 ? tmp * tmp : tmp * tmp * x;
    }
};

06、 2331. 计算布尔二叉树的值

cpp 复制代码
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;
    }
};

07、 129. 求根节点到叶节点数字之和

cpp 复制代码
class Solution 
{
public:
    int sumNumbers(TreeNode* root) 
    {
        return dfs(root, 0);
    }

    int dfs(TreeNode* root, int presum)
    {
        presum = presum * 10 + root->val;
        if(root->left == nullptr && root->right == nullptr) return presum;
        int ret = 0;
        if(root->left) ret += dfs(root->left, presum);
        if(root->right) ret += dfs(root->right, presum);
        return ret;
    }
};

08、 814. 二叉树剪枝

cpp 复制代码
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)
        {
            delete root; // 防止内存泄漏
            root = nullptr;
        }
        return root;
    }
};

09、 98. 验证二叉搜索树

cpp 复制代码
class Solution 
{
    long prev = LONG_MIN;
public:
    bool isValidBST(TreeNode* root) 
    {
        if(root == nullptr) return true;

        bool left = isValidBST(root->left);
        // 剪枝
        if(left == false) return false;
        bool cur = false;
        if(root->val > prev) cur = true;
        // 剪枝
        if(cur == false) return false;
        prev = root->val;
        bool right = isValidBST(root->right); 

        return left && right && cur;
    }
};

10、 230. 二叉搜索树中第K小的元素

cpp 复制代码
class Solution 
{
    int count = 0;
    int ret = 0;
public:
    int kthSmallest(TreeNode* root, int k)
    {
        count = k;
        dfs(root);
        return ret;
    }

    void dfs(TreeNode* root)
    {
        if(root == nullptr || count == 0) return;

        dfs(root->left);
        count--;
        if(count == 0) ret = root->val;
        dfs(root->right);
    }
};

11、 257. 二叉树的所有路径

cpp 复制代码
class Solution 
{
    vector<string> ret;
public:
    vector<string> binaryTreePaths(TreeNode* root)
    {
        string path; // 不能设置全局变量,否则要手动恢复现场
        if(root->nullptr) return ret;
        dfs(root, path);
        return ret;
    }

    void dfs(TreeNode* root, string path) // 不能引用,否则要手动恢复现场
    {
        path += to_string(root->val);
        if(root->left == nullptr && root->right == nullptr)
        {
            ret.push_back(path);
            return;
        }
        path += "->";
        if(root->left) dfs(root->left, path); // 剪枝
        if(root->right) dfs(root->right, path); // 剪枝
    }
};

12、 46. 全排列

cpp 复制代码
// 决策树
class Solution 
{
    vector<vector<int>> ret;
    vector<int> path;
    bool check[7];
public:
    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] == false)
            {
                path.push_back(nums[i]);
                check[i] = true;
                dfs(nums);
                // 回溯 -> 恢复现场
                path.pop_back();
                check[i] = false;
            }
        }
    }
};

13、 78. 子集

解法一:

cpp 复制代码
class Solution 
{
    vector<vector<int>> ret;
    vector<int> path;
public:
    vector<vector<int>> subsets(vector<int>& nums) 
    {
        dfs(nums, 0);
        return ret; 
    }

    void dfs(vector<int>& nums, int pos)
    {
        if(pos == nums.size())
        {
            ret.push_back(path);
            return;
        }

        // 选
        path.push_back(nums[pos]);
        dfs(nums, pos + 1);
        path.pop_back(); // 恢复现场

        // 不选
        dfs(nums, pos + 1);
    }
};

解法二:

cpp 复制代码
class Solution 
{
    vector<vector<int>> ret;
    vector<int> path;
public:
    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(); // 恢复现场
        }
    }
};

14、 1863. 找出所有子集的异或总和再求和

cpp 复制代码
class Solution 
{
    int path = 0;
    int sum = 0;
public:
    int subsetXORSum(vector<int>& nums) 
    {
        dfs(nums, 0);
        return sum;
    }

    void dfs(vector<int>& nums, int pos)
    {
        sum += path;
        for(int i = pos; i < nums.size(); i++)
        {
            path ^= nums[i];
            dfs(nums, i + 1);
            path ^= nums[i]; // 亦或运算:消消乐
        }
    }
};

15、 47. 全排列 II

算法原理:

前提:先把整个数组排序。

剪枝:

①同一个节点的不同分支中,相同的元素只能选择一次。

②同一个数只能选择一次->check。

两种解法:

①只关心"不合法"的分支。

cpp 复制代码
class Solution 
{
    vector<vector<int>> ret;
    vector<int> path;
    bool check[10];
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) 
    {
        sort(nums.begin(), nums.end());

        dfs(nums, 0);
        return ret;
    }

    void dfs(vector<int>& nums, int pos)
    {
        if(pos == nums.size())
        {
            ret.push_back(path);
            return;
        }

        for(int i = 0; i < nums.size(); i++)
        {
            // 剪枝
            if(check[i] == true || (i != 0 && nums[i] == nums[i - 1] && check[i - 1] == false))
                continue;
            path.push_back(nums[i]);
            check[i] = true;
            dfs(nums, pos + 1);
            path.pop_back(); // 恢复现场
            check[i] = false;
        }
    }
};

②只关心"合法"的分支。

cpp 复制代码
class Solution 
{
    vector<vector<int>> ret;
    vector<int> path;
    bool check[10];
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) 
    {
        sort(nums.begin(), nums.end());
        dfs(nums, 0);
        return ret;
    }

    void dfs(vector<int>& nums, int pos)
    {
        if(pos == nums.size())
        {
            ret.push_back(path);
            return;
        }

        for(int i = 0; i < nums.size(); i++)
        {
            // 剪枝
            if(check[i] == false && (i == 0 || nums[i - 1] != nums[i] || check[i - 1] != false))
            {
                path.push_back(nums[i]);
                
                check[i] = true;
                dfs(nums, pos + 1);
                path.pop_back(); // 恢复现场
                check[i] = false;
            }
        }
    }
};

16、 17. 电话号码的字母组合

cpp 复制代码
class Solution 
{
    string hash[10] = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
    string path;
    vector<string> ret;
public:
    vector<string> letterCombinations(string digits) 
    {
        if(digits.size() == 0) return ret;
        dfs(digits, 0);
        return ret;
    }

    void dfs(string& digits, int pos)
    {
        if(pos == digits.size())
        {
            ret.push_back(path);
            return;
        }

        for(auto ch : hash[digits[pos] - '0'])
        {
            path.push_back(ch);
            dfs(digits,pos + 1);
            path.pop_back();
        }
    }
};

17、 22. 括号生成

有效的括号组合:

①左括号的数量=有括号的数量。

②从头开始的任意一个字串,左括号的数量>=右括号的数量。

cpp 复制代码
class Solution 
{
    int left, right, n;
    vector<string> ret;
    string path;
public:
    vector<string> generateParenthesis(int _n) 
    {
        n = _n;
        dfs();
        return ret; 
    }

    void dfs()
    {
        // 递归出口
        if(right == n)
        {
            ret.push_back(path);
            return;
        }

        if(left < n) // 添加左括号
        {
            path.push_back('(');
            left++;
            dfs();
            path.pop_back(); // 恢复现场
            left--;
        }
        
        if(right < left) // 添加右括号
        {
            path.push_back(')');
            right++;
            dfs();
            path.pop_back(); // 恢复现场
            right--;
        }
    }
};

​18、 77. 组合

cpp 复制代码
class Solution 
{
    int n, k;
    vector<vector<int>> ret;
    vector<int> path;
public:
    vector<vector<int>> combine(int _n, int _k) 
    {
        n = _n;
        k = _k;
        dfs(1);
        return ret;
    }

    void dfs(int start)
    {
        if(path.size() == k)
        {
            ret.push_back(path);
            return;
        }

        for(int i = start; i <= n; i++) // 剪枝
        {
            path.push_back(i);
            dfs(i + 1);
            path.pop_back(); // 恢复现场
        }
    }
};

19、 494. 目标和

path作为全局变量:

cpp 复制代码
class Solution 
{
    int path, ret, aim;
public:
    int findTargetSumWays(vector<int>& nums, int target) 
    {
        aim = target;
        dfs(nums, 0);
        return ret;
    }

    void dfs(vector<int>& nums, int pos)
    {
        if(pos == nums.size())
        {
            if(path == aim) ret++;   
            return;
        }

        // 加法
        path += nums[pos];
        dfs(nums, pos + 1);
        path -= nums[pos]; // 恢复现场

        // 减法
        path -= nums[pos];
        dfs(nums, pos + 1);
        path += nums[pos]; // 恢复现场
    }
};

path作为参数:

cpp 复制代码
class Solution 
{
    int ret, aim;
public:
    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]);
    }
};

20、 39. 组合总和

解法一:

cpp 复制代码
class Solution 
{
    int sum, aim;
    vector<int> path;
    vector<vector<int>> ret;
public:
    vector<vector<int>> combinationSum(vector<int>& nums, int target) 
    {
        aim = target;
        dfs(nums, 0, 0);
        return ret;
    }

    void dfs(vector<int>& nums, int pos, int sum)
    {
        if(sum == aim)
        {
            ret.push_back(path);
            return;
        }

        if(pos == nums.size() || sum > aim) return;

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

解法二:

cpp 复制代码
class Solution 
{
    int sum, aim;
    vector<int> path;
    vector<vector<int>> ret;
public:
    vector<vector<int>> combinationSum(vector<int>& nums, int target) 
    {
        aim = target;
        dfs(nums, 0, 0);
        return ret;
    }

    void dfs(vector<int>& nums, int pos, int sum)
    {
        if(sum == aim)
        {
            ret.push_back(path);
            return;
        }

        if(pos == nums.size() || sum > aim) return;

        // 枚举个数
        for(int k = 0; k * nums[pos] + sum <= aim; k++)
        {
            if(k) path.push_back(nums[pos]);
            dfs(nums, pos + 1, sum + k * nums[pos]);
        }

        // 恢复现场
        for(int k = 1; k * nums[pos] + sum <= aim; k++)
        {
            path.pop_back();
        }
    }
};

21、 784. 字母大小写全排列

cpp 复制代码
class Solution 
{
    vector<string> ret;
    string path;
public:
    vector<string> letterCasePermutation(string s) 
    {
        dfs(s, 0);
        return ret;
    }

    void dfs(string& s, int pos)
    {
        if(pos == s.size())
        {
            ret.push_back(path);
            return;
        }

        char ch = s[pos];
        path.push_back(ch);
        dfs(s, pos + 1);
        path.pop_back();

        if(ch < '0' || ch > '9')
        {
            char tmp = change(ch);
            path.push_back(tmp);
            dfs(s, pos + 1);
            path.pop_back();
        }
    }

    char change(char ch)
    {
        if(ch >= 'a' && ch <= 'z') ch -= 32;
        else ch += 32;
        return ch;
    }
};

22、 526. 优美的排列

cpp 复制代码
class Solution 
{
    bool check[16];
    int ret;
public:
    int countArrangement(int n) 
    {
        dfs(1, n);
        return ret;
    }

    void dfs(int pos, int n)
    {
        if(pos == n + 1)
        {
            ret++;
            return;
        }

        for(int i = 1; i <= n; i++)
        {
            if(!check[i] && (pos % i == 0 || i % pos == 0))
            {
                check[i] = true;
                dfs(pos + 1, n);
                check[i] = false; // 恢复现场
            }
        }
    }
};

23、 51. N 皇后

cpp 复制代码
class Solution 
{
    bool checkCol[10], checkDig1[20], checkDig2[20];
    vector<vector<string>> ret;
    vector<string> path;
    int n; 
public:
    vector<vector<string>> solveNQueens(int _n) 
    {
        n = _n;
        path.resize(n);
        for(int i = 0; i < n; i++)
            path[i].append(n, '.');

        dfs(0);
        return ret;
    }

    void dfs(int row)
    {
        if(row == n)
        {
            ret.push_back(path);
            return;
        }

        for(int col = 0; col < n; col++) // 尝试在这一行放皇后
        {
            // 剪枝
            if(!checkCol[col] && !checkDig1[row - col + n] && !checkDig2[row + col])
            {
                path[row][col] = 'Q';
                checkCol[col] = checkDig1[row - col + n] = checkDig2[row + col] = true;
                dfs(row + 1);
                // 恢复现场
                path[row][col] = '.';
                checkCol[col] = checkDig1[row - col + n] = checkDig2[row + col] = false;
            }
        }
    }
};

24、 36. 有效的数独

cpp 复制代码
class Solution 
{
    bool row[9][10];
    bool col[9][10];
    bool grid[3][3][10];

public:
    bool isValidSudoku(vector<vector<char>>& board) 
    {
        for(int i = 0; i < 9; i++)
            for(int j = 0; j < 9; j++)
            {
                if(board[i][j] != '.')
                {
                    int num = board[i][j] - '0';
                    // 是否是有效的
                    if(row[i][num] || col[j][num] || grid[i / 3][j / 3][num])
                        return false;
                    row[i][num] = col[j][num] = grid[i / 3][j / 3][num] = true;
                }
            }
        return true;
    }
};

25、 37. 解数独

cpp 复制代码
class Solution 
{
    bool row[9][10], col[9][10], grid[3][3][10];
public:
    void solveSudoku(vector<vector<char>>& board) 
    {
        // 初始化
        for(int i = 0; i < 9; i++)
        {
            for(int j = 0; j < 9; j++)
            {
                if(board[i][j] != '.')
                {
                    int num = board[i][j] - '0';
                    row[i][num] = col[j][num] = grid[i / 3][j / 3][num] = true;
                }
            }
        }
        dfs(board);
    }

    bool dfs(vector<vector<char>>& board)
    {
        for(int i = 0; i < 9; i++)
        {
            for(int j = 0; j < 9; j++)
            {
                if(board[i][j] == '.')
                {
                    // 填数
                    for(int num = 1; num <= 9; num++)
                    {
                        if(!row[i][num] && !col[j][num] && !grid[i / 3][j / 3][num])
                        {
                            board[i][j] = '0' + num;
                            row[i][num] = col[j][num] = grid[i / 3][j / 3][num] = true;
                            if(dfs(board) == true) return true;
                            // 恢复现场
                            board[i][j] = '.';
                            row[i][num] = col[j][num] = grid[i / 3][j / 3][num] = false;
                        }
                    }
                    return false;
                }
            }
        }
        return true;
    }
};

26、 79. 单词搜索

cpp 复制代码
class Solution 
{
    bool vis[7][7];
    int m, n;
public:
    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;
    }

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

    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 = i + dx[k], y = j + dy[k];
            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;
    }
};

27、 1219. 黄金矿工

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

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

28、 980. 不同路径 III

cpp 复制代码
class Solution 
{
    bool vis[21][21];
    int dx[4] = {0, 0, -1, 1};
    int dy[4] = {1, -1, 0, 0};
    int ret;
    int m, n, step;
public:
    int uniquePathsIII(vector<vector<int>>& grid) 
    {
        m = grid.size(), n = grid[0].size();

        int bx = 0, by = 0;
        for(int i = 0; i < m; i++)
            for(int j = 0; j < n; j++)
                if(grid[i][j] == 0) step++;
                else if(grid[i][j] == 1)
                {
                    bx = i;
                    by = j;
                }
        step += 2;
        vis[bx][by] = true;
        dfs(grid, bx, by, 1);
        return ret;
    }

    void dfs(vector<vector<int>>& grid, int i, int j, int count)
    {
        if(grid[i][j] == 2)
        {
            if(count == step) // 判断是否合法
                ret++;
            return;
        }

        for(int k = 0; k < 4; k++)
        {
            int x = i + dx[k], y = j + dy[k];
            if(x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && grid[x][y] != -1)
            {
                vis[x][y] = true;
                dfs(grid, x, y, count + 1);
                vis[x][y] = false;
            }
        }    
    }
};

二、floodfill算法:

30、 733. 图像渲染

cpp 复制代码
class Solution 
{
    int m, n, prev;
    int dx[4] = {0, 0, -1, 1};
    int dy[4] = {1, -1, 0, 0};
public:
    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 = i + dx[k], y = j + dy[k];
            if(x >= 0 && x < m && y >= 0 && y < n && image[x][y] == prev)
            {
                dfs(image, x, y, color);
            }
        }
    }
};

31、 200. 岛屿数量

cpp 复制代码
class Solution 
{
    vector<vector<bool>> vis;
    int dx[4] = {0, 0, -1, 1};
    int dy[4] = {1, -1, 0, 0};
    int m, n, ret;
public:
    int numIslands(vector<vector<char>>& grid) 
    {
        m = grid.size(), n = grid[0].size();
        vis = vector<vector<bool>>(m, vector<bool>(n));

        for(int i = 0; i < grid.size(); i++)
            for(int j = 0; j < grid[0].size(); j++)
            {
                if(!vis[i][j] && grid[i][j] == '1')
                {
                    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 = i + dx[k], y = j + dy[k];
            if(x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && grid[x][y] == '1')
            {
                dfs(grid, x, y);
            }
        }
    }
};

32、 695. 岛屿的最大面积​​​​​​​

cpp 复制代码
class Solution 
{
    vector<vector<bool>> vis;
    int dx[4] = {0, 0, -1, 1};
    int dy[4] = {1, -1, 0, 0};
    int m, n, count;
public:
    int maxAreaOfIsland(vector<vector<int>>& grid) 
    {
        m = grid.size(), n = grid[0].size();
        vis = vector<vector<bool>>(m, vector<bool>(n));

        int ret = 0;
        for(int i = 0; i < grid.size(); i++)
            for(int j = 0; j < grid[0].size(); j++)
            {
                if(!vis[i][j] && grid[i][j] == 1)
                {
                    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 = i + dx[k], y = j + dy[k];
            if(x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && grid[x][y] == 1)
            {
                dfs(grid, x, y);
            }
        }
    }
};

33、 130. 被围绕的区域

cpp 复制代码
class Solution 
{   // 正难则反
    int dx[4] = {0, 0, -1, 1};
    int dy[4] = {1, -1, 0, 0};
    int m, n;
public:
    void solve(vector<vector<char>>& board) 
    {
        // 1、把边界的0相连的连通块,全部修改成'.'
        m = board.size(), n = board[0].size();
        for(int j = 0; j < n; j++)
        {
            if(board[0][j] == 'O') dfs(board, 0, j);
            if(board[m - 1][j] == 'O') dfs(board, m - 1, j);
        }

        for(int i = 0; i < m; i++)
        {
            if(board[i][0] == 'O') dfs(board, i, 0);
            if(board[i][n - 1] == 'O') dfs(board, i, n - 1);
        }

        // 2、还原
        for(int i = 0; i < m; i++)
            for(int j = 0; j < n; j++)
            {
                if(board[i][j] == '.') board[i][j] = 'O';
                else if(board[i][j] == 'O') board[i][j] = 'X';
            }
    }

    void dfs(vector<vector<char>>& board, int i, int j)
    {
        board[i][j] = '.';
        for(int k = 0; k < 4; k++)
        {
            int x = i + dx[k], y = j + dy[k];
            if(x >= 0 && x < m && y >= 0 && y < n && board[x][y] == 'O')
            {
                 dfs(board, x, y);
            }
        }
    }
};

34、 417. 太平洋大西洋水流问题

cpp 复制代码
class Solution 
{
    int dx[4] = {0, 0, -1, 1};
    int dy[4] = {1, -1, 0, 0};
    int m, n;
public:
    vector<vector<int>> pacificAtlantic(vector<vector<int>>& h) 
    {
        m = h.size(), n = h[0].size();
        vector<vector<bool>> pac(m, vector<bool>(n));
        vector<vector<bool>> atl(m, vector<bool>(n));

        // 1、先处理 pac 洋
        for(int j = 0; j < n; j++) dfs(h, 0, j, pac);
        for(int i = 0; i < m; i++) dfs(h, i, 0, pac);

        // 2、先处理 atl 洋
        for(int i = 0; i < m; i++) dfs(h, i, n - 1, atl);
        for(int j = 0; j < n; j++) dfs(h, m - 1, j, atl);

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

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

35、 529. 扫雷游戏

cpp 复制代码
class Solution 
{
    int dx[8] = {0, 0, 1, -1, 1, 1, -1, -1};
    int dy[8] = {1, -1, 0, 0, 1, -1, 1, -1};
    int m, n;
    
public:
    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;
    }

    void dfs(vector<vector<char>>& board, int i, int j)
    {
        // 统计一下周围的地雷个数
        int count = 0;
        for(int k = 0; k < 8; k++)
        {
            int x = i + dx[k], y = j + dy[k];
            if(x >= 0 && x < m && y >= 0 && y < n && board[x][y] == 'M')
            {
                count++;
            }
        }
        if(count) // 周围有地雷
        {
            board[i][j] = count + '0';
            return;
        }
        else // 周围没有地雷
        {
            board[i][j] = 'B';
            for(int k = 0; k < 8; k++)
            {
                int x = i + dx[k], y = j + dy[k];
                if(x >= 0 && x < m && y >= 0 && y < n && board[x][y] == 'E')
                {
                    dfs(board, x, y);
                }
            }
        }
    }
};

三、记忆化搜索:

37、 509. 斐波那契数

**解法一:**递归 O(2^N)

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

①是什么:带备忘录的递归。

②怎么实现:添加一个备忘录<可变参数,返回值>,把结果放到备忘录里面,在每次进入递归的时候向备忘录里查找一下。

cpp 复制代码
class Solution 
{
    int memo[31]; // memory 
public:
    int fib(int n) 
    {
        // 初始化
        memset(memo, -1, sizeof memo);
        return dfs(n);
    }

    int dfs(int n)
    {
        // 往备忘录里查找一下
        if(memo[n] != -1) // 剪枝
        {
            return memo[n];
        }

        if(n == 0 || n == 1)
        {
            memo[n] = n; // 返回之前放进备忘录里面
            return n;
        }

        memo[n]  = dfs(n - 1) + dfs(n - 2); // 返回之前放进备忘录里面
        return memo[n];
    }
};

解法三:

①确定状态表示(dfs函数的含义);

②推导状态转移方程(dfs的函数体);

③初始化(dfs函数的递归出口);

④确定填表顺序(填写备忘录的顺序);

⑤确定返回值(主函数是如何调用dfs的)。

cpp 复制代码
class Solution 
{
    int dp[31];
public:
    int fib(int n) 
    {
        // 动态规划
        dp[0] = 0, dp[1] = 1;
        for(int i = 2; i <= n; i++)
            dp[i] = dp[i - 1] + dp [i - 2];
        return dp[n];
    }
};

总结: 动态规划和记忆化搜索的本质:

①暴力解法(暴搜)。

②对暴力解法的优化:把已经计算过的值存起来。

问题:

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

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

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

都是一回事。

③自顶向下 vs 自底向上?

记忆化搜索:自顶向下。

常规动态规划:自底向上。

④暴搜->记忆化搜索->动态规划 vs 常规动态规划?

80%可以用前者的思考方式,但有些题用暴搜的时间成本太高了(暴搜只是为我们确定状态表示提供方向)。

38、 ​​​​​​​62. 不同路径

解法一:暴搜->记忆化搜索:

cpp 复制代码
class Solution 
{
public:
    int uniquePaths(int m, int n) 
    {
        // 记忆化搜索
        vector<vector<int>> memo(m + 1, vector<int>(n + 1));
        return dfs(m, n, memo);
    }

    int dfs(int i, int j, vector<vector<int>>& memo)
    {
        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 1;
        }

        memo[i][j] = dfs(i - 1, j, memo) + dfs(i, j - 1, memo);
        return memo[i][j];
    }
};

解法二:动态规划:

cpp 复制代码
class Solution 
{
public:
    int uniquePaths(int m, int n) 
    {
        // 1、创建dp表
        vector<vector<int>> dp(m + 1, vector<int>(n + 1));

        // 2、初始化
        dp[0][1] = 1;

        // 3、填表
        for(int i = 1; i <= m; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
        
        // 4、返回值
        return dp[m][n];
    }
};

39、 ​​​​​​​​​​​​​​300. 最长递增子序列

解法一:暴搜->记忆化搜索:

cpp 复制代码
class Solution 
{
public:
    int lengthOfLIS(vector<int>& nums) 
    {
        int n = nums.size();
        vector<int> memo(n);

        int ret = 0;
        for(int i = 0; i < nums.size(); 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 memo[pos];
    }
};

解法二:动态规划:

cpp 复制代码
class Solution 
{
    int ret = 0;
    int n = nums.size();
public:
    int lengthOfLIS(vector<int>& nums) 
    {
        // 动态规划
        vector<int> dp(n, 1);

        // 填表顺序:从后往前(以i为首的最长递增子序列)
        for(int i = n - 1; i >= 0; i--)
        {
            for(int j = i + 1; j < n; j++)
            {
                if(nums[j] > nums[i])
                {
                    dp[i] = max(dp[i], dp[j] + 1);
                }
            }
            ret = max(ret, dp[i]);
        }
        return ret;
    }
};

40、 375. 猜数字大小 II?

cpp 复制代码
class Solution 
{
    int memo[201][201];
public:
    int getMoneyAmount(int n) 
    {
        return dfs(1, n);
    }

    int dfs(int left, int right)
    {
        if(left >= right) return 0;
        if(memo[left][right] != 0) return memo[left][right];

        int ret = INT_MAX;
        for(int head = left; head <= right; head++) // 选择头结点·
        {
            int x = dfs(left, head - 1);
            int y = dfs(head + 1, right);
            ret = min(ret, head + max(x, y));
        }
        memo[left][right] = ret;
        return memo[left][right];
    }
};

41、 329. 矩阵中的最长递增路径

cpp 复制代码
class Solution 
{
    int dx[4] = {0, 0, -1, 1};
    int dy[4] = {1, -1, 0, 0};
    int m, n;
    int memo[201][201];

public:
    int longestIncreasingPath(vector<vector<int>>& matrix) 
    {
        int ret = 0;
        m = matrix.size(), n = matrix[0].size();

        for(int i = 0; i < m; i++)
            for(int j = 0; j < n; j++)
            {
                ret = max(ret, dfs(matrix, i, j));
            }
        return ret;
    }

    int dfs(vector<vector<int>>& matrix, int i, int j)
    {
        if(memo[i][j] != 0) return memo[i][j];

        int ret = 1;
        for(int k = 0; k < 4; k++)
        {
            int x = i + dx[k], y = j + dy[k];
            if(x >= 0 && x < m && y >= 0 && y < n && matrix[x][y] > matrix[i][j])
            {
                ret = max(ret, dfs(matrix, x, y) + 1);
            }
        }
        memo[i][j] = ret;
        return memo[i][j];
    }
};
相关推荐
刚学HTML1 小时前
leetcode 05 回文字符串
算法·leetcode
AC使者2 小时前
#B1630. 数字走向4
算法
冠位观测者2 小时前
【Leetcode 每日一题】2545. 根据第 K 场考试的分数排序
数据结构·算法·leetcode
古希腊掌管学习的神2 小时前
[搜广推]王树森推荐系统笔记——曝光过滤 & Bloom Filter
算法·推荐算法
qystca2 小时前
洛谷 P1706 全排列问题 C语言
算法
浊酒南街3 小时前
决策树(理论知识1)
算法·决策树·机器学习
就爱学编程3 小时前
重生之我在异世界学编程之C语言小项目:通讯录
c语言·开发语言·数据结构·算法
学术头条3 小时前
清华、智谱团队:探索 RLHF 的 scaling laws
人工智能·深度学习·算法·机器学习·语言模型·计算语言学
Schwertlilien3 小时前
图像处理-Ch4-频率域处理
算法
IT猿手4 小时前
最新高性能多目标优化算法:多目标麋鹿优化算法(MOEHO)求解TP1-TP10及工程应用---盘式制动器设计,提供完整MATLAB代码
开发语言·深度学习·算法·机器学习·matlab·多目标算法