将数组附加到矩阵时出现奇怪的行为。

huangapple go评论80阅读模式
英文:

Strange behavior when appending array to matrix

问题

我对切片的一些基本知识似乎有所遗漏,导致我的结果看起来很奇怪。

是的,这是来自LeetCode的一个问题。我在使用它来学习Go语言,因为我发现在新语言中解决算法问题对我很有帮助。我不需要算法的答案,也不需要知道如何修复算法。我只想知道为什么当我追加另一个值时,我的追加值会改变。

首先,这是我的代码:

type node struct {
    value int
    children []*node
}

func combinationSum(candidates []int, target int) [][]int {
    
    var output [][]int
    
    var acc []int
    
    combinationSum2(candidates, &output, target, acc, 0)
    
    return output
}

func combinationSum2(candidates []int, output *[][]int, target int, acc []int, sum int) {
    
    if sum == target {
        fmt.Println(acc)
        *output = append(*output, acc)
        fmt.Println(output)
        return
    }
    
    if sum > target {
        return
    }
    
    for i := 0; i < len(candidates); i++ {
        combinationSum2(candidates[i:], output, target, append(acc, candidates[i]), sum + candidates[i])
    }
    
}

我使用candidates=[2,3,5]和target=8来测试这段代码。

正确的输出应该是[[2,2,2,2],[2,3,3],[3,5]];然而,我的代码返回[[2,2,2,5],[2,3,3],[3,5]]。

有趣的是,当我在if语句中记录acc值和在追加acc值后记录output时,似乎在追加第二个数组后,我追加的值发生了变化。

acc = [2 2 2 2]
output = &[[2 2 2 2]]

acc = [2 3 3]
output = &[[2 2 2 5] [2 3 3]]

acc = [3 5]
output = &[[2 2 2 5] [2 3 3] [3 5]]

我尝试在本地运行这段代码,结果得到了相同奇怪的行为。是什么导致了这个问题?

英文:

I'm missing something fundamental about slices that's causes my result to end up looking strange.

Yes this is a question from leetcode. I was using it to learn go because I find that solving algorithms in new languages is helpful to me. I don't need the answer to the algorithm and I don't need to know how to fix the algorithm. I just want to know why my appended value changes when I append another value.

First off here's my code:

type node struct {
    value int
    children []*node
}

func combinationSum(candidates []int, target int) [][]int {
    
    var output [][]int
    
    var acc []int
    
    combinationSum2(candidates, &amp;output, target, acc, 0)
    
    return output
}

func combinationSum2(candidates []int, output *[][]int, target int, acc []int, sum int) {
    
    if(sum == target) {
        fmt.Println(acc)
        *output = append(*output, acc)
        fmt.Println(output)
        return
    }
    
    if(sum &gt; target) {
        return
    }
    
    for i := 0; i &lt; len(candidates); i++ {
        combinationSum2(candidates[i:], output, target, append(acc, candidates[i]), sum + candidates[i])
    }
    
}

I was testing this code with candidates=[2,3,5] and target=8

the correct output should be [[2,2,2,2],[2,3,3],[3,5]]; however, my code returns [[2,2,2,5],[2,3,3],[3,5]]

interestingly, when I log about the acc values in the if statement and the output after appending the acc value, it seems the the value that I appended changes after appending the second array.

acc = [2 2 2 2]
output = &amp;[[2 2 2 2]]

acc = [2 3 3]
output = &amp;[[2 2 2 5] [2 3 3]]

acc = [3 5]
output = &amp;[[2 2 2 5] [2 3 3] [3 5]]

I tried running this locally, and get the same strange behavior. What's causing this?

答案1

得分: 1

正如我在评论中写的那样,原始代码中的问题在于combinationSum2函数中的append使用方式。append被用来将当前的acc扩展为当前的candidate并传递给combinationSum2方法。

我在这个函数中添加了更多的日志输出:

func combinationSum2(candidates []int, output *[][]int, target int, acc []int, sum int) {
	if sum == target {
		fmt.Println("Acc:", acc)
		*output = append(*output, acc)
		fmt.Println("Output:", output)
		return
	}

	if sum > target {
		return
	}

	for i := 0; i < len(candidates); i++ {
		extendedAcc := append(acc, candidates[i])
		fmt.Printf("Extended: %v %p\n", extendedAcc, extendedAcc)
		combinationSum2(candidates[i:], output, target, extendedAcc, sum+candidates[i])
	}
}

并收到了以下结果(这只是前几行有趣的输出):

Extended: [2] 0x14000130008
Extended: [2 2] 0x14000130030
Extended: [2 2 2] 0x1400013c020
Extended: [2 2 2 2] 0x1400013c020
Acc: [2 2 2 2]
Output: &[[2 2 2 2]]
Extended: [2 2 2 3] 0x1400013c020
Extended: [2 2 2 5] 0x1400013c020
Extended: [2 2 3] 0x1400013c040
Extended: [2 2 3 3] 0x1400013c040

