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

新的一天,刷题继续!今天小目标:五道题。shiro权限框架的一篇笔记写完。有额外时间就再写个定时器的笔记。好的,就这样!

寻找峰值

题目:峰值元素是指其值大于左右相邻值的元素。给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。你可以假设 nums[-1] = nums[n] = -∞。

示例 1:
输入: nums = [1,2,3,1]
输出: 2
解释: 3 是峰值元素,你的函数应该返回其索引 2。
示例 2:
输入: nums = [1,2,1,3,5,6,4]
输出: 1 或 5
解释: 你的函数可以返回索引 1,其峰值元素为 2;
或者返回索引 5, 其峰值元素为 6。
说明:
你的解法应该是 O(logN) 时间复杂度的。

思路:这个题感觉才是正确的打开方式,这个进阶要求是主要的,不然这个题就是送分题,昨天也是有一个旋转数组找最小值的,就差了这么一句话。好了,闲话不说,logN的时间复杂度,二分法是最容易想到的一个了,我去实现下看看。
好了,面向测试案例编程,我直接贴代码:

class Solution {
    public int findPeakElement(int[] nums) {
        if(nums.length == 1) return 0;
        if(nums[0]>nums[1]) return 0;
        if(nums[nums.length-1]>nums[nums.length-2]) return nums.length-1;
        return dfs(nums,0,nums.length-1);
    }
    public int dfs(int[] nums,int start,int end){
        if(start+1<end){
            int mid = (end-start)/2+start;
            if(nums[mid]>nums[mid-1] && nums[mid]>nums[mid+1])return mid;
            return Math.max(dfs(nums,start,mid),dfs(nums,mid,end));
        }else{
            return 0;
        }
    }
}

先判断边值,边值不行再往里判断,虽然我现在也不确定这个二分法是不是有用,是不是logN了。。。反正这个代码执行是0ms,超过百分百。我去看下别人的代码吧。
看到了一个更好的思路,差不多想法,都是二分,但是思路就是优秀,我先贴代码:

class Solution {
    public int findPeakElement(int[] nums) {
        if(nums.length == 1) return 0;
        if(nums[0]>nums[1]) return 0;
        if(nums[nums.length-1]>nums[nums.length-2]) return nums.length-1;
        return dfs(nums,0,nums.length-1);
    }
    public int dfs(int[] nums,int start,int end){
        if(start+1<end){
            int mid = (end-start)/2+start;
            if(nums[mid]>nums[mid-1] && nums[mid]>nums[mid+1])return mid;
            //后面一定有峰值。
            if(nums[mid]<nums[mid+1]) return dfs(nums,mid,end);
            //前面有峰值
            if(nums[mid]<nums[mid-1]) return dfs(nums,start,mid);
        }
        return -1;
    }
}

一开始我没太明白怎么判断前后一定 有峰值的,后来仔细想了想才明白。其实画个图就能理解了。


题解

好了,这道题比较简单,所以就这样了,下一题了。

比较版本号

题目:比较两个版本号 version1 和 version2。如果 version1 > version2 返回 1,如果 version1 < version2 返回 -1, 除此之外返回 0。你可以假设版本字符串非空,并且只包含数字和 . 字符。
. 字符不代表小数点,而是用于分隔数字序列。例如,2.5 不是“两个半”,也不是“差一半到三”,而是第二版中的第五个小版本。你可以假设版本号的每一级的默认修订版号为 0。例如,版本号 3.4 的第一级(大版本)和第二级(小版本)修订号分别为 3 和 4。其第三级和第四级修订号均为 0。

示例 1:
输入: version1 = "0.1", version2 = "1.1"
输出: -1
示例 2:
输入: version1 = "1.0.1", version2 = "1"
输出: 1
示例 3:
输入: version1 = "7.5.2.4", version2 = "7.5.3"
输出: -1
示例 4:
输入:version1 = "1.01", version2 = "1.001"
输出:0
解释:忽略前导零,“01” 和 “001” 表示相同的数字 “1”。
示例 5:
输入:version1 = "1.0", version2 = "1.0.0"
输出:0
解释:version1 没有第三级修订号,这意味着它的第三级修订号默认为 “0”。
提示:
版本字符串由以点 (.) 分隔的数字字符串组成。这个数字字符串可能有前导零。
版本字符串不以点开始或结束,并且其中不会有两个连续的点。

