返回

C/C++ 剑指 Offer II 115. 重建序列:探索反向思维与动态规划之美

后端

题目

给定一个长度为 n 的整数序列 a1, a2, ..., an,请重建一个长度为 n + 1 的整数序列 b1, b2, ..., bn+1,其中 bi 的值可以是 ai 的任意一个倍数。

例如,如果给定序列为 [1, 2, 3],那么可以重建的序列包括 [1, 2, 3, 1], [1, 2, 6, 1], [3, 6, 9, 3] 等。

题目整理

  • 输入:一个长度为 n 的整数序列 a1, a2, ..., an。
  • 输出:一个长度为 n + 1 的整数序列 b1, b2, ..., bn+1,其中 bi 的值可以是 ai 的任意一个倍数。

题目解题思路

这道题乍一看可能会让人感到棘手,但其实我们可以利用反向思维和动态规划来巧妙地解决它。

首先,我们可以从后往前考虑序列 b 的构建。假设我们已经知道了序列 b 的最后若干项,那么我们就可以根据这些已知项来推导出序列 b 的前若干项。

具体来说,我们可以使用动态规划来解决这个问题。我们定义一个状态 dp[i],表示序列 b 的前 i 项是否可以由序列 a 的前 i 项重建而来。

  • 如果 dp[i] 为 true,则表示序列 b 的前 i 项可以由序列 a 的前 i 项重建而来。
  • 如果 dp[i] 为 false,则表示序列 b 的前 i 项无法由序列 a 的前 i 项重建而来。

我们可以使用以下递推关系来计算 dp[i]:

dp[i] = true if (dp[i-1] and b[i] is a multiple of a[i-1]) or (dp[i-2] and b[i] is a multiple of a[i-2])

其中,dp[i-1] 表示序列 b 的前 i-1 项是否可以由序列 a 的前 i-1 项重建而来,dp[i-2] 表示序列 b 的前 i-2 项是否可以由序列 a 的前 i-2 项重建而来。

如果我们能够求出 dp[n+1],那么我们就知道了序列 b 的前 n+1 项是否可以由序列 a 的前 n+1 项重建而来。

如果 dp[n+1] 为 true,则表示序列 b 可以由序列 a 重建而来,我们可以通过反向推导的方式来找到序列 b 的具体值。

如果 dp[n+1] 为 false,则表示序列 b 无法由序列 a 重建而来,这道题无解。

具体实现

#include <iostream>
#include <vector>

using namespace std;

class Solution {
public:
    vector<int> reconstructSequence(vector<int>& a) {
        int n = a.size();
        vector<int> b(n + 1);
        vector<bool> dp(n + 2);
        dp[0] = true;
        dp[1] = true;
        for (int i = 2; i <= n + 1; i++) {
            dp[i] = (dp[i-1] and b[i-1] % a[i-2] == 0) or (dp[i-2] and b[i-2] % a[i-3] == 0);
        }
        if (!dp[n+1]) {
            return {};
        }
        int index = n;
        b[n] = a[n-1];
        for (int i = n-1; i >= 1; i--) {
            if (dp[i] and b[i] % a[i-1] == 0) {
                index = i-1;
                b[index] = a[i-1];
            }
        }
        return b;
    }
};

int main() {
    Solution solution;
    vector<int> a = {1, 2, 3};
    vector<int> b = solution.reconstructSequence(a);
    for (int x : b) {
        cout << x << " ";
    }
    cout << endl;
    return 0;
}

代码解释

  1. 我们首先定义了一个 Solution 类,并在其中定义了一个名为 reconstructSequence 的方法。这个方法的参数是一个整数序列 a,返回值是一个整数序列 b。

  2. 在 reconstructSequence 方法中,我们首先计算出序列 a 的长度 n,并定义了一个长度为 n + 1 的整数序列 b 和一个长度为 n + 2 的布尔数组 dp。

  3. 然后,我们使用动态规划来计算 dp[i]。我们从 dp[0] 和 dp[1] 开始计算,然后使用递推关系来计算 dp[2] 到 dp[n+1]。

  4. 如果 dp[n+1] 为 true,则表示序列 b 可以由序列 a 重建而来。我们通过反向推导的方式来找到序列 b 的具体值。

  5. 如果 dp[n+1] 为 false,则表示序列 b 无法由序列 a 重建而来,这道题无解。

  6. 最后,我们返回序列 b。

复杂度分析

  • 时间复杂度:O(n^2),其中 n 为序列 a 的长度。这是因为我们在计算 dp[i] 时需要考虑所有可能的组合,因此时间复杂度为 O(n^2)。
  • 空间复杂度:O(n),其中 n 为序列 a 的长度。这是因为我们定义了一个长度为 n + 2 的布尔数组 dp 来存储状态。

总结

这道题考察了反向思维和动态规划的结合应用。通过反向思维,我们可以从后往前考虑序列 b 的构建。通过动态规划,我们可以有效地计算出序列 b 的前 i 项是否可以由序列 a 的前 i 项重建而来。这道题的解题思路比较巧妙,值得我们学习。