刷leetCode算法题+解析(四十三)

周末愉快,刷题继续。

卡牌分组

题目:给定一副牌,每张牌上都写着一个整数。此时,你需要选定一个数字 X,使我们可以将整副牌按下述规则分成 1 组或更多组:每组都有 X 张牌。组内所有的牌上都写着相同的整数。仅当你可选的 X >= 2 时返回 true。

示例 1:
输入:[1,2,3,4,4,3,2,1]
输出:true
解释:可行的分组是 [1,1],[2,2],[3,3],[4,4]
示例 2:
输入:[1,1,1,2,2,2,3,3]
输出:false
解释:没有满足要求的分组。
示例 3:
输入:[1]
输出:false
解释:没有满足要求的分组。
示例 4:
输入:[1,1]
输出:true
解释:可行的分组是 [1,1]
示例 5:
输入:[1,1,2,2,2,2]
输出:true
解释:可行的分组是 [1,1],[2,2],[2,2]
提示:
1 <= deck.length <= 10000
0 <= deck[i] < 10000

思路:这道题的思路也很简单,首先不能有落单的元素,其次每个元素要么是最小元素数。要么是最小元素个数(最小元素数大于等于2)然后我实现了。
这道题其实实现起来还是很麻烦的,真正做了才发现不仅仅是最小元素和最小元素倍数这么简单,而且比较是否有公因数。比如 15 27 81,不是倍数关系但是可以true,因为都是3的倍数。然后麻烦就麻烦在判断是否有公因数了。我是这么做的:找到第一个数的所有大于2因数存起来,然后挨个因数对比是不是给定数的因数,不是移除元素。是则继续判断。什么时候数组元素为0了,则说明这写数没有共同因数,返回false。一直不为0说明有共同因数,返回true。直接贴代码:

class Solution {
    List<Integer> b;
    public boolean hasGroupsSizeX(int[] deck) {
         b = new ArrayList<Integer>();
        int[] ar = new int[10000];
        for(int i : deck){
            ar[i]++;
        }
        int flag = -1;
        for(int i : ar){
            if(i>0){
                if(i==1) return false;
                if(flag==-1){
                    getB(i);
                    flag = 0;
                }else{
                    if(isB(i)) return false;
                }
            }
        }        
        return true;
    }
    //获取一个数的所有因数(2以上的)
    public void getB(int i){
        for(int n = 2;n<=i;n++){
            if(i%n == 0){
                b.add(n);
                if(i/n>2) b.add(i/n);
            }
        }
    }
    //获取这个数和所有因数数组的共同因数。不是共同因数的则remove
    public boolean isB(int i){
        for(int n = b.size()-1;n>=0;n--){
            if(i%b.get(n)!=0) b.remove(n);
        }
        return b.size()==0;
    }

}

这个性能不太好,只超过了百分之七十多的人,我去看看排行第一的代码:

class Solution {
    public boolean hasGroupsSizeX(int[] deck) {
        int[] t = new int[1000];
        for (int i : deck) {
            t[i]++;
        }
        int g = -1;
        for (int i = 0; i < 1000; i++) {
            if (t[i] > 0) {
                if (g == -1) {
                    g = t[i];
                } else {
                    g = gcd(g, t[i]);
                }
            }
        }
        return g >= 2;
    }

    public int gcd(int x, int y) {
        if (x == 0) {
            return y;
        } else {
            return gcd(y % x, x);
        }
    }
}

看着很简洁(相比较)。递归又是循环的,但是我没太看明白。主要是心态也不太好,所有这个留着以后参考吧。我就用我的”笨“方法了。

仅仅反转字母

题目:给定一个字符串 S,返回 “反转后的” 字符串,其中不是字母的字符都保留在原地,而所有字母的位置发生反转。

示例 1:
输入:"ab-cd"
输出:"dc-ba"
示例 2:
输入:"a-bC-dEf-ghIj"
输出:"j-Ih-gfE-dCba"
示例 3:
输入:"Test1ng-Leet=code-Q!"
输出:"Qedo1ct-eeLg=ntse-T!"
提示:
S.length <= 100
33 <= S[i].ASCIIcode <= 122
S 中不包含 \ or "

