关于链表的算法题:两数相加,删除链表的倒数第N个节点,环形链表,排序链表,相交链表

本文介绍了链表相关的算法问题,包括两数相加的链表实现、删除链表倒数第N个节点的方法、环形链表的判断与处理、排序链表的算法以及相交链表的查找。文章提供了Java和JavaScript的解冑方案,涉及快慢指针、哈希表等技巧。
摘要由CSDN通过智能技术生成
1. 两数相加(链表)

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807

Java答案:

1. 先考虑两个链表都不为空时的,在考虑一个链表不为空,再考虑两个链表都为空
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode l3 = new ListNode(0);
        ListNode p = l3;
        ListNode x = l1;
        ListNode y = l2;
        int jinwei = 0;
        while(x!=null && y!=null){
            int sum = x.val+y.val+jinwei;
            jinwei =sum/10;
            sum = sum%10;
            ListNode q = new ListNode(sum);
            p.next = q;
            p=q;
            x=x.next;
            y=y.next;
        }
        //有一个链表为空时
        if(x!=null){ 
            while(jinwei!=0 && x!=null){
                int sum = x.val+jinwei;
                jinwei =sum/10;
                sum=sum%10;   
                ListNode q = new ListNode(sum);
                p.next = q;
                p=q;
                x=x.next;
            }
            p.next=x;
        }
        if(y!=null){
            while(jinwei!=0 && y!=null){
                int sum = y.val+jinwei;
                jinwei =sum/10;
                sum=sum%10;
                ListNode q = new ListNode(sum);
                p.next = q;
                p=q;
                y=y.next;
            }
            p.next=y;
        } 
        //俩个链表都为空时,查看是否有进位
        if(jinwei !=0){
                ListNode q = new ListNode(jinwei);
                p.next = q;
                p=q;
        }   
        return l3.next; 
 }
}
2. 直接一个while循环,用或写出
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode resultList = new ListNode(0);
        int cache = 0;
        ListNode l3 = resultList;
        while (l1 != null || l2 != null || cache > 0){
            int l1Val = l1 == null ? 0 : l1.val;
            int l2Val = l2 == null ? 0 : l2.val;
            int l3Val = l1Val + l2Val + cache;
            cache = 0;
            // 判断是否大于 9 大于9 进一位
            if (l3Val >  9){
                cache = 1;
                l3Val = l3Val - 10;
            }
            l3.next = new ListNode(l3Val);
            l3 = l3.next;
            l1 = l1 == null ? l1 : l1.next;
            l2 = l2 == null ? l2 : l2.next;
        }
        return resultList.next;
    }
}

JavaScript答案:

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var addTwoNumbers = function(l1, l2) {
    let result = new ListNode(null);
    let nextRst = result;
    // 进位
    let params = 0 // 传给下一个层级的值
    let val = 0 // 传给当前层级的值
    while(l1 != null || l2 != null) {
        let x = (l1 != null) ? l1.val : 0;
        let y = (l2 != null) ? l2.val : 0;
        val = (x + y + params) % 10;
        params = Math.floor((x + y + params) / 10);  
        nextRst.next = new ListNode(val) ;
        nextRst = nextRst.next;
        if(l1 != null) l1 = l1.next;
        if(l2 != null) l2 = l2.next;        
    }
    if(params!=0) {
       nextRst.next = new ListNode(params);
    }
    return result.next;
};

注意 : 因为JS中整数相除,返回的是一个小数,不像java中,两个整数相除得到还是整数,所以需要使用Math.floor(),把小数向下取整。

2. 删除链表的倒数第N个节点

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
两种方法 : 1.快慢指针, 2先得到链表的总长度,再从头遍历count-n-1次,删除下一个节点

