LeetCode 11-15 题 详解 Java版 ( 万字 图文详解 LeetCode 算法题11-15 =====>>> <建议收藏>)

简介: LeetCode 11-15 题 详解 Java版 ( 万字 图文详解 LeetCode 算法题11-15 =====>>> <建议收藏>)

目录


第11题. Container With Most Water

题目描述(中等难度)

解法一 暴力解法

解法二

第12题. Integer to Roman

题目描述(中等难度)

解法一

解法二

解法三

第13题: Roman to Integer

题目描述(简单难度)

解法一

解法二

解法三

第14题: Longest Common Prefix

解法一 垂直比较

解法二 水平比较

解法三 递归

第15题 : 3Sum

题目描述(中等难度)

解法一 暴力解法

解法二

喜欢 请点个 + 关注



第11题. Container With Most Water

题目描述(中等难度)


2.jpg


每个数组代表一个高度,选两个任意的柱子往里边倒水,能最多倒多少水。


解法一 暴力解法

直接遍历任意两根柱子,求出能存水的大小,用一个变量保存最大的。

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

时间复杂度:O(n²)。

空间复杂度:O(1)。


解法二


3.jpg

我们理一下思路,大小是由长度和高度决定,如果选 0 到 8 就保证了长度最长,此时大小是 0 号柱子的高度 1 乘以长度 8 。我们如果想面积更大怎么做呢,只能减小长度,增加高度。是左边的柱子向右移动变成 1 号柱子呢?还是右边的柱子向左移动变成 7 号柱子呢?当然是哪边的柱子短就改哪边的!只有这样,高度才有可能增加。


例如我们如果把 8 号柱子变成 7 号柱子,此时长度减少了,然而高度还是 0 号柱子没有变化,所以面积就会减少。把 1 号柱子变成 2 号柱子就很好了,因为此时高度就变成了 8 号柱子的高度,面积就有可能会增加。


如果左右两边柱子相等该怎么办呢?随意!


我们假设 1 号 和 8 号 柱子高度是相等的。如果他们之间的柱子只有 1 根比它俩高或者没有比它俩高的,那么最大面积就一定选取是 1 号和 8 号了,所以 1 号接着变大,或者 8 号接着减小都是无所谓的,因为答案已经确定了。


44.jpg

假设 1 号 和 8 号之间有 2 根或以上的柱子比它俩高,假设是 4 号和 6 号比它俩高。1 号会变到 2 号、3 号,最终为 4 号,8 号会变到 7 号, 6 号,而在这个过程中产生的面积一定不会比 1 号和 8 号产生的面积大,因为过程中的柱子都比 1 号和 8 号低。所以是先变 1 号还是先变 8 号是无所谓的,无非是谁先到达更长的柱子而已。


看一下下边的算法,会更加清楚一些。

public int maxArea2(int[] height) {
    int maxarea = 0, l = 0, r = height.length - 1;
    while (l < r) {
        maxarea = Math.max(maxarea, Math.min(height[l], height[r]) * (r - l));
        if (height[l] < height[r])
            l++;
        else
            r--;
    }
    return maxarea;
}

时间复杂度:O(n)。

空间复杂度:O(1)。


为了减少暴力解法的时间复杂度,只能去深层次的理解题意,从而找出突破点。


第12题. Integer to Roman

题目描述(中等难度)


5.jpg

把数字转换成罗马数字,正常情况就是把每个字母相加,并且大字母在前,小字母在后,上边也介绍了像 4 和 9 那些特殊情况。


解法一

这个是自己的解法,主要思想就是每次取出一位,然后得到相应的罗马数字,然后合起来就行


