You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python recursion与memoization实现子集和问题的差异咨询

Hey there! Let's break this down step by step since you've already got a recursive solution for the subset sum problem and want to level it up with memoization, plus understand the key differences between the two approaches.

1. Recap: Plain Recursive Solution

First, let's confirm the core logic of your existing recursive code—since you're not allowed to use list slicing, you're probably using an index to track which element you're currently considering. The idea is simple: for each element, you have two choices:

  • Include it in the subset, then check if the remaining target sum (K minus the element's value) can be achieved with the rest of the elements.
  • Skip it, then check if the original target sum can be achieved with the rest of the elements.

Here's what that might look like in Python (following your no-slicing rule):

def subset_sum_plain(nums, k):
    def helper(current_index, remaining_sum):
        # Base cases: we found a valid subset, or we've exhausted all options
        if remaining_sum == 0:
            return True
        if current_index >= len(nums) or remaining_sum < 0:
            return False
        
        # Recurse: either take the current number or skip it
        take = helper(current_index + 1, remaining_sum - nums[current_index])
        skip = helper(current_index + 1, remaining_sum)
        return take or skip
    
    return helper(0, k)

The problem with this approach? Massive redundant calculations. For example, if you have a list like [1,2,3,4,5] and target 7, you'll end up calling helper(3, 4) multiple times through different recursive paths—each time re-solving the exact same subproblem from scratch. This leads to a time complexity of O(2^n), which gets painfully slow even for n as small as 20.

2. Adding Memoization to Eliminate Redundancy

Memoization is just a fancy word for "caching the results of subproblems so we don't have to recompute them." For the subset sum problem, the state of each subproblem can be uniquely defined by two values:

  • The current index we're looking at (current_index)
  • The remaining sum we need to reach (remaining_sum)

We can store these states and their results in a cache (like a dictionary) or use Python's built-in lru_cache decorator to handle caching automatically.

Here's the memoized version using a dictionary (no external libraries needed):

def subset_sum_memo(nums, k):
    memo = {}  # Key: (current_index, remaining_sum), Value: True/False
    
    def helper(current_index, remaining_sum):
        key = (current_index, remaining_sum)
        # Check if we've already solved this subproblem
        if key in memo:
            return memo[key]
        
        # Base cases
        if remaining_sum == 0:
            memo[key] = True
            return True
        if current_index >= len(nums) or remaining_sum < 0:
            memo[key] = False
            return False
        
        # Recurse and cache the result
        take = helper(current_index + 1, remaining_sum - nums[current_index])
        skip = helper(current_index + 1, remaining_sum)
        memo[key] = take or skip
        return memo[key]
    
    return helper(0, k)

Or if you prefer using lru_cache (cleaner syntax, but requires importing functools):

from functools import lru_cache

def subset_sum_memo(nums, k):
    @lru_cache(maxsize=None)
    def helper(current_index, remaining_sum):
        if remaining_sum == 0:
            return True
        if current_index >= len(nums) or remaining_sum < 0:
            return False
        return helper(current_index + 1, remaining_sum - nums[current_index]) or helper(current_index + 1, remaining_sum)
    
    result = helper(0, k)
    helper.cache_clear()  # Optional: free up memory after computation
    return result

Notice we're still following the no-slicing rule—we're only passing the index and remaining sum, not modifying or slicing the original list.

3. Key Differences Between Plain Recursion and Memoized Recursion

Let's break down the critical distinctions:

  • Time Complexity:

    • Plain recursion: O(2^n). Every element branches into two recursive calls, leading to an exponential number of operations.
    • Memoized recursion: O(n*K). We only compute each unique subproblem once—there are n possible indices and K+1 possible remaining sums (from 0 to K), so total subproblems are n*(K+1). This is a massive improvement for even moderately sized n or K.
  • Space Complexity:

    • Plain recursion: O(n). The only extra space is the recursion call stack, which goes as deep as the number of elements (n) in the worst case.
    • Memoized recursion: O(n*K). We add the cache space to store all subproblem results, which scales with n and K. This is a tradeoff of space for time.
  • Performance in Practice:

    • Plain recursion will struggle with n > 20—you'll notice significant slowdowns or even stack overflow errors for larger lists.
    • Memoized recursion can handle much larger n (e.g., n=1000) as long as K isn't astronomically large, since it avoids redundant work entirely.
  • Redundancy:

    • Plain recursion wastes cycles re-solving identical subproblems over and over. For example, in your sample input [1,4,8] with K=5, the subproblem "check if we can get sum 5 starting from index 1" might be hit through different paths, but plain recursion re-computes it every time.
    • Memoized recursion eliminates all redundancy—once a subproblem is solved, its result is stored and reused instantly whenever the same state is encountered again.
Example Walkthrough

Let's take your sample input nums=[1,4,8], K=5:

  • Plain recursion: Will call helper(1,5) (skip 1) and helper(1,4) (take 1). The helper(1,4) call will check taking 4 (remaining sum 0, returns True), so the overall result is True. But if there were more elements, it would re-calculate similar states multiple times.
  • Memoized recursion: When helper(1,4) is solved, it's stored in the cache. If any other path leads to the same state, it just returns the cached True instead of re-recursing.

内容的提问来源于stack exchange,提问作者Guy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 08:36:21