链表&习题

反转链表

非递归——add重载

import java.util.*;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> list = new ArrayList<>();
        ListNode tmp = listNode;
        while(tmp!=null){
            list.add(0,tmp.val);
            tmp = tmp.next;
        }
        return list;
    }
}//O(N) O(N)

递归

ArrayList<Integer> list = new ArrayList<>();
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        if(listNode!=null){
            printListFromTailToHead(listNode.next);
            list.add(listNode.val);
        }
        return list;
    }

直接反转链表

public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> list = new ArrayList<>();
        while (listNode != null){
            list.add(listNode.val);
            listNode = listNode.next;
        }
        Collections.reverse(list);
        return list;
    }

使用栈

public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> list = new ArrayList<>();
        Stack<Integer> stack = new Stack<>();
        while (listNode != null){
            stack.push(listNode.val);
            listNode = listNode.next;
        }
        while (!stack.empty()){
            list.add(stack.pop());
        }
        return list;
    }

先反转链表,再遍历存入list中

public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> list = new ArrayList<>();
        ListNode pre = null;
        ListNode cur = listNode;
        ListNode temp = cur;
        while (cur != null){
            temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        while (pre != null){
            list.add(pre.val);
            pre = pre.next;
        }
        return list;
    }

合并两个排序的链表

递归解法

public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1 == null){
            return list2;
        }else if(list2 == null){
            return list1;
        }
        if(list1.val>list2.val){
            list2.next = Merge(list1,list2.next);
            return list2;
        }else {
            list1.next = Merge(list1.next,list2);
            return list1;
        }
    }

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

给定两个单链表A,B,返回第一个公共结点的指针。若没有,返回null

栈解法

使用双端队列Deque的实现类ArrayDeque:

  • addFirst(): 向队头插入元素,如果元素为空,则发生NPE
  • addLast(): 向队尾插入元素,如果为空,则发生NPE
  • offerFirst(): 向队头插入元素,如果插入成功返回true,否则返回false
  • offerLast(): 向队尾插入元素,如果插入成功返回true,否则返回false
  • removeFirst(): 返回并移除队头元素,如果该元素是null,则发生NoSuchElementException
  • removeLast(): 返回并移除队尾元素,如果该元素是null,则发生NoSuchElementException
  • pollFirst(): 返回并移除队头元素,如果队列无元素,则返回null
  • pollLast(): 返回并移除队尾元素,如果队列无元素,则返回null
  • getFirst(): 获取队头元素但不移除,如果队列无元素,则发生NoSuchElementException
  • getLast(): 获取队尾元素但不移除,如果队列无元素,则发生NoSuchElementException
  • peekFirst(): 获取队头元素但不移除,如果队列无元素,则返回null
  • peekLast(): 获取队尾元素但不移除,如果队列无元素,则返回null
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        Deque<ListNode> deque1 = new ArrayDeque<>(),deque2 = new ArrayDeque<>();
        while(pHead1 != null){
            deque1.add(pHead1);
            pHead1 = pHead1.next;
        }
        while (pHead2 != null){
            deque2.add(pHead2);
            pHead2 = pHead2.next;
        }
        ListNode ans = null;
        //peek是获取元素但不会移除类似于get,poll是获取元素并会移除类似于remove
        //不用get和remove的原因是如果该元素是*null*,则发生*NoSuchElementException*
        //而另外两个会返回*null*
        while(!deque1.isEmpty()&&!deque2.isEmpty()&&deque1.peekLast()==deque2.peekLast()){
            ans = deque1.pollLast();
            deque2.pollLast();
        }
        return ans;
    }

利用set特性

public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        Set<ListNode> set = new HashSet<>();
        while (pHead1 != null) {
            set.add(pHead1);
            pHead1 = pHead1.next;
        }
        while(pHead2 != null && !set.contains(pHead2)){
            pHead2 = pHead2.next;
        }
        return pHead2;
    }

双指针法

假设链表1是a+c;链表2是b+c,那么c就是其公共部分。

a+c+b=b+c+a,此时就找到了公共结点。

这种方法还挺美妙的。