public String getRoman(int num,int count){ //count 表示当前的位数,个位,十位...
    char[]ten={'I','X','C','M'}; //1,10,100,1000
    char[]five={'V','L','D'};//5,50,500
    String r="";
    if(num<=3){
        while(num!=0){
            r+=ten[count];
            num--;
        }
    }
    if(num==4){
        r=(ten[count]+"")+(five[count]+"");
    }
    if(num==5){
        r=five[count]+"";
    }
    if(num>5&&num<9){
        r=five[count]+"";
        num-=5;
        while(num!=0){
            r+=ten[count];
            num--;
        }
    }
    if(num==9){
        r = (ten[count] + "") + (ten[count + 1] + "");
    }
    return r;
}
public String intToRoman(int num) {
    String r="";
    int count=0;
    while(num!=0){
        int pop=num%10;
        r=getRoman(pop,count)+r;
        count++;
        num/=10;
    }
    return r;
}


时间复杂度:num 的位数 log10(num)+1log_{10}(num)+1log10(num)+1所以时间复杂度是 O(log(n))。

空间复杂度:常数个变量,O(1)。

下边在分享一些 LeetCode 讨论里的一些解法


解法二

https://leetcode.com/problems/integer-to-roman/discuss/6310/My-java-solution-easy-to-understand

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

相当简洁了,主要就是把所有的组合列出来,因为罗马数字表示的大小就是把所有字母相加,所以每次 append 那个,再把对应的值减去就行了。

时间复杂度:不是很清楚,也许是 O(1)?因为似乎和问题规模没什么关系了。

空间复杂度:O(1).


解法三

https://leetcode.com/problems/integer-to-roman/discuss/6376/Simple-JAVA-solution


public String intToRoman(int num) {
    String M[] = {"", "M", "MM", "MMM"};//0,1000,2000,3000
    String C[] = {"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"};//0,100,200,300...
    String X[] = {"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"};//0,10,20,30...
    String I[] = {"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"};//0,1,2,3...
    return M[num/1000] + C[(num%1000)/100]+ X[(num%100)/10] + I[num%10];
}


这就更加暴力了,把每位的情况都列出来然后直接返回,但思路清晰明了呀。

时间复杂度:O(1)或者说是 num 的位数,不是很确定。

空间复杂度:O(1)。


这道题感觉难度应该是 easy ,没有那么难,就是理清楚题意,然后就可以往出列举就行了。


第13题: Roman to Integer

题目描述(简单难度)

66.jpg


和上一道题相反,将罗马数字转换成阿拉伯数字。


解法一

先来一种不优雅的,也就是我开始的想法。就是遍历字符串,然后转换就可以,但同时得考虑 IV,IX 那些特殊情况。

public int getInt(char r) {
        int ans = 0;
        switch (r) {
        case 'I':
            ans = 1;
            break;
        case 'V':
            ans = 5;
            break;
        case 'X':
            ans = 10;
            break;
        case 'L':
            ans = 50;
            break;
        case 'C':
            ans = 100;
            break;
        case 'D':
            ans = 500;
            break;
        case 'M':
            ans = 1000;
        }
        return ans;
    }
    public int getInt(char r, char r_after) {
        int ans = 0;
        switch (r) {
        case 'I':
            ans = 1;
            break;
        case 'V':
            ans = 5;
            break;
        case 'X':
            ans = 10;
            break;
        case 'L':
            ans = 50;
            break;
        case 'C':
            ans = 100;
            break;
        case 'D':
            ans = 500;
            break;
        case 'M':
            ans = 1000;
            break;
        }
        if (r == 'I') {
            switch (r_after) {
            case 'V':
                ans = 4;
                break;
            case 'X':
                ans = 9;
            }
        }
        if (r == 'X') {
            switch (r_after) {
            case 'L':
                ans = 40;
                break;
            case 'C':
                ans = 90;
            }
        }
        if (r == 'C') {
            switch (r_after) {
            case 'D':
                ans = 400;
                break;
            case 'M':
                ans = 900;
            }
        }
        return ans;
    }
    public boolean isGetTwoInt(char r, char r_after) {
        if (r == 'I') {
            switch (r_after) {
            case 'V':
                return true;
            case 'X':
                return true;
            }
        }
        if (r == 'X') {
            switch (r_after) {
            case 'L':
                return true;
            case 'C':
                return true;
            }
        }
        if (r == 'C') {
            switch (r_after) {
            case 'D':
                return true;
            case 'M':
                return true;
            }
        }
        return false;
    }
    public int romanToInt(String s) {
        int ans = 0;
        for (int i = 0; i < s.length() - 1; i++) {
            ans += getInt(s.charAt(i), s.charAt(i + 1));
            //判断是否是两个字符的特殊情况
            if (isGetTwoInt(s.charAt(i), s.charAt(i + 1))) {
                i++;
            }
        }
        //将最后一个字符单独判断,如果放到上边的循环会越界
        if (!(s.length() >= 2 && isGetTwoInt(s.charAt(s.length() - 2), s.charAt(s.length() - 1)))) {
            ans += getInt(s.charAt(s.length() - 1));
        }
        return ans;
    }


