java学习之路算法提高之LeetCode刷题

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

2019-12-20  本文已影响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道题,总有一天会做完了!加油!
今天的笔记就记到这里,毕竟周末。给自己放个假,吃个夜宵啥的,也祝大家周末愉快,工作顺顺利利!

上一篇下一篇

猜你喜欢

热点阅读