Java答案:
 /**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
 1. 快慢指针:快指针先走n+1步,这里需要判断是否n大于整个链表的长度,或n正好等于整个链表的长度,然后快慢指针一块走,快指针走完链表时,慢指针删除下一个即可
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
       if(n<=0 || head ==null){
           return null;
       }
       ListNode s = head;
       ListNode f = head;
       while(n>=0){
           if(n!=0 && f == null){
               return null;
           }else if(n==0 && f==null){
               return head.next;
           }
           f = f.next;
           n--;
       }
        while(f!=null){
            f=f.next;
            s = s.next;
        }
        s.next = s.next.next;
        return head;
    }
}
执行时间是2ms,内存消耗是34.3MB
2. 首先遍历一次,得到链表的总长度,再从头遍历count-n-1次,删除下一个节点
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        //首先统计出数量
            ListNode p = head;
            int count=0;
            while(p!=null){
                count++;
                p=p.next;
            }
            System.out.println(count);
            if(count<n){ //总长度比n小,说明不成立
                return null;
            }else if(count==n){ //如果总长度等于n,说明删除头结点
                return head.next;
            }
            p=head;
            while(count-n>1){
                p=p.next;
                count--;
            }
            p.next = p.next.next;
            return head;
    }
}
执行时间是7ms,内存消耗是34.8MB

JavaScript答案

1. 快慢指针执行时间是80ms,内存消耗是33.9MB
2. 先求总长度的方法执行时间是76ms,内存消耗是34.4MB
3. 环形链表

给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
示例1:
在这里插入图片描述
示例2:
在这里插入图片描述
示例3:
在这里插入图片描述
两个方法 :1.双指针,2哈希表

Java答案:
/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
 1. 双指针
public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head==null){
            return false;
        }
        ListNode f = head;
        ListNode s = head;
        while(f!=null && f.next!=null){
            s = s.next;
            f=f.next.next;
            if(s==f){
                return true;
            }
        }
        return false;
    }
}
执行时间是1ms,内存消耗:39MB
时间复杂度是o(n),空间复杂度是o(1)
2. 哈希表:可以通过检查一个节点此前是否被访问过来判断链表是否为环形链表。
遍历所有节点,并在哈希表中存储每个节点的引用,如果当前节点为空节点,就已经检测到链表尾部的下一个节点,说明该链表不是环形链表,如果当前节点的引用已经存在于哈希表中,那么返回true。
public class Solution {
    public boolean hasCycle(ListNode head) {
        Set<ListNode> set = new HashSet<>();
        while(head!=null){
            if(set.contains(head)){
                return true;
            }else{
                set.add(head);
            }
            head=head.next;
        }
        return false;
    }
}
执行时间是7ms,内存消耗是36.3MB
时间复杂度是o(n),空间复杂度是o(n)

注意

  1. 哈希表的声明是Set<> set = new HashSet<>();
  2. 添加元素:set.add(元素);
  3. 查看集合中是否存在: set.contains(元素);
JavaScript答案:
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {boolean}
 */
 1. 快慢指针
var hasCycle = function(head) {
        if(head==null){
            return false;
        }
        let f = head;
        let s = head;
        while(f!=null && f.next!=null){
            s = s.next;
            f=f.next.next;
            if(s==f){
                return true;
            }
        }
        return false;
};
执行时间是96ms,内存消耗是36MB
4. 环形链表 ||

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
说明:不允许修改给定的链表。
示例 1:
在这里插入图片描述
示例 2:
在这里插入图片描述
示例 3:
在这里插入图片描述
方法 :快慢指针

java答案:
/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
1. public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        if(head==null){
          return null;
        }
        boolean hasCycle = false;
         //先用快慢指针判断是否是环形
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                hasCycle = true;
                break;
            }
        }
        //如果没有环,就返回Null
        if (!hasCycle) {
            return null;
        }
        //如果有环,就让慢指针从头走,两个指针每次都走一步,相遇时正好是环的入口。
        slow = head;
        while (fast != slow) {
            fast = fast.next;
            slow = slow.next;
        }
        return fast;
    }
}
2. public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode p1 = head;
        ListNode p2 = head;
        while(p2 != null && p2.next != null){// 判断有无环
            p1 = p1.next;
            p2 = p2.next.next;
           
            if(p1 == p2){// 重合表示有环
                p1 = head;// p1重新出发,p1、p2每次都走一步,如果重合,重合处必为环的入口
                while(p1!=p2){
                    p1 = p1.next;
                    p2 = p2.next;
                }
                return p1;
            }
        }
        return null;
    }
}

注意: 如果使用2倍速,快指针每次走两步,慢指针每次走一步,这样第一次两个指针相遇时,快指针正好比慢指针多走了一个环。

