V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  GuuJiang  ›  全部回复第 5 页 / 共 19 页
回复总数  375
1  2  3  4  5  6  7  8  9  10 ... 19  
你这是刚入门 leetcode 吧? leetcode 所有涉及到二叉树的题目的表示法都是一样的,你本地没问题说明你本地从 input 数组构造树的过程也是错误的,构造出的树的高度远远小于实际的高度
@movq 你完全理解错 leetcode 对树的表示法了,它的用例里只会包含叶子结点的 null 节点,而不包含“当表示为完全二叉树时下面的层需要补全的那些虚拟 null 节点”数据输入框本身就自带树可视化功能,点开看一眼图就明白问题在哪了
不要纠结具体的一个测试用例了,你这个思路注定是不可行的,空间复杂度随随便便就爆表了,别说是 20000 个节点了,很容易就能构造出一个表面上看只有几十个结点,但是所需空间超出宇宙中原子个数的树
我说的空节点不仅是显式的空节点,还包括下面所有层,你这样的表示法所需的空间是高度等于原树高度的一棵完全二叉树,画个图就明白问题在哪了
你这样相当于用完全二叉树来表示,其中大量不存在的空节点仍然要占用 index ,最简单的一个例子,假如 20000 个节点退化成了链表,也就是说树的高度达到 20000 ,想想这时候 index 需要达到多少
2022-06-17 09:39:08 +08:00
回复了 yitiaoxiaoxi 创建的主题 奇思妙想 征集软件名字
钢交
同时涵盖了钢铁和交通运输二个行业
2022-06-16 11:06:47 +08:00
回复了 eitomomobaohua 创建的主题 生活 老实人的神奇经历
很遗憾以这种方式认识你
2022-06-14 17:32:39 +08:00
回复了 zhao1014 创建的主题 Java 关于 Stringbuilder 中 append 方法的实现有一个疑问
@skinny 这里是不可能自动优化的,因为无法确保 this.value 不会被别的线程修改
2022-06-14 12:22:12 +08:00
回复了 zhao1014 创建的主题 Java 关于 Stringbuilder 中 append 方法的实现有一个疑问
不这样的话下面的每一行代码都要写成 this.value[c++],对应的 opcode 要多一次 get_field 操作
2022-06-13 12:35:29 +08:00
回复了 ak1ak 创建的主题 Java 关于 Java 泛型方法定义的疑惑
@nothingistrue 如果你真正看懂了我在隔壁的回答就不会有这个疑问了,你在#1 和#3 说的这种场景是不可能实现的,不是 Java 语法的限制,而是你假想的这个需求本身就有问题,我们姑且先忽略掉 List<? super A3>是不能进行 get 操作的这一点,退一步讲,哪怕允许 get 了,简化一下需求,要定义一个方法,其参数可能是 A3 及其父类,那你在这个方法的内部能够把这个参数当成什么类型呢?唯一的选择就只有 Object 了,这里的 A3 没有提供任何信息量,因此这样的方法没有任何的意义,也不可能存在,也不可能具有实际应用场景
和你试图假定的这个场景最接近的应该是下面这个
interface Util<T extends A1> {
void process(List<? super A3> list, Supplier<? extends A3> function);
}
这里的 Supplier<? extends A3>也可以换成 Function<T, ? extends A3>,其中的 T 是任意一个具体类型
然后在你的 process 内部也不能像你想象的那样从 list 中 get 然后交给 function 处理,而只能调用 function 然后将返回值 add 到 list
坦白说这确实是演示 PECS 原则的一个很好的例子
2022-06-10 21:33:11 +08:00
回复了 rqxiao 创建的主题 Java 对 Java 之泛型通配符 ?extends T 的认知 和 ? super T 一些疑惑
@GuuJiang 幹,想换行时老是习惯性按 cmd+enter ,又发出来了,书接上文。。。
把这三个类型写成下面的形式可能会更容易理解
List<T>

@Covariant
List<T>

@Contravariant
List<T>

