《算法通关村第一关——链表青铜挑战笔记》
首先,我们要理解两个单链表第一个公共子节点有什么特点,图下图所示。
特点一:两个链表的第一个公共子节点是 第一个相同的元素,提到查找相同元素,第一想到的解决方法自然就是利用 Hash表来解决
ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//采用Hash
HashSet<ListNode> hashset = new HashSet<>();
ListNode listNode = headA;
while (listNode != null) {
hashset.add(listNode);
listNode = listNode.next;
}
listNode = headB;
while (listNode != null) {
if (!hashset.add(listNode)) {
return listNode;
}
listNode = listNode.next;
}
return null;
}
特点二:从右往左看,第一个公共子节点又是最后一个相同的元素,我们只需要将两个链表逆序对比,找到第一个不同的元素,并返回上一个元素即可,将链表逆序可以利用栈的先进后出的特性
ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//采用栈
//声明两个栈,存储链表将其逆序
Stack <ListNode>stackA = new Stack();
Stack <ListNode>stackB = new Stack();
ListNode listNode=headA;
//分别存储两个链表
while (listNode!=null){
stackA.push(listNode);
listNode=listNode.next;
}
listNode=headB;
while(listNode!=null){
stackB.push(listNode);
listNode=listNode.next;
}
listNode=null;
//寻找第一个不同的元素
while (!stackA.isEmpty()&&!stackB.isEmpty()){
if(stackA.peek()!=stackB.peek()){
break;
}
listNode=stackA.peek();
stackA.pop();
stackB.pop();
}
return listNode;
}
将链表逆序,和原列表依次对比
public boolean isPalindrome(ListNode head) {
ListNode listNode=head;
Stack<ListNode> stack = new Stack<>();
while (listNode!=null){
stack.push(listNode);
listNode=listNode.next;
}
listNode=head;
while (listNode!=null){
if (stack.peek().val!=listNode.val){
return false;
}
stack.pop();
listNode=listNode.next;
}
return true;
}
观察特点,回文串就是对称相等,那么解决问题的关键就分三步走,第一步找到中点,第二步将一半逆序,第三步将两部分遍历对比,最关键一步就是 找中点
存到数组中,利用数组确定长度,找到中点
public boolean isPalindrome1(ListNode head) {
ArrayList<ListNode> listNodeArrayList = new ArrayList<>();
ListNode listNode =head;
while (listNode!=null){
listNodeArrayList.add(listNode);
listNode=listNode.next;
}
for(int i=0, j=listNodeArrayList.size()-1;i<listNodeArrayList.size()/2;i++,j--){
if (listNodeArrayList.get(i).val!=listNodeArrayList.get(j).val){
return false;
}
}
return true;
}
利用双指针,找到中点
public boolean isPalindrome(ListNode head) {
//定义快慢指针,找到中间点
ListNode fast=head,slow=head;
while (fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
}
//根据fast区分奇数偶数个节点,找到链表反转的起点
if(fast!=null){
slow=slow.next;
}
//反转链表
ListNode pre=null,next=null;
while (slow!=null){
next=slow.next;
slow.next=pre;
pre=slow;
slow=next;;
}
//对比
while (pre!=null){
if(pre.val!=head.val){
return false;
}
pre=pre.next;
head=head.next;
}
return true;
}
代码如下(示例):
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode newnode=new ListNode(-1);
ListNode temp=newnode;
while (l1!=null&&l2!=null){
if(l1.val<l2.val){
newnode.next=l1;
l1=l1.next;
}else {
newnode.next=l2;
l2=l2.next;
}
newnode=newnode.next;
}
if (l1!=null){
newnode.next=l1;
}
if(l2!=null){
newnode.next=l2;
}
return temp.next;
}
代码如下(示例):
public ListNode mergeKLists(ListNode[] lists) {
ListNode ln=null;
for(int i=0;i<lists.length;i++){
ln=mergeKList(ln,lists[i]);
}
return ln;
}
public ListNode mergeKList(ListNode l1,ListNode l2){
ListNode newnode=new ListNode(-1);
ListNode temp=newnode;
while(l1!=null&&l2!=null){
if(l1.val<l2.val){
temp.next=l1;
l1=l1.next;
}
else{
temp.next=l2;
l2=l2.next;
}
temp=temp.next;
}
temp.next = l1==null? l2:l1;
return newnode.next;
}
public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
ListNode temp=new ListNode(-1);
temp.next=list1;
int a_temp=a;
while ((a--)>0){
temp=temp.next;
}
//出现过错误,把a自减了,还用a,以后要主意呀
int length=b-a_temp;
ListNode temp1=temp;
while ((length--)>=-1){
temp1=temp1.next;
}
ListNode temp2=list2;
while (temp2.next!=null){
temp2=temp2.next;
}
temp.next=list2;
temp2.next=temp1;
return list1;
}
public ListNode middleNode(ListNode head) {
ListNode fast=head,slow=head;
while(fast!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
}
return slow;
}
乍一看这道题最简单的方法是循环两次,第一确定长度,第二次循环找到我们要的倒数第k个节点。
差k个节点就遍历完整个链表,让个一个指针先快跑k步,另一个指针在开始跑,遍历完一边后,慢的指针就是我们想要的
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode fast=head,slow=head;
while(fast!=null){
if((k--)>0){
fast=fast.next;
}else{
slow=slow.next;
fast=fast.next;
}
}
return slow;
}
输入:head = [1,2,3,4,5], k = 2
输出:[4,5,1,2,3]
首先我们要清楚k时可能要大于链表的长度的,因此我们先取余,如上图所示,k=7和k=2旋转结果是相同的
旋转k个位置就是将后k个元素接到了来年表的头部
public ListNode rotateRight(ListNode head, int k) {
int len=0;
//移除特殊情况
if(head==null){
return null;
}
//计算链表长度
ListNode listnode =head;
while(listnode!=null){
len++;
listnode=listnode.next;
}
//得到后k个元素的k值
k=(k)%len;
//找后k个元素的前一个
ListNode fast =head,preslow=head,slow=null;
while(fast.next!=null){
if((k--)>0){
fast=fast.next;
}else{
preslow=preslow.next;
fast=fast.next;
}
}
//成环
fast.next=head;
//断裂
slow=preslow.next;
preslow.next=null;
return slow;
}
将整个链表反转,找到分割点k,再将两部分别反转
public ListNode rotateRight(ListNode head, int k) {
if(head==null){
return null;
}
int len=0;
ListNode temp=head;
while(temp!=null){
temp=temp.next;
len++;
}
k=k%len;
//循环到原位置,直接返回
if(k==0){
return head;
}
head=fanzhuan(head);
temp=head;
//把握好k的边界值
for (int i = 0; i < k-1; i++) {
temp=temp.next;
}
ListNode l1=fanzhuan(temp.next);
temp.next=null;
ListNode l2=fanzhuan(head);
head.next=l1;
return l2;
}
public ListNode fanzhuan(ListNode head){
ListNode pre=null,cur=head,next=null;
while (cur != null) {
next=cur.next;
cur.next=pre;
pre=cur;
cur=next;
}
return pre;
}
使用递归
public ListNode removeElements(ListNode head, int val) {
if(head==null){
return head;
}
if(head.val==val){
return removeElements(head.next,val);
}else{
head.next=removeElements(head.next,val);
return head;
}
}
使用循环
public ListNode removeElements(ListNode head, int val) {
ListNode newnode=new ListNode(-1,head);
ListNode temp=newnode;
while(temp.next!=null){
if(temp.next.val==val){
temp.next=temp.next.next;
}else{
temp=temp.next;
}
}
return newnode.next;
}
双指针找到倒数,第k个节点的前一个,其要考虑边界条件删除倒数第n个元素
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode fast=head,slow=head;
while(fast.next!=null){
if(n-->0){
fast=fast.next;
}else{
fast=fast.next;
slow=slow.next;
}
}
if(n>0){
return head.next;
}else{
slow.next=slow.next.next;
return head;
}
}
代码改进,加入虚拟头节点,处理删除第一个节点的情况
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode newnode =new ListNode(-1,head);
ListNode fast=newnode,slow=newnode;
while(fast.next!=null){
if(n-->0){
fast=fast.next;
}else{
fast=fast.next;
slow=slow.next;
}
}
slow.next=slow.next.next;
return newnode.next;
}
public ListNode deleteDuplicates(ListNode head) {
ListNode newnode=new ListNode(-1,head);
ListNode temp=newnode;
Set set=new HashSet();
while(temp.next!=null){
if(set.add(temp.next.val)){
temp=temp.next;
}else{
temp.next=temp.next.next;
}
}
return newnode.next;
}
public ListNode deleteDuplicates(ListNode head) {
//加入虚拟头节点,方便删除第一个元素是重复节点的情况
ListNode newnode =new ListNode(-1,head);
ListNode temp=newnode;
while(temp.next!=null){
//分两种情况,后继的后继为空,则一定不重复,不为空则判断是否为重复元素
if(temp.next.next!=null){
if(temp.next.val==temp.next.next.val){
int val=temp.next.val;
while(temp.next!=null&&temp.next.val==val){
temp.next=temp.next.next;
}
}else{
temp=temp.next;
}
}else{
temp=temp.next;
}
}
return newnode.next;
}
`
主要介绍链表基本的操作,可以借用Hash表来解决相同重复等问题,倒转链表可以考虑栈的数据结构,巧妙的使用双指针找到中间元素,使用双指针找到倒数第k个元素