两数之和
概要:从整数数组之中找出两个元素,使得它们相加为目标值target,返回的是这两个元素的索引,这里很重要,下面的三数之和以及四树之和这些就不是求索引了。
注意:题目说了只会存在一个有效答案
思路:首先,这道题需要用到哈希,为什么呢,要快速判断我们想要的元素是否有出现过,哈希可以O(1)的时间做到。然后结合题目需要我们返回索引,我们可以使用key-value的Map结构,key存放元素,value存放索引。
代码:
func twoSum(nums []int, target int) []int {
m := make(map[int]int)
for index, val := range nums {
if preIndex, ok := m[target-val]; ok {
return []int{preIndex, index}
} else {
m[val] = index
}
}
return []int{}
}
O(n)解决,具体算法的实现就是我们去遍历数组,每取到一个数的时候,然后我们去判断map里面是否有nums[target - 这个数] ,如果有的话,我们就找到结果了,返回这个数的索引和map里面对应value的索引。如果没有的话,我们就把它放进哈希map中。
三数之和
概要: 从整数数组之和找到三个数,使得这三个数相加为0,返回的是对应的数组元素而不是索引了
注意: 返回的数组不可以包含重复的三元组
思路: 这道题不要求返回索引了,我们依然用哈希去做吗,哈希做的方法很复杂。哈希做法具体可以看这里哈希做法。
耐心看下去,我先引入这样的两数之和。方便过渡理解。
不要求返回索引的两数之和就简单多了,先进行数组排序,然后用双指针从两边到中间走。这里再加点东西,因为三数之和要求的答案就可能不止一个了,就是两数之和答案只有一个的条件去掉。也就是最后找到的时候不要直接return,要继续往中间寻找,直到左指针和右指针相等。但是要注意结果的去重,也就是当我们left, right相加起来为target的时候,left不能再是这个元素了,向右判断,同理right也要向左进行判断进行结果的去重。
核心代码如下:
sort.Ints(nums)
lo := start
hi := len(nums) - 1
res := make([][]int, 0)
for lo < hi {
sum := nums[lo] + nums[hi]
left, right := nums[lo], nums[hi]
if sum < target {
for lo < hi && nums[lo] == left {
lo++
}
} else if sum > target {
for lo < hi && nums[hi] == right {
hi--
}
} else {
res = append(res, []int{nums[lo], nums[hi]})
for lo < hi && nums[lo] == left {
lo++
}
for lo < hi && nums[hi] == right {
hi--
}
}
}
下面回到我们的三数之和,有了这个两数之和再来看三数之和就方便了,后面的四数之和也是如此。
首先,我们可以思考第一个数可以是什么呢?当然,数组里的每个元素都有可能,所以我们可以通过一个for循环去处理,在for循环确定第一个数的过程,那另外两个数呢?就可以用到我们前面写的两数之和改编版代码
func twoSum(nums []int, start int, target int) [][]int {
lo := start
hi := len(nums) - 1
res := make([][]int, 0)
for lo < hi {
sum := nums[lo] + nums[hi]
left, right := nums[lo], nums[hi]
if sum < target {
for lo < hi && nums[lo] == left {
lo++
}
} else if sum > target {
for lo < hi && nums[hi] == right {
hi--
}
} else {
res = append(res, []int{nums[lo], nums[hi]})
for lo < hi && nums[lo] == left {
lo++
}
for lo < hi && nums[hi] == right {
hi--
}
}
}
return res
}
三数之和核心代码
func threeSum(nums []int) [][]int {
sort.Ints(nums)
slice := make([][]int, 0)
for i := 0; i < len(nums); i++ {
temp := twoSum(nums, i+1, target-nums[i])
for _, v2 := range temp {
v2 = append(v2, nums[i])
slice = append(slice, v2)
}
for i < len(nums)-1 && nums[i] == nums[i+1] {
i++
}
}
return slice
}
同样要考虑去重的问题,这里只需要保证第一个数不重复,后面两个数不重复可以通过twoSum函数来保证。
同理对于4Sum问题呢?一样的套路的:
func fourSum(nums []int) [][]int {
sort.Ints(nums)
slice := make([][]int, 0)
for i := 0; i < len(nums); i++ {
temp := threeSum(nums, i+1, target-nums[i])
for _, v2 := range temp {
v2 = append(v2, nums[i])
slice = append(slice, v2)
}
for i < len(nums)-1 && nums[i] == nums[i+1] {
i++
}
}
return slice
}
对于5Sum...100Sum...NSum问题了,这时候就需要通过一个递归去调用处理
func nSumTarget(nums []int, n int, start int, target int) [][]int {
sort.Ints(nums) // 这个排序函数放到核心代码函数中
result := make([][]int, 0)
sz := len(nums)
if sz < 2 || sz < n {
return result
}
if n == 2 {
lo, hi := start, sz-1
for lo < hi {
sum := nums[lo] + nums[hi]
left, right := nums[lo], nums[hi]
if sum < target {
for lo < hi && nums[lo] == left {
lo++
}
} else if sum > target {
for lo < hi && nums[hi] == right {
hi--
}
} else {
result = append(result, []int{nums[lo], nums[hi]})
for lo < hi && nums[lo] == left {
lo++
}
for lo < hi && nums[hi] == right {
hi--
}
}
}
} else {
for i := start; i < sz; i++ {
temp := nSumTarget(nums, n-1, i+1, target-nums[i])
for _, v2 := range temp {
v2 = append(v2, nums[i])
result = append(result, v2)
}
for i < len(nums)-1 && nums[i] == nums[i+1] {
i++
}
}
}
return result
}
当n==2的时候就是最开始的改版双指针法,n>2的时候就穷举第一个数字,递归调用,当到base case 就有结果了。然后就是代码中注释的地方————排序不要放到nSum函数里,避免每次递归都进行调用。
对于四数之和,直接调用nSum函数即可
func fourSum(nums []int, target int) [][]int {
sort.Ints(nums)
return nSumTarget(nums, 4 , 0 , target)
}