Swift - 特定长度的子数组

5
我有一个数组,比如说[1, 2, 3, 4]。我需要检查一个元素或任意组合的元素是否相加等于特定数字。
举例:
1. 数字为51 + 4 = 52 + 3 = 5。 2. 数字为61 + 2 + 3 = 62 + 4 = 6
一种方法是创建数组的幂集(power-set),如这个答案所示,并循环迭代它。但这不是一个好主意,因为如果元素数量即n增加,幂集将变得内存密集。为此,更好的方法是创建特定长度的子集/子数组,并逐个迭代它们。
假设k是子数组的长度,则
- 当k = 2时,应该给我[[1、2],[1、3],[1、4],[2、3],[2、4],[3、4]] - 当k = 3时,应该给我[[1、2、3],[1、2、4],[2、3、4]] 现在的问题是,如何创建上述特定长度的子数组/子集?

1
这是“子集和问题”,使用动态规划更有效地解决,而不是生成所有子集。 - Martin R
2
请注意,生成大小为0、1、2、3、... N的所有子集与生成幂集是相同的 - Martin R
2
举个例子:一个有100个元素的数组有100891344545564193334812497256个大小为50的子数组。 - Martin R
  1. 所有元素都是正数。
  2. 数组的元素可能重复。
  3. 如果出现零,空数组可以作为答案。
- Adeel Miraj
@Carpsen90,正如Martin所提到的那样,子数组方法在数组大小增加时也不会很有效。子集和是正确的方法。我只是发现它难以实现。 - Adeel Miraj
显示剩余8条评论
2个回答

4
这是子集和问题的一种变体,或者更一般地说,是背包问题。以下解决方案假设:
  • 初始数组的所有元素均为严格正数,
  • 初始数组可能包含重复元素,
  • 如果无法达到总和,则输出为空数组。
让我们以一个例子开始:我们创建一个动态表,在其中尝试通过从[1, 2, 3, 4]中添加元素来找到获得5的所有方法:

dynamic table

在这个表格中,行代表数组的元素,按升序排列,加上0。列从0到总和5
在每个单元格中,我们问自己,通过添加当前和前几行的标题中的一个或多个,我们能否到达此列的标题。
解决方案的数量是在其中具有true的单元格的计数。在这种情况下,有两个解决方案: 1)

3_2

绿色的单元格是true,因此当前行是解决方案的最后一个元素。在这种情况下,3 是解的一部分。因此,找到子数组的和为5的问题,变成了找到子数组的和为5-3的问题。这是2。这由紫色箭头1表示:向左移动五列,向上移动一行。
箭头2中,我们寻找使部分和为2的子集。在这种情况下,我们得到了两个,归功于2元素。因此,遵循箭头2,我们向上移动一行,向左移动两个位置。
使用箭头3,我们到达第一列的第一个单元格,对应于5 - 3 - 2,即02) 我们可以从红色单元格开始另一条路径:

4_1

正如您所看到的,将[1、2、3、4]中取出5个元素的问题,变成了从[1、2、3]中取出1个元素的小问题,然后是从[1、2]中取出1个元素,最后是从`1`中取出1个元素的问题。

让我们创建并填充动态表格:

var dynamicTable: [[Bool]] =
    Array(repeating: Array(repeating: false, count: sum + 1),
          count: array.count + 1)

//All of the elements of the first column are true
//since we can always make a zero sum out of not elements
for i in 0...array.count {
    dynamicTable[i][0] = true
}

for row in 1...array.count {
    for column in 1...sum {
        if column < array[row - 1] {
            dynamicTable[row][column] = dynamicTable[row - 1][column]
        } else {
            if dynamicTable[row - 1][column] {
                dynamicTable[row][column] = true
            } else {
                dynamicTable[row][column] = dynamicTable[row - 1][column - array[row - 1]]
            }
        }
    }
}

让我们找到所有通往总和的路径:

var solutions = [[Int]]()

