返回

极速攻略:768. 最多能完成排序的块 II

后端

LeetCode 768:利用动态规划移动 1 和 0

概要

LeetCode 768 是一道令人着迷的难题,它考验我们的算法和动态规划技能。给定一个包含 0 和 1 的数组,我们的目标是将所有 1 移到数组的前面,同时尽量减少移动次数。在这篇文章中,我们将深入探讨如何使用动态规划来解决这个难题,涵盖从题意理解到算法实现和代码示例。让我们踏上这段算法之旅!

1. 理解问题的本质

LeetCode 768 的核心目标非常简单:将所有 1 移到数组的前面。然而,我们必须采取谨慎的行动,因为每一步的移动都会消耗次数。我们可以选择以下两种操作:

  • 将 1 移到最前面。
  • 将 0 移到最后面。

我们的任务是找到一种将所有 1 移到前面所需的最小移动次数。

2. 动态规划:分而治之的利器

动态规划是一种强大的算法技术,它将问题分解成较小的子问题,并通过递推关系计算每个子问题的最优解。LeetCode 768 正是应用动态规划的理想场景。

我们定义一个 dp 数组,其中 dp[i] 表示将前 i 个元素移到正确位置所需的最小移动次数。然后,我们可以使用以下公式来计算 dp[i]:

dp[i] = min(dp[i-1] + cost(i, 0), dp[i-2] + cost(i, 1))

其中:

  • cost(i, 0) 是将第 i 个元素移到最后面的移动次数。
  • cost(i, 1) 是将第 i 个元素移到最前面的移动次数。

通过计算每个 dp[i],我们最终将获得 dp[n-1],其中 n 是数组的长度,它表示将所有 1 移到前面的最小移动次数。

3. 算法实现:将理论付诸实践

现在,我们将把我们的动态规划策略转化为实际的算法步骤:

  1. 初始化 dp 数组,其中 dp[0] = 0。
  2. 对于 i 从 1 到 n-1:
    • 计算 cost(i, 0) 和 cost(i, 1)。
    • 使用上述公式更新 dp[i]。
  3. 返回 dp[n-1]。

4. 代码示例:C/C++

让我们用 C/C++ 语言实现 LeetCode 768 的解决方案:

class Solution {
public:
    int minMoves2(vector<int>& nums) {
        int n = nums.size();
        vector<int> dp(n, INT_MAX);
        dp[0] = 0;
        for (int i = 1; i < n; i++) {
            dp[i] = min(dp[i-1] + cost(i, 0), dp[i-2] + cost(i, 1));
        }
        return dp[n-1];
    }

private:
    int cost(int i, int target) {
        int n = nums.size();
        int diff = abs(nums[i] - target);
        return diff;
    }
};

5. 常见问题解答

Q1:动态规划的优点是什么?
A1: 动态规划将问题分解成较小的子问题,并通过递推关系计算每个子问题的最优解。这有助于避免重复计算,从而提高效率。

Q2:为什么我们使用 dp[i-1] 和 dp[i-2] 而不是 dp[i-3] 和 dp[i-4]?
A2: 将一个元素移动到最前面或最后面需要一个或两个步骤。因此,我们只考虑前两个子问题(dp[i-1] 和 dp[i-2])。

Q3:如何计算 cost(i, 0) 和 cost(i, 1)?
A3: cost(i, 0) 是 nums[i] 与 0 之间的绝对差,而 cost(i, 1) 是 nums[i] 与最后一个元素(nums[n-1])之间的绝对差。

Q4:代码中 for 循环的目的是什么?
A4: for 循环遍历数组中的每个元素,并根据前两个元素(dp[i-1] 和 dp[i-2])计算每个元素的最小移动次数。

Q5:如何优化算法?
A5: 我们可以使用滚动数组技术优化算法,它通过只存储前两个元素来减少空间复杂度。