思路:我差点笑出了声,我真的发现最近这几道题都简单的离谱,,,差点以为我刷回简单的了呢。这道题的思路就是从前往后逐个比较,然后没有任何额外要求,比如空间,时间复杂度,有点奇怪啊。偷懒点的做法通过点分割数组,然后从第一个开始逐个比较。稍微好一点的拆成char数组,然后比较,至于前导0.应该就是多加判断的事,我去写代码了
回来了,标准的面向测试案例编程,性能超过百分之九十四。我直接贴代码:

class Solution {
    public int compareVersion(String version1, String version2) {
       char[] c1 = version1.toCharArray();
       char[] c2 = version2.toCharArray();
       int sum1 = 0;
       int sum2 = 0;
       int l1 = 0;
       int l2 = 0;
       while(l1<c1.length && l2<c2.length){
           while(l1<c1.length && c1[l1]!='.'){
               sum1 = sum1*10 + (c1[l1]-'0');
               l1++;
           } 
           while(l2<c2.length && c2[l2]!='.'){
               sum2 = sum2*10 + (c2[l2]-'0');
               l2++;
           }
           if(sum2!=sum1) return sum1>sum2?1:-1;
           sum1 = 0;
           sum2 = 0;
           if(l1<c1.length) l1++;
           if(l2<c2.length) l2++;
       }
       if(l1 == c1.length && l2 == c2.length){
            return 0;
       }else if(l1 < c1.length){
            for(int i = l1;i<c1.length;i++){
                if(c1[i] != '0' && c1[i] != '.') return 1;
            }
            return 0;
       }else{
           for(int i = l2;i<c2.length;i++){
               if(c2[i] != '0' && c2[i] != '.') return -1;
           }
           return 0;
       }
    }
}

这个题好麻烦啊,我后悔了,应该直接用字符串分割的方式,我去用那种方式写一遍。

class Solution {
    public int compareVersion(String version1, String version2) {
       String[] c1 = version1.split("\\.");
       String[] c2 = version2.split("\\.");
       for(int i = 0;i<c1.length;i++){
            if(i<c2.length){
                if(Integer.valueOf(c1[i])==Integer.valueOf(c2[i])) continue;
                if(Integer.valueOf(c1[i])>Integer.valueOf(c2[i])){
                    return 1;
                }else{
                    return -1;
                }
            }else{
                if(Integer.valueOf(c1[i])>0) return 1;
            }
       }
       if(c2.length>c1.length){
            for(int i = c1.length;i<c2.length;i++){
                if(Integer.valueOf(c2[i])>0) return -1;
            }
       }
       return 0;
    }
}

第二种写法, 代码简洁,性能是一样的。这道题因为性能比较好了我就不看题解了,直接下一题了。

分数到小数

题目:给定两个整数,分别表示分数的分子 numerator 和分母 denominator,以字符串形式返回小数。如果小数部分为循环小数,则将循环的部分括在括号内。

示例 1:
输入: numerator = 1, denominator = 2
输出: "0.5"
示例 2:
输入: numerator = 2, denominator = 1
输出: "2"
示例 3:
输入: numerator = 2, denominator = 3
输出: "0.(6)"

思路:这个题,难点暂时不知道,可能是循环小数括号?我先写代码试试吧,没有太好的思路,就是硬算被?
我直接贴代码吧,写的有点闹心:

