题目

给定无向连通图中一个节点的引用,返回该图的深拷贝(克隆)。图中的每个节点都包含它的值 val(Int) 和其邻居的列表(list[Node])。
示例:7b2bfd1590ab9401ab6d2eb6d985b68a.png
输入:{“$id”:”1”,”neighbors”:[{“$id”:”2”,”neighbors”:[{“$ref”:”1”},{“$id”:”3”,”neighbors”:[{“$ref”:”2”},{“$id”:”4”,”neighbors”:[{“$ref”:”3”},{“$ref”:”1”}],”val”:4}],”val”:3}],”val”:2},{“$ref”:”4”}],”val”:1}解释:节点 1 的值是 1,它有两个邻居:节点 2 和 4 。节点 2 的值是 2,它有两个邻居:节点 1 和 3 。节点 3 的值是 3,它有两个邻居:节点 2 和 4 。节点 4 的值是 4,它有两个邻居:节点 1 和 3 。提示:节点数介于 1 到 100 之间。无向图是一个简单图,这意味着图中没有重复的边,也没有自环。由于图是无向的,如果节点 p 是节点 q 的邻居,那么节点 q 也必须是节点 p 的邻居。必须将给定节点的拷贝作为对克隆图的引用返回。

解法

深度遍历+深浅克隆
Map用于存放访问过的点,如果访问过直接返回
递归遍历neighbors
结束条件是,所有节点都访问过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> neighbors;

public Node() {}

public Node(int _val,List<Node> _neighbors) {
val = _val;
neighbors = _neighbors;
}
};
*/
class Solution {
public Node cloneGraph(Node node) {
Map<Integer,Node> map = new HashMap<Integer,Node>();
return deepClone(node,map);
}

public Node deepClone(Node node,Map<Integer,Node> map){
Node newNode;
if(map.containsKey(node.val)){
newNode = map.get(node.val);
return newNode;
}else{
newNode = new Node(node.val,null);
map.put(node.val,newNode);
}
List<Node> nei = new ArrayList<Node>();
if(node.neighbors != null){
for(Node n : node.neighbors){
nei.add(deepClone(n,map));
}
}
newNode.neighbors = nei;
return newNode;

}
}

