c++ 通过使用动态编程将元素追加到末尾和前面而构建的最长递减子序列的长度

1wnzp6jl  于 2022-12-05  发布在  其他
关注(0)|答案(2)|浏览(109)

bounty将在5天后过期。回答此问题可获得+50声望奖励。Gabriel Henrique希望吸引更多人关注此问题。

限制是,如果元素大于前面的元素,则可以将其追加到前面;如果元素小于后面的元素,则可以将其追加到后面。它还可以忽略元素(这就带来了困难)。
示例:
输入:{6, 7, 3, 5, 4}
该输入的最长序列为:

  • {6}开始。
  • 将7追加到前面,因为它大于6。{7, 6}
  • 忽略3。
  • 将5附加到后面,因为它较小。{7, 6, 5}
  • 将4附加到后面,因为它较小。{7, 6, 5, 4}

如果我们加上3,序列会更小,因为这样我们就不能加上4。
我试图采用LIS算法来解决它,但结果完全错误。

int adapted_LIS(int input[], int n)
{
    int score[n] = {};
    score[0] = 1;
    for (int i = 1; i < n; i++)
    {
        score[i] = 1;
        int front = input[i];
        int back = input[i];
        for (int j = 0; j < i; j++)
        {
            if (input[j] > front)
            {
                front = input[j];
                score[i] = std::max(score[i], score[j] + 1);
            }
            else if (input[j] < back)
            {
                back = input[j];
                score[i] = std::max(score[i], score[j] + 1);
            }
        }
    }
    return *std::max_element(score, score + n);
}

如何使用动态规划求解?

au9on6nz

au9on6nz1#

动态规划所需的最优子结构是,给定前后相同的两个序列,显然最好扩展较长的一个(或者,如果序列长度相同,则扩展相同的一个)。下面是一些C++代码(为了清晰起见,效率很低,因此不能直接提供给在线评判):

#include <algorithm>
#include <iostream>
#include <map>
#include <utility>
#include <vector>

std::vector<int> PushFront(int x, std::vector<int> subsequence) {
  subsequence.insert(subsequence.begin(), x);
  return subsequence;
}

std::vector<int> PushBack(std::vector<int> subsequence, int x) {
  subsequence.push_back(x);
  return subsequence;
}

void Consider(std::map<std::pair<int, int>, std::vector<int>> &table,
              std::vector<int> subsequence) {
  std::vector<int> &entry = table[{subsequence.front(), subsequence.back()}];
  if (subsequence.size() > entry.size()) {
    entry = std::move(subsequence);
  }
}

std::vector<int> TwoSidedDecreasingSubsequence(const std::vector<int> &input) {
  if (input.empty()) {
    return {};
  }
  // Maps {front, back} to the longest known subsequence.
  std::map<std::pair<int, int>, std::vector<int>> table;
  for (int x : input) {
    auto table_copy = table;
    for (const auto &[front_back, subsequence] : table_copy) {
      auto [front, back] = front_back;
      if (x > front) {
        Consider(table, PushFront(x, subsequence));
      }
      if (back > x) {
        Consider(table, PushBack(subsequence, x));
      }
    }
    Consider(table, {x});
  }
  return std::max_element(
             table.begin(), table.end(),
             [&](const std::pair<std::pair<int, int>, std::vector<int>> &a,
                 const std::pair<std::pair<int, int>, std::vector<int>> &b) {
               return a.second.size() < b.second.size();
             })
      ->second;
}

int main() {
  for (int x : TwoSidedDecreasingSubsequence({6, 7, 3, 5, 4})) {
    std::cout << ' ' << x;
  }
  std::cout << '\n';
}
htrmnn0y

htrmnn0y2#

你试图用Longest Increasing Subsequence (LIS) algorithm来解决这个问题,但这是行不通的,因为在这个问题中构造有效序列的规则与构造最长递增子序列的规则不同。
要使用动态编程来解决这个问题,您需要提出一种新的方法,该方法考虑将元素追加到序列前面或后面的特定规则。
一种方法是创建两个独立的动态编程数组,一个用来跟踪在数组前面结束的最长序列,另一个用来跟踪在数组后面结束的最长序列。然后,您可以迭代输入数组,并根据将元素追加到序列前面或后面的规则更新这两个数组。
下面是我提出的解决方案的大致轮廓:* (这应该作为对下面代码片段的解释)*
1.初始化两个动态编程数组frontback,大小都是n。将这些数组的所有元素设置为1。
1.从左到右迭代输入数组。在每一步i中,执行以下操作:
1.如果索引i处的元素大于序列前面的元素(即输入数组中索引front[i-1]处的元素),则通过将front[i]设置为front[i-1] + 1将此元素追加到序列的前面。
1.如果索引i处的元素小于序列后面的元素(即输入数组中索引back[i-1]处的元素),则通过将back[i]设置为back[i-1] + 1将此元素追加到序列后面。
1.在循环结束时,可以从输入数组构造的最长序列将是最大值of front[n-1]back[n-1]

int longest_sequence(int input[], int n)
{
    // Initialize dynamic programming arrays
    int front[n];
    int back[n];

    // Set all elements of the arrays to 1
    for (int i = 0; i < n; i++)
    {
        front[i] = 1;
        back[i] = 1;
    }

    // Iterate over the input array from left to right
    for (int i = 1; i < n; i++)
    {
        // If the current element is smaller than the element at the front of the sequence,
        // append it to the front of the sequence by updating front[i]
        if (input[i] < input[front[i-1] - 1])
        {
            front[i] = front[i-1] + 1;
        }

        // If the current element is greater than the element at the back of the sequence,
        // append it to the back of the sequence by updating back[i]
        if (input[i] > input[back[i-1] + 1])
        {
            back[i] = back[i-1] + 1;
        }
    }

    // Return the maximum of front[n-1] and back[n-1]
    return std::max(front[n-1], back[n-1]);

我用您的示例测试了我的解决方案:

int input[] = {6, 7, 3, 5, 4};
int n = 5;

int result = longest_sequence(input, n);

正如您所概述的(逐步),最长的序列是长度为4的{7, 6, 5, 4}。这正是longest_sequence返回的结果!
这个算法的时间复杂度为O(n^2)**,因为每一步都需要遍历输入数组,然后遍历动态规划数组,这是最好的结果,因为任何解决这个问题的算法都需要对输入数组中的每个元素至少检查一次。
......这可能是动态实现解决方案的最大警告。

相关问题