LeetCode笔记

java中main 方法为什么必须要是 public static void 声明

在资料中找到的是:
	1. 正因为main方法是静态的,JVM调用这个方法就不需要创建任何包含这个main方法的实例。
	2. 因为C和C++同样有类似的main方法作为程序执行的入口。
	3. 如果main方法不声明为静态的,JVM就必须创建main类的实例,因为构造器可以被重载,JVM就没法确定调用哪个main方法。
	4. 静态方法和静态数据加载到内存就可以直接调用而不需要像实例方法一样创建实例后才能调用,如果main方法是静态的,那么它就会被加载到JVM上下文中成为可执行的方法。
1. 个人的理解是,14可以被理解为是main作为静态方法被调用是不需要创建对象,毕竟在java中普通方法都是依赖于类来实现的,相当于被包在了类中来实现,而static方法可以不去创建类的实例而直接被实现,况且如果jvm需要创建实例去使用main方法,那谁来创建实例同样是一个问题,这样就套娃了。所以需要设置static。

2. 第二点,个人感觉类似于python中的 
	if __name__ == __main__
这个声明,即某个类中的main方法只会在单独解释该类文件时才会被调用,而在作为一个类被引用时是不会被调用的,

3. 因为java中jvm对于方法的调用有明确的分级,将main方法设定为public能更方便被jvm调用。

4. 因为main方法形参是 String[] args,那么如果不声明为静态,那么jvm需要创建main类的实例,那因为java构造器可以重载,这样不确定args的数量也就会让jvm不知道调用哪一个构造器了。所以需要声明为static。

5. main方法本来就作为了程序的入口,相当于告诉了这个方法就是最终逻辑,所以也就不存在需要传递参数的情况,那么就没必要需要返回值,设定为void也就很合理。

对于java的继承,多态,super的用法,以及对虚函数,抽象类,接口的深入理解?

leedcode两数相加的个人理解思考

对于单向链表:
	因为单向链表是不可逆的,所以需要考虑指针的所在位置,按照一般逻辑一直往后加会导致无法复原,找不到头指针那么得到的链表也就完全找不到了。保留头部指针是非常关键的。
	拆分方法从而复用是很好地习惯,不仅让代码整洁,方便debug,而且可以省去很多写重复代码的时间。
	通过浅拷贝可以让工具指针实例去遍历主指针的所有内容,非常好用。

解题思路是,首先创建一个初始化的结果指针,然后不动他,在创建一个工具人指针指向结果指针的next,通过对工具人指针的利用,不断拓展结果指针的链条。
对于输入的两个链表l1 ,l2, 需要将他们往后遍历,直到两个指针都为空运算结束。
把l1, l2所在位置的值加起来,需要进一位的先把他暂时放在结果指针的尾部,在下一步操作的时候再一起加起来。
int类型数据是不能为null的,所以说需要额外去判断,如果为空那么值应该是0.
如果其中有一个链表已经到底而另一个没到,那么就不能把他的指针再往后指,否则会报错,可以用判断让他到底后往后移位。
先做所在位数的运算还是先做下一位数的进制很重要,否则会出现9999999+999 运算出现错误。一定要逻辑来。
由于每次只能知道两位数的值,所以遍历的话不能先去考虑链表尾带0的情况。或者可以先遍历两个链表,确定最高位所在下标后有目的地去操作?确实会是个更好的办法。
如果像我一样用笨方法,那么需要写一个反转的方法,我使用的是递归的思想先把链表拆解再从最后往前拼,但是很麻烦,需要用到全局变量不然难以传参。
在反转后再把指针一直移到非0的位。其中需要判断是不是到了只剩下一位的情况,有可能结果是000000,不考虑的话会出现空指针没有next的报错。

做得少,走了很多弯路,算法思路也不够清晰,但是解题过程比较专注冷静,很享受这种感觉,开了一个好头。

判断int类型为空

String.valueOf(int).equals(""),int作为基础数据类型不能用null来作为判断

java中的变量类型

在java的递归过程中,涉及到三种变量,实例成员变量,局部变量,静态变量
成员变量就是在构造器里面的变量,是可以被外部看到的,类似于结构体里面的数据,局部变量就是方法里临时声明的变量。而静态变量需要加上static||finials 关键词,在递归中是可以累加的,也就是全局变量。

