02.02题:单链表中返回倒数第k个节点
方法1:常规法
思路:常规想法:先得到链表长度,再遍历得到倒数第k个数:
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int kthToLast(ListNode head, int k) {
//***2个循环中的链表名不一样,因为若相同,则前一个遍历完之后head就指在最后一个节点了,在进行第二个循环遍历时就指向最后的NULL空指针了,所以需要把头节点另保存起来
int len = 0;
ListNode first = head;
while(first != null){//得到链表长度
len++;
first = first.next;
}
int lindex = len - k + 1;//正序数该节点的位置
int i=1;
while(i < lindex){
head = head.next;
i++;
}
return head.val;
}
}
- 效率:0ms,36.7MB
方法2:双指针法
思路:为了使得A指针走到最后一步(NULL)而B指针正好走到倒数第k个节点,则令A指针先走到第k+1个节点
int index= 1;
ListNode first = head;
ListNode second = head;
while(first != null){
if(index < k+1){
first = first.next;
}
else{
first = first.next;
second = second.next;
}
index++;
}
- 效率:0ms,36MB
方法3:使用栈解决
这题要求的是返回后面的k个节点,我们只要把原链表的结点全部压栈,然后再把栈中最上面的k个节点出栈,出栈的结点重新串成一个新的链表即可,原理也比较简单,直接看下代码。
public int kthToLast(ListNode head, int k) {
Stack<ListNode> stack = new Stack<>();
//链表节点压栈
while (head != null) {
stack.push(head);
head = head.next;
}
//在出栈串成新的链表
ListNode firstNode = stack.pop();
while (--k > 0) {
ListNode temp = stack.pop();
temp.next = firstNode;
firstNode = temp;
}
return firstNode.val;
}
*看一下运行结果,这个效率就差很多了
方法4:递归求解
之前讲过链表的逆序打印410,剑指 Offer-从尾到头打印链表,其中有这样一段代码
public void reversePrint(ListNode head) {
if (head == null)
return;
reversePrint(head.next);
System.out.println(head.val);
}
这段代码其实很简单,我们要理解他就要弄懂递归的原理,递归分为两个过程,递和归,看一下下面的图就知道了,先往下传递,当遇到终止条件的时候开始往回走。
前面也刚讲过递归的原理426,什么是递归,通过这篇文章,让你彻底搞懂递归,这题如果使用递归的话,我们先来看一下递归的模板
public ListNode getKthFromEnd(ListNode head, int k) {
//终止条件
if (head == null)
return head;
//递归调用
ListNode node = getKthFromEnd(head.next, k);
//逻辑处理
……
}
终止条件很明显就是当节点head为空的时候,就没法递归了,这里主要看的是逻辑处理部分,当递归往下传递到最底端的时候,就会触底反弹往回走,在往回走的过程中记录下走过的节点,当达到k的时候,说明到达的那个节点就是倒数第k个节点,直接返回即可,如果没有达到k,就返回空,搞懂了上面的过程,代码就很容易写了
//全局变量,记录递归往回走的时候访问的结点数量
int size;
public int kthToLast(ListNode head, int k) {
//边界条件判断
if (head == null)
return 0;
int val = kthToLast(head.next, k);
++size;
//从后面数结点数小于k,返回空
if (size < k) {
return 0;
} else if (size == k) {
//从后面数访问结点等于k,直接返回传递的结点k即可
return head.val;
} else {
//从后面数访问的结点大于k,说明我们已经找到了,
//直接返回node即可
return val;
}
}

02.03:实现一种算法,删除单向链表中间的某个节点(即不是第一个或最后一个节点),假定你只能访问该节点。
题目警告:只能访问这个需要被删除的节点
所以先把该节点node的下一个节点的值赋给该节点,再把下一个节点删掉

public void deleteNode(ListNode node) {
//题目警告:只能访问这个需要被删除的节点
//思路:先把该节点的下一个节点的值赋给该节点,在把下一个值删掉
node.val = node.next.val;
node.next = node.next.next;
}
21题:合并两个升序链表
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode L = new ListNode(-1);
ListNode temp = L;
while(l1 != null && l2 != null){//当两个链表都非空时
if(l1.val <= l2.val){
temp.next = l1;
l1 = l1.next;
}
else{
temp.next = l2;
l2 = l2.next;
}
temp = temp.next;
}
temp.next = l1 == null ? l2 : l1;
return L.next;
}
}
很妙的思想
a. 头节点的定义:首先定义一个链表L,其头节点值为-1,同时再定义一个节点temp也指向L,故后续通过temp的移动将两个链表合并起来,最后返回整个链表时用L.next(因为L在头节点,但L值为-1).
b.链接的过程中不要忘记temp自身的移动
c. 最后当某一个链表非空时的三元判断法的写法需要注意下。

876题:找非空链表的中间节点
如果是奇数,找中间节点
如果是偶数,找第二个中间节点
class Solution {
public ListNode middleNode(ListNode head) {
//找中间节点(偶数则返回第二个中间节点)
ListNode fast = head;
ListNode slow = head;
while(fast.next != null && fast.next.next != null){//这个循环找到的是(奇数中间节点和偶数的第一个中间节点)
fast = fast.next.next;
slow = slow.next;
}
if(fast.next != null){//如果是偶数的话,需要再往后找一个
fast = fast.next;
slow = slow.next;
}
return slow;
}
}