在我写这篇文章的时候,是五一假期,大部分人还在假期的欢乐中,或者旅游,或者休闲,但你却在跟病魔的斗争中走完人生的最后一段路,你的离去只是时间问题。死亡是每个人都必须面对的,最终的去处,但能无憾的离去,实在不容易。回顾我们一起的岁月,在我的心中,让你继续走下去。
我们相识在高中,沈阳二中。经过了7中,每天不断的重复做卷子,我来到了二中,我记得中考前,我在一次升旗仪式上,默默的对自己说,我要考上二中对国旗发誓。二中在我心中是下午5点放学,学生自由学习素质教育,然后学习成绩很好。可惜到了发现并不是这样。在高二文理分班后,我们到了0913班,即使很久了我们了并不认识,一开始对你印象不是很深,我们属于不同的人,你篮球打的很好,我是一个几乎不运动的,所以也没什么交集。在一次考试分坐后,我们坐在了一起,我们当时学习成绩一般班级30-40名,中下等,在后3排徘徊。你初始不太友善,不太爱搭理人,我也是这样。但熟了之后,发现你是另外的性格,有点黏人,有点纠结。
你给我印象最深的是,每次考试我们会有一些比较难的大题,老师每次讲解完,我一般会主动放弃,因为它太难,而且以我们现在的成绩,即使做不出来也是正常,你会花时间去扣这些难题,不放弃。我当时觉得你傻,有点不知量力,我自以为的我自己很理智。但我其实是畏惧了,退缩了。你是真的坚持,不退缩,遇到困难,迎难直上。回想起来很佩服,最终你的成绩也是比我好很多,去了东北大学,我因为分数低一些去了南京。
还记得一些细节,高中你会在运动后,带洗面奶去洗脸。说来当时我家里算比较穷的,普通的上班族家庭吧,住在39平的房子里。我当时都没怎么用过洗面奶,第一次看到你用,感觉哇,他家好有钱,好不一样,讲究。后面我也跟你学习,买了洗面奶用,虽然东西不贵,但你身上有种很大方的气质,是我缺乏的,我是很吝啬的,不想与别人分享东西,怕别人抢走,你则不同,主动跟别人共享,境界全然不同,起码那个时候觉得你家里还挺有钱的,起码比我家里好多了。
大学,我们联系得不多,每年过年回家,我们几个吃个饭,随便逛逛,大家一样的烦恼,恋爱,工作。你这个时候还经常去东大学习看书,感觉你还是经常学习。
毕业后,我在上海工作,你也工作了。当时住在闵行,交大附近,突然你说要考上海交大的研究生。我想可以呀,这个很难考的,你都工作一年后,再来考能比得过那些一直学习的吗,我是比较消极的。但你用行动证明了自己,你考上了。不知道你经历了怎么样的痛苦,最终你还在我的小出租房里,跟我住了一个星期,你去复试。我估计当时态度不太好,那时的你有些敏感有些纠结,毕竟在人生转折点。就住了一周,你就走了。我以为你回去了,其实你去做宾馆了,后来知道,我真的很不好意思。我总是觉得别人,蛋疼,做决定纠结这纠结那,左右摇摆。我当时还是自以为理智,但如果我是当事人,估计也会这样。慢慢想来,纠结是人生选择的常态。想起大学毕业去面试的时候说过这么一句话,既然选定了这家企业,我就不会纠结。当时还是听到某个文章推荐这么回答。现在想想还真的是不太对,提前去调查,然后确定某家企业是对的,时间是变动的,企业也是会变的,当初的选择,不代表永恒,后面还是会需要不断的选择。我这个人怕与别人保持太近的距离,当别人靠近的时候,我会主动推开。后来你住进来了学校,我继续加班。
时间过得很快,虽然我们都在上海,离得也不远,但很少相见,只见过1-2天,在交大的校园里。我们也是过年回家,偶尔也聊聊,怎么在上海定居,以后能挣多少钱,你总也是表达了对未来的担心,学的专业没什么发展潜力,不能赚大钱,你也考虑后面也快30了,怎么结婚生子,还没有女朋友,这个时候又觉得你家里没有钱,没法直接安排你后面怎么走,我们同样迷茫,我那时比你挣的多一点,但对未来也是没底。后面听说你要进事业单位了,你说先混了户口,你也觉得没什么发展,但起码有个户口,混1-2年,然后再出来。你也说你可能跟家里亲戚去创业等等。
大概2017年底,2018年初,听说你得白血病了,在水滴凑。我是知道你突然回顾你朋友圈,发现你之前好像打篮球受伤了,伤病回家修养,那个时候还没体会到你发生了什么,以为你还是在矫揉造作。过年回家跟你见了一面,你说你化疗,除了全身没毛外,其他还好。你还是那么乐观。过年回去,跟你吃了顿饭,你虽然看病花了很多钱,但你还是那样的气质,抢着跟我花钱。我不知道怎么面对你,你看起来还不错,说自己不想治了,花那么多钱,想出去玩,但不能动治病的钱。我也在想你要是做骨髓移植,起码花百万,你有这么多钱吗?但你没提跟我借钱,你可能也知道我没什么钱吧,但我确实比你收入多。我当时也想给你多少钱的,我比较纠结,你在面临生命危机,我却在为我自己的未来是不是能买房担心,真的很吝啬,很自私,最终我只给3千,微不足道。
2019年4月,朋友突然给我发你接受采访的报道,说你骨髓移植后,又复发了,时间剩余不多了,你放弃治疗,不想让父母生活品质降低,不想走的很低质量,可能一直插着管子,昏迷等,想安静的走,将遗体捐献给医院,希望给治疗这个病做出贡献。在上班途中,我几乎控制不住自己的苦出来,我不敢问你,不知道怎么跟你开口,一切来的太突然,我还不知道你骨髓移植。没想到这么快,最后的希望,骨髓移植也复发了。最后的手段也不见效了,那确实没有办法了。从你刚的病到现在也就1年多,实在太快了。好绝望,虽然知道很多人死于白血病,但没想到身边人经历后,才发现这个是多么的残酷,多么的无力。我更无法想象,他的父母是什么感受,白发人送黑发人。才刚刚研究生毕业,还是这么好的大学,人生应该是刚刚开始,之前很多都是身不由己,工作后其他自己决定后面的生活,就这样结束了不到30岁。翻看你之前的朋友圈,你18年12月份回了一次上海,发了说想不到外滩这么美。突然泪水控制不住,生命如此短暂,在你刚要开始真正的人生就结束了,你还没有女朋友,你还没有孩子……
迟迟不敢联系你,不知道怎么跟你说,不知道说什么。在五一放假的前一天,跟你联系了,你说在老家往沈阳赶,说自己发烧感觉时间不多了,想回去拿点东西,在海边平静的离去。你他娘的太文艺了,即使走的时候也想文艺的走。看到你在海边的照片,那么美,反差让我控制不住情绪。我下楼走到了外面,不想让认识的人看见,我边走边哭,又尽力控制,我想在下班前跟你视频,找了一圈,在电影广场楼下的银行门前有一块可以坐的地方,在那里又哭了一阵。不甘、心酸,我们相处的总总,没有想到会这样,我的朋友不多,对你也是不太好,你是我不多朋友里经常联系的朋友,本就内向的我很难交朋友,我还以为我们还有大把的时间,不用在意现在,但……
我在想要不要回去,很纠结,听你说,见你最后一面是实现我们一些小愿望,我突然就发现,对于见最后一面,真的是对其他的人的愿望的实现,对你来说,已经无所谓了,这个时候其他都不那么重要了,房子、车子、票子,不能带去,能做的是,为这个世界留下点什么,让其他人还能记得自己,这样也许就是生命的延续。人和人确实不一样,有些人死了,会在大家的思想里,不断延续,其实就是鲜活的生命,肉体死了,灵魂永存。
走在大街上,看着其他人继续的日子,突然感觉不那么真实,有些人可能是以后几天,有些却在虚度、打发时间,有些人欢声笑语,有些人却没有希望。死亡是生命的一部分,一个必然的阶段。只是大家觉得自己不会那么快,还有很多时间,人生需要阶段规划,我想让我的人生不那么遗憾,能思前想后,发现想做的都做到了,没有什么遗憾了。

