第一关链表(白银)
链表高频题目讲解
1.输入两个链表,找出他们第一个公共节点
当拿到一个题目无从下手的时候,我们可以在自己脑海里面把知道的方法全部过一遍,然后在从前往后思考大概有哪些比较可行呢.
数据结构:数组、链表、队、栈、hash、集合、树、堆、图;
算法思想:查找、排序、双指针、递归、迭代、分治、贪心、回溯和动态规划
万变不离其中,在脑海中过一遍会更容易找到对应的解法
这一道题目的关键是如何找到公共点,这个公共点有这样一个特点,他是node(a1)和node(a2)的共同next,并且在他之后两个链表的节点就都相同了,
知道这个条件之后我们就开始思考有哪些解法
链表: 暴力破解,拿一个链表从头到位的节点从头到位去遍历另外一个链表从头到尾的节点,找到相同的节点就是公共节点,时间复杂度大
队: 不合适
栈: 将链表压栈,拿另外一个链表遍历,找到不同的为止
集合: 使用set集合
hash: 找相同
1.1 hash和集合还有栈
/**
* 方法1:通过Hash辅助查找
*
* @param pHead1
* @param pHead2
* @return
*/
public static ListNode findFirstCommonNodeByMap(ListNode pHead1, ListNode pHead2) {
if (pHead1==null || pHead2==null){
return null;
}
HashMap<ListNode,Integer> hashMap=new HashMap<>();
ListNode currentNode01=pHead1;
ListNode currentNode02=pHead2;
while (currentNode01!=null){
hashMap.put(currentNode01,null);
currentNode01=currentNode01.next;
}
while (currentNode02!=null){
if (hashMap.containsKey(currentNode02)){
return currentNode02;
}
currentNode02=currentNode02.next;
}
return null;
}
/**
* 方法2:通过集合来辅助查找
*
* @param headA
* @param headB
* @return
*/
public static ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB) {
if (headA==null || headB==null){
return null;
}
Set<ListNode> listNodeSet=new HashSet<>();
ListNode currentNode01=headA;
ListNode currentNode02=headB;
while (currentNode01!=null){
listNodeSet.add(currentNode01);
currentNode01=currentNode01.next;
}
while (currentNode02!=null){
if (listNodeSet.contains(currentNode02)){
return currentNode02;
}
currentNode02=currentNode02.next;
}
return null;
}
/**
* 方法3:通过栈
*/
public static ListNode findFirstCommonNodeByStack(ListNode headA, ListNode headB) {
if (headA==null || headB==null){
return null;
}
Stack<ListNode> nodeStack01=new Stack<>();
Stack<ListNode> nodeStack02=new Stack<>();
ListNode currentNode01=headA;
ListNode currentNode02=headB;
while (currentNode01!=null){
nodeStack01.add(currentNode01);
currentNode01=currentNode01.next;
}
while (currentNode02!=null){
nodeStack02.add(currentNode02);
currentNode02=currentNode02.next;
}
ListNode preNode=null;
while (nodeStack01.size()>0 && nodeStack02.size()>0){
if (nodeStack01.peek()==nodeStack02.peek()){
preNode=nodeStack01.pop();
nodeStack02.pop();
}else {
break;
}
}
return preNode;
}
1.2 序列拼接和差值实现
序列拼接:两边找公共点难点的就是两个链表的长度不一致 无法进行一一比较 所以 我们就要把两个链表拼接起来 形成两个长度一样的链表 这样子然后再起遍历取公共点就可以了
差值实现:同理这里的差值实现就是把两个长度不等的链表先把长的截取得和短的一样,进行遍历比较即可
/**
* 方法4:通过序列拼接
*/
public static ListNode findFirstCommonNodeByCombine(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;
}
/**
* 方法5:通过差值来实现
*
* @param pHead1
* @param pHead2
* @return
*/
public static ListNode findFirstCommonNodeBySub(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;
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;
}
2.判断链表是否为回文序列
找回文序列有很多方法,快慢指针,栈,递归
/**
* 方法1:通过双指针的方式来判断
*
* @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;
}
/**
* 方法2:全部压栈
*
* @param head
* @return
*/
public static boolean isPalindromeByAllStack(ListNode head) {
if (head==null){
return false;
}
ListNode temp = head;
Stack<Integer> stack = new Stack();
//把链表节点的值存放到栈中
while (temp != null) {
stack.push(temp.val);
temp = temp.next;
}
//然后再出栈
while (head != null) {
if (head.val != stack.pop()) {
return false;
}
head = head.next;
}
return true;
}
/**
* 方法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;
}
/**
* 方法4:通过递归来实现
*/
static ListNode temp;
public static boolean isPalindromeByRe(ListNode head) {
temp=head;
boolean check = check(head);
return check;
}
private static boolean check(ListNode head) {
if (head==null)
return true;
boolean check=check(head.next) && (head.val==temp.val);
temp=temp.next;
return check;
}
3.合并有序链表
/**
* 方法1:面试时就能写出来的方法
*
* @param list1
* @param list2
* @return
*/
public static ListNode mergeTwoLists(ListNode list1, ListNode list2) {
// write code here
ListNode newHead = new ListNode(-1);
ListNode res = newHead;
while (list1 != null || list2 != null) {
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;
} 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;
}
/**
* 方法2:比方法1更加精简的实现方法
*
* @param l1
* @param l2
* @return
*/
public static ListNode mergeTwoListsMoreSimple(ListNode l1, ListNode l2) {
ListNode prehead = new ListNode(-1);
ListNode prev = prehead;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
prev.next = l1;
l1 = l1.next;
} else {
prev.next = l2;
l2 = l2.next;
}
prev = prev.next;
}
// 最多只有一个还未被合并完,直接接上去就行了,这是链表合并比数组合并方便的地方
prev.next = l1 == null ? l2 : l1;
//链表都是对象,存的都是地址,所以本质上还是一样的
return prehead.next;
}
/**
* 方法3:通过递归方式来实现
*
* @param l1
* @param l2
* @return
*/
public static ListNode mergeTwoListsByRe(ListNode l1, ListNode l2) {
if (l1 == null) {
return l2;
}
if (l2 == null) {
return l1;
}
if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
return l1;
} else {
l2.next = mergeTwoLists(l1, l2.next);
return l2;
}
}
/**
* 合并K个链表
*
* @param lists
* @return
*/
public static ListNode mergeKLists(ListNode[] lists) {
ListNode res = null;
for (ListNode list : lists) {
res = mergeTwoListsMoreSimple(res, list);
}
return res;
}
/**
* 给你两个链表 list1 和 list2,它们包含的元素分别为 n 个和 m 个。请你将 list1 中下标从a到b的节点删除,并将list2 接在被删除节点的位置。
*
* @param l1
* @param l2
* @return
*/
public static ListNode mergeInBetween(ListNode l1,int a,int b, ListNode l2) {
ListNode pre1=l1,post1=l1,post2=l2;
int i=0,j=0;
while(pre1!=null && post2!=null && j<b){
if (i!=a-1){
pre1=pre1.next;
i++;
}
if (j!=b){
post1=post1.next;
j++;
}
}
//因为上面的循环条件导致少了一次,现在补上去
post1=post1.next;
while(post2.next!=null){
post2=post2.next;
}
pre1.next=l2;
post2.next=post1;
return l1;
}
4.双指针专题
4.1 寻找链表倒数第K个结点
public static ListNode getKthFromEnd(ListNode head, int k) {
ListNode slow=head;
ListNode fast=head;
while(fast != null && k>0){
fast=fast.next;
k--;
}
while(fast!=null){
slow=slow.next;
fast=fast.next;
}
return slow;
}
4.2 寻找链表中间结点
public static ListNode middleNode(ListNode head) {
ListNode slow=head;
ListNode fast=head;
while (fast!=null && fast.next!=null){
slow=slow.next;
fast=fast.next.next;
}
return slow;
}
4.3 旋转链表
public static ListNode rotateRight(ListNode head, int k) {
if (head == null || k == 0) {
return head;
}
ListNode temp = head;
ListNode fast = head;
ListNode slow = head;
int len = 0;
while (head != null) {
head = head.next;
len++;
}
if (k % len == 0) {
return temp;
}
while ((k % len) > 0) {
k--;
fast = fast.next;
}
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
//链表从始至终都是对象,改变的只是链表的位置,只要没对链表进行操作,链表始终是原来的链表
ListNode res = slow.next;
slow.next = null;
fast.next = temp;
return res;
}
5.删除链表元素专题
5.1 删除给定的节点
/**
* 删除特定值的结点
*
* @param head
* @param val
* @return
*/
public static ListNode removeElements(ListNode head, int val) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode temp = dummyHead;
while (temp.next != null) {
if (temp.next.val == val) {
temp.next = temp.next.next;
} else {
temp = temp.next;
}
}
return dummyHead.next;
}
5.2 找链表的倒数第K个元素开始的链表
/**
* 找链表的倒数第K个元素开始的链表
*
* @param head
* @param k
* @return
*/
public static ListNode getKthFromEnd(ListNode head, int k) {
ListNode pre = head;
ListNode curr = head;
while (curr != null) {
curr = curr.next;
if (k > 0)
k--;
else {
pre = pre.next;
}
}
return pre;
}
/**
* 方法1:删除倒数第N个结点
*
* @param head
* @param n
* @return
*/
public static ListNode removeNthFromEndByLength(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
int length = getLength(head);
ListNode cur = dummy;
for (int i = 1; i < length - n + 1; ++i) {
cur = cur.next;
}
cur.next = cur.next.next;
ListNode ans = dummy.next;
return ans;
}
/**
* 方法2:通过栈实现
*
* @param head
* @param n
* @return
*/
public static ListNode removeNthFromEndByStack(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
Deque<ListNode> stack = new LinkedList<ListNode>();
ListNode cur = dummy;
while (cur != null) {
stack.push(cur);
cur = cur.next;
}
for (int i = 0; i < n; ++i) {
stack.pop();
}
ListNode prev = stack.peek();
prev.next = prev.next.next;
ListNode ans = dummy.next;
return ans;
}
/**
* 方法3:通过双指针
*
* @param head
* @param n
* @return
*/
public static ListNode removeNthFromEndByTwoPoints(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;
}
5.3 重复元素保留一个和重复元素都不要
/**
* 重复元素保留一个
*
* @param head
* @return
*/
public static ListNode deleteDuplicate(ListNode head) {
if (head == null) {
return head;
}
ListNode cur = head;
while (cur.next != null) {
if (cur.val == cur.next.val) {
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
return head;
}
/**
* 重复元素都不要
*
* @param head
* @return
*/
public static ListNode deleteDuplicates(ListNode head) {
if (head == null) {
return head;
}
ListNode dummy = new ListNode(0);
dummy.next = 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;
}