LeetCode 15. 3Sum(三数之和)

271 阅读3分钟

leetcode.com/problems/3s…

Discuss:www.cnblogs.com/grandyang/p…

Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0.

Notice that the solution set must not contain duplicate triplets.

Example 1:

Input: nums = [-1,0,1,2,-1,-4]
Output: [[-1,-1,2],[-1,0,1]]

Example 2:

Input: nums = []
Output: []

Example 3:

Input: nums = [0]
Output: []

Constraints:

  • 0 <= nums.length <= 3000

  • -105 <= nums[i] <= 105

解法一:

暴力求解,会超时。

class Solution {
    fun threeSum(nums: IntArray): List<List<Int>> {
        if (nums.size < 3) {
            return mutableListOf()
        }

        val result = mutableListOf<List<Int>>()
        var isOnceAllZero = false

        for (i in 0..nums.size - 3) {
            val x = nums[i]
            for (j in i + 1..nums.size - 2) {
                val y = nums[j]
                for (k in j + 1 until nums.size) {
                    val z = nums[k]
                    if (x + y + z == 0) {
                        val item = mutableListOf<Int>()
                        item.add(x)
                        item.add(y)
                        item.add(z)

                        var isAdd = true
                        if (x == y && y == z && !isOnceAllZero) {
                            isOnceAllZero = true
                            result.add(item)
                            continue
                        }
                        if (result.isNotEmpty()) {
                            kotlin.run breaking@{
                                result.forEach {
                                    if (it.containsAll(item)) {
                                        isAdd = false
                                        return@breaking
                                    }
                                }
                            }
                        }
                        if (isAdd) {
                            result.add(item)
                        }
                    }
                }
            }
        }
        return result
    }
}

解法二:

利用 Map 存储下标与数组对应下标的值,消除一层循环,但这样还是会超时。

class Solution {
    fun threeSum(nums: IntArray): List<List<Int>> {
        if (nums.size < 3) {
            return mutableListOf()
        }

        val result = mutableListOf<List<Int>>()
        val map = mutableMapOf<Int,Int>()
        var isOnceAllZero = false

        for (index in nums.indices){
            map[index] = nums[index]
        }

        for (i in 0..nums.size - 3) {
            val x = nums[i]
            for (j in i + 1..nums.size - 2) {
                val y = nums[j]

                val z = Math.negateExact((x+y))

                if (map.containsValue(z)&& map.values.lastIndexOf(z)>j){
                    val item = mutableListOf<Int>()
                    item.add(x)
                    item.add(y)
                    item.add(z)

                    var isAdd = true
                    if (x == y && y == z && !isOnceAllZero) {
                        isOnceAllZero = true
                        result.add(item)
                        continue
                    }
                    if (result.isNotEmpty()) {
                        kotlin.run breaking@{
                            result.forEach {
                                if (it.containsAll(item)) {
                                    isAdd = false
                                    return@breaking
                                }
                            }
                        }
                    }
                    if (isAdd) {
                        result.add(item)
                    }
                }
            }
        }
        return result
    }
}

解法三:

对原数组进行排序,然后开始遍历排序后的数组,这里注意不是遍历到最后一个停止,而是到倒数第三个就可以了。这里可以先做个剪枝优化,就是当遍历到正数的时候就 break,为什么呢,因为数组现在是有序的了,如果第一个要 fix 的数就是正数了,则后面的数字就都是正数,就永远不会出现和为 0 的情况了。然后还要加上重复就跳过的处理,处理方法是从第二个数开始,如果和前面的数字相等,就跳过,因为不想把相同的数字 fix 两次。对于遍历到的数,用 0 减去这个 fix 的数得到一个 target,然后只需要再之后找到两个数之和等于 target 即可。用两个指针分别指向 fix 数字之后开始的数组首尾两个数,如果两个数和正好为 target,则将这两个数和 fix 的数一起存入结果中。然后就是跳过重复数字的步骤了,两个指针都需要检测重复数字。如果两数之和小于 target,则将左边那个指针i右移一位,使得指向的数字增大一些。同理,如果两数之和大于 target,则将右边那个指针j左移一位,使得指向的数字减小一些。

class Solution {
    fun threeSum(nums: IntArray): List<List<Int>> {
        if (nums.size < 3) {
            return mutableListOf()
        }

        val result = mutableListOf<List<Int>>()

        nums.sort()
        if (nums.first() > 0 || nums.last() < 0) {
            return mutableListOf()
        }

        for (k in 0..nums.size - 3) {
            if (nums[k] > 0) {
                break
            }
            if (k > 0 && nums[k] == nums[k - 1]) {
                continue
            }
            val target = 0 - nums[k]
            var i = k + 1
            var j = nums.size - 1
            while (i < j) {
                when {
                    nums[i] + nums[j] == target -> {
                        val item = mutableListOf<Int>()
                        item.add(nums[k])
                        item.add(nums[i])
                        item.add(nums[j])
                        result.add(item)
                        while (i < j && nums[i] == nums[i + 1]) {
                            ++i
                        }
                        while (i < j && nums[j] == nums[j - 1]) {
                            --j
                        }
                        ++i
                        --j
                    }
                    nums[i] + nums[j] < target -> {
                        ++i
                    }
                    else -> {
                        --j
                    }
                }
            }
        }
        return result
    }
}

解法四:

也可以利用 Set 的不能包含重复项的特点来防止重复项的产生,那么就不需要检测数字是否被 fix 过两次。

class Solution {
    fun threeSum(nums: IntArray): List<List<Int>> {
        if (nums.size < 3) {
            return mutableListOf()
        }

        val result = mutableSetOf<List<Int>>()

        nums.sort()
        if (nums.first() > 0 || nums.last() < 0) {
            return mutableListOf()
        }

        for (k in 0..nums.size - 3) {
            if (nums[k] > 0) {
                break
            }
            val target = 0 - nums[k]
            var i = k + 1
            var j = nums.size - 1
            while (i < j) {
                when {
                    nums[i] + nums[j] == target -> {
                        val item = mutableListOf<Int>()
                        item.add(nums[k])
                        item.add(nums[i])
                        item.add(nums[j])
                        result.add(item)
                        while (i < j && nums[i] == nums[i + 1]) {
                            ++i
                        }
                        while (i < j && nums[j] == nums[j - 1]) {
                            --j
                        }
                        ++i
                        --j
                    }
                    nums[i] + nums[j] < target -> {
                        ++i
                    }
                    else -> {
                        --j
                    }
                }
            }
        }
        return result.toList()
    }
}