算法集锦(NO.2)
链表
DAY2:加油!!!
删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
进阶:你能尝试使用一趟扫描实现吗?
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
提示:
链表中结点的数目为 sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
方法一:
该题求解的是倒数第n个数字,那么可以想到用两个相同的链表(head),第一个链表遍历n次。然后和第二个链表再同步遍历,直到第一个链表为空时,第二个链表此时的节点正好为题目所要求的倒数第n个节点。因为此题为单向链表,所以没有前置指针,所以此引入第三个链表来存储第二个链表的前置节点。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode node = new ListNode(0,head);
//第一个链表用来进行n次遍历
ListNode tempOne = node.next;
//第二个链表用来定位删去节点
ListNode tempTwo = node.next;
//第三个链表用来定位被删节点的前置节点
ListNode tempThree = node;
while(n>0&&tempOne!=null){
tempOne = tempOne.next;
n--;
}
if(n>0){
return node.next;
}
//同步遍历,知道tempOne为空,此时tempTwo和tempThree分别为被删节点和被删节点的前置节点
while(tempOne!=null){
tempOne = tempOne.next;
tempTwo = tempTwo.next;
tempThree = tempThree.next;
}
//进行指定节点的删除
tempThree.next = tempTwo.next;
return node.next;
}
}
时间复杂度O(N),空间复杂度O(1)
方法二:
遍历链表,取出所有元素放置在一个list中,然后删除倒数第二个元素,创建新链表,用list中的值对新链表赋值。
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
LinkedList <Integer> list =new LinkedList<>();
ListNode node = new ListNode(0);
ListNode temp = node;
//遍历链表
while(head!=null){
list.add(head.val);
head = head.next;
}
//移除倒数第n个元素
list.remove(list.size()-n);
int num=0;
//重组新链表
while(num<list.size()){
ListNode tempNext = new ListNode(list.get(num));
temp.next = tempNext;
temp = temp.next;
num++;
}
return node.next;
}
}
时间复杂度O(N),空间复杂度O(1)
方法三:
使用栈去存储,也是遍历head,存入deque中,然后取出计数,当为n的时候跳过,使用尾插法
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
Deque<Integer>stack = new LinkedList<>();
ListNode node = new ListNode(0);
ListNode temp = node;
//把head的节点值存入stack中
while(head!=null){
stack.push(head.val);
head = head.next;
}
//用num来进行尾部计数
int num=1;
//遍历头插法
while(!stack.isEmpty()){
if(num==n){
num++;
stack.pop();
continue;
}
if(node.next==null){
ListNode tempNext = new ListNode(stack.pop());
node.next = tempNext;
temp = node.next;
}else{
ListNode tempNext = new ListNode(stack.pop());
node.next = tempNext;
tempNext.next = temp;
temp = node.next;
}
num++;
}
return node.next;
}
}
合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
示例 2:
输入:l1 = [], l2 = []
输出:[]
示例 3:
输入:l1 = [], l2 = [0]
输出:[0]
提示:
两个链表的节点数目范围是 [0, 50]
-100 <= Node.val <= 100
l1 和 l2 均按 非递减顺序 排列
方法一:
进行递归。
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1==null){
return l2;
}
if(l2==null){
return l1;
}
//因为从一次遍历可知,后续的递归值都为前一次的后续节点
if(l1.val<l2.val){
l1.next = mergeTwoLists(l1.next,l2);
return l1;
}else{
l2.next = mergeTwoLists(l1,l2.next);
return l2;
}
}
}
时间复杂度O(n),空间复杂度O(1)
方法二:
迭代
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode node =new ListNode(0);
ListNode temp = node;
while(l1!=null||l2!=null){
//numOne和numTwo进行l1和l2节点值的存储
int numOne = l1==null?Integer.MAX_VALUE:l1.val;
int numTwo = l2==null?Integer.MAX_VALUE:l2.val;
//进行值的嵌入
if(numOne<numTwo){
temp.next = new ListNode(numOne);
temp = temp.next;
l1 = l1.next;
}else{
temp.next = new ListNode(numTwo);
temp = temp.next;
l2 = l2.next;
}
}
return node.next;
}
}
时间复杂度O(N),空间复杂度O(1)
合并K个升序链表
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入: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
示例 2:
输入:lists = []
输出:[]
示例 3:
输入:lists = [[]]
输出:[]
提示:
k == lists.length
0 <= k <= 10^4
0 <= lists[i].length <= 500
-10^4 <= lists[i][j] <= 10^4
lists[i] 按 升序 排列
lists[i].length 的总和不超过 10^4
方法一:
使用一个空置节点对lists里面的值进行两两排序。
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
ListNode node = null;
for(int i=0;i<lists.length;i++){
//对其进行遍历,并且和node进行不断的两两合并
node=mergesortKLists(node,lists[i]);
}
return node;
}
public ListNode mergesortKLists(ListNode temp,ListNode lists){
//若两个中,其中一个为空,那么另一个就是解集
if(temp==null||lists==null){
return temp==null?lists:temp;
}
ListNode node = new ListNode(0);
ListNode tempNew = node;
while(temp!=null||lists!=null){
int numOne = temp==null?Integer.MAX_VALUE:temp.val;
int numTwo = lists==null?Integer.MAX_VALUE:lists.val;
if(numOne<numTwo){
tempNew.next=new ListNode(numOne);
temp = temp.next;
tempNew = tempNew.next;
}else{
tempNew.next=new ListNode(numTwo);
lists = lists.next;
tempNew = tempNew.next;
}
}
return node.next;
}
}
该方法的代码优化
class Solution {
public ListNode mergeKLists(ListNode[] lists) {
ListNode node = new ListNode(0);
ListNode temp = node;
for(int i=0;i<lists.length;i++){
mergesortKLists(temp,lists[i]);
}
return node.next;
}
//优化了方法,直接引入辅助节点进行交换来优化程序
public void mergesortKLists(ListNode temp,ListNode lists){
ListNode temp1=temp;
while(lists!=null){
//因为temp1的初始节点为0,所以当他的next为空,即他还未与任何一个不为空的lists进行合并
if(temp1.next==null){
temp1.next=lists;
break;
//当他下一节点的值大于lists的值的时候,进行节点互换
}else if(temp1.next.val>lists.val){
//引入辅助节点tempNext来保存lists的首值
ListNode tempNext = lists;
//这里需要先将lists的next节点进行保存,防止接下来在指针转换时发生子链表丢失的情况
lists = lists.next;
//将所要拼接的节点指向temp1的下一个比他值大的节点
tempNext.next = temp1.next;
//tmp1后移
temp1.next =tempNext;
}else{
temp1 = temp1.next;
}
}
}
}
方法二:
分治算法,分而治之。因为方法一是一个辅助链表一个一个的和链表数组中的链表进行合并,该方法时间比较复杂。所以引入分治算法,使得链表数组自身两两合并,减少不必要的时间。
class Solution {
//merge方法进行对列表的分治拆分
public ListNode mergeKLists(ListNode[] lists) {
return merge(lists,0,lists.length-1);
}
//将lists拆分至1个listnode和1个listnode类似于二叉树的形式,从叶子节点的两个进行合并,然后层层解套,得出最终解
public ListNode merge(ListNode[]lists,int pre,int end){
if(pre==end){
return lists[pre];
}
if(pre>end){
return null;
}
//找到中间平均值,进行两两拆分
int mid = (pre+end)>>1;
//当pre和end为相邻时,此时我们需要将后面的mid+1,因为>>1是向下取整,此时mid=pre。
return mergesortKLists(merge(lists,pre,mid),merge(lists,mid+1,end));
}
//此为前几题相同的两个链表合并,此时需要注意的是,要保持原lists列表不遭到破坏,所以得新取listnode进行替代
public ListNode mergesortKLists(ListNode pre,ListNode end){
if(pre==null||end==null){
return pre==null?end:pre;
}
ListNode node=new ListNode(0);
ListNode temp1 = node;
ListNode tempPre =pre;
ListNode tempEnd = end;
while(tempPre!=null&&tempEnd!=null){
if(tempPre.val<tempEnd.val){
temp1.next=tempPre;
tempPre=tempPre.next;
temp1=temp1.next;
}else{
temp1.next=tempEnd;
tempEnd=tempEnd.next;
temp1 = temp1.next;
}
}
temp1.next = (tempPre!=null?tempPre:tempEnd);
return node.next;
}
}
方法三:
对优先队列进行改造,通过对其头节点的值进行排序,来得出最优解。每次取完头节点后,再将其节点向后移动并重新插入优先队列中。
class Solution {
//简历一个staus的class,里面存在节点值,和当前节点。
class Status implements Comparable<Status> {
int val;
ListNode ptr;
//其构造函数赋值
Status(int val, ListNode ptr) {
this.val = val;
this.ptr = ptr;
}
//该status内嵌有compareTo方法,用来对输入的节点进行排序。此方法主要用于对整个链表的值进行排序。
public int compareTo(Status status2) {
return this.val - status2.val;
}
}
PriorityQueue<Status> queue = new PriorityQueue<Status>();
public ListNode mergeKLists(ListNode[] lists) {
for (ListNode node: lists) {
if (node != null) {
//将链表数组中的链表注入队列里面
queue.offer(new Status(node.val, node));
}
}
//创建新链表进行整合
ListNode head = new ListNode(0);
ListNode tail = head;
while (!queue.isEmpty()) {
//此时queue内第一个链表,必定是首节点值最小的链表,将其取出,并且使用tail链表连接值
Status f = queue.poll();![在这里插入图片描述](https://img-blog.csdnimg.cn/20210702190847299.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQyMzY4NTc3,size_16,color_FFFFFF,t_70)
tail.next = f.ptr;
tail = tail.next;
if (f.ptr.next != null) {
queue.offer(new Status(f.ptr.next.val, f.ptr.next));
}
}
return head.next;
}
}
排序链表
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
进阶:
你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?
示例 1:
输入:head = [4,2,1,3]
输出:[1,2,3,4]
示例 2:
输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]
示例 3:
输入:head = []
输出:[]
提示:
链表中节点的数目在范围 [0, 5 * 104] 内
-105 <= Node.val <= 105
因为要求是O(nlogn),所以考虑到的排序算法有堆排序,快速排序,分治排序。此处选择的是分治排序。通过快慢指针来进行链表的取中计算。
class Solution {
public ListNode sortList(ListNode head) {
return merge(head,null);
}
//此为分治算法的细分过程,将链表细分至节点,再从节点进行合并
public ListNode merge(ListNode pre,ListNode end){
//如果pre为空,则说明此越界不为节点。
if(pre==null){
return null;
}
//如果pre的下一个节点为end,则说明此链表区段为节点状态,已经无法继续细分
if(pre.next==end){
pre.next=null;
return pre;
}
//快慢指针设立
ListNode slow = pre;
ListNode fast = pre;
//快慢指针后移
while(fast!=end){
slow = slow.next;
fast = fast.next;
if(fast!=end){
fast=fast.next;
}
}
ListNode mid=slow;
//递归思想,细分再合并
ListNode leftList=merge(pre,mid);
ListNode rightList=merge(mid,end);
return mergeCount(leftList, rightList);
}
public ListNode mergeCount(ListNode pre,ListNode end){
//两个列表按照节点的大小顺序进行合并
ListNode node = new ListNode(0);
ListNode temp = node;
while(pre!=null||end!=null){
int numPre = pre==null?Integer.MAX_VALUE:pre.val;
int numEnd = end==null?Integer.MAX_VALUE:end.val;
if(numPre<numEnd){
temp.next=new ListNode(numPre);
temp = temp.next;
pre=pre.next;
}else{
temp.next=new ListNode(numEnd);
temp=temp.next;
end=end.next;
}
}
return node.next;
}
}