【问题标题】:find minimum sum of non-neighbouring K entries inside an array在数组中找到非相邻 K 项的最小总和
【发布时间】:2021-05-02 00:40:06
【问题描述】:

给定一个大小为N的整数数组A,求K个非相邻条目的最小总和(条目不能相邻,例如如果K为2,则不能加A[2], A[3] 并将其称为最小总和,即使是,因为它们彼此相邻/相邻),例如:

A[] = {355, 46, 203, 140, 28}, k = 2, result would be 74 (46 + 28)

A[] = {9, 4, 0, 9, 14, 7, 1}, k = 3, result would be 10 (9 + 0 + 1)

这个问题有点类似于 leetcode 上的House Robber,只是我们的任务不是找到不相邻条目的最大总和,而是找到最小总和并限制 K 个条目。

从我的角度来看,这显然是一个动态编程问题,所以我尝试递归地分解问题并实现如下所示:

#include <vector>
#include <iostream>
using namespace std;
int minimal_k(vector<int>& nums, int i, int k)
{
    if (i == 0) return nums[0];
    if (i < 0 || !k) return 0;
    return min(minimal_k(nums, i - 2, k - 1) + nums[i], minimal_k(nums, i - 1, k));
}
int main()
{
    // example above
    vector<int> nums{9, 4, 0, 9, 14, 7, 1};
    cout << minimal_k(nums, nums.size() - 1, 3);
    // output is 4, wrong answer
}

这是我对解决方案的尝试,我玩了很多次但没有运气,那么这个问题的解决方案是什么?

【问题讨论】:

  • 您的解决方案是否不正确或次优?
  • “我玩过很多次,但没有运气” 并不能充分描述您所做的事情或您面临的问题。鉴于您描述任务和代码的冗长,我很惊讶您未能解释您需要帮助的实际问题。
  • 请解释为什么 4 是错误的。还添加更多非工作案例的示例,它们的错误输出以及它们在 taks 描述中相矛盾的内容。您是否发现任何输出正确的情况?调试时,是否存在出现的中间值与您期望的不同的特殊情况?在玩耍时,您尝试过的一些变体是否表现出任何明显不同的不当行为?
  • @Yunnosch k 是 3,在那个例子中,你是如何通过对 3 个不同元素求和得到 4 的?至于其他情况,我尝试了多个并使用了一个备忘录,大多数时候它只返回数组的单个最小元素作为备忘录的每个条目的结果,例如:A[] = {1, 1, 2 , 2, 1, 3, 2, 2, 1, 1, 1, 1, 2}, k = 3, 输出为 1
  • 你不会问我是怎么想出 4 的吧?另外,请edit问题的信息和解释。

标签: c++ algorithm dynamic-programming


【解决方案1】:

这是核心排序相关的问题。要找到最小 k 个非相邻元素的总和,需要通过排序将最小值元素彼此相邻。让我们看看这种排序方法,

给定输入数组 = [9, 4, 0, 9, 14, 7, 1]k = 3
创建另一个数组,其中包含输入数组的元素,其索引如下所示,
[9, 0], [4, 1], [0, 2], [9, 3], [14, 4], [7, 5], [@ 987654329@, 6]
然后对该数组进行排序。

这个元素和索引数组背后的动机是,排序后每个元素的索引信息不会丢失。
还需要一个数组来记录使用过的索引,所以排序后的初始信息如下图,

Element and Index array
..............................
| 0 | 1 | 4 | 7 | 9 | 9 | 14 |
..............................
  2   6   1   5   3   0   4    <-- Index   

Used index record array
..............................
| 0 | 0 | 0 | 0 | 0 | 0 | 0 |
..............................
  0   1   2   3   4   5   6    <-- Index 

在已使用的索引记录数组中0 (false) 表示此索引处的元素尚未包含在最小总和中。
排序数组的前元素是最小值元素,我们将其包含在最小和中,并更新使用的索引记录数组以指示使用该元素,如下所示,
字体元素是索引2 处的0 并且由于使用的索引记录数组的索引2 处设置了1(true),如下所示,

min sum = 0

