Python - 深夜数据结构与算法之 ArrayList

news2024/12/23 14:57:39

目录

一.引言

二.ArrayList 介绍

1.List

2.Linked List

3.Skip List

三.经典算法实战

1.Two-Sum [1]

2.Three-Sum [15]

3.Merge-Two-Sorted-List [21]

4.Remove-Duplicates-From-Sorted-Array [26]

5.Plus-One [66]

6.Rotate-Array [189]

7. Move-Zero [283]

四.总结


一.引言

本文介绍 Python 中的 ArrayList,主要表现形式为列表和链表,其在 Python 中表示为 []。

二.ArrayList 介绍

1.List

◆ 快速访问

每当我们申请创建数组时,计算机会为数组开辟一段连续的地址,每个地址可以通过内存管理器即上面的 Memory Controller 直接访问到,所以访问数组中任意一个元素的时间复杂度都是一样的,即 o(1),所以数组的访问速度非常快。

◆ 元素增删

增加元素最好的情况是从尾部 append,此时时间复杂度 o(1),最坏情况下插入到首位,需要挪动数组内全部元素,此时时间复杂度 o(n),平均下来需要挪动 n/2 个元素。

同理元素删除时也会有类似的时间复杂度,当把元素 Z 从数组中拿出去后,将 DEF 前移,此时将位置 6 置为 None,如果是 Java 会触发类似的垃圾回收机制,对空闲内存进行回收。

◆ 时间复杂度

ArrayList 具有 o(1) 的前后插入和查询的时间复杂度,但是插入和删除除首尾位置的元素,时间复杂度均为 o(n)。

2.Linked List

◆ 常见定义

# Definition for singly-linked list.
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

链表 Node 节点一般包含一个自身值 val 以及一个 next 指针指向其下一个元素,如果 next 为 None 代表没有后续的节点。

◆ 增加节点

链表增加节点只需将对应位置的 Node 的 next 指向 New Node,再将 New Node 的 next 指向原先的 next 节点,进行一次转换即可,操作的时间复杂度是 o(1)。

◆ 删除节点

与上面添加类似,只需将待删除的 Target Node 忽略,直接将前项的 Node 节点 next 指向后项的 Node 节点即可,其时间复杂度同样为 o(1)。

◆ 时间复杂度 

链表增删节点的时间复杂度均为 o(1),但是由于其连接的结构,要想访问链表中间位置的元素,需要一个 next 一个 next 访问,所以查询元素的复杂度为 o(n)。 

3.Skip List

◆ ​​​​​​​基本性质

◆ ​​​​​​​跳表形式

基于二分查找的思想,对于原始的元素有序的链表,我们可以添加一级索引,加速链表查询的步伐,假设我们查找 7,我们只需要在一级索引先查询,这时 1-4 本来需要 next.next 查询,而用第一级索引只需要 next 即可。随着索引层级的增多,我们查询的步伐也越来越大,其速度也越来越快,不过随之而来的是存储的增加,所以这也是一种空间换时间的经典算法思想。

◆ ​​​​​​​时间复杂度

由于是二分查找,随着每一层索引的增加,第 k 层对应的索引节点数就为 n / (2^k),层高 h 即为我们查找的时间复杂度,这里和前面介绍的 Trie 字典树也很像,我们只需要层高次查询即可查到对应的单词,即 o(logn)。

◆ ​​​​​​​空间复杂度

根据每 x 个节点抽取数据的不同,我们每层的节点数目也不相同,但是由于最上面的数列是收敛的,其计算下来空间复杂度仍然为 o(n),但是相比于前面的 List 和 Linked List,其实上面还是多了很多元素。

三.经典算法实战

上面我们介绍了 ArrayList 的几种表现形式以及其对应的一些特性,下面挑选一些 LeetCode 上比较经典的算法,加深 ArrayList 这个数据结构的印象和使用技巧。这里约定一下每个算法的表现形式,Two-Sum 代表算法名称,[x] 中括号里的 x 代表其对应 LeetCode 的第几题。

1.Two-Sum [1]

两数之和: ​​​​​​​https://leetcode.cn/problems/two-sum/description/

◆ ​​​​​​​题目分析

