问题朋友【第二季】——七月打卡帖

本文梳理了LeetCode中的关键题目,涉及并查集、树数据结构(如二叉树和环形链表)、数组动态操作、哈希算法(同构字符串和哈希冲突)、以及二分法在查找中的应用。还对比了JAVA和.NET平台的差异,介绍了C#中字典、哈希表和TCP/UDP的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Leetcode

并查集

【中等】 128. 最长连续序列
【中等】 130. 被围绕的区域
【困难】 765. 情侣牵手

【困难】 124. 二叉树中的最大路径和
【中等】107. 二叉树的层序遍历 II

数组

【简单】1480. 一维数组的动态和
【简单】724. 寻找数组的中心下标

哈希

【简单】205. 同构字符串

二分法

【简单】392. 判断子序列(一个简单题,但是尝试了复杂的做法,复习了二分法和一点点dp)

链表

【简单】21. 合并两个有序链表
【简单】206. 反转链表(不用栈做)
【简单】876. 链表的中间结点(双指针)
【中等】142. 环形链表 II

知识点回顾

JAVA和.NET

JAVA平台包括语言,虚拟机,以及API库。由于使用虚拟机机制,所以JAVA语言在所有的平台上只有唯一的版本,因此它使用RMI协议进行远程通信;微软则在.NET框架中使用DCOM,现在正在逐步演变为SOAP。

.NET是一种用于构建多种应用的免费开源开发平台,可以使用多种语言,编辑器和库开发各种应用。是第一个支持多种语言混合开发的软件运行平台。
.NET框架包括C++, VB.NET和C#等一系列语言;以及与JAVA虚拟机类似的一套运行环境;以及一套倾向与WINDOWS体系的API接口。它的运行时环境可能存在于一个浏览器、或是一个WEB SERVER、或是在操作系统中。

本质区别: java是一个开源的跨平台的语言;.NET是一个跨语言的平台(.NET是语言无关,java是平台无关)

.NET的初级组成是CIL和CLR。

CLR(Commen Language Runtime,公用语言运行时)和Java虚拟机一样也是一个运行时环境,它负责资源管,并保证应用和底层操作系统之间必要的分离。公共语言运行库,负责资源管理(包括内存分配、程序及加载、异常处理、线程同步、垃圾回收等),并保证应用和底层操作系统的分离。

CIL(Common Intermediate Language,通用中间语言)是一种属于通用语言架构和.NET框架的低阶的人类可读的编程语言。目标为.NET 框架的语言被编译成CIL,然后汇编成字节码。CIL类似一个面向对象的汇编语言,并且它是完全基于堆栈的。它运行在虚拟机上,其主要的语言有C#、Visual Basic .NET、C++/CLI以及 J#。

  1. 在编译.NET编程语言时,源代码被翻译成CIL码,而不是基于特定平台或处理器的目标代码。
  2. CIL是一种独立于具体CPU和平台的指令集,它可以在任何支持.NET framework的环境下运行。
  3. CIL码在运行时被检查并提供比二进制代码更好的安全性和可靠性。

.NET与JAVA的区别【完整版】
.net和java的区别有哪些

装箱拆箱

装箱:值类型转化为引用类型(基本类型转变为包装器类型)
拆箱:引用类型转化为值类型(包装器类型转变为基本类型)

包装类和基本类型的主要区别如下:
1、创建的方式不同,包装类需要使用new来创建,基本类型不需要。
2、存储的位置不同,包装类是把对象放在堆中,然后在栈中引用这些对象。基本类型直接将值保存在栈中
3、初始化的值不同,包装类对象的初始值为null,而基本类型不是。
4、参数的传递不同,包装类是对象引用的传递,基本类型是值传递 。

Integer i = new Integer(1); //JDK1.5之前是不支持自动装箱和自动拆箱
Integer i = 1; //JDK1.5开始,提供了自动装箱的功能,自动调用的是Integer的valueOf(int)方法

int n = i;//自动拆箱,自动调用的是Integer的intValue方法

在通过valueOf方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。

public static Integer valueOf(int i) 
{
    if(i >= -128 && i <= IntegerCache.high)
        return IntegerCache.cache[i + 128];
    else
        return new Integer(i);
}