func getSubArraysWithTheSum(arr: [Int], row: Int, currentSum: Int, currentSolution: [Int]) {

    //The following block will be executed when
    //we reach the first cell in the first column
    if row == 0,
        currentSum == 0
    {
        solutions.append(currentSolution)
        //notice the return to exit the scope
        return
    }

    //The following block will be executed if
    //the current cell is NOT used to reach the sum
    if dynamicTable[row - 1][currentSum]
    {
        getSubArraysWithTheSum(arr: arr,
                               row: row - 1,
                               currentSum: currentSum,
                               currentSolution: currentSolution)
    }

    //The following block will be executed if
    //the current cell IS used to reach the sum
    if currentSum >= arr[row - 1],
        dynamicTable[row - 1][currentSum - arr[row - 1]]
    {
        getSubArraysWithTheSum(arr: arr,
                               row: row - 1,
                               currentSum: currentSum - arr[row - 1],
                               currentSolution: currentSolution + [arr[row - 1]])
    }
}

整个函数看起来像这样:
func getSubArrays(from array: [Int], withSum sum: Int) -> [[Int]] {

    guard array.allSatisfy({ $0 > 0 }) else {
        fatalError("All the elements of the array must be strictly positive")
    }

    guard array.count > 0, sum > 0 else {
        return []
    }

    var solutions = [[Int]]()
    var dynamicTable: [[Bool]] =
        Array(repeating: Array(repeating: false, count: sum + 1),
              count: array.count + 1)

    //All of the elements of the first column are true
    //since we can always make a zero sum out of not elements
    for i in 0...array.count {
        dynamicTable[i][0] = true
    }

    for row in 1...array.count {
        for column in 1...sum {
            if column < array[row - 1] {
                dynamicTable[row][column] = dynamicTable[row - 1][column]
            } else {
                if dynamicTable[row - 1][column] {
                    dynamicTable[row][column] = true
                } else {
                    dynamicTable[row][column] = dynamicTable[row - 1][column - array[row - 1]]
                }
            }
        }
    }

    func getSubArraysWithTheSum(arr: [Int], row: Int, currentSum: Int, currentSolution: [Int]) {

        //The following block will be executed when
        //we reach the first cell in the first column
        if row == 0,
            currentSum == 0
        {
            solutions.append(currentSolution)
            return
        }

        //The following block will be executed if
        //the current cell is NOT used to reach the sum
        if dynamicTable[row - 1][currentSum]
        {
            getSubArraysWithTheSum(arr: arr,
                                   row: row - 1,
                                   currentSum: currentSum,
                                   currentSolution: currentSolution)
        }

        //The following block will be executed if
        //the current cell IS used to reach the sum
        if currentSum >= arr[row - 1],
            dynamicTable[row - 1][currentSum - arr[row - 1]]
        {
            getSubArraysWithTheSum(arr: arr,
                                   row: row - 1,
                                   currentSum: currentSum - arr[row - 1],
                                   currentSolution: currentSolution + [arr[row - 1]])
        }
    }

    getSubArraysWithTheSum(arr: array, row: array.count , currentSum: sum, currentSolution: [])

    return solutions
}

以下是一些测试用例:

getSubArrays(from: [3, 1, 4, 2], withSum: 5)        //[[3, 2], [4, 1]]
getSubArrays(from: [1, 2, 2, 4], withSum: 3)        //[[2, 1], [2, 1]]
getSubArrays(from: [7, 3, 4, 5, 6, 1], withSum: 9)  //[[5, 3, 1], [5, 4], [6, 3]]
getSubArrays(from: [3], withSum: 3)                 //[[3]]
getSubArrays(from: [5], withSum: 10)                //[]
getSubArrays(from: [1, 2], withSum: 0)              //[]
getSubArrays(from: [], withSum: 4)                  //[]

这个解决方案是受到了Sumit Ghosh的贡献的启发,此处。如何构建动态表格的详细解释可以在这个视频中找到。

0

这是一种子集和问题。

对于正整数,可以使用动态规划解决,时间复杂度为O(length * sum)

创建长度为(sum + 1)的数组A,填充为零,并将A[0] = 1

对于每个源值v,从A[sum]向下遍历数组A,检查是否A[i-v]非零。如果是,则将A[i]单元格标记为A[i-v]+1(到达此单元格的步骤(值)数)。

如果A[sum]非零,并且包含所需步骤数量的组合,则该总和可能由数组元素组成。

如果您还需要跟踪元素,请在A[i]单元格中添加它们的值以检索子集。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接