1 二維數組的查找
題目描述
在一個二維數組中(每個一維數組的長度相同),每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。
題目分析
該二維數組中的一個數,它左邊的數都比它小,下邊的數都比它大。因此,從右上角開始查找,就可以根據 target 和當前元素的大小關係來縮小查找區間,當前元素的查找區間爲左下角的所有元素。
代碼
function Find(target, array) {
// 邊界條件
if (array == null || array.length === 0 || array[0].length === 0) {
return false;
}
var rows = array.length;
var cols = array[0].length;
var r = 0;
var c = cols - 1;
while (r <= rows - 1 && c >= 0) {
if (target == array[r][c]) {
return true;
} else if (target > array[r][c]) {
r++;
} else {
c--;
}
}
return false;
}
2 替換空格
題目描述
請實現一個函數,將一個字符串中的每個空格替換成“%20”。例如,當字符串爲We Are Happy.則經過替換之後的字符串爲We%20Are%20Happy。
題目分析
我們如果要替換空格,兩步
- 先知道空格的位置
- 替換,但是字符串中有多個空格,所以我們就要循環,替換完之後再去查找字符串空格位置
當然你也可以選擇用正則
代碼
function replaceSpace(str)
{
return str.replace(/\s/g, '%20')
}
3 從尾到頭打印鏈表
題目描述
輸入一個鏈表,按鏈表值從尾到頭的順序返回一個ArrayList。
題目分析
代碼
function printListFromTailToHead(head)
{
// write code here
let arr = [];
while(head) {
arr.push(head.val);
head = head.next
}
return arr.reverse();
}
4 重建二叉樹
題目描述
輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。
題目分析
前序遍歷的第一個值爲根節點的值,使用這個值將中序遍歷結果分成兩部分,左部分爲樹的左子樹中序遍歷結果,右部分爲樹的右子樹中序遍歷的結果。
代碼
/* function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
} */
function reConstructBinaryTree(pre, vin)
{
// write code here
// 邊界條件
if(pre.lenght === 0 || vin.length === 0) {
return null;
}
let index = vin.indexOf(pre[0]);
let left = vin.slice(0, index);
let right = vin.slice(index + 1);
return {
val: pre[0],
left: reConstructBinaryTree(pre.slice(1, index + 1), left),
right: reConstructBinaryTree(pre.slice(index + 1), right)
}
}
5 用兩個棧實現隊列
題目描述
用兩個棧來實現一個隊列,完成隊列的Push和Pop操作。 隊列中的元素爲int類型。
題目分析
in 棧用來處理入棧(push)操作,out 棧用來處理出棧(pop)操作。一個元素進入 in 棧之後,出棧的順序被反轉。當元素要出棧時,需要先進入 out 棧,此時元素出棧順序再一次被反轉,因此出棧順序就和最開始入棧順序是相同的,先進入的元素先退出,這就是隊列的順序。
代碼
var inStack = [];
var outStack = [];
function push(node)
{
// write code here
inStack.push(node);
}
function pop()
{
if(!outStack.length) {
while(inStack.length) {
outStack.push(inStack.pop());
}
}
return outStack.pop();
}
6 旋轉數組中的最小數字
題目描述
把一個數組最開始的若干個元素搬到數組的末尾,我們稱之爲數組的旋轉。 輸入一個非減排序的數組的一個旋轉,輸出旋轉數組的最小元素。 例如數組{3,4,5,1,2}爲{1,2,3,4,5}的一個旋轉,該數組的最小值爲1。 NOTE:給出的所有元素都大於0,若數組大小爲0,請返回0。
題目分析
在一個有序數組中查找一個元素可以用二分查找,二分查找也稱爲折半查找,每次都能將查找區間減半,這種折半特性的算法時間複雜度都爲O(logN)
。
本題可以修改二分查找算法進行求解:
- 當 nums[mid] <= nums[high] 的情況下,說明解在 [low, mid] 之間,此時令 high = mid;
- 否則解在 [mid + 1, high] 之間,令 low = mid + 1。
代碼
暴力使用sort
function minNumberInRotateArray(rotateArray)
{
// 邊界條件
if(rotateArray.length === 0) return 0;
return rotateArray.sort(function(a, b){return a - b})[0];
}
二分法
function minNumberInRotateArray(rotateArray) {
if (rotateArray.length == 0) return 0;
var low = 0;
var high = rotateArray.length - 1;
while (low < high) {
var mid = low + high >> 1;
if (rotateArray[mid] <= rotateArray[high])
high = mid;
else
low = mid + 1;
}
return rotateArray[low];
}
7 斐波那契數列
題目描述
大家都知道斐波那契數列,現在要求輸入一個整數n,請你輸出斐波那契數列的第n項(從0開始,第0項爲0)。
n<=39
題目分析
代碼
遞歸
雖然可以實現,但是運行超時:您的程序未能在規定時間內運行結束,請檢查是否循環有錯或算法複雜度過大。
function Fibonacci(n)
{
// write code here
if(n >= 0 && n < 2) return n;
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
動態規劃
function Fibonacci(n)
{
// write code here
let arr = [];
arr[0] = 0;
arr[1] = 1;
for (let i = 2; i <= n; i++) {
arr[i] = arr[i - 1] + arr[i - 2];
}
return arr[n];
}
8 跳臺階
題目描述
一隻青蛙一次可以跳上1級臺階,也可以跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法(先後次序不同算不同的結果)。
題目分析
稍微分析就知道這是斐波那契數列,所以可以動態規劃來做
- 如果兩種跳法,1階或者2階,那麼假定第一次跳的是一階,那麼剩下的是n-1個臺階,跳法是f(n-1)
- 假定第一次跳的是2階,那麼剩下的是n-2個臺階,跳法是f(n-2)
- 由1、2假設可以得出總跳法爲:f(n) = f(n-1) + f(n-2)
- 通過實際的情況可以得出:只有一階的時候 f(1) = 1 ,只有兩階的時候可以有 f(2) = 2
- 可以發現最終得出的是一個斐波那契數列
代碼
遞歸
function jumpFloor(number)
{
// write code here
if (number <= 2) return number;
return jumpFloor(number - 1) + jumpFloor(number - 2);
}
動態規劃
function jumpFloor(number)
{
// write code here
let arr = new Array(number + 1).fill(null);
arr[0] = 0;
arr[1] = 1;
arr[2] = 2;
for(let i = 3; i <= number; i++) {
arr[i] = arr[i - 1] + arr[i - 2];
}
return arr[number]
}
9 變態跳臺階
題目描述
一隻青蛙一次可以跳上1級臺階,也可以跳上2級……它也可以跳上n級。求該青蛙跳上一個n級的臺階總共有多少種跳法。
題目分析
根據上一個題目可以知道,青蛙只跳1或2可以得出是一個斐波那契問題,即a[n] = a[n-1] + a[n-2]
,那麼能跳1,2,3個臺階時a[n] = a[n-1] + a[n-2] + a[n-3] + ... + a[1]
那麼有:
a[n] = a[n-1] + a[n-2] + ... + a[1] ①
a[n-1] = a[n-2] + a[n-3] + ... + a[1] ②
② - ①
可得:a[n] = 2*a[n-1]
代碼
function jumpFloorII(number)
{
// write code here
let acc = 1;
while(--number) {
acc = acc * 2;
}
return acc;
}
10 矩形覆蓋
題目描述
我們可以用21的小矩形橫着或者豎着去覆蓋更大的矩形。請問用n個21的小矩形無重疊地覆蓋一個2*n的大矩形,總共有多少種方法?
題目分析
其本質就是一個斐波那契數列
代碼
function rectCover(number)
{
// write code here
var arr = [];
arr[0] = 0;
arr[1] = 1;
arr[2] = 2;
for (let i = 3; i <= number; i++) {
arr[i] = arr[i - 2] + arr[i - 1];
}
return arr[number];
}
11 二進制中1的個數
題目描述
題目分析
如果一個整數與1做與運算的結果是1,則表示該整數最右邊一位是1,否則是0
那麼解法就出來了:一個一個向右移位,並且判斷最右邊的那個位是否爲1,爲1就count++
但是這樣輸入負數時會陷入死循環,因爲負數右移時,最高位補得是1,那麼這樣會有無數個1
此時這時候有兩個解決辦法:
- 既然不能對要操作的數一個一個右移位,那麼我們可以考慮對另一個數1進行左移位計算
- 把一個整數減去1,再和原整數做與運算,會把該整數最右邊一個1變成0。那麼一個整數的二進制有多少個1,就可以進行多少次這樣的操作
代碼
方法1
function NumberOf1(n) {
let count = 0;
let flag = 1;
while (flag) {
// 循環的次數等於整數二進制的位數,32位的整數需要循環32位
if (flag & n) count++;
flag = flag << 1;
}
return count;
}
方法2
function NumberOf1(n) {
let count = 0;
while (n) {
// 有幾位就循環幾次,效率高
n = n & n - 1
count++;
}
return count;
}
Java
public class Solution {
public int NumberOf1(int n) {
int count = 0;
int flag = 1;
while(flag != 0) {
if((n & flag) != 0) {
count++;
}
flag = flag << 1;
}
return count;
}
}
12 數值的整數次方
題目描述
給定一個double類型的浮點數base和int類型的整數exponent。求base的exponent次方。
題目分析
- js中所有數字都是浮點數,所以
3 / 2 === 1.5
,所以在進行位運算和乘除運算時,最好都使用parseInt()
- 用右移運算(
>>
)代替除運算(/
),所以parseInt(3) / 2 === parseInt(3) >> 1
,直接3 >> 1
也可以,但是浮點數位運算效率十分低 - 用位與運算代替求餘運算(
%
),所以parseInt(3) % 2 === parseInt(3) & 1
,直接3 & 1
也可以,但是浮點數位運算效率十分低
代碼
function Power(base, exponent) {
// write code here
if (exponent === 0) return 1;
if (exponent === 1) return base;
var isNegative = false;
if (exponent < 0) {
exponent = -exponent;
isNegative = true;
}
var pow = Power(base * base, parseInt(exponent) >> 1);
if (parseInt(exponent) & 1 !== 0) pow = pow * base;
return isNegative ? 1 / pow : pow;
}
13 調整數組順序使奇數位於偶數前面
題目描述
輸入一個整數數組,實現一個函數來調整該數組中數字的順序,使得所有的奇數位於數組的前半部分,所有的偶數位於數組的後半部分,並保證奇數和奇數,偶數和偶數之間的相對位置不變。
題目分析
兩個變量作爲奇數和偶數的下標,我們可以維護兩個指針
- 第一個指針初始化時指向數組的第一個數字,它只向後移動
- 第二個指針初始化時指向數組的最後一個數字,它只向前移動
在兩個指針相遇之前,第一個指針總是位於第二個指針的前面。如果第一個指針指向的數字是偶數,並且第二個指針指向的數字是奇數,則交換這兩個數字
代碼
function reOrderArray(array)
{
// write code here
let oddbegin = 0;
let oddcount = 0;
let arr = [];
for (let i = 0; i < array.length; i++) {
if(array[i] & 1) oddcount++
}
for (let i = 0; i < array.length; i++) {
if (array[i] & 1){
arr[oddbegin++] = array[i];
} else{
arr[oddcount++] = array[i];
}
}
return arr;
}
14 鏈表中倒數第k個節點
題目描述
輸入一個鏈表,輸出該鏈表中倒數第k個結點。
題目分析
代碼
我的解法
/*function ListNode(x){
this.val = x;
this.next = null;
}*/
function FindKthToTail(head, k)
{
// write code here
if (head == null || k <= 0) return null;
let arr = [];
while(head) {
arr.push(head);
head = head.next;
}
return arr[arr.length - k];
}
引用類型不是共享內存嗎?應使用淺拷貝
/* function ListNode(x){
this.val = x;
this.next = null;
}*/
function FindKthToTail(head, k) {
if (head === null || k <= 0) return null;
let pNode1 = head;
let pNode2 = head;
while (--k) {
if (pNode2.next !== null) {
pNode2 = pNode2.next;
} else {
return null;
}
}
while (pNode2.next !== null) {
pNode1 = pNode1.next;
pNode2 = pNode2.next;
}
return pNode1;
}
15 反轉鏈表
題目描述
輸入一個鏈表,反轉鏈表後,輸出新鏈表的表頭。
題目分析
所以第一步要把當前節點的next記住
定義3個指針
- 當前遍歷到的節點
- 它的前一個節點
- 它的後一個節點
代碼
/*function ListNode(x){
this.val = x;
this.next = null;
}*/
function ReverseList(pHead)
{
// write code here
// 鏈表題都要判斷邊界條件,下句比較通用,都可以寫
if (!pHead || !pHead.next) return pHead;
// 記錄當前節點
let current = pHead;
// 記錄當前節點的前一個節點
let pre = null;
// 記錄當前節點的後一個節點
let next;
while(current) {
// 先記錄當前節點的下一個節點,到時候斷掉就找不到了
next = current.next;
// 將當前節點的下一節點指向前一個節點
current.next = pre;
// 前一個節點後移1位
pre = current;
// 當前節點後移1位
current = next;
}
return pre;
}
16 合併兩個排序的鏈表
題目描述
輸入兩個單調遞增的鏈表,輸出兩個鏈表合成後的鏈表,當然我們需要合成後的鏈表滿足單調遞增規則。
題目分析
運用遞歸
代碼
/*function ListNode(x){
this.val = x;
this.next = null;
}*/
function Merge(pHead1, pHead2)
{
// write code here
if(!pHead1) return pHead2;
if(!pHead2) return pHead1;
let pMergeHead = null;
if (pHead1.val < pHead2.val) {
pMergeHead = pHead1;
pMergeHead.next = Merge(pHead1.next, pHead2);
} else {
pMergeHead = pHead2;
pMergeHead.next = Merge(pHead1, pHead2.next);
}
return pMergeHead;
}
17 樹的子結構❓
題目描述
輸入兩棵二叉樹A,B,判斷B是不是A的子結構。(ps:我們約定空樹不是任意一個樹的子結構)
題目分析
分析如何判斷樹B是不是樹A的子結構,只需要兩步。很容易看出來這是一個遞歸的過程。一般在樹的求解方面都和遞歸有關。
- 在樹A中找到和B的根結點的值一樣的結點R
- 判斷樹A中以R爲根結點的子樹是不是包含和樹B一樣的結點
代碼
/* function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
} */
function HasSubtree(pRoot1, pRoot2) {
let res = false;
if (pRoot1 === null || pRoot2 === null) return false;
if (pRoot1.val === pRoot2.val) res = doesTree1HasTree2(pRoot1, pRoot2);
if (!res) res = HasSubtree(pRoot1.left, pRoot2);
if (!res) res = HasSubtree(pRoot1.right, pRoot2);
return res;
}
function doesTree1HasTree2(pRoot1, pRoot2) {
if (pRoot2 === null) return true;
if (pRoot1 === null) return false;
if (pRoot1.val !== pRoot2.val) return false;
return doesTree1HasTree2(pRoot1.left, pRoot2.left) && doesTree1HasTree2(pRoot1.right, pRoot2.right);
}
18 二叉樹的鏡像
題目描述
操作給定的二叉樹,將其變換爲源二叉樹的鏡像
二叉樹的鏡像定義:源二叉樹
題目分析
遞歸交換左右節點
代碼
/* function TreeNode(x) {
this.val = x;
this.left = null;
this.right = null;
} */
function Mirror(root)
{
// write code here
if(root === null) return;
if(root.left === null && root.right === null) return;
let temp = root.left;
root.left = root.right;
root.right = temp;
if(root.left) Mirror(root.left);
if(root.right) Mirror(root.right);
}
19 順時針打印矩陣
題目描述
輸入一個矩陣,按照從外向裏以順時針的順序依次打印出每一個數字,例如
題目分析
用左上和右下的座標定位出一次要旋轉打印的數據,一次旋轉打印結束後,往對角分別前進和後退一個單位。
代碼
function printMatrix(matrix)
{
// write code here
if(matrix == null) return;
const rows = matrix.length;
const cols = matrix[0].length;
let start = 0;
let result = [];
while (rows > start * 2 && cols > start * 2) {
result = result.concat(printMatrixInCircle(matrix, rows, cols, start));
start++;
}
return result;
}
function printMatrixInCircle(matrix, rows, cols, start) {
const endX = cols - 1 - start;
const endY = rows - 1 - start;
let result = [];
// 從左往右
for (let i = start; i <= endX; i++) {
result.push(matrix[start][i]);
}
// 從上往下
for(let i = start + 1; i <= endY; i++) {
result.push(matrix[i][endX]);
}
// 從右往左
for(let i = endX - 1; i >= start && endY > start; i--) {
result.push(matrix[endY][i]);
}
// 從下往上
for(let i = endY -1; i >= start + 1 && endX > start; i--) {
result.push(matrix[i][start]);
}
return result;
}
20 包含min函數的棧 中等 輔助棧
21 棧的壓入、彈出序列 中等 輔助棧
22 從上往下打印二叉樹 簡單 廣度遍歷、隊列
23 二叉樹的後續遍歷序列 中等 畫圖
24 二叉樹和爲某一值的遍歷序列 中等 深度遍歷、遞歸
25 複雜鏈表的複製 難 map保存<N,N’> || N->N’得S->S‘
26 二叉搜索樹與雙向鏈表 中等偏難 遞歸、中序遍歷
27 字符串的排列 難 回溯法 || 遞歸全排列法
28 數組中出現次數超過一半的數 中等 partion法 || times變量變化法
29 最小的k個數 中等 partion法
30 連續子數組的最大值 中等 找規律、動態規劃、注意判斷條件
31 (~n整數中1出現的次數 中等 位運算 || 數學分析
把數組排成最小的數 簡單偏難 改變排序規則
醜數 難 動態規劃、注意判斷條件
第一個只出現一次的字符 哈希表記錄
數組中的逆序對 難+ 基於歸併排序、臨時數組
兩個鏈表中的第一個公共節點 簡單 雙指針法
數字在排序數組中出現的次數 簡單偏難 二分法改造
二叉樹的深度 簡單 遞歸
平衡二叉樹 簡單 遞歸
數組中只出現一次的數字 簡單 indexOf || map記錄 || 異或
和爲S的連續正數序列 中等 數學分析
和爲S的字符串 簡單 雙指針
左旋轉字符串 簡單 裁剪拼接
單次翻轉序列 簡單 轉數組,對每項反序
撲克牌順子 中等 注意題目條件、位運算判斷數字重複
孩子們的遊戲 難 數學分析得出公式 || 畫圖按題目做、注意下標
求1+2+3+…+n 中等 位運算、遞歸
不用加減乘除做加法 中等 位運算
把字符串轉成整數 中等 位運算
數組中重複的數字 中等 將值放到對應位置上
構建乘積數組 中等偏上 藉助中間變量存儲後面的乘積
正則表達式的匹配 難 注意判斷條件、遞歸
表示數值的字符串 中等 正則
字符流中第一個不重複的數字 中等 map記錄 || indexOf法
鏈表中環的入口節點 中等 雙指針法、數學分析
刪除鏈表中重複的節點 中等 加頭節點、注意多個重複
二叉樹的下一個節點 中等 畫圖、分析各種情況
對稱的二叉樹 中等 遞歸、對稱遍歷
按之字形順序打印二叉樹 難 廣度遍歷、兩個棧
把二叉樹打印成多行 中等偏難 隊列+兩個記錄變量
序列化二叉樹 中等 數組代表流、遞歸
二叉搜索樹的第k個節點 中等 中序遍歷+計數變量
數據流的中位數 中等 partion法 || 維持排序 || 排序鏈表法 || AVL樹 || 最大堆和最小堆
滑動窗口中的最大值 難 改變參考對象、雙端隊列、存下標
矩陣中的路徑 中等 回溯法
機器人的運動範圍 中等 回溯法
參考資料
[1] https://www.cnblogs.com/wuguanglin/p/code-interview.html
[2] https://github.com/CyC2018/CS-Notes/blob/master/docs/notes/劍指 Offer 題解 - 目錄.md
[3] https://www.cnblogs.com/wuguanglin/p/SummaryOfJSDoAlgorithmProblem.html