思路:类似的题做过很多,这个也没觉得多复杂。很幸运没有各种进阶条件(比如不需使用额外空间)这么说的话,我第一思路就是获取字符串所有字母的下标。然后根据下标顺序交换,然后就是答案了~~我去实现了。
emmmmm...题很简单,实现容易,性能不行。先贴第一版本代码:

class Solution {
    public String reverseOnlyLetters(String S) {
        char[] x  = S.toCharArray();
        int[] index = new int[x.length];
        int idx = 0;
        for(int i = 0;i<x.length;i++){
            if(Character.isLetter(x[i])){
                index[idx] = i;
                idx++;
            }
        }
        int l = 0;
        int r = idx-1;
        while(l<r){
            char temp = x[index[l]];
            x[index[l]] = x[index[r]];
            x[index[r]] = temp;
            l++;
            r--;
        }
        return new String(x);
    }
}

怎么说呢,这个题我觉得处理也就这样了,怎么优化也没啥思路,我再看一会。
看了性能第一的代码,思路大同小异!!但是人家自己实现的判断一个char是不是字母,然后就0ms第一了。我使用的现有api,然后就1ms第八十多了。。。我能说什么?我也很无奈啊~~~贴上代码下一题了:

class Solution {
    public String reverseOnlyLetters(String S) {
        int start=0,end=S.length()-1;
        char[] chars = S.toCharArray();
        while (start<end){
            if(!isLetter(chars[start])){
                start++;
                continue;
            }
            if(!isLetter(chars[end])){
                end--;
                continue;
            }
            char temp=chars[start];
            chars[start]=chars[end];
            chars[end]=temp;
            start++;
            end--;
        }
        return new String(chars);
    }

    public boolean isLetter(char c){
        return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z';
    }
}

按奇偶排序数组2

题目:给定一个非负整数数组 A, A 中一半整数是奇数,一半整数是偶数。对数组进行排序,以便当 A[i] 为奇数时,i 也是奇数;当 A[i] 为偶数时, i 也是偶数。你可以返回任何满足上述条件的数组作为答案。

示例:
输入:[4,2,5,7]
输出:[4,5,2,7]
解释:[4,7,2,5],[2,5,4,7],[2,7,4,5] 也会被接受。
提示:
2 <= A.length <= 20000
A.length % 2 == 0
0 <= A[i] <= 1000

思路:我很感谢又来一道送分题让我今天的目标可以更快的达到。这道题思路就是两个下标点。一个0开始,每次加2.一个1开始每次加2.奇数用1开始的那个,偶数用0开始的那个,一次遍历over。我去实现了
思路不错,主要是题目简单,一次过:

class Solution {
    public int[] sortArrayByParityII(int[] A) {
        int[] res = new int[A.length];
        int i = 0;
        int n = 1;
        for(int num : A){
            if(num%2==0){
                res[i] = num;
                i = i+2;
            }else{
                res[n] = num;
                n = n+2;
            }            
        }
        return res;
    }
}

又看了性能第一的,深深的感觉愧疚,没有给自己增加难度!!怎么题目不说不让额外空间就真不用了呢??看看人家,没用额外空间性能还好。。。好的吧,继续说题目,人家没用额外空间,在原数组的基础上判断的:

class Solution {
    public int[] sortArrayByParityII(int[] A) {
      // 0. traversal
      for (int e = 0, o = 1; e < A.length; e += 2) {
        // 1. 如果偶数位置是奇数(&1等于0说明是偶数,位置正确继续往下)
        if ((A[e] & 1) == 0)
          continue;
        // 2. 找奇数位置的偶数
        while ((A[o] & 1) == 1)
          o += 2;
        int temp = A[e];
        A[e] = A[o];
        A[o] = temp;
      }
      return A;
    }
}