也就是说给 List<T>这个类型额外指定一个 variance 属性,variance 可以有三种取值,分别是 Invariant(默认)、Covariant 和 Contravariant ,下面给出这三种关系的正式定义
如果 B 是 A 的子类,则 List<B>是 List<A>的子类,那么这样的类型系统称为协变(covariant)
如果 B 是 A 的子类,则 List<A>是 List<B>的子类,那么这样的类型系统称为逆变(contravariant)
如果不管 A 和 B 之间是否有子类关系,List<A>和 List<B>之间都没有子类关系,那么这样的类型系统称为不变(invariant)
所以? extends 和? super 语法的本质是,在 Java 的类型系统默认为 invariant 的前提下,人为指定某个类型的 variance 属性,使其变成 covariant 的或者 contravariant 的
到此为止,我们搞清了第一个问题,即为什么要有协变和逆变,在默认情况下,如果有一个方法的一个参数类型为 List<Number>,那么在使用这个方法时它只接受 List<Number>,而如果将其指定为协变的,它就可以接受 List<Integer>,如果将其指定为逆变的,它就可以接受 List<Object>,所以就好像范型的出现扩展了方法的适用范围一样,协变和逆变的出现进一步扩展了方法的适用范围
下面就到了第二个问题,这个方法的参数可以接收的范围广了以后,对这个参数的值的使用上就和原来有区别了,需要受到一些限制,而这个限制就是被无数人提起过的 PECS 原则,话说 PECS 是第二个我个人不太喜欢的概念,确实,它是一个非常精妙的总结,使得看过它的人能够很容易地记忆对于协变或者逆变后的类型在使用上的限制,但是它同时也带来了另一个误解,PECS 里的“生产”、“消费”等概念假定了范型类型一定是某种“容器”,诚然,用到范型最多的场景确实就是容器,就好像我写的这段话一样,提到范型的时候第一反应也是拿 List 来举例,但是事实上,范型和容器之间没有任何必然联系,对于编译器来说也不存在“生产”、“消费”等业务层面的概念,那么“生产”和“消费”的本质到底是什么呢?
写到这里回头补充下前面的一段,先从简单类型入手了解下类型约束的本质是什么,前面提到的著名误解是误认为范型约束约束的是容器类型和容器内对象的类型,那实际上后者到底是由谁来约束的呢,事实上,如果有一个 List<T>,那这个 List 里能放什么类型的对象呢,答案是 T 及 T 的子类,这个相信每个人都知道,但是深入想想,类型系统是编译器关心的事情,而“一个类型为 List<T>的 list 里面能放什么类型的对象”这个问题明显是个业务层面的问题,对于编译器来说,“容器”、“容器里元素的类型”等这些概念都是不存在的,那我们天天挂在嘴上的“List<T>里能够存放 T 及其子类”这个结论到底是由谁来保证的呢,真正的答案是,List<T>里方法 add 的签名为 add(T),那根据里氏代换原则,这个参数自然接受 T 及其子类都是合法的,这才是这个约束的本质,编译器只负责校验方法的签名,保证了 add 方法只能接受 T 及其子类的参数,最终产生的效果才是我们说的“List<T>里能够存放什么”这个问题
回到 PECS 这边来,所以说,对于编译器而言,“生产”的本质是“调用返回值为 T 的方法”,“消费”的本质是“调用参数为 T 的方法”,理解了这一点,PECS 自然就是顺理成章的了,而且根本不用去记,随便推导一下就能得出答案,下面试验一下按照上述的方法从头推导 PECS 原则,假如现在有一个引用 List<? extends Number> l ,那么下面这个语句是否是合法的
Number n = l.get(0)
答案是是,因为协变原则保证了 l 的实际类型可能为 List<Number>、List<Integer>、List<Float>等,无论是那种,其 get 方法的返回值肯定是 Number 或 Number 的子类,因此将这个返回值赋值给 Number 是完全没有问题的,反之,如果 l 的类型是 List<? super Number>,那么它的实际类型可能是 List<Number>、List<Object>等,因此是无法保证 add 的返回值一定能够赋值给 Number 的
反之,对于如下的语句
Number n = ...
l.add(n)
如果 l 是 List<? super Number>,那么不管它的实际类型是什么,其 add 方法一定能够接收 Number 类型的参数,而如果 l 是 List<? extends Number>,就无法保证其 add 方法一定能够接收 Number 类型的参数
以上才是 PECS 原则的本质