class Solution {
    public String fractionToDecimal(int numerator, int denominator) {
        if(numerator==0) return "0";
        if(denominator==1) return numerator+"";      
        //转成long是因为溢出问题,-最大值除-1.结果溢出,倍数
        long b = (long)numerator/(long)denominator;
        StringBuffer sb = new StringBuffer();
        if(numerator>0 && denominator<0) sb.append("-");
        sb.append(b+"");
        long x = numerator%denominator;//余数
        if(x!=0){//不是整除
            sb.append(".");
            StringBuffer sb2 = new StringBuffer();
            Map<Long,Integer> map = new HashMap<Long,Integer>();
            while(x != 0){
                x *= 10;
                if(map.containsKey(x)){
                    sb2.append(")");
                    sb2.insert(map.get(x),"(");
                    break;
                }else{
                    map.put(x,sb2.length());
                    sb2.append(Math.abs(x/denominator));
                }
                x = x%denominator;                               
            } 
            sb.append(sb2);
        }
        return sb.toString();

    }
}

居然写烦躁了,这个题不难,但是很墨迹,而且很弱智。测试案例更是奇葩,居然还特么溢出了,简直日狗了。这个题重点就是找出循环,然后一开始我甚至想用链表的,但是没写明白。改用map了,因为除数已经确定,所以当被除数x重复说明进入循环了,然后第一的出现的位置填左括号,当前位置添加有括号,跳出循环。
反正就这样吧,我这个代码性能贼差,我去看看排行第一的代码,差不多就行了。

class Solution {
    public String fractionToDecimal(int numerator, int denominator) {
    if (numerator == 0) {
        return "0";
    }
    StringBuilder fraction = new StringBuilder();
    // If either one is negative (not both)
    if (numerator < 0 ^ denominator < 0) {
        fraction.append("-");
    }
    // Convert to Long or else abs(-2147483648) overflows
    long dividend = Math.abs(Long.valueOf(numerator));
    long divisor = Math.abs(Long.valueOf(denominator));
    fraction.append(String.valueOf(dividend / divisor));
    long remainder = dividend % divisor;
    if (remainder == 0) {
        return fraction.toString();
    }
    fraction.append(".");
    Map<Long, Integer> map = new HashMap<>();
    while (remainder != 0) {
        if (map.containsKey(remainder)) {
            fraction.insert(map.get(remainder), "(");
            fraction.append(")");
            break;
        }
        map.put(remainder, fraction.length());
        remainder *= 10;
        fraction.append(String.valueOf(remainder / divisor));
        remainder %= divisor;
    }
    return fraction.toString();
}
}

这是性能排行第一的代码,思路差不多,细节处理有区别,然后过了,我继续往下做了。

二叉搜索树迭代器

题目:实现一个二叉搜索树迭代器。你将使用二叉搜索树的根节点初始化迭代器。调用 next() 将返回二叉搜索树中的下一个最小的数。

示例:
BSTIterator iterator = new BSTIterator(root);
iterator.next(); // 返回 3
iterator.next(); // 返回 7
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 9
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 15
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 20
iterator.hasNext(); // 返回 false
提示:
next() 和 hasNext() 操作的时间复杂度是 O(1),并使用 O(h) 内存,其中 h 是树的高度。
你可以假设 next() 调用总是有效的,也就是说,当调用 next() 时,BST 中至少存在一个下一个最小的数。

题目截图

思路:首先,我看了下这个迭代是中序迭代(因为是二叉搜索树,从小到大肯定是中序)。然后主要难度应该是时间复杂度O(1),也就是常量级别的,还有O(h)内存。首先这个O(h)内存就决定了不能直接遍历整个树来实现这两个方法。其次,因为只能遍历一部分,我觉得应该是先遍历左节点,因为首先第一个next肯定是要获取最左边的节点,保证时间复杂度是常量的情况下一定是先找到这个节点的,有了个模糊的想法,我去实现下。
这个题挺有意思,我也错了几次,但是大概思路是对的,先入后出用栈保存效果好,其实别的数据结构也是可以实现的。我直接贴代码了:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class BSTIterator {
    Stack<TreeNode> stack ;
    public BSTIterator(TreeNode root) {
        //从头遍历,应该是最左边的最后遍历到。适合先入后出
        stack = new Stack<TreeNode>();
        while(root!= null){
            stack.push(root);
            root = root.left;
        }
        //初始化保证栈中最上面的元素的最小值
    }
    
    /** @return the next smallest number */
    public int next() {
        TreeNode n = stack.pop();
        int res = n.val;
        //这个n的左节点要么没有,要么已经用过了, 只要判断右节点就行
        if(n.right!=null){
            n = n.right;
            while(n != null){
                stack.push(n);
                n = n.left;        
            }
        }
        return res;
    }
    
    /** @return whether we have a next smallest number */
    public boolean hasNext() {
         return !stack.isEmpty();
    }
}