ArrayBlockingQueue是一个有界的阻塞队列,底层实现是一个数组+two-condition并发控制,队列实现使用的是循环数组,并发控制使用ReentrantLock加notEmpty,notFull的condition,如果队列满或空,阻塞线程放入等待队列中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/** The queued items */
final Object[] items;

/** items index for next take, poll, peek or remove */
int takeIndex;

/** items index for next put, offer, or add */
int putIndex;

/** Number of elements in the queue */
int count;

/*
* Concurrency control uses the classic two-condition algorithm
* found in any textbook.
*/

/** Main lock guarding all access */
final ReentrantLock lock;

/** Condition for waiting takes */
private final Condition notEmpty;

/** Condition for waiting puts */
private final Condition notFull;
/**
* Shared state for currently active iterators, or null if there
* are known not to be any. Allows queue operations to update
* iterator state.
*/
transient Itrs itrs = null;

item就是实际存储的数组,takeIndex 是弹出的索引,putIndex是压入的索引,count是队列的实际存储的数量。这3个变量配合,count用于判断队列是否空,是否满,takeIndex出队列,putIndex入队列。
lock、notEmpty、notFull并发控制,iters是遍历循环,暂时不讲。

构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Creates an {@code ArrayBlockingQueue} with the given (fixed)
* capacity and the specified access policy.
*
* @param capacity the capacity of this queue
* @param fair if {@code true} then queue accesses for threads blocked
* on insertion or removal, are processed in FIFO order;
* if {@code false} the access order is unspecified.
* @throws IllegalArgumentException if {@code capacity < 1}
*/
public ArrayBlockingQueue(int capacity, boolean fair) {
if (capacity <= 0)
throw new IllegalArgumentException();
this.items = new Object[capacity];
lock = new ReentrantLock(fair);
notEmpty = lock.newCondition();
notFull = lock.newCondition();
}

