本文用到的链表的定义
class ListNode {
public int val;
public ListNode next;
ListNode(int x) {
val = x;
next = null;
}
}
题目一:两个链表第一个公共子节点
这是一道经典的链表问题,剑指offer52 先看一下题目:输入两个链表,找出它们的第一个公共节点。例如下面的两个链表:
两个链表的头结点都是已知的,相交之后成为一个单链表,但是相交的位置未知,并且相交之前的结点数也是未知的,请设计算法找到两个链表的合并点。
输入: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 个节点。
没有思路时该怎么解题?
将常用数据结构和常用算法思想都想一遍,看看哪些能解决问题。
常用的数据结构有数组、链表、队、栈、Hash、集合、树、堆。常用的算法思想有查找、排序、双指针、递归、迭代、分治、贪心、回溯和动态规划等等。
hash和集合
先将一个链表元素全部存到Map里,然后一边遍历第二个链表,一边检测Hash中是否存在当前结点,如果有交点,那么一定能检测出来。 对于本题,如果使用集合更适合,而且代码也更简洁。
public ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB) {
Set<ListNode> set = new HashSet<>();
while (headA != null) {
set.add(headA);
headA = headA.next;
}
while (headB != null) {
if (set.contains(headB))
return headB;
headB = headB.next;
}
return null;
}
使用栈
这里需要使用两个栈,分别将两个链表的结点入两个栈,然后分别出栈,如果相等就继续出栈,一直找到最晚出栈的那一组。
import java.util.Stack;
public ListNode findFirstCommonNodeByStack(ListNode headA, ListNode headB) {
Stack<ListNode> stackA=new Stack();
Stack<ListNode> stackB=new Stack();
while(headA!=null){
stackA.push(headA);
headA=headA.next;
}
while(headB!=null){
stackB.push(headB);
headB=headB.next;
}
ListNode preNode=null;
while(stackB.size()>0 && stackA.size()>0){
if(stackA.peek()==stackB.peek()){
preNode=stackA.pop();
stackB.pop();
}else{
break;
}
}
return preNode;
}
通过拼接辅助查找
public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2) {
if(pHead1==null || pHead2==null){
return null;
}
ListNode p1=pHead1;
ListNode p2=pHead2;
while(p1!=p2){
p1=p1.next;
p2=p2.next;
if(p1!=p2){
//一个链表访问完了就跳到另外一个链表继续访问
if(p1==null){
p1=pHead2;
}
if(p2==null){
p2=pHead1;
}
}
}
return p1;
}
差和双指针
首先第一轮 遍历两个链表 分别获取其长度La、Lb,第二轮 先让长的链表先走 |La-Lb| 让两个未走的长度相同,再一个一个对比。
public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2) {
if(pHead1==null || pHead2==null){
return null;
}
ListNode current1=pHead1;
ListNode current2=pHead2;
int l1=0,l2=0;
//分别统计两个链表的长度
while(current1!=null){
current1=current1.next;
l1++;
}
while(current2!=null){
current2=current2.next;
l2++;
}
current1=pHead1;
current2=pHead2;
int sub=l1>l2?l1-l2:l2-l1;
//长的先走sub步
if(l1>l2){
int a=0;
while(a<sub){
current1=current1.next;
a++;
}
}
if(l1<l2){
int a=0;
while(a<sub){
current2=current2.next;
a++;
}
}
//同时遍历两个链表
while(current2!=current1){
current2=current2.next;
current1=current1.next;
}
return current1;
}
题目二:判断链表是否为回文序列
输入:head = [1,2,2,1]
输出:true
与上一个题相同,先考虑常见的数据结构和算法思想,是否可以解决。
- 数组:将链表中元素取出,放入数组中,使用下标从两头向中间遍历。
- 栈:第一轮遍历,将链表中节点按顺序全部压入栈,第二轮遍历,出栈并与链表对比。
- 优化2,先遍历第一遍,得到总长度。之后一边遍历链表,一边压栈。到达链表长度一半后就不再压栈,而是一边出栈,一边遍历,一边比较,只要有一个不相等,就不是回文链表。这样可以节省一半的空间。
- 优化3,第一轮遍历,压入栈时顺便记录长度,第二轮出栈对比只对比一半。
- 链表:创建一个新的链表,将原链表中的节点按顺序存入新链表,这样新链表就是逆向的原链表(和栈差不多),之后再一个一个对比。
- 优化5,第一轮遍历长度,第二轮将原链表中的节点按顺序存入新链表到一半时,就可以对比了。
- 优化6,使用双指针中的快慢指针,快指针fast一次走两步,慢指针slow一次走一步,当fast到达链表尾部时,slow到达链表中间,接下来可以从头开始逆序一半的元素,或者从slow开始逆序一半的元素,再对比。
/**
* 3:只将一半的数据压栈
*
* @param head
* @return
*/
public static boolean isPalindromeByHalfStack(ListNode head) {
if (head == null)
return true;
ListNode temp = head;
Stack<Integer> stack = new Stack();
//链表的长度
int len = 0;
//把链表节点的值存放到栈中
while (temp != null) {
stack.push(temp.val);
temp = temp.next;
len++;
}
//len长度除以2
len >>= 1;
//然后再出栈
while (len-- >= 0) {
if (head.val != stack.pop())
return false;
head = head.next;
}
return true;
}
/**
* 7:通过双指针的快慢指针方式来判断
*
* @param head
* @return
*/
public static boolean isPalindromeByTwoPoints(ListNode head) {
if (head == null || head.next == null) {
return true;
}
ListNode slow = head, fast = head;
ListNode pre = head, prepre = null;
while (fast != null && fast.next != null) {
pre = slow;
slow = slow.next;
fast = fast.next.next;
pre.next = prepre;
prepre = pre;
}
if (fast != null) {
slow = slow.next;
}
while (pre != null && slow != null) {
if (pre.val != slow.val) {
return false;
}
pre = pre.next;
slow = slow.next;
}
return true;
}
题目三:合并两个有序链表
LeetCode21 将两个升序链表合并为一个新的升序链表并返回,新链表是通过拼接给定的两个链表的所有节点组成的。
创建一个新链表,两个链表一个一个取出节点对比小的节点,放入新链表中。
public ListNode mergeTwoLists (ListNode list1, ListNode list2) {
ListNode newHead=new ListNode(-1);
ListNode res=newHead;
while(list1!=null||list2!=null){
//情况1:都不为空的情况
if(list1!=null&&list2!=null){
if(list1.val<list2.val){
newHead.next=list1;
list1=list1.next;
}else if(list1.val>list2.val){
newHead.next=list2;
list2=list2.next;
}else{ //相等的情况,分别接两个链
newHead.next=list2;
list2=list2.next;
newHead=newHead.next;
newHead.next=list1;
list1=list1.next;
}
newHead=newHead.next;
//情况2:假如还有链表一个不为空
}else if(list1!=null&&list2==null){
newHead.next=list1;
list1=list1.next;
newHead=newHead.next;
}else if(list1==null&&list2!=null){
newHead.next=list2;
list2=list2.next;
newHead=newHead.next;
}
}
return res.next;
}
优化
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode prehead = new ListNode(-1);
ListNode prev = prehead;
while (list1 != null && list2 != null) {
if (list1.val <= list2.val) {
prev.next = list1;
list1 = list1.next;
} else {
prev.next = list2;
list2 = list2.next;
}
prev = prev.next;
}
// 最多只有一个还未被合并完,直接接上去就行了,这是链表合并比数组合并方便的地方
prev.next = list1 == null ? list2 : list1;
return prehead.next;
}
题目四:删除特定结点
LeetCode 203:给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回新的头节点 。
示例1:
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
我们删除某个节点需要知道该节点的前一个结点preNode使其preNode.next = preNode.next.next
所以该题我们可以再创建一个节点dummyHead使其next指向head,当从dummyHead节点开始遍历当其下一个节点的val与要删除的val相同时,就可以删掉下一个节点了。
public ListNode removeElements(ListNode head, int val) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode cur = dummyHead;
while (cur.next != null) {
if (cur.next.val == val) {
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
return dummyHead.next;
}
题目五:删除倒数第n个结点
LeetCode19题要求:给你一个链表,删除链表的倒数第n个结点,并且返回链表的头结点。
示例1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
第一种方法:先遍历一遍链表,找到链表总长度L,然后重新遍历,位置L-N+1的元素就是我们要删的。
第二种方法:将元素全部压栈,然后弹出第N个的时候就是我们要删除的节点。
第三种方法:使用双指针来寻找倒数第K个节点,定义两个指针first,second,firest先走k步既走到位置为k+1的节点,此时firest和second相差k,二者同时继续遍历,当frest走到链表尾部的null时second正好是倒数第k个节点。
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next=head;
ListNode first = head;
ListNode second = dummy;
for (int i = 0; i < n; ++i) {
first = first.next;
}
while (first != null) {
first = first.next;
second = second.next;
}
second.next = second.next.next;
ListNode ans = dummy.next;
return ans;
}
题目六:删除重复元素
LeetCode82:这个题目的要求是重复的元素都不要了
示例1:
输入:head = [1,2,3,3,4,4,5]
输出:[1,2,5]
public ListNode deleteDuplicates(ListNode head) {
if (head == null) {
return head;
}
ListNode dummy = new ListNode(0, head);
ListNode cur = dummy;
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 {
cur = cur.next;
}
}
return dummy.next;
}