前言:
- 第一关是链表的基本问题,第二关是链表反转以及拓展问题。
- 主要内容是:链表高频面试算法题
重点:
- 上一篇针对链表分别学了一个创建还有增删改查,接下来的重点是开拓思路/思维,思考解题思想,以及可以自己写出来。
要点:
- 遇到算法时,思路可以是怎么样的,总结出自己的处理方式,并且小结上手代码后的经验
小结:
1、两个链表第一个公共子节点代码:
import java.util.HashSet;
import java.util.Set;
import java.util.Stack;
@SuppressWarnings("all")
public class FindFirstCommentNode_ {
public static void main(String[] args) {
LinkNode headA = new LinkNode(1);
LinkNode B = new LinkNode(2);
LinkNode C = new LinkNode(2);
LinkNode D = new LinkNode(2);
LinkNode E = new LinkNode(2);
headA.next = B;
B.next = C;
C.next = D;
D.next = E;
hi(headA);
System.out.println(headA);
}
public static void hi(LinkNode headA){
headA = headA.next;
}
//用HashMap找第一个公共节点
public static LinkNode userHash(LinkNode headA,LinkNode headB){
//注意,因为后续不再需要返回(/使用)head,所以不用担心狗熊掰棒子的问题
//先把链表A存到hashset里面
Set<LinkNode> linkNodesA = new HashSet<>();
while (headA != null){
linkNodesA.add(headA);
headA = headA.next;
}
//依次判断链表B是否由于A相同的节点
while (headB != null){
if(linkNodesA.contains(headB)){
return headB;
}
headB = headB.next;
}
return null;
}
//用栈找第一个公共节点
public static LinkNode userStack(LinkNode headA,LinkNode headB){
Stack<LinkNode> stackA = new Stack<>();
Stack<LinkNode> stackB = new Stack<>();
while (headA != null){
stackA.push(headA);
headA = headA.next;
}
while (headB != null){
stackB.push(headB);
headB = headB.next;
}
LinkNode temp = null;
while (stackA.size() > 0 && stackB.size() > 0){
if(stackA.peek() == stackB.peek()){
temp = stackA.pop();
stackB.pop();
}else {
//从这以下,反回结果都一样,所以可以简化
break;
}
}
return temp;
}
//通过拼接的方式找第一个公共节点
public LinkNode useJoint(LinkNode headA,LinkNode headB){
if(headA == null || headB == null){
return null;
}
LinkNode flagA = headA;
LinkNode flagB = headB;
while (flagA != null && flagB != null){
flagA = flagA.next;
flagB = flagB.next;
}
//先拼接短的,再拼接长的
if(flagA == null){
flagA = headB;
while (flagB != null){
flagB = flagB.next;
flagA = flagA.next;
}
flagB = headA;
}
if(flagB == null){
flagB = headA;
while (flagA != null){
flagA = flagA.next;
flagB = flagB.next;
}
flagA = headB;
}
//已经等长了,一直判断到结束即可
//这里可以简化,因为AB都为null时,自然要退出循环,且当AB
//相同(不为空)时,也要退出循环,所以可以把这两种情况写在一起
//所以可以知道,分析while循环的时候,需要详细的把什么时候开始,和什么时候结束搞明白
while (flagA != flagB){
flagA = flagA.next;
flagB = flagB.next;
}
return flagA;
}
//通过求差,找第一个公共节点(涉及双指针)
public LinkNode useDiffe(LinkNode headA,LinkNode headB){
int lengthA = 0;
int lengthB = 0;
LinkNode flagA = headA;
LinkNode flagB = headB;
//求两链表长度
while(flagA != null) {
flagA = flagA.next;
lengthA++;
}
while(flagB != null) {
flagB = flagB.next;
lengthB++;
}
int dif = lengthA > lengthB ? lengthA -lengthB : lengthB - lengthA;
//求让长的先走
if(lengthA > lengthB){
while (dif > 0){
headA = headA.next;
dif--;
}
}else {
while (dif > 0){
headB = headB.next;
dif--;
}
}
//一起走
while (headA != headB){
headA = headA.next;
headB = headB.next;
}
return headA;
}
}
class LinkNode{
public int val;
public LinkNode next;
public LinkNode(int val) {
this.val = val;
}
@Override
public String toString() {
LinkNode i = this;
String s = "";
while (i != null){
s += i.val + "";
i = i.next;
}
return s;
}
}
2、删除链表元素代码:
2.1、删除倒数元素代码:
import java.util.Stack;
//删除倒数第K个元素
//只展示了使用栈实现的方式
@SuppressWarnings("all")
public class DeleteReciprocalKNode {
//使用栈
public LinkNode useStack(LinkNode head, int k){
if(head ==null){
return head;
}
Stack<LinkNode> linkNodes = new Stack<>();
LinkNode cur = head;
LinkNode dummy = new LinkNode(0);
dummy.next = head;
//添加到栈中
while (dummy != null){
linkNodes.push(dummy);
dummy= dummy.next;
}
//判断长度
if((k > linkNodes.size() - 1) || k < 1){
return head;
}
//开始删除
while (k > 0){
linkNodes.pop();
k--;
}
LinkNode node = linkNodes.pop();
node.next = node.next.next;
return node;
}
static class LinkNode{
public int val;
public LinkNode next;
public LinkNode(int val) {
this.val = val;
}
@Override
public String toString() {
LinkNode i = this;
String s = "";
while (i != null){
s += i.val + "";
i = i.next;
}
return s;
}
}
}
2.2、删除重复元素代码:
public class DeleteDuplateNode_ {
/**
* 重复元素保留一个
*
* @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;
}
static class LinkNode{
public int val;
public LinkNode next;
public LinkNode(int val) {
this.val = val;
}
@Override
public String toString() {
LinkNode i = this;
String s = "";
while (i != null){
s += i.val + "";
i = i.next;
}
return s;
}
}
}
问答:
1、为什么老师的代码使用到了静态内部类?内部类和静态内部类的区别是?
答:因为将节点类放在内部类的位置后,如果其他的静态方法要创建这节点对象,那就得将其设置为静态的才不会报错。另外,静态内部类不依赖与引用。
其他/心得:
1、确定好处理的方式后,在思路分析时,可以考虑从整体或细节方面分析,比如:整体分析,就是较整体的角度思考代码的运行流程。整体分析的好处就是代码看起来更简洁,相对的,细节分析的好处就是,运行的时间复杂度会好一些。具体代码的例子就是,在通过拼接的方式来实现寻找两个链表第一个公共子节点代码中,可以明显看出思路考虑的方向;
2、这些高频题目,实际上最后是会落实到增删改查的,所以这个基础需要记得;
3、分析while循环的时候,可以具体的分析把什么时候开始,和什么时候结束的情况列举出来,这样可以简写些自己一时间看不出来的代码。