讲真,我做用的都是现成的数字运算,而这种方法用到了二进制运算。偶数&1是0.奇数&1是1.偶数位判断,位置正确则继续往下判断,位置不正确和另一个位置不正确的互换。很精巧的方法。属于那种看了恍然大悟,不看压根没那么想。
下一题吧,这道题的最大收获就是如何二进制判断奇偶数。

长安键入

题目:你的朋友正在使用键盘输入他的名字 name。偶尔,在键入字符 c 时,按键可能会被长按,而字符可能被输入 1 次或多次。你将会检查键盘输入的字符 typed。如果它对应的可能是你的朋友的名字(其中一些字符可能被长按),那么就返回 True。

示例 1:
输入:name = "alex", typed = "aaleex"
输出:true
解释:'alex' 中的 'a' 和 'e' 被长按。
示例 2:
输入:name = "saeed", typed = "ssaaedd"
输出:false
解释:'e' 一定需要被键入两次,但在 typed 的输出中不是这样。
示例 3:
输入:name = "leelee", typed = "lleeelee"
输出:true
示例 4:
输入:name = "laiden", typed = "laiden"
输出:true
解释:长按名字中的字符并不是必要的。
提示:
name.length <= 1000
typed.length <= 1000
name 和 typed 的字符都是小写字母。

思路:这道题反正我第一次看没看到有什么坑,应该是很简单的。主旨就是输入字符串有的字符数量名字不一定要有那么多,但是名字有的字符数量输入字符串一定要有。就是一个简单的char对比我觉得,我先去实现试试。
思路没问题,而且直接性能超过百分百,给自己点个赞,哈哈~~贴代码:

class Solution {
    public boolean isLongPressedName(String name, String typed) {
        char[] x = name.toCharArray();
        char[] y = typed.toCharArray();
        int ix = 0;
        int iy = 0;
        if(x[0]!=y[0]) return false;
        while(ix<x.length && iy<y.length){
            if(x[ix]==y[iy]){
                ix++;
                iy++;
            }else if(y[iy]!=y[iy-1]) {
                return false;
            }else{
                iy++;
            }
        }
        return ix==x.length;
    }
}

这个其实分两种:一种是去了长按相等的,一种是去了长按不等的。不等又有两种情况。一种是原名字中有的现在没了,一种是原名字中没有的现在多了。
我这个便利第一步:两个都有的直接略过往下判断。然后如果是长按的元素(要等于上一个元素)输入串继续往下遍历。但是如果遍历到不是长按的元素,但是还不是名字中应有的元素,不管是原名字中有的现在没了,一种是原名字中没有的现在多了。反正都是不对了,直接返回false。
最后遍历完了还有两种情况,是名字遍历完了都符合,还是输入字符串遍历完了但是名字没遍历完。所有要判断一下是不是名字遍历完了。
(ps:刚刚写到这顺便发现了题目有个bug。如果遍历完了名字,但是输入串还有额外的字符理论上讲应该也是错误的。但是我这里疏忽了居然过了。已经提交leetcode,不知道结果如何)
这道题就到这里吧,反正思路比较简单,而且代码也不复杂,最后一题。

独特的电子邮件地址

每封电子邮件都由一个本地名称和一个域名组成,以 @ 符号分隔.例如,在 alice@leetcode.com中, alice 是本地名称,而 leetcode.com 是域名。除了小写字母,这些电子邮件还可能包含 '.' 或 '+'。如果在电子邮件地址的本地名称部分中的某些字符之间添加句点('.'),则发往那里的邮件将会转发到本地名称中没有点的同一地址。例如,"alice.z@leetcode.com” 和 “alicez@leetcode.com” 会转发到同一电子邮件地址。 (请注意,此规则不适用于域名。)如果在本地名称中添加加号('+'),则会忽略第一个加号后面的所有内容。这允许过滤某些电子邮件,例如 m.y+name@email.com 将转发到 my@email.com。 (同样,此规则不适用于域名。)可以同时使用这两个规则。给定电子邮件列表 emails,我们会向列表中的每个地址发送一封电子邮件。实际收到邮件的不同地址有多少?

