数据结构(Java)——Day2(Day1作业部分和泛型原理了解)

本文详细解析了大O表示法、时间复杂度与空间复杂度的计算,涵盖递归、排序算法、二分查找等常见算法。同时,介绍了泛型机制原理,包括泛型类、泛型方法的定义与使用,并探讨了类型擦除的概念。此外,针对LeetCode面试题,提供了两种不同的解决方案,分别是基于排序和数学运算的消失数字查找,以及循环数组和反转数组的轮转数组操作。

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

一、11.10题目讲解

1. 例题1:

 解析:答案为C,不会保留系数,大O表示法是当问题规模N趋近于正无穷的时候主要产生影响的部分,当N趋近于正无穷的时候,系数可以忽略不记,因此系数仅保留1

2.  例题2

 解析:答案为D。

方法1:i * 2 * 2... = n。相当于n / 2 / 2 .....。满足n / 某个数一直当n = 1或0的条件,满足log n 级别的时间复杂度

方法2:假设最深层循环的某一语句执行次数为x的时候循环退出(此处这个语句为i = i * 2),则此时的i = 2^x。即为i > n 等价于 2 ^ x > n。即x = log2(n)

3. 例题3

解析:最深层循环的语句a[i][j] = i * j的执行次数即为时间复杂度,也就是O(n ^ 2)级别

4. 例题4

解析:本题目的代码实现思路是采用双引用法来实现。

(1)

left = 0; 指向数组最小值

right = arr.length - 1;指向数组最大值

(2)因为是比较sum和 a + b之间的接近程度,因此计算差值的绝对值,绝对值越小,则越接近

令ret = arr[left] + arr[right];dif = Math.abs(sum - ret) (注意:这里的Math.abs是求绝对值的)

当ret > sum,则说明大了,需要变小,left已经是最小了,因此right --

当ret < sum,则说明小了,需要变大,left ++

(3)重复步骤2,当本次的dif小于上次的dif,则将本次对应的left 和 right的值用变量a和b做接收。否则不做改动。

int a = left;

int b = right;

(4)当left > right的时候,遍历完成整个数组,此时a 和 b保存的就是对应所求

结论:回归原题,由于只需要一次循环就可以完成查找或者说最坏需要对比n次才能查找完成,因此时间复杂度为O(n)数量级。

注意:二分查找有序数组的前提是查找一个目标元素的时候使用

5. 例题5

 解析:对于递归函数来说,时间复杂度等于递归函数执行的次数。在这里也就是n + 1次,也就是O(n)数量级

两种解题思路:

方法1:递归展开图,看一下节点的个数(也就是函数执行次数),求节点的个数就是函数的调用次数

方法2:代入一个实际的值来看一下

T(3) = T(2) + 3 = T(1) + 2 + 3 = T(0) + 1 + 2 + 3。因此执行次数为n + 1次 

6. 例题6

 

解析:当问题规模N趋近于正无穷的时候,这个常数级别的二维数组对于空间复杂度的影响近乎没有,因此为O(1)

7. 例题7

解析:空间复杂度是为了解决问题而额外开辟的空间大小。在这里也就是二维数组。二维数组的行为n,列为从n到1递减。因此二维数组大小为1 + 2 + 3 + 4 + 5 + 6 +...+ n。也就是 n(1 + n) / 2。也就是O(N^2)数量级 

8. 面试题1

面试题 17.04. 消失的数字 - 力扣(Leetcode)https://leetcode.cn/problems/missing-number-lcci/description/题目解析:输入的是一个长度为n的数组arr(也就是nums缺失整数后的数组),nums数组是一个长度为n + 1 的数组。

(1)解法1(时间复杂度为O(nlog)因为最快的排序算法就是这个数量级)

A. 思路:

a. 排序

b. arr从左到右依次遍历,若发现数组的元素值和下标值不相同,则说明缺失的值为该下标的值

c.  若遍历完成后都没有发现,则说明缺失的值是最后一个值,这个缺失值 = arr.length

B. 举例

C. 代码实现

(2)解法2:数学运算。时间复杂度为O(N)

A. 思路:nums数组的值之和 - arr数组的值之和即为消失的数字

B. 代码实现:

(3)解法3:映射

A. 思路:

a. 创建一个长度为n+1的数组hash来存储元素是否出现(或是元素出现的次数)。

b. 第一次遍历arr数组给新数组hash赋值,来建立映射关系

由于hash数组是从0~n+1的,而arr数组是打乱的随机的,因此循环中用hash[arr[i]] = true来赋值

