Одномерная мемоизация в рекурсивном решении самой длинной возрастающей подпоследовательности

Вычисление LIS (самой длинной возрастающей подпоследовательности) в массиве - очень известная проблема динамического программирования. Однако в каждом руководстве они сначала показывают рекурсивное решение без использования концепций DP, а затем решают его, применяя DP снизу вверх (итеративное решение).

У меня вопрос:

Как мы будем использовать мемоизацию в самом рекурсивном решении. Не только мемоизация, но и мемоизация с использованием 1D-массива.

Я провел небольшое исследование, но не нашел ничего подходящего. Хотя есть 2 места, где запрашивалась рекурсивная мемоизация, 1 & 2, но решения там используют 2D-карту / массив для запоминания.

В любом случае запоминание решения с помощью одномерного массива дает неправильный результат. Вот что я сделал:

int lis(int idx, int prev)
{
    if(idx >= N)
        return 0;

    if(dp[idx])
        return dp[idx];

    int best_ans = lis(idx+1, prev);

    int cur_ans = 0;
    if(arr[idx] > prev)
    {
        cur_ans = 1 + lis(idx+1, arr[idx]);
    }
    int ans = max(best_ans, cur_ans);
    dp[idx] = ans;
    return ans;
}

int main()
{
    // Scan N 
    // Scan arr

    ans = lis(0, -1));
    print ans;
}

Хотя я знаю причину, по которой это решение дает неправильный результат:

Для данного индекса может быть несколько решений, основанных на предыдущем значении.

Но я все еще хочу знать, как это можно сделать с помощью одномерного массива.

Мне любопытно узнать решение, потому что я прочитал, что каждое решение DP сверху-вниз может быть преобразовано в снизу-вверх и наоборот.

Было бы очень полезно, если бы кто-нибудь мог дать некоторое представление о том же.

Заранее спасибо.


person Shivang Bansal    schedule 20.02.2017    source источник
comment
Может кто-нибудь объяснить этот вопрос, я запутался с той же проблемой?   -  person Shravan Kumar Suthaar    schedule 25.04.2019


Ответы (2)


Это невозможно сделать, потому что для решения проблемы принципиально нужна двумерная структура данных.

Подход снизу вверх может обмануть, создавая одну строку за раз в структуре данных. При просмотре с течением времени он создает двухмерную структуру данных, но в любой момент времени вы видите только одно ее измерение.

При нисходящем подходе необходимо построить всю 2D-структуру данных.

Это фундаментальный компромисс в DP. Обычно проще записать подход сверху вниз. Но восходящий подход должен иметь только часть общей структуры данных в любое время, и поэтому требования к памяти значительно ниже.

person btilly    schedule 20.02.2017
comment
Спасибо за ответ. И да, вы очень правы. Я особенно видел случаи, когда требования к пространству для мемоизации сверху вниз превышают требования к пространству для мемоизации снизу вверх. Хотя в большинстве из них становится довольно очевидно, почему это делается, поскольку больше не требуется вышеуказанных строк, но все же есть некоторые случаи, когда это не интуитивно понятно . Не могли бы вы объяснить то же самое? Более того, я хотел бы знать, почему нисходящий подход нельзя использовать для получения этой полезности. Также, пожалуйста, объясните проблему с LIS. - person Shivang Bansal; 21.02.2017
comment
@ShivangBansal Причина в том, что рекурсия + memoize не знает, когда конкретный фрагмент данных больше никогда не понадобится, поэтому должен сохранить его все. Внизу вверх вы можете узнать, когда вы действительно закончили с частью данных, потому что вы перешли через свои данные. Если это не имеет интуитивного смысла, я мог бы написать эссе, и это не помогло бы, пока вы не убедитесь в этом сами. - person btilly; 21.02.2017
comment
@btilly есть ли какие-нибудь подсказки из подзадачи, указывающие на использование 2d вместо 1d? Для человека вроде меня, который только начинает заниматься динамическим программированием, я могу видеть подзадачи этой проблемы и интуитивно знать, что если я смогу их кэшировать, мне не придется работать над ними, а просто использовать их повторно, так почему бы не использование 1d работы для этой конкретной проблемы не работает? Любые дополнительные объяснения будут полезны, пожалуйста. - person Spindoctor; 30.03.2021
comment
@Spindoctor У меня нет лучшего объяснения, чем: Попробуйте написать рекурсивную функцию, посмотрите, сколько аргументов необходимо, чтобы функция работала правильно. На самом деле решение нескольких задач принесет больше пользы, чем книга объяснений. - person btilly; 30.03.2021

def LongestIncreasingSubsequenceMemo(nums, i, cache):
    if cache[i] > 0:
        return cache[i]
    result = 1
    for j in range(i):
        if nums[i] > nums[j]:
            result = max(result, 1 + LongestIncreasingSubsequenceMemo(nums, j, cache))
    cache[i] = result
    return result

def main():
    nums = [1,2,3,4,5]
    if not nums:
        return 0        
    n = len(nums)
    cache = [0 for i in range(n)]
    result = 1
    for i in range(n):
        result = max(result, LongestIncreasingSubsequenceMemo(nums, i, cache))
    return result

if __name__ == "__main__":
    print(main())

В приведенном выше решении мы берем одномерный массив и обновляем его для каждого элемента в массиве.

person Roosh    schedule 28.08.2019