本文主要討論鏈表在面試筆試中的題目,內容比較基礎,但在論壇上的面經中出現的頻率很高,也是做更困難鏈表題目的基礎。
class Node<V> {
V value;
Node next;
public Node(V value){
this.value = value;
}
}
class DoubleNode<V> {
V value;
DoubleNode last;
DoubleNode next;
public Node(V value){
this.value = value;
}
}
基礎博文主要包括兩個題目,反轉單向鏈表,反轉雙向鏈表;基礎題目,不做過多分析解釋,看註釋就能看明白。
public static Node reverseList(Node head) {
//指針變數,pre表示操作節點的前一個節點,初始都未null,next爲操作節點的後一個節點
Node pre = null;
Node next;
while(head != null){
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
public static DoubleNode reverseDoubleList(DoubleNode head) {
//指針變數,next爲操作節點的後一個節點,
Node next;
//實現思路比單向還要簡單,就是把每個節點的last和next節點互換就可以了
while(head.next != null){
next = head.next;
head.next = head.last;
head.last = next;
head = next;
}
//上述while回圈到最後一個節點結束,head節點爲最後一個節點,但此時還不能返回head,因爲它還沒把上下節點互換
head.next = head.last;
head.last = null;
return head;
}
題目:
有兩個排好序的單鏈表,列印出兩個鏈表的共同節點;比如:
兩鏈表:1->2->3->7->null;
2->7->9->null;
結果: 2,7
分析:
public class Solution{
public static void printCommonPart1(Node head1, Node head2){
HashSet<Integer> sites = new HashSet<>();
Node temp1 = head1;
Node temp2 = head2;
while(temp1 != null) {
sites.add(temp1.value);
temp1 = temp1.next;
}
while(temp2 != null) {
if(sites.contains(temp2.value))
System.out.print(temp2.value);
temp2 = temp2.next;
}
}
}
要注意有序是指的值有序,所以有序表中存放的是節點的值,而不是節點本身,因爲值等是不代表同一個節點的。
public static void printCommonPart2(Node head1, Node head2){
Node temp1 = head1;
Node temp2 = head2;
while(temp1 != null && temp2 != null) {
if(temp1.value == temp2.value){
System.out.print(temp1.value);
}
else if(temp1.value > temp2.value){
temp2 = temp2.next;
}
else{
temp1 = temp1.next;
}
}
}
對於筆試,一切爲了時間複雜度,從思考時間和編碼速度上來說,使用雜湊表或者有序表是非常方便的,也就是用空間換了時間;
對於面試,時間複雜度仍然是第一位的,但是一定也要找到空間複雜度最優的,不去使用集合API解決問題。
限制條件:要求時間複雜度O(N),空間複雜度爲O(1);
分析:
public static boolean isPalindrome1(Node head){
if(head == null)
return false;
Stack<Integer> st = new Stack<>();
Node temp = head;
while(temp != null) {
st.push(temp.value);
temp = temp.next;
}
temp = head;
while(temp != null) {
if (temp.value != st.pop())
return false;
temp = temp.next;
}
return true;
}
public static boolean isPalindrome2(Node head){
if(head == null)
return false;
Stack<Integer> st = new Stack<>();
Node temp= getMidNode(head).next;
while(midNode != null) {
st.push(midNode .value);
midNode = midNode .next;
}
temp = head;
while(!st.empty()) {
if (temp.value != st.pop())
return false;
temp = temp.next;
}
return true;
}
public static Node getMidNode(Node head) {
if (head.next == null || head.next.next == null)
return head;
//快慢指針
Node s = head;
Node f = head.next.next;
while(f != null) {
s = s.next;
f = f.next == null ? null : f.next.next;
}
return s;
}
以上兩種方式都開闢了額外的記憶體空間,不符合限制條件中的空間複雜度O(1),想要實現這一點,就得在原鏈表上去操作。上面我們做過單鏈表反轉的題目,那麼既然是判斷是否是迴文結構,迴文的特點就是正反讀一樣,那麼很自然想到把鏈表進行反轉,全部反轉的話,那就是重新構造了一個一模一樣的鏈表,還是要申請新的空間,只有在原鏈表上,反轉後半部分,這樣才能 纔能實現不申請額外的記憶體空間。注意判斷結束返回之前要把改變的鏈表恢復到原樣,題目要求只是判斷是否是迴文結構。
比如:1->2->3->3->2->1
1->2->3<-3<-2<-1,其中左3節點的next指針指向null;
public static boolean isPalindrome3(Node head){
if(head == null)
return false;
//反轉之後,從兩端L、R開始向中間遍歷進行匹配,Rtemp作用是最後恢復鏈表
Node L = head;
Node temp= getMidNode(head);
Node R = reverseList(temp);
Node Rtemp = R;
while(L != null) {
if (L.value != R.value){
reverseList(Rtemp); //恢復鏈表
return false;
}
L = L.next;
R = R.next;
}
reverseList(Rtemp);//恢復鏈表
return true;
}
public static Node getMidNode(Node head) {
if (head.next == null || head.next.next == null)
return head;
//快慢指針
Node s = head;
Node f = head.next.next;
while(f != null) {
s = s.next;
f = f.next == null ? null : f.next.next;
}
return s;
}
public static Node reverseList(Node head) {
//指針變數,pre表示操作節點的前一個節點,初始都未null,next爲操作節點的後一個節點
Node pre = null;
Node next;
while(head != null){
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
這裏獲得中間節點,當鏈表有奇數個節點,返回中間節點,如果是奇數個節點,返回的是上中節點。如果有題目要求是下中節點,只需要把s指針初始指向head.next就可以了。
題目:
給定某個值和一個亂序單鏈表(整數型value),將鏈表整體的的前部分小於該值(如果有的話),中間部分等於該值(如果有的話),後部分大於該值(如果有的話)。
分析:
這個題目在鏈表中出現會感覺奇奇怪怪,很熟悉,因爲陣列的題目中算是高頻題目了,註明的荷蘭國旗問題就是這類題目的原型,排序演算法中的快排就是著名的應用之一,這裏也是可以如此解決,把鏈表的各節點放到陣列nodeArr[]裡,進行快排,再把返回的陣列重新構造出鏈表就成了。這兒就不coding了,應該很簡單的。
public static Node classificationByNum(Node head, int num){
//6個變數
Node SH = null;
Node ST= null;
Node EH= null;
Node ET= null;
Node BH= null;
Node BT= null;
//遍歷鏈表,按值分成三個鏈表
while(head!= null){
if(head.value < num) {
process(SH, ST, head);
}else if(cur.value == num){
process(EH, ET, head);
}else {
process(BH, BT, head);
}
head = head.next;
}
//將三部分連成一個鏈表返回
if(SH != null){
head = SH;
ST.next = (EH != null) ? EH : BH;
ET.next = BH;
}else if (EH != null){
head = EH;
ET.next = BH;
}else{
head = BH;
}
return head;
}
public static void process(Node nodeH, Node nodeT, Node cur) {
if(nodeH == null && nodeT == null){
nodeH = cur;
nodeT = cur;
}else{
nodeT.next = cur;
nodeT = cur;
}
}
題目:
鏈表的節點有兩個指針,一個是next,指向下一個節點,還有一個random指針,隨機指向鏈表中任意一個節點(包括null)。要求複製一個這樣的鏈表。
public static Node copyRandomList1(Node head){
class Node{
public int value;
public Node next;
public Node random;
public Node(int value) {
this.value = value;
}
}
HashMap map<Node> = new HashMap<>();
Node temp = head;
while(temp != null){
map.put(temp, new Node(temp.value));
temp = temp.next;
}
temp = head;
while(temp != null){
map.get(temp).next = temp.next;
map.get(temp).random = map.get(temp.random);
}
return map.get(head);
}
public static Node copyRandomList2(Node head){
class Node{
public int value;
public Node next;
public Node random;
public Node(int value) {
this.value = value;
}
}
Node temp = head;
Node nextTemp = null;
//在每個節點後複製新節點
while(temp != null){
nextTemp = temp.next;
temp.next = new Node(temp.value);
temp.next.next = nextTemp;
temp = nextTemp;
}
temp = head;
//爲新節點的random賦值
while(temp != null){
temp.next.random = temp.random;
temp = temp.next.next;
}
temp = head;
Node tempCopy = null;
Node res;
//分離鏈表
while(temp != null) {
nextTemp = temp.next.next;
if(tempCopy == null)
res = temp.next;
tempCopy = temp.next;
temp.next = nextTemp;
tempCopy.next = nextTemp!=null?nextTemp.next:null;
temp = temp.next;
}
return res;
}
public static Node isCycleList1(Node head){
HashSet<Node> set = new HashSet<>();
Node temp = head;
while(temp != null){
if(set.contains(temp))
return temp;
set.add(temp);
temp = temp.next;
}
return null;
}
public static Node isCycleList2(Node head){
Node s = head.next;
Node f = s != null ? s.next : null;
while(f != null){
//第一次相遇,就中止遍歷
if(s == f)
break;
s = s.next;
f = f.next != null ? f.next.next : null;
if(f == null)
return null;
}
//到此處還沒有返回,則有換,且s == f,s原地不動,f回到head
f = head;
//每次都走一步,再次相遇就是入環點
while(s != f){
s = s.next;
f = f.next;
}
return s;
}
對於兩個不知道是否有環的單鏈表是否相交,我們要做的第一步就是判斷兩個鏈表有沒有環。然後再去討論是否相交的問題,6.1的方法可以拿來直接用。
假設兩個鏈表爲L1和L2
public static Node isIntersect(Node L1, Node L2){
if(L1 == null || L2 == null)
return null;
Node loop1 = isCycleList2(L1);
Node loop2 = isCycleList2(L2);
//倆無環單鏈表
if(loop1 == null && loop2 == null){
return noLoopListIntersect(L1, L2);
}
if(loop1 != null && loop2 != null){
//先判斷是三種情況的哪一種
if(loop1 == loop2){
loop1.next = null;
return noLoopListIntersect(L1, L2);
}else{
Node temp1 = loop1.next;
//如果回到loop1之前遇到了loop2,返回任一入環點;如果沒有遇到程式到最後返回null
while(temp == loop1){
if(temp == loop2){
return loop1;
}
temp = temp.next;
}
}
}
return null;
}
public static Node noLoopListIntersect(Node L1, Node L2){
Node temp = L1;
Node temp1= null;
int len1 = 0;
int len2 = 0;
Node end1;
Node end2;
while(temp != null) {
if (temp.next == null)
end1 = temp;
temp = temp.next;
len1++;
}
temp = L2;
while(temp != null) {
if(temp.next == null)
end2 = temp;
temp = temp.next;
len2++;
}
if(end1 != end2)
return null;
int lenAbs = Math.abs(len1-len2);
if(len1 > len2){
temp = L1;
temp1 = L2;
}else{
temp = L2;
temp2 = L1;
}
while(lenAbs != 0){
temp = temp.next;
lenAbs--;
}
while(temp != temp2){
temp = temp.next;
temp2 = temp2.next;
}
return temp;
}