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

如何生成满足求和范围约束的排列(避免全量生成)

Pruned Recursive Backtracking for Constrained Combinations

Great question! The brute-force expand.grid approach works fine for small datasets, but it quickly hits performance walls when dealing with large or numerous sets—this is exactly where pruned recursive backtracking shines. It lets you generate only the valid combinations without ever creating the full cartesian product. Let’s break down how to implement this in R, with logic that scales much better.

Core Pruning Logic

The key insight is to dynamically check if a partial combination can still lead to a valid sum before you finish building it. Here’s the rule:

  • Suppose you’ve already selected elements summing to current_sum, and you have k sets left to pick from.
  • Calculate min_remaining (sum of the smallest elements in each remaining set) and max_remaining (sum of the largest elements in each remaining set).
  • If current_sum + max_remaining < lower_bound: Even picking the largest possible elements from the remaining sets won’t reach the minimum sum—abandon this branch.
  • If current_sum + min_remaining > upper_bound: Even picking the smallest possible elements from the remaining sets will exceed the maximum sum—abandon this branch.

We can also add a pre-pruning step to remove elements that can never be part of any valid combination, regardless of what other elements you pick. For example, your set1 element 10: even when combined with the largest elements from set2 and set3 (9+4=13), the total is 23, which is below your lower bound of 25. We can safely remove 10 upfront to reduce our workload.

R Implementation

Let’s turn this logic into code. First, a function to pre-prune individual sets:

# Pre-prune a single set by removing elements that can't contribute to any valid combination
prune_single_set <- function(single_set, other_sets, lower, upper) {
  min_other_total <- sum(sapply(other_sets, min))
  max_other_total <- sum(sapply(other_sets, max))
  
  # Keep elements where:
  # x + smallest possible sum from other sets <= upper bound, AND
  # x + largest possible sum from other sets >= lower bound
  single_set[single_set + min_other_total <= upper & single_set + max_other_total >= lower]
}

Next, a recursive function that builds combinations and prunes invalid branches on the fly:

# Recursively generate valid combinations with dynamic pruning
generate_valid_combinations <- function(sets, lower, upper, current_sum = 0, current_comb = c()) {
  # Base case: all sets have been processed
  if (length(sets) == 0) {
    if (current_sum >= lower && current_sum <= upper) {
      return(list(current_comb))
    } else {
      return(list())
    }
  }
  
  current_set <- sets[[1]]
  remaining_sets <- sets[-1]
  
  # Calculate bounds for remaining sets to guide pruning
  min_remaining <- sum(sapply(remaining_sets, min))
  max_remaining <- sum(sapply(remaining_sets, max))
  
  # Filter elements in current set that could lead to a valid combination
  valid_elements <- c()
  # Sorting the set lets us break early (optional but efficient)
  sorted_set <- sort(current_set)
  for (x in sorted_set) {
    new_sum <- current_sum + x
    
    # If even the smallest possible total from here is too big, break (since set is sorted)
    if (new_sum + min_remaining > upper) {
      break
    }
    # If the largest possible total from here is still too small, skip
    if (new_sum + max_remaining < lower) {
      next
    }
    
    valid_elements <- c(valid_elements, x)
  }
  
  # Recurse on each valid element
  results <- list()
  for (x in valid_elements) {
    sub_results <- generate_valid_combinations(
      remaining_sets,
      lower,
      upper,
      current_sum + x,
      c(current_comb, x)
    )
    results <- c(results, sub_results)
  }
  
  return(results)
}

Test with Your Example

Let’s apply this to your original data:

# Original sets
set1 <- c(10, 15, 20)
set2 <- c(8, 9)
set3 <- c(1, 2, 3, 4)
all_sets <- list(set1, set2, set3)
lower_bound <- 25
upper_bound <- 29

# Step 1: Pre-prune all sets
pruned_sets <- list()
for (i in seq_along(all_sets)) {
  pruned_sets[[i]] <- prune_single_set(all_sets[[i]], all_sets[-i], lower_bound, upper_bound)
}
# pruned_sets[[1]] is now c(15, 20) — the invalid 10 is gone!

# Step 2: Generate valid combinations
valid_combs <- generate_valid_combinations(pruned_sets, lower_bound, upper_bound)

# Convert to a data frame for readability (matches your original output)
final_df <- do.call(rbind, lapply(valid_combs, function(comb) {
  data.frame(
    Var1 = comb[1],
    Var2 = comb[2],
    Var3 = comb[3],
    sum = sum(comb)
  )
}))

print(final_df)

This will output exactly the same rows as your original final data frame, but without generating all 324=24 combinations first.

Why This Works Better

  • No full cartesian product: We only generate combinations that have a shot at meeting the sum constraint. For large sets, this can reduce computation from millions of rows to thousands or less.
  • Dual pruning: Pre-pruning removes obvious invalid elements upfront, and dynamic pruning stops useless recursive branches early.
  • Scalable: This approach handles more sets or larger individual sets far better than expand.grid, since the pruning logic keeps the number of active branches manageable.

内容的提问来源于stack exchange,提问作者C. Braun

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 06:23:46