面試準備系列01----面試中的鏈表題目彙總
九月份馬上就要校招了,感覺時間很是捉急啊~~~~白天要實習,就只能晚上熬夜來準備基礎知識和寫博客了。此篇博文是面試準備系列的第一篇,隨後會持續更新02,03......以下是常見的關於鏈表的面試題目,題目幾乎都來自LeetCode 或者 劍橋offer,也借鑑了不少網上的答案,感謝感謝~~
本文是面試準備系列的第一篇文章,下一篇爲《面試準備系列02---面試中的棧和隊列題目彙總》。
1.鏈表長度
2.得到鏈表倒數第k個節點的值
3.刪除鏈表的倒數第k個節點
4.求單鏈表的中間節點5.判斷鏈表是否有環
6.找出有環鏈表的環的入口
7.判斷兩個單鏈表是否相交
8.找出兩個相交鏈表的第一個交點
9.從尾到頭打印單鏈表
10.逆置單鏈表
11.合併兩個有序鏈表,使合併後的鏈表依然有序
12.在o(1)的時間複雜度刪除單鏈表中指定的某一節點
13.鏈表成對交換節點
14.無序鏈表排序
15.鏈表首尾交叉排序
package com.sheepmu;
import java.util.Stack;
/**
* 1.鏈表長度
* 2.得到鏈表倒數第k個節點的值
* 3.刪除鏈表的倒數第k個節點
* 4.求單鏈表的中間節點
* 5.判斷鏈表是否有環
* 6.找出有環鏈表的環的入口
* 7.判斷兩個單鏈表是否相交
* 8.找出兩個相交鏈表的第一個交點
* 9.從尾到頭打印單鏈表
* 10.逆置單鏈表
* 11.合併兩個有序鏈表,使合併後的鏈表依然有序
* 12.在o(1)的時間複雜度刪除單鏈表中指定的某一節點
*13.在指定節點前插入一個節點
*14.無序鏈表排序
*15.鏈表首尾交叉排序
* @author sheepmu
*/
class ListNode
{ int value;
ListNode next;
public ListNode(int value)
{
this.value=value;
this.next=null;
}
}
public class List
{
public static void main(String[] args)
{
ListNode n1=new ListNode(1);
ListNode n2=new ListNode(2);
ListNode n3=new ListNode(3);
ListNode n4=new ListNode(4);
ListNode n5=new ListNode(5);
ListNode n6=new ListNode(6);
n1.next=n2;
n2.next=n3;
n3.next=n4;
n4.next=n5;
n5.next=n6;
System.out.println("鏈表長度---->"+getLenRec(n1));
System.out.println("鏈表倒數第三個節點的值---->"+getLastK(n1,3));
System.out.println("刪除鏈表倒數第3個節點---->"+moveLastK(n1,3));//此時鏈表已經是 1,2,3,5,6
System.out.println("刪除倒數第k個後的鏈表長度---->"+getLenRec(n1));
System.out.println("鏈表中間節點的值---->"+getMid(n1));
System.out.println("鏈表是否有環---->"+isHaveC(n1));
System.out.println("鏈表環的入口---->"+getFirstC(n1));
System.out.println("從尾到頭打印單鏈表---->");
reversePrintListStack(n1);
System.out.println("逆置單鏈表後的頭節點--->"+reverseList(n1).value);
}
//1.鏈表長度
public static int getLen(ListNode head)
{
if(head==null)
return 0;
int len=1;
while(head.next!=null)
{
len++;
head=head.next;
}
return len;
}
//1.鏈表長度 -----遞歸------
public static int getLenRec(ListNode head)
{
if(head==null)
return 0;
return getLenRec(head.next)+1;
}
//2.得到鏈表倒數第k個節點的值
/**
* 思路:因爲倒數第k個和最後一個相距k-1個節點,故採用前後指針,第一個先走k-1步,即走到第k個,鏈表我習慣從1開始計算,
然後兩個指針在同時走,當前指針p走到末尾時,後指針q的位置剛好是倒數第k個節點。
*/
public static int getLastK(ListNode head,int k)
{
if(head==null||k<=0)
return -1;
ListNode p=head;
ListNode q=head;
while(--k>0)//讓p先走k-1步,即p走到第k個節點,從1開始計數哈~
{
p=p.next;
if(p.next==null)
break; //防止輸入的倒數k值特別大的異常情況
}
if(p.next==null)//說明輸入的k超出鏈表長度範圍or等於鏈表長度,即刪除第一個。一定要特別注意這些特別情況,代碼的魯棒性~~
{
return head.value;
}
else
{
while(p.next!=null)
{
p=p.next;
q=q.next;
}
return q.value;
}
}
//3.刪除鏈表的倒數第k個節點
/**
* 思路:和上面相比就是要刪除倒數第k個,那麼就需要記錄後指針的前一節點,因爲刪除鏈表的本質就是它的前一節點指向它的後一節點
*/
public static ListNode moveLastK(ListNode head,int k)
{
if(head==null||k<=0)
return null;
ListNode p=head;
ListNode q=head;
while(--k>0)//讓p先走k-1步,即p走到第k個節點,從1開始計數哈~
{
p=p.next;
if(p.next==null)
break; //防止輸入的倒數k值特別大的異常情況
}
if(p.next==null)//說明輸入的k超出鏈表長度範圍or等於鏈表長度,即刪除第一個。
{
return head.next;
}
else
{
ListNode pre=q;//用於記錄刪除節點的前一節點
while(p.next!=null)
{
pre=q;
p=p.next;
q=q.next;
}
pre.next=q.next;
return head;
}
}
//4.求單鏈表的中間節點
/**
* 前後指針,一個每次走2步一個每次走1步,若鏈表長度爲奇數返回中間值,爲偶數返回中間2者的前一者
*/
public static int getMid(ListNode head)
{
if(head==null||head.next==null)//0個節點和1個節點時
return -1;
if(head.next.next==null)//兩個節點時
return head.value;
ListNode p=head;
ListNode q=head;
while(p.next!=null&&p.next.next!=null)//若只有 一個節點 和 兩個節點 時while條件不滿足
{
p=p.next.next;
q=q.next;
}
return q.value;
}
//5.判斷鏈表是否有環
/**
* 思路:前後指針,一個每次走兩步一個每次走一步,若兩指針相遇了則說明鏈表有環
*/
public static boolean isHaveC(ListNode head)
{
if(head==null||head.next==null||head.next.next==null) //只有兩個節點時也無環吧?
return false;
ListNode p=head;
ListNode q=head;
while(p.next!=null&&p.next.next!=null)
{
p=p.next.next;
q=q.next;
if(p==q)
return true;
}
return false;
}
//6.找出有環鏈表的環的入口
/**
* 思路:若有環肯定會在環中相遇第一次相遇的位置到環開始的位置的距離(按環的方向)與頭節點到環的開始的距離相等。
故當相遇時,讓節點q置於頭節點,讓後兩個節點同時走,再次相遇處就是環開始的位置。
*/
public static ListNode getFirstC(ListNode head)
{
if(head==null||head.next==null||head.next.next==null)
return null;
ListNode p=head;
ListNode q=head;
while(p.next!=null&&p.next.next!=null)
{
p=p.next.next;
q=q.next;
if(p==q)
break; //pq相遇後break
}
if(p.next==null||p.next.next==null)//無環
return null;
q=head;//把q置於頭節點
while(p!=q)
{
p=p.next;
q=q.next;
}
return q;
}
//7.判斷兩個單鏈表是否相交
/**
* 思路:若兩鏈表相交,則兩鏈表的尾節點肯定是同一節點
*/
public boolean isXJ(ListNode head1,ListNode head2)
{
if(head1==null||head2==null)
return false;
ListNode tail1=head1;
while(tail1.next!=null)//不包含只有一個節點的情況 ,所以上面需要考慮只有一個節點的情況,但是此時雖只有一個節點都不進入入while,就return時比較就可以了
{
tail1=tail1.next;//得到head1的尾巴
}
ListNode tail2=head2;
while(tail2.next!=null)
{
tail2=tail2.next;
}
return tail1==tail2;
}
//8.找出兩個相交鏈表的第一個交點
/**
* 思路:先讓長的鏈表的指針先走長度差的距離,然後兩個指針一起走,相遇的地方便是交點的開始處。
*/
public static ListNode getFirstJD(ListNode head1,ListNode head2)
{
if(head1==null||head2==null)
return null;
ListNode tail1=head1;
int len1=1;
while(tail1.next!=null)
{
len1++;
tail1=tail1.next;
}
ListNode tail2=head2;
int len2=1;
while(tail2.next!=null)
{
len2++;
tail2=tail2.next;
}
ListNode n1=head1;
ListNode n2=head2;
if(len1>len2)
{
int k=len1-len2;
while(k-->0)//這樣寫更精妙~~~~
n1=n1.next;
}
else
{
int k=len2-len1;
while(k-->0)
n2=n2.next;
}
while(n1!=n2)
{
n1=n1.next;
n2=n2.next;
}
return n1;
}
//9.從尾到頭打印單鏈表
/**
* 思路:逆序的問題首先想到棧,先進後出,要麼用Stack,要麼讓系統使用棧,即遞歸~~
*/
public static void reversePrintListStack(ListNode head)
{
if(head==null)
return;
Stack<ListNode> stack = new Stack<ListNode>();
ListNode cur = head;
while (cur != null)
{
stack.push(cur);
cur = cur.next;
}
while (!stack.empty())
{
ListNode pop= stack.pop();
System.out.print(pop.value + " ");
}
System.out.println();
}
//9.從尾到頭打印單鏈表 -----遞歸------
public static void reversePrintListRec(ListNode head)
{
if (head == null)
return;
reversePrintListRec(head.next); //默認已經把子問題解決了,即已經輸出了6-->5-->3-->2,後面只需要再輸出頭節點的值
System.out.print(head.value + " "); //這樣理解會方便我們很快寫出遞歸,但是還是要知道遞歸真正的實現過程~
}
//10.逆置單鏈表
/**
* 思路:把每次遍歷到的都放到新鏈表的尾巴的前一個
*/
public static ListNode reverseList(ListNode head)
{
// 如果鏈表爲空或只有一個節點,無需反轉,直接返回原鏈表表頭
if (head == null || head.next == null)
return head;
ListNode newTail = null; //新鏈表的尾巴
ListNode cur = head;
while (cur != null)
{
ListNode pre = cur; //新鏈表尾巴的前一個
cur = cur.next; // cur更新到下一個節點
pre.next = newTail ;
newTail = pre; // 把尾巴的前一個設爲新的尾巴
}
return newTail;
}
//10.逆置單鏈表 ----遞歸-----
public static ListNode reverseListRec(ListNode head)
{
if(head == null || head.next == null)
return head;
ListNode reHead = reverseListRec(head.next); //默認爲已經逆置好了6-->5-->3-->2,即只需要在2的後面接1,因爲2是head.next.所以 head.next.next = head;
head.next.next = head;
head.next = null; // 防止循環鏈表
return reHead;
}
//11.合併兩個有序鏈表,使合併後的鏈表依然有序
/**
* 思路:先確定合併後的頭節點,然後對比兩個鏈表的每一個節點值,若有個鏈表沒有合併完就直接加在後面就可以了
*/
public static ListNode mergeSortedList(ListNode head1, ListNode head2)
{
if (head1 == null)
return head2;
if (head2 == null)
return head1;
ListNode mergeHead = null; // 先確定合併後的頭節點
if (head1.value < head2.value )
{
mergeHead = head1;
head1 = head1.next; // 跳過已經合併了的元素
mergeHead.next = null; // 斷開mergeHead的下一個置空
}
else
{
mergeHead = head2;
head2 = head2.next;
mergeHead.next = null;
}
ListNode mergeCur = mergeHead;
while (head1 != null && head2 != null)
{
if (head1.value < head2.value )
{
mergeCur.next = head1; // 把找到較小的元素合併到merge中
head1 = head1.next; // 跳過已經合併了的元素
mergeCur = mergeCur.next; // 找到下一個準備合併的元素
mergeCur.next = null; // 斷開mergeCur和後面的聯繫
}
else
{
mergeCur.next = head2;
head2 = head2.next;
mergeCur = mergeCur.next;
mergeCur.next = null;
}
}
if (head1 != null) // 合併剩餘的元素
mergeCur.next = head1;
else if (head2 != null)
mergeCur.next = head2;
return mergeCur;
}
//11.合併兩個有序鏈表,使合併後的鏈表依然有序 ----遞歸-----
public static ListNode mergeSortedListRec(ListNode head1, ListNode head2)
{
if (head1 == null)
return head2;
if (head2 == null)
return head1;
ListNode mergeHead = null;
if (head1.value < head2.value )
{
mergeHead = head1;
mergeHead.next = mergeSortedListRec(head1.next, head2); // 連接已解決的子問題
}
else
{
mergeHead = head2;
mergeHead.next = mergeSortedListRec(head1, head2.next);
}
return mergeHead;
}
//12.在o(1)的時間複雜度刪除單鏈表中指定的某一節點
/**
* 對於刪除節點,我們普通的思路就是讓該節點的前一個節點指向該節點的下一個節點
* ,這種情況需要遍歷找到該節點的前一個節點,時間複雜度爲O(n)。對於鏈表,
* 鏈表中的每個節點結構都是一樣的,所以我們可以把該節點的下一個節點的數據複製到該節點
* ,然後刪除下一個節點即可。要注意最後一個節點的情況,這個時候只能用常見的方法來操作,先找到前一個節點,但總體的平均時間複雜度還是O(1)
*/
public void delete(ListNode head, ListNode toDelete)
{
if(toDelete == null)
return;
if(toDelete.next != null)//帶刪除的不是尾巴!!!~~~~~~~~~
{
toDelete.value = toDelete.next.value ;
toDelete.next = toDelete.next.next;
}
else
{
if(head == toDelete)//只有一個節點時
head = null;
else
{
ListNode node = head;
while(node.next != toDelete)
node = node.next; // 找到倒數第二個節點
node.next = null;
}
}
}
//14.無序鏈表排序
/**
* 採用歸併排序,先分成兩段分別排序,再合併兩段已排序的。O(nlgn)
*/
public static ListNode sortList(ListNode head)
{
if(head==null || head.next==null)
{ // 鏈表沒有元素或是隻有一個元素的情況直接返回
return head;
}
ListNode fast = head;
ListNode slow = head;
ListNode preSlow = head;
// 找到中間節點的前一個
while(fast!=null && fast.next!=null)
{
fast = fast.next.next;
preSlow = slow;
slow = slow.next;
}
// System.out.println(preSlow.val);
// 斷開,分成兩段
preSlow.next = null;
ListNode first = sortList(head); // 得到以排序好的前半段
ListNode second = sortList(slow); // 得到以排序好的後半段
ListNode dummy = new ListNode(-1);
ListNode dummyCur = dummy;
while(first!=null && second!=null)
{ // 合併兩半段
if(first.value<second.value)
{
dummyCur.next = first;
first = first.next;
}
else if(second.value<=first.value)
{
dummyCur.next = second;
second = second.next;
}
dummyCur = dummyCur.next;
}
while(first != null)
{
dummyCur.next = first;
first = first.next;
dummyCur = dummyCur.next;
}
while(second != null)
{
dummyCur.next = second;
second = second.next;
dummyCur = dummyCur.next;
}
return dummy.next;
}
//15.鏈表首尾交叉排序
/**
* 斷成兩段,後段逆序,把前段和逆序後的後段交叉排序
*/
public static void reorderList(ListNode head)
{
ListNode fast = head;
ListNode slow = head;
// 找到中間節點
while(fast!=null && fast.next!=null)
{
fast = fast.next.next;
slow = slow.next;
}
ListNode preReverse = slow; // preReverse不用翻轉,因爲它永遠在最後一個
if(preReverse == null)
return;
// 翻轉後半段
ListNode reHead = preReverse.next;
if(reHead == null)
return;
ListNode preCur = reHead.next;
ListNode cur = reHead.next;
reHead.next = null;
while(cur != null)
{
cur = cur.next;
preCur.next = reHead;
reHead = preCur;
preCur = cur;
}
preReverse.next = reHead;
// 交叉合併兩個鏈表
preReverse.next = null; // 斷開前半段和翻轉後的後半段元素
cur = head;
while(reHead != null && cur!=null)
{
ListNode tmp = cur.next;
cur.next = reHead;
reHead = reHead.next;
cur.next.next = tmp;
cur = tmp;
tmp = cur.next;
}
}
}