正如你所看到的,extendedAcc变量在添加到Output之后仍然具有相同的地址(它们在值后面以十六进制打印出来)。这个地址的最终值是[2 2 2 5],这也是你在Output中看到的。之所以不是[2 2 3 3],是因为append的内部工作原理。当当前数组中没有足够的空间时,它会创建一个新的数组并返回一个指向它的切片。当你比较extended切片的地址时,就能看到这种行为。

这是一个正常工作的解决方案:

package main

import "fmt"

type node struct {
	value    int
	children []*node
}

func combinationSum(candidates []int, target int) [][]int {
	var output [][]int
	var acc []int
	combinationSum2(candidates, &output, target, acc, 0)
	return output
}

func combinationSum2(candidates []int, output *[][]int, target int, acc []int, sum int) {
	if sum == target {
		fmt.Println(acc)
		*output = append(*output, acc)
		fmt.Println(output)
		return
	}

	if sum > target {
		return
	}

	for i := 0; i < len(candidates); i++ {
		currentAcc := make([]int, 0, len(acc)+1)
		currentAcc = append(currentAcc, acc...)
		currentAcc = append(currentAcc, candidates[i])
		combinationSum2(candidates[i:], output, target, currentAcc, sum+candidates[i])
	}
}

func main() {
	combinationSum([]int{2, 3, 5}, 8)
}

或者,combinationSum2函数可以像这样:

func combinationSum2(candidates []int, output *[][]int, target int, acc []int, sum int) {
	if sum == target {
		fmt.Println(acc)
		accCopy := make([]int, 0, len(acc))
		accCopy = append(accCopy, acc...)
		*output = append(*output, accCopy)
		fmt.Println(output)
		return
	}

	if sum > target {
		return
	}

	for i := 0; i < len(candidates); i++ {
		combinationSum2(candidates[i:], output, target, append(acc, candidates[i]), sum+candidates[i])
	}
}

在我看来,这种写法在一般情况下不太安全,但对于这个问题来说,它可以正常工作。

英文:

As i wrote in the comment the problem in original code is with append usage in combinationSum2 function. The append was used to pass current acc extended by current candidate to combinationSum2 method.
I've added more logging to this function

func combinationSum2(candidates []int, output *[][]int, target int, acc []int, sum int) {
	if sum == target {
		fmt.Println(&quot;Acc:&quot;, acc)
		*output = append(*output, acc)
		fmt.Println(&quot;Output:&quot;, output)
		return
	}

	if sum &gt; target {
		return
	}

	for i := 0; i &lt; len(candidates); i++ {
		extendedAcc := append(acc, candidates[i])
		fmt.Printf(&quot;Extended: %v %p\n&quot;, extendedAcc, extendedAcc)
		combinationSum2(candidates[i:], output, target, extendedAcc, sum+candidates[i])
	}
}

and received following result (this is only few first interesting lines)

Extended: [2] 0x14000130008
Extended: [2 2] 0x14000130030
Extended: [2 2 2] 0x1400013c020
Extended: [2 2 2 2] 0x1400013c020
Acc: [2 2 2 2]
Output: &amp;[[2 2 2 2]]
Extended: [2 2 2 3] 0x1400013c020
Extended: [2 2 2 5] 0x1400013c020
Extended: [2 2 3] 0x1400013c040
Extended: [2 2 3 3] 0x1400013c040

As you see the extendedAcc variable after it was added to Output still has the same address (they are printed as hex after the value). The final value for this address is [2 2 2 5] which is what you see in Output. The reason why it is not [2 2 3 3] is the result of how append works internally. In case when there is not enough space in current array it creates new one and returns a slice to it. And this behavior is visible when you compare the addresses of extended slice .

Here is properly working solution:

package main

import &quot;fmt&quot;

type node struct {
	value    int
	children []*node
}

func combinationSum(candidates []int, target int) [][]int {
	var output [][]int
	var acc []int
	combinationSum2(candidates, &amp;output, target, acc, 0)
	return output
}

func combinationSum2(candidates []int, output *[][]int, target int, acc []int, sum int) {
	if sum == target {
		fmt.Println(acc)
		*output = append(*output, acc)
		fmt.Println(output)
		return
	}

	if sum &gt; target {
		return
	}

	for i := 0; i &lt; len(candidates); i++ {
		currentAcc := make([]int, 0, len(acc) + 1)
		currentAcc = append(currentAcc, acc...)
		currentAcc = append(currentAcc, candidates[i])
		combinationSum2(candidates[i:], output, target, currentAcc, sum+candidates[i])
	}
}

func main() {
	combinationSum([]int{2, 3, 5}, 8)
}

Alternatively the combinationSum2 function may look like this:

func combinationSum2(candidates []int, output *[][]int, target int, acc []int, sum int) {
	if sum == target {
		fmt.Println(acc)
		accCopy := make([]int, 0, len(acc))
		accCopy = append(accCopy, acc...)
		*output = append(*output, accCopy)
		fmt.Println(output)
		return
	}

	if sum &gt; target {
		return
	}

	for i := 0; i &lt; len(candidates); i++ {
		combinationSum2(candidates[i:], output, target, append(acc, candidates[i]), sum+candidates[i])
	}
}

Which in my opinion is less safe in general but will work properly for this problem.

huangapple
  • 本文由 发表于 2021年8月2日 05:31:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/68614505.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定