劍指offer(11-25題)詳解

歡迎關注個人數據結構專欄
劍指offer系列
劍指offer(1-10題)詳解
劍指offer(11-25題)詳解
劍指offer(26-33題)詳解
劍指offer(34-40題)詳解
劍指offer(51-59題)詳解
劍指offer(34-40題)詳解
微信公衆號:bigsai
在這裏插入圖片描述
聲明:大部分題基本未參考題解,基本爲個人想法,如果由效率太低的或者錯誤還請指正!,如果有誤導,還請指正!

11 二進制種1的個數★

題目描述

輸入一個整數,輸出該數二進制表示中1的個數。其中負數用補碼錶示。。

思路
這題感覺應該還會有很多思路和解法,單身筆者自身只寫了兩個方法吧,後面討論看題解會進行補充。

法一直接轉成二進制字符串。然後進行比較裏面的1的個數。

法二: 大家知道每個類型的數據它的背後實際都是二進制操作。大家知道int的數據類型的範圍是(-231,231-1)。並且int有32位。但是並非32位全部用來表示數據。真正用來表示數據大小的也是31位。最高位用來表示正負

首先大家要知道:

1<<0=1 =00000000000000000000000000000001
1<<1=2 =00000000000000000000000000000010
1<<2=4 =00000000000000000000000000000100
1<<3=8 =00000000000000000000000000001000
. . . . . .
1<<30=230 =01000000000000000000000000000000
1<<31=-231 =10000000000000000000000000000000

其次還要知道位運算&與。兩個十進制與運算.每一位同1爲1。所以我們用2的正數次冪與知道的數分別進行與。如果結果不爲0,那麼就說明這位爲1.(前面31個都是大於0的最後一個與結果是負數但是如果該位爲1那麼結果肯定不爲0)

在這裏插入圖片描述

代碼:

 public int NumberOf1(int n) {
		int va=0;
    	String num=Integer.toBinaryString(n);
    	for(int i=0;i<num.length();i++)
    	   {
    		   if(num.charAt(i)=='1')
    		   {
    			 va++;
    		   }
    	   }
       return va;
	}
	 public int NumberOf2(int n) {
			int va=0;
	        for(int i=0;i<32;i++)
		        {
		        	if((n&(1<<i))!=0)
		        	{	        		
		        		va++;
		        	}
		        }
		       return va;	      
	   }
	 public int NumberOf3(int n) {//上面的差不多,可能慢一點
			int va=0;
	    	int num=1;
	        for(int i=0;i<32;i++)
		        {        	    
		        	if((n&num)!=0)
		        	{	        		
		        		va++;
		        	}
		        	num*=2;
		        }
		       return va;	      
	   }

評論區參考:
看了評論區,發現前面的方法很多人採用,但是也有些有差別的:我那個是用這個數分別和2,4,8,16等進行位運算計算,還有事用位運算右移每次和1進行位運算比較。其實本質一樣的,就是選的靜止參照物不同而已

而還有一種非常棒的方法,確實沒想到,就是運用n&(n-1)。n如果不爲0,那麼n-1就是二進制第一個爲1的變爲0,後面全爲1.這樣的n&(n-1)一次運算就相當於把最後一個1變成0.這樣一直到運算的數爲0停止計算次數就好了。
在這裏插入圖片描述
實現代碼爲:

public class Solution {
    public int NumberOf1(int n) {
	   int count=0;
	   while (n!=0) {
		n=n&(n-1);
		count++;
	}
	   return count;
	}
}

12 數值的正數次方

題目描述

給定一個double類型的浮點數base和int類型的整數exponent。求base的exponent次方。
保證base和exponent不同時爲0

思路
法1直接利用數學包求。或者直接進行exponent次相乘。複雜度爲O(n);

法2:快速冪求法。首先快速冪思想可以參考以前寫的一個記錄快速冪模板。但是這題有點不同就是不需要求餘。也就是說數值不會越界,數值可能較小。
還有一點快速冪適合正數次冪這裏可能爲負數次冪。假設a,b大於0.但是a-b可以轉化爲(1/a)b然後進行快速冪!時間複雜度爲O(logn).
在這裏插入圖片描述
代碼:

