Swift - 特定长度的子数组

Posted

技术标签:

【中文标题】Swift - 特定长度的子数组【英文标题】:Swift - Subarrays of specific length 【发布时间】:2019-03-19 14:46:11 【问题描述】:

我有一个数组,可以说[1, 2, 3, 4]。我必须检查一个元素或元素的任何组合是否总和为特定数字。

示例

    51 + 4 = 52 + 3 = 561 + 2 + 3 = 62 + 4 = 6

可能是创建数组的幂集as in this answer,然后循环遍历它。但这不是一个好主意,因为如果元素的数量(即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]]

现在的问题是,我将如何创建像上面这样的特定长度的子数组/子集?

【问题讨论】:

n 的最大值是多少? 那是“subset sum problem“,通过动态规划更有效地解决,而不是生成所有子集。 @MartinR 没错,这就是我要指出的。但 PO 去了子集方向。 另请注意,生成大小为 0、1、2、3、... N 的所有子集与生成幂集相同 举个例子:一个 100 元素的数组有 100891344545564193334812497256 个大小为 50 的子数组。 【参考方案1】:

这是子集和问题的变体,或更一般地说,Knapsack problem。以下解决方案假设:

初始数组的所有元素都是严格正数, 初始数组可能包含重复元素, 如果无法求和,则输出为空数组。

让我们从一个示例开始:让我们创建一个动态表,我们将在其中尝试通过添加来自[1, 2, 3, 4] 的元素来找到获得5 的所有方法:

在此表中,行表示数组的元素,按升序排列,加上0。列从0 到总和5

在每个单元格中,我们都会问自己,是否可以通过添加当前行和前一行的一个或多个标题来获得该列的标题。

解决方案的数量是其中包含true 的单元格的数量。在这种情况下,有两种解决方案:

1)

绿色单元格是true,因此当前行是解决方案中的最后一个元素。在这种情况下,3 是解决方案的一部分。因此,寻找总和为 5 的子数组的问题变成了寻找总和为5 - 3 的子数组。这是2。这由紫色 arrow 1 表示:向左走五列,向上走 1 行。

arrow 2 中,我们寻找可以得到2 部分和的子集。在这种情况下,我们得到两个感谢 2 元素。所以跟着arrow 2,我们往上走一排,往左边走两排。

使用arrow 3,我们到达第一列的第一个单元格,对应于5 - 3 - 2,即0

2)

我们可以采取的另一条路径从红细胞开始:

如你所见,[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 的贡献 here。 this video 详细解释了动态表是如何构建的。

【讨论】:

【参考方案2】:

这是一种子集和问题。

对于正整数,它可以使用复杂的动态规划来解决O(length * sum)

制作长度为(sum + 1)的数组A,用零填充,设置A[0] = 1

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

如果A[sum] 不为零并且毕竟包含所需步数的组合,则此总和可能由数组元素组成。

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

【讨论】:

以上是关于Swift - 特定长度的子数组的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode#209-长度最小的子数组

LeetCode 日记 209. 长度最小的子数组

209. 长度最小的子数组

209-长度最小的子数组

长度最小的子数组

长度最小的子数组