Leetcode-talk04三数之和

给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
【示例】

给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

python:

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        # 初始化result
        result = []
        # 先把nums构造成字典
        counter = collections.Counter(nums)
        # 获取不重复的nums列表
        nums = sorted(counter)
    
        # 先考虑 [i, 0, -i] 的情况
        if counter.get(0, 0) >= 1:
            for i in nums:
                if i < 0:
                    if counter.get(-i, 0) > 0:
                        result.append([i, 0, -i])
            if counter.get(0, 0) >= 3:
                result.append([0, 0, 0])
            
        # 再考虑 [i, i, -2i] 的情况
        for i in nums:
            if counter.get(i, 0) >= 2 and counter.get(-2*i, 0) >= 1 and i != 0:
                result.append([i, i, -2*i])
    
        # 再考虑 [i, j, k] 为了避免重复, 规定 i < 0; j > 0; k大于0时比j大 k小于0时比i小
        negative = {k:v for k,v in counter.items() if k < 0}
        positive = {k:v for k,v in counter.items() if k > 0}
        for i in negative.keys():
            for j in positive.keys():
                k = - i - j
                if k < 0:
                    if k > i and k in negative:
                        result.append([i, j, k])
                if k > 0:
                    if k >j and k in positive:
                        result.append([i, j, k])
    
        return result

参考别人好的想法

热身
首先,先找一下它的简化版 2sum 来热热身。

最简单的想法就是把每两个都拿出来加一下,看看结果是不是我们想要的。但是直觉告诉我们,这样子并不高效。举一个很实际的例子就能明白。

比如这个周末你去参加线下相亲会,全场有且只有两个人才是真爱。于是我们每个人都要去找其他所有人聊天,去寻找 ta 是不是自己要找的另一半。每个人都要和每个人说话,这样时间复杂度很高,翻译成计算机的表示就是 O(n2)。

var twoSum = function(nums, target) {
      for (let i = 0; i < nums.length - 1; i++) { // 每个人
        for (let j = i + 1; j < nums.length; j++) { // 都去问其他的人
          if (nums[i]+nums[j] === target) {
            return [nums[i], nums[j]]
          }
        }
      }
    }

怎么样可以更高效一点?
这时候要引入哈希表,其实就是一个登记册,写上你的名字和你的要求。如果每个人都提前在主持人那里登记一遍,然后只要大家依次再报出自己名字,主持人就能够识别到,ta 就是你要找的人。

var twoSum = function(nums, target) {
      let res = {}
      for (let i = 0; i < nums.length; i++) { // 每个人登记自己想要配对的人,让主持人记住
        res[target - nums[i]] = nums[i]
      }
      for (let j = 0; j < nums.length; j++) { // 每个人再次报数的时候,主持人看一下名单里有没有他
        if (res[nums[j]] !== undefined) {
          return [nums[j], res[nums[j]]]
        }
      }
    }

three sum
好的,热身结束,现在来看 3sum 问题。这个问题复杂了一些,变成了一个人找两个人。

一个新的周末,你再次去现在参加了一个,额,这次不是相亲会,是参加一个街篮比赛,赛前当然要组队啦,现在要想一个方法找到队友。组队还有一个要求,就是队伍的平均实力要符合要求,比如菜鸟抱两个大腿,或者有王者带两个弱鸡。

我们先想一个保底的办法,再去慢慢优化。最简单的办法是,每个人都去依次拉上另一个人一起去找第三个人,这个时间复杂度是 O(n3)。

var threeSum = function(nums) {
      let res = []
      for (let i = 0; i < nums.length - 2; i++) { // 每个人
        for (let j = i + 1; j < nums.length - 1; j++) { // 依次拉上其他每个人
          for (let k = j + 1; k < nums.length; k++) { // 去问剩下的每个人
            if (nums[i] + nums[j] + nums[k] === 0) { // 我们是不是可以一起组队
              res.push([nums[i], nums[j], nums[k]])
            }
          }
        }
      }
      return res
    }

受到上题的启发,在凑齐两人以后,他们可以找主持人登记需求的第三人,而不需要在茫茫人海中去找队友。这样,我们就把问题优化成了每个人都要找其他每个人,即时间复杂度 O(n2),因为需要主持人记录数据,这里还有 O(n)的空间复杂度。

var threeSum = function(nums) {
        let res = []
        let hash = {}
        for (let i = 0; i < nums.length - 2; i++) { // 每个人
          for (let j = i + 1; j < nums.length - 1; j++) { // 依次拉上其他每个人
            if (hash[nums[j]] !== undefined) { // 已经有合适自己的两人组
              res.push([nums[j]].concat(hash[nums[j]]))
              hash[nums[j]] = undefined
            } else { // 没有合适自己的两人组
              let mark = 0 - nums[i] - nums[j]
              hash[mark] = [nums[i], nums[j]]
            }
          }
        }
        return res
    } // 示意代码 未AC

再优化
现在已经想到了可用的通用方案,根据题目的特点,看看还有哪里可以做一些优化。比如提前结束一些不可能的组合。

首先安排所有人按照顺序排队站好,这是一个需要花时间的操作,不过磨刀不误砍柴工,付出这个时间还是值得的。排序可以做到 O(nlogn),这是优于 O(n^2)的。

然后我们选择一个人做C位,既然是C位,那么就需要左右各有一个人。先选择队伍最左边(最小值)和队伍最右边(最大值)两个人,加上你,算一下总和。如果大于 0,说明实力太强了,就把就把右侧的人选调左一位,反之,则调整左边的人选,增强一下实力。当某边选到紧挨着你的人的时候,就意味着组队结束,以你为 C位的所有可能都已经尝试完毕了。

var threeSum = function (nums) {
      let res = []
      nums.sort((a, b) => a - b) // 先排个队,最左边是最弱(小)的,最右边是最强(大)的
      for (let i = 1; i < nums.length - 1; i++) { // C位人选
        let first = 0
        let last = nums.length - 1
        do {
          let result = nums[i] + nums[first] + nums[last]
          if (result === 0) { // 如果可以组队
            res.push([nums[i], nums[first], nums[last]])
          }
          if (result <= 0 && first < i) { // 实力太弱,把菜鸟那边右移一位
            while (nums[first] === nums[++first]); // 如果相等就跳过
          } else if (result > 0 && last > i) { // 实力太强,把大神那边右移一位
            while (nums[last] === nums[--last]);
          } else {
            break // 某一边已经没有人选了
          }
        } while (1) { }
      }
      return res
    } // 示意代码 未AC
发布了41 篇原创文章 · 获赞 0 · 访问量 511

猜你喜欢

转载自blog.csdn.net/qq_39199884/article/details/104649778