关于链表的常见算法题(一)

关于链表的常见算法题(二)

关于链表的常见算法题(一)

链表一直是面试最喜欢考的数据结构,灵活而又多变,代码往往也不是特别长。

从尾到头打印链表

输入一个链表的头节点,从尾到头反过来打印出每个节点的值

例如链表为1->2->3,打印出3 2 1

有三种思路可以参考:

  1. 栈:栈天然是先进后出的,在遍历链表时,把值按顺序放入栈中,最后出栈就是逆序了。
  2. 既然想到了栈,那么递归天然是一个栈结构,一样的,访问到一个节点,先递归输出它的后面节点,再输出自己,这样链表就反过来了
  3. 头插法:使用头插法可以得到一个逆序的链表。
    • 1->2->3,加入一个头节点head,只用于操作中转,不存储数据
    • 先把head->1,然后继续从2->3中取
    • 变为head->2->1,最后变为head->3->2->1,从head.next开始输出就可以了
public class _06 {
    static class ListNode {
        int value;

        public ListNode(int value) {
            this.value = value;
        }

        ListNode next;
    }

    public static void main(String[] args) {
        ListNode listNode1 = new ListNode(1);
        ListNode listNode2 = new ListNode(2);
        ListNode listNode3 = new ListNode(3);
        listNode1.next = listNode2;
        listNode2.next = listNode3;
        _06 test = new _06();
//        List<Integer> list = test.printListFromQueue(listNode1);
//        List<Integer> list = test.printListFromDG(listNode1);
        List<Integer> list = test.printListFromTC(listNode1);
        for (Integer i : list) {
            System.out.println(i);
        }
    }

    /**
     * 栈
     */
    public List<Integer> printListFromQueue(ListNode listNode) {
        Stack<Integer> stack = new Stack<>();
        while (listNode != null) {
            stack.add(listNode.value);
            listNode = listNode.next;
        }
        List<Integer> list = new ArrayList<>();
        while (!stack.isEmpty()) {
            list.add(stack.pop());
        }
        return list;
    }

    /**
     * 递归
     */
    public List<Integer> printListFromDG(ListNode listNode) {
        List<Integer> list = new ArrayList<>();
        if (listNode != null) {
            list.addAll(printListFromDG(listNode.next));
            list.add(listNode.value);
        }
        return list;
    }

    /**
     * 头插法
     */
    public List<Integer> printListFromTC(ListNode listNode) {
        //头插法构建逆序链表

        //新建一个头部节点,只用于中转,不存储值
        ListNode head = new ListNode(-1);
        //链表反转
        while (listNode != null) {
            ListNode temp = listNode.next;
            listNode.next = head.next;
            head.next = listNode;
            listNode = temp;
        }
        List<Integer> list = new ArrayList<>();
        //真正的值是从第一个节点开始
        head = head.next;
        while (head != null) {
            list.add(head.value);
            head = head.next;
        }
        return list;
    }
}
复制代码

删除链表的节点

在O(1)的时间内删除链表节点

给定单身链表的头指针和一个节点指针,定义一个函数在O(1)内删除这个节点。

解题思路
  • 如果这个节点不是尾节点,那么可以直接把下一个节点的值赋值给这个节点,再把这个节点指向下下个节点,时间复杂度为O(1)

    如,1->2->3->4,要删除的是2,那么先把2的下一个节点值3,赋值给2的节点,变为1-3-3-4,然后把第二个节点的位置,指向下下个节点4,这样,就变为了1-3-4,2这个节点这删除了。

  • 如果是尾指针,那么找到前一个节点,把前一个节点指向null,时间复杂度为O(N)

    如果进行N次操作,这个算法的平均时间复杂度为O(1)

    /**
     *  删除指定节点
     */
    public ListNode deleteNode(ListNode head, ListNode tobeDelete) {
        if (head == null || tobeDelete == null) {
            return null;
        }
        if (tobeDelete.next != null) {
            //要删除的不是尾节点
            ListNode next = tobeDelete.next;
            tobeDelete.value = next.value;
            tobeDelete.next = next.next;
        } else {
            if (head == tobeDelete) {
                head = null;
            } else {
                ListNode curNode = head;
                while (curNode.next != tobeDelete) {
                    curNode = curNode.next;
                }
                curNode.next = null;
            }
        }
        return head;
    }
复制代码
删除链表中重复的节点

在一排序的链表中,如何删除重复的节点

1->2->2->3->3->3->4

1->4

解题思路
  • 头节点一样有可能重复
  • 遍历整个链表,如果当前节点与next的下一个节点相同,则都可以删除,然后把当前节点的前一个节点与下下个节点相连,我们必须保证前一个节点是无一个无重复的节点相连,这一想就是递归~~
    /**
     * 删除重复的节点
     */
    public ListNode deleteDuplication(ListNode pHead) {
        if (pHead == null || pHead.next == null) {
            return pHead;
        }
        ListNode next = pHead.next;
        //当头节点有重复时,一直找到没有重复的为止
        if (pHead.value == next.value) {
            while (next != null && pHead.value == next.value) {
                next = next.next;
            }
            return deleteDuplication(next);
        } else {
            pHead.next = deleteDuplication(pHead.next);
            return pHead;
        }
    }
复制代码

链表中倒数第K个节点

输入一个单向链表,输出这个链表中倒数第k个节点

1->2->3->4->5->6

倒数第三个点为4

解题思路

