4.15
72. 编辑距离 - 力扣(LeetCode)
m = word1.size() , n = word2.size()
考虑word1[i] 与 word2[j] 是否相等的情况
- s[i] == t[j]时
dp[i][j] = dp[i - 1][j - 1]- s[i] != t[j]
dp[i][j] = min(dp[i , j - 1] , dp[i - 1][j] , dp[i - 1][j - 1])编辑距离是用动规来解决的经典题目,这道题目看上去好像很复杂,但用动规可以很巧妙的算出最少编辑距离。
易错点1: dp数组的含义
dp[i][j]表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]。因此这里是根据word1[i-1]与word2[j-1]的相等情况来更新dp[i][j]if(word1[i - 1] == word2[j - 1]) dp[i][j] = dp[i - 1][j - 1];易错点2: dp数组如何初始化
dp[i][0]:以下标i-1为结尾的字符串word1,和空字符串word2,最近编辑距离为dp[i][0]。那么
dp[i][0]就应该是i,对word1里的元素全部做删除操作,即:dp[i][0]= i;同理
dp[0][j]= j;
class Solution {
public:
int minDistance(string word1, string word2) {
int m = word1.size() , n = word2.size();
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for(int i = 0 ; i <= m ; i ++) dp[i][0] = i;
for (int j = 0; j <= n; j++) dp[0][j] = j;
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if(word1[i - 1] == word2[j - 1]) dp[i][j] = dp[i - 1][j - 1];
else{
dp[i][j] = min({dp[i][j- 1] , dp[i - 1][j] , dp[i - 1][j - 1]}) + 1;
}
}
}
return dp[m][n];
}
};
994. 腐烂的橘子 - 力扣(LeetCode)
本题是bfs做法,每分钟扩散一圈,不能用dfs岛屿问题的写法来写。
思路:
- 初始遍历统计新鲜橘子数,并把腐烂橘子加入队列
- 当还有新鲜橘子且队列不为空时处理当前队列里的点,对每一层结果ans只加一次(过一分钟),对每一个点遍历四个方向,把感染的橘子记为2,并使新鲜数fresh - -
- 最后如果fresh=0则返回ans,否则不能完全感染返回-1
class Solution {
public:
int orangesRotting(vector<vector<int>>& grid) {
int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int m = grid.size(), n = grid[0].size();
int fresh = 0;
queue<pair<int, int>> q;
// 初始化队列和新鲜橘子计数
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (grid[i][j] == 1) {
++fresh;
} else if (grid[i][j] == 2) {
q.emplace(i, j);
}
}
}
int ans = 0;
// 当还有新鲜橘子且队列不为空时处理
while (fresh > 0 && !q.empty()) {
ans++;
int size = q.size(); // 当前层的节点数
for (int i = 0; i < size; ++i) {
auto [x, y] = q.front();
q.pop();
// 遍历四个方向
for (auto& d : dirs) {
int nx = x + d[0];
int ny = y + d[1];
// 检查边界条件和新鲜橘子
if (nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] == 1) {
grid[nx][ny] = 2; // 腐烂
q.emplace(nx, ny);
fresh--;
}
}
}
}
// 如果仍有新鲜橘子则返回-1,否则返回时间
return fresh == 0 ? ans : -1;
}
};
上述代码中使用 q.emplace(i, j);而不用q.push(i , j)原因如下:
在C++中,
q.emplace和q.push均可用于向队列中添加元素,但二者的使用方式有本质区别。对于你的代码queue<pair<int, int>> q,具体分析如下:
1. emplace 和 push 的区别
emplace
直接在队列内存中构造对象,接受构造对象所需的参数列表。例如:q.emplace(i, j); // 直接传递两个整数,隐式构造 pair<int, int>这等价于:
q.push(pair<int, int>(i, j)); // 显式构造对象后插入
push
需要传递一个已构造好的对象。例如:q.push({i, j}); // C++11 起支持的花括号初始化 q.push(make_pair(i, j)); // 使用 make_pair 辅助函数2. 能否用 push 替代 emplace?
可以,但需正确传递参数:
若错误地直接替换为q.push(i, j),会导致编译失败,因为push只接受一个参数。
正确的push用法应为:q.push({i, j}); // C++11 及以上支持(推荐) q.push(make_pair(i, j)); // 兼容性更好的写法
108. 将有序数组转换为二叉搜索树 - 力扣(LeetCode)
BST 的中序遍历是升序的,因此本题等同于根据中序遍历的序列恢复二叉搜索树。
因此我们可以以升序序列中的任一个元素作为根节点,以该元素左边的升序序列构建左子树,以该元素右边的升序序列构建右子树,这样得到的树就是一棵二叉搜索树.
又因为本题要求高度平衡,因此我们需要选择升序序列的中间元素作为根节点。
class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
auto dfs = [&](this auto&& dfs , int l , int r)->TreeNode*{
if(l > r) return nullptr;
int mid = (l + r) / 2;
TreeNode* root = new TreeNode(nums[mid]);
root->left = dfs(l , mid - 1);
root->right = dfs(mid + 1 , r);
return root;
};
return dfs(0 , nums.size() - 1);
}
};
128. 最长连续序列 - 力扣(LeetCode)
首先,本题是不能排序的,因为排序的时间复杂度是
O(nlogn),不符合题目O(n)的要求。
O(n)提示我们用哈希表。核心思路:
对于 nums 中的元素 x,以 x 为起点,不断查找下一个数 x+1,x+2,⋯ 是否在 nums 中,并统计序列的长度。
为了做到 O(n) 的时间复杂度,需要两个关键优化:
- 把 nums 中的数都放入一个哈希集合中,这样可以 O(1) 判断数字是否在 nums 中。
- 如果 x−1 在哈希集合中,则不以 x 为起点。因为以 x−1 为起点计算出的序列长度,一定比以 x 为起点计算出的序列长度要长!这样可以避免大量重复计算。
⚠注意:遍历元素的时候,要遍历哈希集合,而不是 nums!如果 nums=[1,1,1,…,1,2,3,4,5,…](前一半都是 1),遍历 nums 的做法会导致每个 1 都跑一个 O(n) 的循环,总的循环次数是 \(O(n ^2 )\),会超时。
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
int ans = 0;
unordered_set<int> st(nums.begin(), nums.end()); // 把 nums 转成哈希集合,去重
for (int x : st) { // 遍历哈希集合
if (st.contains(x - 1)) {
continue;
}//不含x-1,从x开始
// x 是序列的起点
int y = x + 1;
while (st.contains(y)) { // 不断查找下一个数是否在哈希集合中
y++;
}
// 循环跳出条件是不含y,则循环结束后,y-1 是最后一个在哈希集合中的数
ans = max(ans, y - x); // 从 x 到 y-1 一共 y-x 个数
}
return ans;
}
};


浙公网安备 33010602011771号