一、递归、深搜、穷举vs暴搜vs深搜vs回溯vs剪枝:
01、面试题 08.06. 汉诺塔问题
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. 合并两个有序链表
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. 反转链表
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. 两两交换链表中的节点
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):
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. 计算布尔二叉树的值
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. 求根节点到叶节点数字之和
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. 二叉树剪枝
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. 验证二叉搜索树
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小的元素
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. 二叉树的所有路径
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. 全排列
// 决策树
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. 子集
解法一:
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);}
};
解法二:
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. 找出所有子集的异或总和再求和
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。
两种解法:
①只关心“不合法”的分支。
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;}}
};
②只关心“合法”的分支。
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. 电话号码的字母组合
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. 括号生成
有效的括号组合:
①左括号的数量=有括号的数量。
②从头开始的任意一个字串,左括号的数量>=右括号的数量。
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. 组合
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作为全局变量:
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作为参数:
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. 组合总和
解法一:
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();}}
};
解法二:
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. 字母大小写全排列
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. 优美的排列
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 皇后?
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. 有效的数独
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. 解数独
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. 单词搜索
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. 黄金矿工
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
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. 图像渲染
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. 岛屿数量
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. 岛屿的最大面积
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. 被围绕的区域
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. 太平洋大西洋水流问题
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. 扫雷游戏
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)
①是什么:带备忘录的递归。
②怎么实现:添加一个备忘录<可变参数,返回值>,把结果放到备忘录里面,在每次进入递归的时候向备忘录里查找一下。
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的)。
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. 不同路径
解法一:暴搜->记忆化搜索:
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];}
};
解法二:动态规划:
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. 最长递增子序列
解法一:暴搜->记忆化搜索:
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];}
};
解法二:动态规划:
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?
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. 矩阵中的最长递增路径
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];}
};