leetCode进阶算法题+解析(二)

作者: 唯有努力不欺人丶 | 来源:发表于2020-01-16 20:00 被阅读0次

    字符串转换整数

    题目:请你来实现一个 atoi 函数,使其能将字符串转换成整数。首先,该函数会根据需要丢弃无用的开头空格字符,直到寻找到第一个非空格的字符为止。当我们寻找到的第一个非空字符为正或者负号时,则将该符号与之后面尽可能多的连续数字组合起来,作为该整数的正负号;假如第一个非空字符是数字,则直接将其与之后连续的数字字符组合起来,形成整数。该字符串除了有效的整数部分之后也可能会存在多余的字符,这些字符可以被忽略,它们对于函数不应该造成影响。注意:假如该字符串中的第一个非空格字符不是一个有效整数字符、字符串为空或字符串仅包含空白字符时,则你的函数不需要进行转换。在任何情况下,若函数不能进行有效的转换时,请返回 0。说明:假设我们的环境只能存储 32 位大小的有符号整数,那么其数值范围为 [−231, 231 − 1]。如果数值超过这个范围,请返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。

    示例 1:
    输入: "42"
    输出: 42
    示例 2:
    输入: " -42"
    输出: -42
    解释: 第一个非空白字符为 '-', 它是一个负号。
    我们尽可能将负号与后面所有连续出现的数字组合起来,最后得到 -42 。
    示例 3:
    输入: "4193 with words"
    输出: 4193
    解释: 转换截止于数字 '3' ,因为它的下一个字符不为数字。
    示例 4:
    输入: "words and 987"
    输出: 0
    解释: 第一个非空字符是 'w', 但它不是数字或正、负号。
    因此无法执行有效的转换。
    示例 5:
    输入: "-91283472332"
    输出: -2147483648
    解释: 数字 "-91283472332" 超过 32 位有符号整数范围。
    因此返回 INT_MIN (−231) 。

    思路:这个题的叙述文字很多啊,多的我都觉得是不是我看漏什么了。单纯的说这道题感觉应该不难,首先第一个字符是字母则返回0.第一个字符是+-则用一个boolean判断这个数是正是负。然后接下来的数字超过整数最大值则返回int最小值。差不多就是这个逻辑,我去实现看看。
    面向测试案例编程。。。一次次尝试补漏,一点点改动终于是改完了,性能超过了百分之九十九。我直接贴代码:

    class Solution {
        public int myAtoi(String str) {
            long res = 0;
            boolean flag = true;
            int f = -1;
            char[] c = str.toCharArray();
            for(int i = 0; i<c.length;i++) {            
                if(f==-1 && (c[i]=='-' || c[i]=='+') ) {
                    flag = c[i]=='-'?false:true;
                    f = 1;
                }else if(Character.isDigit(c[i])) {
                    res = res*10 + (c[i]-'0');
                    if(res>Integer.MAX_VALUE){
                        if(!flag) return  Integer.MIN_VALUE;
                        return Integer.MAX_VALUE;
                    }
                    f = 1;
                }else if(c[i]>30 ){
                    if(f==-1 && c[i]==' ') continue;
                    break;
                }
            }
            return flag == true?(int)res:(int)-res;
        }
    }
    

    首先这个一开始我想到可能溢出所以直接用long保存的,但是万万没想到long也能溢出,所以改成了每次追加都要判断,int溢出了直接返回。
    其次测试案例+-1这种,一开始我也没想到,所以在没数字之前的+-正常判断的,所以添加了f表示第一个正负号已经搞定。反正都是小问题,一点点补漏就行了。感觉这道题对不起中等难度,下一题了。

    盛最多水的容器

    题目:给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。说明:你不能倾斜容器,且 n 的值至少为 2。
    题目截图

    思路:暂时没啥思路啊,就是生算呗。。。我打算先暴力法试试水。就是第一个跟后面每一个试试,第二个跟后面每一个试试,,以此类推,不超时肯定是稳了,,超时再说超时的。我去撸代码了。
    咳咳,喜大普奔,暴力法通过了, 但是遗憾的是将将通过,性能排行二十多。。我先贴上代码,再想想不暴力要怎么做:

    class Solution {
        public int maxArea(int[] height) {
            int res = 0;
            for(int i = 0;i<height.length-1;i++){
                for(int j = i+1;j<height.length;j++){
                    int l = Math.min(height[i],height[j]);
                    res = Math.max(l*(j-i),res);
                }
            }
            return res;
        }
    }
    

    其实换个思路,不用全遍历,因为在给定值后面的,最高值前面的那些都不用计算,毕竟没最高值长,也没最高值高,不可能比最高值大,但是要怎么写呢?还有当前值*最后值的长度也没当前面积大的页不用考虑了。其实优化点还很多,我一点点看看怎么写。
    改了一点点,但是还没到达想要的目标。 尽我所能一点点调试才到百分之三十八。估计不换思路也就这样了,附上改良版代码:

    class Solution {
        public int maxArea(int[] height) {
            int res = 0;
            int max = height[0];
            int len = height.length;
            for(int i = 0;i<len-1;i++){
                if(height[i]*(len-i)<res || height[i]<max) continue;
                max = height[i];
                for(int j = i+1;j<len;j++){
                    int l = Math.min(height[i],height[j]);
                    res = Math.max(l*(j-i),res);
                }
            }
            return res;
        }
    }
    
    性能改良过程

    我还是直接看性能排行第一的代码吧。
    好了,看完了并且分析完了,而且自己用自己的方式写完了,先说一下大概思路:双指针,首先第一步第一个和最后一个的面积。然后两个指针一点点往中间走,如果遇到比边上的还要短的直接继续往下,因为长度已经在缩短了,如果长度还要短就没啥可比性了,接下来贴代码:

    class Solution {
        public int maxArea(int[] height) {
            int res = 0;
            int l = 0;
            int r = height.length-1;
            while(l<r){
                int left = height[l];
                int right = height[r];
                int n = Math.min(left,right);
                res = Math.max(res,n*(r-l));
                //前面的短挪前面的
                if(left<=right){
                    l++;
                    while(left>height[l] && l<height.length){
                        l++;
                    }
                }else{//后面的短挪后面的
                    r--;
                    while(right>height[r] && r>=0){
                        r--;
                    }
                }
            }
            return res;
        }
    }
    

    其实这个厉害的是思路,我在做的时候就若有若无的有点小思路,但是没有坚持完善成代码。急功近利的锅,虽然有可能坚持也想不到这样。反正又学到了一招。
    感觉现在刷题每道题都能学到东西,挺好的。下一题吧。

    整数转罗马数字

    题目:罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
    字符          数值
    I             1
    V             5
    X             10
    L             50
    C             100
    D             500
    M             1000
    
    例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:
    I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
    X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。 
    C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
    
    给定一个整数,将其转为罗马数字。输入确保在 1 到 3999 的范围内。

    示例 1:
    输入: 3
    输出: "III"
    示例 2:
    输入: 4
    输出: "IV"
    示例 3:
    输入: 9
    输出: "IX"
    示例 4:
    输入: 58
    输出: "LVIII"
    解释: L = 50, V = 5, III = 3.
    示例 5:
    输入: 1994
    输出: "MCMXCIV"
    解释: M = 1000, CM = 900, XC = 90, IV = 4.

    思路:这个题做过类似的,我记得当时是罗马数字转整数,是个简单的题目。这个就是反过来,我记得麻烦是很麻烦,但是并不难。因为题目范围是1-3999之间,我完全可以钻空子,(顺便说一下,前几天做的两个非0数和的那个也是钻了这个空子,其实我自己感觉也不太好,毕竟取值范围扩大了就逻辑啥的都要重新写,但是不得不说好用啊~)直接判断数的大小然后去写。我去实现啦。
    好的,今天的第一道一次及格的题目。就是生生写了好几十行判断的,我直接贴出来:

    class Solution {
        public String intToRoman(int num) {
            StringBuffer sb = new StringBuffer();
            while(num>=1000){
                sb.append("M");
                num -= 1000;
            }
            if(num>=900){
                sb.append("CM");
                num -= 900;
            }
            if(num>=500){
                sb.append("D");
                num -= 500;
            }
            if(num>=400){
                sb.append("CD");
                num -= 400;
            }
            while(num>=100){
                sb.append("C");
                num -= 100;
            }
            if(num>=90){
                sb.append("XC");
                num -= 90;
            }
            if(num>=50){
                sb.append("L");
                num -= 50;
            }
            if(num>=40){
                sb.append("XL");
                num -= 40;
            }
            while(num>=10){
                sb.append("X");
                num -= 10;
            }
            if(num>=9){
                sb.append("IX");
                num -= 9;
            }
            if(num>=5){
                sb.append("V");
                num -= 5;
            }
            if(num>=4){
                sb.append("IV");
                num -= 4;
            }
            while(num>=1){
                sb.append("I");
                num -= 1;
            }
            return sb.toString();
        }
    }
    

    然后挺开心的,而且我看了下,已经给定的罗马字也没啥数组扩大的情况。比如超过5千题目上也没有啊。而且改动只要在最开始添加几个判断就行了,反正对自己挺满意。
    接下来去看看排行第一的代码:
    看了排行第一的代码,其实就是把我这个整合到一个循环里,而且用了两个数组来让数字和值对应上,不难,挺简单的,我贴出来分享下然后下一题了:

    class Solution {
        public String intToRoman(int num) {
            int[] nums = {1,4,5,9,10,40,50,90,100,400,500,900,1000};
            String[] roma = {"I","IV","V","IX","X","XL","L","XC","C","CD","D","CM","M"};
            StringBuilder sb = new StringBuilder();
            int i = nums.length-1;
            while(i>=0) {
                if (num >=nums[i]) {
                    sb.append(roma[i]);
                    num = num-nums[i];
                }
                else i--;
            }
            return sb.toString();
        }
    }
    

    三数之和

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

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

    思路:目前刚刚审完题,思路是三次循环,第一层i=0开始,第二层j=i+1开始,第三层k=j+1开始,如果i+j+k等于0就add进结果集。暂时就这个想法,不知道超时不,我去实现了。
    感觉三重循环这个死在了不能有重复的三元组,所以换个思路,还是别暴力法了,想想什么技巧可言解决这个问题。
    额, 我又想到了数组下标代替数字。因为这个是有正有负的,所以目前打算是两个数组,一个正一个负。然后负的两个相加如果正的这个值是有的,则是一对。反之没有继续往下。毕竟abc相加等于0除了三个000必然有正有负。就是不知道这个题abc的范围是多少。但是我觉得思路是没问题的,我去试试。
    很好,又超时了!!!最后的数字大于数百万。。313个测试案例过了311个,最后两个卡主了。我也很无奈啊。。继续想怎么处理吧。
    或者放在set中,判断是否有这个值?然后重复的元素单独处理、我再去试试。
    好的吧,这个题我绝望了,做了一下午两个多小时,超时两次,剩下各种bug,直接看了题解的思路,居然回归到一开始的循环,只不过处理的优雅了点,变成了两层循环,并且用sort排序处理重复的三元组。。我去试着根据理解写代码了。
    写是写出来了,然后性能依旧可怜,我不知道是不是因为我自己写list的原因,一会再调整,我先贴代码:

    class Solution {
        public List<List<Integer>> threeSum(int[] nums) {
            List<List<Integer>> res = new ArrayList<List<Integer>>();
            Arrays.sort(nums);
            //当第i个元素大于0说明已经不可能凑成三元组了
            //所以这里一定是主动break,无所谓i的范围
            for(int i = 0;i<nums.length;i++) {
                if(nums[i]>0) break;
                //第一个正常算,第二个开始直接continue
                if(i>0 && nums[i]==nums[i-1]) continue;
                int l = i+1;//第二个元素
                int r = nums.length-1;//第三个元素
                //这里用了双指针,不断往中间计算,省去了两次循环
                while(l<r) {
                    int sum = nums[i] + nums[l] + nums[r];
                    if(sum==0) {//正好是结果,存入结果集
                        res.add(getList(nums[i], nums[l], nums[r]));
                        while(l<r && nums[l]==nums[l+1]) l++;
                        while(r>l && nums[r]==nums[r-1]) r--;
                        //注意这里是除了while还要加加减减,因为不相等的时候都不进while
                        l++;
                        r--;
                    }else if (sum>0) {//大于0说明右边的大了,往左移指针
                        r--;
                    }else {//小于0说明左边的小了,往右移指针
                        l++;
                    }
                }           
            }
            return res;
        }
        public List<Integer> getList(int i,int j,int k){
            List<Integer> list = new ArrayList<Integer>();
            list.add(i);
            list.add(j);
            list.add(k);
            return list;
        }
    }
    

    因为这个题我思路也不是很清楚,所以都是一点点写代码的。感觉写的挺全了。接下来我去试着优化下了。
    把这个新添加list改成数组转list的形式,时间超过百分之九十二的人了。而且可能这种时间长的速度不稳定。提交几次几次不同的结果,但是因为最好的成绩超过百分之九十二,所以我这道题就这样了,贴上最后版的代码:

    class Solution {
        public List<List<Integer>> threeSum(int[] nums) {
            List<List<Integer>> res = new ArrayList<List<Integer>>();
            Arrays.sort(nums);
            //当第i个元素大于0说明已经不可能凑成三元组了
            //所以这里一定是主动break,无所谓i的范围
            for(int i = 0;i<nums.length;i++) {
                if(nums[i]>0) break;
                //第一个正常算,第二个开始直接continue
                if(i>0 && nums[i]==nums[i-1]) continue;
                int l = i+1;//第二个元素
                int r = nums.length-1;//第三个元素
                //这里用了双指针,不断往中间计算,省去了两次循环
                while(l<r) {
                    int sum = nums[i] + nums[l] + nums[r];
                    if(sum==0) {//正好是结果,存入结果集
                        res.add(Arrays.asList(nums[i],nums[l],nums[r]));
                        while(l<r && nums[l]==nums[l+1]) l++;
                        while(r>l && nums[r]==nums[r-1]) r--;
                        //注意这里是除了while还要加加减减,因为不相等的时候都不进while
                        l++;
                        r--;
                    }else if (sum>0) {//大于0说明右边的大了,往左移指针
                        r--;
                    }else {//小于0说明左边的小了,往右移指针
                        l++;
                    }
                }           
            }
            return res;
        }
    }
    

    最接近的三数之和

    题目:给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。例如,给定数组 nums = [-1,2,1,-4], 和 target = 1.与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).

    思路:这个题目如此简洁。。。啧啧啧。其次上道题是三数之和,这道题是最接近的三数之和。有区别,但是又好像是有联系。我不知道能不能用上道题的思路来解,但是起码要试试。我去试试。
    我不知道要怎么说,结果这道题很稳,借鉴上一道题的思路。一次过了,但是性能就不是那么好,只超过了百分之八十五的人!算了,我先贴出第一版代码:

    clclass Solution {
        public int threeSumClosest(int[] nums, int target) {
            int res = Integer.MAX_VALUE;
            boolean flag = true;
            Arrays.sort(nums);
            for(int i = 0;i<nums.length-2;i++){
                int l = i+1;
                int r = nums.length-1;
                while(l<r){
                    int sum = nums[i]+nums[l]+nums[r];
                    //结果大了,右指针往左
                    if(sum>target){
                        if(sum-target<res){
                            flag = true;
                            res = sum - target;
                        }
                        r--;
                    } else {//结果小了左指针往右
                        if(target-sum<res){
                            flag = false;
                            res = target - sum;
                        }
                        l++;
                    }                
                }
            }
            return flag==true?target+res:target-res;
            }
    }
    

    自己优化了半天也没进步,最好结果也就是百分之八十五,我直接去看排行第一的代码吧。

    class Solution {
        public int threeSumClosest(int[] nums, int target) {
            Arrays.sort(nums);
            int result = Integer.MAX_VALUE,left,right,min = Integer.MAX_VALUE;
            for (int i = 0; i < nums.length - 2; i++) {
                left = i + 1;
                right = nums.length - 1;
                int rangemax = nums[i] + nums[right] + nums[right - 1];
                int rangemin = nums[i] + nums[left] + nums[left + 1];
                if (rangemin > target) {
                    if (rangemin - target < min) {
                        min = rangemin -target;
                        result = rangemin;
                    }
                } else if (rangemax < target) {
                    if (rangemax - target < min) {
                        min = target - rangemax;
                        result = rangemax;
                    }
                } else {
                    while (left < right) {
                        int sum = nums[left] + nums[i] + nums[right];
                        if (sum > target) {
                            right--;
                        } else if (sum < target) {
                            left++;
                        } else {
                            return sum;
                        }
                        if (Math.abs(sum - target) < min) {
                            min = Math.abs(sum - target);
                            result = sum;
                        }
                    }
                }
               
            }
             return result;
        }
    }
    

    感觉百分之九十的相似度。就是差不多的。只不过人家在我代码的基础上多了两个判断。就是这个双端取值。也就是多了这一步直接性能上来了,这个真的是想的不够多,不是疏忽,是真的没想到。学到了。
    今天的笔记就记到这里。昨天刷了四道题,今天五道。是在进步的,加油!另外本篇文章如果稍微帮到你了记得点个喜欢点个关注,也祝大家工作顺顺利利!

    相关文章

      网友评论

        本文标题:leetCode进阶算法题+解析(二)

        本文链接:https://www.haomeiwen.com/subject/yrtfzctx.html