常见算法题目:链表(二)

188 阅读1分钟

前言

如果想进大厂工作,除了基础知识,项目经验,还有一些非常重要的能力需要锻炼,其中最重要的就是算法。算法题千千万,但是类型也就几十种,接下来的系列文章,我将面试中常见的算法题类型进行归纳。


链表面试题01. 链表求和

给定两个用链表表示的整数,每个节点包含一个数位。这些数位是反向存放的,也就是个位排在链表首部。编写函数对这两个整数求和,并用链表形式返回结果。

编写函数对这两个整数求和,并用链表形式返回结果。

1


代码:
解法一(该方法目前存在问题):

/**
 * 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) {
        ArrayList<Integer> arrayListA = new ArrayList();
        ArrayList<Integer> arrayListB = new ArrayList();

        double tempA = 0;
        int countA = 0;
        while (l1 != null) {
            tempA = tempA + l1.val * Math.pow(10, countA);
            countA++;
            l1 = l1.next;
        }

        double tempB = 0;
        int countB = 0;
        while (l2 != null) {
            tempB = tempB + l2.val * Math.pow(10, countB);
            countB++;
            l2 = l2.next;
        }

        double result = 0;
        result = tempA + tempB;

        int count = ((int)result + "").length();

        ListNode list = new ListNode(0);
        ListNode listHead = list;

        for (int i = 0; i < count; i++) {
            ListNode temp = new ListNode((int)((result / Math.pow(10, i)) % 10));
            list.next = temp;
            list = list.next;
        }

        return listHead.next;

    }
} 

该解法存在的问题:
image


分析:
(1)取L1,L2中的各一个元素,相加
(2)得到的结果,如果大于10,取模之后存入新的链表,如果小于10,则直接存入新的链表
(3)得到的结果,如果大于10,位数加1,即下次(1)的结果再加1
(4)说明:/为除号 结果为整除后的个数,3 / 5 = 0.6 = 0; 12 / 5 = 2.4 = 2
%为取余 左边小于右边时,余数为左边,左边大于右边时,余数为剩余数 2 % 5 = 2; 17 % 5 = 2

代码:

解法二:

/**
 * 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) {
        int x = 0;  // 进位
        ListNode dummy = new ListNode(0);   // 哑节点
        ListNode node = dummy;
        
        while(l1 != null || l2 != null || x != 0) {
            int sum = x;    // 当前位的和
            if (l1 != null) {
                sum += l1.val;
                l1 = l1.next;
            }
            if (l2 != null) {
                sum += l2.val;
                l2 = l2.next;
            }
            node.next = new ListNode(sum % 10);
            x = sum / 10;
            node = node.next;
        }
        return dummy.next;
    }
}

链表面试题02. 回文链表

编写一个函数,检查输入的链表是否是回文的。

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

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

进阶:
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

代码:

class Solution {
    public boolean isPalindrome(ListNode head) {
        List<Integer> vals = new ArrayList<Integer>();

        // 将链表的值复制到数组中
        ListNode currentNode = head;
        while (currentNode != null) {
            vals.add(currentNode.val);
            currentNode = currentNode.next;
        }

        // 使用双指针判断是否回文
        int front = 0;
        int back = vals.size() - 1;
        while (front < back) {
            if (!vals.get(front).equals(vals.get(back))) {
                return false;
            }
            front++;
            back--;
        }
        return true;
    }
}

链表面试题03. 链表相交

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null

image


示例 1:

image

输入: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 个节点。


示例 2:

image

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。


示例 3:

image

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。


注意:

如果两个链表没有交点,返回 null 。
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。


解题思路

方法一:暴力法

思路:直接利用两个for循环,判断一个链表中的引用是否在另一个链表中存在

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
 
public class Solution {

    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        // 暴力法
        if(headA==null||headB==null){
            //若有一个链表为空,则不可能有相交
            return null;
        }
        ListNode curA=headA;
        ListNode curB=headB;
        for(;curA!=null;curA=curA.next){
            curB=headB;
            for(;curB!=null;curB=curB.next){
                if(curA==curB){
                    return curA;
                }
            }
        }
        return null;
    }
}

方法二:list集合

思路:将一个链表的所有引用存储在list中,再判断另一个链表中的引用是否存在与前一个链表相同的引用

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        //745 48.6
        if(headA==null||headB==null){
            //若有一个链表为空,则不可能有相交
            return null;
        }
        List<ListNode> list=new LinkedList<>();
        ListNode curA=headA;
        while(curA!=null){
            list.add(curA);
            curA=curA.next;
        }
        ListNode curB=headB;
        while(curB!=null){
            if(list.contains(curB)){
                return curB;
            }
            curB=curB.next;
        }
        return null;
    }

方法三:你变成我,我变成你,我们便相遇了。

你变成我,我变成你,我们便相遇了。
那么为什么能相遇呢?
设长链表A长度为LA,短链表长度LB;
由于速度相同,则在长链表A走完LA长度时,短链表B已经反过头在A上走了LA-LB的长度,剩余要走的长度为LA-(LA-LB) = LB;
之后长链表A要反过头在B上走,剩余要走的长度也是LB;
也就是说目前两个链表“对齐”了。因此,接下来遇到的第一个相同节点便是两个链表的交点。
那如果两个链表不存在交点呢?
答:这样的话第4步就会一直执行到两个链表的末尾,la,lb都为null,也会跳出循环,返回null。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode la = headA;
        ListNode lb = headB;
        while(la != lb){
            //到达链表末尾时,重新走另一条链表的路
            la = la == null ? headB : la.next;
            lb = lb == null ? headA : lb.next;
        }
        return la;
    }
}

链表面试题04. 环路检测

给定一个链表,如果它是有环链表,实现一个算法返回环路的开头节点。若环不存在,请返回 null。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

1

2

3


分析:
我们遍历链表中的每个节点,并将它记录下来;一旦遇到了此前遍历过的节点,就可以判定链表中存在环。借助哈希表可以很方便地实现


代码:

public class Solution {
    public ListNode detectCycle(ListNode head) {
        ListNode pos = head;
        Set<ListNode> visited = new HashSet<ListNode>();
        while (pos != null) {
            if (visited.contains(pos)) {
                return pos;
            } else {
                visited.add(pos);
            }
            pos = pos.next;
        }
        return null;
    }
}