个人记录-LeetCode 15. 3Sum

本文探讨了LeetCode上的经典问题“三数之和”,即寻找数组中三个数相加等于0的所有唯一组合。提供了两种解决方案,一种是暴力解法,尽管能得出正确答案但效率较低;另一种更高效的解决方案受到了求最大水位问题的启发。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题:
Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.

Note: The solution set must not contain duplicate triplets.

For example, given array S = [-1, 0, 1, 2, -1, -4],

A solution set is:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

题目的要求是:
从给定的数组中找到不重复的组合。
每个组合包含3个元素,其和为0。

代码示例:
1、暴力解法

public class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();

        if (nums == null || nums.length < 3 ) {
            return result;
        }

        int len = nums.length;
        //现将给定数组从小到大排序
        Arrays.sort(nums);

        //最小值大于0或最大值小于0,显然是无法找到需要的组合
        if (nums[0] > 0 || nums[len-1] < 0) {
            return result;
        }

        //考虑到组合的和为0,因此整个组合不可能全部为负数或正数

        //用于存储负数,及负数出现的次数
        HashMap<Integer, Integer> negativeMap = new HashMap<>();
        ArrayList<Integer> negativeList = new ArrayList<>();

        //用于存储正数,及正数出现的次数
        HashMap<Integer, Integer> positiveMap = new HashMap<>();
        ArrayList<Integer> positiveList = new ArrayList<>();

        //存储是否出现0
        int numOfZero = 0;

        for (int num : nums) {
            if (num < 0) {
                addNumToCollections(num, negativeMap, negativeList);
            } else if (num > 0) {
                addNumToCollections(num, positiveMap, positiveList);
            } else {
                ++numOfZero;
            }
        }

        //出现3个以上的0,显然0 0 0的组合满足条件
        if (numOfZero >= 3) {
            List<Integer> answer = new ArrayList<>();
            for (int i = 0; i < 3; ++i) {
                answer.add(0);
            }
            result.add(answer);
        }

        //每次先取一个负数,再取一个正数
        for (int i = 0; i < negativeList.size(); ++i) {
            for (int j = 0; j < positiveList.size(); ++j) {
                int negative = negativeList.get(i);
                int positive = positiveList.get(j);
                //求出需要的第3个数
                int complete = 0 - negative - positive;

                boolean addFlag = false;

                //需要的数小于0,并且存在
                if (complete < 0 && negativeMap.containsKey(complete)) {
                    //需要的数与主动取出的负数不一致时,它的下标必须大于主动取得负数的下标,否则会出现重复的组合
                    if ((complete != negative && negativeList.indexOf(complete) > i)||
                            //需要的数与主动取出的负数一致,那么要求这个负数至少出现两次以上
                            (complete == negative && negativeMap.get(negative) > 1)) {
                        addFlag = true;
                    }
                //所需的数为正数,类似
                } else if (complete > 0 && positiveMap.containsKey(complete)){
                    if ((complete != positive && positiveList.indexOf(complete) > j)
                            || (complete == positive && positiveMap.get(positive) > 1)) {
                        addFlag = true;
                    }
                } else if (complete == 0 && numOfZero > 0){
                    addFlag = true;
                }

                if (addFlag) {
                    addAnswerToResult(negative, positive, complete, result);
                }
             }
        }

        return result;
    }

    private void addNumToCollections(int num, Map<Integer, Integer> map, List<Integer> list) {
        if (map.containsKey(num)) {
            map.replace(num, map.get(num), map.get(num)+1);
        } else {
            map.put(num, 1);
            list.add(num);
        }
    }

    private void addAnswerToResult(int negative, int positive, int complement, List<List<Integer>> result) {
        List<Integer> answer = new ArrayList<>();
        answer.add(negative);
        answer.add(positive);
        answer.add(complement);

        result.add(answer);
    }
}

这个方法可以得到正确结果,但是会超时。整个算法仅看for循环似乎复杂度是O( N2 ),但算上Map的查询,复杂度可能接近O( N3 )。

2、类似于求最大水位的问题
个人记录-LeetCode 11. Container With Most Water

public class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();

        if (nums == null || nums.length < 3) {
            return result;
        }

        int len = nums.length;
        Arrays.sort(nums);

        if (nums[0] > 0 || nums[len-1] < 0) {
            return result;
        }

        //轮询数组中的每一个数字,为了避免重复
        //找到在这个数字之后,出现的另外两个数字,构成组合
        for (int i = 0; i < len-2;) {
            int firstIndex = i + 1;
            int lastIndex = len - 1;
            while (firstIndex < lastIndex) {
                int curr = nums[firstIndex] + nums[lastIndex] + nums[i];
                //类似于求水位问题,当前的和大于0,降低高位的下标
                if (curr > 0) {
                    do {
                        --lastIndex;
                    //跳过重复的值
                    } while (firstIndex < lastIndex && nums[lastIndex] == nums[lastIndex+1]);
                //当前的和小于0,增加低位的下标
                } else if (curr < 0){
                    do {
                        ++firstIndex;
                    } while(firstIndex < lastIndex && nums[firstIndex] == nums[firstIndex-1]);
                } else {
                    addAnswerToResult(nums[firstIndex], nums[lastIndex], nums[i], result);
                    //等于0时,找出新的两个数,与当前数字构成组合
                    //低位增加、高位减小才有可能满足条件
                    do {
                        ++firstIndex;
                    }while(firstIndex < lastIndex && nums[firstIndex] == nums[firstIndex-1]);
                    do {
                        --lastIndex;
                    }while (firstIndex < lastIndex && nums[lastIndex] == nums[lastIndex+1]);
                }
            }
            //跳过重复的组合
            do{
                ++i;
            }while(i < nums.length && nums[i] == nums[i-1]);
        }

        return result;
    }

    private static void addAnswerToResult(int first, int last, int complement, List<List<Integer>> result) {
        List<Integer> answer = new ArrayList<>();
        answer.add(first);
        answer.add(last);
        answer.add(complement);

        result.add(answer);
    }
}

这个方法的复杂度是O( N2 )。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值