三数之和

14 阅读3分钟

题目

思路

说明:排序+双指针法

先排序,然后在当前元素的右侧的最左侧、最右侧设为左、右指针

注意,当前值固定时,左右指针遇到符合条件的元素后,仍然可能有其他符合条件的元素,但是需要跳过重复的元素,跳过重复的元素,只需要让左右指针都移动

错误思路:

判断是否有重复的,三个值相同,其实只要两个值相同就算重复,然后又是有顺序的,最小的两个值相同即可,用map存,map 使用前需要初始化

这种麻烦

//var myMap map[int][]int
//myMap使用前需要初始化
myMap := make(map[int][]int)

if !contains(myMap[i], left) {
    result = append(result, []int{nums[i], nums[left], nums[right]})
    myMap[i] = append(myMap[i], nums[left])
}

func contains(slice []int, val int) bool {
	for _, num := range slice {
		if num == val {
			return true
		}
	}
	return false
}

代码

func threeSum(nums []int) [][]int {
    if len(nums) == 0 {
        return [][]int{}
    }
    //先排序,再使用双指针
    sort.Ints(nums)
    var result [][]int
    i := 0

    //i右侧一定有两个元素
    for i < len(nums)-2 {
        left, right := i+1, len(nums)-1
        val := 0 - nums[i]
        for left < right {
            if nums[left]+nums[right] < val {
                left++
            } else if nums[left]+nums[right] > val {
                right--
            } else {
                result = append(result, []int{nums[i], nums[left], nums[right]})
                //不应该break,因为可能还能继续找的到,随便leftright变下,但是为了避免重复,left应该变到下个不相同的元素
                //break
                //这里左右指针都移动到不重复的元素
                curLeft := nums[left]
                left++
                for left < right && nums[left] == curLeft {
                    left++
                }

                curRight := nums[right]
                right--
                for left < right && nums[right] == curRight {
                    right--
                }
            }
        }
        //找到下个不重复的i
        curVal := nums[i]
        i++
        for i < len(nums)-2 && nums[i] == curVal {
            i++
        }
    }
    return result
}

变体1 最接近的三数之和

这个只返回最接近的值,且只有一个,更简单。也是排序+双指针

func threeSumClosest(nums []int, target int) int {
    //先排序,再使用双指针
    sort.Ints(nums)
    i := 0
    result := nums[0] + nums[1] + nums[len(nums)-1]
    //与目标值的插值
    cha := math.Abs(float64(result - target))
    //i右侧一定有两个元素
    for i < len(nums)-2 {
        left, right := i+1, len(nums)-1

        for left < right {
            newVal := nums[i] + nums[left] + nums[right]
            tmpCha := math.Abs(float64(newVal - target))
            if tmpCha < cha {
                result = newVal
                cha = tmpCha
            }

            if newVal < target {
                left++
            } else if newVal > target {
                right--
            } else {
                return newVal
            }
        }
        i++
    }
    return result
}

变体 2 四数之和

也是排序+双指针,没什么区别

固定 i j,右侧 left right 双指针,i j 的变化按照两层循环递增的方式

func fourSum(nums []int, target int) [][]int {
	if len(nums) < 4 {
		return [][]int{}
	}
	//先排序,再使用双指针
	sort.Ints(nums)
	var result [][]int
	i := 0

	for i < len(nums)-3 {
		j := i + 1
		for j < len(nums)-2 {
			left, right := j+1, len(nums)-1
			val := target - nums[j] - nums[i]
			for left < right {
				if nums[left]+nums[right] < val {
					left++
				} else if nums[left]+nums[right] > val {
					right--
				} else {
					result = append(result, []int{nums[i], nums[j], nums[left], nums[right]})
					//不应该break,因为可能还能继续找的到,随便leftright变下,但是为了避免重复,left应该变到下个不相同的元素
					//break
					//这里左右指针都移动到不重复的元素
					curLeft := nums[left]
					left++
					for left < right && nums[left] == curLeft {
						left++
					}

					curRight := nums[right]
					right--
					for left < right && nums[right] == curRight {
						right--
					}
				}
			}
			//找到下个不重复的i
			curVal := nums[j]
			j++
			for j < len(nums)-2 && nums[j] == curVal {
				j++
			}
		}
		curVal := nums[i]
		i++
		for i < len(nums)-3 && nums[i] == curVal {
			i++
		}
	}

	//i右侧一定有两个元素

	return result
}