重复子串的map映射

详细介绍参考:https://blog.csdn.net/qq_43555323/article/details/84954245
通过 Map<Obj,Obj> var = new Maptype<Obj,Obj>();来声明
map类型类似于映射,通过键对值查找,类似于数组查找比数组快

审题出错导致浪费时间

在做题目的时候一定一定先清楚知道题目需要做什么。不要先入为主,急着答题。通过认真理解题意也可以让逻辑更加清晰。
因为审题不当,这次对于简单的题目浪费了很多时间,尤其是在完全没必要用hashmap的情况下硬用,最后导致很不方便,完全没有体现hashmap的优点。

解题过程

截取最短子串需要考虑遇到重复字符后从哪里继续截取
比如对于 
	asdasdfgf
在子串为	asda	时,不能简单地把asd截取掉,然后从第二个a开始往后走,因为可能存在	asdfaqw	这种情况,显然	asdf 比 sdfaqw 短。

思路应该是截掉重复字符第一次出现的前面的所有部分
比如 asdfd--, 在进行第二个d时应该去掉 asd 继续往后走。完全不用重新开始。
在每次需要进行截取操作时判断是否需要更新最大子串长度值。
在逻辑上,应该在读完字符串后把最后留下来的字符串长度与保存的字符串长度进行对比,因为最长的那个字符串可能是在截取过程中截掉的,也可能在最后被保留下来了。

积累

对于Character类型,如果需要判断是否为空,不能用 null 来判断,可以通过var+“”==null来判断,即把char类型转化为String类型。
因为基础数据类型不是对象,没有什么方法在里面,所以不能去判断null之类的。
String.charAt 找字符串指定下标的字符。

二分法找出两个数组中位数

目的要找到两个有序数组的中位数,并且需要时间复杂度小,那么需要用到二分法。
首先最直观的描述是将第一个数组当成目标数组。依次从第二个数组中拿出元素用二分法插入第一个数组,直到插入的位置下标值大于或者等于中位数的位置下标数值(m+n)/2。然后计算第一个数组的中位数值。
但是这种方法需要用到数组的插入,对于数组而言很复杂,所以需要更好的思路。
思路是:
1. 首先从第二个数组中依次拿出元素对比第一个数组的中间一个元素。2. 如果取出的值大于第一个数组的中间一元素数值,并且如果第一个数组的前一半元素个数(即中位数的下标)小于目标中位数的下标,那么可以把小于中位数的下标的元素当成目标中位数的献祭,不用继续参与目标中位数判断,因为中位数肯定在中位数后面的元素或者第二个数组。并且在进行后续重复时需要把中位数后半段的中位元素取出来进行操作。
3. 如果取出的元素小于第一个数组的中间一元素数值,那么说明取出的元素在中位数的左半边,如果中位数的下标加上取出元素的下标加一小于目标中位数的下标,那么直接取第二个数组中的第二个元素继续跟第一个数组的中位数进行判断,一直重复 1. 3. 步骤直到:
4. 如果当2. 个步骤进行到中位数下标加上从第二个数组中拿出的元素的下标超过了目标中位数的下标值,说明目标的中位数就在还没被献祭的第一个数组元素与中位数元素之间的元素或者已经拿出的元素之间。这时候需要取出第二个数组前一次取出的元素,下标减一即可。然后将中位数换成第一个数组还没被献祭的第一个数组元素与中位数元素之间的元素的中位数继续进行 2. 3. 操作。
5. 如果当3. 步骤进行到中位数的下标加上取出元素的下标加一大于目标中位数的下标,说明在目标中位数就在中位数前面的元素与从第二个数组中被取出的元素之间,这时直接可以断定目标中位数一定是当前中位数,或者是目标中位数加上(前一位或者当前取出的元素)
6. 其中需要额外int 变量来记录献祭的元素下标

寻找最长回文子串

要求找出最短的回文子串,最直接的逻辑首先设定一个int 类型数记录预测长度,这个数初始大小设为s.length()。即尽量先去判断有可能的最长的子串,如果是那么可以停止继续判断,如果不是那么将可能的回文子串长度减一,继续测试。
直到找到后停止判断,或者记录数值减为1自然终止循环,这时说明该源字符串没有长度大于一的子串回文。
其中需要提前设定一些int 类型数来记录找到的最长子串的起始下标。
原理不难,但有许多逻辑上的细节在编程过程中才能体会。

