# Nicksxs's Blog
learn from zero,技术博客，Nicksxs，史学森

What hurts more, the pain of hard work or the pain of regret?

0%

## Leetcode 160 相交链表(intersection-of-two-linked-lists) 题解分析

发表于 2021-01-10 分类于 Java ， leetcode ， Linked List ， java ， Linked List 阅读次数： Disqus：

## 题目介绍

写一个程序找出两个单向链表的交叉起始点，可能是我英语不好，图里画的其实还有一点是交叉以后所有节点都是相同的
Write a program to find the node at which the intersection of two singly linked lists begins.

For example, the following two linked lists:
begin to intersect at node c1.

### Example 1:

    Input: intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
    Output: Reference of the node with value = 8
    Input Explanation: The intersected node's value is 8 (note that this must not be 0 if the two lists intersect). From the head of A, it reads as [4,1,8,4,5]. From the head of B, it reads as [5,6,1,8,4,5]. There are 2 nodes before the intersected node in A; There are 3 nodes before the intersected node in B.

## 分析题解

一开始没什么头绪，感觉只能最原始的遍历，后来看了一些文章，发现比较简单的方式就是先找两个链表的长度差，因为从相交点开始肯定是长度一致的，这是个很好的解题突破口，找到长度差以后就是先跳过长链表的较长部分，然后开始同步遍历比较 A，B 链表；

### 代码

    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
            if (headA == null || headB == null) {
                return null;
            }
            // 算 A 的长度
            int countA = 0;
            ListNode tailA = headA;
            while (tailA != null) {
                tailA = tailA.next;
                countA++;
            }
            // 算 B 的长度
            int countB = 0;
            ListNode tailB = headB;
            while (tailB != null) {
                tailB = tailB.next;
                countB++;
            }
            tailA = headA;
            tailB = headB;
            // 依据长度差，先让长的链表 tail 指针往后移
            if (countA > countB) {
                while (countA > countB) {
                    tailA = tailA.next;
                    countA--;
                }
            } else if (countA < countB) {
                while (countA < countB) {
                    tailB = tailB.next;
                    countB--;
                }
            }
            // 然后以相同速度遍历两个链表比较
            while (tailA != null) {
                if (tailA == tailB) {
                    return tailA;
                } else {
                    tailA = tailA.next;
                    tailB = tailB.next;
                }
            }
            return null;
        }

### 总结

可能缺少这种思维，做的还是比较少，所以没法一下子反应过来，需要锻炼，我的第一反应是两重遍历，不过那样复杂度就高了，这里应该是只有 O(N) 的复杂度。

## 聊聊 Java 的 equals 和 hashCode 方法

发表于 2021-01-03 阅读次数： Disqus：

Java 中的这个话题也是比较常遇到的，关于这块原先也是比较忽略的，但是仔细想想又有点遗忘了就在这里记一下
简单看下代码
`java.lang.Object#equals`

    public boolean equals(Object obj) {
            return (this == obj);
        }

对于所有对象的父类，`equals` 方法其实对比的就是对象的地址，也就是是否是同一个对象，试想如果像 Integer 或者 String 这种，我们没有重写 `equals`，那其实就等于是在用`==`，可能就没法达到我们的目的，所以像 String 这种常用的 jdk 类都是默认重写了
`java.lang.String#equals`

    public boolean equals(Object anObject) {
            if (this == anObject) {
                return true;
            }
            if (anObject instanceof String) {
                String anotherString = (String)anObject;
                int n = value.length;
                if (n == anotherString.value.length) {
                    char v1[] = value;
                    char v2[] = anotherString.value;
                    int i = 0;
                    while (n-- != 0) {
                        if (v1[i] != v2[i])
                            return false;
                        i++;
                    }
                    return true;
                }
            }
            return false;
        }

然后呢就是为啥一些书或者 `effective java` 中写了 `equals` 跟 `hashCode` 要一起重写，这里涉及到当对象作为 `HashMap` 的 `key` 的时候
首先 `HashMap` 会使用 `hashCode` 去判断是否在同一个槽里，然后在通过 `equals` 去判断是否是同一个 `key`，是的话就替换，不是的话就链表接下去，如果不重写 `hashCode` 的话，默认的 `object` 的`hashCode` 是 `native` 方法，根据对象的地址生成的，这样其实对象的值相同的话，因为地址不同，`HashMap` 也会出现异常，所以需要重写，同时也需要重写 `equals` 方法，才能确认是同一个 `key`，而不是落在同一个槽的不同 `key`.