/**
 * Your BSTIterator object will be instantiated and called as such:
 * BSTIterator obj = new BSTIterator(root);
 * int param_1 = obj.next();
 * boolean param_2 = obj.hasNext();
 */

之前在next中有点错误,一开始我是想着用if,如果有左节点添加进去,后来提交报错我检查了好几遍因为树又不好调试,怎么也找不到问题。最后去看了题解,才发现这个问题。因为树高有限,一层存一个节点空间复杂度满足O(h)。
我这个性能只超过百分之七十多,我去看看性能排行第一的代码。
额,他用的全遍历,O(n)空间复杂度,,没啥好看的了。
这个题就这样吧。最后一道题。
接下来几道题是sql题,本来我打算自己写写就好了,但是看到一个不错的题目,所以分享下。

分数排名

题目截图

这个题其实做法很多,反正我是不会。因为我是sql渣,工作中只学会了crud。稍微难点的就是靠着百度一点点尝试了,哈哈,不过基本的连表查询还是知道一些的。然后这个题我是直接看的题解,有的写得复杂有的写得简单,我把我看了觉得很容易懂的分享下:

SELECT a.score as Score,count(DISTINCT b.score) as Rank
from scores a join scores b where b.score>=a.score
group by a.id
order by a.score Desc

这个几乎是最简洁的做法了,关联条件大于等于,然后distinct b.score使得从1开始排名。group by a.id使得每一个同学都有排名,最后的升序排列不用说了。我不是说这个sql性能会多好,但是真的很容易理解,顺便贴上我看了就眼花的sql:

-- select Scores.Score ,
-- if(@agerank=Scores.Score ,@curRank, @curRank := @curRank + 1) as Rank,
-- @agerank := Scores.Score 
-- from Scores , (select @curRank :=0,@agerank = NULL) r
-- order by Score desc;

SELECT Score, Rank FROM
(SELECT Score,
@curRank := IF(@prevRank = Score, @curRank+0,@curRank:=@curRank+1) AS Rank,
@prevRank := Score
FROM Scores, (
SELECT @curRank :=0, @prevRank := NULL
) r
ORDER BY Score DESC) s

说真的,我都开始怀疑我平时用的是不是假的mysql数据库!!!看不太懂我都,这个@是什么?
哎,反正我第一次贴的那个方法我是记住了,也算是学到了,这个就这样了。

最大数

题目:给定一组非负整数,重新排列它们的顺序使之组成一个最大的整数。

示例 1:
输入: [10,2]
输出: 210
示例 2:
输入: [3,30,34,5,9]
输出: 9534330
说明: 输出结果可能非常大,所以你需要返回一个字符串而不是整数。

思路:这个题比较简单啊,我目前的想法是从写排序规则,然后排序,排序规则是从第一个数开始比较,大的就大,如果第一个数一样比较第二个。如果是数位少的默认按照当前首位计算。说起来有点清楚,我去写一下吧。
很好,学到一点,基本类型不能重写compare。。。拉倒吧,我自己排序吧。
这个题做出来了,我之前思路错了,没必要一个数一个数比较。直接转成字符串相加,a+b 和b+a比较就行了。我先贴代码:

class Solution {
    public String largestNumber(int[] nums) {
        for(int i = 0;i<nums.length-1;i++){
            for(int j = i;j<nums.length;j++){
                if(!compare(nums[i],nums[j])){
                    int t = nums[i];
                    nums[i] = nums[j];
                    nums[j] = t;
                }
            }
        }
        StringBuffer sb = new StringBuffer();
    for(int i : nums){
        sb.append(String.valueOf(i));
    }
    if(sb.charAt(0)=='0') return "0";
    return sb.toString();
    }
    //比较两个数大小,第一个大true,第二个大false
    public boolean compare(int a, int b){
        String sa = String.valueOf(a);
        String sb = String.valueOf(b);
        String suma = sa+sb;
        String sumb = sb+sa;
        for(int i = 0;i<suma.length();i++){
            if(suma.charAt(i)>sumb.charAt(i)) return true;
            if(suma.charAt(i)<sumb.charAt(i)) return false;
        }
        return true;
    }
}

至于这个代码的性能,我觉得是浪费在冒泡排序上了啊,我去优化一下。
改成快排变成7ms了。

class Solution {
    public String largestNumber(int[] nums) {
        qSort(nums,0,nums.length-1);
        StringBuffer sb = new StringBuffer();
    for(int i : nums){
        sb.append(String.valueOf(i));
    }
    if(sb.charAt(0)=='0') return "0";
    return sb.toString();
    }
    public void qSort(int[] nums,int start,int end){//快排
        if(start>=end) return;
        int l = start;
        int r = end; 
        int mid = nums[(start+end)/2];
        while(l<=r){
            while(l<end && compare(nums[l],mid)) l++;
            while(r>start && compare(mid,nums[r])) r--;
            if(l<=r){
                int t = nums[l];
                nums[l] = nums[r];
                nums[r] = t;
                l++;
                r--;
            }
        }
        if(start<r) qSort(nums,start,r);
        if(end>l) qSort(nums,l,end);
    }
    //比较两个数大小,第一个大true,第二个大false
    public boolean compare(int a, int b){
        String sa = String.valueOf(a);
        String sb = String.valueOf(b);
        String suma = sa+sb;
        String sumb = sb+sa;
        for(int i = 0;i<suma.length();i++){
            if(suma.charAt(i)>sumb.charAt(i)) return true;
            if(suma.charAt(i)<sumb.charAt(i)) return false;
        }
        return false;
    }
    
}

刚刚看了下性能好的代码,是直接把数字数组转化成String数组。其实这个确实是好办法,不然像我这样要来回来去转换,一个单词转换好多次。。
附上大佬的代码:

class Solution {
  public String largestNumber(int[] nums) {
        String[] numstr = new String[nums.length];
        for(int i=0;i<nums.length;i++){
            numstr[i] = String.valueOf(nums[i]);
        }
        quickSort(numstr,0,nums.length-1);
        // System.out.println(Arrays.toString(numstr));
        StringBuffer sb = new StringBuffer();
        if(numstr[0].charAt(0)=='0'){
            return "0";
        }
        for(String num:numstr){
            sb.append(num);
        }
        return sb.toString();
    }
    int compare(String a,String b){
        int l1 = a.length();
        int l2 = b.length();
        int l = l1+l2;
        int i=0;
        for(;i<l;i++){
            char ac = a.charAt(i%l1);
            char bc = b.charAt(i%l2);
            if(ac==bc){
                continue;
            }
            return ac-bc;
        }
        return 0;
        // System.out.println("a:"+a+",b:"+b+",cp:"+cp);
        // return cp;
    }
    void quickSort(String[] nums,int start,int end){
        if(start>=end){
            return;
        }
        int index = getIndex(nums,start,end);
        quickSort(nums,start,index-1);
        quickSort(nums,index+1,end);
    }
    int getIndex(String[]nums,int low,int high){
        String tmp = nums[low];
        while(low<high){
            while(low<high&&compare(nums[high],tmp)<=0){
                high--;
            }
            nums[low] = nums[high];
            while(low<high&&compare(nums[low],tmp)>=0){
                low++;
            }
            nums[high] = nums[low];
        }
        nums[low] = tmp;
        return low;
    }
}

今天的笔记就记到这里,如果稍微帮到你了记得点个喜欢点个关注,也祝大家工作顺顺利利!生活健健康康!~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容