百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

【leetcode】ksum 求符合条件的 k 个数

toyiye 2024-06-21 12:04 9 浏览 0 评论

1. Two Sum 两数之和

题目

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

示例:

给定 nums = [2, 7, 11, 15], target = 9


因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]

思路

最简单粗暴的一个双层循环:

(1)遍历第一层数组 nums[i]

(2)遍历第二层数组 nums[j],如果 nums[i] + nums[j] == t,符合。

基础解法

java 实现:

public int[] twoSumBasic(int[] nums, int target) {
    for(int i = 0; i < nums.length; i++) {
        for(int j = 0; j < nums.length; j++) {
            // 每个元素只使用一次
            if(i == j) {
                continue;
            }
            if(nums[i] + nums[j] == target) {
                return new int[]{i, j};
            }
        }
    }
    // 实际每个都有答案,不应该都到这里。
    return null;
}

性能分析:

可谓惨不忍睹,为什么呢?

是否有比较好的优化思路?

Runtime: 167 ms, faster than 5.01% of Java online submissions for Two Sum.
Memory Usage: 41.3 MB, less than 10.92% of Java online submissions for Two Sum.

优化解法

优化思路:

借助 HashMap 数据结构,将原本 O(n) 的遍历,降低为 O(1) 的查询。

java 实现如下:

实现时注意 HashMap 的扩容问题,此处直接指定为和数组一样大。

public int[] twoSum(int[] nums, int target) {
    int[] result = new int[2];
    final int length  = nums.length;
    Map<Integer, Integer> map = new HashMap<>(length);
    for(int i = 0; i < length; i++) {
        int num = nums[i];
        int targetKey = target - num;
        if (map.containsKey(targetKey)) {
            result[1] = i;
            result[0] = map.get(targetKey);
            return result;
        }
        map.put(num, i);
    }
    return result;
}

效果:

Runtime: 1 ms, faster than 99.93% of Java online submissions for Two Sum.
Memory Usage: 39.5 MB, less than 69.35% of Java online submissions for Two Sum.

15. 3Sum 三数之和

结束了第一道开胃菜之后,我们来看看第二道菜。

题目

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

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

示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],


满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

粗暴解法

最简单的思路,我们直接来一个三层循环:

public List<List<Integer>> threeSum(int[] nums) {
    if(nums.length < 3) {
        return Collections.emptyList();
    }
    List<List<Integer>> results = new ArrayList<>();
    Set<String> stringSet = new HashSet<>();
    for(int i = 0; i < nums.length-2; i+=3) {
        for(int j = i+1; j < nums.length-1; j++) {
            for(int k = j+1; k < nums.length; k++) {
                if((nums[i] + nums[j]+nums[k]) == 0) {
                    List<Integer> list = Arrays.asList(nums[i], nums[j], nums[k]);
                    // 排序保证结果不重复
                    Collections.sort(list);
                    String string = list.toString();
                    if(stringSet.contains(string)) {
                        continue;
                    }
                    stringSet.add(string);
                    results.add(list);
                }
            }
        }
    }
    return results;
}

?执行结果

很不幸,这个是不通过的。会执行超时,因为执行的比较慢。

优化解法

优化思路:

(1)对原始数组进行排序,保证可以使用双指针法

(2)固定 1 个元素。剩余的两个元素采用双指针的方式。初始化时,一个在最左边,一个在最右边。然后不断调整位置,直到符合条件为止。

(3)不可以包含重复的三元组,要同时跳过重复的信息。

java 实现:

public List<List<Integer>> threeSum(int[] nums) {
    //1. 排序
    Arrays.sort(nums);
    List<List<Integer>> results = new ArrayList<>(nums.length);
    //2. 双指针
    for(int i = 0; i < nums.length; i++) {
        int num = nums[i];
        if(num > 0) {
            return results;
        }
        if(i > 0 && nums[i] == nums[i-1]) {
            continue;
        }
        int l = i+1;
        int r = nums.length-1;
        while (l < r) {
            int sum = num + nums[l] + nums[r];
            if(sum < 0) {
                l++;
            } else if(sum > 0) {
                r--;
            } else {
                List<Integer> integers = new ArrayList<>(3);
                integers.add(num);
                integers.add(nums[l]);
                integers.add(nums[r]);
                results.add(integers);
                // 跳过重复的元素
                while(l < r && nums[l+1] == nums[l]) {
                    l++;
                }
                while (l < r && nums[r-1] == nums[r]) {
                    r--;
                }
                l++;
                r--;
            }
        }
    }
    return results;
}

性能:

速度超过 99.87 的用户提交,还不错。

16. 最接近的三数之和

给你一个长度为 n 的整数数组 nums 和 一个目标值 target。

请你从 nums 中选出三个整数,使它们的和与 target 最接近。

返回这三个数的和。

假定每组输入只存在恰好一个解。

示例 1:

输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

示例 2:

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

提示:

3 <= nums.length <= 1000

-1000 <= nums[i] <= 1000

-10^4 <= target <= 10^4

V1-双指针法

思路