优化算法

我用到的是通过设定一个int 类型的predict值,来预测这个点是否是最长回文的中心点。
考虑到如果回文,那么一定存在一个中心线,子串是基于中心线对称的。
那么只需要考虑这个中心线所在的位置即可。
不断更新中心线的下标,即从前往后推移中心线,直到中心线下标predict超出了数组,或者目前已经被找到的最长子串长度大于剩余部分的对称子串长度。即
	(s.length()-predict-1)*2 < maxLen
终止判断。
后者是因为当中心线移动到字符串后半部分的位置,可能出现当前情况的最优解也小于已经找到的最优解,那么再继续移动中心线也只会让情况更加糟糕,不可能有更好的情况出现,那么即可终止循环。
在程序中需要初始化一些int 类型变量用于记录当前最长子串的起始下标以及长度。随后在中心线下标不断移动中进行更新。
在确定中心线后,需要以中心线为轴心将下标往两段移动判断,如果相等那么回文长度自加,否则直接结束循环。
如果当前情况回文长度大于最优回文长度则更新。

需要注意的是,中心线可能在某一元素上,也可能在两元素中间,那么回文就有可能为奇数或者偶数,这时在逻辑方面需要细致判断,容易忽略判断造成程序失败。
我采用的是设定开关为 int status, 初始为 0;
	if (status == 0){
		status = 1;
        continue;
     }
     else {
        status = 0;
        predict++;
      }

而这个status也可以放在逻辑语句中,可以避免分两种情况讨论而重复编程。同时在确定由中心线确定最长回文实际长度时也需要用到。十分有意思。

Z字型变换

首先要理解题目意义,是将一个字符串按z字形排列还是把一个原本已经按z字形排列的字符串转化成普通字符串,显然是后者。
重点主要是搞清楚z字形每一行元素之间下标的数值规律。
比如对于 长度为 7 的字符串,排列成 4 行的话,第i行的相邻下标遵循
 2*(numRows - i-1) 或 2*i 规律振动  
因为z字形是周期的排列,那么肯定存在一个固定周期。
而当知道了每两个元素之间的振动规律,只需要依次让初始下标累加获得下一个字符,然后再依次加到目标字符串中。
容易犯错误的一点是,想当然的认为应该设其中一种周期固定为 T,这是不对的。
例如 如果我们想当然认为这个z字形的固定周期是 4,但是这个只对第一行数据有效,对于后续行就会出现问题,因为元素排列并非线性。
搞懂其中的振幅,这个题目也就不难了。

整型反转

心态爆炸的一道题目。定性为简单
我采用的方法是,首先用二分法找到最高位数,确定了最高位数后进入for循环,依次将原值从个 十 百.. 位往前推,同时将目标result乘上应该的指数。
花了太多时间在数字换位上,明明主要方法已经弄好了,硬是换了半天。。
而后面在确定传入值是否超过范围上找不到合适的方法,后来通过看答案得知可以先用一个long 型变量存放结果值,最后如果long型变量转化成整型与原值不相同,那么即可判断内存溢出。
long result;
if ((int)result != result) return  0;
十分有效。

看了一下更优的方法,只需要持续将原数反复取模10,同时每取一次就将原数除以10,就可以持续得到剩余的单独尾数,非常巧妙省事,根本不用考虑位数。直到取完结束。
心态爆炸。

目前规划与当前状态总结

经过这段时间的学习感觉自己很适合这种学习状态,同时也有些可惜曾经因为迷茫焦虑惰性蹉跎的时光。
由于对于自己想要做的事情有了更加清晰的认识,我思考了一下后续需要做的事情,觉得很有必要在每个不同的学习阶段指定需要去做的事情。

在大学中对于计算机的底层原理没有深入去了解是我犯下的错误,所以现在在这种更加艰难的时期去弥补也是我自己的救赎,更何况我还很享受这种状态。