最最最基础的数组算法题,学计算机没做过 Two-Sum,就像考研英语没有背 Abandon 一样。针对本题,基本有两种思路。一种是直接两层 for 循环搞定,但是相对不够优雅;还有一种是借助 HashMap,通过空间辅助。

◆ ​​​​​​​双重循环

    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        length = len(nums)
        for i in range(length):
            for j in range(i + 1, length):
                if nums[i] + nums[j] == target:
                    return [i, j]
        return []

这里 i 遍历 range(length),j 遍历 range(i+1,length),其实就是枚举了数组中两两元素之间所有的可能,这里找到满足条件的 [i, j] 返回即可。时间复杂度 o(n^2),因为遍历了两轮数组,空间复杂度 o(1),没有使用额外的空间。

◆ ​​​​​​​HashMap 缓存

    def twoSum(self, nums, target):
        """
        :type nums: List[int]
        :type target: int
        :rtype: List[int]
        """
        m = {}
        
        for i in range(0, len(nums)):
            cur = target - nums[i]
            if (cur in m):
                return [m[cur], i]
            m[nums[i]] = i
        
        return []

m 用于缓存元素及其对应索引 index,我们只需要一层循环,由于 x + y = target,所以 y = target - x,针对每一个元素 x,我们只需要找 y 在不在 m 中,如果在的话返回二者索引即可,如果不在,则将当前元素缓存至 m 中。这里时间复杂度 o(n) 因为只 for 循环遍历了一次数组,空间复杂度也是 o(n),因为最差需要存储 n-1 个元素。

2.Three-Sum [15]

三数之和: https://leetcode.cn/problems/3sum/description/

◆ ​​​​​​​题目分析

两数之和、三数之和都是非常经典的算法题目,这里题目要求 i、j、k 三处的元素相加为 0。我们可以进行转换, nums[i] + nums[j] = -1 * nums[k],再令 -1 * nums[k] 为 Target,即可将题目转换为 Two-Sum,唯一区别是我们需要遍历多个 k 生成多个 Target,相当于做多次 Two-Sum。审题后我们大致得到下述信息:结果为不重复的三元组、a + b + c = 0 => a + b = -c。

◆ ​​​​​​​双指针

def threeSum(nums):
    result = []
    # 先将 nums 排序
    nums.sort()

    for k in range(len(nums) - 2):
        # 位置 k>0,则 k 后面的加起来必不可能为 0
        if nums[k] > 0:
            break

        # 排除两数相同的情况
        if k > 0 and nums[k] == nums[k - 1]:
            continue

        i, j = k + 1, len(nums) - 1

        while i < j:
            total = nums[i] + nums[j] + nums[k]

            if total < 0:
                i += 1
            elif total > 0:
                j -= 1
            else:
                # 排除左边、右边重复的情况
                result.append([nums[i], nums[j], nums[k]])
                while i < j and nums[i] == nums[i + 1]:
                    i += 1
                while i < j and nums[j] == nums[j - 1]:
                    j -= 1
                i += 1
                j -= 1
    return result

下面我们看下双指针的实现思路:

nums.sort() - 将数组排序后可以根据和的大小对左右指针进行移动

range(len(nums) - 2) - 因为是三元组,所以只需要遍历到倒数第 3 个元素即可

nums[k] > 0 - 因为数组有序,如果 k > 0,则 k 后面的三个元素怎么相加都可能为 0 

nums[k] == nums[k-1] -  target = nums[k] * 1,这里 continue 相当于实现了基于 target 的去重

while i < j - 左右指针进行夹逼法,如果满足 total = 0,则将 [i, j, k] 添加到 result 中

nums[i] == nums[i+1] - 这里相当于实现了基于三元组元素的去重

i += 1, j -= 1 - 完成一组解答后,继续遍历其他的 i,j 的情况 

Tips:

这里双指针的用法是 ArrayList 非常经典的算法,很多题目都可以借助双指针的思想,一定要好好掌握。 

3.Merge-Two-Sorted-List [21]

合并两个有序链表: https://leetcode.cn/problems/merge-tow-sorted-lists/

◆ ​​​​​​​题目分析

