此部分内容为个人回顾,想到哪里写哪里,不具备系统参考价值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U3SFfe5D-1589719665223)(E:\MarkDown\简历知识对照篇\images\img1.png)]
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode prehead = new ListNode(-1);
ListNode prev = prehead;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
prev.next = l1;
l1 = l1.next;
} else {
prev.next = l2;
l2 = l2.next;
}
prev = prev.next;
}
// 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
prev.next = l1 == null ? l2 : l1;
return prehead.next;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uzoBcPs7-1589719665236)(E:\MarkDown\简历知识对照篇\images\img2.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SD3EdzOJ-1589719665238)(E:\MarkDown\简历知识对照篇\images\img3.png)]
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
int length = 0;
ListNode first = head;
while (first != null) {
length++;
first = first.next;
}
length -= n;
//第二次遍历时是指向dummy
first = dummy;
while (length > 0) {
length--;
first = first.next;
}
first.next = first.next.next;
return dummy.next;
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PIZK3PxQ-1589719665242)(E:\MarkDown\简历知识对照篇\images\img4.png)]
分析:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rmQZw5O6-1589719665245)(E:\MarkDown\简历知识对照篇\images\img5.png)]
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode dummyHead = new ListNode(0);
ListNode p = l1, q = l2, curr = dummyHead;
int carry = 0;
while (p != null || q != null) {
int x = (p != null) ? p.val : 0;
int y = (q != null) ? q.val : 0;
int sum = carry + x + y;
carry = sum / 10;
curr.next = new ListNode(sum % 10);
curr = curr.next;
if (p != null) p = p.next;
if (q != null) q = q.next;
}
if (carry > 0) {
curr.next = new ListNode(carry);
}
return dummyHead.next;
}
思路:
我们可以通过检查一个结点此前是否被访问过来判断链表是否为环形链表。常用的方法是使用哈希表。
算法
我们遍历所有结点并在哈希表中存储每个结点的引用(或内存地址)。如果当前结点为空结点 null(即已检测到链表尾部的下一个结点),那么我们已经遍历完整个链表,并且该链表不是环形链表。如果当前结点的引用已经存在于哈希表中,那么返回 true(即该链表为环形链表)。
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public boolean hasCycle(ListNode head) {
Set<ListNode> nodesSeen = new HashSet<>();
while (head != null) {
if (nodesSeen.contains(head)) {
return true;
} else {
nodesSeen.add(head);
}
head = head.next;
}
return false;
}
方法二:双指针
思路
想象一下,两名运动员以不同的速度在环形赛道上跑步会发生什么?
算法
通过使用具有 不同速度 的快、慢两个指针遍历链表,空间复杂度可以被降低至 O(1)O(1)。慢指针每次移动一步,而快指针每次移动两步。
如果列表中不存在环,最终快指针将会最先到达尾部,此时我们可以返回 false。
现在考虑一个环形链表,把慢指针和快指针想象成两个在环形赛道上跑步的运动员(分别称之为慢跑者与快跑者)。而快跑者最终一定会追上慢跑者。这是为什么呢?考虑下面这种情况(记作情况 A)- 假如快跑者只落后慢跑者一步,在下一次迭代中,它们就会分别跑了一步或两步并相遇。
其他情况又会怎样呢?例如,我们没有考虑快跑者在慢跑者之后两步或三步的情况。但其实不难想到,因为在下一次或者下下次迭代后,又会变成上面提到的情况 A。
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
双指针迭代
class Solution {
public ListNode reverseList(ListNode head) {
//申请节点,pre和 cur,pre指向null
ListNode pre = null;
ListNode cur = head;
ListNode tmp = null;
while(cur!=null) {
//记录当前节点的下一个节点
tmp = cur.next;
//然后将当前节点指向pre
cur.next = pre;
//pre和cur节点都前进一位
pre = cur;
cur = tmp;
}
return pre;
}
}
使用队列完成
入对根节点
循环一下步骤:
(1)出队根节点,入队左右子节点。
public class FileDemo2 {
/*
* listFiles(): 获得所有文件夹和文件
* */
public static void main(String[] args) {
File file=new File("d://");
File[] listFiles = file.listFiles();
for (File file2 : listFiles) {
//先判断是不是文件
if (file2.isFile()) {
//是文件再判断是否以.jpg结尾
if (file2.getName().endsWith(".jpg")) {
System.out.println(file2.getName());
}
}
}
}
}
public class Demo4 {
//思想决定一切,不求甚解不如不解
/*
* 需求:请大家把D:\JAVA目录下所有的java结尾的文件的绝对路径给输出在控制台。
* A:封装目录
* B :获取该目录下所有的文件或者文件夹的Fi1e数组
* C:遍历该File数组,得到每一个File对象
* D:判断该File对象是否是文件夹 是:回到B 否:继续判断是否以。java结尾
* 是:就输出该文件的绝对路径
* 否:不搭理它
*/
public static void main(String[] args) {
File file=new File("D:\\JAVA");
getAbsoluteName(file);
}
private static void getAbsoluteName(File file) {
//得到该目录下所有文件
File[] listFiles = file.listFiles();
//遍历,同时判断是否是文件夹
for (File file2 : listFiles) {
//如果是文件夹,就递归调用本方法
if (file2.isDirectory()) {
getAbsoluteName(file2);
}else {
//如果不是文件夹,就判断是否是以.java结尾的文件,同时得到该文件的绝对路径。
if (file2.getName().endsWith(".java")) {
System.out.println(file2.getAbsolutePath());
}
}
}
}
}
public class Demo3 {
/*
* System.in 标准输入流。是从键盘获取数据的
*
* 三种键盘录入数据:
* A:main方法的args接收参数。
* java HelloWorld hello world java
* B:Scanner(JDK5以后的)
* Scanner sc = new Scanner(System.in);
* String s = sc.nextLine();
* int x = sc.nextInt()
* C:通过字符缓冲流包装标准输入流实现(在Scanner出现之前用这个)
* BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
*/
public static void main(String[] args) throws IOException {
//第三种,使用BudderReader的readLine()实现。
//inputStream=System.in;
//但是字符缓冲流封装的是字符流,所以我们仍不能封装上面的标准输入流
//BufferedReader bReader=new BufferedReader(in);
//所以我们想到了使用一个东西将字符流装换为字节流,所以我们想到使用转换流InputStreamReader
BufferedReader bReader=new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入你的名字");
String name=bReader.readLine();
System.out.println("请输入你的年龄");
int age = Integer.parseInt(bReader.readLine());
System.out.println("你的姓名为:"+name+"你的年龄为:"+age);
}
}
随机访问流:
RandomAccessFile类不属于流,是Object类的子类。
但它融合了InputStream和OutputStream的功能。
支持对文件的随机访问读取和写入。
public RandomAccessFile(String name,String mode):第一个参数是文件路径,第二个参数是操作文件的模式。
模式有四种,我们最常用的一种叫"rw",这种方式表示我既可以写数据,也可以读取数据
序列化流:把对象按照流一样的方式存入文本文件或者在网络中传输。对象 – 流数据(ObjectOutputStream)
反序列化流:把文本文件中的流对象数据或者网络中的流对象数据还原成对象。流数据 – 对象(ObjectInputStream)
public class Demo3 {
/*
* 这里的集合必须是Properties集合:
* public void load(Reader reader):把文件中的数据读取到集合中
* public void store(Writer writer,String comments):把集合中的数据存储到文件
*
* 单机版游戏:
* 进度保存和加载。
* 三国群英传,三国志,仙剑奇侠传...
*
* 吕布=1
* 方天画戟=1
*/
public static void main(String[] args) throws IOException {
Properties prop=new Properties();
Reader inStream=new FileReader("prop.txt");
prop.load(inStream);
inStream.close();
System.out.println(prop);
System.out.println(prop.getProperty("address"));
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2l6tDwXO-1589719665248)(E:\MarkDown\简历知识对照篇\images\img6.png)]
add()
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
arraycopy仅仅是将index位置的元素全部向后移一位。
扩容方法ensureCapacityInternal(size + 1)
最终它调用的是calculateCapacity()方法,比较默认数组长度10和传进来的size+1即minCapacity大小,返回较大的一个值。
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
LinkedList结果为双向链表,在JDK1.6之前为循环链表,JDK1.7取消了循环。
// 成员变量指向第一个节点和最后一个节点的指针。
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
包括一个静态内部类Node,其成员变量包括
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
添加节点到第一个位置addFirst(e)方法
内部调用linkFirst(e)方法
private void linkFirst(E e) {
final Node f = first;
final Node newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
其他添加方法都类似。
clear()方法
可以看出,使用了一个循环,从第一元素开始删除节点。
public void clear() {
// Clearing all of the links between nodes is "unnecessary", but:
// - helps a generational GC if the discarded nodes inhabit
// more than one generation
// - is sure to free memory even if there is a reachable Iterator
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null;
size = 0;
modCount++;
}
remove(Object o)方法核心代码
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
可以看出,它也是从first开始开始遍历,当某节点值等于要删除的值时就进行一个删除。所以时间复杂度为O(N).
hashmap仅仅来看一下它扰动函数,因为红黑树暂时没有特别清楚,所以后续再研究:
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
hash(key)方法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
可以看到,h = key.hashCode()) ^ (h >>> 16)就是它的一个核心,来解释一下:
首先这个函数的终极目标就是得到一个值,然后根桶的长度进行与运算,所得结果就是这个key放在桶的一个索引的地方,所以这个函数如果算出来的值比较固定,那么它的碰撞概率就比较大。再来解释这个函数,首先得到key的一个hashCode,然后跟这个hashCode右移16位进行一个或运算,那么为什么要这一步呢,这一步的作用就是使得这个最终返回的一个地址值更加均匀。举个例子比如key的hashcode算出来时10101000100000000,可以看到,hashcode的低位全部是0,如果用这个数去和桶长度进行一个与运算,那么每次的结果都是0,显而易见,碰撞概率十分大。右移十六位就是为了解决这个问题。