c. 第二次遍历新数组,if(hash[i] == false),则说明是消失的数字。由于hash是n+1长度的,且一定存在消失的数字,因此不可能在循环中找不到

d. 在循环外return -1,因为消失的数字范围为0~n,这里表示万一出现错误导致在hash中找不到,就返回-1

B. 图文(实际上这个nums可以是打乱的,只要按照上述的方法赋值即可,左边是arr,右边是hash)

C. 代码

9. 面试题2

189. 轮转数组 - 力扣(Leetcode)jhttps://leetcode.cn/problems/rotate-array/(1)解法1:循环数组法

A. 思路:

a. 复制一个和原数组nums一模一样的新数组newNums来存储原数组的值,之后操作原数组nums的值并返回nums即可(为了防止值的覆盖)

b. 通过遍历,修改nums数组的值

循环中的逻辑部分:

当i + k <= num.length - 1的时候,可以直接移动,nums[i + k] = newNums[i]。这里其实也满足下列的取余操作,也就是i + k 没超过 nums.length等于其本身nums[i + k % nums.legnth] = newNums[i];

当i + k > num.lenth -1的时候,则需要进行取余操作,即nums[(i + k) % nums.length] = newNums[i]

总结:nums[(i + k) % nums.length] = newNums[i]

注意1:取模运算a % b的值是在[0,b)超过b的部分从0开始向右偏移

注意2:这里为什么不能用i + k  - nums.length,当k 超过了数组长度,则会溢出。当然这里可以对k % nums.legnth来保证k一定是小于7的来解决这个问题。

注意3:比较标准的做法其实是复制一个新数组,操作新数组,最后一个循环写回

B. 图文逻辑

 C. 代码

(2)解法2:反转数组法

A. 思路:

a. k可能会超过数组的长度,为了后续遍历数组时候不会数组越界访问,则对k取模nums.length ,保证k是小于nums.length的

b. 首先整个翻转nums(为了将后面的移动到前面去)

c . 然后分别翻转nums[0~k-1]和nums[k~nums.length -1]的子数组即可(反转后的子数组元素还原为原数组的顺序)

B. 图文逻辑

C. 代码 

(3)解法3:求最大公约数,拓展了解

三、泛型机制原理了解

1. day1泛型复习部分

(1)泛型类的定义

A. 语法:

class 类名称<类型参数,一般用大写字母代替>{

}

B. 举例

class Point<T>{

        T t;

}

(2)泛型类的使用:产生泛型类对象

Point<String> p1 = new Point<>();

Point<Integer> p2 = new Point<>();

(3)泛型方法的定义

A. 语法:

权限修饰符 <类型参数> 返回值类型 方法名称(参数列表)

B. 例子:

//第一个T是声明类型参数,第二个T是表示返回值类型也为T,第三个T表示形参t的类型也为T

public <T> T fun(T t){

        return t;

}

C. 注意:当泛型类和泛型方法共存的时候,泛型方法始终以自己的泛型类型参数为准

2. 泛型原则:泛型只存在于编译阶段,运行阶段没有泛型(类型擦除)

(1)泛型只能用在成员域,不能用在静态域。即为static修饰的内容不能使用泛型

A.原理:静态域和类相关,泛型是必须产生对象才能进行类型擦除,也就是依附于对象。因此矛盾不能使用

B. 示例

a. 静态变量

 b. 静态方法

(2)产生泛型对象的时候,具体的类型不能使用基本数据类型,要用基本数据类型的话使用其对应的包装类

A. 原理:基本数据类型不能直接被Object类接收,需要自动拆装箱。

B. 示例:

 

(3)不能直接创建和实例化泛型数组,要使用泛型数组,统一使用Object数组

A. 原理:new关键字是在运行阶段JVM开辟空间,但是泛型仅存在于编译阶段,也就是在执行的时候JVM不能识别E和T到底是什么类型的。

B. 示例:注意:不能new一个泛型数组

C. 正确定义和使用泛型数组:统一使用在泛型类中定义Object数组,然后在类中定义CURD(增删查改基本操作)的时候使用泛型类的类型参数作为CURD的形参或者返回值

a. 定义部分

b. 使用部分

注意:这里的add一定得赋值为Integer类型,因为values[size] = e,这一步操作是向上转型。为了保证get()中的强转操作一定不会失败,则这里一定要输入Integer类型。

 3. 类型擦除:泛型的信息只存在编译阶段,在进入到JVM运行之前的class文件,与泛型有关的所有信息都会被编译器擦除掉,专业术语称为“类型擦除”。即,泛型类和普通类在进入JVM之后都一样,在进入JVM后没有泛型这个东西

证明1:

 证明2:

结论: 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值