4.15

72. 编辑距离 - 力扣(LeetCode)

image-20250326223016636

m = word1.size() , n = word2.size()

考虑word1[i] 与 word2[j] 是否相等的情况

  1. s[i] == t[j]时
    • dp[i][j] = dp[i - 1][j - 1]
  2. 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岛屿问题的写法来写。

思路:

  1. 初始遍历统计新鲜橘子数,并把腐烂橘子加入队列
  2. 当还有新鲜橘子且队列不为空时处理当前队列里的点,对每一层结果ans只加一次(过一分钟),对每一个点遍历四个方向,把感染的橘子记为2,并使新鲜数fresh - -
  3. 最后如果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.emplaceq.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) 的时间复杂度,需要两个关键优化:

  1. 把 nums 中的数都放入一个哈希集合中,这样可以 O(1) 判断数字是否在 nums 中。
  2. 如果 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;
    }
};
posted @ 2025-04-15 22:51  七龙猪  阅读(3)  评论(0)    收藏  举报
-->