【问题标题】:Comparing returned vectors from recursion tree branches比较从递归树分支返回的向量
【发布时间】:2021-06-30 05:24:39
【问题描述】:

假设我有一个给定的总和,比如 sum = 4。我还得到一个向量 = {2,4}。有两种方法可以从给定的向量生成给定的总和(可以重复使用元素)。 一种方法就是 {4} 导致 4 = 4。 第二种方法是 {2,2} 导致 2 + 2 = 4。 我必须找到可能的最短组合,因此在这种特殊情况下,答案是 {4}。

这是我的方法 - 我遍历树,当在叶子上得到 0 时,我们达到基本情况,返回 {} 向量,并在遍历树时填充向量。当我到达一个节点时,我选择两个(或更多)向量中较小的一个。这样当我到达根节点时,我应该得到一个可以产生目标总和的最短组合的向量。

到目前为止,我并不关心时间限制,我知道有很多重复的计算正在进行,所以一旦我能够正确地获得基本版本,我就必须记住它。

我一直在试图弄清楚为什么这段代码不起作用。任何见解将不胜感激。

#include <vector>
#include <algorithm>
#include <iostream>

using namespace std;

vector<int> findBestSum(int targetSum, const vector<int> &elements, vector<vector<int>> &temp) {
    if (targetSum == 0)
        return {};
    else if (targetSum < 0)
        return {-1};
    else {
        vector<int> small;
        for (auto &i : elements) {
            int remainder = targetSum - i;
            vector<int> returnedVector = findBestSum(remainder, elements, temp);
            if ((!returnedVector.empty() && find(returnedVector.begin(), returnedVector.end(), -1) == returnedVector.end()) || returnedVector.empty()) {
                returnedVector.push_back(i);
                temp.push_back(returnedVector);
            }
            int smallestLength = temp[0].size();
            for (auto &j : temp)
                if (smallestLength >= j.size())
                    small = j;
        }
        return small;
    }
}

int main() {
    int targetSum = 6;
    const vector<int> elements{2, 3, 5}; // answer should be [3,3] however I just get a 3...
    vector<vector<int>> temp;
    vector<int> bestSumVector = findBestSum(targetSum, elements, temp);
    for (auto i : bestSumVector)
        cout << i << " ";
} 

更新(2021 年 7 月 14 日):

在忙碌了几个月后,我试图解决这个问题,这次我的代码如下所示:

#include <iostream>
#include <vector>
#include <map>
#include <numeric>

using namespace std;

bool howSum(int &targetSum, vector<int> &elementVector, vector<int> &howSumVector, vector<vector<int>> &allSums) {
    static int originaltargetsum = targetSum;
    if (targetSum == 0)
        return true;
    else if (targetSum < 0)
        return false;
    else {
        for (auto i : elementVector) {
            int remainder = targetSum - i;
            bool flag = howSum(remainder, elementVector, howSumVector, allSums);
            if (flag) {
                howSumVector.push_back(i);
                if (targetSum == originaltargetsum ||
                    accumulate(howSumVector.begin(), howSumVector.end(), 0) == originaltargetsum) {
                    allSums.push_back(howSumVector);
                    howSumVector.clear();
                }
                return true;
            }
        }
        return false;
    }
}

int main() {
    int sum = 8; 
    vector<int> elements = {1, 4, 5}; 
    vector<vector<int>> allSums = {};
    vector<int> workingBench = {};
    howSum(sum, elements, workingBench, allSums);
    for (auto &i : allSums) {
        for (auto &j : i) {
            cout << j << " ";
        }
        cout << endl;
    }
}
 

为此,我将sum 设置为8,将elements 设置为{1, 4, 5}。 此外,我现在正在存储和显示所有可能的解决方案(一旦正确完成,找到最短向量和记忆应该很容易)。在这种情况下可能的解决方案是:

[1, 1, 1, 1, 1, 1, 1, 1]
[4, 4]
[5, 1, 1, 1]
[4, 1, 1, 1, 1]

目前我的代码只显示第一个可能的组合。我很确定我错误地返回了truefalse,请在这里帮帮我。

【问题讨论】:

  • 好吧,我并没有真正看到任何将某些东西推送到small 的东西,只有small = j 对其进行了修改,所以这可能就是为什么你只得到3 作为输出的原因。尝试调试它。
  • small = j 复制最小的j 的内容(我的向量向量中的最小向量),这就是我没有使用push_back(...) 或其他任何东西的原因。
  • 通过调试代码我可以看到j 实际上确实具有temp 中的最小向量。问题在于将i 推回returnedVector,然后将returnedVector 推回temp
  • 一旦对howSum 的调用返回true,它们将all 返回true,并且您只会将一个元素添加到结果数组中。我认为您在进行递归调用时会希望在howSum 中添加和删除元素,因为(以您的示例)在添加[4, 1, 1, 1, 1] 之后,您需要在再次递归尝试时将4 保留在howSumelementVector (4) 中的下一个元素。

标签: c++ recursion dynamic-programming subset-sum


【解决方案1】:

我认为您需要更改实现以正确处理向量的元素。 在您的实现中,它不会遍历所有向量项,仅遍历第一个。 如果您使用矢量元素作为函数中的第一个参数,这是一种方法。

vector<int> findBestSum(int element, int targetSum, const vector<int>& elements, 
vector<vector<int>>& temp) {
if (targetSum == 0)
    return {};
else if (targetSum < 0)
    return { -1 };
else {
    int remainder = targetSum - element;
    vector<int> returnedVector = findBestSum(element, remainder, elements, temp);
    if ((!returnedVector.empty() && find(returnedVector.begin(), returnedVector.end(), -1) == returnedVector.end()) || returnedVector.empty()) {
        returnedVector.push_back(element);
        return returnedVector;
    }
    return returnedVector;
}

}