public double Power(double base, int exponent) {
        return Math.pow(base, exponent);
	  }
 	public double Power(double base, int exponent) {
		if(base==0)return 0;
		if(exponent<0) {exponent=-exponent;base=1/base;}
        if(exponent==0)return 1;
        else if (exponent % 2 == 0) {
			return Power(base*base, exponent/ 2) ;
		} else
			return base*Power(base*base, (exponent-1)/ 2);
	}

13 調整數組順序使奇數位於偶數前面

題目描述

輸入一個整數數組,實現一個函數來調整該數組中數字的順序,使得所有的奇數位於數組的前半部分,所有的偶數位於數組的後半部分,並保證奇數和奇數,偶數和偶數之間的相對位置不變。

思路:
題目要求1、奇數位於偶數前面2、奇數之間相對位置不變
注意事項: void函數,注意對象克隆,深淺拷貝。如果是java最好先了解java函數調用參數(指針)問題。

  • 需要新開數組team複製(克隆)array數組
  • 進行兩次遍歷team,一次從左向右找奇數從左向右賦值給array對應位置。
  • 一次從右向左遍歷team將數值從右往左賦值給array對應位置。
public class Solution {
public void reOrderArray(int [] array) {
        int team[]=array.clone();
        int index=0;
        for(int i=0;i<team.length;i++)
        {
        	if((team[i]&1)==1)//如果爲奇數
        		array[index++]=team[i];    		
        }
        index=team.length-1;
        for(int i=team.length-1;i>=0;i--)
        {
        	if((team[i]&1)==0)
        		array[index--]=team[i];    		
        }
    }
}

參考討論區:
我是基於開闢新空間數組跑兩遍實現的,但是有的討論區不開闢新空間的話那就只能考慮一些排序(插入排序)。當然,在vx打卡交流羣中有個小姐姐曾經用兩個隊列跑一遍儲存感覺思想也很棒!還沒想到用隊列呢!

14 鏈表中倒數第K個節點

題目描述

輸入一個鏈表,輸出該鏈表中倒數第k個結點。

思路:
感覺這題可以搞得實現方式比較多吧。
比如你可以藉助一個Arraylist之類集合將鏈表遍歷加入。根據集合大小和K直接取值(自行ac)。

比如你還可以不借助外部集合,第一次遍歷到尾獲得鏈表長度l。根據長度l和倒數第k個節點獲得正數的序號。再遍歷一次取該節點的值即可!

代碼:

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
     		public ListNode FindKthToTail(ListNode head, int k) {

			int count = 0;
			ListNode team = head;
			while (team != null) {
				count++;
				team = team.next;
			}
			int value = count - k;
			int index = 0;
			while (head!= null) {
				if (index == value) {
					return head;
				}
				index++;
				head = head.next;
				
			}
			return head;
		}
	    }

參考討論區
這題參考了討論區發現了一些其他也很不錯的解法:
比如遞歸先到頭,返回的時候對整型數值疊加符合題意返回,判斷處理即可!
還有就是定義快慢指針。這個思想就像兩輛車中間拖個繩子,前車走的如果超過繩子長度就拉着後車保持繩子長度跑。如果繩子沒拉直那就肯定不符合題意噠!
在這裏插入圖片描述

15 反轉鏈表

題目描述

輸入一個鏈表,反轉鏈表後,輸出新鏈表的表頭。

思路:
法一:最簡單的想法是遍歷整個鏈表,head用來遍歷。team用來作爲新鏈表返回。每次遍歷時候新建一個listNode指向前面的team。
在這裏插入圖片描述
實現代碼:

public  static class ListNode {
		int val;
		ListNode next = null;

		ListNode(int val) {
			this.val = val;
		}
	}

	public ListNode ReverseList(ListNode head) {
		if (head == null)
			return head;
		ListNode team = new ListNode(head.val);// head的第一個當作team的最後一個
		while (head != null) {
			head = head.next;
			if (head != null) {
				ListNode listNode = new ListNode(head.val);
				listNode.next = team;// 將它指向lsitNode
				team = listNode;// team指向listnode
			}
		}
		return team;
	}

