一、递归、深搜、穷举vs暴搜vs深搜vs回溯vs剪枝:
01、面试题 08.06. 汉诺塔问题
class Solution
{
public:
void hanota(vector& a, vector& b, vector& c)
{
dfs(a, b, c, a.size());
}
void dfs(vector& a, vector& b, vector& 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 ret;
public:
vector 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> ret;
vector path;
bool check[7];
public:
vector> permute(vector& nums)
{
dfs(nums);
return ret;
}
void dfs(vector& 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> ret;
vector path;
public:
vector> subsets(vector& nums)
{
dfs(nums, 0);
return ret;
}
void dfs(vector& 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> ret;
vector path;
public:
vector> subsets(vector& nums)
{
// 解法二:
dfs(nums, 0);
return ret;
}
void dfs(vector& 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& nums)
{
dfs(nums, 0);
return sum;
}
void dfs(vector& 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> ret;
vector path;
bool check[10];
public:
vector> permuteUnique(vector& nums)
{
sort(nums.begin(), nums.end());
dfs(nums, 0);
return ret;
}
void dfs(vector& 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> ret;
vector path;
bool check[10];
public:
vector> permuteUnique(vector& nums)
{
sort(nums.begin(), nums.end());
dfs(nums, 0);
return ret;
}
void dfs(vector& 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 ret;
public:
vector 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 ret;
string path;
public:
vector 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> ret;
vector path;
public:
vector> 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& nums, int target)
{
aim = target;
dfs(nums, 0);
return ret;
}
void dfs(vector& 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& nums, int target)
{
aim = target;
dfs(nums, 0, 0);
return ret;
}
void dfs(vector& 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 path;
vector> ret;
public:
vector> combinationSum(vector& nums, int target)
{
aim = target;
dfs(nums, 0, 0);
return ret;
}
void dfs(vector& 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 path;
vector> ret;
public:
vector> combinationSum(vector& nums, int target)
{
aim = target;
dfs(nums, 0, 0);
return ret;
}
void dfs(vector& 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 ret;
string path;
public:
vector 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> ret;
vector path;
int n;
public:
vector> 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>& 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>& 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>& 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>& 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>& 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>& 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>& 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>& 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>& 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> floodFill(vector>& 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>& 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> vis;
int dx[4] = {0, 0, -1, 1};
int dy[4] = {1, -1, 0, 0};
int m, n, ret;
public:
int numIslands(vector>& grid)
{
m = grid.size(), n = grid[0].size();
vis = vector>(m, vector(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>& 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> vis;
int dx[4] = {0, 0, -1, 1};
int dy[4] = {1, -1, 0, 0};
int m, n, count;
public:
int maxAreaOfIsland(vector>& grid)
{
m = grid.size(), n = grid[0].size();
vis = vector>(m, vector(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>& 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>& 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>& 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> pacificAtlantic(vector>& h)
{
m = h.size(), n = h[0].size();
vector> pac(m, vector(n));
vector> atl(m, vector(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> 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>& h, int i, int j, vector>& 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> updateBoard(vector>& board, vector& 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>& 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> memo(m + 1, vector(n + 1));
return dfs(m, n, memo);
}
int dfs(int i, int j, vector>& 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> dp(m + 1, vector(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& nums)
{
int n = nums.size();
vector 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& nums, vector& 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& nums)
{
// 动态规划
vector 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>& 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>& 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];
}
};