初始化数组大小,初始化锁,可以传入公平锁还是非公平锁,创建不空、不满条件,给管程等待队列用。
入队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/**
* Inserts the specified element at the tail of this queue if it is
* possible to do so immediately without exceeding the queue's capacity,
* returning {@code true} upon success and throwing an
* {@code IllegalStateException} if this queue is full.
*
* @param e the element to add
* @return {@code true} (as specified by {@link Collection#add})
* @throws IllegalStateException if this queue is full
* @throws NullPointerException if the specified element is null
*/
public boolean add(E e) {
return super.add(e);
}
public boolean add(E e) {
if (offer(e))
return true;
else
throw new IllegalStateException("Queue full");
}
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lock();
try {
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
lock.unlock();
}
}
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();
enqueue(e);
} finally {
lock.unlock();
}
}
public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {

checkNotNull(e);
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length) {
if (nanos <= 0)
return false;
nanos = notFull.awaitNanos(nanos);
}
enqueue(e);
return true;
} finally {
lock.unlock();
}
}
private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length)
putIndex = 0;
count++;
notEmpty.signal();
}

Add、offer(不带超时)方法如果队列满,就会返回false,不会阻塞线程。put、offer(带超时)方法会阻塞线程。offer(带超时)通过中断实现超时,从而按时间返回。
enqueue是实际入队列的方法,通过putIndex索引,加入数组,如果putIndex大于数组的长度,从0位置再开始,是一个循环队列。如果入队列了代表,出队列因为数组空的notEmpty阻塞的线程可以重新去执行了,所以notEmpty signal唤醒。 循环队列的空与满,不是通过putIndex与takIndex的位置关系判断了,而是使用了一个新的变量count去判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public E poll() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
return (count == 0) ? null : dequeue();
} finally {
lock.unlock();
}
}
public E take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0)
notEmpty.await();
return dequeue();
} finally {
lock.unlock();
}
}
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}

与入站的构造很类似,poll锁不可中断,take与poll(带超时)都可以中断。
总结:ArrayBlocking 是一个有界循环队列,并发控制使用ReentrantLock的two-condition,队列空阻塞出队列,队列满阻塞入队列。

实现数据结构是堆,堆底层实现结构是数组,堆是完全二叉树。
主要的成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* Priority queue represented as a balanced binary heap: the two
* children of queue[n] are queue[2*n+1] and queue[2*(n+1)]. The
* priority queue is ordered by comparator, or by the elements'
* natural ordering, if comparator is null: For each node n in the
* heap and each descendant d of n, n <= d. The element with the
* lowest value is in queue[0], assuming the queue is nonempty.
*/
transient Object[] queue; // non-private to simplify nested class access

/**
* The number of elements in the priority queue.
*/
private int size = 0;

/**
* The comparator, or null if priority queue uses elements'
* natural ordering.
*/
private final Comparator<? super E> comparator;

/**
* The number of times this priority queue has been
* <i>structurally modified</i>. See AbstractList for gory details.
*/
transient int modCount = 0; // non-private to simplify nested class access