深入剖析Java中的装箱和拆箱
装箱和拆箱

  1. 为什么要装箱?
    (1)泛型容器需要Object来保证通用(2)方法的参数是Object类型的(3)不确定参数类型的时候可以方便参数的存储和传递
  2. 装箱内部操作?【额外耗费cpu和内存资源】
    (1)分配内存(2) 拷贝值(3)返回托管堆中分配的地址
  3. 拆箱内部操作?【拆箱的消耗低于装箱】
    (1)检查对象实例(2) 拷贝值

C#字典和哈希表

哈希冲突

对利用开放地址法查了冲突所产生的哈希表中删除一个元素,不能简单地直接删除,因为这样将截断其它具有相同哈希地址的元素的查找地址,所以应设定一个特殊的标志以表明该元素已被删除。

  1. 开放地址法(闭散列法)
    (1)线性探测:D = H(key); ND = (D+di)%m; di取1,2,3,……,m-1
    (2)再平方探测:D = H(key); ND = (D+di)%m; di取1*1,-1*1,2*2,-2*2,……,K*K,-K*K  (K≤m/2)
    (3)伪随机探测:di是随机数
  2. 拉链法(链地址法、开散列法)
  3. 再哈希法
  4. 建立公共溢出区:额外创建一个公共溢出区,专门用来存放发生哈希冲突的元素

字典 Dictionary

Entry的结构,存储字典内容

private struct Entry {
	public int hashCode; // 除符号位以外的31位hashCode值, 如果该Entry没有被使用,那么为-1
	public int next; // 下一个元素的下标索引,如果没有下一个就为-1
	public TKey key; // 存放元素的键
	public TValue value; // 存放元素的值
}

字典初始化

private void Initialize(int capacity)
{
     int prime = HashHelpers.GetPrime(capacity);  // 大于字典容量的一个最小的质数
     this.buckets = new int[prime];  // 用来进行Hash碰撞
     for (int i = 0; i < this.buckets.Length; i++)
     {
         this.buckets[i] = -1;
     }
     this.entries = new Entry<TKey, TValue>[prime];  // 用来存储字典的内容,并且标识下一个元素的位置
     this.freeList = -1;  // 被删除Entry在entries中的下标index,这个位置是空闲的
 }

添加四个元素之后,前面的三个元素有哈希冲突的情况,可以看到buckets存放的是最后一个entry的下标
在这里插入图片描述

  1. 通过Hash算法来碰撞到指定的Bucket上,碰撞到同一个Bucket槽上所有数据形成一个单链表
  2. 默认情况Entries槽中的数据按照添加顺序排列
  3. 删除的数据会形成一个FreeList的链表,添加数据的时候,优先向FreeList链表中添加数据,FreeList为空则按照count依次排列

哈希表 Hashtable

bucket来表示哈希表中的单个元素

private struct Bucket
{
     public object? key;
     public object? val;
     public int hash_coll;   // 存储的是hash(key, i)的值而不是哈希地址 
}

C#使用了闭散列法来解决冲突,java采用开散列法解决冲突

  1. key/value均为object类型,由包含集合元素的存储桶组成。
  2. 每一存储桶都与一个哈希代码关联,该哈希代码是使用哈希函数生成的并基于该元素的键。
  3. 优点就在于其索引的方式,速度非常,适用于做对象缓存,树递归算法的替代,和各种需提升效率的场合。
  4. 线程安全的,是因为方法上都加了synchronized关键字。

Hashtable的遍历

Hashtable hashtable = new Hashtable();
//方法一
foreach (System.Collections.DictionaryEntry de in hashtable)
{ //注意HastTable内存储的默认类型是object,需要进行转换才可以输出
  Console.WriteLine(de.Key.ToString());
  Console.WriteLine(de.Value.ToString());
}

//方法二
System.Collections.IDictionaryEnumerator enumerator = hashtable.GetEnumerator();
while (enumerator.MoveNext())
{
  Console.WriteLine(enumerator.Key);    // Hashtable关健字
  Console.WriteLine(enumerator.Value);   // Hashtable值
}

