美文网首页java学习之路算法提高之LeetCode刷题
刷leetCode算法题+解析(二十八)

刷leetCode算法题+解析(二十八)

作者: 唯有努力不欺人丶 | 来源:发表于2019-12-20 19:39 被阅读0次

    下一个更大的元素

    题目:给定两个没有重复元素的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。找到 nums1 中每个元素在 nums2 中的下一个比其大的值。nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出-1。

    示例 1:
    输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
    输出: [-1,3,-1]
    解释:
    对于num1中的数字4,你无法在第二个数组中找到下一个更大的数字,因此输出 -1。
    对于num1中的数字1,第二个数组中数字1右边的下一个较大数字是 3。
    对于num1中的数字2,第二个数组中没有下一个更大的数字,因此输出 -1。
    示例 2:
    输入: nums1 = [2,4], nums2 = [1,2,3,4].
    输出: [3,-1]
    解释:
    对于num1中的数字2,第二个数组中的下一个较大数字是3。
    对于num1中的数字4,第二个数组中没有下一个更大的数字,因此输出 -1。

    注意:

    nums1和nums2中所有元素是唯一的。
    nums1和nums2 的数组大小都不超过1000。
    

    思路:这道题感觉就和很复杂,需要存储的点太多了。比如数组数组元素1的位置。并且两个都是无序的所以如果暴力判断还得一遍一遍循环,绝对不应该这么写,目前的想法就是保存每一个元素和他下一个大元素,存在一个map里,没有存value存-1.等把num2遍历存到map后在直接获取num1中的每一个元素对应的value。

    public class Solution {
        public int[] nextGreaterElement(int[] findNums, int[] nums) {
            //这里用到了栈先进后出的原则
            Stack < Integer > stack = new Stack < > ();
            HashMap < Integer, Integer > map = new HashMap < > ();
            int[] res = new int[findNums.length];
            for (int i = 0; i < nums.length; i++) {
                while (!stack.empty() && nums[i] > stack.peek())
                    map.put(stack.pop(), nums[i]);
                stack.push(nums[i]);
            }
            while (!stack.empty())
                map.put(stack.pop(), -1);
            for (int i = 0; i < findNums.length; i++) {
                res[i] = map.get(findNums[i]);
            }
            return res;
        }
    }
    

    第一个版本的代码。这里有一个很绕的逻辑:就是栈中,后入元素一定小于前面的元素。不然也不会累加存储。这块的我参考题解的逻辑。我自己一开始用的数组,结果发现来回来去for循环要写疯了。看了题解才发现思路没问题,数据结构用错了而已。


    栈中元素

    剩下也看了执行时间1ms,2ms的代码。讲真大循环小循环,随随便便四五个for循环,这个题真的有点复杂啊。贪多嚼不烂,我就暂时会这一种方法就行了。下一题吧。

    键盘行

    给定一个单词列表,只返回可以使用在键盘同一行的字母打印出来的单词。键盘如下图所示。
    题目截图

    思路:额,如图吧,这道题应该不难做,但是绝对很麻烦。毕竟看着就是那种判断来判断去的。初步一看判断数组中每个字符串的每个字符就已经是双层循环了。。还有细节处理,啧啧。一点点来吧只能。我去写代码
    emmmm~第一版本做完了,一次通过了,除了墨迹点没别的。性能也超过百分百:

    class Solution {
        public String[] findWords(String[] words) {
            String[] res = new String[words.length];
            String fir = "qwertyuiopQWERTYUIOP";
            String sec = "asdfghjklASDFGHJKL";
            String tir = "zxcvbnmZXCVBNM";
            int k = 0;
            for(int i = 0;i<words.length;i++){
                String temp = "";
                for(int j = 0;j<words[i].length();j++){
                    if(fir.indexOf(words[i].charAt(0))!=-1){
                        temp = fir;
                    }else if(sec.indexOf(words[i].charAt(0))!=-1){
                        temp = sec;
                    }else{
                        temp = tir;
                    }
                    if(temp.indexOf(words[i].charAt(j))==-1){
                        break;
                    }
                    if(temp.indexOf(words[i].charAt(j))!=-1&&j==words[i].length()-1){
                        res[k]=words[i];
                        k++;
                    }
                }
            }
            String[] result = new String[k];
            for(int p = 0;p<k;p++){
                result[p] = res[p];
            }
            return result;
        }
    }
    

    咳咳,这里其实可以用统一小写的,但是我直接在给定字符串就大小写都算上了,其实我想的是先做出来如果性能不行再优化,但是直接0ms就不优化了。
    思路就是判断一个字符串的第一个单词属于哪一行的,接下来照着这行判断,出现这行不存在的直接break。都判断完了没有不是的加到结果集中。
    因为一开始不知道结果集多长所以创建的数组和给定数组长度一样,再遍历一遍使得结果集大小正好。

    二叉搜索树中的众数

    题目:给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。

    假定 BST 有如下定义:

    结点左子树中所含结点的值小于等于当前结点的值
    结点右子树中所含结点的值大于等于当前结点的值
    左子树和右子树都是二叉搜索树
    
    题目

    思路:额,树的问题,递归迭代循环。我觉得这个进阶怕不是一个隐含的提醒,告诉我们要用递归吧?哈哈,反正我现在的思路就是遍历树,存list或者数组。然后排序,然后判断出现次数最多的元素。然后输出。对,就是这个逻辑。我去尝试写代码。
    好的吧,要开始写了发现我这个思路的大bug,对,用了额外的空间了。。。但是我还是不要脸的用了这个办法,别说不用额外空间了,我不仅用了,还大用特用,list,map都没缺的。没办法,不然真的没思路。先把代码贴出来(虽然性能不咋地,但是跟题解的代码比短了一半得)

    /**
     * Definition for a binary tree node.
     * public class TreeNode {
     *     int val;
     *     TreeNode left;
     *     TreeNode right;
     *     TreeNode(int x) { val = x; }
     * }
     */
    class Solution {
        public int[] findMode(TreeNode root) {
            List<Integer> list = new ArrayList<Integer>();
            getAllNode(root,list);
            Map<Integer,Integer> map = new HashMap<Integer,Integer>();
            int max = 0;
            int n = 1;
            for(Integer i :list){
                if(map.containsKey(i)){
                    map.put(i,map.get(i)+1);
                    if(max<map.get(i)){
                        //当有次数更多的,max被替换掉。同时数量也归1
                        max = map.get(i);
                        n = 1;
                    }else if(max==map.get(i)){
                        //当数量最多的有多个n计数。
                        n++;
                    }              
                }else{
                    map.put(i,1);
                }
            }
            //说明没有两次出现的元素,则原树中所有元素都是众数
            if(max==0) return list.stream().mapToInt(Integer::valueOf).toArray();
            int [] res = new int[n];
            int index = 0;
            for(Integer key: map.keySet()){
                if(map.get(key)==max){
                    res[index] = key;
                    index++;
                }    
            }
            return res;
        }
        public void getAllNode(TreeNode root,List<Integer> list){
            if(root==null) return;
            list.add(root.val);
            getAllNode(root.left,list);
            getAllNode(root.right,list);
        }
    }
    

    很遗憾,这道题一共就31题解,65评论,那种三五行解决问题的大神至今没出现。而且提交记录我之前的代码没有不用额外空间的。所以这道题的优化先待定吧。下一题。

    七进制数

    题目:给定一个整数,将其转化为7进制,并以字符串形式输出。

    示例 1:
    输入: 100

    输出: "202"
    示例 2:
    输入: -7
    输出: "-10"
    注意: 输入范围是 [-1e7, 1e7] 。

    思路:怎么说呢,从二进制到16进制,之前的excel格还用到了26进制,今天又有一个七进制,完全不意外。本来看到负号我还挺虚,寻思还得补码啥的咋的?再一看不就是正数前面加一个负号么,我直接去撸代码了。
    emmm。。。还好这道题没什么坑,一次通过的送分题,算是和上个题的难度综合?直接贴代码:

    class Solution {
        public String convertToBase7(int num) {
            String res = "";
            if(num==0) return res+0;
            if(num<0){
                num = -num;
                while(num>0){
                    res = num%7 + res;
                    num = num/7;
                }
               return "-"+res;
            }
            while(num>0){
                res = num%7 + res;
                num = num/7;
            }
            return res;
        }
    }
    

    因为性能直接超过百分百了,所以我也不看题解了,直接下一题。

    相对名次

    题目:给出 N 名运动员的成绩,找出他们的相对名次并授予前三名对应的奖牌。前三名运动员将会被分别授予 “金牌”,“银牌” 和“ 铜牌”("Gold Medal", "Silver Medal", "Bronze Medal")。

    (注:分数越高的选手,排名越靠前。)
    示例 1:
    输入: [5, 4, 3, 2, 1]
    输出: ["Gold Medal", "Silver Medal", "Bronze Medal", "4", "5"]
    解释: 前三名运动员的成绩为前三高的,因此将会分别被授予 “金牌”,“银牌”和“铜牌” ("Gold Medal", "Silver Medal" and "Bronze Medal").
    余下的两名运动员,我们只需要通过他们的成绩计算将其相对名次即可。

    提示:

    N 是一个正整数并且不会超过 10000。
    所有运动员的成绩都不相同。
    

    思路:额,这道题有点意思,乍一看很简单啊。仔细一看居然一时间想不出来好办法。但是我相信没有万能哈希解决不了的问题。二话不说先上排序存个map在往下。
    我哈希大法好,果然解决了问题。至于性能什么的是实现以后该考虑的,贴上第一版本的代码:

    class Solution {
        public String[] findRelativeRanks(int[] nums) {
            Map<Integer,Integer> map = new HashMap<Integer,Integer>();
            int [] ar = new int[nums.length];
            for(int l=0;l<nums.length;l++){
                ar[l] = nums[l];
            }
            Arrays.sort(ar);
            int j = 1;
            for(int i = ar.length-1;i>=0;i--){
                map.put(ar[i],j);
                j++;
            }
            String [] res = new String[nums.length];
            for(int k = 0;k<nums.length;k++){
                if(map.get(nums[k])<=3){
                    if(map.get(nums[k])==3) res[k] = "Bronze Medal";
                    if(map.get(nums[k])==2) res[k] = "Silver Medal";
                    if(map.get(nums[k])==1) res[k] = "Gold Medal";
                }else{
                    res[k] = map.get(nums[k])+"";
                }
                
            }
            return res;
        }
    }
    

    我也不知道我现在是中了什么毒,写出来的代码又臭又长,哎,开始想着怎么优化吧。其实我觉得这道题的优化点应该是在于map。我这里map的key 是数值没问题,但是value完全就是一个没用的表示(这里value是排名,但是其实也可以用数组下标表示,所以不是必要的)。我觉得这里如果把map换成数组性能会好多了。
    但是问题来了,这个数组,长度是多少?想用下标表示原数组的数值,万一是1,10000,那么就要建立一个10001的数组。。。不过应该也是一个优化点,我去试试再说。

    class Solution {
        public String[] findRelativeRanks(int[] nums) {
            int max = 0;
            for(int n:nums){
                max = Math.max(max,n);
            }
            int [] arr = new int[max+1];
            int j = 1;
            for(int i = nums.length-1;i>=0;i--){
                arr[nums[i]] = i+1;
            }
            String [] res = new String[nums.length];
            for (int i = max, rank = 1; i >= 0; i--) {
                if (arr[i] > 0) {
                    //因为是从后往前遍历,所以第一个元素就是最大的。金牌
                    switch (rank) {
                        case 1:
                        //这里arr[i]的值是nums[index]的值,相当于直接放到了排名的下标位置
                            res[arr[i] - 1] = "Gold Medal";
                            break;
                        case 2:
                            res[arr[i] - 1] = "Silver Medal";
                            break;
                        case 3:
                            res[arr[i] - 1] = "Bronze Medal";
                            break;
                        default:
                            res[arr[i] - 1] = Integer.toString(rank);
                            break;
                    }
                    rank++;
                }
            }
            return res;
        }
    }
    

    把map换成数组果然性能上来的,但是逻辑也更不直观了,尽量理解吧。

    完美数

    题目:对于一个 正整数,如果它和除了它自身以外的所有正因子之和相等,我们称它为“完美数”。给定一个 整数 n, 如果他是完美数,返回 True,否则返回 False

    示例:
    输入: 28
    输出: True
    解释: 28 = 1 + 2 + 4 + 7 + 14
    提示:
    输入的数字 n 不会超过 100,000,000. (1e8)

    思路:我理解的这道题分为两部分:1,这个数的因数。 2,和是不是相等。首先这个因数这个好说,暴力法也能判断,但是那样太low了,关键是怎么做的优雅?首先因子肯定成对出现的。所以只要遍历到开根号的数字就可以了。再大的属于成对的另一个数字。不需要遍历。直接上代码。

    class Solution {
        public boolean checkPerfectNumber(int num) {
            if(num==1) return false;
            int res = num-1;
            for(int i =2;i<=Math.sqrt(num);i++){
                if(num%i==0){
                    res -= i;
                    res -= num/i;
                }
            }
            return res==0?true:false;
        }
    }
    

    !!!我看了排名第一的代码,6的飞起,真的,蒂花之秀!


    性能第一的代码

    斐波那契数列

    题目:斐波那契数,通常用 F(n) 表示,形成的序列称为斐波那契数列。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:

    F(0) = 0, F(1) = 1
    F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
    给定 N,计算 F(N)。

    示例 1:
    输入:2
    输出:1
    解释:F(2) = F(1) + F(0) = 1 + 0 = 1.
    示例 2:
    输入:3
    输出:2
    解释:F(3) = F(2) + F(1) = 1 + 1 = 2.
    示例 3:
    输入:4
    输出:3
    解释:F(4) = F(3) + F(2) = 2 + 1 = 3.
    提示:
    0 ≤ N ≤ 30

    思路:讲真,这个是早有耳闻的一个数学知识。依稀记得刚刚接触的时候是在爬楼梯的那道题。也第一次知道了所谓的动态规划。甚至当时钻研了很久才勉强理解。不过现在动态规划的题做了好几道了,也多多少少比一开始有那么一点意识了。再回来看这道题,就是送分的了。直接上代码,主思路妥妥的递归。

    class Solution {
        public int fib(int N) {
            return N<=1?N:fib(N-1)+fib(N-2);
        }
    }
    

    额,三分钟写出来的递归,虽然性能不行但是进步妥妥的,我有一个不太成熟的猜想,我觉得性能好的可能用了常量switch-case。。毕竟大力扣什么人才都有。。哈哈
    好的吧,是我小人了,其实只不过是把递归用循环实现了而已。直接上代码:

    class Solution {
        public int fib(int N) {
            if (N == 0) {
                return 0;
            }
            if (N == 1) {
                return 1;
            }
            int before = 0, current = 1;
            for (int i = 2; i <= N; i++) {
                int temp = before + current;
                before = current;
                current = temp;
            }
            return current;
        }
    }
    

    感慨一下,真的很谢谢这道题,让我实实在在的看到了自己的进步。这篇文章是第二十八篇。刷leetcode差不多一个月了。从基础不扎实,数据结构不清晰,递归用的少等等等等,到现在虽然没多厉害但是确实很多以前不熟悉,不会的东西都手到擒来。其实学习坚持最难。为什么?因为这个不是一个可以一直肉眼可见进度的东西。
    就好像比如一个学生,刻苦努力一个月,但是这次月考的题恰好不是复习到的,会出现的状况就是努力学习一个月越学成绩越下降了。吃苦不可怕,看不到进步,可能是在做无用功等迷茫不确定,才会把一个人击垮!
    又是一个周五,时光荏苒,力扣的1298道题,总有一天会做完了!加油!
    今天的笔记就记到这里,毕竟周末。给自己放个假,吃个夜宵啥的,也祝大家周末愉快,工作顺顺利利!

    相关文章

      网友评论

        本文标题:刷leetCode算法题+解析(二十八)

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