看了一下,LeetCode
上劍指OFFER
的題目大概有70道左右,難度大多爲簡單到中等。但是如果放開思路,深究多種解法,難度也並不低。總之適合我等菜鳥,先記錄在此,並立下🏳️🏳️🏳️ 🚩🚩🚩:
這些算法題,我要全部喫掉!!!
當然,這裏只記錄一些常規的,或者比較不錯的算法,其中也有自己的思考,但是文字註釋並不多,很適合下次光臨回味的那種。每一篇blog
大概3~4
道題的記錄量,那麼,話不多說。
287. 尋找重複數
給定一個包含 n + 1 個整數的數組 nums
,其數字都在 1
到 n
之間(包括 1
和 n
),可知至少存在一個重複的整數。假設只有一個重複的整數,找出這個重複的數。
示例 1:
輸入: [1,3,4,2,2]
輸出: 2
示例 2:
輸入: [3,1,3,4,2]
輸出: 3
說明:
- 不能更改原數組(假設數組是隻讀的)。
- 只能使用額外的
O(1)
的空間。 - 時間複雜度小於
O(n2)
。 - 數組中只有一個重複的數字,但它可能不止重複出現一次。
如果沒有說明中的限制,這裏有很多種做法。比如開闢一個哈希表,或者開闢一個數組,其索引對應的就是其應該正確放置的值。這些方法就保證了時間優先的原則。
var findRepeatNumber = function(nums) {
// set
const s = new Set();
for (let num of nums) {
if (s.has(num)) {
return num;
}
s.add(num);
}
};
此外,也可以利用O(nlogn)
的時間複雜度將數組排序,那麼重複的數字也就很明顯了。
如果對空間排序有要求,就像題目所說的那樣,就可以通過抽屜原理來解決,抽屜原理是啥?
抽屜原理的一般含義爲:“如果每個抽屜代表一個集合,每一個蘋果就可以代表一個元素,假如有n+1個元素放到n個集合中去,其中必定有一個集合裏至少有兩個元素。
對此,我們可以通過不斷將元素放置到其正確的抽屜處,如此循環。這樣就能保證在一段時間內發現一個抽屜具有兩個元素,從而找到重複的數字。具體思路如下:
從頭掃描數組,遇到下標爲i
的元素如果不等於i
的話(假設等於j
),就將其與下標爲j
的元素互換,每一次互換就確保了某個數字正確歸位,因此,如果數組中存在重複的元素,則必然會引起衝突
,即當前元素值與待交換的值相等,這樣就找到了重複的元素。
var findRepeatNumber = function(nums) {
// 抽屜原理
const n = nums.length;
for (let i = 0; i < n; i++) {
let val = nums[i]
while (val !== i) {
if (val === nums[val]) {
return val
}
// python 這裏有坑
[nums[val], nums[i]] = [nums[i], nums[val]];
}
}
};
面試題07. 重建二叉樹
輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。
例如,給出
前序遍歷 preorder = [3,9,20,15,7]
中序遍歷 inorder = [9,3,15,20,7]
返回如下的二叉樹:
3
/ \
9 20
/ \
15 7
限制:
0 <= 節點個數 <= 5000
方法一:遞歸
整體來看邏輯比較複雜,但是用遞歸進行分而治之能很簡單地解決。算法的基本思路在於將重建二叉樹的複雜度平攤到各個子樹的遞歸構建上,要構建子樹,就需要確定子樹相應的前序遍歷和中序遍歷。對此就可以利用前序遍歷和中序遍歷的特點來求解。首先preorder
的第一個節點爲當前根節點,而中序遍歷的遍歷策略爲左=>根=>右
,因此利用inorder
求上面根節點的索引就不難找出相應的左右子樹範圍,遞歸建樹即可,停止條件爲當前僅當列表中只有一個節點,直接返回。
var buildTree = function(preorder, inorder) {
if (preorder.length < 1) return null;
const root = new TreeNode(preorder[0]);
if (preorder.length === 1) return root;
const ind = inorder.findIndex(node => node === root.val);
const leftInorder = inorder.slice(0, ind);
const rightInorder = inorder.slice(ind+1);
root.left = buildTree(preorder.slice(1, 1+ind), leftInorder);
root.right = buildTree(preorder.slice(1+ind), rightInorder);
return root;
};
面試題05. 替換空格
請實現一個函數,把字符串 s
中的每個空格替換成"%20"。
示例 1:
輸入:s = "We are happy."
輸出:"We%20are%20happy."
限制:
0 <= s 的長度 <= 10000
方法一:快捷方法
最簡單的方法就是通過循環遍歷原字符串,一旦遇到空格則在後續str += '20%'
即可,問題不大。當然也可以做順風車,直接利用字符串中成熟的API
就可以得到結果:
str.split(' ').join('20%');
方法二:雙指針從後向前填充法
對於源字符串而言,只要存在一個空格字符,就將其變爲20%
,也就是每一個空格就會使得結果字符串的長度多出2個。出於對原字符串就地修改的想法,首先遍歷原字符串找到空格個數,然後在原字符串的末尾擴充相應的空白待填空間,設置兩個指針p1, p2
分別指向原字符串末尾和擴充後的字符串末尾,然後兩個指針向前移動,如果p1
指針指向了原字符中的空格字符,則在p2
指針處依次填充0、2、%
字符,否則就簡單將p1
指針的字符賦值給p2
即可,直到p1,p2
的指向相同,則退出。
var replaceSpace = function(s) {
const n = s.length;
if (n === 0) return '';
// 統計空格個數
let res = [],
cnt = 0;
for (let i of s) {
if (i === ' ') {
cnt += 1;
}
res.push(i);
}
// JS 字符 immutable 性質
// 轉換爲數組操作
res = [...res, ...new Array(2*cnt).fill(' ')];
let [p1, p2] = [n-1, res.length-1];
while (p1 >= 0 && p2 > p1) {
if (res[p1] === ' ') {
res[p2--] = '0';
res[p2--] = '2';
res[p2--] = '%';
p1--;
continue;
}
res[p2--] = res[p1--];
}
return res.join('');
};
雖然此方法的初衷在於減小空間複雜度。但是因爲JavaScript
的immutable
性質,似乎只有通過開闢結果數組來輔助實現,或許我還沒有想到新的方法。。。
面試題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]
提示:
1 <= values <= 10000
最多會對 appendTail、deleteHead 進行 10000 次調用
將其中一個棧stack1
作爲數據壓入存放的棧,將第二個棧stack2
作爲數據彈出的棧。基本思路爲:當執行所謂隊列的appendTail
操作的時候,stack1
執行相應邏輯。如果執行deleteHead
,分兩種情況討論,如果stack2
爲空的話,則考慮將stack1
中的所有數據pop
到stack2
中,然後彈出stack2
頂部即可,這樣就實現了隊列的彈出順序,當然stack1
裏也沒有數據的時候,就如題意返回-1
即可。
var CQueue = function() {
this.stack1 = [];
this.stack2 = [];
};
/**
* @param {number} value
* @return {void}
*/
CQueue.prototype.appendTail = function(value) {
this.stack1.push(value);
};
/**
* @return {number}
*/
CQueue.prototype.deleteHead = function() {
if (this.stack2.length === 0) {
if (this.stack1.length > 0) {
while (this.stack1.length > 0) {
const val = this.stack1.pop();
this.stack2.push(val);
}
} else {
return -1;
}
}
const top = this.stack2.pop();
return top;
};
當然這裏利用兩個棧實現了隊列,換一個思想,我們也可以通過兩個隊列來實現一個棧…具體怎麼操作,閉上眼睛,感受一下。