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

压缩字符串

题目:给定一组字符,使用原地算法将其压缩。压缩后的长度必须始终小于或等于原数组长度。数组的每个元素应该是长度为1 的字符(不是 int 整数类型)。在完成原地修改输入数组后,返回数组的新长度。

示例 1:
输入:
["a","a","b","b","c","c","c"]
输出:
返回6,输入数组的前6个字符应该是:["a","2","b","2","c","3"]
说明:
"aa"被"a2"替代。"bb"被"b2"替代。"ccc"被"c3"替代。
示例 2:
输入:
["a"]
输出:
返回1,输入数组的前1个字符应该是:["a"]
说明:
没有任何字符串被替代。
示例 3:
输入:
["a","b","b","b","b","b","b","b","b","b","b","b","b"]
输出:
返回4,输入数组的前4个字符应该是:["a","b","1","2"]。
说明:
由于字符"a"不重复,所以不会被压缩。"bbbbbbbbbbbb"被“b12”替代。
注意每个数字在数组中都有它自己的位置。
进阶:
你能否仅使用O(1) 空间解决问题?
注意:
所有字符都有一个ASCII值在[35, 126]区间内。
1 <= len(chars) <= 1000。

思路:好吧,我真心觉得这道题很难,难得我脑阔痛。归根结底怪我想太多。写着写着觉得太磨叽了,是不是我想错思路了,然后在继续和看题解中挣扎半天,但是因为这道题是今天第一道题,最终还是咬咬牙自己做。看审题:首先重要是空间复杂度O(1),所以就原地处理,什么map或者新数组存储肯定是不行了,还有提示数组长度小于1000。所以最大的连续字符不会超过一千,也就是只需要考虑百位数就可以了。虽然现在写完了性能也超过百分百,但是我还是觉得我自己写的有点墨迹。直接上代码吧

class Solution {
    public int compress(char[] chars) {
        int sum = 0;
        int index = 0;
        char temp = chars[0];
        for(int i = 0;i<chars.length;i++){
            if(temp==chars[i]){                
                sum++;
            }else{
                chars[index]=temp;
                index++;
                if(sum>1) {                   
                        if(sum/100!=0){
                            chars[index] = (char)(sum/100+'0');
                            index++;
                            sum = sum-(sum/100*100);
                            chars[index] = (char)(sum/10+'0');
                            index++;
                            sum = sum-(sum/10*10); 
                            chars[index] = (char)(sum+'0');
                            index++;
                        }else if(sum/10!=0){
                            chars[index] = (char)(sum/10+'0');
                            index++;
                            sum = sum-(sum/10*10); 
                            chars[index] = (char)(sum+'0');
                            index++;
                        }else{
                            chars[index] = (char)(sum+'0');
                            index++;
                        }                
                }
                temp= chars[i];                
                sum=1;
            }
        }
        chars[index] = chars[chars.length-1];
        // System.out.println(sum);
        if(sum>1) {    
            index++;               
            if(sum/100!=0){
                chars[index] = (char)(sum/100+'0');
                index++;
                sum = sum-(sum/100*100);
                chars[index] = (char)(sum/10+'0');
                index++;
                sum = sum-(sum/10*10); 
                chars[index] = (char)(sum+'0');
            }else if(sum/10!=0){
                chars[index] = (char)(sum/10+'0');
                index++;
                sum = sum-(sum/10*10); 
                chars[index] = (char)(sum+'0');
            }else{
                chars[index] = (char)(sum+'0');
            }                
        }      
        //index是下标,长度要+1
        return index+1;        
    }
}

其实主要逻辑分为:是否有相同字符连着,有则第一个输出。剩下的计数。
第二个逻辑是计数多少: 个十百位(长度原因不会有千位)要分开写,这个有点超出以前的习惯不能从小到大获取,必须从大到小获取。然后我上面的代码墨迹阶段主要就是这块。差不多的代码写了两遍,也是日狗了!
现在看了题解其实可以都放在循环里的,就是循环到数组外的一位。不过我当时确实是没想到。
现在看了下别人写的,逻辑差不多,而且我这里纯数字处理没用到字符串反而性能不错,至于代码墨迹还有的优化呢!估计都放到循环里就能好很多,我去试试。勉强算是优化完了,总之比之前的要好 :