## 聊聊 mysql 索引的一些细节

发表于 2020-12-27 分类于 Mysql ， C ， 索引 ， Mysql 阅读次数： Disqus：

前几天同事问了我个 mysql 索引的问题，虽然大概知道，但是还是想来实践下，就是 is null，is not null 这类查询是否能用索引，可能之前有些网上的文章说都是不能用索引，但是其实不是，我们来看个小试验

    CREATE TABLE `null_index_t` (
      `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
      `null_key` varchar(255) DEFAULT NULL,
      `null_key1` varchar(255) DEFAULT NULL,
      `null_key2` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `idx_1` (`null_key`) USING BTREE,
      KEY `idx_2` (`null_key1`) USING BTREE,
      KEY `idx_3` (`null_key2`) USING BTREE
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

用个存储过程来插入数据

    delimiter $	#以delimiter来标记用$表示存储过程结束
    create procedure nullIndex1()
    begin
    declare i int;
    declare j int;
    set i=1;
    set j=1;
    while(i<=100) do
    	while(j<=100) do
    		IF (i % 3 = 0) THEN
    	     INSERT INTO null_index_t ( `null_key`, `null_key1`, `null_key2` ) VALUES (null , LEFT(MD5(RAND()), 8), LEFT(MD5(RAND()), 8));
        ELSEIF (i % 3 = 1) THEN
    			 INSERT INTO null_index_t ( `null_key`, `null_key1`, `null_key2` ) VALUES (LEFT(MD5(RAND()), 8), NULL, LEFT(MD5(RAND()), 8));
    	  ELSE
    			 INSERT INTO null_index_t ( `null_key`, `null_key1`, `null_key2` ) VALUES (LEFT(MD5(RAND()), 8), LEFT(MD5(RAND()), 8), NULL);
        END IF;
    		set j=j+1;
    	end while;
    	set i=i+1;
    	set j=1;
    end while;
    end
    $
    call nullIndex1();

然后看下我们的 is null 查询

    EXPLAIN select * from null_index_t WHERE null_key is null;

再来看看另一个

    EXPLAIN select * from null_index_t WHERE null_key is not null;

从这里能看出来啥呢，可以思考下

从上面可以发现，`is null`应该是用上了索引了，所以至少不是一刀切不能用，但是看着`is not null`好像不太行额
我们在做一点小改动，把这个表里的数据改成 9100 条是 null，剩下 900 条是有值的，然后再执行下
然后再来看看执行结果

    EXPLAIN select * from null_index_t WHERE null_key is null;

    EXPLAIN select * from null_index_t WHERE null_key is not null;

是不是不一样了，这里再补充下我试验使用的 mysql 是 5.7 的，不保证在其他版本的一致性，
其实可以看出随着数据量的变化，mysql 会不会使用索引是会变化的，不是说 is not null 一定会使用，也不是一定不会使用，而是优化器会根据查询成本做个预判，这个预判尽可能会减小查询成本，主要包括回表啥的，但是也不一定完全准确。

## 从丁仲礼被美国制裁聊点啥

发表于 2020-12-20 分类于 生活 ， 吐槽 ， 疫情 ， 美国 阅读次数： Disqus：

几年前看了柴静的《穹顶之下》觉得这个记者调查得很深入，挺有水平，然后再看到了她跟丁仲礼的采访，其实没看完整，也没试着去理解，就觉得环境问题挺严重的，为啥柴静这个对面的这位好像对这个很不屑的样子，最近因为丁仲礼上了美国制裁名单，B 站又有人把这个视频发了出来，就完整看了下，就觉得自己挺惭愧的，就抱着对柴静的好感而没来由的否定了丁老的看法和说法，所以人也需要不断地学习，改正之前错误的观点，当然不是说我现在说的就是百分百正确，只是个人的一些浅显的见解

先聊聊这个事情，整体看下来我的一些理解，IPCC给中国的方案其实是个很大的陷阱，它里面有几个隐藏的点是容易被我们外行忽略的，第一点是基数，首先发达国家目前（指2010年采访或者IPCC方案时间）的人均碳排放量已经是远高于发展中国家的了，这也就导致了所谓的发达国家承诺减排80%是个非常有诚意的承诺其实就是忽悠；第二点是碳排放是个累计过程，从1900年开始到2050年，或者说到2010年，发达国家已经排的量是远超过发展中国家的，这是非常不公平的；第三点其实是通过前两点推导出来的，也就是即使发达国家这么有诚意地说减排80%，扒开这层虚伪的外衣，其实是给他们11亿人分走了48%的碳排放量，留给发展中国家55亿人口的只剩下了52%；第四点，人是否因为国家的发达与否而应受到不平等待遇，如果按国家维度，丁老说的，摩纳哥要跟中国分同样的排放量么，中国人还算不算人；第五点，这点算是我自己想的，也可能是柴静屁股决定脑袋想不到的点，她作为一个物质生活条件已经足够好了，那么对于那些生活在物质条件平均线以下的，他们是否能像城里人那样有空调地暖，洗澡有热水器浴霸，上下班能开车，这些其实都直接或者间接地导致了碳排放；他们有没有改善物质生活条件地权利呢，并且再说回来，其实丁老也给了我们觉得合理地方案，我们保证不管发达国家不管减排多少，我们都不会超过他们的80%，我觉得这是真正的诚意，而不是接着减排80%的噱头来忽悠人，也是像丁老这样的专家才能看破这个陷阱，碳排放权其实就是发展权，就是人权，中国人就不是人了么，或者说站在贫困线以下的人民是否有改善物质条件的权利，而不是说像柴静这样，只是管她自己，可能觉得小孩因为空气污染导致身体不好，所以做了穹顶之下这个纪录片，想去改善这个事情，空气污染不是说对的，只是每个国家都有这个过程，如果不发展，哪里有资源去让人活得好，活得好了是前提，然后再去各方面都改善。

对于这个问题其实更想说的是人的认知偏差，之前总觉得美帝是更自由民主，公平啥的，或者说觉得美帝啥都好，有种无脑愤青的感觉，外国的月亮比较圆，但是经历了像川普当选美国总统以来的各种魔幻操作，还有对于疫情的种种不可思议的美国民众的反应，其实更让人明白第一是外国的月亮没比较圆，第二是事情总是没那么简单粗暴非黑即白，美国不像原先设想地那么领先优秀，但是的确有很多方面是全球领先的，天朝也有体制所带来的优势，不可妄自菲薄，也不能忙不自大，还是要多学习知识，提升认知水平。

## Leetcode 105 从前序与中序遍历序列构造二叉树(Construct Binary Tree from Preorder and Inorder Traversal) 题解分析

发表于 2020-12-13 分类于 Java ， leetcode ， Binary Tree ， java ， Binary Tree ， DFS 阅读次数： Disqus：

## 题目介绍

Given preorder and inorder traversal of a tree, construct the binary tree.
给定一棵树的前序和中序遍历，构造出一棵二叉树

### 注意

You may assume that duplicates do not exist in the tree.
你可以假设树中没有重复的元素。(PS: 不然就没法做了呀)

### 例子:

    preorder = [3,9,20,15,7]
    inorder = [9,3,15,20,7]

返回的二叉树

      3
     / \
    9  20
      /  \
     15   7

## 简要分析

看到这个题可以想到一个比较常规的解法就是递归拆树，前序就是根左右，中序就是左根右，然后就是通过前序已经确定的根在中序中找到，然后去划分左右子树，这个例子里是 3，找到中序中的位置，那么就可以确定，9 是左子树，15,20,7是右子树，然后对应的可以根据左右子树的元素数量在前序中划分左右子树，再继续递归就行

    class Solution {
        public TreeNode buildTree(int[] preorder, int[] inorder) {
          // 获取下数组长度
            int n = preorder.length;
            // 排除一下异常和边界
            if (n != inorder.length) {
                return null;
            }
            if (n == 0) {
                return null;
            }
            if (n == 1) {
                return new TreeNode(preorder[0]);
            }
            // 获得根节点
            TreeNode node = new TreeNode(preorder[0]);
            int pos = 0;
            // 找到中序中的位置
            for (int i = 0; i < inorder.length; i++) {
                if (node.val == inorder[i]) {
                    pos = i;
                    break;
                }
            }
            // 划分左右再进行递归，注意下`Arrays.copyOfRange`的用法
            node.left = buildTree(Arrays.copyOfRange(preorder, 1, pos + 1), Arrays.copyOfRange(inorder, 0, pos));
            node.right = buildTree(Arrays.copyOfRange(preorder, pos + 1, n), Arrays.copyOfRange(inorder, pos + 1, n));
            return node;
        }
    }