考虑到我想要做的事情是安全渗透方向与算法,那么我需要还学习的东西依次为:
	1. 算法与数据结构
	一切的基础,是整个计算机科学的核心,需要足够地深入了解算法与数据结构的奥妙。采取的学习方法是,一方面在LeetCode上做题深入思考总结,在应用中理解算法与数据结构。另一方面研读算法与数据结构相关书籍,不考虑在网上上视频课学习。
	
	2. 计算机网络
	为之后的安全渗透方向打好基础。之前学到的知识过于宽泛浅薄,所以需要自己更深入理解计算机网络,这将会大大加快后续的学习进度。
	
	3. 在前期系统学习算法与数据结构,计算机网络后需要开始专注于对当今流行技术的学习,以适应现实的社会。并且在这个阶段以工作为目标有目的地进行学习,学习内容多参考业内人与招聘网站的要求。
	
在学习中焦虑的情绪得到了很大的缓解,或许这本来就是我应该走的路,但我做错了,所以走慢了,远远地落在了其他人后面。不要怕被指责,被看不起,被嘲讽,这本来就是我应该承担的,是我自己犯的错我就应该承担后果。蛰伏修炼是我需要维持的一种状态。

状态记录

在解答一个正则匹配的题目时陷入了瓶颈。
花费了很多时间思考,尝试,结果始终感觉一团糟,而自己也越做越急。
随后开始了一个新的题目,仍然是思路不够清晰。
有点吃力,感觉自己是不是方法有问题。
在反思了之后,我想为了能够更好的学习,我需要调整整体做题方法与学习思路。

首先,对于题目不能在难题上死磕。我应该做到的学习状态是,承认自己不能解答所有问题,一定有不会的情况,这个时候陷入泥潭无法自拔不仅浪费时间,而且非常影响我的心态,这些都是非常不利的。

在遇见一个新的题目
1. 理解透彻题目意思
2. 是否有清晰的解题思路
3. 大胆尝试
4. 在一段时间没解决时果断选择看解释,并做笔记。

送给自己一句话。越难的东西,从不会到会才越有价值。
如果觉得自己疲惫了,不想动了,没有激情了,就强迫自己惯性学习。

思考

在LeetCode中对于指针的应用比较顺利,指针概念一旦理解就能很好地去使用。
其中,指针变量只是存储指针位置,相当于存放一个元素的下标,而对于指针指向元素的操作会影响源元素,但对于指针变量本身的任何操作都不会影响原来数据关系的变化。抓住这一点解题就不会迷糊。

另一个需要注意的点是一定要考虑保存头指针,否则容易导致在操作完后因为头指针丢失导致找不到数据。

1. 深入了解为什么重写equals需要重写hashcode?

对于 a==b ,如果a,b为基本数据类型,那么会进行值对比,否则会进行对对象地址的对比。

首先,在顶级父类 Object中的equals方法返回为 return(o == this),完全就是对比两个对象的地址是否相等来判断两个对象是否相等。

因为对象会被保存在堆内存中,即:即使两个新建对象值完全相同,但地址也会不同。而常量字符串会被保存在常量池中,在新建一个常量之前,会先判断常量池中是否已经有了相同常量,如果有就把相同常量的地址返回给变量。否则就在常量池中新建一个常量再把地址返回给这个变量。

而那么如果String没有重写equals方法,就会导致两个拥有相同字符串的String对象因为地址不同而导致equals返回为false。这样是因为new String(“www”) != new String("www")。而从逻辑上来说,两个包含字符串相同的String对象equals应该返回true。

而java中的String类是重写了equals的,即对于传入对象,首先他会对比两个String对象地址是否相等,如果相等直接返回true,如果不相等会判断另一个对比对象是否是String类,如果是的话就会对比两个String对象的值。

在这种情况下,两个String对象即使地址不一样,因为值相同,equals也会返回true。

这时如果不重写hashCode,就会直接调用父类Object的hashCode方法,即对该对象的地址求哈希值。那么会导致两个String对象equals为true,但hashCode却不相等。这样就违背了java中的规定:equals方法的结果必须与hashcode的结果保持一致,即equals为true时,hashcode必须相等。

否则在java的一些类中,如hashMap,hashTable中,将键对值插入时通过判断两个key对象的哈希值如果不相等,那么这两个key对象就不等,就会导致会同时插入
	hashMap.put("k","v1")
	hashMap.put("k":"v2")