class Solution {
    public int compress(char[] chars) {
        int sum = 0;
        int index = 0;
        char temp = chars[0];
        for(int i = 0;i<=chars.length;i++){
            if(i==chars.length || temp!=chars[i] ){
                chars[index]=temp;
                index++;
                if(sum>1) {                   
                        if(sum/100!=0){
                            chars[index] = (char)(sum/100+'0');
                            index++;
                            sum = sum-(sum/100*100);
                            chars[index] = (char)(sum/10+'0');
                            index++;
                            sum = sum-(sum/10*10); 
                            chars[index] = (char)(sum+'0');
                            index++;
                        }else if(sum/10!=0){
                            chars[index] = (char)(sum/10+'0');
                            index++;
                            sum = sum-(sum/10*10); 
                            chars[index] = (char)(sum+'0');
                            index++;
                        }else{
                            chars[index] = (char)(sum+'0');
                            index++;
                        }                
                }
                if(i!=chars.length) temp= chars[i];                
                sum=1;
            }else{            
                sum++;
            }
        }
        return index;        
    }
}

下一题吧,优化的我心累。

回旋镖的数量

给定平面上 n 对不同的点,“回旋镖” 是由点表示的元组 (i, j, k) ,其中 i 和 j 之间的距离和 i 和 k 之间的距离相等(需要考虑元组的顺序)。找到所有回旋镖的数量。你可以假设 n 最大为 500,所有点的坐标在闭区间 [-10000, 10000] 中。

示例:
输入:[[0,0],[1,0],[2,0]]
输出:
2
解释:
两个回旋镖为 [[1,0],[0,0],[2,0]] 和 [[1,0],[2,0],[0,0]]

思路:这个有点抽象,我是用图理解题目的。大概通俗点理解:一个标往前扔多少能往后退双倍的。换言之从扔点开始,扔达点和回旋后的落脚点距离始发点是一样的。再换言之:两个点连线,如果线的中点也存在说明这是两个回旋镖(因为可以往前扔也可以往后扔。)
其实这道题的难点在于点和点的距离,很不直观。而且不容易算。这里用到了勾股定理的规律:将两个点 位移,计算出两个点的横坐标的差距和纵坐标的差距为两个直角边。而两个点的距离就是斜边了。(如果这两个点本身就在一条线上,那么另一个具体就是0,所以距离计算也是对的。)
能计算出两个点的距离也就能计算出一个点和其他所有点的距离。如果距离其中一个点的两个点距离相等,说明多了两个回旋镖(因为可以往两个方向扔)。如果具体一个点的三个点距离相等是多了六个回旋镖(自己画草图看看,三个可以1-2,1-3,2-3,2-1,3-2,3-1)。同样四个的话是n*(n-1)而不是简单的多两个。
一个点做中点可以扔的回旋镖数求出来,再把每一个点都累加一遍,这道题就做完了,讲真,我觉得这个不仅仅是简单难度的,接下来贴代码:

class Solution {
    public int numberOfBoomerangs(int[][] points) {
        int res = 0;
        Map<Integer,Integer> map = new HashMap<Integer,Integer>();
        for(int i = 0;i<points.length;i++){
            map.clear();
            for(int j = 0;j<points.length; j++){
                if(i==j){
                    continue;
                }
                //三角形两直角边平方和等于斜边平方和
                int d = (points[i][0] - points[j][0])*(points[i][0] - points[j][0])+(points[i][1]-points[j][1])*(points[i][1]-points[j][1]);
                if(map.containsKey(d)){
                    res += 2*map.get(d);
                    map.put(d,map.get(d)+1); 
                }else{
                    map.put(d,1);
                }
            }
        }
        return res;    
    }
}

下一题,不知道为什么,现在觉得每个题都贼难。。

找到所有数组中消失的数字

题目:给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。找到所有在 [1, n] 范围之间没有出现在数组中的数字。您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。

示例:
输入:
[4,3,2,7,8,2,3,1]
输出:
[5,6]

思路:这个题说简单就是会编程就能做,说难是不使用额外空间切时间复杂度O(n)。目前思路是先排序再挨个判断哪个数字是被跳过的。我也不知道排序能不能用,反正第一思路是这样。
第一版代码出炉,我觉得今天可能早晨起床没带脑子,反正性能差的一批:

class Solution {
    public List<Integer> findDisappearedNumbers(int[] nums) {
        Arrays.sort(nums);
        int j = 1;
        List<Integer> res = new ArrayList<Integer>();
        for(int i = 0;i<nums.length;i++){
            if(nums[i]==j){
                j++;
            }else if(i!=0 &&nums[i]==nums[i-1]){
                continue;
            }else{
                res.add(j);
                i --;
                j ++;
            }
        }
        for(int k = j;k<=nums.length;k++){
            res.add(k);
        }
        return res;
    }
}