时间复杂度:O(n),n 是字符串的长度。

空间复杂度:O(1)。

下边分享一些优雅的。


解法二

https://leetcode.com/problems/roman-to-integer/description/


public int romanToInt(String s) {
     int sum=0;
    if(s.indexOf("IV")!=-1){sum-=2;}
    if(s.indexOf("IX")!=-1){sum-=2;}
    if(s.indexOf("XL")!=-1){sum-=20;}
    if(s.indexOf("XC")!=-1){sum-=20;}
    if(s.indexOf("CD")!=-1){sum-=200;}
    if(s.indexOf("CM")!=-1){sum-=200;}
    char c[]=s.toCharArray();
    int count=0;
   for(;count<=s.length()-1;count++){
       if(c[count]=='M') sum+=1000;
       if(c[count]=='D') sum+=500;
       if(c[count]=='C') sum+=100;
       if(c[count]=='L') sum+=50;
       if(c[count]=='X') sum+=10;
       if(c[count]=='V') sum+=5;
       if(c[count]=='I') sum+=1;
   }
   return sum;
}


把出现的特殊情况,提前减了就可以。

时间复杂度:O(1)。

空间复杂度:O(1


解法三

https://leetcode.com/problems/roman-to-integer/discuss/6509/7ms-solution-in-Java.-easy-to-understand


利用到罗马数字的规则,一般情况是表示数字大的字母在前,数字小的字母在后,如果不是这样,就说明出现了特殊情况,此时应该做减法。


 private int getVal(char c){
        switch (c){
            case 'M':
                return 1000;
            case 'D':
                return 500;
            case 'C':
                return 100;
            case 'L':
                return 50;
            case 'X' :
                return 10;
            case 'V':
                return 5;
            case 'I':
                return 1;
        }
        throw new IllegalArgumentException("unsupported character");
    }
    public int romanToInt(String s) {
        int res = 0;
        if(s.length() == 0) return res;
        for (int i = 0; i < s.length() - 1; i++) {
            int cur = getVal(s.charAt(i));
            int nex = getVal(s.charAt(i+1));
            if(cur < nex){
                res -= cur;
            }else{
                res += cur;
            }
        }
        return res + getVal(s.charAt(s.length()-1));
    }


时间复杂度:O(1)。

空间复杂度:O(1)


这道题也不难,自己一开始没有充分利用罗马数字的特点,而是用一些 if,switch 语句判断是否是特殊情况,看起来就很繁琐了。


第14题: Longest Common Prefix


8.jpg


解法一 垂直比较


9.jpg

我们把所有字符串垂直排列,然后一列一列的比较,直到某一个字符串到达结尾或者该列字符不完全相同。

下边看一下我的代码,看起来比较多


//这个函数判断 index 列的字符是否完全相同
public boolean isSameAtIndex(String[] strs, int index) {
    int i = 0;
    while (i < strs.length - 1) {
        if (strs[i].charAt(index) == strs[i + 1].charAt(index)) {
            i++;
        } else {
            return false;
        }
    }
    return true;
}
public String longestCommonPrefix(String[] strs) {
    if (strs.length == 0)
        return "";
    //得到最短的字符串的长度
    int minLength = Integer.MAX_VALUE;
    for (int i = 0; i < strs.length; i++) {
        if (strs[i].length() < minLength) {
            minLength = strs[i].length();
        }
    }
    int j = 0;
    //遍历所有列
    for (; j < minLength; j++) {
        //如果当前列字符不完全相同,则结束循环
        if (!isSameAtIndex(strs, j)) {
            break;
        }
    }
    return strs[0].substring(0, j);
}

下边看一下,官方的代码

public String longestCommonPrefix(String[] strs) {
    if (strs == null || strs.length == 0) return "";
    //遍历所有列
    for (int i = 0; i < strs[0].length() ; i++){
        char c = strs[0].charAt(i); // 保存 i 列第 0 行的字符便于后续比较
        //比较第 1,2,3... 行的字符和第 0 行是否相等
        for (int j = 1; j < strs.length; j ++) {
            /**
             * i == strs[j].length() 表明当前行已经到达末尾
             * strs[j].charAt(i) != c  当前行和第 0 行字符不相等
             * 上边两种情况返回结果
             */
            if (i == strs[j].length() || strs[j].charAt(i) != c)
                return strs[0].substring(0, i);             
        }
    }
    return strs[0];
}


时间复杂度:最坏的情况就是 n 个 长度为 m 的完全一样的字符串,假设 S 是所有字符的和,那么 S = m * n,时间复杂度就是 O(S)。当然正常情况下并不需要比较所有字符串,最多比较 n * minLen 个字符就可以了。

空间复杂度:O(1),常数个额外空间。


解法二 水平比较


10.jpg

我们将字符串水平排列,第 0 个和第 1 个字符串找最长子串,结果为 leet,再把结果和第 2 个字符串比较,结果为 leet,再把结果和第 3 个字符串比较,结果为 lee,即为最终结果。

public String longestCommonPrefix3(String[] strs) {
        if (strs.length == 0)
            return "";
        String prefix = strs[0]; // 保存结果
        // 遍历每一个字符串
        for (int i = 1; i < strs.length; i++) {
            // 找到上次得到的结果 prefix 和当前字符串的最长子串
            int minLen = Math.min(prefix.length(), strs[i].length());
            int j = 0;
            for (; j < minLen; j++) {
                if (prefix.charAt(j) != strs[i].charAt(j)) {
                    break;
                }
            }
            prefix = prefix.substring(0, j);
        }
        return prefix;
    }


时间复杂度:最坏情况和解法一是一样,n 个长度为 m 的完全相同的字符,就要比较所有的字符 S,S = n * m 。但对于正常情况,处于最短字符串前的字符串依旧要比较所有字符,而不是最短字符串个字符,相对于解法一较差。

空间复杂度:O(1)。


解法三 递归11.jpg


我们把原来的数组分成两部分,求出左半部分的最长公共前缀,求出右半部分的最长公共前缀,然后求出的两个结果再求最长公共前缀,就是最后的结果了。


求左半部分的最长公共前缀,我们可以继续把它分成两部分,按照上边的思路接着求。然后一直分成两部分,递归下去。


直到该部分只有 1 个字符串,那么最长公共子串就是它本身了,直接返回就可以了。

public String longestCommonPrefix(String[] strs) {
    if (strs == null || strs.length == 0) return "";    
        return longestCommonPrefix(strs, 0 , strs.length - 1);
}
//递归不断分成两部分
private String longestCommonPrefix(String[] strs, int l, int r) {
    if (l == r) {
        return strs[l];
    }
    else {
        int mid = (l + r)/2;
        String lcpLeft =   longestCommonPrefix(strs, l , mid);
        String lcpRight =  longestCommonPrefix(strs, mid + 1,r);
        return commonPrefix(lcpLeft, lcpRight);
   }
}
//求两个结果的最长公共前缀
String commonPrefix(String left,String right) {
    int min = Math.min(left.length(), right.length());       
    for (int i = 0; i < min; i++) {
        if ( left.charAt(i) != right.charAt(i) )
            return left.substring(0, i);
    }
    return left.substring(0, min);
}


时间复杂度:

空间复杂度:

每次遇到递归的情况,总是有些理不清楚,先空着吧。


进行了垂直比较和水平比较,又用到了递归,solution 里还介绍了二分查找,感觉这里用二分查找有些太僵硬了,反而使得时间复杂度变高了。还介绍了前缀树,这里后边遇到再总结吧。


第15题 : 3Sum

题目描述(中等难度)


12.jpg

解法一 暴力解法

无脑搜索,三层循环,遍历所有的情况。但需要注意的是,我们需要把重复的情况去除掉,就是 [1, -1 ,0] 和 [0, -1, 1] 是属于同一种情况的。


public List<List<Integer>> threeSum(int[] nums) {
    List<List<Integer>> res = new ArrayList<List<Integer>>();
    for (int i = 0; i < nums.length; i++) {
        for (int j = i + 1; j < nums.length; j++)
            for (int k = j + 1; k < nums.length; k++) {
                if (nums[i] + nums[j] + nums[k] == 0) {
                    List<Integer> temp = new ArrayList<Integer>();
                    temp.add(nums[i]);
                    temp.add(nums[j]);
                    temp.add(nums[k]); 
                    //判断结果中是否已经有 temp 。
                    if (isInList(res, temp)) {
                        continue;
                    }
                    res.add(temp);
                }
            }
    }
    return res;
}
public boolean isInList(List<List<Integer>> l, List<Integer> a) {
    for (int i = 0; i < l.size(); i++) {
        //判断两个 List 是否相同
        if (isSame(l.get(i), a)) {
            return true;
        }
    }
    return false;
}
public boolean isSame(List<Integer> a, List<Integer> b) {
    int count = 0;
    Collections.sort(a);
    Collections.sort(b);
    //排序后判断每个元素是否对应相等
    for (int i = 0; i < a.size(); i++) {
        if (a.get(i) != b.get(i)) {
            return false;
        }
    }
    return true;
}


时间复杂度:n 表示 num 的个数,三个循环 O(n³),而 isInList 也需要 O(n),总共就是 O(n4)O(n^4)O(n4),leetCode 复杂度到了 O(n3)O(n^3)O(n3) 一般就报超时错误了,所以算法还得优化。


空间复杂度:最坏情况,即 O(N), N 是指 n 个元素的排列组合个数,即 N=Cn3N=C^3_nN=Cn3,用来保存结果。


解法二


参考了这里


主要思想是,遍历数组,用 0 减去当前的数,作为 sum ,然后再找两个数使得和为 sum。


这样看来遍历需要 O(n),再找两个数需要 O(n²)的复杂度,还是需要 O(n³)。


巧妙之处在于怎么找另外两个数。


最最优美的地方就是,首先将给定的 num 排序。


这样我们就可以用两个指针,一个指向头,一个指向尾,去找这两个数字,这样的话,找另外两个数时间复杂度就会从 O(n²),降到 O(n)。


而怎么保证不加入重复的 list 呢?


要记得我们的 nums 已经有序了,所以只需要找到一组之后,当前指针要移到和当前元素不同的地方。其次在遍历数组的时候,如果和上个数字相同,也要继续后移。文字表述比较困难,可以先看下代码。


public List<List<Integer>> threeSum(int[] num) {
    Arrays.sort(num); //排序
    List<List<Integer>> res = new LinkedList<>(); 
    for (int i = 0; i < num.length-2; i++) {
        //为了保证不加入重复的 list,因为是有序的,所以如果和前一个元素相同,只需要继续后移就可以
        if (i == 0 || (i > 0 && num[i] != num[i-1])) {
            //两个指针,并且头指针从i + 1开始,防止加入重复的元素
            int lo = i+1, hi = num.length-1, sum = 0 - num[i];
            while (lo < hi) {
                if (num[lo] + num[hi] == sum) {
                    res.add(Arrays.asList(num[i], num[lo], num[hi]));
                    //元素相同要后移,防止加入重复的 list
                    while (lo < hi && num[lo] == num[lo+1]) lo++;
                    while (lo < hi && num[hi] == num[hi-1]) hi--;
                    lo++; hi--;
                } else if (num[lo] + num[hi] < sum) lo++;
                else hi--;
           }
        }
    }
    return res;
}


时间复杂度:O(n²),n 指的是 num

空间复杂度:O(N),最坏情况,即 N 是指 n 个元素的排列组合个数,即 N=Cn3N=C^3_nN=Cn3,用来保存结果。


对于遍历,这里用到了从两头同时遍历,从而降低了时间复杂度,很妙!

今天我们一起学习了LeetCode 10-15 题的算法分析,感谢大家阅读,觉得不错记得收藏哦!

喜欢 请点个 + 关注

目录
相关文章
|
14天前
|
算法 Java C语言
C++和Java中的随机函数你玩明白了吗?内附LeetCode470.rand7()爆改rand10()巨详细题解,带你打败LeetCode%99选手
C++和Java中的随机函数你玩明白了吗?内附LeetCode470.rand7()爆改rand10()巨详细题解,带你打败LeetCode%99选手
|
1天前
|
设计模式 算法 Java
[设计模式Java实现附plantuml源码~行为型]定义算法的框架——模板方法模式
[设计模式Java实现附plantuml源码~行为型]定义算法的框架——模板方法模式
|
7天前
|
算法
代码随想录算法训练营第六十天 | LeetCode 84. 柱状图中最大的矩形
代码随想录算法训练营第六十天 | LeetCode 84. 柱状图中最大的矩形
18 3
|
7天前
|
算法
代码随想录算法训练营第五十七天 | LeetCode 739. 每日温度、496. 下一个更大元素 I
代码随想录算法训练营第五十七天 | LeetCode 739. 每日温度、496. 下一个更大元素 I
11 3
|
7天前
|
算法
代码随想录算法训练营第五十六天 | LeetCode 647. 回文子串、516. 最长回文子序列、动态规划总结
代码随想录算法训练营第五十六天 | LeetCode 647. 回文子串、516. 最长回文子序列、动态规划总结
28 1
|
8天前
|
算法 DataX
二叉树(中)+Leetcode每日一题——“数据结构与算法”“剑指Offer55-I. 二叉树的深度”“100.相同的树”“965.单值二叉树”
二叉树(中)+Leetcode每日一题——“数据结构与算法”“剑指Offer55-I. 二叉树的深度”“100.相同的树”“965.单值二叉树”
|
17天前
|
算法 安全 Java
java代码 实现AES_CMAC 算法测试
该代码实现了一个AES-CMAC算法的简单测试,使用Bouncy Castle作为安全提供者。静态变量K定义了固定密钥。`Aes_Cmac`函数接受密钥和消息,返回AES-CMAC生成的MAC值。在`main`方法中,程序对给定的消息进行AES-CMAC加密,然后模拟接收ECU的加密结果并进行比较。如果两者匹配,输出&quot;验证成功&quot;,否则输出&quot;验证失败&quot;。辅助方法包括将字节转为16进制字符串和将16进制字符串转为字节。
|
23天前
|
搜索推荐 Java
Java排序算法
Java排序算法
18 0
|
23天前
|
搜索推荐 Java
Java基础(快速排序算法)
Java基础(快速排序算法)
24 4
|
1月前
|
传感器 算法 计算机视觉
基于肤色模型和中值滤波的手部检测算法FPGA实现,包括tb测试文件和MATLAB辅助验证
该内容是关于一个基于肤色模型和中值滤波的手部检测算法的描述,包括算法的运行效果图和所使用的软件版本(matlab2022a, vivado2019.2)。算法分为肤色分割和中值滤波两步,其中肤色模型在YCbCr色彩空间定义,中值滤波用于去除噪声。提供了一段核心程序代码,用于处理图像数据并在FPGA上实现。最终,检测结果输出到&quot;hand.txt&quot;文件。