【代碼優化方法論02】選擇合適的數據結構——將昂貴的“時間”轉換爲廉價的“空間”

相關文章:【代碼優化方法論01】衡量程序運行效率——複雜度


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(n3n^3)。

第二種解法:

#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(n2n^2)。

例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(n2n^2)。

第二種解法:

#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(n2n^2)降低到O(n),空間複雜度從O(1)增加到O(n)。


參考鏈接:https://kaiwu.lagou.com/course/courseInfo.htm?courseId=185#/detail/pc?id=3340

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章