劍指Offer第二版題解
面試題03. 數組中重複的數字
找出數組中重複的數字。
在一個長度爲 n 的數組 nums 裏的所有數字都在 0~n-1 的範圍內。數組中某些數字是重複的,但不知道有幾個數字重複了,也不知道每個數字重複了幾次。請找出數組中任意一個重複的數字。
示例 1:
輸入:
[2, 3, 1, 0, 2, 5, 3]
輸出:2 或 3
思路和代碼:
class Solution {
public int findRepeatNumber(int[] nums) {
for(int i=0;i<nums.length;i++){
//如果當前值不是當前索引對應的值
if(i!=nums[i]){
//當前值對應的索引是否已經存有了當前值,如果是說明重複
int cur=nums[i];
if(cur==nums[cur])
return cur;
else{//如果不是,就將當前值放到自己對應的索引,將自己對應索引的值放過來
nums[i]=nums[cur];
nums[cur]=cur;
}
}
}
//沒找到,返回0(隨便返回一個數即可)
return 0;
}
}
面試題04. 二維數組中的查找
在一個 n * m 的二維數組中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。
示例:
現有矩陣 matrix 如下:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
給定 target = 5
,返回 true
。
給定 target = 20
,返回 false
。
思路和代碼:
我們分析該二維數組的特點,左到右遞增,上到下遞增,因此可以從右上角開始尋找。
如果比目標值大,就往左邊找,如果比目標值小,就往下邊找。
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
if(matrix.length==0||matrix[0].length==0)
return false;
int r=0;//起始行爲第一行
int c=matrix[0].length-1;//起始列爲最後一列
while(r<matrix.length&&c>=0){//邊界條件
if(matrix[r][c]<target)
r++;
else if(matrix[r][c]>target)
c--;
else
return true;
}
return false;
}
}
面試題05. 替換空格
請實現一個函數,把字符串 s
中的每個空格替換成"%20"。
示例 1:
輸入:s = "We are happy."
輸出:"We%20are%20happy."
思路和代碼:
這道題很簡單沒什麼好說的,利用StringBuilder拼接字符串即可,如果遇到空格就替換。
class Solution {
public String replaceSpace(String s) {
StringBuilder sb=new StringBuilder();
for(int i=0;i<s.length();i++){
if(s.charAt(i)==' ')
sb.append("%20");
else
sb.append(s.charAt(i));
}
return sb.toString();
}
}
面試題06. 從尾到頭打印鏈表
輸入一個鏈表的頭節點,從尾到頭反過來返回每個節點的值(用數組返回)。
示例 1:
輸入:head = [1,3,2]
輸出:[2,3,1]
思路和代碼:
利用ArrayList順序保存鏈表,然後逆序保存到結果數組中返回即可。
class Solution {
public int[] reversePrint(ListNode head) {
List<Integer> list=new ArrayList<>();
while(head!=null){
list.add(head.val);
head=head.next;
}
int[] res=new int[list.size()];
for(int i=0;i<res.length;i++)
res[i]=list.get(list.size()-i-1);
return res;
}
}
面試題07. 重建二叉樹
輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。
例如,給出
前序遍歷 preorder = [3,9,20,15,7]
中序遍歷 inorder = [9,3,15,20,7]
返回如下的二叉樹:
3
/ \
9 20
/ \
15 7
思路和代碼:
class Solution {
//key是中序遍歷的值,value是中序遍歷的結果
HashMap<Integer,Integer> indexMap=new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
//保存中序遍歷的信息
for(int i=0;i<inorder.length;i++){
indexMap.put(inorder[i],i);
}
return createTree(preorder,0,inorder,0,inorder.length-1);
}
//preIndex是前序遍歷的索引,inStart和inEnd是中序遍歷的索引範圍
private TreeNode createTree(int[] preorder,int preIndex,int[] inorder,int inStart,int inEnd){
if(inStart>inEnd)
return null;
//獲取前序遍歷的值
int val=preorder[preIndex];
//獲取前序遍歷值在中序遍歷的位置
int inIndex=indexMap.get(val);
//以該值作爲根節點的值創建根節點
TreeNode root=new TreeNode(val);
//根節點的左子樹節點數目
int leftNum=inIndex-inStart;
//根節點以左創建左子樹,根節點以右創建右子樹
root.left=createTree(preorder,preIndex+1,inorder,inStart,inIndex-1);
root.right=createTree(preorder,preIndex+1+leftNum,inorder,inIndex+1,inEnd);
return root;
}
}
面試題09. 用兩個棧實現隊列
用兩個棧實現一個隊列。隊列的聲明如下,請實現它的兩個函數 appendTail
和 deleteHead
,分別完成在隊列尾部插入整數和在隊列頭部刪除整數的功能。(若隊列中沒有元素,deleteHead
操作返回 -1 )
示例 1:
輸入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
輸出:[null,null,3,-1]
示例 2:
輸入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
輸出:[null,-1,null,null,5,2]
思路和代碼:
class CQueue {
private Stack<Integer> s1=new Stack<>();
private Stack<Integer> s2=new Stack<>();
public CQueue() {
}
public void appendTail(int value) {
s1.add(value);
}
public int deleteHead() {
if(s1.empty())
return -1;
while(s1.size()!=1){
s2.add(s1.pop());
}
int temp=s1.pop();
while(!s2.empty()){
s1.add(s2.pop());
}
return temp;
}
}
面試題10- II. 青蛙跳臺階問題
一隻青蛙一次可以跳上1級臺階,也可以跳上2級臺階。求該青蛙跳上一個 n
級的臺階總共有多少種跳法。
答案需要取模 1e9+7(1000000007),如計算初始結果爲:1000000008,請返回 1。
示例 1:
輸入:n = 2
輸出:2
示例 2:
輸入:n = 7
輸出:21
提示:
0 <= n <= 100
思路和代碼:
簡單的動態規劃,跳到0或1級有1種方法,之後跳到i的方法數量=跳到i-1的方法數量+跳到i-2的方法數量(因爲每次可以跳1或2級)。
由於每次結果要取模所以要mod1000000007。
public int numWays(int n) {
if(n==0||n==1)
return 1;
int mod=1000000007;
int[] dp=new int[n+1];
dp[0]=1;
dp[1]=1;
for(int i=2;i<=n;i++)
dp[i]=(dp[i-1]%mod+dp[i-2]%mod)%mod;
return dp[n];
}
面試題10- I. 斐波那契數列
寫一個函數,輸入 n
,求斐波那契(Fibonacci)數列的第 n
項。斐波那契數列的定義如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契數列由 0 和 1 開始,之後的斐波那契數就是由之前的兩數相加而得出。
答案需要取模 1e9+7(1000000007),如計算初始結果爲:1000000008,請返回 1。
示例 1:
輸入:n = 2
輸出:1
示例 2:
輸入:n = 5
輸出:5
思路和代碼:
和上一題類似,幾乎沒有變化,只需要修改n的初始值爲0。
class Solution {
public int fib(int n) {
if(n==0||n==1)
return n;
int mod=1000000007;
int[] dp=new int[n+1];
dp[0]=0;
dp[1]=1;
for(int i=2;i<=n;i++)
dp[i]=(dp[i-1]%mod+dp[i-2]%mod)%mod;
return dp[n];
}
}
面試題11. 旋轉數組的最小數字
把一個數組最開始的若干個元素搬到數組的末尾,我們稱之爲數組的旋轉。輸入一個遞增排序的數組的一個旋轉,輸出旋轉數組的最小元素。例如,數組 [3,4,5,1,2]
爲 [1,2,3,4,5]
的一個旋轉,該數組的最小值爲1。
示例 1:
輸入:[3,4,5,1,2]
輸出:1
示例 2:
輸入:[2,2,2,0,1]
輸出:0
思路和代碼:
比較簡單,我們從頭遍歷,如果一個數字大於它的下一個,就返回它的下一個。
例如示例1中,5>1,返回1,示例2中,2>0,返回0。
如果沒找到就返回第一個,例如12345返回1。
class Solution {
public int minArray(int[] numbers) {
for(int i=0;i<numbers.length-1;i++){
if(numbers[i]>numbers[i+1])
return numbers[i+1];
}
return numbers[0];
}
}
面試題12. 矩陣中的路徑
請設計一個函數,用來判斷在一個矩陣中是否存在一條包含某字符串所有字符的路徑。路徑可以從矩陣中的任意一格開始,每一步可以在矩陣中向左、右、上、下移動一格。如果一條路徑經過了矩陣的某一格,那麼該路徑不能再次進入該格子。例如,在下面的3×4的矩陣中包含一條字符串“bfce”的路徑(路徑中的字母用加粗標出)。
[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]
但矩陣中不包含字符串“abfb”的路徑,因爲字符串的第一個字符b佔據了矩陣中的第一行第二個格子之後,路徑不能再次進入這個格子。
示例 1:
輸入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
輸出:true
示例 2:
輸入:board = [["a","b"],["c","d"]], word = "abcd"
輸出:false
思路和代碼:
使用深度優先搜索回溯,從全部字符以頭開始遍歷,如果尋找到了就直接返回true,否則繼續以下一個字符爲頭重新開始。
k代表已經成功匹配的字符數量,初始爲0,每匹配一個加1,當達到目標長度時返回true。
每次進行下一層搜索時將當前字符設爲一個非字母值,這樣可以防止重複遍歷。
class Solution {
public boolean exist(char[][] board, String word) {
char[] words=word.toCharArray();
for(int i=0;i<board.length;i++){
for(int j=0;j<board[0].length;j++){
if(dfs(board,words,i,j,0))
return true;
}
}
return false;
}
private boolean dfs(char[][] board,char[] words,int i,int j,int k){
if(i<0||j<0||i==board.length||j==board[0].length)//邊界判斷防止越界
return false;
if(board[i][j]!=words[k])//如果遍歷字符和目標字符不符,返回false
return false;
//如果全部匹配成功,k已達到目標數組長度,返回true
if(k==words.length-1)
return true;
char temp=board[i][j];//保存當前字符
board[i][j]='*';//當前字符遍歷後,防止在dfs中重複遍歷,設爲任意非字母字符
//保存當前結果
boolean res=dfs(board,words,i+1,j,k+1)||dfs(board,words,i-1,j,k+1)
||dfs(board,words,i,j+1,k+1)||dfs(board,words,i,j-1,k+1);
board[i][j]=temp;//還原字符,下次遍歷正常
//還原字符後返回結果
return res;
}
}
面試題13. 機器人的運動範圍
地上有一個m行n列的方格,從座標 [0,0]
到座標 [m-1,n-1]
。一個機器人從座標 [0, 0]
的格子開始移動,它每次可以向左、右、上、下移動一格(不能移動到方格外),也不能進入行座標和列座標的數位之和大於k的格子。例如,當k爲18時,機器人能夠進入方格 [35, 37] ,因爲3+5+3+7=18。但它不能進入方格 [35, 38],因爲3+5+3+8=19。請問該機器人能夠到達多少個格子?
示例 1:
輸入:m = 2, n = 3, k = 1
輸出:3
示例 2:
輸入:m = 3, n = 1, k = 0
輸出:1
提示:
1 <= n,m <= 100
0 <= k <= 20
思路和代碼:
設置一個boolean類型的二維數組,用來標誌是否訪問過該位置。
由於最多100行,100列,因此索引從0~99。用/和%計算各位的數字之和,例如35和37,35/10+35%10+37/10+37%10=18。
class Solution {
int M;//保存行
int N;//保存列
int K;//保存閾值
boolean[][] visited;//訪問數組
public int movingCount(int m, int n, int k) {
M=m;
N=n;
K=k;
visited=new boolean[m][n];
return count(0,0);
}
public int count(int i,int j){
//如果越界或已訪問過或各位加起來大於閾值,返回0
if(i<0||i==M||j<0||j==N||visited[i][j]||(i/10+i%10+j/10+j%10)>K)
return 0;
//更新訪問標記
visited[i][j]=true;
//在上下左右四個方法繼續尋找
return 1+count(i-1,j)+count(i+1,j)+count(i,j-1)+count(i,j+1);
}
}
面試題14- I. 剪繩子
給你一根長度爲 n
的繩子,請把繩子剪成整數長度的 m
段(m、n都是整數,n>1並且m>1),每段繩子的長度記爲 k[0],k[1]...k[m]
。請問 k[0]*k[1]*...*k[m]
可能的最大乘積是多少?例如,當繩子的長度是8時,我們把它剪成長度分別爲2、3、3的三段,此時得到的最大乘積是18。
示例 1:
輸入: 2
輸出: 1
解釋: 2 = 1 + 1, 1 × 1 = 1
示例 2:
輸入: 10
輸出: 36
解釋: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:
2 <= n <= 58
思路和代碼:
class Solution {
public int cuttingRope(int n) {
if(n<=3)
return n-1;
int a=n/3;
int b=n%3;
if(b==0)//情況1,是3的倍數
return (int)Math.pow(3,a);
if(b==1)//情況2,餘1,拆出一個4
return (int)Math.pow(3,a-1)*4;
return (int)Math.pow(3,a)*2;//情況3,餘2,拆出一個2
}
}
也可以寫成這樣
public int cuttingRope(int n) {
if(n==2||n==3)
return n-1;
long res = 1;
//在剩下的繩子中,優先拆分出3,如果剩下0相當於情況1,剩下4相當於情況2,剩下2相當於情況3
while(n>4){
res*=3;
n-=3;
}
return (int)(res*n);
}
面試題14- II. 剪繩子 II
給你一根長度爲 n
的繩子,請把繩子剪成整數長度的 m
段(m、n都是整數,n>1並且m>1),每段繩子的長度記爲 k[0],k[1]...k[m]
。請問 k[0]*k[1]*...*k[m]
可能的最大乘積是多少?例如,當繩子的長度是8時,我們把它剪成長度分別爲2、3、3的三段,此時得到的最大乘積是18。
答案需要取模 1e9+7(1000000007),如計算初始結果爲:1000000008,請返回 1。
思路和代碼:
和上一題幾乎一樣,只需要多一個取模,套用第二個方法模板。
class Solution {
public int cuttingRope(int n) {
if(n==2||n==3)
return n-1;
int mod = 1000000007;
long res = 1;
while(n>4){
res*=3;
res%=mod;
n-=3;
}
return (int)(res*n%mod);
}
}
面試題15. 二進制中1的個數
請實現一個函數,輸入一個整數,輸出該數二進制表示中 1 的個數。例如,把 9 表示成二進制是 1001,有 2 位是 1。因此,如果輸入 9,則該函數輸出 2。
示例 1:
輸入:00000000000000000000000000001011
輸出:3
解釋:輸入的二進制串 00000000000000000000000000001011 中,共有三位爲 '1'。
示例 2:
輸入:00000000000000000000000010000000
輸出:1
解釋:輸入的二進制串 00000000000000000000000010000000 中,共有一位爲 '1'。
思路和代碼:
我們可以將n和1做與運算,與運算相同爲1不同爲0,因此如果n的最後1位是1,和1與的結果肯定是1,此時計數器加1;如果與的結果是0,代表最後1位是0,此時計數器加0。
比較完最後一位之後,將n無符號右移一位,這樣倒數第二位就成了最後一位,然後繼續比較。
當n包含1時肯定是大於0的,不包含1時也就比較完了,此時n=0。
public int hammingWeight(int n) {
int count=0;//1計數器
while(n!=0){
count+=n&1;
n>>>=1;
}
return count;
}
面試題16. 數值的整數次方
實現函數double Power(double base, int exponent),求base的exponent次方。不得使用庫函數,同時不需要考慮大數問題。
示例 1:
輸入: 2.00000, 10
輸出: 1024.00000
示例 2:
輸入: 2.10000, 3
輸出: 9.26100
示例 3:
輸入: 2.00000, -2
輸出: 0.25000
解釋: 2-2 = 1/22 = 1/4 = 0.25
思路和代碼:
0次方的時候返回1,1次方返回自己,-1次方返回自己的倒數。
爲了加快速度,如果n是偶數,直接計算n/2的結果再做平方即可,例如計算2的10次方,只需要計算2的5次方,再將結果做平方運算。
如果是奇數,也計算n/2的結果,然後如果n>0就乘上x,小於0就乘上1/x。
例如計算2的9次方,將2的4次方做平方運算再乘2。
計算2的-11次方,將2的-5次方做平方運算再乘1/2。
class Solution {
public double myPow(double x, int n) {
if(n==0)
return 1;
if(n==1)
return x;
if(n==-1)
return 1/x;
double res=myPow(x,n/2);
if(n%2==0){
return res*res;
}
return n<0?res*res*1/x:res*res*x;
}
}
面試題17. 打印從1到最大的n位數
輸入數字 n
,按順序打印出從 1 到最大的 n 位十進制數。比如輸入 3,則打印出 1、2、3 一直到最大的 3 位數 999。
示例 1:
輸入: n = 1
輸出: [1,2,3,4,5,6,7,8,9]
說明:
- 用返回一個整數列表來代替打印
- n 爲正整數
思路和代碼:
先計算出最大的n位數,也就是n個9,做n次(x10+9)的運算即可。
即9,99,999…
class Solution {
public int[] printNumbers(int n) {
//計算最大的n位數,999...9(n個9)
int res=0;
while(n!=0){
res=res*10+9;
n--;
}
int[] arr=new int[res];
for(int i=0;i<arr.length;i++)
arr[i]=i+1;
return arr;
}
}
面試題18. 刪除鏈表的節點
給定單向鏈表的頭指針和一個要刪除的節點的值,定義一個函數刪除該節點。
返回刪除後的鏈表的頭節點。
**注意:**此題對比原題有改動
示例 1:
輸入: head = [4,5,1,9], val = 5
輸出: [4,1,9]
解釋: 給定你鏈表中值爲 5 的第二個節點,那麼在調用了你的函數之後,該鏈表應變爲 4 -> 1 -> 9.
示例 2:
輸入: head = [4,5,1,9], val = 1
輸出: [4,5,9]
解釋: 給定你鏈表中值爲 1 的第三個節點,那麼在調用了你的函數之後,該鏈表應變爲 4 -> 5 -> 9.
思路和代碼:
因爲第一個節點也有可能要刪除,所以我們要創建一個新的頭節點,將它與頭節點連接,然後從新的頭節點開始遍歷,這裏需要一個新的指針,因pre不能移動。如果當前節點A的下一個節點B不爲空就判斷B的值是否等於要刪除的值,如果是就將當前節點A指向B的下一個節點C,然後break。
最後返回新頭節點的下一個節點即可。
class Solution {
public ListNode deleteNode(ListNode head, int val) {
ListNode pre=new ListNode(-1);
pre.next=head;
ListNode cur=pre;
while(cur!=null&&cur.next!=null){
if(cur.next.val==val){
cur.next=cur.next.next;
break;
}
cur=cur.next;
}
return pre.next;
}
}
面試題19. 正則表達式匹配
請實現一個函數用來匹配包含'. '
和'*'
的正則表達式。模式中的字符'.'
表示任意一個字符,而'*'
表示它前面的字符可以出現任意次(含0次)。在本題中,匹配是指字符串的所有字符匹配整個模式。例如,字符串"aaa"
與模式"a.a"
和"ab*ac*a"
匹配,但與"aa.a"
和"ab*a"
均不匹配。
示例 1:
輸入:
s = "aa"
p = "a"
輸出: false
解釋: "a" 無法匹配 "aa" 整個字符串。
示例 2:
輸入:
s = "aa"
p = "a*"
輸出: true
解釋: 因爲 '*' 代表可以匹配零個或多個前面的那一個元素, 在這裏前面的元素就是 'a'。因此,字符串 "aa" 可被視爲 'a' 重複了一次。
示例 3:
輸入:
s = "ab"
p = ".*"
輸出: true
解釋: ".*" 表示可匹配零個或多個('*')任意字符('.')。
示例 4:
輸入:
s = "aab"
p = "c*a*b"
輸出: true
解釋: 因爲 '*' 表示零個或多個,這裏 'c' 爲 0 個, 'a' 被重複一次。因此可以匹配字符串 "aab"。
示例 5:
輸入:
s = "mississippi"
p = "mis*is*p*."
輸出: false
思路和代碼:
class Solution {
public boolean isMatch(String s, String p) {
//任意一個爲空,則匹配失敗
if(s==null||p==null)
return false;
return match(s.toCharArray(),0,p.toCharArray(),0);
}
public boolean match(char[] str, int s,char[] pattern,int p){
//如果字符串和匹配字符串都到達了末尾,則說明匹配成功
if(s==str.length&&p==pattern.length)
return true;
//如果字符串未遍歷結束,但匹配字符串已結束,匹配失敗
if(s<str.length&&p==pattern.length)
return false;
//如果匹配字符串的下一個字符是*
if(p<pattern.length-1&&pattern[p+1]=='*'){
//如果當前字符串字符和匹配字符串字符相同或者匹配字符爲.
if(s<str.length&&(str[s]==pattern[p]||pattern[p]=='.'))
return match(str, s+1, pattern, p)//.*可以匹配多個字符 如aa匹配a*
||match(str, s, pattern, p+2);//字符串已匹配完,放棄匹配字符串的兩個位置,例如a匹配ab*
else
return match(str, s, pattern, p+2);//忽略當前匹配字符串的2個字符 如a不匹配b*,可跳過b*這兩個位置
}
//匹配字符串沒有下一個位置或下一個位置不爲*,那麼當前位置必須相等或爲.,否則匹配失敗
if(s<str.length&&(str[s]==pattern[p]||pattern[p]=='.')){
return match(str,s+1,pattern,p+1);//各自匹配一個位置
}
return false;
}
}
面試題20. 表示數值的字符串
請實現一個函數用來判斷字符串是否表示數值(包括整數和小數)。例如,字符串"+100"、“5e2”、"-123"、“3.1416”、“0123"都表示數值,但"12e”、“1a3.14”、“1.2.3”、“±5”、"-1E-16"及"12e+5.4"都不是。
思路和代碼:
[abc]表示匹配a、b、c任意一個即可。[0-9]表示匹配數字0-9
\\.
表示小數點,?表示0或1次,+表示1或多次,*表示任意次,|表示或。
首先判斷開頭,[±]?表示可以以加號或減號開頭,也可以不以加減號開頭。
數字部分是[0-9]+\\.?
表示0-9必須出現一次,小數點可以不出現,例如111
,11.
或者是[0-9]*\\.[0-9]+
,表示小數點前任意次,小數點必須有,小數點後必須有,例如.5
,1.5
最後指數[e][+-]?[0-9]+
匹配時表示必須有e,正負號可有可無,有e時後面必須有數字,?表示也可以沒指數。
class Solution {
public boolean isNumber(String s) {
return s.trim()
.matches("^[+-]?(([0-9]+\\.?)|([0-9]*\\.[0-9]+))([e][+-]?[0-9]+)?$");
}
}
面試題21. 調整數組順序使奇數位於偶數前面
輸入一個整數數組,實現一個函數來調整該數組中數字的順序,使得所有奇數位於數組的前半部分,所有偶數位於數組的後半部分。
示例:
輸入:nums = [1,2,3,4]
輸出:[1,3,2,4]
注:[3,1,2,4] 也是正確的答案之一。
思路和代碼:
類似於快速排序的思想,使用雙指針,從頭找偶數,從尾找奇數,然後交換它們的位置。
class Solution {
public int[] exchange(int[] nums) {
int p=0;
int q=nums.length-1;
while(p<q){
//從前面找到第一個偶數
while(p<=q&&nums[p]%2!=0)
p++;
//從後面找到第一個奇數
while(p<=q&&nums[q]%2==0)
q--;
if(p<q){//交換數字
int temp=nums[p];
nums[p]=nums[q];
nums[q]=temp;
}
}
return nums;
}
}
面試題22. 鏈表中倒數第k個節點
輸入一個鏈表,輸出該鏈表中倒數第k個節點。爲了符合大多數人的習慣,本題從1開始計數,即鏈表的尾節點是倒數第1個節點。例如,一個鏈表有6個節點,從頭節點開始,它們的值依次是1、2、3、4、5、6。這個鏈表的倒數第3個節點是值爲4的節點。
示例:
給定一個鏈表: 1->2->3->4->5, 和 k = 2.
返回鏈表 4->5.
思路和代碼:
可以使用雙指針,讓快指針先走k個,這樣當快指針爲null時慢指針就是答案。
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode pre=new ListNode(-1);
pre.next=head;
ListNode slow=pre;
ListNode fast=pre;
for(int i=0;i<k;i++)
fast=fast.next;
while(fast!=null){
fast=fast.next;
slow=slow.next;
}
return slow;
}
}
面試題24. 反轉鏈表
定義一個函數,輸入一個鏈表的頭節點,反轉該鏈表並輸出反轉後鏈表的頭節點。
示例:
輸入: 1->2->3->4->5->NULL
輸出: 5->4->3->2->1->NULL
思路和代碼:
利用三個指針,pre指針保存前一個節點,cur指針用於遍歷鏈表,last指針總是指向cur的下一個,這樣在cur指向pre之後,cur還是可以跳到之前的下一個位置。
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre=null;
ListNode last=null;
ListNode cur=head;
while(cur!=null){
last=cur.next;
cur.next=pre;
pre=cur;
cur=last;
}
return pre;
}
}
面試題25. 合併兩個排序的鏈表
輸入兩個遞增排序的鏈表,合併這兩個鏈表並使新鏈表中的節點仍然是遞增排序的。
示例1:
輸入:1->2->4, 1->3->4
輸出:1->1->2->3->4->4
思路和代碼:
比較簡單,由於是有序鏈表,所以分別從頭開始遍歷,如果l1更小,將節點的下一個指向l1,否則就指向l2。如果有一個爲空,就將另一個剩下的鏈表直接拼接。
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode pre=new ListNode(-1);
ListNode cur=pre;
while(l1!=null&&l2!=null){
if(l1.val<l2.val){
cur.next=l1;
l1=l1.next;
}else{
cur.next=l2;
l2=l2.next;
}
cur=cur.next;
}
cur.next=l1==null?l2:l1;
return pre.next;
}
}
面試題26. 樹的子結構
輸入兩棵二叉樹A和B,判斷B是不是A的子結構。(約定空樹不是任意一個樹的子結構)
B是A的子結構, 即 A中有出現和B相同的結構和節點值。
示例 1:
輸入:A = [1,2,3], B = [3,1]
輸出:false
示例 2:
輸入:A = [3,4,5,1,2], B = [4,1]
輸出:true
思路和代碼:
簡單遞歸
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
if(A==null||B==null)
return false;
//以當前節點爲根匹配,或者以左右節點爲根匹配
return isSub(A,B)||isSubStructure(A.left,B)||isSubStructure(A.right,B);
}
//將A作爲根節點匹配B
public boolean isSub(TreeNode A,TreeNode B){
if(B==null)//B爲空代表匹配完畢
return true;
if(A==null)//B非空但A空,匹配失敗
return false;
//兩個節點值不同,匹配失敗
if(A.val!=B.val)
return false;
//根節點相同,匹配左右節點
return isSub(A.left,B.left)&&isSub(A.right,B.right);
}
}
面試題27. 二叉樹的鏡像
請完成一個函數,輸入一個二叉樹,該函數輸出它的鏡像。
思路和代碼:
簡單遞歸即可
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if(root!=null){
//將右子樹翻轉作爲新的左子樹
TreeNode newLeft=mirrorTree(root.right);
//將左子樹翻轉作爲新的右子樹
TreeNode newRight=mirrorTree(root.left);
root.left=newLeft;
root.right=newRight;
}
return root;
}
}
面試題28. 對稱的二叉樹
給定一個二叉樹,檢查它是否是鏡像對稱的。
例如,二叉樹 [1,2,2,3,4,4,3]
是對稱的。
1
/ \
2 2
/ \ / \
3 4 4 3
但是下面這個 [1,2,2,null,3,null,3]
則不是鏡像對稱的:
1
/ \
2 2
\ \
3 3
思路和代碼:
如果根節點爲空,那麼空節點是對稱的。
否則比較它的左右子樹是否對稱,如果都爲空那對稱,如果只有一個爲空肯定不對稱。
如果都不爲空那麼比較值,值不同肯定不對稱,如果值相同,再比較左節點的右子樹和右節點的左子樹是否對稱(最裏面那一層),比較左節點的左子樹和右節點的右子樹是否對稱(最外面那一層)。
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null)
return true;
return isSymmetric(root.left,root.right);
}
public boolean isSymmetric(TreeNode p,TreeNode q){
if(p==null&q==null)
return true;
if(p==null||q==null)
return false;
if(p.val!=q.val)
return false;
return isSymmetric(p.left,q.right)&&isSymmetric(p.right,q.left);
}
}
面試題29. 順時針打印矩陣
輸入一個矩陣,按照從外向裏以順時針的順序依次打印出每一個數字。
示例 1:
輸入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
輸出:[1,2,3,6,9,8,7,4,5]
示例 2:
輸入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
輸出:[1,2,3,4,8,12,11,10,9,5,6,7]
思路和代碼:
在一個死循環中按照右下左上的順序循環,每次改變方向前先判斷是否越界。
class Solution {
public int[] spiralOrder(int[][] matrix) {
if(matrix.length==0)
return new int[0];
int[] nums=new int[matrix.length*matrix[0].length];
int index=0;
int up=0;
int down=matrix.length-1;
int left=0;
int right=matrix[0].length-1;
while(true){
//從左向右移動
for(int i=left;i<=right;i++)
nums[index++]=matrix[up][i];
if(++up>down) break;
//從上向下移動
for(int i=up;i<=down;i++)
nums[index++]=matrix[i][right];
if(--right<left) break;
//從右向左移動
for(int i=right;i>=left;i--)
nums[index++]=matrix[down][i];
if(--down<up) break;
//從下向上移動
for(int i=down;i>=up;i--)
nums[index++]=matrix[i][left];
if(++left>right) break;
}
return nums;
}
}
面試題30. 包含min函數的棧
定義棧的數據結構,請在該類型中實現一個能夠得到棧的最小元素的 min 函數在該棧中,調用 min、push 及 pop 的時間複雜度都是 O(1)。
示例:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.min(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.min(); --> 返回 -2.
思路和代碼:
使用一個輔助棧s2作爲最小棧,進棧出棧s1都沒有限制,對於最小棧s2只有s2爲空或入棧節點值小於等於s2棧頂時纔可入棧,出棧時,只有s1和s2棧頂相同s2纔出棧。
class MinStack {
private Stack<Integer> s1=new Stack<>();
private Stack<Integer> s2=new Stack<>();
public MinStack() {
}
public void push(int x) {
if(s2.empty()||x<=s2.peek())
s2.add(x);
s1.add(x);
}
public void pop() {
if(s1.pop().equals(s2.peek()))
s2.pop();
}
public int top() {
return s1.peek();
}
public int min() {
return s2.peek();
}
}
面試題31. 棧的壓入、彈出序列
輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否爲該棧的彈出順序。假設壓入棧的所有數字均不相等。例如,序列 {1,2,3,4,5} 是某棧的壓棧序列,序列 {4,5,3,2,1} 是該壓棧序列對應的一個彈出序列,但 {4,3,5,1,2} 就不可能是該壓棧序列的彈出序列。
示例 1:
輸入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
輸出:true
解釋:我們可以按以下順序執行:
push(1), push(2), push(3), push(4), pop() -> 4,
push(5), pop() -> 5, pop() -> 3, pop() -> 2, pop() -> 1
示例 2:
輸入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
輸出:false
解釋:1 不能在 2 之前彈出。
思路和代碼:
利用一個輔助棧來模擬,停止條件是壓棧序列全部入棧,然後入棧時如果棧頂和出棧序列的當前元素相同就出棧一個,直到棧空或棧頂不等於出棧序列當前元素。
最後判斷棧是否空,如果空代表入棧出棧有效。
class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
Stack<Integer> stack=new Stack<>();
int pushIndex=0;
int popIndex=0;
while(pushIndex!=pushed.length){
stack.push(pushed[pushIndex]);
while(!stack.empty()&&stack.peek()==popped[popIndex]){
stack.pop();
popIndex++;
}
pushIndex++;
}
return stack.empty();
}
}
面試題32 - I. 從上到下打印二叉樹
從上到下打印出二叉樹的每個節點,同一層的節點按照從左到右的順序打印。
例如:
給定二叉樹: [3,9,20,null,null,15,7]
,
3
/ \
9 20
/ \
15 7
返回:
[3,9,20,15,7]
思路和代碼:
利用一個ArrayList作爲隊列,存儲樹節點,當隊列不爲空就就將其從頭移除,並將值加入結果集。然後按照先左後右的順序,再把它的非空子節點入隊。
class Solution {
public int[] levelOrder(TreeNode root) {
//保存樹節點的隊列
List<TreeNode> treeList=new ArrayList<>();
//保存結果集
List<Integer> list=new ArrayList<>();
//空樹返回空數組
if(root==null)
return new int[0];
treeList.add(root);
while(treeList.size()!=0){
TreeNode temp=treeList.get(0);
list.add(temp.val);
treeList.remove(0);
if(temp.left!=null){//左節點非空進隊
treeList.add(temp.left);
}
if(temp.right!=null){//右節點非空進隊
treeList.add(temp.right);
}
}
//返回結果
int[] res=new int[list.size()];
for(int i=0;i<res.length;i++)
res[i]=list.get(i);
return res;
}
}
面試題32 - II. 從上到下打印二叉樹 II
從上到下按層打印二叉樹,同一層的節點按從左到右的順序打印,每一層打印到一行。
例如:
給定二叉樹: [3,9,20,null,null,15,7]
,
3
/ \
9 20
/ \
15 7
返回其層次遍歷結果:
[
[3],
[9,20],
[15,7]
]
思路和代碼:
和上一題類似,只是要將每一行的數值保存到同一個list中。每次出隊之前先計算當前隊列的大小,這個大小就是這一層的節點數量,然後按這個數量依次從隊頭移除。
注意這個數量必須提前計算,而不能使用list.size(),因爲size一直在改變。
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<TreeNode> treeList=new ArrayList<>();//樹節點隊列
List<Integer> temp;//每一層的臨時列表
List<List<Integer>> resultList=new ArrayList<>();//結果集
if(root!=null){
treeList.add(root);
while(treeList.size()!=0){
int n=treeList.size();//計算該層節點數
temp=new ArrayList<>();//每一層都創建一個list保存結果
for(int i=0;i<n;i++){//按數量出隊
TreeNode node=treeList.remove(0);
temp.add(node.val);
if(node.left!=null)
treeList.add(node.left);
if(node.right!=null)
treeList.add(node.right);
}
resultList.add(temp);//將這一層的結果添加到結果集
}
}
return resultList;
}
}
面試題32 - III. 從上到下打印二叉樹 III
請實現一個函數按照之字形順序打印二叉樹,即第一行按照從左到右的順序打印,第二層按照從右到左的順序打印,第三行再按照從左到右的順序打印,其他行以此類推。
例如:
給定二叉樹: [3,9,20,null,null,15,7]
,
3
/ \
9 20
/ \
15 7
返回其層次遍歷結果:
[
[3],
[20,9],
[15,7]
]
思路和代碼:
和上一題幾乎一樣,我們只需要增加一個方向變量,每一層都改變它即可。
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<TreeNode> treeList=new ArrayList<>();//樹節點隊列
List<Integer> temp;//每一層的臨時列表
List<List<Integer>> resultList=new ArrayList<>();//結果集
boolean leftToRight=true;//控制方向,初始從左往右
if(root!=null){
treeList.add(root);
while(treeList.size()!=0){
int n=treeList.size();//計算該層節點數
temp=new ArrayList<>();//每一層都創建一個list保存結果
for(int i=0;i<n;i++){//按數量出隊
TreeNode node=treeList.remove(0);
temp.add(node.val);
if(node.left!=null)
treeList.add(node.left);
if(node.right!=null)
treeList.add(node.right);
}
if(!leftToRight){//如果是逆序,就把該層結果逆序
Collections.reverse(temp);
}
resultList.add(temp);//將這一層的結果添加到結果集
leftToRight=!leftToRight;//每一層完了都改變方向
}
}
return resultList;
}
}
面試題33. 二叉搜索樹的後序遍歷序列
輸入一個整數數組,判斷該數組是不是某二叉搜索樹的後序遍歷結果。如果是則返回 true
,否則返回 false
。假設輸入的數組的任意兩個數字都互不相同。
參考以下這顆二叉搜索樹:
5
/ \
2 6
/ \
1 3
示例 1:
輸入: [1,6,3,2,5]
輸出: false
示例 2:
輸入: [1,3,2,6,5]
輸出: true
思路和代碼:
根據後序遍歷左-右-根的特點,從左邊找到第一個大於根節點的值劃分左右子樹,左子樹的值必須都比該值小(由於找到的是第一個大於根節點的值,這點肯定滿足),接下來由於右子樹都比根節點值大,再找到第一個不大於根節點的值,此時索引肯定等於根節點,如果不等於就是false。
class Solution {
public boolean verifyPostorder(int[] postorder) {
return verify(postorder,0,postorder.length-1);
}
//j是根節點
public boolean verify(int[] postorder,int i,int j){
if(i>=j)
return true;
int mid=i;
//左-右-根,因此mid相當於找到了第一個右節點
//例如1,3,2,6,9,7,5,root從1開始遍歷到6停止,mid=6,132就是左子樹
while(postorder[mid]<postorder[j])
mid++;
int root=mid;
//左-右-根,由於已經開始遍歷右子樹,如果找到第一個不大於j的值只能是j本身
//例如1,3,2,6,9,7,5,root從6開始遍歷到5停止,root=j
while(postorder[root]>postorder[j])
root++;
//例如1,3,2,6,9,2,5,root從6開始遍歷到2停止,root!=j,5的右子樹有2,肯定不對
if(root!=j)
return false;
//繼續判斷左右子樹,i到mid-1就是1到2,mid到j-1就是6到7
return verify(postorder,i,mid-1)&&verify(postorder,mid,j-1);
}
}
面試題34. 二叉樹中和爲某一值的路徑
輸入一棵二叉樹和一個整數,打印出二叉樹中節點值的和爲輸入整數的所有路徑。從樹的根節點開始往下一直到葉節點所經過的節點形成一條路徑。
示例:
給定如下二叉樹,以及目標和 sum = 22
,
5
/ \
4 8
/ / \
11 13 4
/ \ / \
7 2 5 1
返回:
[
[5,4,11,2],
[5,8,4,5]
]
思路和代碼:
class Solution {
List<List<Integer>> res=new ArrayList<>();
List<Integer> temp=new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int sum) {
help(root,sum);
return res;
}
public void help(TreeNode root,int sum){
if(root==null)
return;
temp.add(root.val);
//只有在路徑末尾是葉子節點時才添加結果
if(root.val==sum&&root.left==null&&root.right==null){
res.add(new ArrayList<>(temp));
}
help(root.left,sum-root.val);
help(root.right,sum-root.val);
//回溯減去末尾值
temp.remove(temp.size()-1);
}
}
面試題35. 複雜鏈表的複製
請實現 copyRandomList
函數,複製一個複雜鏈表。在複雜鏈表中,每個節點除了有一個 next
指針指向下一個節點,還有一個 random
指針指向鏈表中的任意節點或者 null
。
示例 1:
輸入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
輸出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
示例 2:
輸入:head = [[1,1],[2,1]]
輸出:[[1,1],[2,1]]
示例 3:
輸入:head = [[3,null],[3,0],[3,null]]
輸出:[[3,null],[3,0],[3,null]]
示例 4:
輸入:head = []
輸出:[]
解釋:給定的鏈表爲空(空指針),因此返回 null。
提示:
-10000 <= Node.val <= 10000
Node.random
爲空(null)或指向鏈表中的節點。- 節點數目不超過 1000 。
思路和答案:
class Solution {
public Node copyRandomList(Node head) {
if(head==null)
return null;
Node newHead=head;
//在每一個節點後都複製一個節點
while(head!=null){
Node copy=new Node(head.val);
copy.next=head.next;
head.next=copy;
head=copy.next;
}
head=newHead;
//每一個複製節點的隨機節點都是前一個節點的隨機節點的next
while(head!=null){
Node copy=head.next;
copy.random=head.random==null?null:head.random.next;
head=copy.next;
}
head=newHead;
Node res=newHead.next;
//拆分鏈表
while(head!=null){
Node copy=head.next;
head.next=copy.next;
head=copy.next;
copy.next=head!=null?head.next:null;
}
return res;
}
}
面試題36. 二叉搜索樹與雙向鏈表
輸入一棵二叉搜索樹,將該二叉搜索樹轉換成一個排序的循環雙向鏈表。要求不能創建任何新的節點,只能調整樹中節點指針的指向。
爲了讓您更好地理解問題,以下面的二叉搜索樹爲例:
我們希望將這個二叉搜索樹轉化爲雙向循環鏈表。鏈表中的每個節點都有一個前驅和後繼指針。對於雙向循環鏈表,第一個節點的前驅是最後一個節點,最後一個節點的後繼是第一個節點。
下圖展示了上面的二叉搜索樹轉化成的鏈表。“head” 表示指向鏈表中有最小元素的節點。
特別地,我們希望可以就地完成轉換操作。當轉化完成以後,樹中節點的左指針需要指向前驅,樹中節點的右指針需要指向後繼。還需要返回鏈表中的第一個節點的指針。
思路和代碼:
這道題其實不難,由於是二叉搜索樹所以使用中序遍歷從小到大遍歷即可,每次遍歷的時候把當前節點的left指向前一個節點,把前一個節點的right指向當前節點即可。
遍歷到的第一個節點就是最小節點,此時head就是它,從第二個節點開始操作,每次更新pre。
遍歷完之後pre就是最後一個節點,再把它和head連起來就行。
class Solution {
Node head;
Node pre;
public Node treeToDoublyList(Node root) {
if(root==null)
return null;
dfs(root);
pre.right=head;
head.left=pre;
return head;
}
public void dfs(Node root){
if(root!=null){
treeToDoublyList(root.left);
if(pre==null){//第一次操作的節點就是頭節點
head=root;
}else{
root.left=pre;
pre.right=root;
}
pre=root;
treeToDoublyList(root.right);
}
}
}
面試題38. 字符串的排列
輸入一個字符串,打印出該字符串中字符的所有排列。
你可以以任意順序返回這個字符串數組,但裏面不能有重複元素。
示例:
輸入:s = "abc"
輸出:["abc","acb","bac","bca","cab","cba"]
思路和代碼:
回溯法
class Solution {
List<String> list=new ArrayList<>();
char[] c;
public String[] permutation(String s) {
c=s.toCharArray();
dfs(0);
return list.toArray(new String[0]);
}
public void dfs(int n){
if(n==c.length-1){
list.add(String.valueOf(c));
return;
}
HashSet<Character> set=new HashSet<>();
for(int i=n;i<c.length;i++){
if(set.contains(c[i]))
continue;
set.add(c[i]);
swap(n,i);
dfs(n+1);
swap(n,i);//還原字符
}
}
//交換兩個位置的字符
public void swap(int a,int b){
char temp=c[a];
c[a]=c[b];
c[b]=temp;
}
}
面試題39. 數組中出現次數超過一半的數字
數組中有一個數字出現的次數超過數組長度的一半,請找出這個數字。
你可以假設數組是非空的,並且給定的數組總是存在多數元素。
示例 1:
輸入: [1, 2, 3, 2, 2, 2, 5, 4, 2]
輸出: 2
思路和代碼:
使用投票算法,當票數爲0就改變leader,由於這個數字超過了一半,所以最後leader結果一定是它。
class Solution {
public int majorityElement(int[] nums) {
int leader=0;
int count=0;
for(int num:nums){
if(count==0)//如果票數爲0,證明沒有leader,讓當前數字當leader
leader=num;
count+=num==leader?1:-1;//當前數字和leader一樣,票數加1,否則減1
}
return leader;
}
}
面試題40. 最小的k個數
輸入整數數組 arr
,找出其中最小的 k
個數。例如,輸入4、5、1、6、2、7、3、8這8個數字,則最小的4個數字是1、2、3、4。
示例 1:
輸入:arr = [3,2,1], k = 2
輸出:[1,2] 或者 [2,1]
示例 2:
輸入:arr = [0,1,2,1], k = 1
輸出:[0]
思路和代碼:
利用快速排序的思想,返回的索引index
的左邊0~index-1對應的數字共index個都比index對應的值小,右邊都比index對應的值大,所以只要讓返回的索引index=k即可。
如果返回的索引index小於k,那麼以index+1對應的值爲基準值繼續排序,如果index大於k,則以index-1對應的值繼續排序。
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if(arr.length==k)
return arr;
help(arr,0,arr.length-1,k);
//複製前k個數作爲結果
return Arrays.copyOf(arr,k);
}
//當index=k時,索引0~k-1都比k對應的值小,此時前k個數肯定是最小的
public void help(int[] arr,int low,int high,int k){
int index=getPivotIndex(arr,low,high);
if(index!=k){
if(index<k)//前index個已經排好,還要從index+1開始繼續排
help(arr,index+1,high,k);
else//超過了k個,index左邊的都小於index,所以排除掉index,從index-1繼續排
help(arr,low,index-1,k);
}
}
private int getPivotIndex(int[] arr, int start, int end) {
//默認選擇第一個作爲基準
int pivot=arr[start];
int i=start;
int j=end;
while(i<j){//循環條件
//從左起找到第一個大於pivot的元素
while (i<=j&&arr[i]<=pivot)
i++;
//從右起找到第一個小於等於pivot的元素
while (i<=j&&arr[j]>pivot)
j--;
//如果左邊元素大於右邊則交換
if(i<j){
swap(arr,i,j);
}
}
swap(arr,start,j);
return j;
}
public void swap(int[] arr,int i,int j){
int temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
簡單做法:
public int[] getLeastNumbers(int[] arr, int k) {
Arrays.sort(arr);
//複製前k個數作爲結果
return Arrays.copyOf(arr,k);
}
面試題42. 連續子數組的最大和
輸入一個整型數組,數組裏有正數也有負數。數組中的一個或連續多個整數組成一個子數組。求所有子數組的和的最大值。
要求時間複雜度爲O(n)。
示例1:
輸入: nums = [-2,1,-3,4,-1,2,1,-5,4]
輸出: 6
解釋: 連續子數組 [4,-1,2,1] 的和最大,爲 6。
提示:
1 <= arr.length <= 10^5
-100 <= arr[i] <= 100
思路和代碼:
使用一個變量保存最大值,一個變量保存臨時和。
class Solution {
public int maxSubArray(int[] nums) {
int max=nums[0];//保存最大和
int temp=nums[0];//保存臨時和
for(int i=1;i<nums.length;i++){
if(temp<0)
temp=nums[i];
else
temp+=nums[i];
if(temp>max)
max=temp;
}
return max;
}
}
面試題43. 1~n整數中1出現的次數
難度中等29
輸入一個整數 n
,求1~n這n個整數的十進制表示中1出現的次數。
例如,輸入12,1~12這些整數中包含1 的數字有1、10、11和12,1一共出現了5次。
示例 1:
輸入:n = 12
輸出:5
示例 2:
輸入:n = 13
輸出:6
思路和代碼:
class Solution {
public int countDigitOne(int n) {
return hasOne(n);
}
public int hasOne(int n){
if(n==0)
return 0;
//將數字轉爲字符串
String number=String.valueOf(n);
//獲取最高位
int high=number.charAt(0)-'0';
//獲取最接近的100..00
int pow=(int)(Math.pow(10,number.length()-1));
//獲取剩餘數字
int left=n-high*pow;
if(high==1)
return hasOne(pow-1)+left+1+hasOne(left);
else
return pow+high*hasOne(pow-1)+hasOne(left);
}
}
面試題45. 把數組排成最小的數
輸入一個正整數數組,把數組裏所有數字拼接起來排成一個數,打印能拼接出的所有數字中最小的一個。
示例 1:
輸入: [10,2]
輸出: "102"
示例 2:
輸入: [3,30,34,5,9]
輸出: "3033459"
思路和代碼:
String數組的排序規則是對x插入y,如果x+y<y+x,就排成xy否則排成yx。比較時是從左至右比較不同的第一位。
例如10,插入2,102肯定小於210,因此排成102。
例如3,30,34,5,9,首先插入3,30+3小於3+30,因此此時爲303,插入34,303的第二位是0,因此此時是30334,插入5,此時第一位3<5,因此排成303345,接着排成303349。
class Solution {
public String minNumber(int[] nums) {
//先將int數組轉爲String數組
String[] strs=new String[nums.length];
for(int i=0;i<nums.length;i++)
strs[i]= String.valueOf(nums[i]);
//對String數組排序
Arrays.sort(strs,(x,y)->(x+y).compareTo(y+x));
StringBuilder sb = new StringBuilder();
for(String str:strs)
sb.append(str);
return sb.toString();
}
}
面試題46. 把數字翻譯成字符串
給定一個數字,我們按照如下規則把它翻譯爲字符串:0 翻譯成 “a” ,1 翻譯成 “b”,……,11 翻譯成 “l”,……,25 翻譯成 “z”。一個數字可能有多個翻譯。請編程實現一個函數,用來計算一個數字有多少種不同的翻譯方法。
示例 1:
輸入: 12258
輸出: 5
解釋: 12258有5種不同的翻譯,分別是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
思路和代碼:
動態規劃,首先將int轉爲String字符串,再轉爲char數組。
由於遞推關係涉及到前兩個,防止越界,所以dp數組要比char數組長度大1。
dp[0]是初始化條件,dp[1]代表的是char[0]的翻譯方法數,一個字符當然只有一種。
從dp[2]開始動態規劃,也就是從字符串第二位開始。
由於每一位都可以單獨組成一個字符,所以dp[i]=dp[i-1],例如123的方法數肯定等於12的方法數,因爲3單獨組成一個。
之後判斷能否和前一位組成1~25的數字,如果可以就要加上前2位的方法數,例如123的方法數要加上1的方法數,因爲23可以組合成一個。
所以dp(i)的初始值就是dp(i-1),然後判斷和前一位能否組成2位數,可以再加上dp(i-2)。
注意dp比chars長度大一,所以dp[i]對應的是chars[i-1]。
class Solution {
public int translateNum(int num) {
char[] chars=String.valueOf(num).toCharArray();
int[] dp=new int[chars.length+1];
dp[0]=1;
dp[1]=1;
for(int i=2;i<dp.length;i++){
dp[i]=dp[i-1];
if(chars[i-2]=='1'||(chars[i-2]=='2'&&chars[i-1]<='5'))
dp[i]+=dp[i-2];
}
return dp[dp.length-1];
}
}
面試題47. 禮物的最大價值
在一個 m*n 的棋盤的每一格都放有一個禮物,每個禮物都有一定的價值(價值大於 0)。你可以從棋盤的左上角開始拿格子裏的禮物,並每次向右或者向下移動一格、直到到達棋盤的右下角。給定一個棋盤及其上面的禮物的價值,請計算你最多能拿到多少價值的禮物?
示例 1:
輸入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
輸出: 12
解釋: 路徑 1→3→5→2→1 可以拿到最多價值的禮物
思路和代碼:
class Solution {
public int maxValue(int[][] grid) {
for(int i=1;i<grid.length;i++)
grid[i][0]+=grid[i-1][0];
for(int i=1;i<grid[0].length;i++)
grid[0][i]+=grid[0][i-1];
for(int i=1;i<grid.length;i++){
for(int j=1;j<grid[0].length;j++){
grid[i][j]+=Math.max(grid[i-1][j],grid[i][j-1]);
}
}
return grid[grid.length-1][grid[0].length-1];
}
}
面試題48. 最長不含重複字符的子字符串
請從字符串中找出一個最長的不包含重複字符的子字符串,計算該最長子字符串的長度。
示例 1:
輸入: "abcabcbb"
輸出: 3
解釋: 因爲無重複字符的最長子串是 "abc",所以其長度爲 3。
示例 2:
輸入: "bbbbb"
輸出: 1
解釋: 因爲無重複字符的最長子串是 "b",所以其長度爲 1。
示例 3:
輸入: "pwwkew"
輸出: 3
解釋: 因爲無重複字符的最長子串是 "wke",所以其長度爲 3。
請注意,你的答案必須是 子串 的長度,"pwke" 是一個子序列,不是子串。
思路和代碼:
class Solution {
public int lengthOfLongestSubstring(String s) {
int len=0;
HashMap<Character,Integer> map=new HashMap<>();
for(int start=0,end=0;end<s.length();end++){
char c=s.charAt(end);
if(map.containsKey(c))
start=Math.max(start,map.get(c));
len=Math.max(len,end-start+1);
map.put(c,end+1);
}
return len;
}
}
面試題49. 醜數
我們把只包含因子 2、3 和 5 的數稱作醜數(Ugly Number)。求按從小到大的順序的第 n 個醜數。
示例:
輸入: n = 10
輸出: 12
解釋: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 個醜數。
說明:
1
是醜數。n
不超過1690。
思路和代碼:
class Solution {
public int nthUglyNumber(int n) {
int a=0;
int b=0;
int c=0;
int[] dp=new int[n];
dp[0]=1;
for(int i=1;i<n;i++){
int n1=dp[a]*2;
int n2=dp[b]*3;
int n3=dp[c]*5;
int min=Math.min(n1,Math.min(n2,n3));
dp[i]=min;
if(n1==min)
a++;
if(n2==min)
b++;
if(n3==min)
c++;
}
return dp[n-1];
}
}
面試題50. 第一個只出現一次的字符
在字符串 s 中找出第一個只出現一次的字符。如果沒有,返回一個單空格。
示例:
s = "abaccdeff"
返回 "b"
s = ""
返回 " "
思路和代碼:
使用一個HashMap,如果是第一次出現,就將字符放入map,並設置值爲true。如果不是第一次出現(已經存在於map)就把值改爲false。
之後遍歷字符串,找到第一個對應值爲true的字符,表示是第一個出現一次的字符。
class Solution {
public char firstUniqChar(String s) {
HashMap<Character,Boolean> map=new HashMap<>();
for(char c:s.toCharArray()){
if(map.containsKey(c))
map.put(c,false);
else
map.put(c,true);
}
for(char c:s.toCharArray()){
if(map.get(c))
return c;
}
return ' ';
}
}
面試題52. 兩個鏈表的第一個公共節點
輸入兩個鏈表,找出它們的第一個公共節點。
如下面的兩個鏈表**:**
在節點 c1 開始相交。
示例 1:
輸入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
輸出:Reference of the node with value = 8
輸入解釋:相交節點的值爲 8 (注意,如果兩個列表相交則不能爲 0)。從各自的表頭開始算起,鏈表 A 爲 [4,1,8,4,5],鏈表 B 爲 [5,0,1,8,4,5]。在 A 中,相交節點前有 2 個節點;在 B 中,相交節點前有 3 個節點。
思路和代碼:
相交前的距離設爲S1和S2,S1和S2可能相等也可能不等,相交後直到null的距離設爲x,讓p和q分別從A和B開始走,如果遍歷到空就分別指向B和A,這樣p和q相遇時走的距離都是S1+x+S2,p走了S1+x相當於走完了鏈表A,又走了S2就是相交距離,同理q走了S2+x就相當於走完了鏈表B,又走了S1就是相交距離。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode p=headA;
ListNode q=headB;
while(p!=q){
p=p==null?headB:p.next;
q=q==null?headA:q.next;
}
return p;
}
}
面試題53 - I. 在排序數組中查找數字 I
統計一個數字在排序數組中出現的次數。
示例 1:
輸入: nums = [5,7,7,8,8,10], target = 8
輸出: 2
示例 2:
輸入: nums = [5,7,7,8,8,10], target = 6
輸出: 0
限制:
0 <= 數組長度 <= 50000
思路和代碼:
利用二分查找找到等於目標值的元素索引,找到左邊第一個不爲該元素的索引,找到右邊第一個不爲該元素的索引,然後右索引-左索引-1即爲長度。
class Solution {
public int search(int[] nums, int target) {
int start=0;
int end=nums.length-1;
while(start<=end){
int mid=(start+end)/2;
if(nums[mid]<target)
start=mid+1;
else if(nums[mid]>target)
end=mid-1;
else{
//找到了該數字
int left=mid;
int right=mid;
while(left>=0&&nums[left]==target)//從該位置向左查找
left--;
while(right<nums.length&&nums[right]==target)//從該位置向右查找
right++;
return right-left-1;
}
}
return 0;
}
}
面試題53 - II. 0~n-1中缺失的數字
一個長度爲n-1的遞增排序數組中的所有數字都是唯一的,並且每個數字都在範圍0~n-1之內。在範圍0~n-1內的n個數字中有且只有一個數字不在該數組中,請找出這個數字。
示例 1:
輸入: [0,1,3]
輸出: 2
示例 2:
輸入: [0,1,2,3,4,5,6,7,9]
輸出: 8
思路和代碼:
由於長度爲n-1並且數字範圍也是0~n-1,對數組進行排序,如果索引對應的值不等於索引,那麼說明該數字缺失了。如果全部都對應,那麼說明n-1缺失了。
class Solution {
public int missingNumber(int[] nums) {
Arrays.sort(nums);
for(int i=0;i<nums.length;i++){
if(nums[i]!=i)
return i;
}
return nums.length;
}
}
面試題54. 二叉搜索樹的第k大節點
給定一棵二叉搜索樹,請找出其中第k大的節點。
示例 1:
輸入: root = [3,1,4,null,2], k = 1
3
/ \
1 4
\
2
輸出: 4
示例 2:
輸入: root = [5,3,6,2,4,null,null,1], k = 3
5
/ \
3 6
/ \
2 4
/
1
輸出: 4
思路和代碼:
由於是二叉搜索樹,可以按照中序遍歷保存其升序結果。然後倒數第k個就是第k大的。
class Solution {
List<Integer> list=new ArrayList<>();
public int kthLargest(TreeNode root, int k) {
inorder(root);
return list.get(list.size()-k);
}
public void inorder(TreeNode root){
if(root!=null){
inorder(root.left);
list.add(root.val);
inorder(root.right);
}
}
}
面試題55 - I. 二叉樹的深度
輸入一棵二叉樹的根節點,求該樹的深度。從根節點到葉節點依次經過的節點(含根、葉節點)形成樹的一條路徑,最長路徑的長度爲樹的深度。
例如:
給定二叉樹 [3,9,20,null,null,15,7]
,
3
/ \
9 20
/ \
15 7
返回它的最大深度 3 。
思路和代碼:
比較簡單,利用後序遍歷先計算左右子樹的高度,取大值返回。
class Solution {
public int maxDepth(TreeNode root) {
if(root==null)
return 0;
int hL=maxDepth(root.left);
int hR=maxDepth(root.right);
return Math.max(hL,hR)+1;
}
}
面試題55 - II. 平衡二叉樹
輸入一棵二叉樹的根節點,判斷該樹是不是平衡二叉樹。如果某二叉樹中任意節點的左右子樹的深度相差不超過1,那麼它就是一棵平衡二叉樹。
示例 1:
給定二叉樹 [3,9,20,null,null,15,7]
3
/ \
9 20
/ \
15 7
返回 true
。
示例 2:
給定二叉樹 [1,2,2,3,3,null,null,4,4]
1
/ \
2 2
/ \
3 3
/ \
4 4
返回 false
。
思路和代碼:
可以利用上一題計算二叉樹深度的方法作爲輔助方法,求出左右子樹的高度,如果相差大於1則不是平衡二叉樹,否則判斷左右子樹,如果左右子樹都是平衡二叉樹則平衡。
class Solution {
public boolean isBalanced(TreeNode root) {
if(root==null)//空樹是平衡二叉樹
return true;
int dL=maxDepth(root.left);
int dR=maxDepth(root.right);
if(Math.abs(dL-dR)>1)
return false;
return isBalanced(root.left)&&isBalanced(root.right);
}
public int maxDepth(TreeNode root) {
if(root==null)
return 0;
int hL=maxDepth(root.left);
int hR=maxDepth(root.right);
return Math.max(hL,hR)+1;
}
}
面試題56 - I. 數組中數字出現的次數
一個整型數組 nums
裏除兩個數字之外,其他數字都出現了兩次。請寫程序找出這兩個只出現一次的數字。要求時間複雜度是O(n),空間複雜度是O(1)。
示例 1:
輸入:nums = [4,1,4,6]
輸出:[1,6] 或 [6,1]
示例 2:
輸入:nums = [1,2,10,4,1,4,3,3]
輸出:[2,10] 或 [10,2]
思路和代碼:
class Solution {
public int[] singleNumbers(int[] nums) {
int x=nums[0];
for(int i=1;i<nums.length;i++)
x=x^nums[i];
int p=x&(-x);//最低1位那個分界數字
int[] arr=new int[2];
for(int i=0;i<nums.length;i++){
if((nums[i]&p)==1)
arr[0]^=nums[i];
else
arr[1]^=nums[i];
}
return arr;
}
}
面試題56 - II. 數組中數字出現的次數 II
在一個數組 nums
中除一個數字只出現一次之外,其他數字都出現了三次。請找出那個只出現一次的數字。
示例 1:
輸入:nums = [3,4,3,3]
輸出:4
示例 2:
輸入:nums = [9,1,7,9,7,9,7]
輸出:1
思路和代碼:
使用一個HashMap,鍵是每個數字,value是一個boolean值,如果是第一次放入就設爲true,否則就設爲false。
class Solution {
public int singleNumber(int[] nums) {
HashMap<Integer,Boolean> map=new HashMap<>();
for(int n:nums){
if(map.containsKey(n))
map.put(n,false);
else
map.put(n,true);
}
Set<Integer> integers = map.keySet();
for(int i:integers){
if(map.get(i))
return i;
}
return -1;
}
}
面試題57. 和爲s的兩個數字
輸入一個遞增排序的數組和一個數字s,在數組中查找兩個數,使得它們的和正好是s。如果有多對數字的和等於s,則輸出任意一對即可。
示例 1:
輸入:nums = [2,7,11,15], target = 9
輸出:[2,7] 或者 [7,2]
示例 2:
輸入:nums = [10,26,30,31,47,60], target = 40
輸出:[10,30] 或者 [30,10]
思路和代碼:
根據數組有序的特點利用雙指針,如果和比目標大,將尾指針前移,和比目標小將頭指針後移,找到就返回。
class Solution {
public int[] twoSum(int[] nums, int target) {
int p=0;
int q=nums.length-1;
while(p<q){
int sum=nums[p]+nums[q];
if(sum>target)
q--;
else if(sum<target)
p++;
else
return new int[]{nums[p],nums[q]};
}
return new int[2];
}
}
面試題57 - II. 和爲s的連續正數序列
輸入一個正整數 target
,輸出所有和爲 target
的連續正整數序列(至少含有兩個數)。
序列內的數字由小到大排列,不同序列按照首個數字從小到大排列。
示例 1:
輸入:target = 9
輸出:[[2,3,4],[4,5]]
示例 2:
輸入:target = 15
輸出:[[1,2,3,4,5],[4,5,6],[7,8]]
思路和代碼:
利用求和公式來計算和,使用雙指針,初始頭指針1,尾指針2,如果和小於目標,將尾指針後移。
如果和大於目標,將頭指針後移,如果頭指針超過了尾指針就終止。
如果和等於目標創建一個數組,長度是尾指針-頭指針+1,然後將start開始的數組長度個數賦值給數組。之後將數組添加到結果集。
最後將list轉爲int數組。
class Solution {
public int[][] findContinuousSequence(int target) {
List<int[]> res=new ArrayList<>();
int start=1;
int end=2;
while(start<end){
int sum=(start+end)*(end-start+1)/2;
if(sum<target)
end++;
else if(sum>target)
start++;
else{
int[] temp=new int[end-start+1];
int num=start;
for(int i=0;i<temp.length;i++)
temp[i]=num++;
res.add(temp);
start++;
end++;
}
}
int[][] result=new int[res.size()][];
for(int i=0;i<result.length;i++)
result[i]=res.get(i);
return result;
}
}
面試題58 - I. 翻轉單詞順序
輸入一個英文句子,翻轉句子中單詞的順序,但單詞內字符的順序不變。爲簡單起見,標點符號和普通字母一樣處理。例如輸入字符串"I am a student. “,則輸出"student. a am I”。
示例 1:
輸入: "the sky is blue"
輸出: "blue is sky the"
示例 2:
輸入: " hello world! "
輸出: "world! hello"
解釋: 輸入字符串可以在前面或者後面包含多餘的空格,但是反轉後的字符不能包括。
示例 3:
輸入: "a good example"
輸出: "example good a"
解釋: 如果兩個單詞間有多餘的空格,將反轉後單詞間的空格減少到只含一個。
思路和代碼:
利用雙指針,首先去掉首尾空格。
讓j總是指向單詞的末尾,i指向單詞的起始位置的前一個。
這樣i就是空格,i+1~j就是單詞,因此substring(i+1,+1)就是單詞子串。
class Solution {
public String reverseWords(String s) {
s=s.trim();
int j=s.length()-1;
int i=j;
StringBuilder sb=new StringBuilder();
while(i>=0){
//從後向前找到第一個空格,此時i爲空格,i+1爲單詞起始位置
while(i>=0&&s.charAt(i)!=' ')
i--;
//將i+1~j子串添加到結果,substring第二個參數是不想複製的第一個位置
sb.append(s.substring(i+1,j+1)+" ");
//此時跳過空格找到下一個單詞的末尾
while(i>=0&&s.charAt(i)==' ')
i--;
j=i;//讓j繼續指向單詞末尾
}
return sb.toString().trim();
}
}
面試題58 - II. 左旋轉字符串
字符串的左旋轉操作是把字符串前面的若干個字符轉移到字符串的尾部。請定義一個函數實現字符串左旋轉操作的功能。比如,輸入字符串"abcdefg"和數字2,該函數將返回左旋轉兩位得到的結果"cdefgab"。
示例 1:
輸入: s = "abcdefg", k = 2
輸出: "cdefgab"
示例 2:
輸入: s = "lrloseumgh", k = 6
輸出: "umghlrlose"
思路和代碼:
比較簡單,就是把索引n開始的字符串作爲頭拼上索引0~n-1的字符串作爲尾。
注意substring(n)表示截取n開始到末尾的字符串,substring(m,n)表示截取m到n-1的字符串。
class Solution {
public String reverseLeftWords(String s, int n) {
return s.substring(n)+s.substring(0,n);
}
}
面試題59 - II. 隊列的最大值
請定義一個隊列並實現函數 max_value
得到隊列裏的最大值,要求函數max_value
、push_back
和 pop_front
的均攤時間複雜度都是O(1)。
若隊列爲空,pop_front
和 max_value
需要返回 -1
示例 1:
輸入:
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
輸出: [null,null,null,2,1,2]
示例 2:
輸入:
["MaxQueue","pop_front","max_value"]
[[],[],[]]
輸出: [null,-1,-1]
思路和代碼:
使用兩個雙端隊列。
class MaxQueue {
LinkedList<Integer> l1=new LinkedList<>();
LinkedList<Integer> l2=new LinkedList<>();
public MaxQueue() {
}
//l2不爲空就返回隊首,否則-1
public int max_value() {
return l2.isEmpty()?-1:l2.peekFirst();
}
//入隊時l1正常操作,l2要把隊尾小於value的移除掉,然後入隊
public void push_back(int value) {
l1.add(value);
while(!l2.isEmpty()&&l2.peekLast()<value)
l2.pollLast();
l2.add(value);
}
//出隊時l1正常操作,l2的隊首=l1的隊首纔出隊
public int pop_front() {
int temp=l1.isEmpty()?-1:l1.pollFirst();
if(!l2.isEmpty()&&temp==l2.peekFirst())
l2.pollFirst();
return temp;
}
}
面試題61. 撲克牌中的順子
從撲克牌中隨機抽5張牌,判斷是不是一個順子,即這5張牌是不是連續的。2~10爲數字本身,A爲1,J爲11,Q爲12,K爲13,而大、小王爲 0 ,可以看成任意數字。A 不能視爲 14。
示例 1:
輸入: [1,2,3,4,5]
輸出: True
示例 2:
輸入: [0,0,1,2,5]
輸出: True
思路和代碼:
首先對數組進行排序,然後定義一個變量joker表示大小王的數量。
因爲是順子,必須5張全部不一樣,除非一樣的是大小王,否則無法湊成順子。
例如12300可以湊成順子,因爲大小王可以當作45,但是12344就不行了。
因此如果一個數字不是0,即不是大小王,它又和後面的數字一樣,肯定就不能湊成順子。
有幾個大小王,joker就是幾,例如00125,joker=2,此時不爲大小王的起始索引也是2,判斷最大元素減去不爲大小王的元素,如果小於5,就可以湊成順子。
例如00125中,5-1小於5,可以湊成順子,因爲125已經佔據了3個,大小王佔據了2個。
12345中也可以,5-1小於5,並且本來就是五個不同的。
01236就不可以,因爲6-1等於5,無法湊成順子。
class Solution {
public boolean isStraight(int[] nums) {
int joker=0;
Arrays.sort(nums);
int i=0;
for(i=0;i<4;i++){
if(nums[i]==0)
joker++;
else if(nums[i]==nums[i+1])
return false;
}
return nums[4]-nums[joker]<5;
}
}
面試題62. 圓圈中最後剩下的數字
0,1,n-1這n個數字排成一個圓圈,從數字0開始,每次從這個圓圈裏刪除第m個數字。求出這個圓圈裏剩下的最後一個數字。
例如,0、1、2、3、4這5個數字組成一個圓圈,從數字0開始每次刪除第3個數字,則刪除的前4個數字依次是2、0、4、1,因此最後剩下的數字是3。
示例 1:
輸入: n = 5, m = 3
輸出: 3
示例 2:
輸入: n = 10, m = 17
輸出: 2
思路和代碼:
class Solution {
public int lastRemaining(int n, int m) {
int res=0;
for(int i=2;i<=n;i++)
res=(res+m)%i;
return res;
}
}
面試題63. 股票的最大利潤
假設把某股票的價格按照時間先後順序存儲在數組中,請問買賣該股票一次可能獲得的最大利潤是多少?
示例 1:
輸入: [7,1,5,3,6,4]
輸出: 5
解釋: 在第 2 天(股票價格 = 1)的時候買入,在第 5 天(股票價格 = 6)的時候賣出,最大利潤 = 6-1 = 5 。
注意利潤不能是 7-1 = 6, 因爲賣出價格需要大於買入價格。
示例 2:
輸入: [7,6,4,3,1]
輸出: 0
解釋: 在這種情況下, 沒有交易完成, 所以最大利潤爲 0。
思路和代碼:
實時更新最大利潤和最小買入價格。
class Solution {
public int maxProfit(int[] prices) {
if(prices.length==0)
return 0;
int max=0;//第一天只能買,沒有利潤
int minBuy=prices[0];//此時最低買入價就是第一天的買入價
//第二天開始纔可以賣股票
for(int i=1;i<prices.length;i++){
//如果此時賣出可以大於最大利潤就更新最大利潤
if(prices[i]-minBuy>max)
max=prices[i]-minBuy;
//如果此時價格小於最小買入價格就更新買入價
if(prices[i]<minBuy)
minBuy=prices[i];
}
return max;
}
}
面試題64. 求1+2+…+n
難度中等64
求 1+2+...+n
,要求不能使用乘除法、for、while、if、else、switch、case等關鍵字及條件判斷語句(A?B:C)。
示例 1:
輸入: n = 3
輸出: 6
示例 2:
輸入: n = 9
輸出: 45
思路和代碼:
使用短路與(&&)的前半部分來進行循環判斷,如果n==1了就不累加了,直接返回n。
如果n>1,就將n累加上n-1的值再返回,第二個布爾表達式的後半部分(”==0“)可以隨便寫,只要前面是對n累加即可。
class Solution {
public int sumNums(int n) {
boolean b=(n>1)&&(n+=sumNums(n-1))==0;
return n;
}
}
面試題65. 不用加減乘除做加法
寫一個函數,求兩個整數之和,要求在函數體內不得使用 “+”、“-”、“*”、“/” 四則運算符號。
示例:
輸入: a = 1, b = 1
輸出: 2
思路和代碼:
將a+b的和拆分爲非進位和(異或運算)和進位(與運算)。
計算出進位要左移一位,例如0010和0010的與結果還是0010但進位是0100,要左移一位。
class Solution {
public int add(int a, int b) {
while(b!=0){//進位不爲0
int c=(a&b)<<1;//計算進位
a^=b;//計算無進位和
b=c;//更新進位
}
return a;
}
}
面試題66. 構建乘積數組
給定一個數組 A[0,1,…,n-1]
,請構建一個數組 B[0,1,…,n-1]
,其中 B
中的元素 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]
。不能使用除法。
示例:
輸入: [1,2,3,4,5]
輸出: [120,60,40,30,24]
提示:
- 所有元素乘積之和不會溢出 32 位整數
a.length <= 100000
思路和代碼:
先正着乘,保存自己以左的元素乘積,再反着乘一次,乘上自己以右的元素乘積。
class Solution {
public int[] constructArr(int[] a) {
int[] b=new int[a.length];
for(int i=0,n=1;i<b.length;i++){
b[i]=n;
n*=a[i];
}
for(int i=b.length-1,n=1;i>=0;i--){
b[i]*=n;
n*=a[i];
}
return b;
}
}
面試題67. 把字符串轉換成整數
寫一個函數 StrToInt,實現把字符串轉換成整數這個功能。不能使用 atoi 或者其他類似的庫函數。
首先,該函數會根據需要丟棄無用的開頭空格字符,直到尋找到第一個非空格的字符爲止。
當我們尋找到的第一個非空字符爲正或者負號時,則將該符號與之後面儘可能多的連續數字組合起來,作爲該整數的正負號;假如第一個非空字符是數字,則直接將其與之後連續的數字字符組合起來,形成整數。
該字符串除了有效的整數部分之後也可能會存在多餘的字符,這些字符可以被忽略,它們對於函數不應該造成影響。
注意:假如該字符串中的第一個非空格字符不是一個有效整數字符、字符串爲空或字符串僅包含空白字符時,則你的函數不需要進行轉換。
在任何情況下,若函數不能進行有效的轉換時,請返回 0。
說明:
假設我們的環境只能存儲 32 位大小的有符號整數,那麼其數值範圍爲 [−231, 231 − 1]。如果數值超過這個範圍,請返回 INT_MAX (231 − 1) 或 INT_MIN (−231) 。
示例 1:
輸入: "42"
輸出: 42
示例 2:
輸入: " -42"
輸出: -42
解釋: 第一個非空白字符爲 '-', 它是一個負號。
我們儘可能將負號與後面所有連續出現的數字組合起來,最後得到 -42 。
示例 3:
輸入: "4193 with words"
輸出: 4193
解釋: 轉換截止於數字 '3' ,因爲它的下一個字符不爲數字。
示例 4:
輸入: "words and 987"
輸出: 0
解釋: 第一個非空字符是 'w', 但它不是數字或正、負號。
因此無法執行有效的轉換。
示例 5:
輸入: "-91283472332"
輸出: -2147483648
解釋: 數字 "-91283472332" 超過 32 位有符號整數範圍。
因此返回 INT_MIN (−231) 。
思路和代碼:
首先去掉空格,轉爲字符數組,如果此時字符數組長度已爲0,直接返回0。
然後使用boolean遍歷標識正負值,如果是負號就設爲false,如果不是正號就代表第一個字符不是正負號,從第一個字符開始計算。
使用long類型變量保存臨時結果,一旦超過Int範圍就返回int的最大值或最小值。
class Solution {
public int strToInt(String str) {
//去掉空格
char[] chars=str.trim().toCharArray();
//去掉空格後長度0
if(chars.length==0)
return 0;
//標識正負數
boolean positive=true;
int start=1;
//第一個是減號,從第二個開始,如果第一個不是減號也不是加號,從第一個開始
if(chars[0]=='-')
positive=false;
else if(chars[0]!='+')
start=0;
long res=0;//用long類型保存結果防止int越界
for(int i=start;i<chars.length;i++){
if(chars[i]<'0'||chars[i]>'9')//遇到非數字結束
break;
res=res*10+(chars[i]-'0');
if(res>Integer.MAX_VALUE)//如果越界根據正負號返回結果
return positive?Integer.MAX_VALUE:Integer.MIN_VALUE;
}
return positive?(int)res:(int)-res;
}
}
面試題68 - I. 二叉搜索樹的最近公共祖先
給定一個二叉搜索樹, 找到該樹中兩個指定節點的最近公共祖先。
百度百科中最近公共祖先的定義爲:“對於有根樹 T 的兩個結點 p、q,最近公共祖先表示爲一個結點 x,滿足 x 是 p、q 的祖先且 x 的深度儘可能大(一個節點也可以是它自己的祖先)。”
例如,給定如下二叉搜索樹: root = [6,2,8,0,4,7,9,null,null,3,5]
示例 1:
輸入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8
輸出: 6
解釋: 節點 2 和節點 8 的最近公共祖先是 6。
示例 2:
輸入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 4
輸出: 2
解釋: 節點 2 和節點 4 的最近公共祖先是 2, 因爲根據定義最近公共祖先節點可以爲節點本身。
說明:
- 所有節點的值都是唯一的。
- p、q 爲不同節點且均存在於給定的二叉搜索樹中。
思路和代碼:
利用二叉搜索樹的性質可以很簡單地解決問題,在二叉搜索樹中,所有左節點的值都比當前節點值小,所有右節點的值都比當前節點值大。
由於按照先序遍歷(根-左-右)的順序進行dfs(深度優先查找),而一個節點本身也是自己的祖先節點,如果當前節點就是p或q,那麼直接返回。
如果p和q的值都比當前節點值小,說明它們都在左子樹,所以遞歸在左子樹繼續尋找祖先節點。
如果p和q的值都比當前節點值大,說明它們都在右子樹,所以遞歸在右子樹繼續尋找祖先節點。
如果p和q一個值比當前節點小,一個值比當前節點大,那麼說明它們分別在當前節點的左右子樹,那麼它們的祖先節點只能是當前節點。
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null||root==p||root==q)
return root;
if(p.val<root.val&&q.val<root.val)
return lowestCommonAncestor(root.left,p,q);
if(p.val>root.val&&q.val>root.val)
return lowestCommonAncestor(root.right,p,q);
return root;
}
}
面試題68 - II. 二叉樹的最近公共祖先
給定一個二叉樹, 找到該樹中兩個指定節點的最近公共祖先。
百度百科中最近公共祖先的定義爲:“對於有根樹 T 的兩個結點 p、q,最近公共祖先表示爲一個結點 x,滿足 x 是 p、q 的祖先且 x 的深度儘可能大(一個節點也可以是它自己的祖先)。”
例如,給定如下二叉樹: root = [3,5,1,6,2,0,8,null,null,7,4]
示例 1:
輸入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
輸出: 3
解釋: 節點 5 和節點 1 的最近公共祖先是節點 3。
示例 2:
輸入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
輸出: 5
解釋: 節點 5 和節點 4 的最近公共祖先是節點 5。因爲根據定義最近公共祖先節點可以爲節點本身。
說明:
- 所有節點的值都是唯一的。
- p、q 爲不同節點且均存在於給定的二叉樹中。
思路和代碼:
利用一個輔助方法來判斷當前節點是否是p或q其中一個的祖先節點,如果是則返回true,不是則返回false,方法中利用了一個局部變量sum來計算當前節點可以作爲祖先節點的次數,如果sum爲0表示當前節點既不是p也不是q的祖先節點,如果sum爲1表示它是其中一個節點的祖先節點,如果sum爲2表示它既是p也是q的祖先節點,此時將root的值賦給全局變量ancestor,即我們要求的節點。
class Solution {
TreeNode ancestor=null;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
isAncestor(root,p,q);
return ancestor;
}
public boolean isAncestor(TreeNode root,TreeNode p,TreeNode q){
if(root==null)
return false;
int sum=0;
//判斷當前節點是否是p或q,因爲自己也是自己的祖先節點
sum+=(root==p||root==q)?1:0;
//判斷當前節點的左節點是否是p或q的祖先節點,左節點是自己肯定也是
sum+=isAncestor(root.left,p,q)?1:0;
//右節點同理
sum+=isAncestor(root.right,p,q)?1:0;
if(sum==2)
ancestor=root;
return sum>0;
}
}