法二(參考):參考了評論區一個遞歸的方案,感覺很巧妙,至於理解就像圖所畫。

實現代碼爲:

public static ListNode ReverseList(ListNode head) {
		if (head == null||head.next==null)
			return head;			
		else {			
			ListNode node=ReverseList(head.next);
			head.next.next = head;
	         head.next = null;	
			return node;	 
		}

理解爲:
在這裏插入圖片描述

16 合併兩個排序的鏈表

題目描述

輸入兩個單調遞增的鏈表,輸出兩個鏈表合成後的鏈表,當然我們需要合成後的鏈表滿足單調不減規則。

思路:
感覺可行方案還是不止一個的。

比如兩個鏈表你可以用一個list1作爲主鏈表返回。返回另一個list2進行遍歷比較插入到主鏈表適當位置中。有興趣可以試一試。
當然你還可以直接建立一個新鏈表頭節點value。list1和list2同時遍歷,取小的節點添加到value爲頭節點的鏈表中。同時小的那個鏈表向下進行下一輪比較。直到list1和list2都爲null爲止。

代碼爲:

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
   	 public ListNode Merge(ListNode list1,ListNode list2) {
		   if(list1==null)return list2;
		   else if (list2== null) {return list1;
			
		}
	       ListNode value=new ListNode(0);
	       ListNode team=value;
	       while(list1!=null&&list2!=null)
	       {
	    	   if(list1.val<list2.val)
	    	   {
	    		   team.next=new ListNode(list1.val);
	    		   list1=list1.next;
	    		   team=team.next;
	    		   if(list1==null)
	    		   {
	    			   team.next=list2;
	    		       break;   
	    		   }
	    	   }
	    	   else {
	    		   team.next=new ListNode(list2.val);
	    		   list2=list2.next;
	    		   team=team.next;
	    		   if(list2==null)
	    		   {
	    			   team.next=list1;
	    		       break;   
	    		   }	    		   
	    	   }
	       }
	       value=value.next;
	       return value;	      
	    }
}

參考討論區:
有個圖感覺很精妙,省的畫啦,當然我是用用兩個節點一直while(這樣的比較),而討論區有兄弟用遞歸的方法感覺也很不錯的!具體討論可以到牛客討論區看得到哈。
在這裏插入圖片描述

17 樹的子結構

題目描述

輸入兩棵二叉樹A,B,判斷B是不是A的子結構。(ps:我們約定空樹不是任意一個樹的子結構)

思路:
這題當時還卡住了。判斷B是不是A的子結構。我最初想着是不是直接在A樹上某個節點開始結構一直往下就和B相同。但這顯然是錯的。因爲B是A的子結構。很可能不到葉子節點就是中間的一小部分。但是這個也不難解決。只需要判斷B到葉子節點停止一下就可以了。

這題需要一個節點一個節點的進行比較。我才用隊列的層序遍歷(bfs)。當有相同的就停止返回。執行到最後不停止就說明這個是沒有相同的
在這裏插入圖片描述

代碼爲:

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
import java.util.ArrayDeque;
import java.util.Queue;
public class Solution {
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
	    
	    Queue<TreeNode>q1=new ArrayDeque<TreeNode>();
        if (root2==null||root1==null)
        	return false;
        else {
        	 q1.add(root1);
			while (!q1.isEmpty()) {
				TreeNode t1=q1.poll();
				if(jud(t1, root2))
					return true;
				else {
				    if(t1.left!=null)q1.add(t1.left);
				    if(t1.right!=null)q1.add(t1.right);
				}
			}
			return false;
        }
    }
public boolean jud(TreeNode root1,TreeNode root2) {
	
	if(root2==null)return true;
	else if(root1==null)
	{
		return false;
	}
	else if(root1.val==root2.val) {
		return (jud(root1.left, root2.left)&&jud(root1.right,root2.right));		
	}	
	else {
		return false;
	}
}
}