示例:
输入:["test.email+alex@leetcode.com","test.e.mail+bob.cathy@leetcode.com","testemail+david@lee.tcode.com"]
输出:2
解释:实际收到邮件的是 "testemail@leetcode.com" 和 "testemail@lee.tcode.com"。
提示:
1 <= emails[i].length <= 100
1 <= emails.length <= 100
每封 emails[i] 都包含有且仅有一个 '@' 字符。

思路:首先这种到底有多少种类的问题!第一反应是set,map这种唯一性的。而这道题没必要key-value。所以暂定返回的数组格式是set.size()。所以这道题目前思路就是遍历原数组。每一个邮件的最终地址add进set,最后返回set中的元素。我去实现了。
很好,一顿操作贼,五分钟搞定问题,然后性能只超过百分之三十三的人。。。笑死了。先贴上代码;

class Solution {
    public int numUniqueEmails(String[] emails) {
        Set<String> set = new HashSet<String>();
        for(String s :emails){
            String[] str = s.split("@");
            String[] ss = str[0].split("\\+");
            String r = ss[0].replace(".","")+"@"+str[1];
            set.add(r);
        }
        return set.size();
    }
}

其实我个人都知道为啥性能这么差,各种字符串分割替换操作,能好就怪了。但是架不住省事啊。都是现成api。哈哈。行了,不闹了,说说优化:其实转成char数组操作绝对不会差的。但是麻烦多了。哎,为了性能我去麻烦麻烦了。

class Solution {
    public int numUniqueEmails(String[] emails) {
        Set<String> set = new HashSet<String>();
        for(String s :emails){
            StringBuffer sb = new StringBuffer();
            boolean flag = true;
            boolean flag2 = false;
            for(char c :s.toCharArray()){
                if(c=='+') flag = false;
                if(c=='@') {
                    flag = true;
                    flag2 = true;
                }
                if(c=='.' && flag2) sb.append('.');
                if(flag && c!='.') sb.append(c);
            }
            set.add(sb.toString());
        }
        return set.size();
    }
}

性能超过百分之八十五,虽然还没及格但是已经进步多了。我继续看看怎么优化。
很好,又换个思路,性能超过百分之九十七的人了,凭自己及格,贴上代码:

class Solution {
    public int numUniqueEmails(String[] emails) {
        Set<String> set = new HashSet<String>();
        for(String s :emails){
            StringBuffer sb = new StringBuffer();
            int index = s.indexOf("@");
            char[] c = s.toCharArray();
            for(int i = 0;i<index;i++){
                if(c[i]=='+') break;
                if(c[i]!='.')sb.append(c[i]);                
            }
            sb.append(s.substring(index));
            set.add(sb.toString());
        }
        return set.size();
    }
}

其实本来都用字符判断会有很多无用的判断,但是改进完即前面的判断做好了,又最后@后原样照抄做好了。
今天的笔记就做到这里。如果稍微帮到你了记得点个喜欢点个关注~~每天进步一点点!另外祝大家周末愉快,工作顺顺利利呦!

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

推荐阅读更多精彩内容

  • “他根本不爱你” “嗖”的一声,一辆帅气的卡宴停在我们的车旁。她一脸沮丧的走下来,一上我们的车就噼里啪啦说个不停,...
    Catherineliao阅读 510评论 0 1
  • 下雨,一整天,烦郁..... 原来,我们都是容易害怕的人,你害怕的我努力去猜想,没结果。而我,害怕失去,害怕的太多...
    HR浅末阅读 322评论 0 5
  • 呵呵,我们已经在一起3个多月了,干什么都依你,我很爱你,可是你和你的前对象还来的情相思,我只笑笑不说话
    fba8e0c70629阅读 222评论 0 0
  • 歌词里说:再见,就是再也不见,过去我们不再留恋。而我的再见,是一定要再相见。 从今天起,跟简书告别,说一声再见。十...
    云紫烟阅读 457评论 9 9
  • 为什么这么说呢? 一家公司除了老板 其他什么亲戚、蜜友、高管、哪怕是小蜜 在公司体系里最终都会拿个人职业价值说事 ...
    小文新声阅读 423评论 0 0