queue是堆的底层实现,size是记录堆的大小的,comparator是自己实现比较方法的,可以实现大顶堆和小顶堆,mouCount用于控制并发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Creates a {@code PriorityQueue} with the specified initial capacity
* that orders its elements according to the specified comparator.
*
* @param initialCapacity the initial capacity for this priority queue
* @param comparator the comparator that will be used to order this
* priority queue. If {@code null}, the {@linkplain Comparable
* natural ordering} of the elements will be used.
* @throws IllegalArgumentException if {@code initialCapacity} is
* less than 1
*/
public PriorityQueue(int initialCapacity,
Comparator<? super E> comparator) {
// Note: This restriction of at least one is not actually needed,
// but continues for 1.5 compatibility
if (initialCapacity < 1)
throw new IllegalArgumentException();
this.queue = new Object[initialCapacity];
this.comparator = comparator;
}

初始化的时候,可以自定义比较器和容器大小,默认是11
主要方法
添加数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Inserts the specified element into this priority queue.
*
* @return {@code true} (as specified by {@link Queue#offer})
* @throws ClassCastException if the specified element cannot be
* compared with elements currently in this priority queue
* according to the priority queue's ordering
* @throws NullPointerException if the specified element is null
*/
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e);
return true;
}

如果队列大小与容量上限一直,就去扩容,从最后一位去向上堆化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Increases the capacity of the array.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
int oldCapacity = queue.length;
// Double size if small; else grow by 50%
int newCapacity = oldCapacity + ((oldCapacity < 64) ?
(oldCapacity + 2) :
(oldCapacity >> 1));
// overflow-conscious code
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
queue = Arrays.copyOf(queue, newCapacity);
}

扩容方法,如果容量小于64 ,则一次扩容 2n+2,大于则 扩容1.5n,最后复制原来数组到新数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
* Inserts item x at position k, maintaining heap invariant by
* promoting x up the tree until it is greater than or equal to
* its parent, or is the root.
*
* To simplify and speed up coercions and comparisons. the
* Comparable and Comparator versions are separated into different
* methods that are otherwise identical. (Similarly for siftDown.)
*
* @param k the position to fill
* @param x the item to insert
*/
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}

