Reputation: 48
The restrictions are that the elements can be appended to the front if they are greater than the element at the front and to the back if they are smaller than the back. It can also ignore elements (and there comes the difficulty).
Example:
Input:
{6, 7, 3, 5, 4}
The longest sequence to that input is:
Start with {6}
.
Append 7 to the front because it is greater than 6. {7, 6}
Ignore 3.
Append 5 to the back because it is smaller. {7, 6, 5}
Append 4 to the back because it is smaller. {7, 6, 5, 4}
If we appended 3, the sequence would be smaller {7, 6, 3}
because then we wouldn't be able to append 4.
I tried to adapt a LIS algorithm to solve it, but the results are totally wrong.
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);
}
How can I solve it using Dynamic Programming?
Upvotes: 2
Views: 413
Reputation: 65458
The optimal substructure that we need for dynamic programming is that, given two sequences with the same front and back, it’s obviously better to extend the longer one (or the same, if the sequences have the same length). Here’s some C++. It’s inefficient for clarity and so that it can’t be fed directly to an online judge, but there’s a straightforward path to O(n³). With a little data structural cleverness, O(n² log n).
#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';
}
Upvotes: 3