參考評論區:
主要思想差不多吧,要注重子樹和子結構的區別。當然個別大佬把HasSubtree函數用遞歸寫了感覺效率很差,就不貼了有興趣的可以自己研究哈!

18 二叉樹的鏡像

題目描述

操作給定的二叉樹,將其變換爲源二叉樹的鏡像。

輸入描述

二叉樹的鏡像定義:源二叉樹 
    	    8
    	   /  \
    	  6   10
    	 / \  / \
    	5  7 9 11
    	鏡像二叉樹
    	    8
    	   /  \
    	  10   6
    	 / \  / \
    	11 9 7  5

思路:
最直觀和好想的肯定是遞歸法。遇到二叉樹這種未知結構大部分都可以採用遞歸求解,只需要交換節點左右子樹即可。而上述的遞歸過程大致如下:

二叉樹的鏡像定義:源二叉樹 
    	    8
    	   /  \
    	  6   10
    	 / \  / \
    	5  7 9  11
    	//step1 第三層交換
    	    8
    	   /  \
    	  6   10
    	 / \  / \
    	7  5 11  9
    	//step2  第二層交換,第一層不需要交換
    	    8
    	   /  \
    	  10   6
    	 / \  / \
    	11 9 7   5

實現代碼爲:

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
     public void Mirror(TreeNode root) {
         if(root==null) return;
    	TreeNode teamNode=root.right;
		root.right=root.left;
		root.left=teamNode;
		if(root.left!=null) {Mirror(root.left);}
		if(root.right!=null) {Mirror(root.right);}
    }
}

19 順時針打印矩陣

題目描述

輸入一個矩陣,按照從外向裏以順時針的順序依次打印出每一個數字,例如,如果輸入如下4 X 4矩陣: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 則依次打印出數字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

思路:
這題其實方法還是蠻重要的。個人主要兩個想法吧:
思路一:模擬

  • 大家都知道是順時針進行旋轉式的輸出。那麼這本身就是一次運動,那麼我們就可以模擬這個光標的運動,一直到結束。
  • 想法雖然簡單,但是實現起來很麻煩!比如,你可能需要用參數標記當前的方向,還可能需要用個boolean數組判斷是否走過和是否越界問題。還可能需要多個if else當判定當前結束選擇下一個方向。想想這個代碼一定是很臃腫的。雖然可以實現,有興趣的可以嘗試。
    在這裏插入圖片描述

思路二:數學計算(不一定說的很好可以自行理解)
正常情況下:一個順時針是進行四次!但是每次走的位置咱們是可以確定的。咱麼假設豎的爲X,橫的爲Y。

  • 先不考慮走完的問題。每次走的四次開始的座標和結束的座標是可以算出來的!因爲相當於每走一圈橫縱座標都減二。而開始和結束位置分別加1和減1。所以每次走的結束位置可以準確表示出來。
    在這裏插入圖片描述
  • 考慮走完的問題。肯定是根據窄的走完。問題還有奇偶的問題。如果偶數的話可以剛好走完,而奇數需要特殊判斷一下省略最後一次循環一些步驟才行!
    在這裏插入圖片描述
  • 其他注意點。當循環添加一輪的時候,實際上當前的座標是有變化的。需要手動修正一下
    在這裏插入圖片描述

實現代碼爲:

import java.util.ArrayList;
public class Solution {
     public ArrayList<Integer> printMatrix(int [][] matrix) {
		ArrayList<Integer> list=new ArrayList<Integer>();
		int xlen=matrix.length;
		int ylen=matrix[0].length;
		int min=Math.min(xlen, ylen);
		int x=0;//上下 縱座標
		int y=0;//左右 橫座標
		for(int i=0;i<(min+1)/2;i++)
		{
		  for(;y<ylen-i;y++) {list.add(matrix[x][y]);}y--;x++;
		   for(;x<xlen-i;x++) {list.add(matrix[x][y]);}x--;y--;
		   if(i==xlen/2)continue;
		   for(;y>=i;y--) {list.add(matrix[x][y]);}y++;x--;
		    if(i==ylen/2)continue;
		   for(;x>i;x--) {list.add(matrix[x][y]);}x++;y++;
		    }
		return list;
}
}