针对多个数之和,对无序的数组进行一次排序,可以大大降低后续的时间复杂度。

我们通过两个指针,l r 分别计算每一次的差值,找到最小的差异。

当然,等于肯定最小,直接返回即可。

java 实现

    /**
     * 思路:
     *
     * 能否继续借助排序+双指针?
     *
     * 1. 如果相等,则直接返回
     * 2. 否则需要保存最接近的一个值。
     *
     * 3. 如果差异越来越大,则直接停止。
     *
     * 使用 abs
     *
     * @param nums 数字
     * @param target 目标值
     * @return 结果
     * @since v1
     */
    public int threeSumClosest(int[] nums, int target) {
        // 最小
        if(nums.length == 3) {
            return nums[0]+nums[1]+nums[2];
        }


        //1. 排序
        Arrays.sort(nums);


        //2. 双指针
        int diff = Integer.MAX_VALUE;
        for(int i = 0; i < nums.length; i++) {
            int l = i+1;
            int r = nums.length-1;


            // 去重 i,l,r
            while (l < r) {
                // 此处可以直接返回
                int sum = nums[i] + nums[l] + nums[r];
                int loopDiff = sum-target;


                if(sum == target) {
                    return target;
                } else if(loopDiff < 0) {
                    // 偏小
                    l++;


                    if(Math.abs(loopDiff) < Math.abs(diff)) {
                        diff = loopDiff;
                    }
                } else {
                    // 偏大
                    r--;


                    if(Math.abs(loopDiff)  < Math.abs(diff)) {
                        diff = loopDiff;
                    }
                }
            }
        }


        return target+diff;
    }

效果

Runtime: 5 ms, faster than 99.27% of Java online submissions for Container With Most Water.
Memory Usage: 39 MB, less than 100% of Java online submissions for Container With Most Water.

效果还是非常不错的。

V2-优化

思路

针对上面的算法进行优化。

能否继续借助排序+双指针?

1.

最大值如果依然小于原有差异,跳过

2.

最小值如果依然大于原有差异,跳过。

直接先把可能有结果的大概范围找到,然后再进一步细化,快速定位结果。

java 实现

    public int threeSumClosest(int[] nums, int target) {
        // 最小
        int result = nums[0] + nums[1] + nums[2];


        //1. 排序
        Arrays.sort(nums);


        //2. 双指针
        for(int i = 0; i < nums.length-2; i++) {
            int l = i+1;
            int r = nums.length-1;


            if (nums[i] + nums[i+1] + nums[i+2] - target >= Math.abs(target - result)) {
                break;  //Too big, can't get better result!
            }
            if (i < nums.length-3 && nums[i+1] + nums[nums.length-2] + nums[nums.length-1] < target) {
                continue; //Too small, skip
            }


            while (l < r) {
                // 此处可以直接返回
                int sum = nums[i] + nums[l] + nums[r];


                // 如果差异较小
                if(Math.abs(sum-target) < Math.abs(result-target)) {
                    result = sum;
                } else if(sum < target) {
                    // 偏小
                    l++;
                } else if(sum > target) {
                    r--;
                } else {
                    return sum;
                }
            }
        }


        return result;
    }

效果

Runtime: 1 ms, faster than 100.00% of Java online submissions for 3Sum Closest.
Memory Usage: 38.9 MB, less than 85.21% of Java online submissions for 3Sum Closest.

18. 4Sum 四数之和

常言道,有二有三必须有四。

这道题当然还有四个数之和的版本。

题目

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

注意:

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

示例:

给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。


满足要求的四元组集合为:
[
  [-1,  0, 0, 1],
  [-2, -1, 1, 2],
  [-2,  0, 0, 2]
]

解法

解题思路类似上述的 3 个数之和,区别是这次我们固定左边 2 个元素。

java 实现如下:

public List<List<Integer>> fourSum(int[] nums, int target) {
    // 大小可以优化
    List<List<Integer>> resultList = new ArrayList<>(nums.length);
    //1. 排序
    Arrays.sort(nums);
    //2. 类似双指针,固定左边2个元素。
    for(int i = 0; i < nums.length -3; i++) {
        // 跳过 i 的重复元素
        if(i > 0 && nums[i] == nums[i-1]) {
            continue;
        }
        for(int j = i+1; j < nums.length-2; j++) {
            // 确保跳过 j 的重复元素
            if(j > i+1 && nums[j] == nums[j-1])  {
                continue;
            }
            // 双指针法则
            int l = j+1;
            int r = nums.length-1;
            while (l < r) {
                int sum = nums[i]+nums[j]+nums[l]+nums[r];
                // 遍历完所有符合的信息
                if(sum < target) {
                    l++;
                } else if(sum > target) {
                    r--;
                } else {
                    List<Integer> result = Arrays.asList(nums[i], nums[j], nums[l], nums[r]);
                    resultList.add(result);
                    // 跳过重复的元素
                    while (l < r && nums[l] == nums[l+1]) {
                        l++;
                    }
                    while(l < r && nums[r] == nums[r-1]) {
                        r--;
                    }
                    l++;
                    r--;
                }
            }
        }
    }
    return resultList;
}

