[Leetcode刷题]——链表

 

[leecode刷题]系列博客

用于记录自己力扣刷题的过程,所有题目均来自https://leetcode-cn.com/。

解题顺序参考 https://github.com/CyC2018/CS-Notes。

 

一、160.相交链表

二、206.反转链表

三、21.合并两个有序链表

四、83.删除链表中的重复元素

五、19.删除链表的倒数第n个元素

六、24.两两交换链表中的相邻结点

七、445.两数相加

八、234.回文链表

九、725.分割链表

十、328.奇偶链表

 

 

一、找出两个链表的交点

160.相交链表(easy)2021-01-05

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

如下面的两个链表,在c1 处相交:

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

解法:如果两个链表有相交,设第一个链表长度为 a+c, 第二个链表长度为b+c。

  让两个链表指针从头开始遍历,第一个链表遍历a+c后接着从头开始遍历第二个链表的b,第二个链表遍历b+c后从头开始遍历第二个链表的a。

  最后两个指针会同时到达相交的地方。

  注意,条件判断时判断的是(l1 == null)而不是 (l1.next == null) 。其目的是为了如果两个链表没有相交,那么在遍历两个链表后连个指针均指向了链表最后的null,而非陷入死循环。

  此解法的复杂度为O(n)。

 

二、反转链表

  206.反转链表(easy)2021-01-06

  反转一个单链表。

  示例:

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

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

  进阶:
  你可以迭代或递归地反转链表。你能否用两种方法解决这道题?

 

  我的原始解法,错误示范:

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode newHead = head;
        newHead.next = null;
        ListNode traverseNode = head;
        while(traverseNode.next != null){
            traverseNode = traverseNode.next;  
            ListNode temp = traverseNode;
            temp.next = newHead;
            newHead = temp;
        }
        return newHead;
    }
}

 

 

 

 

 

错误分析:上述代码理论推导似乎可行,但是编译器运行不通过。

     debug后发现,第2-5行,将head结点赋值给newhead结点,然后将newhead结点的next指针改为null,发现原始的head结点的next指针自动也变成了null。

            在idea中可以看到,虽然head的值赋值给newHead 和traverseNode, 但是并没有开辟新空间,所以这里不能当一般变量处理。

 

标准解法:

1.递归,yyds

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

2.头插法

public ListNode reverseList(ListNode head) {
    ListNode newHead = new ListNode(-1);
    while (head != null) {
        ListNode next = head.next;
        head.next = newHead.next;
        newHead.next = head;
        head = next;
    }
    return newHead.next;
}

 

 三、合并两个有序链表

21、合并两个有序链表(easy) 2021-01-07

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

示例:

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

我的代码:

常规思路,需要注意的是在进行循环后的第二个if 语句时,又重新判断了( l1 != null )。 如果不加判断条件有可能为null就直接写l1.val会产生NullPointerException异常。

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode newHead = new ListNode(-1);
        ListNode temp = newHead;
        while(l1 != null && l2 != null){
            if(l1.val < l2.val){
                temp.next = l1;
                temp = l1;
                l1 = l1.next;
            }
            if(l1 != null && l2.val <= l1.val){
                temp.next = l2;
                temp = l2;
                l2 = l2.next;
            }
        }
        if(l1 == null){
            temp.next = l2;
        }
        if(l2 == null){
            temp.next = l1;
        }
        return newHead.next;
    }
}

递归的写法,真简洁:

 

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;
    }
}
}

 

四、从有序链表中删除重复结点

83、删除排序链表中的重复元素   (easy)  2021-01-08

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

示例 1:

输入: 1->1->2
输出: 1->2
示例 2:

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

 我的解法:需要注意的一点是在没有判断结点是否为空的情况下之直接就使用.val 调用结点的数值会报NullPointerException异常

 

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode tempNode = head;
        while(tempNode != null){
            if(tempNode.next != null && tempNode.val == tempNode.next.val){
                tempNode.next = tempNode.next.next;
            }else{
                tempNode = tempNode.next;
            }  
        }
        return head;
    }
}

 

 

 

 递归的解法,妙呀,三行就搞定!唯一不足的就是内存消耗较多。

class Solution {
    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;
}
}

 

 五、删除链表的倒数第N个结点

19、给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。(medium) 2021-01-09

示例:

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

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

给定的 n 保证是有效的。

进阶:

你能尝试使用一趟扫描实现吗?

我的解法,遍历两次,内存击败29.8%,还是太菜

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
         ListNode tempNode = head;
         int len = 1;
         while(tempNode.next != null){
             len++;
             tempNode = tempNode.next;
         }
         tempNode = head;
         if(n == len){
             return head.next;
         }else{
             while(tempNode != null && tempNode.next != null){
                 if(len == n + 1){
                     tempNode.next = tempNode.next.next;
                  }
                 len--;
                 tempNode = tempNode.next;
             }
             return head;
         }

    }
}

