一、函数的独占时间
给你一个进程数量,和运行日志。运行日志log的格式为:进程id:(start/end):运行时间
其中一个进程运行时可以被另一个优先级较高的进程抢占cpu。求每个进程独占cpu的时间。
输入:n = 2, logs = ["0:start:0","1:start:2","1:end:5","0:end:6"] 输出:[3,4]
思路:
利用栈的特点(栈反应了各个进程使用cpu的情况);
1.当遇到start的时候,可能是新的进程优先级更高,也可能是此时没有进程使用cpu。
1.1 如果是优先级更高的进程抢占了,那么栈一定不为空,更新之前进程的独立运行时间
1.2 如果栈为空,就说明之前没有进程使用cpu 那么直接入栈 更新startTime
2. 遇到end就结束了,更新当前进程的独立运行时间。
代码:
class Solution {
public int[] exclusiveTime(int n, List<String> logs) {
int[] time=new int[n];
Stack<Integer> stack=new Stack<>();
int startTime=-1;
for(String log:logs){
String[] split=log.split(":");
int id=Integer.parseInt(split[0]);
int curTime=Integer.parseInt(split[2]);
if(split[1].equals("start")){
//栈不为空 说明是优先级更高的进程抢占了,需要更新之前进程独立运行的时间
if(!stack.isEmpty())time[stack.peek()]+=curTime-startTime;
stack.push(id);//入栈
startTime=curTime;
}else{
//如果遇到了"end"符 就说明该进程结束了
time[id]+=curTime-startTime+1;
stack.pop();
startTime=curTime+1;
}
}
return time;
}
}
链表
二、删除链表的倒数第N个节点(双指针)二刷
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
思路:
定义slow、fast指针。如何让slow指针移动到要删除节点的前一个节点。eg:如上图所示,先让fast移动n下,也就是先把5,4移动了。然后同时移动1 2 3 。这时fast下一个必为空。拿这个当作判断条件,就可以把slow移动到删除结点的前一个结点
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummyHead=new ListNode();
dummyHead.next=head;//设置虚拟头节点
ListNode fast=dummyHead;
ListNode slow=dummyHead;
for(int i=0;i<n;i++)fast=fast.next;
while(fast.next!=null){
fast=fast.next;
slow=slow.next;
}
slow.next=slow.next.next;
return dummyHead.next;
}
}
三、扁平化多级双向链表(类似于插入节点)
你会得到一个双链表,其中包含的节点有一个下一个指针、一个前一个指针和一个额外的 子指针 。给定链表的头节点 head ,将链表 扁平化 ,以便所有节点都出现在单层双链表中。
题意:
跟插入节点类似,但插入节点一般是插入单个节点。这个要插入多个节点。
如果有子节点的话,下一个节点就是子节点,如果没有子节点,下一个节点就是next。
思路:
1.判断是否有子节点,如果没有,下一个节点就是next
2.如果有子节点,下一个节点就是如何规则的孩子节点们...,孩子节点们的下一个节点是该节点之前的下一个节点。
if(head.child!=null)Node temp=head.next;Node child=flatten(head.child);head.next=child;child.prev=head;head.child=null;
while(child.next!=null)child=child.next; child.next=temp; if(temp!=null)temp.prev=child;
代码:
class Solution {
public Node flatten(Node head) {
Node dummyHead =new Node();
dummyHead.next=head;
while(head!=null){
if(head.child!=null){
//存储head节点之前的下一个节点
Node temp=head.next;
//获取孩子节点
Node child=flatten(head.child);
head.next=child;
child.prev=head;
head.child=null;//孩子节点变成下一个节点 因此孩子节点消失
//将head节点之前的下一个节点放到孩子最后一个节点后面
while(child.next!=null)child=child.next;
child.next=temp;
if(temp!=null)temp.prev=child;
}else{
head=head.next;
}
}
return dummyHead.next;
}
}
四、反转链表II(一次遍历穿针引线/双指针?)
给你单链表的头指针 head
和两个整数 left
和 right
,其中 left <= right
。请你反转从位置 left
到位置 right
的链表节点,返回 反转后的链表 。
一次遍历穿针引线法:
第一次:1->3->2->4->5
第二次:1->4->3->2->5
思路:
pre、cur、next三个指针。pre指针一直不变,next=cur.next;所以三个节点中只有一个cur是在主动变化。大概思路就是:每次先将cur.next=next.next,然后将next.next改为pre.next。也就是将这个节点提前。pre.next=next;
工作流程:
eg:1-2-3-4-5 left:2 right:4
1.pre=1 cur=2 next=3
2.首先将3从2的next中去除(因为3要提前),变成2-4-5
3.然后pre和next相连,变成1-3
4.next和cur相连 1-3-2-4-5
代码:
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode dummyHead=new ListNode();
dummyHead.next=head;
ListNode pre=dummyHead;
for(int i=0;i<left-1;i++)pre=pre.next;//pre就是要第一个反转节点的前一个节点
ListNode cur=pre.next;
ListNode next=new ListNode();
for(int i=0;i<right-left;i++){
//每次更新next 随着cur的变化而变化
next=cur.next;
//先将next在cur.next中省去(因为next要提前)
cur.next=next.next;
//将next提前到至pre.next
pre.next=next;
//连接next和cur
next.next=cur;
}
return dummyHead.next;
}
}
双指针翻转?
思路:
1.首先找到翻转部分第一个节点的前一个结点pre;并且找到翻转部分的最后一个结点rightNode
2.然后截取中间部分,ListNode leftNode=pre.next;ListNode curr=rightNode.next;
要反转的部分是:以leftNode为起点到以rightNode为结尾的这一部分。然后把两边部分的结点断开:pre.next=null;rightNode.next=null;
3.然后放到reverseLinkedList(leftNode)进行翻转
4.翻转之后,rightNode翻到前面,pre.next=rightNode; 然后leftNode翻到后面,leftNode.next=curr;
5.返回 return dummyHead.next;
代码:
class Solution {
public ListNode reverseBetween(ListNode head, int left, int right) {
ListNode dummyHead=new ListNode();
dummyHead.next=head;
ListNode pre=dummyHead;
//第一步 找到pre
for(int i=0;i<left-1;i++)pre=pre.next;//pre就是要第一个反转节点的前一个节点
//第二步 找到反转范围内最右边的结点
ListNode rightNode=dummyHead;//右边的节点
for(int i=0;i<right;i++){
rightNode=rightNode.next;
}
//第三步切断出一个子链表(截取链表)
ListNode leftNode=pre.next;
ListNode curr=rightNode.next;
pre.next=null;
rightNode.next=null;
//反转 以leftNode为起点 以rightNode为结尾的这段范围中的
reverse(leftNode);
//拼接
pre.next=rightNode;
leftNode.next=curr;
return dummyHead.next;
}
public void reverse(ListNode head){
ListNode dummyHead =new ListNode();
dummyHead.next=head;
ListNode pre=dummyHead;
ListNode cur=head;
while(cur!=null){
ListNode temp=cur.next;
cur.next=pre;
pre=cur;
cur=temp;
}
}
}
五、旋转链表
给你一个链表的头节点 head
,旋转链表,将链表每个节点向右移动 k
个位置。
思路:
找到翻转后新的头节点,然后将新头节点之前的结点都放到新节点中最后一个结点之后。
首先要计算出链表的长度length,然后计算出新头节点前一个的位置index;index=length-(k%length)。如果从虚拟头结点开始数的话,这个位置应该是前一个结点的位置。
然后把前面那些结点连到后面就行了。
代码:
class Solution {
public ListNode rotateRight(ListNode head, int k) {
if(head==null||head.next==null||k==0)return head;
ListNode dummyHead=new ListNode();
dummyHead.next=head;
//计算链表的长度
ListNode cur=dummyHead;
int length=0;
while(cur!=null&&cur.next!=null){
cur=cur.next;
length++;
}
if(k%length==0)return head;
//找到旋转后的首结点 length-(k%length)+1;
ListNode pre=dummyHead;//旋转首节点的前一个结点
int index=length-(k%length);//示例1中4的位置
for(int i=0;i<index;i++){
pre=pre.next;
}
ListNode realHead=pre.next;
pre.next=null;
ListNode rightNode=realHead;
while(rightNode.next!=null){
rightNode=rightNode.next;
}
rightNode.next=dummyHead.next;
dummyHead.next=realHead;
return dummyHead.next;
}
}
六、两数相加II
思路:
和两数相加I类似,1.先把两个链表反转 2.然后相加 3.最后将结果反转返回
代码:
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode dummyHead1=new ListNode();
dummyHead1.next=reverseLinkedList(l1);
ListNode addHead=dummyHead1;
ListNode dummyHead2=new ListNode();
dummyHead2.next=reverseLinkedList(l2);
ListNode dummyHead3=new ListNode();
ListNode cur=dummyHead3;
int carry=0;
while(dummyHead1.next!=null||dummyHead2.next!=null){
int num1=dummyHead1.next==null?0:dummyHead1.next.val;
int num2=dummyHead2.next==null?0:dummyHead2.next.val;
System.out.println("num1:"+num1+" num2:"+num2);
cur.next=new ListNode((num1+num2+carry)%10);
cur=cur.next;
carry=(num1+num2+carry)/10;
if(dummyHead1.next!=null)dummyHead1=dummyHead1.next;
if(dummyHead2.next!=null)dummyHead2=dummyHead2.next;
}
if(carry!=0)cur.next=new ListNode(carry);
return reverseLinkedList(dummyHead3.next);
}
public ListNode reverseLinkedList(ListNode head){
ListNode dummyHead=new ListNode();
dummyHead.next=head;
ListNode slow=null;
ListNode fast=head;
while(fast!=null){
ListNode temp=fast.next;
fast.next=slow;
slow=fast;
fast=temp;
}
return slow;
}
}
七、合并k个升序链表
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
输入:lists = [[1,4,5],[1,3,4],[2,6]] 输出:[1,1,2,3,4,4,5,6] 解释:链表数组如下: [ 1->4->5, 1->3->4, 2->6 ] 将它们合并到一个有序链表中得到。 1->1->2->3->4->4->5->6
思路:
在长度为n的结点数组中,遍历n次,找到最小的结点,然后放到新链表的下一个。并且更新最小结点为最小结点的下一个。
外层循环while(true),在内层循环for(int i=0;i<n;i++)中如果没有找到一个最小的节点(说明已经寻找完毕了,这时就要break;)
代码:
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
int k=lists.length;
ListNode dummyHead=new ListNode();
ListNode cur=dummyHead;
while(true){
ListNode minNode=null;
int minPointer=-1;
for(int i=0;i<k;i++){
if(lists[i]==null)continue;
if(minNode==null||lists[i].val<minNode.val){
minNode=lists[i];
minPointer=i;
}
}
if(minPointer==-1)break;//结束了 链表里面没有东西了
cur.next=minNode;
cur=cur.next;
lists[minPointer]=lists[minPointer].next;
}
return dummyHead.next;
}
}