面试题 02.07. 链表相交 - 力扣(LeetCode)
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
图示两个链表在节点 c1
开始相交**:**
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
public ListNode getIntersectionNode(ListNode headA, ListNode headB)
{
HashSet<ListNode> hashSet = new HashSet<>();
while (headA != null)
{
hashSet.add(headA);
headA = headA.next;
}
while (headB != null)
{
if(hashSet.contains(headB))
return headB;
headB = headB.next;
}
return null;
}
public static ListNode getIntersectionNode(ListNode headA, ListNode headB)
{
ArrayDeque<ListNode> stackA = new ArrayDeque<>();
ArrayDeque<ListNode> stackB = new ArrayDeque<>();
//两个链表分别入不同的栈
while (headA != null)
{
stackA.push(headA);
headA = headA.next;
}
while (headB != null)
{
stackB.push(headB);
headB = headB.next;
}
ListNode intersectionNode = null; //要跟着两链表相同的结点
//从后往前理解(栈先进后出的特性)
while (!stackA.isEmpty() && !stackB.isEmpty())
{
if (stackA.peek().val == stackB.peek().val)
{
intersectionNode = stackA.pop();
stackB.pop();
}
else
break; //两链表开始分叉,intersectionNode也指向了第一个交汇结点
}
return intersectionNode;
public ListNode getIntersectionNode(ListNode headA, ListNode headB)
{
//排除掉不好统一处理的空链表情况
if(headA == null || headB == null)
return null;
ListNode p = headA;
ListNode q = headB;
while(p != q)
{
p = p.next;
q = q.next;
if(p != q)
{
//自己的链表遍历完了就跳到另一个链表一直遍历下去,直到相遇,或者都等于null
if(p == null)
p = headB;
if(q == null)
q = headA;
}
}
//return得放在外面,否则编译报错:无返回语句
return p;
}
或称快慢指针。长链表的指针比短链表的指针先走|len1 - len2|步,然后同步移动,指向同一结点时返回答案
public ListNode getIntersectionNode(ListNode headA, ListNode headB)
{
if(headA == null || headB == null)
return null;
//获取headA的表长
ListNode p = headA;
int len_A = 0;
while (p != null)
{
len_A++;
p = p.next;
}
//获取headB的表长
ListNode q = headB;
int len_B = 0;
while (q != null)
{
len_B++;
q = q.next;
}
int diff = Math.abs(len_A - len_B);
if(len_A < len_B) //headB先走
{
while (diff != 0)
{
headB = headB.next;
diff--;
}
}
else if(len_A > len_B) //headA先走
{
while (diff != 0)
{
headA = headA.next;
diff--;
}
}
while (headA != headB)
{
headA = headA.next;
headB = headB.next;
}
return headA;
}
234. 回文链表 - 力扣(LeetCode)
给你一个单链表的头节点 head
,请你判断该链表是否为回文链表。如果是,返回 true
;否则,返回 false
。
示例 1:
输入:head = [1,2,2,1]
输出:true
虽然这么做简单易懂,但面试手撕算法题时不推荐,因为会被视为逃避链表,失去了考验价值
public boolean isPalindrome(ListNode head)
{
//将每一个结点的值复制到ArrayList。
ArrayList<Integer> vals = new ArrayList<>();
while(head != null)
{
vals.add(head.val);
head = head.next;
}
//进行回文判断
int left = 0;
int right = vals.size() - 1;
while(left < right)
{
if(vals.get(left).equals(vals.get(right)) == false)
return false;
left++;
right--;
}
return true;
}
//寻找中间结点。若链表有奇数个节点,则中间的节点应该看作是前半部分。
private ListNode endOfFirstHalf(ListNode head)
{
ListNode fast = head;
ListNode slow = head;
while(fast.next != null && fast.next.next != null)
{
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
//反转链表
private ListNode reverseList(ListNode head)
{
ListNode pre = null;
ListNode suc = head;
while(head != null)
{
suc = head.next;
head.next = pre;
pre = head;
head = suc;
}
return pre;
}
public boolean isPalindrome(ListNode head)
{
if(head == null)
return true;
// 找到前半部分链表的尾节点并反转后半部分链表
ListNode firstHalfEnd = endOfFirstHalf(head);
ListNode secondHalfStart = reverseList(firstHalfEnd.next);
//判断是否是回文链表
ListNode p = head;
ListNode q = secondHalfStart;
boolean ans = true;
while(q != null)
{
if(p.val != q.val)
{
ans = false;
break;
}
p = p.next;
q = q.next;
}
//恢复后半部分链表
firstHalfEnd.next = reverseList(secondHalfStart);
//返回判断结果
return ans;
}
public static boolean isPalindrome(ListNode head)
{
ArrayDeque<ListNode> stack = new ArrayDeque<>();
ListNode t = head;
while (t != null)
{
stack.push(t);
t = t.next;
}
while (head != null)
{
if(stack.pop().val != head.val)
return false;
head = head.next;
}
return true;
}
21. 合并两个有序链表 - 力扣(LeetCode)
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
示例 1:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
public ListNode mergeTwoLists(ListNode list1, ListNode list2)
{
if(list1 == null)
return list2;
if(list2 == null)
return list1;
ListNode p = list1; //新的引用
ListNode q = list2;
ListNode head = new ListNode(0 , null); //新链表的头结点
ListNode r = head;
while(p != null && q != null)
{
//一次只处理一个结点
if(p.val <= q.val)
{
r.next = p;
p = p.next;
}
else
{
r.next = q;
q = q.next;
}
r = r.next; //先链接,后更新
}
if(p == null)
r.next = q;
if(q == null)
r.next = p;
return head.next;
}
在3.1的基础上
public ListNode mergeKLists(ListNode[] lists)
{
ListNode res = null;
//逐一链接
for (ListNode list: lists)
res = mergeTwoLists(res,lists);
return res;
}
1669. 合并两个链表 - 力扣(LeetCode)
给你两个链表 list1
和 list2
,它们包含的元素分别为 n
个和 m
个。
请你将 list1
中下标从 a
到 b
的全部节点都删除,并将list2
接在被删除节点的位置。
下图中蓝色边和节点展示了操作后的结果:
请你返回结果链表的头指针。
示例 1:
输入:list1 = [0,1,2,3,4,5], a = 3, b = 4, list2 = [1000000,1000001,1000002]
输出:[0,1,2,1000000,1000001,1000002,5]
解释:我们删除 list1 中下标为 3 和 4 的两个节点,并将 list2 接在该位置。上图中蓝色的边和节点为答案链表。
public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
ListNode pre = list1;
ListNode post = list1;
int i = 0;
int j = 0;
//定位待删除部分的前驱和后继
while (i < a - 1) { //用<号,i=a-2时最后一次进入循环,退出循环时pre刚好指向下标为a-1的结点
pre = pre.next;
i++;
}
while (j < b + 1) {
post = post.next;
j++;
}
//定位list2的尾结点
ListNode r = list2;
while (r.next != null)
r = r.next;
//连接
pre.next = list2;
r.next = post;
return list1;
}
876. 链表的中间结点 - 力扣(LeetCode)
给你单链表的头结点 head
,请你找出并返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例 1:
输入:head = [1,2,3,4,5]
输出:[3,4,5]
解释:链表只有一个中间结点,值为 3 。
public ListNode middleNode(ListNode head)
{
ListNode p = head;
ListNode q = head;
//p每次走两步,q每次走一步
//p走到最后的NULL时,q恰好到中间
//偶数个结点的话,q则指向第二个中间结点
while(p !=null && p.next != null)
{
p = p.next.next;
q = q.next;
}
return q;
}
第一次遍历得到链表长度,第二次遍历找倒数K个元素
两种模板
public ListNode getNthFromEnd(ListNode head, int n)
{
ListNode fast = head;
ListNode slow = head;
while (fast != null)
{
if(n <= 0)
slow = slow.next;
fast = fast.next;
n--;
}
return slow;
}
public ListNode getKthFromEnd(ListNode head, int k)
{
ListNode fast = head;
ListNode slow = head;
while(fast != null && k > 0)
{
fast = fast.next;
k--;
}
while(fast != null)
{
fast = fast.next;
slow = slow.next;
}
return slow;
}
61. 旋转链表 - 力扣(LeetCode)
给你一个链表的头节点 head
,旋转链表,将链表每个节点向右移动 k
个位置。
示例 1:
输入:head = [1,2,3,4,5], k = 2
输出:[4,5,1,2,3]
示例 2:
输入:head = [0,1,2], k = 4
输出:[2,0,1]
public ListNode rotateRight(ListNode head, int k)
{
if (head == null || k == 0)
return head;
//获取链表长度
ListNode t = head;
int length = 0;
while (t != null)
{
length++;
t = t.next;
}
int K = k % length; //最小旋转次数
if(K == 0)
return head;
//寻找倒数第K个结点的前驱
ListNode fast = head;
ListNode slow = head; //倒数第K个结点的前驱
while (fast.next != null) //fast需要停留在最后一个结点
{
if(K <= 0)
slow = slow.next;
fast = fast.next;
K--;
}
ListNode res = slow.next; //旋转过后的链表的头结点为倒数第K个结点
slow.next = null; //断开前驱与倒数第K个结点的链接,避免成环。即slow作为新链表的尾结点
fast.next = head; //原尾结点连接原头结点,"旋转"完成
return res;
}
203. 移除链表元素 - 力扣(LeetCode)
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
示例 1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution
{
public ListNode removeElements(ListNode head, int val)
{
//val出现在头结点
while(head != null && head.val == val)
head = head.next;
if(head == null)
return head; //直接解决全部都是val的情况
ListNode p = head;
while(p != null && p.next != null) //常规情况
{
if(p.next.val == val)
{
p.next = p.next.next;
}
else
{
p = p.next;
}
}
return head;
}
}
public ListNode removeElements(ListNode head, int val)
{
ListNode dummyNode = new ListNode(-1);
dummyNode.next = head; //为将头结点当作普通结点处理
ListNode cur = dummyNode;
while (cur.next != null) //如果尾结点也是要删除的结点,那它也能被“跳过”。要时刻谨记.next存的是下一结点的地址,不是一根线
{
if(cur.next.val == val)
cur.next = cur.next.next; //cur所指结点的后继变了,且cur仍然指向当前结点
else
cur = cur.next; //下一个结点的值(终于)不重复时,cur才指向下一个结点
}
return dummyNode.next;
}
19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
struct ListNode* removeNthFromEnd(struct ListNode* head, int n)
{
//计算链表长度
struct ListNode* fast = head;
struct ListNode* slow = head;
while(fast)
{
if(n < 0)
slow = slow->next; //慢指针开始与快指针同步移动
fast = fast->next;
n--;
}
if(n == 0) //要删除的是头结点,返回其余结点
return head->next;
slow->next = slow ->next->next; //删除指定结点
return head;
}
public ListNode removeNthFromEnd(ListNode head, int n)
{
//计算链表长度
ListNode fast = head;
ListNode slow = head;
while(fast != null)
{
if(n < 0) //慢指针开始与快指针同步移动,最终slow会指向待删除结点的前驱
slow = slow.next;
fast = fast.next;
n--;
}
if(n == 0) //若删除的是头结点,则返回其余结点
return head.next;
slow.next = slow.next.next; //删除指定结点
return head;
}
83. 删除排序链表中的重复元素 - 力扣(LeetCode)
给定一个已排序的链表的头 head
, 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
示例 1:
输入:head = [1,1,2]
输出:[1,2]
示例 2:
输入:head = [1,1,2,3,3]
输出:[1,2,3]
public ListNode deleteDuplicates(ListNode head)
{
if (head == null)
return null;
ListNode cur = head;
while (cur.next != null)
{
if(cur.val == cur.next.val)
cur.next = cur.next.next;
else
cur = cur.next;
}
return head;
}
82. 删除排序链表中的重复元素 II - 力扣(LeetCode)
给定一个已排序的链表的头 head
, 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。
示例 1:
输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]
示例 2:
输入:head = [1,1,1,2,3]
输出:[2,3]
public ListNode deleteDuplicates(ListNode head)
{
ListNode dummyNode = new ListNode(0, head);
ListNode cur = dummyNode;
while (cur.next != null && cur.next.next != null)
{
if (cur.next.val == cur.next.next.val)
{
//用一个小循环删除这部分重复元素
int x = cur.next.val;
while (cur.next != null && cur.next.val == x)
{
cur.next = cur.next.next; //把指针域也画出来才容易理解
}
}
else //如if被执行,则cur不会往后移动,这使得两个while可以合力一口气删完如[2,2,3,3,5,5,5]的重复部分,然后cur才需要往后移动
cur = cur.next;
}
return dummyNode.next;
}