Used index record array
..............................
| 0 | 0 | 1 | 0 | 0 | 0 | 0 |
..............................
  0   1   2   3   4   5   6   

迭代到排序数组中的下一个元素,如上所示,它是1,索引为6。要在最小总和中包含1,我们必须找到,1 的左侧或右侧相邻元素是否已使用,因此1 具有索引6,它是输入数组中的最后一个元素,这意味着我们只有检查索引5 的值是否已被使用,这可以通过查看已使用的索引记录数组来完成,如上所示usedIndexRerocd[5] = 0 所以1 可以考虑为最小总和。使用1后,状态更新为关注,

min sum = 0 + 1

Used index record array
..............................
| 0 | 0 | 1 | 0 | 0 | 0 | 1 |
..............................
  0   1   2   3   4   5   6   

而不是迭代到索引1 处的下一个元素4 但这不能考虑,因为索引0 处的元素已被使用,元素7, 9 也会发生同样的情况,因为这些位于索引5, 3分别与使用的元素相邻。
最后在 index = 0 处迭代到 9 并通过查看使用的索引记录数组 usedIndexRecordArray[1] = 0 这就是为什么 9 可以包含在最小总和中并达到以下最终状态,

min sum = 0 + 1 + 9

Used index record array
..............................
| 1 | 0 | 1 | 0 | 0 | 0 | 1 |
..............................
  0   1   2   3   4   5   6   

终于minimum sum = 10,

最坏情况场景之一,输入数组已经排序,然后至少必须迭代 2*k - 1 元素以找到非相邻 k 元素的最小总和,如下所示
输入数组 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]k = 4
然后应考虑以下突出显示的元素以获得最小总和,
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

注意:您必须包括所有输入验证,就像验证之一是,如果你想找到k 非相邻元素的最小总和,那么输入应该至少有2*k - 1 元素。我不包括这些验证,因为我知道问题的所有输入约束。

#include <iostream>

#include <vector>
#include <algorithm>

using std::cout;

long minSumOfNonAdjacentKEntries(std::size_t k, const std::vector<int>& arr){

    if(arr.size() < 2){
        return 0;
    }

    std::vector<std::pair<int, std::size_t>> numIndexArr;
    numIndexArr.reserve(arr.size());

    for(std::size_t i = 0, arrSize = arr.size(); i < arrSize; ++i){

        numIndexArr.emplace_back(arr[i], i);
    }

    std::sort(numIndexArr.begin(), numIndexArr.end(), [](const std::pair<int, std::size_t>& a,
              const std::pair<int, std::size_t>& b){return a.first < b.first;});

    long minSum = numIndexArr.front().first;

    std::size_t elementCount = 1;
    std::size_t lastIndex = arr.size() - 1;

    std::vector<bool> usedIndexRecord(arr.size(), false);

    usedIndexRecord[numIndexArr.front().second] = true;

    for(std::vector<std::pair<int, std::size_t>>::const_iterator it = numIndexArr.cbegin() + 1,
        endIt = numIndexArr.cend(); elementCount < k && endIt != it; ++it){

        bool leftAdjacentElementUsed = (0 == it->second) ? false : usedIndexRecord[it->second - 1];
        bool rightAdjacentElementUsed = (lastIndex == it->second) ? false : usedIndexRecord[it->second + 1];

        if(!leftAdjacentElementUsed && !rightAdjacentElementUsed){

            minSum += it->first;

            ++elementCount;
            usedIndexRecord[it->second] = true;
        }
    }

    return minSum;
}

int main(){

    cout<< "k = 2, [355, 46, 203, 140, 28], min sum = "<< minSumOfNonAdjacentKEntries(2, {355, 46, 203, 140, 28})
        << '\n';

    cout<< "k = 3, [9, 4, 0, 9, 14, 7, 1], min sum = "<< minSumOfNonAdjacentKEntries(3, {9, 4, 0, 9, 14, 7, 1})
        << '\n';
}

输出:

k = 2, [355, 46, 203, 140, 28], min sum = 74
k = 3, [9, 4, 0, 9, 14, 7, 1], min sum = 10

