链表算法—面试题
来源:互联网 发布:沙盘软件哪个好 编辑:程序博客网 时间:2024/06/05 02:23
public class Solution { public ListNode addTwoNumbers(ListNode l1, ListNode l2) { ListNode myHead=new ListNode(-1),last=myHead; ListNode p=l1,q=l2; int add=0; while(p!=null||q!=null){ int sum=add+(p==null?0:p.val)+(q==null?0:q.val); add=sum/10; ListNode t=new ListNode(sum%10);//加入链表 last.next=t; last=t; if(p!=null)p=p.next; if(q!=null)q=q.next; } if(add!=0){//最后的进位 ListNode t=new ListNode(add); last.next=t; last=t; }return myHead.next; }}
分析
我们可以利用两个指针同步后移,当后一个指针到达链表末尾时,前一个指针刚好指向待删除元素的前驱节点。
public class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { ListNode myHead=new ListNode(-1),p,q; myHead.next=head; p=myHead; q=myHead; while(n-->0){ q=q.next; } while(q.next!=null){ p=p.next; q=q.next; } p.next=p.next.next;return myHead.next; }}
public class Solution { public ListNode mergeTwoLists(ListNode l1, ListNode l2) { ListNode myHead=new ListNode(-1),last=myHead,p,q,t; p=l1; q=l2; while(p!=null||q!=null){ if(q==null||(p!=null&&p.val<=q.val)){//处理链表1的节点 t=p; p=p.next;<img src="http://img.blog.csdn.net/20160731205700468?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="" /> }else{//否则处理链表2的节点 t=q; q=q.next; }t.next=null;//尾插last.next=t;last=t; }return myHead.next; }}
分析
这里涉及多个链表的合并,我们可以利用归并算法的思想,采用分治的思想,将问题进行划分。
递归版
public class Solution { public ListNode mergeKLists(ListNode[] lists) { if(lists.length==0) return null; doMergeLists(lists,0,lists.length-1); return lists[0]; } private void doMergeLists(ListNode[] lists ,int start,int end){ if(start>=end){ return; } int mid=start+(end-start)/2; doMergeLists(lists,start,mid); doMergeLists(lists,mid+1,end); lists[start]=mergeTwoLists(lists[start],lists[mid+1]); } public ListNode mergeTwoLists(ListNode l1, ListNode l2) { ListNode myHead=new ListNode(-1),last=myHead,p,q,t; p=l1; q=l2; while(p!=null||q!=null){ if(q==null||(p!=null&&p.val<=q.val)){ t=p; p=p.next; }else{ t=q; q=q.next; }t.next=null;//尾插last.next=t;last=t; }return myHead.next; }}
迭代版
public class Solution { public ListNode mergeKLists(ListNode[] lists) { int n=lists.length; if(n==0) return null; int size=1;//跨度,2的幂(这样每次归并后的结果下次正好用上) while(size<=n){ int first=0,second=first+size; while(second<n){ lists[first]=mergeTwoLists(lists[first],lists[second]); first=second+size; second=first+size; } size<<=1; } return lists[0]; } public ListNode mergeTwoLists(ListNode l1, ListNode l2) { ListNode myHead=new ListNode(-1),last=myHead,p,q,t; p=l1; q=l2; while(p!=null||q!=null){ if(q==null||(p!=null&&p.val<=q.val)){ t=p; p=p.next; }else{ t=q; q=q.next; }t.next=null;//尾插last.next=t;last=t; }return myHead.next; }}
分析
如果可以修改链表节点的值,我们迭代节点时,每次将指针移动两个位置,并将指针随后两个节点的值进行交换。
但是题目限定了我们不能修改链表节点中的值,那么我们只能换一种思路,每两个节点我们当做一个链表进行逆转。
递归版
public class Solution { public ListNode swapPairs(ListNode head) { if(head==null) return head; return swapAsGroup(head,2); } private ListNode swapAsGroup(ListNode head,int k){ if(head==null) return head; ListNode p=head; int count=k; while(count-->1&&p.next!=null){ p=p.next; } ListNode left=p.next; ListNode end=head,start=p; p.next=null; reverseList(head); end.next=swapAsGroup(left,k); return start; } private ListNode reverseList(ListNode head){//头插入法ListNode myHead=new ListNode(-1),p=head,t;while(p!=null){t=p;p=p.next; t.next=myHead.next;myHead.next=t;} return myHead.next; }}
迭代版
public ListNode swapPairs(ListNode head) { if(head==null) return head; return swapAsGroup(head,2); } private ListNode swapAsGroup(ListNode head,int k){ ListNode myHead=new ListNode(-1),p,q; myHead.next=head; p=myHead;//p为前驱节点 while(p!=null&&p.next!=null){ int count=k; q=p; while(count-->0&&q.next!=null){ q=q.next; }//q指向该组元素的末尾元素 ListNode left=q.next;//left指向剩余的元素的开头 ListNode first=q,end=p.next; //记录逆转后的头和尾,就是逆转前的尾和头 q.next=null; reverseList(p.next);//逆转分组 p.next=first;//重新将链表链接起来 end.next=left; p=end;//更新前驱节点 } return myHead.next; }private ListNode reverseList(ListNode head){//头插入法ListNode myHead=new ListNode(-1),p=head,t;while(p!=null){t=p;p=p.next; t.next=myHead.next;myHead.next=t;} return myHead.next; }
分析
我们上一题的算法,已经可以实现了根据分组大小逆转。但是,现在我们要保证末尾大小不足k的分组不进行逆转,我们只需要添加条件判断即可。
迭代版
private ListNode swapAsGroup(ListNode head,int k){ if(head==null) return head; ListNode p=head; int count=k; while(count>1&&p.next!=null){ p=p.next; count--;//只有后移了才减少 } if(count!=1){ return head; }//这里添加条件判断 ListNode left=p.next; ListNode end=head,start=p; p.next=null; reverseList(head); end.next=swapAsGroup(left,k); return start; }
递归版
private ListNode swapAsGroup(ListNode head,int k){ ListNode myHead=new ListNode(-1),p,q; myHead.next=head; p=myHead;//p为前驱节点 while(p!=null&&p.next!=null){ int count=k; q=p; while(count>0&&q.next!=null){ q=q.next; count--;//只有移动了才减少 }//q指向该组元素的末尾元素 if(count!=0){//这里添加条件判断 break; } ListNode left=q.next;//left指向剩余的元素的开头 ListNode first=q,end=p.next; //记录逆转后的头和尾,就是逆转前的尾和头 q.next=null; reverseList(p.next);//逆转分组 p.next=first;//重新将链表链接起来 end.next=left; p=end;//更新前驱节点 } return myHead.next; }
分析
我们只需要找到倒数第K的元素的前驱节点,然后将倒数k个元素组成的链表整体转移到表头即可。
public class Solution { public ListNode rotateRight(ListNode head, int k) { int n=0; ListNode p=head; while(p!=null){ p=p.next; n++; } if(n==0||k==0||(k=k%n)==0){ return head; } ListNode myHead=new ListNode(-1),q; myHead.next=head; p=myHead; q=myHead; while(k>0){//先将q后移k步 q=q.next; k--; } while(q.next!=null){//同步后移 p=p.next; q=q.next; } q.next=myHead.next; myHead.next=p.next; p.next=null;return myHead.next; }}
扩展
如果我们旋转的是数组的时候,按照上面的思路,就没有链表旋转这样方便了。将尾部元素移动到最前面时,我们不仅需要额外的存储空间缓存我们将要移动的元素,还需要将其余元素后移。不过,因为数组的随机存取特性,我们可以将整个数组逆转,然后将前k的元素和后n-k个元素分别逆转,最终也能得到结果。这里应该可以很好的体会到链表离散存储和数组顺序存储的特性,以及这些特性给各自带来的局限性和优点。
public class Solution { public ListNode deleteDuplicates(ListNode head) { ListNode myHead=new ListNode(-1),p=head; myHead.next=head; while(p!=null){ if(p.next==null){ break; }else{ if(p.next.val!=p.val){ p=p.next; }else{ p.next=p.next.next; } } }return myHead.next; }}
此外,我们还可以采用尾插法重新建立链表,对与链表尾节点的值相同的节点我们忽略掉。
public ListNode deleteDuplicates(ListNode head) { ListNode myHead=new ListNode(-1),last=myHead,p,candidate,t; p=head; while(p!=null){ if(p.next==null||p.next.val!=p.val){//末尾或者只出现一次 t=p; p=p.next; t.next=null; last.next=t; last=t; }else{ candidate=p; while(p!=null&&p.val==candidate.val){ p=p.next; } } }return myHead.next; }
分析
我们直接利用尾插法建立两个链表,最后再合并即可。
public class Solution { public ListNode partition(ListNode head, int x) { ListNode smallerHead=new ListNode(-1),smallerLast=smallerHead; ListNode biggerHead=new ListNode(-1),biggerLast=biggerHead; ListNode p=head,t; while(p!=null){ t=p; p=p.next; t.next=null; if(t.val<x){ smallerLast.next=t; smallerLast=t; }else{ biggerLast.next=t; biggerLast=t; } } smallerLast.next=biggerHead.next; return smallerHead.next; }}
public class Solution { public ListNode reverseBetween(ListNode head, int m, int n) { if(m==n) return head; ListNode myHead=new ListNode(-1),p,q; myHead.next=head; p=myHead; int count=m; while(count-->1){ p=p.next; }//p后移m-1步,p指向逆转部分前驱节点 count=n-m+1; q=p; while(count-->0){//q后移n-m+1步,q指向逆转部分尾节点 q=q.next; } ListNode start=q,end=p.next,left=q.next; //start表示逆转后的头,end表示逆转后的尾,left表示后续链表的头 q.next=null;//注意将链表断开再逆转 reverseList(p.next); end.next=left; p.next=start; return myHead.next; } private ListNode reverseList(ListNode head){//头插入法ListNode myHead=new ListNode(-1),p=head,t;while(p!=null){t=p;p=p.next; t.next=myHead.next;myHead.next=t;} return myHead.next; }}
分析
我们采用分治的思想,将问题进行划分为左右子树构建BST。
public class Solution { public TreeNode sortedListToBST(ListNode head) { ListNode p=head; int n=0; while(p!=null){ p=p.next; n++; } return buildBST(head,n); } private TreeNode buildBST(ListNode head,int length){ if(length==0)return null; TreeNode root=new TreeNode(-1); if(length==1){ root.val=head.val; return root; } int index=1; int mid=(1+length)/2; ListNode midNode=head; while(index<mid){ midNode=midNode.next; index++; } root.val=midNode.val; root.left=buildBST(head,mid-1); root.right=buildBST(midNode.next,length-mid); return root; }}
分析:
如果我们复制链表主体的时候复制随机指针,可能这时候随机指针指向的元素,我们还没有进行复制,因此比较繁琐。
我们可以先复制链表的主体部分,我们只需要使用尾插法即可。第二趟我们来复制随机指针,因为原来链表中的的随机指针指向原链表的节点这里不能直接复制,因此需要将指向原链表节点的指针映射到新链表的节点,因此需要利用哈希表建立这种映射关系。
public class Solution { public RandomListNode copyRandomList(RandomListNode head) { RandomListNode myHead=new RandomListNode(-1),last=myHead,p=head; HashMap<RandomListNode,RandomListNode> map=new HashMap<RandomListNode,RandomListNode>(); while(p!=null){//尾插入法 RandomListNode t=new RandomListNode(p.label); map.put(p, t);//建立映射关系,便于随机指针的复制 last.next=t; last=t; p=p.next; } p=head; while(p!=null){//复制随机指针 RandomListNode t=map.get(p); if(p.random==null){ t.random=null; }else{ t.random=map.get(p.random); } p=p.next; } return myHead.next; } }
分析
我们利用利用两个指针,一个每次往后移动一步,一个每次往后移动两步。如果任何一个指针为null,即到达链表末尾,返回false。否则,最终两个指针肯定会碰头(相等),返回true。
public class Solution { public boolean hasCycle(ListNode head) { ListNode p,q; if(head==null||head.next==null){ return false;//空或者第一个节点没有后继节点 } p=head; q=head.next; while(p!=q){ p=p.next; if(p==null) return false; q=q.next; if(q==null) return false; q=q.next; if(q==null) return false; //p和q都不为null }return true ; }}
分析
首先我们利用上题中的方法判定有没有环,如果没环直接返回null。如果有环,p指向环中的某一个节点。
现在我们考虑两个链表,链表1:从头节点到p的链表;链表2:p的后继节点到p的链表。
不难发现,链表1和链表2具有相同的尾节点,现在问题就退化成了:求两个链表的第一个公共节点。
public class Solution { public ListNode detectCycle(ListNode head) { ListNode p,q,t; if(head==null||head.next==null){ return null;//空或者第一个节点没有后继节点 } p=head; q=head.next; while(p!=q){ p=p.next; if(p==null) return null; q=q.next; if(q==null) return null; q=q.next; if(q==null) return null; //p和q都不为null } //此时,p指向环中的某个节点 ListNode headOne=head,endOne=p; ListNode headTwo=p.next,endTwo=p; int lengthOne=1,lengthTwo=1; p=headOne; while(p!=endOne){ p=p.next; lengthOne++; } q=headTwo; while(q!=endTwo){ q=q.next; lengthTwo++; } if(lengthOne<lengthTwo){//保证第一个链表比第二个链表长,方便后续处理 t=headOne;headOne=headTwo;headTwo=t; int temp=lengthOne;lengthOne=lengthTwo;lengthTwo=temp; } p=headOne; q=headTwo; int count=lengthOne-lengthTwo; while(count>0){//先把长链表的指针后移两个链表的长度差 p=p.next; count--; } //指针同步后移 while(p!=q){ p=p.next; q=q.next; } return p ; }}
分析
首先我们可以将链表划分为前后两部分,1->2->null和3->4->null,然后将后半部分链表逆转为4->3->null。
然后将两个链表中的元素交替取出并采用尾插法建表。
public class Solution { public void reorderList(ListNode head) { int n=0; ListNode myHead=new ListNode(-1),last=myHead; ListNode p=head,firstHead,secondHead,t; while(p!=null){ p=p.next; n++; } if(n<=2) return; int preLength,postLength; postLength=n/2; preLength=n-postLength; firstHead=head; secondHead=head; int count=preLength; while(count>0){ secondHead=secondHead.next; count--; } secondHead=reverseList(secondHead);//逆转后半段链表 count=postLength; while(count>0){ t=firstHead; firstHead=firstHead.next; t.next=null; last.next=t; last=t; t=secondHead; secondHead=secondHead.next; t.next=null; last.next=t; last=t; count--; } if(preLength>postLength){//最后可能多的单独元素 t=firstHead; t.next=null; last.next=t; last=t; } } private ListNode reverseList(ListNode head){//头插入法ListNode myHead=new ListNode(-1),p=head,t;while(p!=null){t=p;p=p.next; t.next=myHead.next;myHead.next=t;} return myHead.next; }}
public class Solution { public ListNode insertionSortList(ListNode head) { ListNode myHead=new ListNode(-1); ListNode p=head,t; while(p!=null){ t=p; p=p.next; insertList(myHead,t); }return myHead.next; } private void insertList(ListNode myHead,ListNode t){ ListNode pre=myHead,post=myHead.next; while(post!=null&&post.val<=t.val){ pre=post; post=post.next; } t.next=post; pre.next=t; }}
public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { ListNode p=headA,q=headB,t; int lengthA=0,lengthB=0; while(p!=null){ lengthA++; p=p.next; } while(q!=null){ lengthB++; q=q.next; } if(lengthA<lengthB){//保证第一个链表比第二个长 int temp=lengthA;lengthA=lengthB;lengthB=temp; t=headA;headA=headB;headB=t; } int count=lengthA-lengthB; p=headA; q=headB; while(count>0){//指针对齐 p=p.next; count--; } while(p!=q){//同步后移 p=p.next; q=q.next; }return p; }}
public class Solution { public ListNode removeElements(ListNode head, int val) { ListNode myHead=new ListNode(-1),pre; myHead.next=head; pre=myHead; while(pre.next!=null){ if(pre.next.val==val){ pre.next=pre.next.next; }else{ pre=pre.next; } }return myHead.next; }}
public class Solution { public ListNode reverseList(ListNode head){//头插法ListNode myHead=new ListNode(-1),p=head,t;while(p!=null){t=p;p=p.next; t.next=myHead.next;myHead.next=t;} return myHead.next; }}
分析
我们可以将链表拆分为两半,并将后半段逆转,接着只需要前半段和后半段对应元素是否相等即可。判定完后,将后半段恢复。
public class Solution { public boolean isPalindrome(ListNode head) { int n=0; ListNode p=head,firstHead,secondHead,t,q,firstEnd=null; while(p!=null){ p=p.next; n++; } if(n<=1) return true; int preLength,postLength; postLength=n/2; preLength=n-postLength; firstHead=head; secondHead=head; int count=preLength; while(count>0){ if(count==1){ firstEnd=secondHead; } secondHead=secondHead.next; count--; } secondHead=reverseList(secondHead);//逆转后半段链表 p=firstHead; q=secondHead; count=postLength; while(count>0){ if(p.val!=q.val){ return false; } p=p.next; q=q.next; count--; } firstEnd.next=reverseList(secondHead);//恢复链表后半段return true; } private ListNode reverseList(ListNode head){//头插入法ListNode myHead=new ListNode(-1),p=head,t;while(p!=null){t=p;p=p.next; t.next=myHead.next;myHead.next=t;} return myHead.next; }}
分析
删除节点时,我们一般会找到该节点的前驱,然后让前驱节点的指针指向该节点的后继节点。但是,此处我们只知道当前节点,根据当前节点我们可以获取到后继节点。我们不可能找到前驱节点,怎么办呢?
我们只能采用偷梁换柱的方法,我们将后继节点的值赋给当前节点,现在我们只需要删除后继节点即可。
对于非尾节点,其必定有后继节点。
public class Solution { public void deleteNode(ListNode node) { node.val=node.next.val; node.next=node.next.next; }}
分析
我们先将链表拆分为两个链表,再链接起来即可。
public class Solution { public ListNode oddEvenList(ListNode head) { ListNode oddHead=new ListNode(-1),oddLast=oddHead; ListNode evenHead=new ListNode(-1),evenLast=evenHead; ListNode p=head,t; while(p!=null){ t=p; p=p.next; t.next=null; oddLast.next=t; oddLast=t; if(p==null) break; t=p; p=p.next; t.next=null; evenLast.next=t; evenLast=t; } oddLast.next=evenHead.next;return oddHead.next; }}
- 链表算法—面试题
- 链表操作 算法面试题
- 链表的算法面试题总结
- 算法与数据结构面试题(10)-颠倒链表
- 九章算法面试题18 复制链表
- 九章算法面试题72 翻转链表I
- 九章算法面试题73 翻转链表II
- 九章算法面试题82 合并有序链表
- Java算法面试题
- Java算法面试题
- 算法面试题
- 微软算法面试题
- 面试题算法题
- 算法面试题总结
- 单链表算法面试题
- 算法面试题
- 算法面试题一
- 算法面试题二
- Android之在ubuntu过滤日志以及ps总结
- ListView学习总结
- 【数组10】数组中的逆序对
- 死锁
- 高精度运算-748 求幂
- 链表算法—面试题
- Windows Socket 编程_ 简单的服务器/客户端程序
- 决策树算法
- uboot移植之网络驱动移植--移植理论知识--7.29
- I/O流之进步认识、InputStream以及FileInputStream
- huu 1003Max Sum dp
- Android RecyclerView中ViewHolder的复用导致数据错乱解决办法
- CentOS7 防火墙操作
- 减少视图层级<merge />