L1、L2 为给定的两个有序链表,需要将两个有序链表合成一个新的有序链表,思路比较好想但是实现起来需要一点技巧。这里需要构建一个新的 Node 节点,分别比较 L1 和 L2 的 head,将较小的节点连接到 Node,即 Node.next = Smaller,随后把 L1 或 L2 指向 next ,直到其中某个为空,再将剩下的节点补齐至最后即可。

◆ ​​​​​​​递归实现

    def mergeTwoLists(self, list1, list2):
        if not list1: return list2
        if not list2: return list1

        if list1.val <= list2.val:
            list1.next = self.mergeTwoLists(list1.next, list2)
            return list1
        else:
            list2.next = self.mergeTwoLists(list1, list2.next)
            return list2

这里开始的 not list1 和 not list2 用于检测链表为空的情况。后面判断大小也很好理解,博主当时有疑问的地方是 return list1,list2 这个部分,后来看了乐扣上网友的分析感觉很有道理:

递归的核心在于,我只关注我这一层要干什么,返回什么,至于我的下一层(规模减1),我不管,我就是甩手掌柜.

自己的理解:

结合这个图,针对最前面的 list,其只管返回自己即可,后面其余部分的链表合并可以看做是一个整体,和他没有关系,他甩手后面自己合并即可,但是他需要返回,因为这里不返回的话我们最终就没有最终输出结果了。

◆ ​​​​​​​从头再来

    def mergeTwoLists(self, l1, l2):
        cur = dummy = ListNode() # dummy 为影子节点

        while l1 and l2:
            # 判断数值并更新链表
            if l1.val > l2.val:
                cur.next = l2
                l2 = l2.next
            else:
                cur.next = l1
                l1 = l1.next

            cur = cur.next
        
        # 补齐剩下的有序链表   
        if l1:
            cur.next = l1
        if l2:
            cur.next = l2

        return dummy.next

这一版实现和我上面自己的思路分析比较一致,因为递归的思想有时候并不是很好想或者实现。这里 dummy 又称为影子节点,因为 cur 需要随着 while 循环逐步推进,这时候 cur 和 dummy 都指向同一个地址,我们后续想要获得头结点就只需要 dummy.next 即可,其主要是这个作用。whlie 和下面 if 的比较好理解,这一版是自己的思路实现,感觉更符合自己目前的水平哈哈。

​​​

4.Remove-Duplicates-From-Sorted-Array [26]

有序数组移除重复元素: https://leetcode.cn/problems/remove-duplicates-from-sorted-array

◆ ​​​​​​​题目分析

数组是非严格递增的,代表可能存在相同的元素。原地删除元素,代表我们不能引入额外的数据结构处理,例如引入 set 直接去重。最后是返回值,这里返回 nums 中非重复元素的数组长度而不是非重复元素。

◆ ​​​​​​​单指针

    def removeDuplicates(self, nums):

        # 长度为 1 直接返回
        if len(nums) == 1:
            return 1

        j = 0
        # 判重
        for i in range(len(nums)):
            if nums[i] != nums[j]:
                j += 1
                nums[j] = nums[i]

        return j + 1

引入单指针索引 j,遍历每个元素,如果其与上一个 j 对应的元素不相等,则 j += 1,放置新的非重复元素,最后由于数组索引是 0 开始,所以我们返回长度需要返回 j + 1。 

5.Plus-One [66]

数组元素加一: https://leetcode.cn/problems/plus-one/description/

◆ ​​​​​​​题目分析

这一题使用数组表示数字,将数字 +1 后再重新使用数组表示,数学逻辑的话很简单,就是一个加法,主要是其涉及到进位所以对于数组长度以及数组的索引元素会造成修改,所以我们主要进位导致的这两个问题即可。自己想了两个思路,一种是把数字 Int 直接提出来,加完再找新数组放进去;另外就是原地执行,通过一个辅助变量记录是否进位。

◆ ​​​​​​​直截了当

    def plusOne(self, digits):
        """
        :type digits: List[int]
        :rtype: List[int]
        """
        
        num = ""
        for i in digits:
            num += str(i)

        plus = str(int(num) + 1) # 数组转数字 Int

        re = []
        for i in plus:
            re.append(int(i))

        return re

 这个方法是我读题第一时间想到的,执行起来看着还能接受,内存也没有消耗过大。