@SuppressWarnings("unchecked")
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (key.compareTo((E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = key;
}
private void siftUpUsingComparator(int k, E x) {
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (comparator.compare(x, (E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = x;
}

向上堆化,如果自定义了比较器 就用siftUpUsingComparator,其他就用默认的比较器,按字母顺序排序,两个方法实现基本相同
K是新插入的节点的索引,它的父是( k-1)/2,这个地方跟一般的堆有些区别,是从0开始算的,父是n,左子树是2n+1,右子树是2(n+1)
优先级小的在上面,如果遇到子比父优先级大,结束,将x赋值给k位。如果父优先级更大,将父的值赋给子节点,继续比较父的父级,知道k=0.

1
2
3
4
5
6
7
8
9
10
11
12
13
@SuppressWarnings("unchecked")
public E poll() {
if (size == 0)
return null;
int s = --size;
modCount++;
E result = (E) queue[0];
E x = (E) queue[s];
queue[s] = null;
if (s != 0)
siftDown(0, x);
return result;
}

弹出堆顶值,queue[0]是优先级最小的,要弹出的值,sfitDown是从堆顶向堆底堆化,从最后一位取一个值,放到堆顶,然后从顶开始堆化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/**
* Inserts item x at position k, maintaining heap invariant by
* demoting x down the tree repeatedly until it is less than or
* equal to its children or is a leaf.
*
* @param k the position to fill
* @param x the item to insert
*/
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}

@SuppressWarnings("unchecked")
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>)x;
int half = size >>> 1; // loop while a non-leaf
while (k < half) {
int child = (k << 1) + 1; // assume left child is least
Object c = queue[child];
int right = child + 1;
if (right < size &&
((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
c = queue[child = right];
if (key.compareTo((E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = key;
}
@SuppressWarnings("unchecked")
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}

跟从下到上结构相似,看是否有自实现的比较器。
size/2 是差不多最后一个非叶子节点,最后到这个节点去堆化,从k=0 开始,其次与左右子树比,谁优先级更小,不断循环,直到x的优先级,小于某个的节点,然后放置到k处

总结
优先级队列,底层数据结构是堆,堆的底层数据结构是数组,每次插入和删除,都要堆化,有2种堆化方式,一种是从上到下,一种是从下到上。从数组角度去理解还是有点难度的,需要想象成树的结构,然后根据序号存储在数组内。

作者撰写此书的目的希望以一种更易于理解的方式,讲述以亚马逊的Dynamo、谷歌的Bigtable和MaReduce等为代表的分布式系统背后的核心思想
看了一遍,英语也有点差,每天看一周才勉强看完一遍,内容还穿不起来,需要再多看几遍。
作者看来分布式只有2件事,一个是 数据传输速率不会超过光速,第二个,独立的节点独立的失败
换句话是说是处理距离和不止一件事。处理距离、时间和同步模型。

从两个角度去看这部片子,从吴军的近代史纲得到的启示,回望与俯瞰。
回望了解历史进程,俯瞰查看因素的关联,掌握历史的规律。

首先每个人的记录看成这个人的历史,这些人的记录总和是英国的历史,可以一窥历史,并不能完全代表。
从每个人的历史看一个人一生应该经历的各种事情,提前规划。
横向看每个人的历史,就是英国的历史,看英国历史是如何发展。

个人历史

回望人生,大概几个阶段
1.上学,接受教育
2.工作
3.婚姻
4.孩子
5.养老

上学阶段的区分是上公立学校还是私立学校,大学是文法大学还是综合性大学,是否读预科为上大学做准备。

工作提供金钱,是保证你生活的水平,后面的发展如何,有些人的工作到了40-50多岁因为经济原因被取消,导致没有收入。金钱保证你是否能做想做的事情。

婚姻,什么时间去结婚,是否离婚,婚后生活如何,如果两个人共同生活。
什么时候要孩子,要几个孩子,怎么教育孩子。

养老,才56岁,没有提及。

上学阶段

家庭原因,会给一个人造成永久的影响,尤其是童年,父母离婚,父母的爱,家庭的温暖,
眼界的扩展,是否出去旅游,关心的事情,空闲时间都在做什么
分化从这个时候开始,私立教育提供很大的机会上更好的大学,为未来提高更高的门槛。
童年的不幸会影响一个人很久,有些非婚、或者离婚家庭的孩子,对未来会很不自信,缺乏安全感。
后面比较成功的孩子,都是对未来有一些规划,但很多没有想很清楚,未来也是没有目标去努力。

工作阶段

工作是一个人未来生活的基础,如果没有足够的收入,生活总是会做捉襟见肘,生活比较富裕的,基本是从事律师,达到合伙人的阶段,
生活才能比较轻松,养育子女,才能没有那么多局促

婚姻阶段

何时结婚,大部分人在很年轻的时候就结婚,然后离婚再婚,有些着是比较晚结婚,有人婚内出轨,看你如何经营婚姻,好的婚姻两个人互相滋养,坏的互相消耗,两个如何相处,日常琐事如何处理。

养育子女阶段

生育几个孩子,如何培养孩子,如何跟子女相处,有的人离婚自己养育孩子,有的人生一大堆孩子然后离婚,子女生活遇到一些问题,如何分担

从个人里看英国发展

整体国力在下降,面临就业岗位减少,很多人找不到工作。
私立教育与公立教育差异很大,有钱人就读私立更好的资源,加剧阶级分化。
离婚情况比较多,癌症概率很大。
种族问题。

题目

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
说明:
你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

思路1

插入一个k大小的最小堆,
满了之后,再最小值比较,然后从上到下堆化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class Solution {
public int findKthLargest(int[] nums, int k) {
Heap heap = new Heap(k);
for(int i = 0; i< nums.length ; i++){
heap.insert(nums[i]);
}
return heap.maxK();
}
static class Heap{
int[] nums;
int count;
int capacity;
public Heap(int capacity){
this.nums = new int[capacity+1];
this.count = 0;
this.capacity = capacity;
}

public void insert(int num){
if( count < capacity){
notFullInsert(num);
}else{
fullInsert(num);
}
}
public void fullInsert(int num){
int min = nums[1];
if(num < min){
return;
}
nums[1] = num;
int minPos = 1;
int i = 1;
while(true){
if(i*2 <= capacity && nums[i] > nums[i*2]){minPos = i*2;}
if(i*2+1 <= capacity && nums[minPos] > nums[i*2+1]){minPos = i*2+1;}
if(i== minPos)break;
swap(nums,i,minPos);
i = minPos;
}

}
public void notFullInsert(int num){
int index = count+1;
nums[index] = num;
count++;
while(index/2 > 0 && nums[index/2] > nums[index]){
swap(nums,index , index/2);
index = index/2;
}
}
public void swap(int[] array, int fromIndex ,int toIndex){
int tmp = array[toIndex];
array[toIndex] = array[fromIndex];
array[fromIndex] = tmp;
}
public int maxK(){
return nums[1];
}
}
}

思路2

先填充 k个大小的数组,然后从下到上进行堆化,
满了之后,再最小值比较,然后从上到下堆化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Solution {
public int findKthLargest(int[] nums, int k) {
int[] arrays = new int[k+1];

for(int i = 1 ; i<= k ; i++ ){
arrays[i] = nums[i-1];
}
initTree(arrays);

for(int i = k ; i< nums.length ; i++){
int min = arrays[1];
if(min < nums[i]){
arrays[1] = nums[i];
heapify(arrays,1, k);

}
}
return arrays[1];
}
private void initTree(int[] array){
for(int i = array.length/2; i>= 1;i--){
heapify(array, i, array.length -1);
}
}
private void heapify(int[] array,int i, int n){
while(true){
int minPos = i;
if(i*2 <= n && array[i] > array[i*2]){
minPos = i*2;
}
if(i*2+1 <= n && array[minPos] > array[i*2+1]){minPos = i*2+1;}
if(minPos == i)break;
swap(array, i, minPos);
i = minPos;
}
}

public void swap(int[] nums , int fromIndex , int toIndex){
int tmp = nums[toIndex];
nums[toIndex] = nums[fromIndex];
nums[fromIndex] = tmp;
}

这段时间在以阿里的标准去复习,发现自己之前真的很多不会的。基础的集合类从来没想过要了解它的实现,不知道实现就不知道它的限制,这样是不可能使用好的。虚拟机没有看过,当时觉得虚拟机太复杂,还没腾出时间去看。并发没有好好研究,它和虚拟机一样也是认为它太难了。算法,数据结构也是,一直认为它很重要,但遇到点困难就退缩了,回到舒适区。这些困难可能暂时不会影响到那时的我,所以我也没有太在意,虽然他们都在,也都知道,但重要性没有那么高。

最近再把这些知识作为必须要学会的内容去学习,开头很难,制定计划内心都是拒绝的,开始1-2个星期,1天只能坐1-2道算法题,那个时候内心是崩溃的,但过了1-2个月,发现情况出现好转,每天做题的速度慢慢上来了。虽然并发还是不怎么会,但通过订阅别人的专栏,我发现,一开始不会的时候,知识的凌乱的,但通过师傅寻找内在的逻辑,精心编怕,你可以找到秩序,把混乱变成有序,需要付出努力,自然界是熵增的。学习就是这样一个过程, 把无序变成有序。

从我女朋友学编程这件事来说,我一直想教她,但我理不出头绪,一般就是扔一本书给她,或者开头热教她一下,但过不了1个星期就不继续了,这是我没有吃透的表现,没有从混乱转变成有序,也不能将这种有序传授给别人。李笑来之前就说他口头教会别人游泳,说明功力很深厚,我也尝试教女朋友,模仿我学习的过程,但效果一般,我没有掌握好各种过渡的阶段,无法有效的表达。很多时候就是这样,明明自己会,但你不敢说会,因为你不能教会别人。以教为学很重要。

最近也体会到一个人意志坚定的重要性,认准一个道理,身边的不同人会给你不同的压力,顶住压力,你就能突破自我,完成进化。屈从压力,你可能会继续随波逐流,活在别人的阴影中,别人的意志里。

阅读 8个分布式系统的谬误

  1. The network is reliable.
  2. Latency is zero.
  3. Bandwidth is infinite.
  4. The network is secure.
  5. Topology doesn’t change.
  6. There is one administrator.
  7. Transport cost is zero.
  8. The network is homogeneous.

阅读了1遍发现没什么深刻的印象,私自总结一下,
首先分布式网络是不可靠的,不安全的,异构的,拓扑是可能改变的,传输消耗不是0,延迟不是0,带宽不是无限的,主要分布在网络上,
在非分布式网络下,情况可能是单一的,特殊的,但扩散到分布式下,网络的各种情况都会遇到,所以不能轻易假设,网络是我们预期的那样简单。
其他事管理员可能不止一个人。

分布式就是要考虑各种各样的网络特殊情况,防止这些疏忽导致的系统异常。

题目

给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:

  1. 首先找到需要删除的节点;
  2. 如果找到了,删除它。
    说明: 要求算法时间复杂度为 O(h),h 为树的高度。
    示例:
    root = [5,3,6,2,4,null,7]
    key = 3
    5
    / \
    3 6
    / \ \
    2 4 7
    给定需要删除的节点值是 3,所以我们首先找到 3 这个节点,然后删除它。
    一个正确的答案是 [5,4,6,2,null,null,7], 如下图所示。
    5
    / \
    4 6
    / \
    2 7
    另一个正确答案是 [5,2,6,null,4,null,7]。
    5
    / \
    2 6
    \ \
    4 7

思路1

分几种情况
1.没有左右子树,直接删除
2.只有一个子树,将父指向子,直接删除
3.如果是有父子节点,找到右子树最小的节点,替换

犯错点

1.只有一个元素的时候,删除没考虑好
2.删除根节点没有处理好

思路2

参考别人的算法,自己在后面处理没写好,别人的算法是通过递归的,主要好处就不用找父节点了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
if(root == null)return null;
if(root.val > key){
root.left = deleteNode(root.left, key);
return root;
}else if(root.val < key){
root.right = deleteNode(root.right, key);
return root;
}else{
if(root.left == null){
return root.right;
}else if(root.right == null){
return root.left;
}else{
TreeNode successor = minNode(root.right);
successor.right = deleteMin(root.right);
successor.left = root.left;
return successor;
}
}
}
private TreeNode minNode(TreeNode node){
if(node.left == null){
return node;
}
return minNode(node.left);

}
private TreeNode deleteMin(TreeNode node){
if(node.left == null){
return node.right;
}
node.left = deleteMin(node.left);
return node;

}
}

通过递归的方式
1.如果node值大于 key,则递归左子树,返回值覆给左子树
2.如果node值小于key,则递归右子树,返回值覆给右子树
3.相等情况比较复杂
3.1 如果node值等于key,并且左子树为空,那么直接返回右子树,这样就删除了该node。
3.2 如果node值等于key,并且右子树为空,那么直接返回右子树,这样就删除了该node。
3.3 如果node等于key,同时左右都不为空
3.3.1 获取该子树最小的node,命名为successor
3.3.2 将右子树删除最小值,并且将最新的右子树赋值给successor的右子树
3.3.3 将之前节点的左子树赋值给successor的左子树
3.3.4 返回新构建的节点

minNode是返回某个子树的最小值,也就是找最左节点

deleteMin是删除子树的最小值,递归的方式
退出条件是某个节点的左节点是空,则返回右节点,其他情况递归左子树

主要使用了递归的手段, 这种手段可以避免找父节点,但我还不是很熟悉,需要继续熟悉递归的思维方式。