这是一个单向链表,如果我们使用遍历,很明显不方便从尾开始遍历的,这时候我们可以想到的方法是双指针法。

  • 定义两个指针p1和p2,p2先不动,p1走k-1步,这时p1指向3,p2指向1
  • 现在p1和p2同时向后遍历,当p1遍历到6,它的next为null时,p2所指向的值就是我们所要的值
    public ListNode FindKthToTail(ListNode head, int k) {
        if (head == null) {
            return null;
        }
        ListNode p1 = head;
        while (p1 != null && k > 0) {
            p1 = p1.next;
            k--;
        }
        if (k > 0) {
            return null;
        }
        ListNode p2 = head;
        while (p1 != null) {
            p1 = p1.next;
            p2 = p2.next;
        }
        return p2;
    }
复制代码

反转链表

解题思路
  • 头插法(迭代)

    • 前面其实已经用到了,使用一个头head,只做中转,而不存储数据。
  • 递归

    ​ 分解成最简单的两个节点,就next.next = head,这样就能把1->2反转成2->1。1的next是2,它的next指向1,就已经完成了2->1

    public ListNode reverseList(ListNode head){
        if (head==null||head.next==null){
            return head;
        }
        ListNode next = head.next;
        head.next = null;
        ListNode newHead = reverseList(next);
        next.next = head;
        return newHead;
    }

    public ListNode reverseList2(ListNode head){
        if (head==null||head.next==null){
            return head;
        }
        ListNode newList = new ListNode(-1);

        while (head!=null){
            ListNode next = head.next;
            head.next = newList.next;
            newList.next = head;
            head = next;
        }
        return newList.next;
    }
复制代码

合并两个排序的链表

输入两个递增排序的链表,合并这两个链表并使新的链表中的节点仍然是递增的。

1-3-5-7

2-4-6-8

1-2-3-4-5-6-7-8

解题思路

首先我们要比较两个链表头,发现1比2小,那么新链表的头就是1,然后继续比较,这时还是比较链表头,我们想到了,这就是个递归啊,把头继续比较,依次加进新的链表中,所以本题可以用递归来解,当然也能使用迭代。

    /**
     * 递归
     */
    public ListNode merge(ListNode node1, ListNode node2) {
        if (node1 == null) {
            return node2;
        }
        if (node2 == null) {
            return node1;
        }
        if (node1.value < node2.value) {
            node1.next = merge(node1.next, node2);
            return node1;
        } else {
            node2.next = merge(node1, node2.next);
            return node2;
        }
    }

    /**
     * 迭代
     */
    public ListNode merge2(ListNode node1, ListNode node2) {
        ListNode head = new ListNode(-1);
        ListNode cur = head;
        while (node1 != null && node2 != null) {
            if (node1.value <= node2.value) {
                cur.next = node1;
                node1 = node1.next;
            } else {
                cur.next = node2;
                node2 = node2.next;
            }
            cur = cur.next;
        }
        if (node1 != null) {
            cur.next = node1;
        }
        if (node2 != null) {
            cur.next = node2;
        }
        return head.next;
    }
复制代码

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

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

1-2-3-6-7

4-5-6-7

解题思路
  • 暴力破解法

    在第一个链表上顺序遍历到一个节点,就到第二个链表上遍历每一个节点,如果有一个节点与这个值相等,就找到了。但是时间复杂度为O(mn)

  • 我们发现,公共部分一定是在尾部,如果我们从后面开始找会更快,最后一个相同点就是我们要找的点,不过这是单向链表,所以我们想到了栈的结构——后进先出

  • 求差法

    其实我们用栈,只是为了想同时能到达两个链表的尾节点。当两链表长度不同时,我们从头开始遍历,到后面的时间就不一致。我们可以先遍历两个链表,得到它们的长度,然后,链表长的先走先走多的几个节点,接着同时在两个链表遍历,找到的第一个相同点就是我们要找的第一个公共点

    /**
     *  用栈取
     */
    public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        Stack<ListNode> stack1 = new Stack<>();
        Stack<ListNode> stack2 = new Stack<>();
        
        //分别放入栈中
        while (pHead1 != null) {
            stack1.push(pHead1);
            pHead1 = pHead1.next;
        }
        while (pHead2 != null) {
            stack2.push(pHead2);
            pHead2 = pHead2.next;
        }

        ListNode result = null;
        //从栈顶取值,取到不相等时,上一个就是第一个公共节点
        while (!stack1.isEmpty() && !stack2.isEmpty() && stack1.peek() == stack2.peek()) {
            stack1.pop();
            result = stack2.pop();
        }
        return result;
    }

    /**
     * 差值法
     */
    public ListNode findFirstCommonNode2(ListNode pHead1, ListNode pHead2) {
        if (pHead1 == null || pHead2 == null) {
            return null;
        }
        int count1 = 1;
        int count2 = 1;
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        //获取两个链表的长度
        while (p1.next != null) {
            p1 = p1.next;
            count1++;
        }
        while (p2.next != null) {
            p2 = p2.next;
            count2++;
        }

        //哪个长就先走差值步
        if (count1 > count2) {
            int dif = count1 - count2;
            while (dif != 0) {
                pHead1 = pHead1.next;
                dif--;
            }
        } else {
            int dif = count2 - count1;
            while (dif != 0) {
                pHead2 = pHead2.next;
                dif--;
            }
        }
        
        //同时走,当相等时得到公共节点
        while (pHead1 != null && pHead2 != null) {
            if (pHead1 == pHead2) {
                return pHead1;
            }
            pHead1 = pHead1.next;
            pHead2 = pHead2.next;
        }
        return null;
    }
复制代码

By《剑指offer》


我的CSDN

下面是我的公众号,欢迎大家关注我

转载于:https://juejin.im/post/5d067f4ee51d4577596486fb

猜你喜欢

转载自blog.csdn.net/weixin_34186931/article/details/93165115