经过 3sum 之后,你对自己的实现信心十足。

效果对比打败了 49.10% 的用户。WTF!

其实答案也很简单,因为大家和你一样已经学会了这种解法。

那么,我们还能优化吗?

优化方法

思路:

主要是可以快速返回的一些优化

(1)对于长度小于 4 的数组,直接返回

(2)如果在当前循环中 max 小于 target,或者 min 大于 target 其实也可以快速跳过。

java 实现:

public List<List<Integer>> fourSum(int[] nums, int target) {
    //1.1 快速返回
    if(nums.length < 4) {
        return Collections.emptyList();
    }
    // 大小可以优化
    List<List<Integer>> resultList = new ArrayList<>(nums.length);
    //2. 排序
    Arrays.sort(nums);
    //1.2 范围判断
    final int length = nums.length;
    int min = nums[0] + nums[1] + nums[2] + nums[3];
    int max = nums[length-1] + nums[length-2] + nums[length-3] + nums[length-4];
    if(min > target || max < target) {
        return resultList;
    }
    //3. 类似双指针,固定左边2个元素。
    for(int i = 0; i < length -3; i++) {
        // 跳过 i 的重复元素
        if(i > 0 && nums[i] == nums[i-1]) {
            continue;
        }
        for(int j = i+1; j < length-2; j++) {
            // 确保跳过 j 的重复元素
            if(j > i+1 && nums[j] == nums[j-1])  {
                continue;
            }
            // 双指针法则
            int l = j+1;
            int r = length-1;
            // 快速跳过
            int minInner = nums[i] + nums[j] + nums[j+1] + nums[j+2];
            int maxInner = nums[i] + nums[j] + nums[r-1] + nums[r];
            if(minInner > target || maxInner < target) {
                continue;
            }
            while (l < r) {
                int sum = nums[i]+nums[j]+nums[l]+nums[r];
                // 遍历完所有符合的信息
                if(sum < target) {
                    l++;
                } else if(sum > target) {
                    r--;
                } else {
                    List<Integer> result = Arrays.asList(nums[i], nums[j], nums[l], nums[r]);
                    resultList.add(result);
                    // 跳过重复的元素
                    while (l < r && nums[l] == nums[l+1]) {
                        l++;
                    }
                    while(l < r && nums[r] == nums[r-1]) {
                        r--;
                    }
                    l++;
                    r--;
                }
            }
        }
    }
    return resultList;
}

效果:

超过了 92.32% 的提交,勉强过关。

Runtime: 5 ms, faster than 92.21% of Java online submissions for 4Sum.
Memory Usage: 39.9 MB, less than 56.17% of Java online submissions for 4Sum.

ksum

题目

经过了上面的 2sum/3sum/4sum 的洗礼,我们现在将这道题做下推广。

如何求 ksum?

思路

其实所有的这种解法都可以转换为如下的两个问题:

(1)sum 问题

(2)将 k sum 问题转换为 k-1 sum 问题

示例代码

/**
 * 对 k 个数进行求和
 * @param nums 数组
 * @param target 目标值
 * @param k k
 * @param index 下标
 * @return 结果类表
 * @since v1
 */
public List<List<Integer>> kSum(int[] nums, int target, int k, int index) {
    int len = nums.length;
    List<List<Integer>> resultList = new ArrayList<>();
    if (index >= len) {
        return resultList;
    }
    if (k == 2) {
        int i = index, j = len - 1;
        while (i < j) {
            //find a pair
            if (target - nums[i] == nums[j]) {
                List<Integer> temp = new ArrayList<>();
                temp.add(nums[i]);
                temp.add(target - nums[i]);
                resultList.add(temp);
                //skip duplication
                while (i < j && nums[i] == nums[i + 1]) {
                    i++;
                }
                while (i < j && nums[j - 1] == nums[j]) {
                    j--;
                }
                i++;
                j--;
                //move left bound
            } else if (target - nums[i] > nums[j]) {
                i++;
                //move right bound
            } else {
                j--;
            }
        }
    } else {
        for (int i = index; i < len - k + 1; i++) {
            //use current number to reduce ksum into k-1 sum
            List<List<Integer>> temp = kSum(nums, target - nums[i], k - 1, i + 1);
            if (temp != null) {
                //add previous results
                for (List<Integer> t : temp) {
                    t.add(0, nums[i]);
                }
                resultList.addAll(temp);
            }
            while (i < len - 1 && nums[i] == nums[i + 1]) {
                //skip duplicated numbers
                i++;
            }
        }
    }
    return resultList;
}

开源地址

为了便于大家学习,所有实现均已开源。欢迎 fork + star~

https://github.com/houbb/leetcode

参考资料

https://leetcode-cn.com/problems/two-sum

https://leetcode.com/problems/two-sum/discuss/3/Accepted-Java-O(n)-Solution

https://leetcode-cn.com/problems/3sum

https://leetcode-cn.com/problems/4sum

https://leetcode.com/problems/4sum/discuss/8609/My-solution-generalized-for-kSums-in-JAVA

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码