歡迎關注個人數據結構專欄哈
劍指offer系列
:
劍指offer(1-10題)詳解
劍指offer(11-25題)詳解
劍指offer(26-33題)詳解
劍指offer(34-40題)詳解
劍指offer(51-59題)詳解
劍指offer(34-40題)詳解
微信公衆號:bigsai
聲明:大部分題基本未參考題解,基本爲個人想法,如果由效率太低的或者錯誤還請指正!!
26 二叉搜索樹與雙向鏈表
題目描述
輸入一棵二叉搜索樹,將該二叉搜索樹轉換成一個排序的雙向鏈表。要求不能創建任何新的結點,只能調整樹中結點指針的指向
思路:
關於二叉樹搜索(排序樹)需要我們轉換成一個排序的雙鏈表。我們知道二叉樹有left
和right
。用來表示樹的左右結構,但是它要求我們將樹形結構轉換成鏈式,並且還是有序的,並且還不能建立新的節點。二叉搜索樹(排序樹)我們知道它的中序序列是有序的,所以我們可以使用非遞歸的中序遍歷,先想辦法將節點按照中序的規則進行釋放。根據釋放的順序,記錄前一個節點互相記住左右就好了。
對於二叉樹各種遍歷以及遞歸非遞歸方式,筆者以前都梳理過,大家可以看看。
利用棧記錄一下節點,用一個prenode記錄前一個節點指針,當拋出當前節點兩個節點左右互指以下就OK了。當然,要有個headnode記錄第一個節點返回。
有使用遞歸版本的過也可以,可以自行嘗試。
實現代碼爲:
import java.util.Stack;
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public TreeNode Convert(TreeNode pRootOfTree) {
Stack<TreeNode> q1 = new Stack();
TreeNode preNode=null;//前
TreeNode headNode=null;
while(!q1.isEmpty()||pRootOfTree!=null)
{
if (pRootOfTree!=null) {
q1.push(pRootOfTree);
pRootOfTree=pRootOfTree.left;
}
else {
pRootOfTree=q1.pop();//彈出
if(preNode==null){preNode=pRootOfTree;headNode=preNode;}
else
{
preNode.right=pRootOfTree;
pRootOfTree.left=preNode;
}
preNode=pRootOfTree;
pRootOfTree=pRootOfTree.right;
}
}
return headNode;
}
}
27 字符串的排列
題目描述
輸入一個字符串,按字典序打印出該字符串中字符的所有排列。例如輸入字符串abc,則打印出由字符a,b,c所能排列出來的所有字符串abc,acb,bac,bca,cab和cba。
輸入描述:
輸入一個字符串,長度不超過9(可能有字符重複),字符只包括大小寫字母。
思路:
這題我不知道別人有啥方法,但是我的第一感覺就是全排列問題。當然我知道的全排列有兩種實現方法。一種是dfs回溯法,另一種是普通遞歸+排序才能過這題,因爲dfs的全排列是有序的,而普通遞歸交換的全排列是沒序的。這題兩者效率可能相差不大,但就全排列的序列而言。掌握遞歸的全排列效率更高。當然這也是筆者所採用的。至於全排列筆者以前雖然記錄過但寫的一般有興趣可以參考其他講解。兩種方式實現全排列。
當然,這題它可能有相同的字母,所以需要用個Hash去重下更好,但我懶直接用list判斷存不存在了。
實現代碼爲:
import java.util.ArrayList;
public class Solution {
public ArrayList<String> Permutation(String str) {
ArrayList<String> list=new ArrayList<String>();
char a[]=str.toCharArray();
arrange(a, 0, a.length - 1,list);
list.sort(null);
return list;
}
private void arrange(char[] a, int start, int end, ArrayList<String> list) {
// TODO Auto-generated method stub
if (start == end) {
String team=getstr(a);
if(!list.contains(team))list.add(getstr(a));
return;
}
for (int i = start; i <= end; i++) {
swap( i, start,a);
arrange(a, start + 1, end,list);
swap( i, start,a);
}
}
private String getstr(char[] a) {
String str="";
for(int i=0;i<a.length;i++)
str+=a[i];
return str;
}
private void swap(int i, int j, char a[]) {
char team=a[i];
a[i]=a[j];
a[j]=team;
}
}
28 數字中出現次數超過一半的數字(待優化)★
題目描述
數組中有一個數字出現的次數超過數組長度的一半,請找出這個數字。例如輸入一個長度爲9的數組{1,2,3,2,2,2,5,4,2}。由於數字2在數組中出現了5次,超過數組長度的一半,因此輸出2。如果不存在則輸出0。
思路:
這題是一道經典題。我只知道有一個更好方法但是我還沒消化。二輪吸收討論區的時候肯定會加上的。而我採用笨方法就是hashmap存入判斷。有則返回。這樣就是開了更大的內存。這題後面還會優化。
實現代碼爲:
import java.util.HashMap;
import java.util.Map;
public class Solution {
public int MoreThanHalfNum_Solution(int[] array) {
if(array.length==1)return array[0];
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
int len = array.length / 2;
int team = 0;
for (int a : array) {
if (map.containsKey(a)) {
team = map.get(a);
if (team >= len)
return a;
else {
map.put(a, team + 1);
}
} else {
map.put(a, 1);
}
}
return 0;
}
}
參考討論區:
這題是經典面試題,很多情況會問不允許開更大的空間完成這個要求,這就要根據數據的特點來完成:
如果有大於一半的數!那麼假設所有數爭奪一個獵物!同樣的聚成羣,不一樣的1v1
同歸於盡,那麼最後剩的一羣或者一個那麼就是大於一半的(如果存在一半的話)!
- 在具體實現就是設兩個數,一個數
num
等於遍歷的數,另一個count
就是出現的次數。 - 如果遍歷的數等於num,那麼
count++
; - 如果不等於,那麼消滅一個num,
count--
,如果count被減成0,那麼就換個數,同時count變成1.
實現代碼爲:
public int MoreThanHalfNum_Solution(int[] array) {
int num=array[0];int count=0;
for(int i=0;i<array.length;i++)
{
if(num==array[i])count++;
else {
if(--count==0)
{
num=array[i];
count=1;
}
}
}
if(count==0)return 0;
count=0;
for(int i=0;i<array.length;i++)
{
if(num==array[i])count++;
}
if(count>array.length/2)
return num;
return 0;
}
29 最小的K個數
題目描述
輸入n個整數,找出其中最小的K個數。例如輸入4,5,1,6,2,7,3,8這8個數字,則最小的4個數字是1,2,3,4,。
思路:
排序取前K個數字,當然需要討論下特殊情況。
實現代碼:
import java.util.Arrays;
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> list=new ArrayList<Integer>();
if(k>input.length)return list;
Arrays.sort(input);
for(int i=0;i<k;i++)
{
list.add(input[i]);
}
return list;
}
}
30 連續子數組最大和
題目描述
HZ偶爾會拿些專業問題來忽悠那些非計算機專業的同學。今天測試組開完會後,他又發話了:在古老的一維模式識別中,常常需要計算連續子向量的最大和,當向量全爲正數的時候,問題很好解決。但是,如果向量中包含負數,是否應該包含某個負數,並期望旁邊的正數會彌補它呢?例如:{6,-3,-2,7,-15,1,2,2},連續子向量的最大和爲8(從第0個開始,到第3個爲止)。給一個數組,返回它的最大連續子序列的和,你會不會被他忽悠住?(子向量的長度至少是1)
思路:
dp動態規劃問題問題,最長遞增子序列(LIS),和最長公共子串(LCS)都是經典問題。此題大家可以瞭解LIS。至於這種dp問題,要掌握它們的初始化+動態方程,至於這個動態方程。用dp[i]表示第i個節點的最大序列和。
而本題的dp方程爲:dp[i]=max(array[i],array[i]+dp[i-1])
我們不考慮整個串串,只考慮連個點之間的關係。1、串串連續 2、串串長度任意可以爲一.對於第i個節點,他想要最長的連續序列。那麼有兩種情況。第一種就是和前面連在一起然後加上自己。當然前面的是多少我們不管,我們只知道前面的最大時dp[i-1].或者自立門戶長度爲1不和前面的接起來。當然這需要兩者比較取最大。
實現代碼爲:
public class Solution {
public int FindGreatestSumOfSubArray(int[] array) {
int max=0;
int dp[]=new int[array.length];
dp[0]=array[0];max=dp[0];
for(int i=1;i<array.length;i++)
{
dp[i]=max(dp[i-1]+array[i],array[i]);
max=max>dp[i]?max:dp[i];
}
return max;
}
static int max(int a,int b)
{
return a>b?a:b;
}
}
31 整數中1出現的次數
題目描述
求出1~ 13的整數中1出現的次數,並算出100~ 1300的整數中1出現的次數?爲此他特別數了一下1~13中包含1的數字有1、10、11、12、13因此共出現6次,但是對於後面問題他就沒轍了。ACMer希望你們幫幫他,並把問題更加普遍化,可以很快的求出任意非負整數區間中1出現的次數(從1 到 n 中1出現的次數)。
思路:
最笨的方法(可過)。使用字符串,將從1道n的字符串拼湊成新的字符串,然後遍歷查找1就可以了。至於數學方法的話當初想了一會感覺考慮點挺多,後面還會再想想。
實現代碼:
public class Solution {
public int NumberOf1Between1AndN_Solution(int n) {
StringBuilder str=new StringBuilder();
for(int i=1;i<=n;i++)
{
str.append(i);
}
int va=0;
for(int i=0;i<str.length();i++)
{
if(str.charAt(i)=='1')va++;
}
return va;
}
}
參考討論區:
用數學方法效率更高,不過這個規律還是得經驗,,,我還是有點菜當時想了一段時間總是bug。。
32 把數組排成最小的數
題目描述
輸入一個正整數數組,把數組裏所有數字拼接起來排成一個數,打印能拼接出的所有數字中最小的一個。例如輸入數組{3,32,321},則打印出這三個數字能排成的最小數字爲321323。
思路:
貪心算法,主要是一種貪心思想。其實也是個變形排序,其實你不用考慮具體的貪心思想是什麼,你要保證整個序列排成的數字最小。那麼你就要保證任意兩個數字排序的相對數字最小哦。,本質是需要我們考慮最小前綴和組合的問題,但是對於任意兩個數,在比較接口中你直接拼湊比較就可以了。但是如果有更好方法後面會學習。
實現代碼:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
public class Solution {
public String PrintMinNumber(int [] numbers) {
Integer[] nums=new Integer[numbers.length];//賦值
for(int i=0;i<numbers.length;i++)
{
nums[i]=numbers[i];
}
Arrays.sort(nums, cmp);
StringBuilder str=new StringBuilder();
for(int i=0;i<nums.length;i++)
{
str.append(nums[i]);
}
return str.toString();
}
static Comparator<Integer>cmp=new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
//51 3551
//921 3
//51 515
String a1=o1+"";
String a2=o2+"";
return Integer.parseInt(a1+a2)-Integer.parseInt(a2+a1);
}
};
}
33 醜數★
題目描述
把只包含質因子2、3和5的數稱作醜數(Ugly Number)。例如6、8都是醜數,但14不是,因爲它包含質因子7。 習慣上我們把1當做是第一個醜數。求按從小到大的順序的第N個醜數。
思路:
這題自己的方法只過了87%的樣例。當數據大的時候就涼了。這題想了一段時間最終忍不住看了題解發現真的太雞兒秒了!
先說說我自己的想法,怎麼想的,他首先沒說數據範圍是啥,所以我一直以爲這個會是一個慢慢試探的過程。
- 剛開始嘗試的肯定是暴力。他說第N個,那我我就一個個試。每個數除以2、3、5直到無法除看看找到第n個。這種最暴力也最好想。我嘗試了但是失敗了。
- 我知道任何數都能實現質因數分解。也就是這個醜數要滿足(2a*3b*5c)。我抱着僥倖嘗試用三層循環(剪枝優化一下次數),將(2a*3b*5c)的所有可能性全部添加到集合中然後排個序取出。但是結果依然爆炸。
- 根據第二種的進行優化。我是這樣想的,這個序列我們是不是可以用一個優先隊列來維護它?比如將
1
放進去,然後拋出的元素放入到hashset中去重
。每次拋出的數值m將m * 2,m * 3, m * 5
放入優先隊列,然後再拋最小的出來加入set。直到set中元素滿了爲止。但是面對巨大的數字依然失敗了。因爲佔據大空間,思想沒錯,但是巨大重複!!
整個過程數組這個結構都被我忽略了!!!!實在hold不出來,忍不住去參考了下討論區發現這個方法真的是太好了。其實思想還是跟我上面的很相似,只是結構用了最優結構——數組!
簡單來說就是用了三個光標,a2,a3,a5.每次比較三個位置分別*2,*3,*5
誰最小,然後對應光標往右移動一位。!到結束即可!
看個圖就理解了:
實現代碼爲:
import java.util.*;
public class Solution {
public static int GetUglyNumber_Solution(int index) {
int a2=0,a3=0,a5=0;
if(index<7)return index;//小於7都是
List<Integer>list=new ArrayList<Integer>();
list.add(1);
for(int i=1;i<index;i++)
{
int min=Math.min(list.get(a2)*2, Math.min(list.get(a3)*3, list.get(a5)*5));
list.add(min);
if(min==list.get(a2)*2)a2++;
if(min==list.get(a3)*3)a3++;
if(min==list.get(a5)*5)a5++;
}
return list.get(index-1);
}
}