【数据结构与算法】链表经典题

两数相加

LeetCode:两数相加

题目描述:

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。

如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。

您可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例:

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807

思想:

链表遍历,关键的地方在于补0,因为两个链表长短可能不一致,不补0的话很麻烦

代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode p = l1,q=l2;
        int twoSum=0,nextAdd=0;
        while(p!=null||q!=null){ 
            if(p.next==null&&q.next!=null)
                p.next = new ListNode(0);
            if(q.next==null&&p.next!=null)
                q.next = new ListNode(0);
            twoSum = p.val + q.val;
            p.val = (twoSum + nextAdd)%10;
            nextAdd = (twoSum + nextAdd)/10;
            if(p.next==null&&q.next==null&&nextAdd>0){
                p.next = new ListNode(nextAdd);
                break;
            }
            p=p.next;
            q=q.next;
        }
        return l1;
    }
}

2021-3-30更新

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode L = l1;
        int carry = 0;
        while(l1!=null){
            int temp = l1.val + carry + (l2==null?0:l2.val);
            carry = temp / 10;
            l1.val = temp % 10;
            if(l1.next==null){
                if(carry>0) l1.next = new ListNode(0);
                else{
                    l1.next = l2==null?null:l2.next;
                    break;
                }
            }
            l1 = l1.next;
            if(l2!=null) l2 = l2.next;
        }
        return L;
    }
}

两数相加2

LeetCode:两数相加 II

题目描述:

给定两个非空链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储单个数字。将这两数相加会返回一个新的链表。

你可以假设除了数字 0 之外,这两个数字都不会以零开头。

进阶:

如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。

示例:

输入: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
输出: 7 -> 8 -> 0 -> 7

思想:

与上一题不一样,链表顺序是反过来的,使用栈进行处理。

代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        Stack<Integer> stackL1 = buildStack(l1);
        Stack<Integer> stackL2 = buildStack(l2);
        ListNode L=null,p=null;
        int x,y,item,carry=0;
        while(!(stackL1.empty()&&stackL2.empty()) || carry>0){
            x = stackL1.empty()?0:stackL1.pop();
            y = stackL2.empty()?0:stackL2.pop();
            item = (x+y+carry)%10;
            carry = (x + y + carry)/10;
            L = new ListNode(item);
            L.next = p;
            p = L;
        }
        return L;
    }
    private Stack<Integer> buildStack(ListNode L){
        Stack<Integer> stack = new Stack<>();
        while(L!=null){
            stack.push(L.val);
            L=L.next;
        }
        return stack;
    }
}

回文链表

LeetCode:回文链表

题目描述:

判断一个链表是否为回文链表。

示例:

输入: 1->2->2->1
输出: true

思想:

先用快慢指针取得中间位置的指针,将后半段链表进行翻转,再遍历比较前半段和后半段是否一致。时间复杂度O(3/2n),空间复杂度O(1)

优化:在快慢指针的循环中,同时翻转前半段链表,随后从中间位置往两头遍历,时间复杂度O(n)

代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        if(head==null||head.next==null)
            return true;
        ListNode mid=head,end=head;
        while(end!=null&&end.next!=null){
            mid = mid.next;
            end = end.next.next;
        }
        ListNode p=mid,q=mid.next,t;
        while(q!=null){
            t=q.next;
            q.next=p;
            p=q;q=t;
        }
        q=head;
        while(q!=mid){
            if(q.val!=p.val)
                return false;
            q=q.next;
            p=p.next;
        }
        return true;
    }
}

优化版,但是代码着实有点复杂,没有上一种方法直白

class Solution {
    public boolean isPalindrome(ListNode head) {
        if(head==null||head.next==null)
            return true;
        ListNode mid=head,end=head.next,q=head.next,t;
        while(end!=null&&end.next!=null){
            end = end.next.next;
            t = q.next;
            q.next = mid;
            mid = q;q = t;
        }
        ListNode p=(end==null)?mid.next:mid;
        while(q!=null){
            if(q.val!=p.val)
                return false;
            q=q.next;
            p=p.next;
        }
        return true;
    }
}

