Go语言力扣刷题-四数之和 | Go主题月

256 阅读2分钟

18. 四数之和

给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

注意:答案中不可以包含重复的四元组。

输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/4s… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路分析

类似三数之和问题,按照以下思路解决:

  1. 从小到大排序
  2. 两重循环枚举前两个数组,另外两个数字通过双指针来查找

实现代码

代码涉及到Go的知识包括:

  1. 数组的使用

    ans := make([][]int, 0)
    
  2. append函数的使用

    ans = append(ans, []int{nums[i], nums[j], nums[left], nums[right]})
    
  3. Go中的while循环:

    for condition {
    	// loop
    }
    

    实现完整代码如下:

/**
* 实现思路:
	1. 从小到大排序
	2. 两重循环枚举前两个数字,另外两个数字使用双指针来查找:一个指针指向开头,一个指针指向尾部
*/
func fourSum(nums []int, target int) [][]int {
	l := len(nums)
	if l <= 3 {
		return [][]int{}
	}

	sort.Ints(nums)
	// fmt.Println(nums)
	ans := make([][]int, 0)
	for i := 0; i < l-3; i++ {
		// fmt.Println("i = ", i)
		// if nums[i] > target {
		// 	break
		// }
		for j := i + 1; j < l-2; j++ {
			// fmt.Println("j = ", j)
			// if nums[i]+nums[j] > target {
			// 	break // 此时外边的循环也应该跳出了,怎么免除这些操作?
			// }
			for left, right := j+1, l-1; left < right; {
				// fmt.Println("i = ", i, ", j = ", j, ", left = ", left, ", right = ", right)
				// fmt.Println("left = ", left, ", right = ", right)
				sum := nums[i] + nums[j] + nums[left] + nums[right]
				// fmt.Println("i = ", i, ", j = ", j, ", left = ", left, ", right = ", right, ", sum = ", sum)
				if sum > target {
					// fmt.Println("和 大于 target, right左移")
					right--
					continue
				}

				if sum < target {
					// fmt.Println("和 小于 target, left右移")
					left++
					continue
					// sum = nums[i] + nums[j] + nums[left] + nums[right]
				}

				if sum == target {
					// fmt.Println([]int{nums[i], nums[j], nums[left], nums[right]})
					ans = append(ans, []int{nums[i], nums[j], nums[left], nums[right]})
					// 忽略下一个相同的数字
					for nums[left+1] == nums[left] && left+1 < right {
						// fmt.Println("判断left右侧数字是否相同")
						left++
					}
					left++
					continue
				}

				// 梳理逻辑后,发现此部分代码永远不会执行:忽略下一个相同的数字
				// for nums[left+1] == nums[left] && left+1 < right {
					// fmt.Println("判断left右侧数字是否相同")
				//	left++
				//}
				//for nums[right-1] == nums[right] && right-1 > left {
					// fmt.Println("判断right左侧数字是否相同")
				//	right--
				//}

			}
			for j+1 < l && nums[j+1] == nums[j] {
				j++
			}
		}
		for i+1 < l && nums[i+1] == nums[i] {
			i++
		}
	}
	return ans
}

总结

经过与官方代码对比后,发现有以下优化的点:

  1. 排序后,第一层循环中nums[ i ],如何判断数组后续是否可能存在其他三个数字与其之和为target?对于第二层循环中的nums[ j ]又该如何判断呢?

↑答↑:若nums[ i ]与数组最大的三个数,即数组最后的三个数,之和小于target,则该数组中不可能存在其他三个数字满足条件。同理,对于nums[ j ] 判断其与num[ i ] 以及数组最大的两个数,即数组最大两个数,之和小于target,则数组中不可能存在其他两个数字满足条件。

if i > 0 && nums[i] == nums[i-1] || nums[i]+nums[n-3]+nums[n-2]+nums[n-1] < target {
    continue
}
  1. 对于双指针部分,在查找到一个数字之后,如何快速移动双指针?

↑答↑:因为数组是有序的,因此在查找到一个解后,两个指针需要同时移动到下一个不等的值进行查找。

for left++; left < right && nums[left] == nums[left-1]; left++ {
}
for right--; left < right && nums[right] == nums[right+1]; right-- {
}