其实逻辑说简单也简单。j是判断应该到了哪个数字。i是判断遍历到哪个数字。如果碰到相同的直接跳过,如果是正常的j正常++。如果遇到不是重复但是也不是应该有的数字说明这块有缺的了,先添加到list中,然后让j正常++。但是i得--这样下一次遍历还是判断这个数字是不是下一个。自觉哪怕是连着却的数字也能发现。
我总觉得我这么写肯定是忽略了什么省事的方法,因为这样空间复杂度满足了可是时间复杂度应该不是O(n)。我再想想思路。
看到了一种很秀的思路:把每一个出现的数字的数字位改成负数。然后遍历(判断的时候要取这个数的绝对值)。遍历一遍后数组中还是正数的位数说明缺这个数字(记得下标要+1才是位数)。
只能说真的秀!比什么鸽子原理什么的看着易懂还简单。我去试着写代码:

class Solution {
    public List<Integer> findDisappearedNumbers(int[] nums) {
        for(int i=0;i<nums.length;i++){
            //一定要判断是不是大于0.不然一个数负两次,负负得正了
            if(nums[Math.abs(nums[i])-1]>0){
                nums[Math.abs(nums[i])-1] =  -nums[Math.abs(nums[i])-1];
            }          
        }
        List<Integer> res = new ArrayList<Integer>();
        for(int j=0;j<nums.length;j++){
            if(nums[j]>0){
                res.add(j+1);
            }
        }        
        return res;
    }
}

看了下性能靠前的代码,都是用了额外空间了,我还是下一题吧。

最小移动次数使数组相等

题目:给定一个长度为 n 的非空整数数组,找到让数组所有元素相等的最小移动次数。每次移动可以使 n - 1 个元素增加 1。

示例:
输入:
[1,2,3]
输出:
3
解释:
只需要3次移动(注意每次移动会增加两个元素的值):
[1,2,3] => [2,3,3] => [3,4,3] => [4,4,4]

思路:哎,现在别说思路了,审题都得审一会儿,反正大概思路就是每次最大的数不要动,其余的+1,一直加到所有数组元素都相等。我去理理思路。

我刚刚灵机一动!!!我觉得自己简直聪明绝顶了,所有除了最大元素+1.和最大元素-1.虽然数值不一样,但是本质上相等比较是一样!!!
所以每次最大元素减1.什么时候减到所有元素相等就行了!!!简直被我自己的机智感动哭了~~~完美测试通过,虽然性能不是那么好,但是真的挺无脑的,哈哈,这道题比想的简单多了。

class Solution {
    public int minMoves(int[] nums) {
        Arrays.sort(nums);
        int res = 0;
        for(int i=0;i<nums.length;i++){
            res += nums[i]-nums[0];
        }
        return res;
    }
}

我发现我上面最大的失误就是sort排序,其实只要知道最小值就行了,没必要排序,所以这里用一个循环自己找出最小值比sort性能要好。

class Solution {
    public int minMoves(int[] nums) {
int min = nums[0];
        int res = 0;
        for (int i = 0; i < nums.length; i++) {
            if (min > nums[i]) min = nums[i];
        }
        for (int i = 0; i < nums.length; i++) {
            res += nums[i] - min;
        }
        return res;
    }
}

这道题思路很顺,我觉得我还是喜欢晚上学习,比白天效率要高的多。

分发饼干

题目:假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。注意:你可以假设胃口值为正。一个小朋友最多只能拥有一块饼干。

示例 1:
输入: [1,2,3], [1,1]
输出: 1
解释
你有三个孩子和两块小饼干,3个孩子的胃口值分别是1 2 3
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。
示例 2
输入: [1,2], [1,2,3]
输出: 2
解释:你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。所以你应该输出2.

思路:最麻烦的来说就是两个数组排序,从最小开始挨个比对(饼干小于孩子胃口就饼干往后,孩子不变),一直比对到饼干没有了。这个时候比过的孩子就是可以喂饱的孩子。
这只是一个初步的思路,具体的内容还要慢慢实现,我先去写代码。写完出来了,这个思路没问题,还很简单:

class Solution {
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s); 
        int lg = 0;
        int sg = 0;
        while(lg<g.length && sg<s.length){
            if(g[lg]<=s[sg]){
                lg++;
                sg++;
            }else {
                sg++;
            }
        }
        return  lg;
    }
}

就是如图,能满足则孩子饼干都往下走,不然孩子不走饼干走。
不知道是晚上做的题都简单还是说白天思路不好,一整个白天做了两道题,结果晚上一个多小时做了三道。哈哈。
今天的笔记就记录到这里了,如果稍微帮到你了记得点个喜欢点个关注呦~!也祝大家工作顺顺利利!

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

推荐阅读更多精彩内容