分隔链表

LeetCode:分隔链表

题目描述:

给定一个头结点为 root 的链表, 编写一个函数以将链表分隔为 k 个连续的部分。

每部分的长度应该尽可能的相等: 任意两部分的长度差距不能超过 1,也就是说可能有些部分为 null。

这k个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度应该大于或等于后面的长度。

返回一个符合上述规则的链表的列表。

举例: 1->2->3->4, k = 5 // 5 结果 [ [1], [2], [3], [4], null ]

示例:

输入: 
root = [1, 2, 3], k = 5
输出: [[1],[2],[3],[],[]]
解释:
输入输出各部分都应该是链表,而不是数组。
例如, 输入的结点 root 的 val= 1, root.next.val = 2, \root.next.next.val = 3, 且 root.next.next.next = null。
第一个输出 output[0] 是 output[0].val = 1, output[0].next = null。
最后一个元素 output[4] 为 null, 它代表了最后一个部分为空链表。

思想:

没什么思想,就是先遍历一遍获得链表长度,再找分隔点进行分割。主要是代码逻辑有点复杂,需要多加练习。

代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode[] splitListToParts(ListNode root, int k) {
        int len=0;
        ListNode p=root,pre=null;
        while(p!=null){
            ++len;
            p=p.next;
        }
        int base=len/k,add=len%k,i=0,count=0;
        p=root;
        ListNode[] res = new ListNode[k];
        while(p!=null){
            if(count ==0){
                if(pre!=null)
                    pre.next = null;
                res[i++]=p;
                count = base + (add-->0?1:0);
            }
            --count;
            pre=p;
            p=p.next;
        }
        return res;
    }
}

奇偶链表

LeetCode:奇偶链表

题目描述:

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

示例:

输入: 2->1->3->5->6->4->7->NULL 
输出: 2->3->6->7->1->5->4->NULL

思想:

第一次写的方法是在遍历时使用一个flag注明奇偶,这样太蠢了。使用双指针比较直观,如下方代码

代码:

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode oddEvenList(ListNode head) {
        if(head==null||head.next==null)
            return head;
        ListNode m=head.next,n=m;
        ListNode p=head;
        while(n!=null&&n.next!=null){
            p.next = p.next.next;
            n.next = n.next.next;
            p=p.next;
            n=n.next;
        }
        p.next=m;
        return head;
    }
}

相交链表

LeetCode:相交链表

题目描述:

编写一个程序,找到两个单链表相交的起始节点。

示例:

输入: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
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

思想:

这题代码简洁,非常巧妙。A走完A的路再走B的路,B走完B的路再去走A的路,最后一段路一定是重合的,即最后一定会在交点相遇。

代码:

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode p=headA,q=headB;
        while(p!=q){
            p=(p==null)?headB:p.next;
            q=(q==null)?headA:q.next;
        }
        return p;
    }
}

翻转链表

LeetCode:翻转链表

题目描述:

反转一个单链表。

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

思想:

前插法很熟悉了,主要是递归。注意每次递归返回末尾结点,即新链表的头结点,不然得不到结果;函数体内记录当前结点和下一个结点,这递归相当于从倒数第二个结点开始向左翻转,每次返回后,左移一个结点继续下一次翻转。

代码

//前插
private ListNode preInsert(ListNode p){
    ListNode L =new ListNode(0),temp;
    L.next=null;
    while(p!=null){
        temp = p.next;
        p.next=L.next;
        L.next = p;
        p=temp;
    }
    return L.next;
}
//递归
private ListNode recursive(ListNode p){
    if(p==null||p.next==null){
        return p;
    }
    ListNode q =p.next;
    ListNode head = recursive(q);
    q.next=p;
    p.next=null;
    return head;
}
public ListNode reverseList(ListNode head) {
    return recursive(head);
    //return preInsert(head);
}