int main() {
  const int targetSum = 6;
  const vector<int> elements{ 2, 3, 5 }; // answer should be [3,3] however I just get a 3...
  vector<vector<int>> temp;
  for (auto i : elements) {
      vector<int> returnedVector = findBestSum(i, targetSum, elements, temp);
      if ((!returnedVector.empty() && find(returnedVector.begin(), returnedVector.end(), -1) == returnedVector.end()) || returnedVector.empty())
          temp.push_back(returnedVector);
  }

  if (temp.size() > 0) {
      vector<int> bestSum = {};
      size_t small = 0;
      size_t smallestLength = temp[0].size();
      for (auto& j : temp)
          if (smallestLength >= j.size()) {
              small = j.size();
              bestSum = j;
          }
      for (auto i : bestSum)
          cout << i << " ";
    }
    else
        cout << " sum not found" << endl;
}

【讨论】:

  • findBestSum() 找到一个为 0 的叶子节点时,它立即返回到顶层,但忽略中间可能有 0 的任何叶子。我的意思是,对于targetSum = 8elements {2 , 3 , 5},我们应该得到的最终答案是{3 , 5}。但是此代码返回 {2 , 2 , 2 , 2}。只要画一个递归树,你就会明白我想说的。
  • 发生这种情况是因为if(...) 中的return returnedVector;。关于如何不跳过树的其他分支的任何建议?
  • 我提供的实现是解决方案的一部分 - 找到向量的元素,它可用于生成总和(上一个示例中的 8 )。现在您需要根据向量元素找到所有可能的组合来构建总和。我看不出您的实现是如何尝试这样做的。
  • 这就是我最初遇到的问题。如何将所有分支的向量返回到其父节点进行比较?有什么想法吗?
  • 在软件中,正确描述您要解决的问题非常重要。如果你说“有一个整数向量和一个目标总和,我需要找到任何整数的组合来总和为目标总和”,那么我相信你会通过指向 en.wikipedia.org/wiki/Subset_sum_problem 的链接获得帮助迅速地。这将帮助您找到您正在寻找的解决方案。
【解决方案2】:

我对此进行了尝试。我确实有一个可行的解决方案,希望这是您想要的:

#include <iostream>
#include <vector>
#include <algorithm>

void howSum(int targetSum, const std::vector<int> & elementVector, const std::vector<int> & howSumVector, std::vector<std::vector<int>> & allSums)
{
    static int originaltargetsum = targetSum;

    if (targetSum == 0)
    {
        allSums.push_back(howSumVector);
        return;
    }
    else if (targetSum < 0)
    {
        return;
    }
    else
    {
        for (const auto i : elementVector)
        {
            // an element less than or equal to 0 would cause an infinite loop
            if (i <= 0)
                continue;

            std::vector<int> newSumVector = howSumVector;
            newSumVector.push_back(i);

            std::vector<int> newElementVector;
            std::copy_if(std::begin(elementVector), std::end(elementVector), std::back_inserter(newElementVector), [i](int element){ return element >= i; });

            howSum(targetSum - i, newElementVector, newSumVector, allSums);
        }
    }
}

int main()
{
    int sum = 8;
    std::vector<int> elements = { 1, 4, 5 };
    std::vector<std::vector<int>> allSums = {};
    std::vector<int> workingBench = {};

    howSum(sum, elements, workingBench, allSums);

    for (const auto & i : allSums)
    {
        for (const auto & j : i)
        {
            std::cout << j << " ";
        }

        std::cout << std::endl;
    }

    return 0;
}

我认为,总的来说,您对问题的思考过度或设计过度。就像其他人提到的那样,您当前的代码返回 true 太早了,除了第一个元素/组合之外,没有任何其他测试。对于递归,请务必注意返回案例 - 实际上,您只需要一个或两个基本案例,否则您希望递归。

使用我这里的解决方案,我添加的主要内容是复制您需要测试的每个元素的当前元素组合。这解决了您不测试每个数字组合的主要问题。除此之外,在达到targetSum 时附加到allSums 似乎更好。通过这些更改,我能够取消 bool 返回值并稍微简化代码。运行上面的代码给出了这些解决方案:

1 1 1 1 1 1 1 1
1 1 1 1 4
1 1 1 4 1
1 1 1 5
1 1 4 1 1
1 1 5 1
1 4 1 1 1
1 5 1 1
4 1 1 1 1
4 4
5 1 1 1

这确实有一些重复(因为测试的顺序),但我觉得它已经足够好了,因为你只想要最小的解决方案,4 4。要找到它,您只需按内部向量大小对 allSums 向量进行排序,然后取第一个条目。

【讨论】:

  • 你对这个问题的记忆有什么建议?我试图将特定targetSum 的最小组合放在map&lt;int, vector&lt;int&gt;&gt; 中。这样我们就不必重新计算任何重叠的子问题。
  • @DivyanshuVarma 如果你想阻止计算重复的工作,我认为一个优雅的方法是调整递归函数内部的elementVector。通过从elementVector 中删除小于i 的所有元素,您可以保证没有重复。
  • 您可以使用地图,但这对我来说没有多大意义 - 向量需要成为键(您想删除重复项?)并且地图的值将毫无意义。如果您只是使用地图来删除重复的计算,最好不要一开始就计算它们。
  • @DivyanshuVarma 我已经编辑了我的答案,添加了几行以删除重复项的计算。
  • 你能解释一下下面这行吗? std::copy_if(std::begin(elementVector), std::end(elementVector), std::back_inserter(newElementVector), [i](int element){ return element &gt;= i; });
猜你喜欢
  • 2014-12-08
  • 1970-01-01
  • 1970-01-01
  • 2012-08-10
  • 1970-01-01
  • 2020-01-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多