剑指offer刷题:链表篇

链表题

  1. 设置两个指针。
  2. 用栈或队列或hash表来存储相关信息。

一、从尾到头打印链表(1版面试题5)

1.题目

输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

2.思路

要注意算法是否会改变原链表的结构
后进先出首先就想到用栈结构。

3.代码

import java.util.*;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
       Stack<Integer> temp = new Stack<>();
        ArrayList<Integer> arr = new ArrayList<>();
        while(listNode != null){            //把结点依次放入栈中
            temp.push(listNode.val);
            listNode = listNode.next;
        }
        while(!(temp.isEmpty())){           //依次弹栈存到目标链表中
            arr.add(temp.pop());
        }
        return arr;
    }
}

二、链表中环的入口结点(2版面试题23)

1.题目

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

2.思路

遍历链表,当遇到之前遍历过的结点时(说明有环),返回这个结点。
在这里插入图片描述
判断是否遇到遍历过的结点的方法:
将之前遍历的结点以此放入到ArrayList中,然后利用contains函数即可。

3.代码

import java.util.*;
/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public ListNode EntryNodeOfLoop(ListNode pHead)
    {
         ListNode start = null;
        ArrayList<ListNode> arr = new ArrayList<>();
        while(pHead != null){
            if(arr.contains(pHead)){
                return pHead;
            }
            arr.add(pHead);
            pHead = pHead.next;
        }
        return start;
    }   
}

三、删除链表中的重复结点

1.题目

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

2.算法思路

设置两个结点,一个是上一个不重复的结点pre。一个是寻找下一个不重复的结点last。同时,再创建一个头节点head(好判断pHead是否重复)

3.代码

public class Solution {
    public ListNode deleteDuplication(ListNode pHead)
    {
        if(pHead==null || pHead.next==null){return pHead;}
       ListNode head = new ListNode(Integer.MIN_VALUE);
       head.next = pHead;
       ListNode pre = head;         //pre是上一个不重复的结点
       ListNode last = pHead;       //last是用来寻找下一个不重复的结点
        while(last != null){
            if(last.next != null && last.val == last.next.val){    //发现last和last.next重复
                while(last.next != null && last.val == last.next.val){   //该循环是找到当前一直重复的最后一个结点
                    last = last.next;
                }
                pre.next = last.next;
                last = last.next;
            }else{
                pre = pre.next;
                last = last.next;
            }
    }
        return head.next;       //注意,pHead可能已经改变(有可能出现11112的情况)
    }
}

四、链表中倒数第k个结点

1.题目

输入一个链表,输出该链表中倒数第k个结点。

2.思路

(1)利用栈,需要额外的空间
先将链表放入栈中,再弹出第k个结点即可。
(2)设置两个指针p,q
p从开始就走,q比p慢k步。当p走到链表尾时,这是q就处于倒数第k个结点。

3.代码

(1)利用栈,需要额外的空间

import java.util.Stack;

public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        if(head==null || k<=0){return null;}	//特殊情况
        Stack<ListNode> sta = new Stack<>();
        ListNode temp = head;
        while(temp!=null){		//链表放入栈中
            sta.push(temp);
            temp = temp.next;
        }
        for(int i=1;i<k;i++){		//先弹出k-1个结点
            sta.pop();
        }
        if(sta.size() == 0){			//若不存在倒数第k个
            return null;
        }
        return sta.pop();		//弹出倒数第k个
    }
}

(2)设置两个指针p,q

public class Solution {
public ListNode FindKthToTail(ListNode head,int k) { 
        ListNode p, q;
        p = q = head;               //两个指针都从头开始走
        int i = 0;
        for (; p != null; i++) {        //p开始就走,一直走到链表结尾
        if (i >= k){                    //当p走了k步时,q才开始走
            q = q.next;
        }
        p = p.next;                     //p开始就走
        }
        return i < k ? null : q;        //当p走到链表尾时,这是q刚好在倒数第k(q比p慢k步)
        }
}

五、反转链表

1.题目

输入一个链表,反转链表后,输出新链表的表头。

2.思路

链表的题目先画图。
在这里插入图片描述

3.代码

public class Solution {
    public ListNode ReverseList(ListNode head) {       
        ListNode pre = null;    //pre用于记录当前结点的前一个结点      
        ListNode next = null;    //next用于记录当前结点的下一个结点    
        while(head != null){
            next = head.next;    //用next记录当前结点的下一个结点地址           
            head.next = pre;     //让被当前结点与链表断开并指向前一个结点pre。          
            pre = head;         //pre指针指向当前结点          
            head = next;        //head指向next(保存着原链表中head的下一个结点地址)
        }
        return pre;//当循环结束时,pre所指的就是反转链表的头结点
    }
}

六、两个链表的第一个公共结点

1.题目

输入两个链表,找出它们的第一个公共结点。

2.思路

**公共结点的特征:**有两个前结点,有一个后结点。
在这里插入图片描述
(1)利用公共结点特征+栈
用两个栈分别来装这两个链表。然后这两个栈一起弹栈,若相等,则说明使用的同一个结点。第一次遇到不同,说明,上一个结点就是第一个公共结点。

3.代码

(1)利用公共结点特征+栈

import java.util.Stack;

public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        Stack<ListNode> sta1 = new Stack<>();
        Stack<ListNode> sta2 = new Stack<>();
        ListNode result = null;
        while(pHead1!=null){        //用栈sta1装链表1
            sta1.push(pHead1);
            pHead1 = pHead1.next;
        }
        while(pHead2!=null){        //用栈sta2装链表2
            sta2.push(pHead2);
            pHead2 = pHead2.next;
        }
        while(!(sta1.isEmpty() || sta2.isEmpty())){  //两个栈同时弹栈
            if(sta1.peek().val == sta2.peek().val){  //若相等,说明是公共结点,并记录这个公共结点
                result = sta1.peek();
                sta1.pop();
                sta2.pop();
            }else{          //第一次发现不相等,直接结束循环,此时result保存的就是第一个公共结点
                break;
            }
        }
        return result;
    }
}

七、合并两个排序的链表

1.题目

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

2.思路

要说明的是,ListNode类是指链表的节点,每个节点都存放着本节点的值和下一节点的地址(对象)
先考虑特殊的情况,如果 l1 或者 l2 一开始就是 null ,那么没有任何操作需要合并,所以我们只需要返回非空链表。否则,我们要判断 l1 和 l2 哪一个的头元素更小,然后递归地决定下一个添加到结果里的值(即相当于从同时从两个链表中找出下一个最小值放在下一节点)。如果两个链表都是空的,那么过程终止。
部分步骤如下,也就是说merge(L1,L2)这个函数相当于找出L1与L2中的最小值
在这里插入图片描述

3.代码


public class Solution {
    public ListNode Merge(ListNode l1,ListNode l2){
        if(l1 == null){
            return l2;
        }
        if(l2 == null){
            return l1;
        }
        if(l1.val < l2.val){										//若L1的头节点值 < L2的头节点值
            l1.next = Merge(l1.next,l2);		//相当于把L2插入到L1中
            return l1;
        } else{													//若L1的头节点值 >= L2的头节点值
            l2.next = Merge(l1,l2.next);			//相当于把L1插入到L2中
            return l2;
        }
    }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值