“ARTS” 是一個學習打卡活動,發起人是骨灰級程式設計師陳皓,網名左耳朵耗子。任務是做一道 LeetCode 演算法題(Algorithm)、閱讀並點評一篇英文技術文章(Review)、學習一個技術技巧(Tip),或分享一篇有觀點和思考的技術文章(Share),都是基於學習和思考的刻意練習。
共勉
一苦一樂相磨鍊,煉極而成福者,其福始久;一疑一信相參勘,勘極而成知者,其知始真。——《菜根譚》
Algorithm
在講解演算法題之前,先補充一個知識點:演算法的時間與空間複雜度
我們評價一個演算法的優劣主要從兩個維度來考量:
-
時間維度:是指執行當前演算法所消耗的時間,我們通常用「時間複雜度」來描述。
-
空間維度:是指執行當前演算法需要佔用多少記憶體空間,我們通常用「空間複雜度」來描述。
1.時間複雜度
時間複雜度的表示方法:大 O 符號表示法,即 T(n)=O(f(n))
這個公式被稱為:演算法的漸進時間複雜度
f(n)
表示每行程式碼的執行次數之和,而 O
表示正比例關係。來看一個具體的例子:
for (int i = 0; i < n; i++) {
j = i;
j++;
}
複製程式碼
上面一共 4 行程式碼,我們假設每一行程式碼的執行時間是一樣的,我們用 一顆粒時間 來表示。
第一行程式碼的執行耗時:n 個顆粒時間 第二行程式碼的執行耗時:n 個顆粒時間 第三行程式碼的執行耗時:n 個顆粒時間 第四行程式碼的執行耗時:是符號,暫時忽略
總的時間:T(n) = 3n * 顆粒時間。從這個耗時可以看出,總的執行時間是隨著 n 的變化而變化,因此,我們可以簡化一下這個演算法的時間複雜度表示:T(n) = O(n)
。
之所以可以簡化,是因為大 O 符號表示法並不是用來表示演算法真實的執行時間,它是用來表示程式碼執行時間的增長變化趨勢的。
常見的時間複雜度量級有:
- 常數階O(1)
- 對數階O(logN)
- 線性階O(n)
- 線性對數階O(nlogN)
- 平方階O()
- 立方階O()
- K次方階O()
- 指數階()
上面從上至下依次的時間複雜度越來越大,執行的效率越來越低。
2.空間複雜度
明白了時間複雜度,那麼空間複雜度也是類似的。空間複雜度也不是用來計算程式實際佔用的空間的。
空間複雜度是對一個演算法在執行過程中臨時佔用儲存空間大小的一個量度,同樣反映的是一個趨勢。
空間複雜度比較常用的有:O(1)、O(n)、O(n²)
來看兩個例子:
int i = 1;
int j = 2;
int sum = i + j;
複製程式碼
程式碼裡的 i 和 j 所分配的空間不會隨著處理資料量而變化,因此它的空間複雜度 S(n) = O(1)
。
第二個例子:
int[] m = new int[n];
for (int i = 0; i < n; i++) {
j = i;
j++;
}
複製程式碼
在第一行程式碼中,通過 new 關鍵字建立了一個陣列出來,陣列 m 佔用的大小為 n ,第 2 到第 5 行程式碼雖然是一個迴圈,但是沒有再分配新的空間,因此,上述程式碼的空間複雜度是:S(n) = O(n)
。
本期演算法
給定一個整數陣列 nums 和一個整數目標值 target,請你在該陣列中找出 和為目標值 的那 兩個 整數,並返回它們的陣列下標。
你可以假設每種輸入只會對應一個答案。但是,陣列中同一個元素在答案裡不能重複出現。 你可以按任意順序返回答案。
示例1:
輸入:nums = [2,7,11,15], target = 9
輸出:[0,1]
解釋:因為 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
複製程式碼
示例2:
輸入:nums = [3,2,4], target = 6
輸出:[1,2]
複製程式碼
示例3:
輸入:nums = [3,3], target = 6
輸出:[0,1]
複製程式碼
解題思路
最直接的方法是採用 暴力列舉法
,用兩個 for 迴圈來遍歷兩個數相加,等於 target 就返回兩個數的下標,程式碼如下:
class Solution {
public int[] twoSum(int[] nums, int target) {
int n = nums.length;
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
if (nums[i] + nums[j] == target) {
return new int[]{i, j};
}
}
}
return new int[0];
}
}
複製程式碼
複雜度分析
-
時間複雜度:O()
-
空間複雜度:O(1)
方法二:雜湊表
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(target - nums[i])) {
return new int[]{map.get(target - nums[i]), i};
}
map.put(nums[i], i);
}
return new int[0];
}
}
複製程式碼
首先建立一個 HashMap,用來存放遍歷過的元素。然後使用一個 for 迴圈來遍歷陣列,內部判斷 HashMap 中是否包含了 target - x
,如果包含直接返回下標,如果不包含,則將該元素存入 HashMap,用元素的值做 key,下標做 value。
複雜度分析
-
時間複雜度:O(N)。其中 N 是陣列中的元素數量。對於每一個元素 x,我們可以 O(1) 地尋找
target - x
。 -
空間複雜度:O(1)
Review
本期的文章來自 Medium。
原文:These 10 Flutter Widgets Every Developer Should Know
本文主要介紹了 Flutter 開發中常用的 10 個基礎 Widget。
- SafeArea
- Expanded
- Wrap
- Opacity
- FutureBuilder
- FloatingActionButton
- PageView
- Table
- FadeInImage
- StreamBuilder
作者選取的這 10 個 Widget 對於實際開發來說還是很實用的,每一個 Widget 都能針對性的解決一些問題,並且在實際使用中也很簡單並且穩定。
當然,在真實的專案中,肯定不止這 10 個 Widget,不過就像作者在開篇寫的一樣:programming is a field that you should practice or getting to know something about daily(程式設計是一個需要每天實踐或者學習一點東西的領域)。
我們可以以這 10 個 Widget 作為基礎,每天學習一點新知識,不斷壯大自己的知識體系。
Tip
使用 Dart 的兩個技巧
1.Use the call Method To Make Classes Callable Like a Function
使用 call 方法讓類像函式一樣可以被呼叫
定義一個類:
class CallMethodDemo {
// Defining call method
String call(String name) => 'hello $name';
}
複製程式碼
使用:
void main() {
var call_input = CallMethodDemo();
// Calling the class through its instance
var call_output = call_input('jack');
print(call_output);
}
複製程式碼
這種讓例項像函式那樣被直接呼叫的類,稱為 callable class
。
注意:一個類只允許有一個 call 方法
2.Use entries To Iterate Through a Map
使用 entries 來遍歷 map 集合
for (var entry in moneySpent.entries) {
// do something with keys and values
print('${entry.key}: ${entry.value}');
}
複製程式碼
保證空安全。
Share
文章連結:Flutter state management for minimalists
狀態管理一直是響應式程式設計中一個繞不開的話題。目前 Flutter 有很多狀態管理的框架,比如 Provider、Bloc、Redux、MobX 等等。
這篇文章的目的並不是對比每個框架的優缺點,而是試圖去解釋清楚什麼是狀態管理。
學習了這篇文章再去理解各個狀態管理框架的話,應該能有所收穫。