javaScript答案:
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var detectCycle = function(head) {
    if(head == null || head.next == null) {
        return null
    }
    let slow = head.next
    let fast = head.next.next
    let p = head
    
    while(true) {
        if(fast == null || fast.next == null) {
            return null
        }
        
        if(fast == slow) {
            // 相遇
            while(p != slow) {
                p = p.next
                slow = slow.next
            }
            return p
        }
        
        slow = slow.next
        fast = fast.next.next
    }
};

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var detectCycle = function(head) {
    if(head==null){
       return null;
    }
    var p = head;
    var q = head;
    var isCycle = false;
    while(p!=null && p.next!==null){
      p=p.next.next;
      q= q.next;
      if(p==q){
         isCycle = true;
         break;
      }
    }
    if(isCycle==false){
       return null;
    }
    q = head;
    while(p!=q){
        p=p.next;
        q=q.next;
    }
    return q;
};
5. 排序链表

在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
示例 1:
输入: 4->2->1->3
输出: 1->2->3->4
示例 2:
输入: -1->5->3->4->0
输出: -1->0->3->4->5
两种方法 :1是两层循环,找到最小的和第一个换,类推。2是两路归并的思想

java答案:
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
1. class Solution {
    public ListNode sortList(ListNode head) {
        ListNode p = head;
        while(p!=null && p.next!=null){
            ListNode q = p.next;
            ListNode m = p.next;
            int min = q.val;
            while(q.next!=null){
                if(q.next.val < min){
                    min = q.next.val;
                    m = q.next;
                }
                q=q.next;
            }
            if(min < p.val){
                m.val = p.val;
                p.val = min;
            }
            p=p.next;
        }
        return head;
    }
}
执行时间是1162ms,内存消耗是41.8MB。
2. /**
     * 链表归并排序
     * 参考:Sort List——经典(链表中的归并排序) https://www.cnblogs.com/qiaozhoulin/p/4585401.html
     * 
     * 归并排序法:在动手之前一直觉得空间复杂度为常量不太可能,因为原来使用归并时,都是 O(N)的,
     * 需要复制出相等的空间来进行赋值归并。对于链表,实际上是可以实现常数空间占用的(链表的归并
     * 排序不需要额外的空间)。利用归并的思想,递归地将当前链表分为两段,然后merge,分两段的方
     * 法是使用 fast-slow 法,用两个指针,一个每次走两步,一个走一步,知道快的走到了末尾,然后
     * 慢的所在位置就是中间位置,这样就分成了两段。merge时,把两段头部节点值比较,用一个 p 指向
     * 较小的,且记录第一个节点,然后 两段的头一步一步向后走,p也一直向后走,总是指向较小节点,
     * 直至其中一个头为NULL,处理剩下的元素。最后返回记录的头即可。
     * 
     * 主要考察3个知识点,
     * 知识点1:归并排序的整体思想
     * 知识点2:找到一个链表的中间节点的方法
     * 知识点3:合并两个已排好序的链表为一个新的有序链表
     */
   class Solution {
     public ListNode sortList(ListNode head) {
        return head == null ? null : mergeSort(head); // 判断head是否为空
    }

    private ListNode mergeSort(ListNode head) {
        if (head.next == null) { // 如果链表中只有一个节点,直接返回这个节点
            return head;
        }
        ListNode p = head, q = head, pre = null;
        while (q != null && q.next != null) { //p走到二分后一个链表的第一个节点, pre是前一个链表的最后一个
            pre = p;
            p = p.next;
            q = q.next.next;
        }
        pre.next = null; //pre后面断开
        ListNode l = mergeSort(head);//递归左边的链表
        ListNode r = mergeSort(p);//递归右边的链表
        return merge(l, r);//排序左右两边的链表
    }
 //排序左右两边的链表方法,合并两个已排好序的链表为一个新的有序链表
    ListNode merge(ListNode l, ListNode r) {
        ListNode dummyHead = new ListNode(0);
        ListNode cur = dummyHead;
        while (l != null && r != null) {
            if (l.val <= r.val) {
                cur.next = l;
                cur = cur.next;
                l = l.next;
            } else {
                cur.next = r;
                cur = cur.next;
                r = r.next;
            }
        }
        if (l != null) {
            cur.next = l;
        }
        if (r != null) {
            cur.next = r;
        }
        return dummyHead.next;
    }
}
执行时间是13ms,内存消耗是45.3MB。
JavaScript答案:
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
 //合并两个已排好序的链表为一个新的有序链表