想到哪写到哪,一不小心写了这么多,可能有点啰嗦,其实相信对于很多人来说,看到讲误解的那一段应该就能自己想通后面的这些内容了,权当是自己的一份笔记吧,希望这篇回答可以就此终结所有关于范型约束的月经问题
最后再补充一点,凡是提到 Java 的范型相关的问题总有人要提到擦除法,实际上对于今天的这个问题来说,和擦除法没有任何的关系,因为这些都是类型系统相关的问题,是编译阶段处理的问题,而擦除法是运行阶段的问题,二者之间没有任何联系,不管 Java 采不采用擦除法,今天讨论的这个问题的结论都不会有任何变化
2022-06-10 20:45:21 +08:00
回复了 rqxiao 创建的主题 Java 对 Java 之泛型通配符 ?extends T 的认知 和 ? super T 一些疑惑
@GuuJiang 不小心按错发出来了,接着写
参见我在 https://v2ex.com/t/790199#reply32 里的回答,这里我多花点篇幅争取把这个问题一次性讲清楚
首先准备点开胃菜,当我们有一个类型为 A 的引用 a 和一个类型为 B 的对象 b ,什么情况下 a = b 这个语句是合法的?答案就是当 b instanceof A 的时候,这就是 OOP 的几大基石之一的里氏代换原则,也就是说当需要一个类型 A 的场合,类型 A 及其子类都是可以接受的,这也就是为什么 instanceof 运算符的反射版本名字叫作 isAssignableFrom
对于简单类型来说,任给两个类型 A 和 B ,判断 A 和 B 之间是否具有 instanceof 关系是很简单的,直接看两个类的定义就行,如果类的定义里有直接或者间接的 extends 或者 implement 关系,则具有 instanceof 关系,否则不具有
但是到了范型的世界里,问题就开始变得复杂起来了,如果 B 是 A 的子类,那 List<A>和 List<B>这两个类型里,谁是谁的子类?答案是不具有 instanceof 关系,这就叫做 invariant ,但是 invariant 的类型系统功能比较弱,很多想要的功能实现不了,这就需要引入另外两种关系,即协变(covariant)、逆变(contravariant)
回到我最开始说的,在关于范型约束的这个问题上,很多人存在一个误解,或者曾经存在这个误解,导致总是觉得有哪里不对,怎么也圆不回来,再听完别人说什么协变逆变、PECS 等以后就更懵了,而只要这个误解一消除,范型约束瞬间就变得异常简单,没有任何不理解的地方,这个误解就是“认为 List<? extends T>和 List<? super T>里的 extends 和 super 约束的是类型 T 和这个 list 里能够放的对象类型之间的 instanceof 关系”,而这个误解的正确答案是,把 List<? extends T>这个类型整体视为类型 A ,把 List<? super T>这个类型整体视为 B ,然后现在有若干的类型 C 、D 、E……,都是 List<X>的形式,其中的 X 为某个具体类型,extends 和 super 真正约束的是 A 与 B 和 C 、D 、E 之间的 instanceof 关系,换句话说就是,有一个 List<? extends T>类型的引用,这个引用能接受什么类型的 list 对象,有一个 List<? super T>类型的引用,这个引用能接受什么类型的 list 对象,这才是范型约束的真正含义
其实我个人并不喜欢 Java 的这个语法,应该说这个误解这么普遍,可能跟 Java 选择的这个语法有一定的关系,而相比起来 c#和 scala 在这方面的语法就会稍好一点,因为 Java 的这个语法给人一种错觉,就是 List<T>、List<? super T>、List<? extends T>是三个不同的类型,当然严格说起来它们确实是三个不同的类型,但是从某种意义上来说,可以认为它们都是 List<T>,但是具有不同的 variance 属性,换句话说,如果换成下面的伪代码可能会更好理解
2022-06-10 20:13:48 +08:00
回复了 rqxiao 创建的主题 Java 对 Java 之泛型通配符 ?extends T 的认知 和 ? super T 一些疑惑
这可以算得上是一个月经问题了,和其他另外几个问题一样,属于里面有一个弯转过来了就一切迎刃而解,转不过来就始终似懂非懂
@inprtx 你没有理解 op 的目的,结合着 op 的上一篇帖子看,是利用墙内部不同模块之间的优先级,所以人为制造一个满足被墙条件的场景是必需的
2022-06-06 17:13:06 +08:00
回复了 zxCoder 创建的主题 问与答 请教一个算法问题
对 b 建立 AC 自动机
@cpstar 帮 op 断下句
两个(((等于 1 的位)不重叠)的二进制数)
2022-05-31 16:22:41 +08:00
回复了 git00ll 创建的主题 问与答 关于年终奖多发一元,多扣税几千的计算方式有点搞不明白
是个典型的不顾适用条件滥用二级结论的例子
首先,月薪的个人所得税是分段函数,但是为了简化分段函数的计算,所以总结出了速算扣除数,用速算扣除数计算和直接计算分段函数是等价的
但是到了年终奖的计算时如果仍然想继续使用速算扣除数的话,它的值应该 x12 ,这样背后对应的仍然是一个合理的分段函数,而把计算月薪时的速算扣除数机械地搬过来是没有任何道理的,因为这个速算扣除数再还原回原本的函数就会得到现在的那个锯齿状的函数
生活中有很多这样的例子,为了计算或者记忆简便,人们总结出了很多的二级结论,但是随着时间的推移,人们慢慢地只记二级结论,而完全忽视了背后的本质内容,并且在本质结论不适用的场景强行套用二级结论,自然就得到了错误的结果,类似的例子有极限计算里的等价无穷小
@xiaoyanbot 故名思义就是不填充,但是有个限制就是数据必须正好要是 16 的整数倍,所以通常都不会使用这个模式的,而是使用 pkcs5 填充,因为你要求得到和你给的 go 示例里完全一致的结果,所以在这个例子里只有 nopad 才能满足,但是真正用于通用场景的话还是应该使用 pkcs5 填充
顺便附上解密
echo -n 319d4fa655ed543b4aa0d1efdc3619d8 | xxd -r -p | openssl enc -aes-256-ecb -d -nopad -K 746869735f6d7573745f62655f6f665f33325f627974655f6c656e6774682121
1  2  3  4  5  6  7  8  9  10 ... 19  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   4033 人在线   最高记录 6543   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 34ms · UTC 10:19 · PVG 18:19 · LAX 03:19 · JFK 06:19
Developed with CodeLauncher
♥ Do have faith in what you're doing.