这样通过key来查找就会混乱。
这样即使我们重写了key对象的equals方法,也会导致两个key相等的对象作为多个key插入。
这就是为什么java中重点申明需要保证两个对象如果equals为true,那么两个对象的哈希值也必须一样。
当然,另外,即使两个对象哈希值相等,由于哈希碰撞也可能存在两个对象不相等,所以会继续判定两个对象是否地址相等或者是否equals为true。
由此,总的来说,如果在对象中重写了equals方法,那么必须要重写hashCode方法使得两个equals判定为true的对象,hashCode也必须相等,否则可能会在后续难以预料地出现诸多问题。如hashMap,hashTable,hashSet等等。
一个对象的hashCode是对于该对象的重要标识。hashCode的对比结果必须与equals方法的结果保持一致。

2. 深入了解AVL树(平衡二叉树)、红黑树、B-树、B+树

1. AVL树(平衡二叉树)
	AVL树是绝对平衡的查找二叉树,它必须保证左右子树高度差不超过1,以此保证查找效率。平均查找的复杂度为O(logn)
	优点是对于查找而言他的效率相比红黑树更加高,因为它是绝对平衡的二叉树。而红黑树的性质是最长路径长度不超过最短路径长度的两倍。
	缺点是为了维持AVL树的绝对平衡,在进行节点插入时需要进行复杂的旋转操作,甚至可能导致为了维持平衡的开销超出查找效率的收益,所以在实际操作中,使用AVL树的情况不多。
2. 红黑树
	红黑树的类似于AVL树,是通过节点的颜色属性来维持查找二叉树的平衡,这样对于维持平衡方面来说,红黑树相比AVL树需要更少的旋转次数来保持。
	红黑树遵循以下原则:
		1. 每个节点或是红色,或是黑色
		2. 根节点为黑色
		3. 叶节点为黑色
		4. 如果一个节点为红色,那么它的两个子节点都为黑色
		5. 对于每个节点而言,从该节点到其子孙节点的所有路径上包含相同数目的黑节点。
	红黑树引入颜色概念,使得红黑树的平衡条件简化。红黑树不追求完美的平衡,而部分的平衡降低了旋转的要求,使得性能得到了提高。
	
3. B-树
	B树是一种平衡的多路查找树,相比于红黑树他的深度远远小于红黑树。树的深度过大会造成磁盘的频繁读写,进而导致效率低下,所以B树更适用于与磁盘I/P方面有关的应用中,例如文件系统中。
	B树的性质为(对于一颗m阶的B树):
		1. 树中每个节点至多有m棵子树
		2. 若根节点不是叶子节点,则至多有两颗子树
		3. 除根之外的所有非终端结点至少有m/2(取上标,下面均是)棵子树
		4. 所有非终端结点的包含关键字个数必须满足(m/2-1<=n<=m-1)
		5. 所有叶子节点必须出现在同一层次上,并且不包含信息
	B树的插入是遵循先找插入位置,然后插入(此时先不考虑是否超过节点最大的关键字容量),然后判断插入后是否超过容量,如果超过,将有序的关键字的中间关键字插入到父节点,并将插入后的位置的前后指针分别指向被分割的两节点。如果父节点也超出最大关键字个数,继续往父节点生长。我的理解是,B树的增长遵循向上增长的原则。
		
4. B+树

3. 深入了解java内存释放机制,为什么不用像c一样手动free指针?

赫夫曼树(最优二叉树)

叶节点带权值,叶节点权值乘该叶节点到根节点的路径为该叶节点的带权路径。
赫夫曼树将塑造一个树使得这个树的所有叶节点带权路径之和最小。
塑造这种树的方法为,对于n个节点,找出权值最小的两个节点,将这两个节点分别作为左右子节点塑造一个二叉树,并将两个节点权值相加作为新的节点加入节点组中,同时在节点组中删除已经处理的那两个节点。重复上述操作直到节点组只剩一个元素,即根节点。
例如:
step 1 : 2 4 5 3 7 9 
step 2 : 5 4 5 7 9 
step 3 : 9 5 7 9 
step 4 : 9 12 9
step 5 : 12 18
step 6 : 30
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值