老师你好,买了你这门数据结构,算法和leetcode,我是转专业有工作经验的,为了补基础现在一边上课一边自己刷leetcode

来源:2-2 二次封装属于我们自己的数组

慕数据3866775

2018-07-21

我看到有人列了刷题的这几个阶段

1.      不知道怎么刷题,对很多概念比如Big O, DP都陌生。每天纠结到底是看那个算法书/资料入门快。  

2.      在一个良好的IDE里,做Medium题目只要愿意想一般都能做出来,做不出来看答案也就懂了。Hard也能做出不少。除了有些复杂算法还在消化中,算法上基本已经入门。但脱离IDE写代码,就有点发怵。函数signature甚至函数名都老记错。搞个string操作还得Google一下API。非常依赖自动补全。很多新人以及多年经验的老人,可能都处于这个阶段。  

3.      除了偶尔遇到个Hard题得想很久,基本平时刷LC非常comfortable。同时能非常comfortable的在白板或者LC等网站的网页简单IDE里写代码。写代码过程中不需要Google函数用法。不依赖自动补全。而且甚至觉着这么写比在IDE里还舒服。  

4.      在3的基础上,一般题目都是20分钟一道,写完基本扫一下一检查就可以bug-free。Hard题噼里啪啦码一堆,不管逻辑再混乱,心里非常清楚自己码的是什么,复杂度多少,有没有优化潜力。且非常自信自己码的是对的。对于一次提交就AC是自己的基本要求。对参加LC contest(contest里都要求一次提交就AC,否则罚时)等业余竞赛比较comfortable

我现在显然是2到3之间,现在在传统公司做码农,为了去一个一流互联网公司在努力;但是算法一直很虚,虽然不停地学习但是有好多基础的东西也不牢固,我上这个课的时候听你说可以来问你算法题的问题,于是就厚着脸皮来问了,这个我google了很多但是可能太基础了并没有有人说清楚:

leetcode第389题 - Find the Difference

你看我的解法

http://img.mukewang.com/szimg/5b5244a300012fe307550286.jpg

整个题用异或来找到多出来的那个字符,整体没问题;但我想不明白的是为什么第四行ch初始值一定要是多一个字符那个字符串的最后一个字符,为什么不能是倒数第二个或者就正数第一个(结果是错的),这个关键点在哪里?比如两个字符串"abcd"和"ebadc",结果是'e',那初始值为c到底起到了什么作用,不都是a^b^b=a这个原理吗?

同样的,这个做法把char可以用数字来表示,两个String的每个字符相减然后多余数字转换成char就是缺少的那个字符:

http://img.mukewang.com/szimg/5b52479d0001742507460278.jpg

这个办法同样不明白为什么为什么charCode初始值得是较长字符串的最后一位字符,初始值是别的字符就会得到错误答案,我去IDE debug了还是没明白规律是怎么样的。

下面这种办法我就明白的,初始值为空白' ',然后直接从左到右异或就行了。

http://img.mukewang.com/szimg/5b5248170001cfe307510253.jpg

请老师解答一下前面两个解法的初始值应该怎么去理解,非常感谢!

写回答

1回答

liuyubobobo

2018-07-21

哈哈:)不用觉得自己脸皮厚,这个可既然说可以答疑Leetcode的所有问题,就是可以:)尤其像你把问题描述的这么清晰,非常欢迎。可见素质极高,继续努力,前途无量:)


说回你的问题。

其实很简单,关键是下面的那重循环。以你给的第一个Solution为例,下面的循环是从0到len-1访问每一个s[i]和t[i],注意:i这个索引同时作用在了s和t身上。


所以,想象一下,假设s有5个元素,t有6个元素:

index  0  1  2  3  4 
s:    s0 s1 s2 s3 s4
t:    t0 t1 t2 t3 t4 t5


你给出的解法相当于就是首先让ch=t5,然后,就不用考虑t5了!之后,可以把s和t看作是两个同样长度为5的字符串。我们在for循环中,就是以for i from 0 to 4,每次同时处理s[i]和t[i]了:)

想象一下,如果你让ch初始为t0,为了把s和t的所有元素都扫描遍,就不能只写一个循环了,需要写两个循环:)因为这两个循环不统一。理解一下下面的代码,我让ch初始值为t0,也可以AC:)

class Solution {    
    public char findTheDifference(String s, String t) {        
        char ch = t.charAt(0);        
        for(int i = 0; i < s.length(); i ++)            
            ch ^= s.charAt(i);                
            
        // 由于ch初始为t[0],所以遍历t要从1开始,到t的结尾        
        // 因为遍历s从0开始,遍历t从1开始,所以两个循环不能合并        
        // 当然,也可以合并,在遍历t的时候做一个索引的偏移,但是我个人不推荐这样做,因为语意不清:)        
        for(int i = 1; i< t.length(); i ++)            
            ch ^= t.charAt(i);        
            
        return ch;    
    }
}

而如果对于你给出的第一个解法,只是简单地把ch设置成t[0],其他逻辑不变。就会造成t[0]访问了两遍(初始时和循环时),而t的最后一个元素访问不到(因为循环长度到len-1)。


相信你已经理解了:)


你的第二个解法同理。不过注意,第二个解法中len的定义和第一个解法不同(第一个解法len是t的长度,第二个解法len是s的长度,造成边界不同)


加油!


1
1
慕数据3866775
非常感谢!谢谢老师,看第一段就明白了,然后你后面解释得还这么详细,我想这辈子都不会忘记了!
2018-07-21
共1条回复

玩转数据结构

动态数组/栈/队列/链表/BST/堆/线段树/Trie/并查集/AVL/红黑树…

6221 学习 · 1704 问题

查看课程