字典和哈希表区别

  1. 数据类型(Hashtable相当于Dictionary<Object,Object>)
    (1)Dictionary是泛型的,是类型安全的;Hashtable不是泛型的,不是类型安全的。
    (2)Dictionary添加的元素有类型约束,无需装箱、拆箱;可以添加任何元素,添加时装箱,读取时拆箱。
  2. 数据存储顺序
    Dictionary在只添加不删除的时候能够保持读取数据的顺序和添加时候的顺序是一致的;Hashtable则是无序的。
  3. 读写速度
    频繁调用数据时Dictionary快;添加数据时Hashtable快。
  4. 线程角度
    (1)单线程程序中推荐使用 Dictionary,有泛型优势,且读取速度较快,容量利用更充分。
    (2)多线程程序中推荐使用 Hashtable,默认的 Hashtable 允许单线程写入,多线程读取,对 Hashtable 进一步调用 Synchronized()方法可以获得完全线程安全的类型。而Dictionary 非线程安全,必须人为使用 lock 语句进行保护,效率大减。
    (3)Dictionary不是线程安全的,Hashtable是线程安全的。
  5. 读取不存在的键值
    Dictionary引发“KeyNotFoundException”异常;Hashtable返回null。

HashSet/HashTable/Dictionary,这三个容器的底层都是哈希表

带你看懂Dictionary的内部实现
浅析C# Dictionary实现原理
HashTable .net core 中的Hashtable的实现原理
聊聊C# 中HashTable与Dictionary的区别说明
C#:Hashtable和Dictionary

Java HashMap

之前写过的用法
Java语法篇-Map

jdk1.6、jdk1.7中采用位桶(数组)+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表;
jdk1.8引入了红黑树的设计,当hash表的单一链表长度超过 8个的时候,链表结构就会转为红黑树结构。
为什么要这样设计呢?好处就是避免在最极端的情况下(位于一个桶中的元素较多,即hash值相等的元素较多)链表变得很长很长,在查询的时候,效率会非常慢。

底层实现(数组+链表+红黑树)

  1. 初始容量为16,默认负载因子为0.75(C#为0.72),每次扩容为原来的两倍。
  2. 插入元素
    (1)根据key计算哈希值,找到桶的位置;
    (2)hash冲突的话,拉链法(链表是尾插法);
    (3)没有冲突,new一个新链表放进桶中;
    (4)链表长度大于8时,转化为红黑树。
  3. 查找元素
    (1)获取到hash值,然后找到桶;
    (2)遍历链表或者红黑树;
    (3)先比较hashcode方法;
    (4)hashcode相同,再比较equals方法。
    在这里插入图片描述

要点

  1. 线程不安全;
    (1)使用HashTable替代来解决(HashTable的put操作有synchronized修饰);
    (2)jdk1.8中ConcurrentHashMap通过 CAS+synchronized实现线程安全;
    (3)使用Map map = Collections.synchronizedMap(new HashMap());
  2. key和value都可以是null;
  3. 链表长度小于8使用红黑树的话左旋右旋操作消耗时间;
  4. 扩容之后可能要重新计算hash;
  5. 红黑树阈值是8,是因为泊松分布,单个hash槽中元素为8的概率小于百万分之一;
  6. 使用HashTable替代HashMap来解决

java面试题:Hashmap常见面试题
Java面试–HashMap是如何添加元素的

TCP和UDP的区别

详见之前写过的
计算机网络面试——基础+层次

  1. TCP是面向连接的,可靠性高;UDP是基于非连接的,可靠性低。
  2. TCP在IP协议的基础上添加了序号机制、确认机制、超时重传机制等,保证了传输的可靠性,不会出现丢包或乱序,而UDP有丢包,故TCP开销大,UDP开销较小。
  3. TCP面向字节流,UDP面向报文。
  4. 由于TCP是连接的通信,需要有三次握手、重新确认等连接过程,会有延时,实时性差,同时过程复杂,也使其易于攻击;UDP没有建立连接的过程,因而实时性较强,也稍安全。
  5. 在传输相同大小的数据时,TCP首部开销20字节;UDP首部开销8字节,TCP报头比UDP复杂,故实际包含的用户数据较少。
  6. 每条TCP连接只能时点到点的;UDP支持一对一、一对多、多对一、多对多的交互通信。

TCP可靠传输使用机制:校验、序号、确认、重传

  1. 校验:TCP的校验机制和UDP相同;
  2. 序号:TCP首部的序号字段用来保证数据能有序提交到应用层(只保证有序发送,不保证有序到达);
  3. 确认:TCP采用累积确认,确认号是期望收到对方下一个报文段的数据的第一个字节的序号;
  4. 重传:导致TCP重传的两种事件——超时和冗余ACK。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值