20 包含main函數的棧

題目描述

定義棧的數據結構,請在該類型中實現一個能夠得到棧中所含最小元素的min函數(時間複雜度應爲O(1))。

思路:
這題感覺這樣要求返回棧中最小元素爲O(1),那麼說明需要有個常數min來維護這個最小值,可以直接返回。而這個min維護的時候只需要注意兩點:

  1. push加入的時候是不是更小,如果更小的話需要更新這個min。
  2. pop元素是不是和min一樣大。如果比min大不需要管。但是如果和min一樣大那麼就需要遍歷重新確定min。當然整個函數的最大開銷也就在這裏了。

實現代碼爲:

import java.util.Stack;
public class Solution {
private int a[]=new int [1000];
	private int index=0 ;
	private int min=Integer.MAX_VALUE;
    public void push(int node) {
	   a[index]=node;
	   if(a[index]<min)min=a[index];
	   index++;
    }   
    public void pop() {
        if(a[--index]==min)
        {
        	min=Integer.MAX_VALUE;
        	for(int i=0;i<index;i++)
        	{
        		if(a[i]<min)
        		{
        			min=a[i];
        		}
        	}
        }       
    }   
    public int top() {
        return a[index-1];        
    }    
    public int min() {
        return min;
    }
}

21 棧的壓入、彈出序列

題目描述

輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否可能爲該棧的彈出順序。假設壓入棧的所有數字均不相等。例如序列1,2,3,4,5是某棧的壓入順序,序列4,5,3,2,1是該壓棧序列對應的一個彈出序列,但4,3,5,1,2就不可能是該壓棧序列的彈出序列。(注意:這兩個序列的長度是相等的)

思路:
這題核心思想是模擬吧。給了壓入序列看是不是彈出序列。我們假設這就是個彈出序列。然後根據這個彈出序列的順序。將第一個數組的數據一個一個壓入棧。當遇到在彈出序列當前位置的值時候就彈出。最終如果棧爲空那麼說明假設成立,如果不爲空說明這個假設失敗,就不是彈出序列
在這裏插入圖片描述
實現代碼爲:

import java.util.ArrayList;
import java.util.Stack;
public class Solution {
    	public boolean IsPopOrder(int[] pushA, int[] popA) {
		Stack<Integer> stack = new Stack<Integer>();
		int index = 0;
		for (int i = 0; i < pushA.length; i++) {// 找到第一個push元素的位置
			stack.push(pushA[i]);
			//while (!stack.isEmpty()) {
				while (!stack.isEmpty()&&stack.peek() == popA[index]) {
					stack.pop();
					index++;
				//}
				//break;
			}
		}
		if (stack.size() == 0)
			return true;
		else {
			return false;
		}
	}
}

22 從下往上打印二叉樹

題目描述

從上往下打印出二叉樹的每個節點,同層節點從左至右打印。

思路:
用隊列,就是二叉樹的層序遍歷。bfs的思想。

實現代碼:

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Queue;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;
    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
     public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
		    
	        ArrayList<Integer>list=new ArrayList<Integer>();
	        if(root==null)return list;
	        Queue<TreeNode>q1=new ArrayDeque<>();
	        q1.add(root);
	        while (!q1.isEmpty()) {
				TreeNode node=q1.poll();
				list.add(node.val);
				if(node.left!=null) {q1.add(node.left);}
				if(node.right!=null) {q1.add(node.right);}
			}
	        return list;
	    }
}

23 二叉搜索樹的後序遍歷序列

題目描述

輸入一個整數數組,判斷該數組是不是某二叉搜索樹的後序遍歷的結果。如果是則輸出Yes,否則輸出No。假設輸入的數組的任意兩個數字都互不相同。

思路:
這個題和前面的前中確定構造二叉樹那題思想很想。後序遍歷順序爲左區域 右區域 根,其中每個節點的左區域肯定小於根,右區域全部大於根。同樣裏面的每個小區域也滿足這個要求,再考慮一下邊界和特殊情況,合理使用遞歸即可!
在這裏插入圖片描述
實現代碼:

public class Solution {
  public boolean VerifySquenceOfBST(int [] sequence) {
	    if (sequence.length == 0)
			return false;
		return jud(sequence, 0, sequence.length - 1);
    }
    public boolean jud(int a[],int left,int right)//左右包含左和右
    {
    	int index=0;int mid=0;
    	if(left>=right)return true;
    	for(;index<right;index++)
    	{
    		if(a[index]>a[right])
    			break;
    	}
    	mid=index;
    	for(;index<right;index++)
    	{
    		if(a[index]<a[right])break;
    	}
    	if(index!=right)return false;
    	else {
			return jud(a, left, mid-1)&&jud(a, mid, right-1);
		}
    }
}

24 二叉樹中和爲某一值的路徑

題目描述

輸入一顆二叉樹的跟節點和一個整數,打印出二叉樹中結點值的和爲輸入整數的所有路徑。路徑定義爲從樹的根結點開始往下一直到葉結點所經過的結點形成一條路徑。(注意: 在返回值的list中,數組長度大的數組靠前)

思路:
dfs深度優先遍歷。要注意的是要遍歷到葉子節點的路徑。如果有中途遍歷的時候路徑滿足而非葉子節點是不停止的。

dfs(TreeNode root, int target, ArrayList<Integer> list, ArrayList<ArrayList<Integer>> list2

而這樣一個dfs函數target是個參數,每到達一個點會減去對應值。list是當前遍歷的路徑存儲列表。而這個dfs是一個回溯的過程,list根據遞歸過程元素是不斷變化的。所以這個list不能直接加到list2中。當遍歷到根節點的時候滿足條件要用新的list3複製list的值加入到list2中如果對這個地方不理解請百度深度優先搜索或者回溯法 。 當然更多還需要自己理解的。

另外,要求路徑中較長的路徑在前面。這裏我就實現了個Comparator排個序就行了。

實現代碼爲:

import java.util.ArrayList;

import java.util.Comparator;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
     public  ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {

		ArrayList<Integer> team = new ArrayList<Integer>();
		ArrayList<ArrayList<Integer>> list = new ArrayList<ArrayList<Integer>>();
		if (root == null) {
			return list;
		}
		dfs(root, target, team, list);
		return list;
	}

	static Comparator<ArrayList<Integer>> comparator = new Comparator<ArrayList<Integer>>() {

		public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2) {
			// TODO Auto-generated method stub
			return o2.size() - o1.size();
		}
	};

	public  void dfs(TreeNode root, int target, ArrayList<Integer> list, ArrayList<ArrayList<Integer>> list2) {
		if (target < 0) {// 停止
		} else {
			list.add((Integer) root.val);
			target -= root.val;
			if (root.left != null)
				dfs(root.left, target, list, list2);

			if (root.right != null)
				dfs(root.right, target , list, list2);
			 if (root.left==null&&root.right==null&&target == 0) {//葉子節點
				ArrayList<Integer> list3 = new ArrayList<Integer>();
				list3.addAll(list);
				list2.add(list3);
			}
            target+=root.val;
			list.remove((Integer) root.val);
		}
	}
}

參考評論區
評論區看到有個遞歸寫的非常好,比我的要精簡很多,但是沒有保證最短的在前面(需要自己排序一下,爲了更好實現可以寫個新函數代替這個只能在返回的函數中只需進行一次排序)。但是過了說明這個數據還是很水的。大家可以參考這個過程。
在這裏插入圖片描述

25 複雜鏈表的複製

題目描述:

輸入一個複雜鏈表(每個節點中有節點值,以及兩個指針,一個指向下一個節點,另一個特殊指針指向任意一個節點),返回結果爲複製後複雜鏈表的head。(注意,輸出結果中請不要返回參數中的節點引用,否則判題程序會直接返回空)

思路:
說實話,這題自己還卡了不少時間。剛開始一直沒想到該怎麼處理。後面經過自己不斷思考終於克服了!——你雖然是鏈表,但我想怎麼搞怎麼搞(搞個數組)。

