【Android春招每日一练】(十九) LeetCode Hot 5题+操作系统基础
作者:互联网
文章目录
概览
LeetCode Hot:两数之和、两数相加、无重复字符的最长子串、寻找两个正序数组的中位数、最长回文子串
操作系统基础:操作系统的四个特性、操作系统的主要功能、进程的状态与转换、进程与线程的区别、进程通信、进程同步
LeetCode Hot
2.1 两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
//建立一个哈希表存储
class Solution {
public int[] twoSum(int[] nums, int target) {
HashMap<Integer,Integer> map = new HashMap<>();
for(int i = 0;i < nums.length;i++){
if(map.containsKey(target - nums[i]))
return new int[]{map.get(target-nums[i]),i};
map.put(nums[i],i);
}
return new int[0];
}
}
2.2 两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807
//注意进位即可
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode head = null,tail = null;
int carry = 0;
while(l1 != null || l2 != null){
int n1 = l1 != null ? l1.val : 0;
int n2 = l2 != null ? l2.val : 0;
int sum = n1 + n2 + carry;
carry = sum / 10;
if(head == null){
tail = head = new ListNode(sum % 10);
}else{
tail.next = new ListNode(sum % 10);
tail = tail.next;
}
if(l1 != null)
l1 = l1.next;
if(l2 != null)
l2 = l2.next;
}
if(carry > 0)
tail.next = new ListNode(carry);
return head;
}
}
2.3 无重复字符的最长子串
与剑指offer47相同
2.4 寻找两个正序数组的中位数
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
//二分思想,hard题目
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int length1 = nums1.length, length2 = nums2.length;
int totalLength = length1 + length2;
if (totalLength % 2 == 1) {
int midIndex = totalLength / 2;
double median = getKthElement(nums1, nums2, midIndex + 1);
return median;
} else {
int midIndex1 = totalLength / 2 - 1, midIndex2 = totalLength / 2;
double median = (getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0;
return median;
}
}
public int getKthElement(int[] nums1, int[] nums2, int k) {
/* 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
* 这里的 "/" 表示整除
* nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个
* nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个
* 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个
* 这样 pivot 本身最大也只能是第 k-1 小的元素
* 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组
* 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组
* 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数
*/
int length1 = nums1.length, length2 = nums2.length;
int index1 = 0, index2 = 0;
int kthElement = 0;
while (true) {
// 边界情况
if (index1 == length1) {
return nums2[index2 + k - 1];
}
if (index2 == length2) {
return nums1[index1 + k - 1];
}
if (k == 1) {
return Math.min(nums1[index1], nums2[index2]);
}
// 正常情况
int half = k / 2;
int newIndex1 = Math.min(index1 + half, length1) - 1;
int newIndex2 = Math.min(index2 + half, length2) - 1;
int pivot1 = nums1[newIndex1], pivot2 = nums2[newIndex2];
if (pivot1 <= pivot2) {
k -= (newIndex1 - index1 + 1);
index1 = newIndex1 + 1;
} else {
k -= (newIndex2 - index2 + 1);
index2 = newIndex2 + 1;
}
}
}
}
2.5 最长回文子串
给你一个字符串 s
,找到 s
中最长的回文子串。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案
//中心扩散法
class Solution {
public String longestPalindrome(String s) {
if (s == null || s.length() < 1){
return "";
}
// 初始化最大回文子串的起点和终点
int start = 0;
int end = 0;
// 遍历每个位置,当做中心位
for (int i = 0; i < s.length(); i++) {
// 分别拿到奇数偶数的回文子串长度
int len_odd = expandCenter(s,i,i);
int len_even = expandCenter(s,i,i + 1);
// 对比最大的长度
int len = Math.max(len_odd,len_even);
// 计算对应最大回文子串的起点和终点
if (len > end - start){
start = i - (len - 1)/2;
end = i + len/2;
}
}
// 注意:这里的end+1是因为 java自带的左闭右开的原因
return s.substring(start,end + 1);
}
private int expandCenter(String s,int left,int right){
// left = right 的时候,此时回文中心是一个字符,回文串的长度是奇数
// right = left + 1 的时候,此时回文中心是一个空隙,回文串的长度是偶数
// 跳出循环的时候恰好满足 s.charAt(left) != s.charAt(right)
while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)){
left--;
right++;
}
// 回文串的长度是right-left+1-2 = right - left - 1
return right - left - 1;
}
}
操作系统基础
操作系统的四个特性
并发: 同一段时间内多个程序执行(注意区别并行和并发,前者是同一时刻的多个事件,后者是同一时间段内的多个事件)
共享: 系统中的资源可以被内存中多个并发执行的进线程共同使用
虚拟: 通过时分复用(如分时系统)以及空分复用(如虚拟内存)技术实现把一个物理实体虚拟为多个
异步: 系统中的进程是以走走停停的方式执行的,且以一种不可预知的速度推进
操作系统的主要功能
处理机管理: 处理机分配都是以进程为单位,所以处理机管理也被看做是进程管理。包括进程控制,进程同步,进程通信和进程调度
存储器管理(或者内存管理): 内存分配,内存保护,地址映射,内存扩充
设备管理: 管理所有外围设备,包括完成用户的IO请求;为用户进程分配IO设备;提高IO设备利用率;提高IO速度;方便IO的使用
文件管理: 管理用户文件和系统文件,方便使用同时保证安全性。包括:磁盘存储
空间管理,目录管理,文件读写管理以及文件共享和保护
提供用户接口: 程序接口(如API)和用户接口(如GUI)
进程的状态与转换
运行状态:进程正在处理机上运行。在单处理机环境下,每一时刻最多只有一个进程处于运行状态。
就绪状态:进程已处于准备运行的状态,即进程获得了除处理机之外的一切所需资源,一旦得到处理机即可运行。
阻塞状态,又称等待状态:进程正在等待某一事件而暂停运行,如等待某资源为可用(不包括处理机)或等待输入/输出完成。即使处理机空闲,该进程也不能运行。
注意区别就绪状态和等待状态: 就绪状态是指进程仅缺少处理机,只要获得处理机资源就立即执行;而等待状态是指进程需要其他资源(除了处理机)或等待某一事件。
就绪状态 -> 运行状态: 处于就绪状态的进程被调度后,获得处理机资源(分派处理机时间片),于是进程由就绪状态转换为运行状态。
运行状态 -> 就绪状态: 处于运行状态的进程在时间片用完后,不得不让出处理机,从而进程由运行状态转换为就绪状态。此外,在可剥夺的操作系统中,当有更高优先级的进程就 、 绪时,调度程度将正执行的进程转换为就绪状态,让更高优先级的进程执行。
运行状态 -> 阻塞状态: 当进程请求某一资源(如外设)的使用和分配或等待某一事件的发生(如I/O操作的完成)时,它就从运行状态转换为阻塞状态。进程以系统调用的形式请求操作系统提供服务,这是一种特殊的、由运行用户态程序调用操作系统内核过程的形式。
阻塞状态 -> 就绪状态: 当进程等待的事件到来时,如I/O操作结束或中断结束时,中断处理程序必须把相应进程的状态由阻塞状态转换为就绪状态。
进程与线程的区别
进程: 进程是进程实体的运行过程,是系统进行资源分配和调度的一个独立单位 (具有动态、并发、独立、异步的特性,以及就绪、执行、阻塞3种状态);引入进程是为了使多个程序可以并发的执行,以提高系统的资源利用率和吞吐量。
线程: 是比进程更小的可独立运行的基本单位,可以看做是轻量级的进程(具有轻型实体,独立调度分派单位,可并发执行,共享进程资源等属性);引入目的是为了减少程序在并发执行过程中的开销,使OS的并发效率更高。
两者的对比:
-
调度方面:在引入线程的OS中,线程是独立的调度和分派单位,而进程作为资源的拥有单位(相当于把未引入线程的传统OS中的进程的两个属性分开了)。由于线程不拥有资源,因此可以显著的提高并发度以及减少切换开销。
-
并发性:引入了线程的OS中,进程间可以并发,而且一个进程内部的多个线程之间也是可以并发的,这就使OS具有更好的并发性,有效的提高了系统资源利用率和吞吐量。
-
拥有资源:无论OS是否支持线程,进程都是基本的资源拥有单位,线程只拥有很少的基本的资源,但是线程可以访问所隶属的进程的资源(进程的代码段,数据段和所拥有的系统资源如fd)
-
系统开销:创建或者撤销进程的时候,系统要为之创建或回收PCB,系统资源等,切换时也需要保存和恢复CPU环境。而线程的切换只需要保存和恢复少量的寄存器,不涉及存储器管理方面的工作,所以开销较小。此外,统一进程中的多个线程由于共享地址空间,所以通信同步等都比较方便。
进程通信
进程通信是指进程之间的信息交换。PV操作是低级通信方式,髙级通信方式是指以较高的效率传输大量数据的通信方式。高级通信方法主要有以下三个类。
共享存储
在通信的进程之间存在一块可直接访问的共享空间,通过对这片共享空间进行写/读操作实现进程之间的信息交换。在对共享空间进行写/读操作时,需要使用同步互斥工具(如 P操作、V操作),对共享空间的写/读进行控制。共享存储又分为两种:
低级方式的共享是基于数据结构的共享;高级方式则是基于存储区的共享。操作系统只负责为通信进程提供可共享使用的存储空间和同步互斥工具,而数据交换则由用户自己安排读/写指令完成。
需要注意的是,用户进程空间一般都是独立的,要想让两个用户进程共享空间必须通过特殊的系统调用实现,而进程内的线程是自然共享进程空间的。
消息传递
在消息传递系统中,进程间的数据交换是以格式化的消息(Message)为单位的。若通信的进程之间不存在可直接访问的共享空间,则必须利用操作系统提供的消息传递方法实现进程通信。进程通过系统提供的发送消息和接收消息两个原语进行数据交换。
-
直接通信方式:发送进程直接把消息发送给接收进程,并将它挂在接收进程的消息缓冲队列上,接收进程从消息缓冲队列中取得消息。
-
间接通信方式:发送进程把消息发送到某个中间实体中,接收进程从中间实体中取得消息。这种中间实体一般称为信箱,这种通信方式又称为信箱通信方式。该通信方式广泛应用于计算机网络中,相应的通信系统称为电子邮件系统。
管道通信
管道通信是消息传递的一种特殊方式。所谓“管道”,是指用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件,又名pipe文件。向管道(共享文件)提供输入的发送进程(即写进程),以字符流形式将大量的数据送入(写)管道;而接收管道输出的接收进程(即读进程),则从管道中接收(读)数据。为了协调双方的通信,管道机制必须提供以下三方面的协调能力:互斥、同步和确定对 方的存在。
进程同步
多进程虽然提高了系统资源利用率和吞吐量,但是由于进程的异步性可能造成系统的混乱。进程同步的任务就是对多个相关进程在执行顺序上进行协调,使并发执行的多个进程之间可以有效的共享资源和相互合作,保证程序执行的可再现性
同步机制需要遵循的原则:
-
空闲让进:当没有进程处于临界区的时候,应该许可其他进程进入临界区的申请
-
忙则等待:当前如果有进程处于临界区,如果有其他进程申请进入,则必须等待,保证对临界区的互斥访问
-
有限等待:对要求访问临界资源的进程,需要在有限时间内进入临界区,防止出现死等
-
让权等待:当进程无法进入临界区的时候,需要释放处理机,边陷入忙等
经典的进程同步问题:生产者-消费者问题;哲学家进餐问题;读者-写者问题
同步的解决方案:管程,信号量
总结
1.算法开始刷力扣热题100题,每天5题,预计20天刷完,其中有些题回合剑指offer上重复;
2.开始复习计算机基础知识,近两天是操作系统的知识,因为上课学得不错加上刚上过没多久,只列举知识要点(可能会有遗漏),重在理解记忆。
标签:状态,int,Hot,线程,春招,进程,Android,nums1,nums2 来源: https://blog.csdn.net/z18223345669/article/details/122791429