◆ ​​​​​​​原地起飞

    def plusOne(self, digits):

        length = len(digits)
        is_add = False # 记录是否进位
        for i in range(length):
            # 因为加法从个位开始,所以数组从后向前遍历
            cur_index = length - (i + 1)
            cur_value = digits[cur_index]

            if is_add or i == 0:
                cur_value = cur_value + 1
            
            # 判断是否需要进位    
            if cur_value >= 10:
                is_add = True
                digits[cur_index] = 0
            else:
                is_add = False
                digits[cur_index] = cur_value

        # 判断是否需要在数组前追加一位
        if is_add:
            return [1] + digits
        else:
            return digits

这个就是正常的加法逻辑,其通过 is_add 记录是否需要进位,随后从后向前没遍历一位都执行相加和修改元素,最后再判断一次 is_add 判断相加是否导致数字进位从而为数组补一位。由于是原地执行,所以内存的占用会比上面的方法小一些。

6.Rotate-Array [189]

轮转数组: https://leetcode.cn/problems/rotate-array/description/

 ◆ ​​​​​​​题目分析

根据 k 的步伐,数组后面的元素依次挤过来,前面的被顶到后边。以 [1,2,3,4,5,6,7] k=3 为例,挪动后的数组为 [5,6,7,1,2,3,4],所以 k 次移动对应的是 -k 即倒数 k 的元素对应的数组挪到前面,再把剩下的数组挪到后面。

 ◆ ​​​​​​​前后颠倒 - 错误版

    def rotate(self, nums, k):
        """
        Do not return anything, modify nums in-place instead.
        """
        if k == 0:
            return nums
        else:
            length = len(nums)
            move = k % length

            return nums[-move:] + nums[:-move]

这一版按照我们前面的思路写的,k % length 是为了防止数组轮转多遍。但是执行后会报错:

都是 "Do not return anything, modify nums in-place instead" 惹的祸,还是读题不严谨,而且题目要求原地修改,不允许修改内存地址。 

◆ ​​​​​​​前后颠倒 - 正确版

    def rotate(self, nums, k):
        """
        Do not return anything, modify nums in-place instead.
        """
        if k == 0:
            return nums
        else:
            length = len(nums)
            move = k % length

            nums[:] = nums[-move:] + nums[:-move]

这里去掉了 return,增加了 nums[:],[:] 的写法可以保持内存地址 id 不变,而 '=' 号后面的写法负责改变 value,这样就实现了数组轮转。

旋转跳跃

class Solution:

    # 数组对称交换
    def swap(self, nums, left, right):
        while left < right:
            nums[left], nums[right] = nums[right], nums[left]
            left += 1
            right -= 1
        
    # 换就完事了
    def rotate(self, nums, k):
        """
        Do not return anything, modify nums in-place instead.
        """
        if k == 0:
            return nums
        else:
            length = len(nums)
            k %= length
            self.swap(nums, 0, length-k-1)
            self.swap(nums, length-k, length-1)
            self.swap(nums, 0, length-1)

除了上面精简的写法外,还有一个 swap 的方法,其是根据元素对称性实现的,为了更好的理解代码,下面给出简单的执行过程,还是以 [1,2,3,4,5,6,7] k=3 为例: 

第一次 swap: [4,3,2,1,7,6,5] 反转前面 n-k 个

第二次 swap: [1,2,3,4,7,6,5] 反转后 k 个

第三次 swap: [5,6,7,1,2,3,4] 整体前后反转

多次 swap 执行时间较长,但是由于 swap 是原地交换,所以内存消耗较小。

7. Move-Zero [283]

移动零: https://leetcode.cn/problems/move-zeroes/description/

题目分析 

将所有零移动到数组后面,且不改变原始顺序,还是前面的思路方法,我们引入一个单指针,记录存放非0元素的位置,遇到为零的元素 i,将该元素与 j 交换,相当于把 0 扔后面,把非 0 拿到前面,再将 j += 1 指定下一个存放非 0 位置的元素即可。

斗转星移

class Solution(object):

    def swap(self, i, j, nums):
        nums[i], nums[j] = nums[j], nums[i]


    def moveZeroes(self, nums):
        """
        Do not return anything, modify nums in-place instead.
        """
        j = 0
        for i in range(len(nums)):
            if nums[i] != 0:
                self.swap(i, j, nums)
                j += 1

