Java 递归确定是否有可能找到目标编号

Java 递归确定是否有可能找到目标编号,java,recursion,combinations,Java,Recursion,Combinations,我将为初学者更好地解释标题。我的问题与常见的问题非常相似:查找整数数组的所有置换问题 我试图找到,给定一个整数列表和一个目标数字,是否可以从列表中选择任意数字组合,使它们的总和与目标匹配。 它必须使用函数式编程实践来完成,所以这意味着所有的循环和突变都是错误的,只有巧妙的递归。完全公开:这是一项家庭作业,方法标题由教授按原样设置。这就是我得到的: public static Integer sum(final List<Integer> values) { if(value

我将为初学者更好地解释标题。我的问题与常见的问题非常相似:查找整数数组的所有置换问题

我试图找到,给定一个整数列表和一个目标数字,是否可以从列表中选择任意数字组合,使它们的总和与目标匹配。 它必须使用函数式编程实践来完成,所以这意味着所有的循环和突变都是错误的,只有巧妙的递归。完全公开:这是一项家庭作业,方法标题由教授按原样设置。这就是我得到的:

public static Integer sum(final List<Integer> values) {
     if(values.isEmpty() || values == null) {
             return 0;
     }
     else {
             return values.get(0) + sum(values.subList(1, values.size()));
     }
 }

public static boolean groupExists(final List<Integer> numbers, final int target) {
     if(numbers == null || numbers.isEmpty()) {
             return false;
     }
     if(numbers.contains(target)) {
             return true;
     }
      if(sum(numbers) == target) {
              return true;
      }
      else {
              groupExists(numbers.subList(1, numbers.size()), target);
              return false;
      }
  }
公共静态整数和(最终列表值){
if(values.isEmpty()| | values==null){
返回0;
}
否则{
返回值.get(0)+sum(values.subList(1,values.size());
}
}
公共静态布尔groupExists(最终列表编号、最终整数目标){
if(numbers==null | | numbers.isEmpty()){
返回false;
}
if(数字。包含(目标)){
返回true;
}
if(总和(数字)=目标){
返回true;
}
否则{
groupExists(numbers.subList(1,numbers.size()),target);
返回false;
}
}

sum方法已经过测试并且有效,groupExists方法就是我正在研究的方法。我认为它非常接近,如果给出一个列表[1,2,3,4],它将为3和10这样的目标返回true,但为6返回false,这让我感到困惑,因为1,2,3按顺序是正确的,加上6。很明显,有些东西不见了。此外,我所看到的主要问题是,它没有测试所有可能的组合,例如,第一个和最后一个数字没有作为一种可能性被加在一起

更新: 在根据Simon的答案工作了一段时间后,我看到的是:

public static boolean groupExists(final List<Integer> numbers, final int target) {
     if(numbers == null || numbers.isEmpty()) {
             return false;
      }
     if(numbers.isEmpty()) {
              return false;
      }
      if(numbers.contains(target)) {
              return true;
      }
      if(sum(numbers.subList(1, numbers.size())) == (target - numbers.get(0))) {
              return true; }                                                                                                                                                                                                     
      else {
              return groupExists(numbers.subList(1, numbers.size()), target);
      }
  }
publicstaticbooleanggroupexists(最终列表编号,最终整数目标){
if(numbers==null | | numbers.isEmpty()){
返回false;
}
if(numbers.isEmpty()){
返回false;
}
if(数字。包含(目标)){
返回true;
}
if(sum(numbers.subList(1,numbers.size())==(target-numbers.get(0))){
返回true;}
否则{
返回groupExists(numbers.subList(1,numbers.size()),target);
}
}

检查所有组合都是阶乘(您的实现中缺少了一点)。 为什么不尝试另一种(动态)方法:见第1页(子集和)

您的主要方法如下:

boolean canSum(numbers, target) {
  return computeVector(numbers)[target]
}
boolean[] computeVector(numbers, vector) {
  if numbers is empty:
    return vector

  addNumber(numbers[0], vector)

  return computeVector(tail(numbers), vector);
}
computeVector
返回向量,该向量包含可与一组
数字求和的所有数字。
方法
computeVector
递归执行起来有点棘手,但您可以执行以下操作:

boolean canSum(numbers, target) {
  return computeVector(numbers)[target]
}
boolean[] computeVector(numbers, vector) {
  if numbers is empty:
    return vector

  addNumber(numbers[0], vector)

  return computeVector(tail(numbers), vector);
}
addNumber
将获取向量,并用新的“可行”数字“填充”(有关解释,请参见搭便车者)
addNumber
也可能有点棘手,我将把它留给您。基本上,您需要以重现的方式编写以下循环:

for(j=M; j>=a[i]; j--)
  m[j] |= m[j-a[i]];

假设您有
n
个数
a[0]
a[1]
,…,
a[n-1]
,您想知道一些子集是否和
n
相加

假设你有这样一个子集。现在,要么
a[0]
包含在内,要么不包含在内。如果包含它,则必须存在一个子集
a[1]
,…,
a[n]
,其总和为
n-a[0]
。如果不是,则存在一个子集
a[1]
,…,
a[n]
,其总和为
n


这将引导您找到一个递归解决方案。

通过在每次递归时询问一个非常简单的决定,可以获得所有可能组合的列表。这个组合是否包含我列表的标题?要么是这样,要么不是这样,因此每个阶段有两条路径。如果任何一条路径都指向一个解决方案,那么我们希望返回true

boolean combination(targetList, sourceList, target)
{
    if ( sourceList.isEmpty() ) {
        return sum(targetList) == target;
    } else {
        head = sourceList.pop();
        without = combination(targetList, sourceList, target); // without head
        targetList.push(head);
        with = combination(targetList, sourceList, target); // with head
        return with || without;
    }
}

为方便起见,请申报

static Integer head(final List<Integer> is) {
  return is == null || is.isEmpty()? null : is.get(0);
}
static List<Integer> tail(final List<Integer> is) {
  return is.size() < 2? null : is.subList(1, is.size());
}
静态整数头(最终列表为){
return is==null | | is.isEmpty()?null:is.get(0);
}
静态列表尾部(最终列表为){
返回is.size()<2?null:is.subList(1,is.size());
}
那么你的功能是:

static boolean groupExists(final List<Integer> is, final int target) {
  return target == 0 || target > 0 && head(is) != null &&
       (groupExists(tail(is), target) || groupExists(tail(is), target-head(is)));
}
static boolean groupExists(最终列表为,最终int目标){
返回目标==0 | |目标>0&&head(is)!=null&&
(groupExists(tail(is)、target)| groupExists(tail(is)、target head(is));
}
实际上,定期检查基本情况和最后一行并没有什么奇怪的,其中左操作数和右操作数分别搜索包含或不包含头部的“组”


我编写它的方式让人一眼就看出,这些都是纯函数,但是,由于这是Java语言,而不是FP语言,所以这种编写方式非常不理想。最好将多次发生的函数调用缓存到最终的本地变量中。当然,这还是书上说的。

你只检查了列表尾部的总和,所有组合的测试都有很多遗漏。是的,我的帖子中已经提到了。你说“遗漏了一些东西”和“非常接近”。我说“您只检查了列表的尾部”和“缺少了很多”。“我看到的主要问题是它没有测试所有可能的组合,例如,第一个和最后一个数字没有作为一种可能添加在一起。”您不需要使用
sum()
。将其重新编写为对
groupExists()
的调用,打破了我现有的一些规则,但将我推向了解决方案,所以谢谢:)Marko,非常感谢您在这方面所做的工作。刚刚测试过,效果很好。我真的很喜欢你测试列表可能的排列方式。