[13].羅馬數字轉整數

羅馬數字轉整數

 


題目

羅馬數字包含以下七種字符: I, V, X, L,C,D 和 M。

字符          數值
I             1
V             5
X             10
L             50
C             100
D             500
M             1000

例如, 羅馬數字 2 寫做 IIII ,即爲兩個並列的 1。12 寫做 XIIXII ,即爲 X+IIX + II 。 27 寫做 XXVIIXXVII, 即爲 XX+V+IIXX + V + II

通常情況下,羅馬數字中小的數字在大的數字的右邊。但也存在特例,例如 4 不寫做 IIIIIIII,而是 IVIV。數字 1 在數字 5 的左邊,所表示的數等於大數 5 減小數 1 得到的數值 4 。同樣地,數字 9 表示爲 IXIX。這個特殊的規則只適用於以下六種情況:

  • II 可以放在 VV (5) 和 XX (10) 的左邊,來表示 4 和 9。
  • XX 可以放在 LL (50) 和 CC (100) 的左邊,來表示 40 和 90。
  • CC 可以放在 DD (500) 和 MM (1000) 的左邊,來表示 400 和 900。

給定一個羅馬數字,將其轉換成整數。輸入確保在 1 到 3999 的範圍內。

示例 1:

輸入: "III"
輸出: 3

示例 2:

輸入: "IV"
輸出: 4

示例 3:

輸入: "IX"
輸出: 9

示例 4:

輸入: "LVIII"
輸出: 58
解釋: L = 50, V= 5, III = 3.

示例 5:

輸入: "MCMXCIV"
輸出: 1994
解釋: M = 1000, CM = 900, XC = 90, IV = 4.

 


函數原型

C的函數原型:

int romanToInt(char * s){}

 


邊界判斷

題目裏有說,輸入確保在 1 到 3999 的範圍內。

不過這個怎麼判斷呢,用戶輸入的是羅馬數字,而不是阿拉伯數字…

你問我,我也不知道啊。

但可以對輸入參數做檢查。

if( s == NULL || *s == '\0')  // 指針是否爲NULL
    return 0;

 


算法設計:查表法

因爲只包含了 7 個數字,我們可以建一個表來映射羅馬數字與阿拉伯數字之間的關係。

int map[90] = {'\0'};
// 建表, 來映射數字間的關係;大寫字母的範圍是 65(A)-90(Z)
字符          數值
I             1
V             5
X             10
L             50
C             100
D             500
M             1000

建表
map['I'] = 1;
map['V'] = 5;
map['X'] = 10;
map['L'] = 50;
map['C'] = 100;
map['D'] = 500;
map['M'] = 1000;

讀了示例後,發現羅馬數字主要有倆種情況(從左往右看):

  • 左加:左的數字(前一個數)比右邊的數字(後一個數)大、相等時,加上前一個數
  • 右減:左的數字(前一個數)比右邊的數字(後一個數)小時,減去前一個數

例如,IVIV

I<VI < V,[前一個數II] 比 [後一個數VV] 小,就要減掉前一個數II

int romanToInt(char * s){
    if( s == NULL || *s == '\0')  // 指針是否爲NULL
        return 0;

	int map[90] = {'\0'};
	// 建表, 來映射數字間的關係
	map['I'] = 1;
	map['V'] = 5;
	map['X'] = 10;
	map['L'] = 50;
	map['C'] = 100;
	map['D'] = 500;
	map['M'] = 1000;

    int Roman_val = 0; 						// 定義一個變量,保存羅馬數字轉換後的值
    
    // 倆種情況,分別討論
    for(int i=0; i<strlen(s); i++)         // 從左往右看
        if( map[s[i]] >= map[s[i+1]] )     // 左加(前一個數 >= 後一個數)
            Roman_val += map[s[i]];
        else							   // 右減(前一個數 < 後一個數)
            Roman_val -= map[s[i]];
            
    return Roman_val;
}

AC。

查表法的複雜度:

  • 時間複雜度:Θ(n)\Theta(n)
  • 空間複雜度:Θ(1)\Theta(1)

查表法是使用空間換時間,這個題目需要的空間很小,畢竟只有 7 種狀態。

而大部分能使用查表法的題目,可能就需要大量的空間。雖然會讓時間複雜度很好,但佔用內存太多了。

在時間複雜度上,查表是 Θ(1)\Theta(1)

但是,在真實的計算機中,內存和處理器之間還有一個高速緩存,程序和數據要先從內存進入高速緩存,才能運行。

高速緩存的空間非常有限,通常只有幾兆(MM),查表佔用的內存空間可能是緩存容量的上千倍,這肯定是放不下的,遇到這種情況,計算機本身要進行上千次額外操作,把內存的內容往緩存倒騰。

也就是說,如果建立一個大表,雖然查表只需要做一次,但是準備工作可能要做上千次。

其實划不來,當然,也有一種補救的方法。

把一張大表拆分爲幾個小表,每個表查找一次,再把幾次的相加,雖然查找次數多了,但佔用的內存就很少的,這樣就即有查表法的優點(查表時間複雜度是 Θ(1)\Theta(1)),又補救了佔用大量內存空間的缺陷。

 


算法設計:過程模擬

研究【右減】的情況,前一個數比後一個數小,就要減掉前一個數。

而這個規則只適用於以下六種情況:

  • II 可以放在 VV (5) 和 XX (10) 的左邊,來表示 4 和 9。
  • XX 可以放在 LL (50) 和 CC (100) 的左邊,來表示 40 和 90。
  • CC 可以放在 DD (500) 和 MM (1000) 的左邊,來表示 400 和 900。

如果第 ii 個元素是 II,第 i+1i+1 個元素比第 ii 個元素大,那就只有 VXV、X

如果第 ii 個元素是 XX,第 i+1i+1 個元素比第 ii 個元素大,那就只有 LCL、C

如果第 ii 個元素是 CC,第 i+1i+1 個元素比第 ii 個元素大,那就只有 DMD、M

int romanToInt(char * s){
    int count = 0;
	while (*s){
	    // 左加(前一個數比後一個數大)
		if (*s == 'V')         count += 5;
		else if (*s == 'L')    count += 50;
		else if (*s == 'D')    count += 500;
		else if (*s == 'M')    count += 1000;
        
        // 右減(前一個數比後一個數小,就要減掉前一個數)
		else if (*s == 'I')
			count = (*(s + 1) == 'V' || *(s + 1) == 'X') ? count - 1 : count + 1;
		else if (*s == 'X')
			count = (*(s + 1) == 'L' || *(s + 1) == 'C') ? count - 10 : count + 10;
		else if (*s == 'C')
			count = (*(s + 1) == 'D' || *(s + 1) == 'M') ? count - 100 : count + 100;
		s++;
	}
	return count;
}

過程模擬複雜度:

  • 時間複雜度:Θ(n)\Theta(n)
  • 空間複雜度:Θ(1)\Theta(1)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章