前面是数组的 swap,这个是直接 i、j 索引的 swap,遍历数组,判断非零元素并交换即可。以 [0,1,0,3,12] 为例:

i=1, j=0 => [1,0,0,3,12]

i=3, j=1 => [1,3,0,0,12]

i=4, j=2 => [1,3,12,0,0]

四.总结

上面讲解了数组的基本概念以及 LeetCode 里一些基本的算法操作,这里介绍的题目以简单和中等为主,主要是用于了解一些常用的算法实现方式,这里数组和链表我们使用了:单指针、双指针、递归、元素 swap、数组 swap、空间换时间等最常用的技巧,后面可以多多练习加深印象,博主也会出其他数据结构的相关栏目。

本文都是工作之外的时间创作,大部分在晚上,所以其名为 <深夜数据结构与算法>,整理一篇文章大概需要两周左右,创作不易,如果对你有帮助,欢迎留下痕迹 ^_^

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1301938.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何公网访问内网的群晖NAS随时随地远程访问本地存储的学习资源

文章目录 前言本教程解决的问题是&#xff1a;按照本教程方法操作后&#xff0c;达到的效果是前排提醒&#xff1a; 1. 搭建群晖虚拟机1.1 下载黑群晖文件vmvare虚拟机安装包1.2 安装VMware虚拟机&#xff1a;1.3 解压黑群晖虚拟机文件1.4 虚拟机初始化1.5 没有搜索到黑群晖的解…

双向链表(数据结构与算法)

✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅✅ ✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨ &#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1f33f;&#x1…

基于Java的商城网站系统设计与实现(6000字论文范例)

基于Java的商城网站系统设计与实现 姓 名&#xff1a; 刘德华 学 号&#xff1a; 指导教师&#xff1a; 2023年4月 摘要 随着我国经济活力的不断提升和互联网的快速发展&#xff0c;信息的重要性正在…

SpringIOC之ConditionEvaluator

博主介绍:✌全网粉丝5W+,全栈开发工程师,从事多年软件开发,在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战,博主也曾写过优秀论文,查重率极低,在这方面有丰富的经验✌ 博主作品:《Java项目案例》主要基于SpringBoot+MyBatis/MyBatis-plus+…

如何使用cpolar+Inis在Ubuntu系统快速搭建本地博客网站公网可访问

文章目录 前言1. Inis博客网站搭建1.1. Inis博客网站下载和安装1.2 Inis博客网站测试1.3 cpolar的安装和注册 2. 本地网页发布2.1 Cpolar临时数据隧道2.2 Cpolar稳定隧道&#xff08;云端设置&#xff09;2.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 3. 公网访问测试总…

安装Nacos2.2.3集群

目录 一、传统方式安装 二、Docker安装 一、传统方式安装 1、配置jdk环境 vi /etc/profile JAVA_HOME/usr/local/java JRE_HOME/usr/local/java/jre CLASSPATH.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib PATH$JAVA_HOME/bin:$PATH export PATH JAVA_…

统筹高级前端,系统进阶精选案例实战,高效奠定前端基石

在当今的软件开发中&#xff0c;前端技术的重要性日益突出。为了应对不断变化的市场需求和用户期望&#xff0c;前端开发人员需要不断进阶&#xff0c;并掌握高级技术和系统化的实战经验。本文将介绍一些高级前端开发的精选案例&#xff0c;帮助开发者高效地奠定前端基石&#…

WRF--修改geo_em.d01.nc中的变量,保持其他信息不变

WRF–修改geo_em.d01.nc中的变量&#xff0c;保持其他信息不变 首先呢&#xff0c;找到编译WRF过程中自带的读取nc的一个fortran函数&#xff1a;read_wrf_nc.f90 可以使用Linux命令&#xff1a; find / -name read_wrf_nc.f90 找到之后&#xff0c;修改这个文件&#xff0c…

ke14--10章-1数据库JDBC介绍

