今天是秋招预备队准备算法第五天,今天打算写两道题
问题1:链表中倒数最后k个结点
描述:
输入一个长度为 n 的链表,设链表中的元素的值为 ai ,返回该链表中倒数第k个节点。如果该链表长度小于k,请返回一个长度为 0 的链表。
数据范围:0≤n≤10^5,0≤ai≤10^9,0≤k≤10^9
要求:空间复杂度 O(n),时间复杂度 O(n)
进阶:空间复杂度 O(1),时间复杂度 O(n)
解题方法:
1、双指针
1)设置左右两个指针,左指针指向链表头结点,右指针指向距头结点k-1个结点的结点
2)当右指针在移动k-1个结点内到达尾节点null时,则链表长度小于k,返回null
3)当链表长度不小于k时,同时一步一步的移动左右指针,直到右结点遍历到链表尾结点
4)返回左指针,其刚好是倒数第k个结点
为什么是k-1个,因为返回该链表中倒数第k个节点,即返回的结点个数为k,所以左右指针这段链表的结点数也为k,减去左指针结点,剩下的结点数k-1即为右结点移动的次数
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* public ListNode(int val) {
* this.val = val;
* }
* }
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pHead ListNode类
* @param k int整型
* @return ListNode类
*/
public ListNode FindKthToTail (ListNode pHead, int k) {
// write code here
//链表不为空并且k大于0
if(pHead == null || k == 0){
return null;
}
//设置左右指针
ListNode left = pHead;
ListNode right = pHead;
//移动右指针到距头节点k-1的结点处
for(int i = 0; i < k - 1; i++){
//链表长度小于k
if(right.next == null){
return null;
}
right = right.next;
}
//链表总长度不小于k,所以同时移动左右指针,直到右指针指向尾结点
while(right.next != null){
left = left.next;
right = right.next;
}
return left;
}
}
时间复杂度:遍历了两次链表,最坏情况下,两次遍历刚好遍历完整个链表n,所以为O(n)
空间复杂度:只占用了两个常量结点的空间,所以为O(1)
2、栈
1)利用栈将链表所有结点存储
2)因为在栈中链表节点刚好是反转的,所以只需要将栈顶的k个节点出栈,并组成链表即可
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* public ListNode(int val) {
* this.val = val;
* }
* }
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pHead ListNode类
* @param k int整型
* @return ListNode类
*/
public ListNode FindKthToTail (ListNode pHead, int k) {
//链表不为空或者k大于0
if(pHead == null || k == 0){
return null;
}
//栈
Stack<ListNode> stack = new Stack<>();
//将链表所有结点存入栈
while(pHead != null){
stack.push(pHead);
pHead = pHead.next;
}
//判断链表长度是否小于k
if(stack.size() < k){
return null;
}
//将尾结点出栈,便于连接链表
ListNode index = stack.pop();
//将剩余结点出栈,并组合链表
for(int i = 0; i < k - 1; i++){
ListNode cur = stack.pop();
cur.next = index;
index = cur;
}
return index;
}
}
时间复杂度:O(n)
空间复杂度:O(n)
问题2:删除链表的倒数第n个节点
描述:
给定一个链表,删除链表的倒数第 n 个节点并返回链表的头指针
例如:
给出的链表为: 1→2→3→4→5, n= 2.
删除了链表的倒数第 n 个节点之后,链表变为1→2→3→5.
数据范围: 链表长度 0≤n≤1000,链表中任意节点的值满足0≤val≤100
要求:空间复杂度 O(1),时间复杂度 O(n)
备注:题目保证 nn一定是有效的
解题方法:
1、栈
1)将链表所有结点存入栈,此时栈顶是链表尾结点
2)循环将结点弹出栈,组成链表
3)当遍历到倒数第n个结点时,将其删除
4)将栈中结点全部出栈,组成的链表即为删除倒数第 n 个节点的链表
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类
* @param n int整型
* @return ListNode类
*/
public ListNode removeNthFromEnd (ListNode head, int n) {
//链表不为空
if(head == null){
return null;
}
//栈
Stack<ListNode> stack = new Stack<>();
//入栈
while(head != null){
stack.push(head);
head = head.next;
}
//第一个结点,方便连接链表
ListNode cur = null;
//出栈
int m = stack.size();
for(int i = 1; i <= m; i++){
//当结点为倒数第n个结点时,将其删除
if(i == n){
stack.pop();
continue;
}
ListNode tmp = stack.pop();
tmp.next = cur;
cur = tmp;
}
//返回删除倒数第n个结点的新链表
return cur;
}
}
时间复杂度:O(n)
空间复杂度:O(n)
2、双指针
1)定义左右两个指针,其相距为n个结点,即右指针向前走n步
2)同步一步一步的移动左右指针,直到右指针来到链表的尾结点,即right.next = null
3)此时左指针刚好位于倒数第n个结点的前一个结点
4)将该结点删除,返回头节点即可
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类
* @param n int整型
* @return ListNode类
*/
public ListNode removeNthFromEnd (ListNode head, int n) {
if(head == null || n < 1){
return null;
}
//虚拟头节点
ListNode dummy = new ListNode(0);
dummy.next = head;
//左右指针
ListNode left = dummy;
ListNode right = dummy.next;
//将右指针移动n步
while(--n > 0){
if(right != null){
right = right.next;
}
else{
return null;
}
}
//移动双指针,直到右指针来到链表尾结点
while(right.next != null){
left = left.next;
right = right.next;
}
//删除倒数第n个结点
left.next = left.next.next;
return dummy.next;
}
}
时间复杂度:O(n),其中n为链表长度,最坏情况遍历整个链表1次
空间复杂度:O(1),常数级指针,无额外辅助空间使用
3、长度统计法
1)统计链表长度
2)遍历到倒数第n个结点的前一个结点
3)删除该结点
4)返回新链表
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类
* @param n int整型
* @return ListNode类
*/
public ListNode removeNthFromEnd (ListNode head, int n) {
if(head == null || n < 1){
return null;
}
//虚拟头节点
ListNode dummy = new ListNode(0);
dummy.next = head;
//获取链表长度
int length = listNodeLenth(head);
//获取倒数第n个结点的前一个结点
ListNode pre = dummy;
for(int i = 0; i < length - n; i++){
pre = pre.next;
}
//删除倒数第n个结点
pre.next = pre.next.next;
return dummy.next;
}
//计算链表长度函数
public int listNodeLenth(ListNode head){
int length = 0;
while(head != null){
length++;
head = head.next;
}
return length;
}
}
时间复杂度:O(n),其中n为链表长度,最坏情况遍历整个链表2次
空间复杂度:O(1),常数级指针,无额外辅助空间使用