遍历一次的原理,设置两个指针,让一个指针先走n长度,然后两个指针一起走,当快的指针走到链表结尾时,慢的指针就在倒数第n个位置

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
    ListNode fast = head;
    while (n-- > 0) {
        fast = fast.next;
    }
    if (fast == null) return head.next;
    ListNode slow = head;
    while (fast.next != null) {
        fast = fast.next;
        slow = slow.next;
    }
    slow.next = slow.next.next;
    return head;
    }
}

六、两两交换链表中的相邻结点

24、给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。(medium) 2021-01-10

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

示例 1:

输入:head = [1,2,3,4]
输出:[2,1,4,3]

 

解法:力扣里的链表的头节点都是第一个结点,在这个题中,设置第一个结点的前一个结点为头结点,在判断后两个结点是否为空,然后进行交换即可。

class Solution {
    public ListNode swapPairs(ListNode head) {
        ListNode preHeadNode = new ListNode(-1);
        preHeadNode.next = head;
        ListNode traverseNode = preHeadNode;
        while(traverseNode.next != null && traverseNode.next.next != null){
            ListNode n1 = traverseNode.next;
            ListNode n2 = traverseNode.next.next;
            traverseNode.next = n2;
            ListNode temp = n2.next;
            n2.next = n1;
            n1.next = temp;
            traverseNode = n1;  
        }
        return preHeadNode.next;
    }
}

  递归的写法,需要系统的学习递归,链表和树的大多数题目太适合递归了。

  参考博客https://lyl0724.github.io/2020/01/25/1/ 

  递归三部曲:1.找终止条件 2.找返回值 3.单次过程

class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }
        ListNode next = head.next;
        head.next = swapPairs(next.next);
        next.next = head;
        return next;
    }
}

 七、两数相加

 445、两数相加 (medium) 2021-01-11

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

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

进阶:

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

示例:

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

  我的解法:我尝试用最简单的解法解这个问题,先将链表转成数字,相加后再将数组转成链表。遇到的问题是链表太长,转成int型数字溢出了,换成long型居然还溢出了。此路不通。

    

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        int sum = listToInteger(l1) + listToInteger(l2);
        return integerToList(sum);
    }
    public int listToInteger(ListNode head){
        ListNode node = head;
        int sum = 0;
        while(node != null){
            sum = sum * 10 + node.val;
            node = node.next;
        }
        return sum;
    }
    public ListNode integerToList(int sum){
        if(sum == 0){
            return new ListNode(0);
        }
        ListNode head = new ListNode(-1);
         while(sum != 0){
             int val = sum % 10;
             ListNode node = new ListNode(val);
             node.next = head.next;
             head.next = node;
             sum = sum / 10;
         }
         return head.next;
    }
}

   使用另一个常规解法,翻转两个链表,然后一个结点一个结点的进行相加并设置进位。代码冗长,逻辑简单。

  需要注意的是,在解此题时,翻转链表函数时,又出现了与前几天相似的错误。即复制一个结点并对他的next指针进行修改时,原始的结点也发生了改变。方法是在修改前保存下next指针。

  

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode node1 = reverseList(l1);  //链表的头结点
        ListNode node2 = reverseList(l2);
        ListNode head = new ListNode(-1);  //求和链表的头结点
        ListNode temp = new ListNode(-1);  //求和链表的结点
        int flag = 0;  //进位标志
        int sum = 0;  
        while(node1 != null || node2 != null || flag != 0){  //要考虑求和后进位
            if(node1 == null && node2 == null){
                sum = 0;
            }else if(node1 == null){
                sum = node2.val;
            }else if(node2 == null){
                sum = node1.val;
            }else{
                sum = node1.val + node2.val;
            }
            if(sum + flag >= 10){
                temp = new ListNode(sum - 10 + flag);
                flag = 1;
            }else{
                temp = new ListNode(sum + flag);
                flag = 0;
            }
            if(node1 != null){
                node1 = node1.next;
            }
            if(node2 != null){
                node2 = node2.next;
            }
            temp.next = head.next;
            head.next = temp;
        }
        return head.next;
    }
    public ListNode reverseList(ListNode head){  //头插法
        ListNode node = new ListNode(-1);
        ListNode traverseNode = head;
        while(traverseNode != null){
            ListNode next = traverseNode.next;  //这里先保存next指针
            traverseNode.next = node.next;
            node.next = traverseNode;
            traverseNode = next;
        }
        return node.next;
    }
}

  如果不对链表进行翻转操作,使用栈进行处理。大体都相似,翻转后取出和入栈后再取出作用相同。使用栈耗时更久但是内存占用较小。

 

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        Stack<Integer> stack1 = listToStack(l1);
        Stack<Integer> stack2 = listToStack(l2);
        int flag = 0;  //设置一个进位的标志
        ListNode head = new ListNode(-1);
        while(!stack1.isEmpty() ||  !stack2.isEmpty() || flag == 1){
            int sum;  //新链表结点的值
            ListNode node;
            if(stack1.isEmpty() && stack2.isEmpty()){
                sum = flag;
            }else if(stack1.isEmpty()){
                sum = stack2.pop() + flag;
            }else if(stack2.isEmpty()){
                sum = stack1.pop() + flag;
            }else{
                sum = stack1.pop() + stack2.pop() + flag;
            }
            if(sum < 10){
                node = new ListNode(sum);
                flag = 0;
            }else{
                node = new ListNode(sum - 10);
                flag = 1;
            }
            node.next = head.next;
            head.next = node;
        }
        return head.next;
    }

    public Stack<Integer> listToStack(ListNode head){
        ListNode node = head;
        Stack<Integer> stack = new Stack<>();
        while(node != null){
            stack.push(node.val);
            node = node.next;
        }
        return stack;
    }
}

 

