前言
如果想进大厂工作,除了基础知识,项目经验,还有一些非常重要的能力需要锻炼,其中最重要的就是算法。算法题千千万,但是类型也就几十种,接下来的系列文章,我将面试中常见的算法题类型进行归纳。
链表面试题01. 链表求和
给定两个用链表表示的整数,每个节点包含一个数位。这些数位是反向存放的,也就是个位排在链表首部。编写函数对这两个整数求和,并用链表形式返回结果。
编写函数对这两个整数求和,并用链表形式返回结果。
代码:
解法一(该方法目前存在问题):
/**
* 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;
}
}
分析:
(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
示例 1:
输入: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:
输入: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:
输入: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 不作为参数进行传递,仅仅是为了标识链表的实际情况。
分析:
我们遍历链表中的每个节点,并将它记录下来;一旦遇到了此前遍历过的节点,就可以判定链表中存在环。借助哈希表可以很方便地实现
代码:
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;
}
}