234题:判断回文链表
思路分析
如果是对数组操作,很显然采用双指针的思想,从前端和后端分别开始遍历
但是单链表存在两个问题:长度不容易知,只能从头开始遍历
因此考虑:将链表的后半段逆序,然后利用快慢指针对后半段和前半段分别进行遍历比较
解决问题:
1.找到中间节点(利用快慢指针,慢指针走1步,快指针走2步,当快指针走到最后一步时,慢指针刚好都中间节点)(奇数中间节点,偶数第一个中间节点)
2. 对后半段进行逆序(这是一个重点!!!)
3. 前后端比较
4. 对链表复原
时间O(n),空间O(1)
class Solution {
public boolean isPalindrome(ListNode head){
if(head == null || head.next == null){
return true;
}
//找到前半段的尾节点并反转后半段链表
ListNode firstHalfEnd = endOfFirstHalf(head);
ListNode secondHalfStart = reverselist(firstHalfEnd.next);
//判断是否回文,前后段比较,head指针(前半段)与backrev指针(后半段)移动比较
boolean flag = true;
ListNode p1 = head;
ListNode p2 = secondHalfStart;
while(flag && p2 != null){
if(p1.val != p2.val){
flag = false;
//break;
}
p1 = p1.next;
p2 = p2.next;
}
//还原链表并返回结果
firstHalfEnd.next = reverselist(secondHalfStart);
return flag;
}
//链表逆序函数
private ListNode reverselist(ListNode head){
ListNode cur = head;
ListNode rev = null;//因为链表的最后一个节点都是空节点
while(cur != null){
ListNode tempnext = cur.next;//用来保存当前节点的下一个节点
cur.next = rev;
rev = cur;
cur = tempnext;
}
return rev;
}
//找到前半部分的尾节点
private ListNode endOfFirstHalf(ListNode head){
ListNode fast = head;
ListNode slow = head;
while(fast.next != null && fast.next.next != null){
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
}

18. 删除某个节点
题目给定头指针和被删节点的值
分析做题过程中需要注意的点:
1.如果被删的是头节点
2.while循环在碰到被删节点时结束,而不是按常规的从头至尾遍历
3.当最后一个节点时,cur不为空,那cur.next不报错,相当于cur.next=null,因为链表的最后有一个空节点?
class Solution{
public ListNode deleteNode(ListNode head,int val){
ListNode pre = head;
ListNode cur = pre.next;
if(head == null) return null;
if(head.val == val) return head.next;//被删节点是头节点
while(cur != null && cur.val != val){//这个循环的好处:当碰到被删节点时就不再继续遍历了节省时间且好操作,而之前的方法是一直遍历完。
pre = cur;
cur = cur.next;
}
if(cur != null){//这个地方需要注意一下,即使cur是最后一个节点,其cur.next即为null节点也不报错,应该是因为链表的最后一个节点就是null节点,刚好把这个null赋给pre.next=null;
pre.next = cur.next;
}
return head;
}
}
61.旋转链表
题目描述
给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。
示例 1:
输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
示例 2:
输入: 0->1->2->NULL, k = 4
输出: 2->0->1->NULL
思路分析:
1.先找到旧的头和尾以及链表长度
2.再把链表整合成闭环链表
3.根据数字规律找到新的头和尾
当k<n时,新尾:n-k;新头:n-k-1
当k>=n时,新尾:n-k%n;新头:n-k%n+1
可以发现,头总在尾的后一个节点(以上按索引从1开始)
4.在尾巴处断链
class Solution {
public ListNode rotateRight(ListNode head, int k) {
if(head == null) return null;
if(head.next == null) return head;
//找到旧的头和尾
ListNode last = head;
int n = 1;//链表长度
while(last.next != null){
n++;
last = last.next;
}
last.next = head;//闭环
ListNode tailnew = head;
for(int i = 1;i < n-k%n;i++){//找到新尾节点
tailnew = tailnew.next;
}
ListNode headnew = tailnew.next;//找到新头节点
tailnew.next = null;
return headnew;
}
}
52.两个链表的首个公共节点
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
思路分析:浪漫的双指针相遇
我变成了你,走你走过的路;你变成了我,走我走过的路;然后我们就相遇了。<你的名字>
A长度为L1+C;B长度为L2+C;当A和B两者分别走L1+L2+C,两者顶相遇
当PA指针走到A的最后一个节点时,再重新定位到headB;
当PB指针走到B的最后一个节点时,再重新定位到headA;
这样,当PA=PB时,定为第一个公共点。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode PA = headA;
ListNode PB = headB;
while(PA != PB){
PA = PA==null? headB : PA.next;
PB = PB==null? headA : PB.next;
}
return PA;
}
}

&spm=1001.2101.3001.5002&articleId=110671158&d=1&t=3&u=8c88bea39af74e058805dc4858a353f3)
1万+

被折叠的 条评论
为什么被折叠?