八、回文链表
  234.回文链表  (easy) 2021-01-12

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

示例 1:

输入: 1->2
输出: false
示例 2:

输入: 1->2->2->1
输出: true
进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

我的解法:先计算链表长度,再遍历链表,前一半遍历时存储到栈中,后一半遍历时就出栈进行比较是否一致。使用栈那么空间复杂度变成O(n)。
     如果不使用栈,将后半段链表分割下来翻转作用一样。
     好不容易来个题一遍过, 时间击败37% ,空间击败26%也太难顶了吧。

 

class Solution {
    public boolean isPalindrome(ListNode head) {
        Stack<Integer> stack = new Stack<>();
        ListNode node = head;
        int length = length(head);
        for(int i = 0; i < length / 2; i++){
            stack.push(node.val);
            node = node.next;
        }
        if(length % 2 == 1){
            node = node.next;
        }
        while(node != null){
            int pre = stack.pop();
            int rear = node.val;
            if(pre != rear){
                return false;
            }
            node =  node.next;
        }
        return true;
    }
    //返回链表的长度
    public int length(ListNode head){
        ListNode tempNode = head;
        int length = 0;
        while(tempNode != null){
            length++;
            tempNode = tempNode.next;
        }
        return length;
    }

 

 

九、分割链表

725、分隔链表 (medium)2021-01-13

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

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

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

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

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

  解法:此题代码较长但是逻辑简单,分类讨论除法的三种结果。

  

class Solution {
    public ListNode[] splitListToParts(ListNode root, int k) {
        int length = length(root);
        ListNode[] arr = new ListNode[k];  //初始化长度为k的数组
        ListNode traverseNode = root;
        ListNode temp = null;
        if(length == 0){   //0除了啥都等于0,都算除尽,单独考虑
            return arr;
        }else if(length % k == 0){  //正好除尽
            for(int i = 0; i < k; i++){
                arr[i] = traverseNode;
                for(int j = 0 ; j < length / k - 1; j++){
                    traverseNode = traverseNode.next;
                }
                temp = traverseNode.next;
                traverseNode.next = null;
                traverseNode = temp;
            } 
        }else if(length % k == length){   //说明k大于length
            for(int i = 0; i < length; i++){
                arr[i] = traverseNode;
                temp = traverseNode.next;
                traverseNode.next = null;
                traverseNode = temp;
            }
            for(int i = length; i < k; i++){
                arr[i] = null;
            }
        }else{        //k不能除尽length,前几个链表会比后面的长1
            for(int i = 0; i < length % k; i++){
                arr[i] = traverseNode;
                for(int j = 0 ; j < length / k ; j++){  //多移了一个
                    traverseNode = traverseNode.next;
                }
                temp = traverseNode.next;
                traverseNode.next = null;
                traverseNode = temp;
            }
            for(int i = length % k; i < k; i++){
                arr[i] = traverseNode;
                for(int j = 0 ; j < length / k - 1; j++){
                    traverseNode = traverseNode.next;
                }
                temp = traverseNode.next;
                traverseNode.next = null;
                traverseNode = temp;
            }
        }
        return arr;
    }
    //返回链表的长度
    public int length(ListNode root){
        ListNode node = root;
        int length = 0;
        while(node != null){
            length++;
            node = node.next;
        }
        return length;
    }
}

 

十、奇偶链表

328、奇偶链表  (medium) 2021-01-13

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

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

示例 1:

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

  常规思路。

class Solution {
    public ListNode oddEvenList(ListNode head) {
        if(head == null || head.next == null ){
            return head;
        }
        ListNode evenNode = head;
        ListNode oddNode = head.next;
        ListNode oddNodeHead = oddNode;
        while(evenNode.next.next != null && oddNode.next.next != null){
            evenNode.next = evenNode.next.next;
            oddNode.next = oddNode.next.next;
            evenNode = evenNode.next;
            oddNode = oddNode.next;
        }
        if(oddNode.next != null){
            evenNode.next = evenNode.next.next;
            evenNode = evenNode.next;
            oddNode.next = null;
        }
        evenNode.next = oddNodeHead;
        return head;
    }
}

 

posted @ 2021-01-05 21:59  -野比大雄-  阅读(119)  评论(0)    收藏  举报