剑指offer(1-10题)详解
文章目錄
- 01二維數(shù)組的查找
- 02替換空格
- 03從尾到頭打印鏈表
- 04重建二叉樹(shù)★
- 05 用兩個(gè)棧實(shí)現(xiàn)隊(duì)列
- 06旋轉(zhuǎn)數(shù)組的最小數(shù)字
- 07 斐波那契數(shù)列
- 08 跳臺(tái)階
- 09 變態(tài)跳臺(tái)階★
- 10 矩陣覆蓋
歡迎關(guān)注個(gè)人數(shù)據(jù)結(jié)構(gòu)專(zhuān)欄哈
劍指offer系列:
劍指offer(1-10題)詳解
劍指offer(11-25題)詳解
劍指offer(26-33題)詳解
劍指offer(34-40題)詳解
劍指offer(41-50題)詳解
劍指offer(51-59題)詳解
劍指offer(60-67題)詳解
微信公眾號(hào):bigsai
聲明:大部分題基本未參考題解,基本為個(gè)人想法,如果由效率太低的或者錯(cuò)誤還請(qǐng)指正!,如果有誤導(dǎo),還請(qǐng)指正!
01二維數(shù)組的查找
題目描述
在一個(gè)二維數(shù)組中(每個(gè)一維數(shù)組的長(zhǎng)度相同),每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請(qǐng)完成一個(gè)函數(shù),輸入這樣的一個(gè)二維數(shù)組和一個(gè)整數(shù),判斷數(shù)組中是否含有該整數(shù)。
思路:
選定一個(gè)維度(行或列)先找到需要查找的元素所在的行(列),再?gòu)脑撔?列)找到該元素的該元素具體的列(行)位置。復(fù)雜度O(n)。
優(yōu)化:因?yàn)閿?shù)列是遞增有序的,可以進(jìn)行二分查找進(jìn)行優(yōu)化,但是本題可以不進(jìn)行二分也可以過(guò)。因?yàn)榇蠹矣信d趣可以去查一查編程語(yǔ)言數(shù)組可以開(kāi)多大。然后單個(gè)查找在這個(gè)范圍內(nèi)即使不優(yōu)化也不會(huì)超時(shí)。有興趣的可以自己寫(xiě)一寫(xiě)二分!復(fù)雜度O(logn)
代碼:
02替換空格
題目描述
請(qǐng)實(shí)現(xiàn)一個(gè)函數(shù),將一個(gè)字符串中的每個(gè)空格替換成“%20”。例如,當(dāng)字符串為We Are Happy.則經(jīng)過(guò)替換之后的字符串為We%20Are%20Happy。
思路:
水題,字符串遍歷重構(gòu)即可。遇到為 的字符直接在新的字符添加一個(gè)%20即可。當(dāng)然,在java中直接使用replaceAll即可。復(fù)雜度O(n);
ps: replaceall效率較低,建議使用StringBuider之類(lèi)完成
參考討論區(qū):
在討論區(qū)看到了一些不一樣的聲音,大致就是 有討論是在原字符串上進(jìn)行移動(dòng)還是新建字符串的問(wèn)題。當(dāng)然新建字符串會(huì)更簡(jiǎn)單些,但是如果遇到要求在原字符串基礎(chǔ)上進(jìn)行移動(dòng)的,因?yàn)镾tring的底層實(shí)現(xiàn)是數(shù)組,那就要首先遍歷一次知道有多少空格,然后擴(kuò)充空間。至于遍歷完空格的移動(dòng)方式:從后往前移動(dòng)的方式更好,因?yàn)樽罱K移動(dòng)的位置是相同的,但是從前往后每次遇到空格都會(huì)拖家?guī)Э诤竺嬉淮家苿?dòng)。(好比國(guó)旗下講話排隊(duì)往后挫,要挫很多次整體慢慢騰騰移動(dòng)),而從后往前插就相當(dāng)于很明確一個(gè)個(gè)明確移動(dòng)到哪。
雖然兩者最終已經(jīng)總距離一樣的,但是從前往后每個(gè)單詞要經(jīng)過(guò)(1-n)次才能移動(dòng)到最后,而從后往前每個(gè)單詞只1次就移動(dòng)到目標(biāo)位置!
代碼:
public class Solution {public static String replaceSpace(StringBuffer str) {String team=str.toString();return team.replaceAll(" ", "%20");}public static String replaceSpace(StringBuffer str) {//方法二StringBuffer str2 = new StringBuffer();char demo = ' ';for (int i = 0; i < str.length(); i++) {demo = str.charAt(i);if (demo == ' ') {str2.append("%20");} else {str2.append(demo);}}return str2.toString();} }03從尾到頭打印鏈表
題目描述
輸入一個(gè)鏈表,按鏈表從尾到頭的順序返回一個(gè)ArrayList。
思路:
題目給我們一個(gè)鏈表讓我們返回一個(gè)序列數(shù)字。而這個(gè)序列要求我們將鏈表從后向前的順序返回。當(dāng)然,這樣的話處理方法就比較多了。比如先從前往后存到一個(gè)數(shù)組中,然后數(shù)組再?gòu)暮笸巴鵏ist中塞。
當(dāng)然Arraylist本身也是一個(gè)線性表(順序表),可以進(jìn)行頭插。將鏈表每次向后遍歷的數(shù)插在首位,最后返回即可。
/** * public class ListNode { * int val; * ListNode next = null; * * ListNode(int val) { * this.val = val; * } * } * */ import java.util.ArrayList; public class Solution {public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {ArrayList<Integer>list=new ArrayList<Integer>();while (listNode!=null) {list.add(0, listNode.val);listNode=listNode.next;}return list;} }參考討論區(qū):
這題參考了討論區(qū),發(fā)現(xiàn)了一些其他比較優(yōu)秀的解法,比如可以用遞歸函數(shù)進(jìn)行插入,當(dāng)next為null的時(shí)候停止,當(dāng)然還有就是利用棧的結(jié)構(gòu)儲(chǔ)存然后拋出啦。這些思想都可實(shí)現(xiàn)!
04重建二叉樹(shù)★
題目描述
輸入某二叉樹(shù)的前序遍歷和中序遍歷的結(jié)果,請(qǐng)重建出該二叉樹(shù)。假設(shè)輸入的前序遍歷和中序遍歷的結(jié)果中都不含重復(fù)的數(shù)字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹(shù)并返回。
思路:
說(shuō)實(shí)話這題還是有難度的,以前手動(dòng)模擬的時(shí)候也沒(méi)掌握方法只是瞎畫(huà)。以前再力扣也曾經(jīng)遇到這題當(dāng)時(shí)不會(huì)停滯下來(lái)。不過(guò)這次憑借思考還是克服了。
我們都知道一個(gè)中序序列帶著一個(gè)前序或者后序序列都能確定一棵完整的二叉樹(shù)。首先分析這種問(wèn)題。二叉樹(shù)的問(wèn)題大部分有可重復(fù)性,經(jīng)常會(huì)使用遞歸。所以大部分人應(yīng)該能夠想到使用遞歸,但是可能不清楚該怎么遞歸。其實(shí)遞歸的使用不需要你考慮全篇,需要你謹(jǐn)慎完整的考慮其中一個(gè)過(guò)程。現(xiàn)在我們看看前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},的構(gòu)造過(guò)程!
代碼:
05 用兩個(gè)棧實(shí)現(xiàn)隊(duì)列
題目描述
用兩個(gè)棧來(lái)實(shí)現(xiàn)一個(gè)隊(duì)列,完成隊(duì)列的Push和Pop操作。 隊(duì)列中的元素為int類(lèi)型
思路:
首先要了解隊(duì)列,隊(duì)列是一種先進(jìn)先出(排隊(duì))的結(jié)構(gòu),而棧是一種后進(jìn)先出的結(jié)構(gòu)。如果基本概念不清可以看我以前寫(xiě)的哈。要求完成push和pop兩種操作。push就是加入隊(duì)尾(tail)類(lèi)似enqueue,而pop是返回并拋出隊(duì)頭(front)類(lèi)似dequeue。我們假設(shè)stack1是用作返回,而stack2用作中轉(zhuǎn)。可以先看看下面的圖。假設(shè)7、3、5、6在隊(duì)列中,待加入8(push8).
實(shí)現(xiàn)代碼為:
import java.util.Stack;public class Solution {Stack<Integer> stack1 = new Stack<Integer>();Stack<Integer> stack2 = new Stack<Integer>();public void push(int node) { while (!stack1.isEmpty()) {stack2.push(stack1.pop());}stack2.push(node);while (!stack2.isEmpty()) {stack1.push(stack2.pop());}stack2.clear();}public int pop() {return stack1.pop();} }06旋轉(zhuǎn)數(shù)組的最小數(shù)字
題目描述
把一個(gè)數(shù)組最開(kāi)始的若干個(gè)元素搬到數(shù)組的末尾,我們稱(chēng)之為數(shù)組的旋轉(zhuǎn)。
輸入一個(gè)非遞減排序的數(shù)組的一個(gè)旋轉(zhuǎn),輸出旋轉(zhuǎn)數(shù)組的最小元素。
例如數(shù)組{3,4,5,1,2}為{1,2,3,4,5}的一個(gè)旋轉(zhuǎn),該數(shù)組的最小值為1。
NOTE:給出的所有元素都大于0,若數(shù)組大小為0,請(qǐng)返回0。
思路:
就是要求我們?cè)谶@么一組序列中找到最小的一個(gè)數(shù)字,非遞減的旋轉(zhuǎn),也就是這么一串有兩段非遞減的連續(xù)串串。找到第二個(gè)非遞減的串串頭就是結(jié)果。
然而,我們只需第一次查找到最小即可結(jié)束。不會(huì)超時(shí)還是因?yàn)閿?shù)組大小有限制。無(wú)法提供更大量輸入數(shù)據(jù)。復(fù)雜度為O(n);
public int minNumberInRotateArray(int [] array) {if(array.length==0)return 0;int min=array[0];for(int i=0;i<array.length;i++){if(array[i]<min){min=array[i];break;}}return min;}參考題解:
之前提過(guò)二分,但是這題很多討論區(qū)的方案并非完善,漏了非遞減比如101111這種情況。而二分也只是壓縮搜索空間,如歸一個(gè)非遞減串被分成左側(cè)非遞減和右側(cè)非遞減,右側(cè)的最大要小于等于左側(cè)最小的。就跟就行分類(lèi)討論,下面分享一個(gè)講的不錯(cuò)的題解(原文鏈接):
07 斐波那契數(shù)列
題目描述
大家都知道斐波那契數(shù)列,現(xiàn)在要求輸入一個(gè)整數(shù)n,請(qǐng)你輸出斐波那契數(shù)列的第n項(xiàng)(從0開(kāi)始,第0項(xiàng)為0)。
n<=39
思路:斐波那契的公式為:
F(0)=0;F(1)=1;
F(n)=F(n-1)+F(n-2); (n>=2)
你可以使用遞歸,但是遞歸效率極低!因?yàn)檫f歸的一項(xiàng)會(huì)產(chǎn)生新的兩項(xiàng)遞歸函數(shù)。它的復(fù)雜度是O(2n)指數(shù)級(jí)別的。具體原因可以參考以前的一篇文章的動(dòng)態(tài)圖遞歸詳解。這里不做累述。因?yàn)檫f歸浪費(fèi)太多資源,進(jìn)行很多沒(méi)必要的運(yùn)算。所以我們采用數(shù)組從前往后計(jì)算。兩種方法都附上代碼。
代碼為(推薦法2):
public int Fibonacci(int n) {if (n == 1 || n == 0) {return n;} else {return Fibonacci(n - 1) + Fibonacci(n - 2);}}/** 法二,打表法*/public int Fibonacci2(int n) {int Fibo[]=new int [40];Fibo[0]=0;Fibo[1]=1;for(int i=2;i<=n;i++){Fibo[i]=Fibo[i-1]+Fibo[i-2];}return Fibo[n];}參考討論區(qū):
其他的其實(shí)沒(méi)有多少區(qū)別的,主要是動(dòng)態(tài)規(guī)劃從前往后計(jì)算不需要整個(gè)數(shù)組。只需要兩個(gè)變量就夠了,這樣空間能夠節(jié)省一些。
08 跳臺(tái)階
題目描述
一只青蛙一次可以跳上1級(jí)臺(tái)階,也可以跳上2級(jí)。求該青蛙跳上一個(gè)n級(jí)的臺(tái)階總共有多少種跳法(先后次序不同算不同的結(jié)果)。
思路:
可以遞歸或者dp吧,因?yàn)楫?dāng)前臺(tái)階F(n)可能是1步跳來(lái)的,也可能是2步跳來(lái)的。所以F(n)=F(n-1)+F(n-2).(初始情況單獨(dú)考慮)。這題遞歸和正向dp時(shí)間復(fù)雜度差不多,區(qū)別不大。
代碼為:
public int JumpFloor(int target) {int dp[]=new int[target+1];dp[0]=1;dp[1]=1;for(int i=2;i<=target;i++){dp[i]=dp[i-1]+dp[i-2];}return dp[target];}參考評(píng)論區(qū):
本題評(píng)論區(qū)的有些遞歸方案感覺(jué)感覺(jué)不太好,只有優(yōu)化和斐波那契差不多,可以用兩個(gè)數(shù)進(jìn)行計(jì)算就可以了,節(jié)省部分空間。
09 變態(tài)跳臺(tái)階★
題目描述
一只青蛙一次可以跳上1級(jí)臺(tái)階,也可以跳上2級(jí)……它也可以跳上n級(jí)。求該青蛙跳上一個(gè)n級(jí)的臺(tái)階總共有多少種跳法。
思路:
這種復(fù)雜的就別想著用遞歸了,從正面考慮吧。對(duì)于n位置的跳法,可能從n-1跳,可能從n-2跳-------可能從1跳,也可能直接從開(kāi)始跳。所以F(n)=1(直接跳)+sum(F(k)) (k屬于1到n-1)。我們肯定需要一個(gè)數(shù)組儲(chǔ)存F[n];但是我們不能每次都要相加。所以用一個(gè)sum[]數(shù)組儲(chǔ)存前n個(gè)跳法的合即可!
代碼:
public class Solution {public int JumpFloorII(int target) { int a[]=new int[target+1];//存儲(chǔ)結(jié)果int sum[]=new int[target+1];//儲(chǔ)存和a[1]=1;sum[1]=1;for(int i=2;i<=target;i++){a[i]=1+sum[i-1];sum[i]=a[i]+sum[i-1];}return a[target]; } }參考評(píng)論區(qū):
這題,個(gè)人分析方法是基于常規(guī)算法的,看了討論區(qū)有個(gè)非常非常牛的用數(shù)學(xué)推理方法,類(lèi)似等差等比數(shù)列的推理,做題的時(shí)候沒(méi)想到直接這么推哈哈,大家可以學(xué)習(xí)一波!
10 矩陣覆蓋
題目描述
我們可以用2 * 1 的小矩形橫著或者豎著去覆蓋更大的矩形。請(qǐng)問(wèn)用n個(gè)2 * 1的小矩形無(wú)重疊地覆蓋一個(gè)2*n的大矩形,總共有多少種方法?
思路:
遞歸或dp。每個(gè)矩形的大小都是2*1;同樣第F(n)=F(n-1)+F(n-2).(第n-1個(gè)橫鋪一塊,第n-2豎直兩塊)考慮下初始即可
代碼為:
/** dp*/public int RectCover(int target) {if(target==0)return 0;int dp[]=new int[target+1];dp[0]=1;dp[1]=1;for(int i=2;i<=target;i++){dp[i]=dp[i-1]+dp[i-2];}return dp[target];}/** 遞歸*/ // public int RectCover(int target) { // if(target==0)return 0; // if(target==1)return 1; // if(target==2)return 2; // else { // return RectCover(target-1)+RectCover(target-2); // } // }歡迎關(guān)注、交流,微信公眾號(hào):bigsai!
總結(jié)
以上是生活随笔為你收集整理的剑指offer(1-10题)详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 剑指offer打卡计划、建议(持续进行)
- 下一篇: 剑指offer(11-25题)详解