2021-1-24原地翻转

public class Solution {
    public ListNode ReverseList(ListNode head) {
        if(head==null||head.next==null) return head;
        ListNode left=null,right=head;
        ListNode temp;
        while(right!=null){
            temp = right.next;
            right.next=left;
            left = right;
            right = temp;
        }
        return left;
    }
}

合并两个有序链表

LeetCode:合并两个有序链表

题目描述:

将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4

思想:

这题用递归来做,思路比较清晰简洁。

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1==null) return l2;
        if(l2==null) return l1;
        if(l1.val<l2.val){
            l1.next = mergeTwoLists(l1.next,l2);
            return l1;
        }else{
            l2.next = mergeTwoLists(l1,l2.next);
            return l2;
        }
    }
}

删除链表的倒数第k个节点

LeetCode:删除链表的倒数第N个节点

题目描述:

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

示例:

给定一个链表: 1->2->3->4->5, 和 n = 2.

当删除了倒数第二个节点后,链表变为 1->2->3->5.

思想:

本题可以用一趟遍历实现。快慢指针构成一个窗口,再滑动窗口。具体:先循环n次将high指针移动到第n位置,与头结点位置的slow指针同时移动,这样当high指向null时,slow指针指向了目标位置。

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode fast = head,slow=head;
        while(n-->0){
            fast = fast.next;
        }
        if(fast==null) return head.next;
        while(fast.next!=null){
            fast = fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;
        return head;
    }
}

环形链表

LeetCode:环形链表

题目描述:

给定一个链表,判断链表中是否有环。

示例:

输入:head = [3,2,0,-4]
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

思想:

快慢指针,一次跨两个节点,一次跨一个节点,两个指针重合就说明是环形链表。

代码:

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head==null) return false;
        ListNode slow = head;
        ListNode fast = head.next;
        while(fast!=slow){
            if(fast==null||fast.next==null) return false;
            slow = slow.next;
            fast = fast.next.next;
        }
        return true;
    }
}

两两交换链表中的结点

LeetCode:两两交换链表中的结点

题目描述:

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例:

给定 1->2->3->4, 你应该返回 2->1->4->3.

思想:

没啥思想,一个递归,一个循环,两种方法,注意熟练掌握。

代码

  • 递归
private ListNode swapPairsImpl(ListNode L){
    if(L.next==null) return null;
    if(L.next.next == null) return L.next;
    ListNode p = L.next,q=p.next;
    p.next = swapPairsImpl(q);
    L.next = q;
    q.next = p;
    return q;
}
public ListNode swapPairs(ListNode head) {
    ListNode L =new ListNode(-1);
    L.next = head;
    return swapPairsImpl(L);
}
  • 循环
public ListNode swapPairs(ListNode head) {
    ListNode L =new ListNode(-1);
    L.next = head;
    ListNode m, p=L,q;
    while(p.next!=null&&p.next.next!=null){
        m=p;p = m.next;q=p.next;

        p.next = q.next;
        m.next = q;
        q.next = p;
    }
    return L.next;
}

删除排序链表中的重复元素

LeetCode:删除排序链表中的重复元素

题目描述:

给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

示例:

输入: 1->1->2->3->3
输出: 1->2->3

思想:

递归方法很巧妙,只需要三行代码。关键点在于,通过return来返回相同结点最右边的结点,达到删除目的。

代码

  • 普通循环(自己想到的)
public ListNode deleteDuplicates(ListNode head) {
    ListNode p =head;
    while(p!=null&&p.next!=null){
        if(p.next.val == p.val)
            p.next = p.next.next;
        else
            p=p.next;
    }
    return head;
}
  • 递归
public ListNode deleteDuplicates(ListNode head) {
    if(head==null||head.next==null) return head;
    head.next = deleteDuplicates(head.next);
    return head.val==head.next.val?head.next:head;
}
posted @ 2020-04-13 10:03  数小钱钱的种花兔  阅读(357)  评论(0编辑  收藏  举报