const merge = (l, r) => {
    let result = new ListNode(0);
    let current = result;
    while(l && r) {
        if(l.val < r.val) {
            current.next = l;
            l = l.next;
            current = current.next;
        } else {
            current.next = r;
            r = r.next;
            current = current.next;
        }
    }
    current.next = l || r;
    return result.next;
}
// 归并排序
var sortList = function(head) {
    if(head === null || head.next === null) return head;
    let fast = head;
    let slow = head;
    while(fast.next && fast.next.next) {
        fast = fast.next.next;
        slow = slow.next;
    }
    const mid = slow.next;
    console.log(mid);
    slow.next = null;
    return merge(sortList(head), sortList(mid));
};
执行时间:140ms,内存消耗:40.1MB
6. 相交链表

编写一个程序,找到两个单链表相交的起始节点。
如下面的两个链表:
在这里插入图片描述
在节点 c1 开始相交。
示例 1:
在这里插入图片描述
示例 2:
在这里插入图片描述
示例 3:
在这里插入图片描述
注意:
如果两个链表没有交点,返回 null.
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
两种方法 :1是set保存,再contains。2是比较长度,然后长的先走,走到和短的一样长,再一起走,比较节点是否相等

java答案:
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
 1. public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA==null || headB==null){
            return null;
        }
        Set<ListNode> set = new HashSet<>();
        while(headA != null) {
            set.add(headA);
            headA = headA.next;
        }
        while(headB != null) {
            if(set.contains(headB)) {
                return headB;
            }
            else {
                headB = headB.next;
            }
        }
        return null;
    }
}
执行时间:20ms,内存消耗:47MB。
2. 先判断两个链表的长度,让长的链表先走长出来的那些节点,然后一起走,判断这两个节点是否相等,相等返回,不相等继续比较下边的 
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA==null || headB==null){
            return null;
        }
        int countA =0;
        int countB=0;
        ListNode p = headA;
        while(p!=null){
            countA++;
            p=p.next;
        }
        ListNode q = headB;
        while(q!=null){
            countB++;
            q=q.next;
        }
        p=headA;
        q=headB;
        int sum = countA>countB?countA-countB:countB-countA;
        //ListNode m = countA>countB?p:q;
        while(sum>0){
            if(countA>countB){
                p=p.next;
                sum--;
            }else if(countA<countB){
                q=q.next;
                sum--;
            }
        }
        while(p!=null && q!=null){
            if(p==q){
                return p;
            }
            p=p.next;
            q=q.next;
        }
        return null;
    }
}
执行时间:2ms,内存消耗:48.2MB

注意: hashSet的一些方法:

  1. HashSet的特点:存取集合的顺序不一致,没有索引,元素没有重复的。
  2. hashSet的唯一性原理:先比较哈希值,如果哈希值不同,就存入集合;如果哈希值相同,就调用equals方法,如果equals方法返回true,就不添加,返回false就添加。
  3. 创建集合对象:HashSet<泛型> set= new HashSet<>();
  4. 添加元素:set.add(元素);
  5. 查看集合中是否存在: set.contains(元素);
javaScript答案:
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */
var getIntersectionNode = function(headA, headB) {
     if(headA==null || headB==null){
            return null;
        }
        let countA =0;
        let countB=0;
        let p = headA;
        while(p!=null){
            countA++;
            p=p.next;
        }
        let q = headB;
        while(q!=null){
            countB++;
            q=q.next;
        }
        p=headA;
        q=headB;
        let sum = countA>countB?countA-countB:countB-countA;
        //ListNode m = countA>countB?p:q;
        while(sum>0){
            if(countA>countB){
                p=p.next;
                sum--;
            }else if(countA<countB){
                q=q.next;
                sum--;
            }
        }
        while(p!=null && q!=null){
            if(p==q){
                return p;
            }
            p=p.next;
            q=q.next;
        }
        return null;
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值