public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode pa = pHead1,pb = pHead2;
        while (pb != pa){
            pa = pa == null?pHead2:pa.next;
            pb = pb == null?pHead1:pb.next;
        }
        return pa;
    }

我一开始没想清楚,以为有bug:如果两个链表没有交点呢?但其实结果还是正确的,没有交点的时候一定能够保证二者同时为null跳出循环,举个栗子:

//有公共结点时:a:[1,2,3,4,6,7];b:[5,6,7]
l1:1 2 3  4   6 7 null 5 6 
l2:5 6 7 null 1 2  3   4 6
    找到了公共结点6
//无公共结点时:a:[1,2,3,4,6,7];b:[8,9,5]
l1:1 2 3  4   6 7 null 8 9 5 null
l2:8 9 5 null 1 2  3   4 6 7 null
    二者同时为null跳出循环并返回结果null

链表中环的入口结点

给一个长度为n链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null。

Set方法

public ListNode EntryNodeOfLoop(ListNode pHead) {
        Set<ListNode> set = new HashSet<>();
        while(pHead!=null&&set.add(pHead)){
            pHead = pHead.next;
        }
        return pHead;
    }

快慢指针法

我们已经掌握了如何使用快慢指针判断是否有环:慢指针一次走一步,快指针一次走两步,如果相遇就说明存在环。

那么如何找到环的入口结点呢:我们经过推导可以得知:两个指针相遇后,再使用两个指针,一个从链表起始点开始,一个从相遇点开始,每次他们都走一步,直到再次相遇,那么这个相遇点就是环的入口。

public ListNode EntryNodeOfLoop(ListNode pHead) {
        ListNode slow = pHead;//快指针
        ListNode fast = pHead;//慢指针
        while (fast != null && fast.next != null) {
            //快慢指针,快指针每次走两步,慢指针每次走一步
            fast = fast.next.next;
            slow = slow.next;
            //先判断是否有环,
            if (slow == fast) {
                //确定有环之后才能找环的入口
                while (pHead != slow) {
                    //两指针,一个从头结点开始,
                    //一个从相遇点开始每次走一步,直到
                    //再次相遇为止
                    pHead = pHead.next;
                    slow = slow.next;
                }
                return slow;
            }
        }
        return null;
    }

链表中倒数最后K个结点

输入一个长度为 n 的链表,设链表中的元素的值为 ai ,返回该链表中倒数第k个节点。如果该链表长度小于k,请返回一个长度为 0 的链表。

用栈解决

public ListNode FindKthToTail (ListNode pHead, int k) {
        Stack<ListNode> stack = new Stack<>();
        while(pHead != null){
            stack.push(pHead);
            pHead = pHead.next;
        }
        ListNode ans = null;
        for(int i = k;;i--){
            if(stack.isEmpty()){
                return null;
            }else if(i==1){
                ans = stack.pop();
                return ans;
            }else {
                stack.pop();
            }
        }
    }

差值法

我们可以先对链表进行一次完整遍历,拿到总长度 cntcnt,最后由 cnt - kcntk 即是倒数第 kk 个节点距离 headhea**d 节点的距离。

public ListNode FindKthToTail (ListNode pHead, int k) {
        int count = 0;
        ListNode temp = pHead;
        while(temp!=null){
            count++;
            temp = temp.next;
        }
        count = count - k;
        if(count<0){
            return null;
        }else {
            while (count--!=0){
                pHead = pHead.next;
            }
            return pHead;
        }
    }

快慢指针法

第一个指针先移动k步,然后第二个指针再从头开始,这个时候这两个指针同时移动,当第一个指针到链表的末尾的时候,返回第二个指针即可。如果第一个指针还没走k步的时候链表就为空了,我们直接返回null即可。

public ListNode FindKthToTail (ListNode pHead, int k) {
        if (pHead == null){
            return null;
        }
        ListNode fast = pHead;
        ListNode slow = pHead;
        while (k-->0){
            if (fast == null){
                return null;
            }
            fast = fast.next;
        }
        while (fast != null){
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }

今日推歌

-----《年轮》

荒草丛生的青春
倒也过的安稳
代替你陪着我的
是年轮
数着一圈圈年轮
我认真
将心事都封存
密密麻麻是我的自尊

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星回昭以烂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值