注册数据库(两种方式),获取连接,通过Connection对象获取Statement对象,使用Statement执行SQL语句。操作ResultSet结果集 ,回收数据库资源. 需要语句: 1Class.forName("DriverName");2Connection conn DriverManager.getConnection(String url, String user, String…

通过异步序列化提高图表性能 Diagramming for WPF

通过异步序列化提高图表性能 2023 年 12 月 6 日 MindFusion.Diagramming for WPF 4.0.0 添加了异步加载和保存文件的功能&#xff0c;从而提高了响应能力。 MindFusion.Diagramming for WPF 提供了一个全面的工具集&#xff0c;用于创建各种图表&#xff0c;包括组织结构图、图…

华为数通---配置本地端口镜像示例(1:1)

镜像概念 定义 镜像是指将指定源的报文复制一份到目的端口。指定源被称为镜像源&#xff0c;目的端口被称为观察端口&#xff0c;复制的报文被称为镜像报文。 镜像可以在不影响设备对原始报文正常处理的情况下&#xff0c;将其复制一份&#xff0c;并通过观察端口发送给监控…

IntelliJ IDEA无公网远程连接Windows本地Mysql数据库提高开发效率

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f3a5;系列专栏&#xff1a;《C语言》 《数据结构》 《Linux》《Cpolar》 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;…

每日一练【查找总价格为目标值的两个商品】

一、题目描述 题目链接 购物车内的商品价格按照升序记录于数组 price。请在购物车中找到两个商品的价格总和刚好是 target。若存在多种情况&#xff0c;返回任一结果即可。 示例 1&#xff1a; 输入&#xff1a;price [3, 9, 12, 15], target 18 输出&#xff1a;[3,15] …

Pytorch深度强化学习1-6:详解时序差分强化学习(SARSA、Q-Learning算法)

目录 0 专栏介绍1 时序差分强化学习2 策略评估原理3 策略改进原理3.1 SARSA算法3.2 Q-Learning算法 0 专栏介绍 本专栏重点介绍强化学习技术的数学原理&#xff0c;并且采用Pytorch框架对常见的强化学习算法、案例进行实现&#xff0c;帮助读者理解并快速上手开发。同时&#…

摄像机镜头,家庭监控云台等安防监控系统镜头选型分析,低噪声,低振动,多通道

安防镜头步进驱动选用型号 GC6107 C6109 GC6209 GC6119 GC6129 GC6139 GC6208 GC6150 GC6151 GC6152 GC6125 GC6236采用5V的镜头驱动 。其中GC6107 C6109 GC6209 GC6119 GC6129 GC6139 GC6208关键特性两通道&#xff0c;256细分&#xff0c;低噪&#xff0c;内部和外部时钟…

【算法系列篇】递归、搜索和回溯(二)

文章目录 前言1. 两两交换链表中的节点1.1 题目要求1.2 做题思路1.3 代码实现 2. Pow(X,N)2.1 题目要求2.2 做题思路2.3 代码实现 3. 计算布尔二叉树的值3.1 题目要求3.2 做题思路3.3 代码实现 4. 求根节点到叶结点数字之和4.1 题目要求4.2 做题思路4.3 代码实现 前言 前面为大…

解锁数据安全秘诀:医药企业选择上海迅软DSE,防范泄密威胁!

随着数字化和信息化程度的提高&#xff0c;医药企业存储了大量的患者医疗记录、药品研发数据、临床试验数据以及财务信息。但由于医药行业的特殊性和敏感性&#xff0c;其数据的变现价值非常高&#xff0c;在各种利益的非法驱动下&#xff0c;医药行业早已成为数据泄露的重灾区…

想知道修改图片dpi会影响清晰度吗?点击这里找答案

很多人都对图片dpi分辨率有不少疑问&#xff0c;比如dpi对图片清晰的影响&#xff0c;还有哪些地方需要修改图片dpi&#xff1f;其实dpi是指每英寸墨点的数量。对同一张图像来说,一般使用300dpi比使用72dpi打印出来的效果要清晰很多 &#xff0c;一般只有在打印照片或者上传证件…

Windows系统Java开发环境安装

总结一下Java软件开发工程师常见的环境的安装&#xff0c;仅限Windows环境。 以下下载链接均来自官网&#xff0c;网络条件自己克服。 目录 1. JDKJDK Oracle 官网下载地址配置系统环境变量 2. Mavenapache maven 官网地址本地仓库和中央仓库配置配置系统环境变量 3. GitGit 官…

java学习part42反射

187-反射机制-反射的理解与使用举例_哔哩哔哩_bilibili