一、巧用數組下標數組
例:在統計一個字符串中字幕出現的次數時,咱們就能夠把這些字母當作數組下標,在遍歷的時候,若是字母A遍歷到,則arr['a']就能夠加1了。bash
法一:利用對象的key值不重複優化
var str = 'hajshdjldjf';
function count(str){
var obj = {};
for(var i = 0; i < str.length; i++){
if(obj[str[i]]){
obj[str[i]]++;
}else{
obj[str[i]] = 1;
}
}
console.log(obj);
return obj;
}
count(str);複製代碼
法二:利用數組的下標ui
var str = 'hajshdjldjf';
function count(str){
var arr = [];
for(var i = 0; i < str.length; i++){
if(arr[str[i]]){
arr[str[i]]++;
}else{
arr[str[i]] = 1;
}
}
}
count(str);複製代碼
其實這兩種方法的思想是一致的。spa
例:給你n個無序的int整型數組arr,而且這些整數的取值範圍都在0-20之間,要你在 O(n) 的時間複雜度中把這 n 個數按照從小到大的順序打印出來。指針
對於這道題,若是你是先把這 n 個數先排序,再打印,是不可能O(n)的時間打印出來的。可是數值範圍在 0-20。咱們就能夠巧用數組下標了。把對應的數值做爲數組下標,若是這個數出現過,則對應的數組加1。
code
利用對象:cdn
var arr = [1,2,3,4,5,4,5,6,6,7,6,9,17,16,15,14,12,11];
//常規解法 利用對象的key值不能重複去計算次數
//res去記錄數字出現的順序
function fn(arr){
arr.sort(function(a,b){
return a - b;
});
var res = [];
var resdetail = [];
for(var i = 0; i < arr.length; i++){
if(res.length === 0 || res[res.length-1] !== arr[i]){
res.push(arr[i]);
var obj = {
key:arr[i],
value:1
};
resdetail.push(obj);
}else{
resdetail[resdetail.length-1].value++;
}
}
console.log(resdetail);
return resdetail;
}
fn(arr);複製代碼
利用數組下標對象
var arr = [1,2,3,4,5,4,5,6,6,7,6,9,17,16,15,14,12,11];
//利用數組下標 時間複雜度爲O(n)
function fn(arr){
var temp = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
for(var i = 0; i < arr.length; i++){
temp[arr[i]]++;
}
for(var j = 0; j < 21; j++){
for(var k = 0; k < temp[j]; k++){
console.log(j);
}
}
}
fn(arr);複製代碼
二、巧用取餘blog
有時候咱們在遍歷數組的時候,會進行越界判斷,若是下標差很少要越界了,咱們就把它置爲0從新遍歷。特別是在一些環形的數組中,例如用數組實現的隊列。每每會寫出這樣的代碼:
for (int i = 0; i < N; i++)
{
if (pos < N) {
//沒有越界
// 使用數組arr[pos]
else
{
pos = 0;//置爲0再使用數組
//使用arr[pos]
}
pos++;
}
}複製代碼
實際上咱們能夠經過取餘的方法來簡化代碼
for (int i = 0; i < N; i++) {
//使用數組arr[pos] (咱們假設剛開始的時候pos < N)
pos = (pos + 1) % N;
}複製代碼
三、巧用雙指針
對於雙指針,在作關於單鏈表的題是特別有用,好比「判斷單鏈表是否有環」、「如何一次遍歷就找到鏈表中間位置節點」、「單鏈表中倒數第 k 個節點」等問題。對於這種問題,咱們就可使用雙指針了,會方便不少。
(1)判斷單鏈表中是否有環
咱們能夠設置一個快指針和一個慢指針,慢的一次移動一個節點,快的一次移動兩個節點。若是存在環,快指針會在第二次遍歷時和慢指針相遇。
(2)如何一次遍歷就找到鏈表中間位置節點
同樣是設置一個快指針和慢指針。慢的一次移動一個節點,快的一次移動兩個。當快指針遍歷完成時,慢指針恰好到達中點。
(3)單鏈表中倒數第 k 個節點
設置兩個指針,其中一個指針先移動k-1步,從第k步開始,兩個指針以相同的速度移動。當那個先移動的指針遍歷完成時,第二個指針指向的位置即倒數第k個位置。
四、巧用位移
有時候咱們在進行除數或乘數運算的時候,例如n / 2,n / 4, n / 8這些運算的時候,咱們就能夠用移位的方法來運算了,這樣會快不少。
例如:
n / 2 等價於 n >> 1
n / 4 等價於 n >> 2
n / 8 等價於 n >> 3。
這樣經過移位的運算在執行速度上是會比較快的,也能夠顯的你很厲害的樣子,哈哈。
還有一些 &(與)、|(或)的運算,也能夠加快運算的速度。例如判斷一個數是不是奇數,你可能會這樣作
if(n % 2 == 1){
dosomething();
}複製代碼
不過咱們用與或運算的話會快不少。例如判斷是不是奇數,咱們就能夠把n和1相與了,若是結果爲1,則是奇數,不然就不會。即
if(n & 1 == 1){
dosomething();
)複製代碼
五、設置哨兵位
在鏈表的相關問題中,咱們常常會設置一個頭指針,並且這個頭指針是不存任何有效數據的,只是爲了操做方便,這個頭指針咱們就能夠稱之爲哨兵位了。
例如咱們要刪除頭第一個節點是時候,若是沒有設置一個哨兵位,那麼在操做上,它會與刪除第二個節點的操做有所不一樣。可是咱們設置了哨兵,那麼刪除第一個節點和刪除第二個節點那麼在操做上就同樣了,不用作額外的判斷。固然,插入節點的時候也同樣。
有時候咱們在操做數組的時候,也是能夠設置一個哨兵的,把arr[0]做爲哨兵。例如,要判斷兩個相鄰的元素是否相等時,設置了哨兵就不怕越界等問題了,能夠直接arr[i] == arr[i-1]?了。不用怕i = 0時出現越界。
六、與遞歸相關的一些優化
(1)對於能夠遞歸的問題考慮狀態保存。
當咱們使用遞歸來解決一個問題時,很容易產生重複去算同一個子問題,這個時候咱們要考慮狀態保存以防止重複計算。
例:斐波那契數列
function fn(n){
if(n <= 2){
return 1;
}else{
return fn(n-1) + fn(n-2);
}
}
console.log(fn(10));複製代碼
不過對於可使用遞歸解決的問題,咱們必定要考慮是否有不少重複計算。顯然對於 f(n) = f(n-1) + f(n-2) 的遞歸,是有不少重複計算的。如
就有不少重複計算了。這個時候咱們要考慮狀態保存。而且能夠自底向上。
function fn(n){
var res = [];
res[0] = 1;
res[1] = 1;
for(var i = 2; i < n; i++){
res[i] = res[i-1] + res[i-2];
}
console.log(res[n-1]);
return res[n-1];
}
fn(10);複製代碼
進一步優化:使用兩個變量。
function fn(n){
var a = 1;
var b = 1;
for(var i = 3; i <= n; i++){
a = a + b;
b = a - b;
}
return a;
}
fn(10);複製代碼