1. 時間昂貴、空間廉價
不管是在面試中手撕代碼,還是在實際應用中去優化代碼效率。其核心就是要:儘量降低時間複雜度和空間複雜度。
一段代碼會消耗計算時間、資源空間,從而產生時間複雜度和空間複雜度。代碼效率的瓶頸一般也發生在時間或者空間這兩個方面。如果是缺少計算空間,花錢買服務器就可以了。但是如果是計算時間太長,只能投入寶貴的人生去跑無限的程序了!
相比於空間複雜度,時間複雜度的降低就顯得更加重要了。因此,你會發現這樣的結論:空間是廉價的,而時間是昂貴的。
既然時間是昂貴的,空間是廉價的。那麼自然想到一個問題:能不能通過損失一點空間,換得時間上的縮短呢?
注:圖片來源:https://kaiwu.lagou.com/course/courseInfo.htm?courseId=185#/detail/pc?id=3340
其實這是可以的。舉我們城市交通的例子:在市中心的十字路上,通行的車輛很多,常常需要設置交通燈維持秩序。這就會使得司機花費一些時間去等待,因此時間成本就會大大增加。但是當我們建立了交橋後,車輛就可以選擇分流行駛,這會大大減小等待交通燈的時間,實現了以空間換取司機們寶貴的時間。
總結上面的例子,可以得到結論:可以用廉價的空間換取寶貴的時間。那麼在代碼中,也是遵循着一樣的邏輯,可以通過用空間去換取時間,完成時間複雜度向空間複雜度的轉換。 而在這個過程中,需要我們選擇合適的數據結構。
2. 程序優化核心思路
降低時間複雜度的方法有:遞歸、二分法、排序算法、動態規劃等
降低空間複雜度的核心思路就是:能用低複雜度的數據結構能解決問題,就千萬不要用高複雜度的數據結構。
程序優化的最核心的思路,分如下三個步驟:
- 第一步,暴力解法。在沒有任何時間、空間約束下,完成代碼任務的開發。
- 第二步,去除無效操作。將代碼中的無效計算、無效存儲剔除,降低時間或空間複雜度。
- 第三步,時空轉換。設計合理的數據結構,完成時間複雜度向空間複雜度的轉移。
注:常用的算法、數據結構將會在後面陸續分享。
3. 舉例說明:代碼優化
例1:假設小明有任意多張面額爲 2 元、5元、10元的貨幣,現要用它們湊出 100 元,請問小明最多總能找出多少種方法?。
第一種解法:
#include<iostream>
using namespace std;
int main()
{
int count=0;
for(int i=0;i<=100/10;i++)
{
for(int j=0;j<=100/5;j++)
{
for(int k=0;k<=100/2;k++)
{
if(10*i+5*j+2*k==100)
count+=1;
}
}
}
cout<<"一共有"<<count<<"種解決方法"<<endl;
return 0;
}
運行結果:
分析:這段代碼用了三層嵌套的for循環,所以時間複雜度是O()。
第二種解法:
#include<iostream>
using namespace std;
int main()
{
int count=0;
for(int i=0;i<=100/10;i++)
{
for(int j=0;j<=100/5;j++)
{
if((100-10*i-5*j>=0)&&((100-10*i-5*j)%2==0))
count+=1;
}
}
cout<<"一共有"<<count<<"種解決方法"<<endl;
return 0;
}
運行結果:
分析:對代碼結構進行了優化,通過兩個嵌套的for循環後,直接判斷餘下的錢能否被2整除。因此整個時間複雜度是O()。
例2:查找出一個數組中,出現次數最多的那個元素的數值。例如,輸入數組 a = [1,6,3,5,5,5,6 ] 中,查找出現次數最多的數值。
第一種解法:
#include<iostream>
using namespace std;
int main()
{
int temp=0,max_temp=0,max=0;
int a[7]={1,6,3,5,5,5,6};
for(int i=0;i<7;i++)
{
temp=0; //清零
for(int j=0;j<7;j++)
{
if(a[i]==a[j])
temp+=1; //記錄每一個元素出現的次數
}
if(temp>max_temp)
{
max_temp=temp;
max=a[i];
}
}
cout<<"出現次數最多的數是:"<<max<<endl;
return 0;
}
運行結果:
分析:在這裏,我們用了兩個嵌套的for循環,用來統計每一個元素出現的次數。因此這種代碼結構的時間複雜度是O()。
第二種解法:
#include<iostream>
#include<map>
using namespace std;
int main()
{
int max = 0; //出現最多的次數
int max_num = 0; //出現次數最多的數
int a[7]={1,6,3,5,5,5,6};
map<int, int>mymap; //創建一個新的字典
for(int i=0;i<7;i++)
{
if(mymap.find(a[i])==mymap.end()) //判斷字典中是否存在該鍵a[i]
{
mymap.insert(pair<int, int>(a[i],1)); //如果不存在,插入一個鍵值對:{a[i]:1}
}
else
{
mymap[a[i]]+=1; //如果存在,該鍵對應的值加1 (即該元素出現的次數加1)
}
if(mymap[a[i]]>max) //比較元素出現的次數次
{
max = mymap[a[i]]; //取最大次數
max_num = a[i]; //最大次數對應的鍵
}
}
cout<<"出現次數最多的數是:"<<max_num<<endl;
return 0;
}
運行結果:
分析:這段代碼採用字典map的數據結構,來存放 {元素:元素出現的次數}。整個代碼結構上,新創建了一個字典用來存放鍵值對,因此空間複雜度是O(n);只用了一個for循環,所以時間複雜度降低爲O(n)。
從這個案例可以看出,使用了合理的數據結構——字典,使得這個任務的時間複雜度向空間複雜度轉移。時間複雜度從O()降低到O(n),空間複雜度從O(1)增加到O(n)。
參考鏈接:https://kaiwu.lagou.com/course/courseInfo.htm?courseId=185#/detail/pc?id=3340