带你通关经典两数之和、三数之和...n树之和

117 阅读2分钟

两数之和

题目链接

概要:从整数数组之中找出两个元素,使得它们相加为目标值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)
}