【讨论】:

    【解决方案2】:

    这一行:

    if (i < 0 || !k) return 0;
    

    如果 k 为 0,您可能应该返回 return 0。但如果 i &lt; 0 或如果数组的有效长度小于 k,您可能需要返回一个 VERY LARGE 值,以便求和结果高于任何有效的解决方案。

    在我的解决方案中,当递归到无效子集或 k 超过剩余长度时,我将递归返回 INT_MAX 作为 long long。

    与任何这些动态编程和递归问题一样,结果缓存可以帮助您避免重复相同的递归搜索。对于非常大的输入集,这将使处理速度提高几个数量级。

    这是我的解决方案。

    #include <iostream>
    #include <vector>
    #include <unordered_map>
    #include <algorithm>
    
    using namespace std;
    
    // the "cache" is a map from offset to another map
    // that tracks k to a final result.
    typedef unordered_map<size_t, unordered_map<size_t, long long>> CACHE_MAP;
    
    bool get_cache_result(const CACHE_MAP& cache, size_t offset, size_t k, long long& result);
    void insert_into_cache(CACHE_MAP& cache, size_t offset, size_t k, long long result);
    
    
    long long minimal_k_impl(const vector<int>& nums, size_t offset, size_t k, CACHE_MAP& cache)
    {
        long long result = INT_MAX;
        size_t len = nums.size();
    
        if (k == 0)
        {
            return 0;
        }
    
        if (offset >= len)
        {
            return INT_MAX; // exceeded array boundary, return INT_MAX
        }
    
        size_t effective_length = len - offset;
    
        // If we have more k than remaining elements, return INT_MAX to indicate
        // that this recursion is invalid
        // you might be able to reduce to checking (effective_length/2+1 < k)
        if ( (effective_length < k)  ||  ((effective_length == k) && (k != 1)) )
        {
            return INT_MAX;
        }
    
        if (get_cache_result(cache, offset, k, result))
        {
            return result;
        }
    
        long long sum1 = nums[offset] + minimal_k_impl(nums, offset + 2, k - 1, cache);
        long long sum2 = minimal_k_impl(nums, offset + 1, k, cache);
        result = std::min(sum1, sum2);
    
        insert_into_cache(cache, offset, k, result);
    
        return result;
    }
    
    long long minimal_k(const vector<int>& nums, size_t k)
    {
        CACHE_MAP cache;
        return minimal_k_impl(nums, 0, k, cache);
    }
    
    
    bool get_cache_result(const CACHE_MAP& cache, size_t offset, size_t k, long long& result)
    {
        // effectively this code does this:
        // result = cache[offset][k]
    
        bool ret = false;
        auto itor1 = cache.find(offset);
        if (itor1 != cache.end())
        {
            auto& inner_map = itor1->second;
            auto itor2 = inner_map.find(k);
            if (itor2 != inner_map.end())
            {
                ret = true;
                result = itor2->second;
            }
        }
        return ret;
    }
    
    void insert_into_cache(CACHE_MAP& cache, size_t offset, size_t k, long long result)
    {
        cache[offset][k] = result;
    }
    
    
    int main()
    {
        vector<int> nums1{ 355, 46, 203, 140, 28 };
        vector<int> nums2{ 9, 4, 0, 9, 14, 7, 1 };
        vector<int> nums3{8,6,7,5,3,0,9,5,5,5,1,2,9,-10};
    
        long long result = minimal_k(nums1,  2);
        std::cout << result << std::endl;
    
        result = minimal_k(nums2, 3);
        std::cout << result << std::endl;
    
        result = minimal_k(nums3, 3);
        std::cout << result << std::endl;
    
    
        return 0;
    }
    

    【讨论】:

    • 如果你真的解释了你的算法对读者来说会容易得多
    • 我清理了代码并添加了几个cmets。
    • 它适用于我拥有的所有测试用例,“你可能需要返回一个非常大的”我认为这是越界时的关键,我的解决方案中还有一个微妙的错误if statement "(nums[i] == 0)" where i return nums[0],谢谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-03-12
    • 1970-01-01
    • 1970-01-01
    • 2010-09-23
    • 2019-10-26
    • 2022-12-12
    • 2021-01-16
    相关资源
    最近更新 更多