首先明確,他讓我們克隆,也就是不能引用原鏈表的任何一個節點。也就是要100%的創造和克隆。大致分析思路如下:

  1. 它每個節點有一個next,有一個random。也就是一個指向後面,一個瞎指的。 如果兩個指針同時考慮真的太複雜了,那我先分開考慮。只看next他就是一個鏈表。對吧(先不考慮循環)
  2. 我們用個RandomListNode list[]遍歷鏈表將鏈表各個節點存進去。RandomListNode clone[]是我們想克隆的數組。至於克隆數組每個對應位子應該和list是相似的。並且每個next應該指向下一位clone[i].next=clone[i+1],至於每個random的位置。我們可以挨個遍歷list中random的相對位置,然後clone的random也指向相應位置。這個是開銷最大的部分,時間複雜度爲O(n2).但是我自己沒想到更好的方法,後面參考題解如果有更好方法會進行更新吧。
    在這裏插入圖片描述
    實現代碼爲:
/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
public class Solution {
public RandomListNode Clone(RandomListNode pHead) {
		//step1 計算有多少個
		int count=0;
		RandomListNode team=pHead;
		while(team!=null)
		{count++;team=team.next;if(team==pHead)break;}//先不考慮是否會是循環鏈表
		//step2 將每個位置的指針存在
		RandomListNode list[]=new RandomListNode[count+1];
		RandomListNode clone[]=new RandomListNode[count+1];
	    team=pHead;
        count=0;
		while(team!=null)
		{
			clone[count]=new RandomListNode(team.label);
			list[count++]=team;
		    team=team.next;
		    if(team==pHead)break;		    
		}
		//step3 克隆指針
		
		for(int i=0;i<count;i++)
		{
			clone[i].next=clone[i+1];
			for(int j=0;j<count;j++)
			{
				if(list[i].random==list[j])
				{
					clone[i].random=clone[j];
					break;
				}
			}
		}
		
		return clone[0];
	}
}

參考討論區:
這題在討論區發現很多人再用的非常巧妙的效率很高的方法(誰是原創也不知道)。

實現代碼爲:

鏈接:https://www.nowcoder.com/questionTerminal/f836b2c43afc4b35ad6adc41ec941dba?f=discussion
來源:牛客網
/*
*解題思路:
*1、遍歷鏈表,複製每個結點,如複製結點A得到A1,將結點A1插到結點A後面;
*2、重新遍歷鏈表,複製老結點的隨機指針給新結點,如A1.random = A.random.next;
*3、拆分鏈表,將鏈表拆分爲原鏈表和複製後的鏈表
*/
public class Solution {
    public RandomListNode Clone(RandomListNode pHead) {
        if(pHead == null) {
            return null;
        }
         
        RandomListNode currentNode = pHead;
        //1、複製每個結點,如複製結點A得到A1,將結點A1插到結點A後面;
        while(currentNode != null){
            RandomListNode cloneNode = new RandomListNode(currentNode.label);
            RandomListNode nextNode = currentNode.next;
            currentNode.next = cloneNode;
            cloneNode.next = nextNode;
            currentNode = nextNode;
        }
         
        currentNode = pHead;
        //2、重新遍歷鏈表,複製老結點的隨機指針給新結點,如A1.random = A.random.next;
        while(currentNode != null) {
            currentNode.next.random = currentNode.random==null?null:currentNode.random.next;
            currentNode = currentNode.next.next;
        }
         
        //3、拆分鏈表,將鏈表拆分爲原鏈表和複製後的鏈表
        currentNode = pHead;
        RandomListNode pCloneHead = pHead.next;
        while(currentNode != null) {
            RandomListNode cloneNode = currentNode.next;
            currentNode.next = cloneNode.next;
            cloneNode.next = cloneNode.next==null?null:cloneNode.next.next;
            currentNode = currentNode.next;
        }
         
        return pCloneHead;
    }
}

歡迎關注公衆號:bigsai
在這裏插入圖片描述

發佈了196 篇原創文章 · 獲贊 1879 · 訪問量 75萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章