ARTS打卡第二周

Algorithm 做一道leetcode算法题

题目:三数之和

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

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

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
示例 2:

输入:nums = []
输出:[]
示例 3:

输入:nums = [0]
输出:[]

提示:
0 <= nums.length <= 3000
-105 <= nums[i] <= 105

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/3sum/

解题思路

首先想到的就是穷举法
从序列里依次取出三个不同位置的数,排序后放入一个set里
首先 用排序的方式,可以排除数字相同但是位置不同的情况
然后 将数据放到set里,set本身就会去重

解题代码

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {

        //定义返回结果集合
        List<List<Integer>> res = new ArrayList<List<Integer>>();

        //长度不足3的直接返回
        if(nums.length < 3) return res;

        //创建数字存放集合
        Set<List<Integer>> numSet = new HashSet<>();

        //穷举数组
        for(int i = 0; i < nums.length - 2; i++){

            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){

                        //符合三数之和为零的条件
                        Integer[] cell = {nums[i] , nums[j] , nums[k]};

                        //排序
                        Arrays.sort(cell);

                        //放入不可重复集合
                        numSet.add(Arrays.asList(cell));
                    }
                }
            }
        }

        //放入返回结果集合
        res.addAll(numSet);

        return res;
    }
}

结果提交的时候,执行超时,穷举法的效率还是太低了吧
理论上可以解决问题,但是执行效率太低

看了一下官方的题解
里边说,可以先对数组整体排序,然后保证每一层循环的数字都和上一次不同,就可以去掉很多不必要的选项
改进之后的代码

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {

        //定义返回结果集合
        List<List<Integer>> res = new ArrayList<List<Integer>>();

        //长度不足3的直接返回
        if(nums.length < 3) return res;

        //创建数字存放集合
        Set<List<Integer>> numSet = new HashSet<>();

         //排序
        Arrays.sort(nums);

        //存储各层循环的上一次数字
        Integer numi = null;
        Integer numj = null;
        Integer numk = null;

        //穷举数组
        for(int i = 0; i < nums.length - 2; i++){

            //跳过和上一次相同的数字
            if(numi != null && numi == nums[i]) continue;
            numi = nums[i];
            numj = null;

            for(int j = i + 1; j < nums.length - 1; j++){

                numk = null;
                //跳过和上一次相同的数字
                if(numj != null && numj == nums[j]) continue;
                numj = nums[j];

                for(int k = j + 1; k < nums.length; k++){
                    
                    //跳过和上一次相同的数字
                    if(numk != null && numk == nums[k]) continue;
                    numk = nums[k];
                    
                    int result = nums[i] + nums[j] + nums[k];
                    if( result > 0) break;

                    if( result == 0){

                        //符合三数之和为零的条件
                        Integer[] cell = {nums[i] , nums[j] , nums[k]};

                        //放入不可重复集合
                        numSet.add(Arrays.asList(cell));
                    }
                }
            }
        }

        //放入返回结果集合
        res.addAll(numSet);

        return res;
    }
}

结果还是超时,三层循环的效率还是太低

后面又说,可以使用双指针的方式

因为数组已经进行排序
a + b + c 中 b向后移,b一定会变大,然后,得到的结果就是 c 肯定比原来的小
也就是 c 一定比上一次满足结果的c要小

双指针的移动方式和如下图所示
ARTS打卡第二周_第1张图片
当两个指针相遇了,就意味着本次循环结束了
这样就比三重循环的效率高了很多

最后得到的代码

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {

        //创建结果集合
        List<List<Integer>> res = new ArrayList<>();

        //不满足的情况直接返回
        if (nums == null || nums.length < 3) return res;

        //排序
        Arrays.sort(nums);

        for (int i = 0; i < nums.length - 2; i++) {

            // 第一个数大于 0,后面的数都比它大,肯定不成立了
            if (nums[i] > 0) break; 

            // 去掉重复情况
            if (i > 0 && nums[i] == nums[i - 1]) continue; 

            //目标是找到后两个数的和为第一个数的相反数
            int target = -nums[i];

            //定义左右指针
            int left = i + 1, right = nums.length - 1;

            //左右指针不重合的时候进行循环
            while (left < right) {

                //如果找到目标
                if (nums[left] + nums[right] == target) {

                    //添加到集合
                    res.add(new ArrayList<>(Arrays.asList(nums[i], nums[left], nums[right])));

                    // 移动指针
                    left++; right--; 

                    //左指针移动到下一个不重复的数字
                    while (left < right && nums[left] == nums[left - 1]) left++;

                    //右指针移动到下一个不重复的位置
                    while (left < right && nums[right] == nums[right + 1]) right--;

                } else if (nums[left] + nums[right] < target) {

                    //如果结果比目标小,移动左指针
                    left++;
                } else {

                    //否则移动右指针
                    right--;
                }
            }
        }
        return res;
    }
}

Review
阅读并点评至少一篇英文技术文章

你的时间并没有你想象的那么值钱

点评:

外包确实可以节省你的时间和金钱。但是,并没有你想象的那么节省。所以,在某些时候,自己动手去做会是一个更好的选择。既可以培养你的某一项技能,又能节约资金。

Tips

学习至少一个技术技巧

数据库的分页问题
前几天,同事让我帮他看一个奇怪的问题,他用查询语句从数据库中将数据用分页的方式将数据输出到文件里。
查询结果里的数据是没有重复的,用distinct id 和 直接查询到的结果的数量是一致的。
但是,文件中的数据却有部分重复,20多万条数据里边大概出现了1万多条重复数据,这些重复数据是没有规律。
然后,我发现,文件里边的id是乱序的,正常情况下,mysql的查询语句会默认的用主键排序。
因为他用了关联查询,关联了多张表,所以可能因为这个原因,导致了mysql使用了别的字段来进行排序。
然后,我就会想起,我有一次遇到过类似的问题,就是我在oracle里边用时间字段进行排序之后出现的问题。
数据的总数是没有变的,但是会导致分页的时候,某些页的数据会重复,和这次的情况很像。
然后我就在查询语句中加入了使用某一个表的主键来排序,果然,导出的结果就正确了。

Share
分享一篇有观点和思考的技术文章

输出就是最好的输入

这是我在学习一个理财课程的时候学到的一句话,现在我将它分享给大家。

在初中的时候,我们化学老师要求我们必须做笔记。必须将她讲的每一堂课的内容都记录下来。
但是我记的笔记重来都没有翻过,每次都是,我写下来之后,我就记住了。

记笔记的过程,就是一个将课堂知识输出到笔记本上的过程。

首先,我们不可能把老师讲的每一个字都记录在笔记上,我们没有那么快的写字速度,老师也不可能讲的每一句话都是重点。

所以,输出的第一步就是要提炼重点。
意味着,我们要将学到的知识在脑子里过一遍,将我们认为的重点提炼出来,然后再以自己的语言将它输出。

我上培训班的时候,班上有一个特别笨的同学,老师讲的内容他根本听不懂。
然后他来问我,我就会绞尽脑汁,尽量举一些生动的例子,好让他能够理解。

这就让我对老师讲解的知识的理解更加的深刻。

所以,输出,就是最好的输入。因为,在输出的过程中,你会对自己学到的知识进行提炼和更深层次的去理解。

你可能感兴趣的:(打卡,学习历程,java,算法,数据库)