Dynamic Programming
338. Counting Bits
[Desciption]
[Analysis]
method-1
本題有非常簡單的做法,就是可以直接對每個數單獨計算其二進制下1的個數。
vector<int> countBits(int num) {
vector<int> vec(num + 1, 0);
for (int i = 0; i <= num; i++) {
int count = 0;
int value = i;
while (value) {
count += value % 2;
value >>= 1;
}
vec[i] = count;
}
return vec;
}
但很顯然,這種做法下,時間複雜度爲O(n * sizeof(i))。
method-2
其實在計算每個數的二進制下1的個數時,存在着大量的重複。
例如111(7)的1的個數,就可以表示爲最後一位的1的個數+除最後一位外1的個數。也就是1 + 2。
因此,我們可以寫出代碼。
class Solution {
public:
vector<int> countBits(int num) {
vector<int> vec(num + 1, 0);
for (int i = 1; i <= num; i++) {
vec[i] = vec[i>>1] + i%2;
cout << vec[i] << " " << vec[i >> 1] << endl;
}
return vec;
}
};
此時的時間複雜度爲O(n)。
647. Palindromic Substrings
[Description]
[analysis]
method-1
最簡單而直觀的方法就是不採取動態規劃,直接遍歷字符串,尋找所有可能的迴文字符串。也就是說,用一個雙重循環遍歷字符串,可以得到該字符串所能構成的所有子字符串,然後對這些子字符串檢驗是否爲迴文串。
時間複雜度爲O( n^3 ),空間複雜度爲O(1)。
method-2
顯然,方法1存在很多重複計算的過程。這時我們可以通過動態規劃的方法來減少時間複雜度。在檢驗迴文字符串的時候,不一定要對所有的子字符串都進行檢驗。比如說,對於abccbs這樣的子字符串,顯然收尾已經不相同,不需要進行檢驗,又對於abccba,此時首尾相同,需要檢驗bccb是否爲迴文字符串,但顯然這個檢驗的過程已經在前面檢驗以b爲尾字符的子字符串中完成了,所以這時候我們就可以得到一種優化的方法。不妨構建一個矩陣,保存所有能夠構成迴文字符串的首index和尾index。此時同樣需要遍歷,但不需要O(n)的時間複雜度進行迴文字符串校驗了。
int countSubstrings(string s) {
int len = s.length();
int count = 0;
vector<vector<int>> matrix(len, vector<int>(len, 0));
for (int i = 0; i < len; i++) {
matrix[i][i] = 1;
}
for (int i = 0; i < len; i++) {
count++;
for (int j = 0; j < i; j++) {
if (s[j] == s[i]) {
if (j == i - 1 || matrix[j + 1][i - 1] == 1) {
count++;
matrix[j][i] = 1;
}
}
}
}
return count;
}
但需要明白的問題是,此時我們是以犧牲空間換時間,時間複雜度爲O( n^2 ),空間複雜度爲O( n^2 )。
method-3
實際上,還存在一種更好的方法。因爲對於任意一個子字符串,都只存在一個唯一的中間index(當子字符串長度爲奇數時,index爲單一值;當子字符串長度爲偶數時,index爲兩個值)。這時,我們便可以把外層循環表示爲遍歷所有子字符串的中間index,然後從這個中間index向兩邊拓展。在拓展的過程中校驗是否滿足迴文字符串。
void extend(string s, int left, int right, int& count) {
while(left >= 0 && right < s.length() && s[left] == s[right]){
left--;
right++;
count++;
}
}
int countSubstrings(string s) {
int count = 0;
for (int i = 0; i < s.length(); i++) {
extend(s, i, i, count);
extend(s, i, i + 1, count);
}
return count;
}
此時,空間複雜度爲O(1),時間複雜度爲O( n^2 )。
646. Maximum Length of Pair Chain
[Description]
646. Maximum Length of Pair Chain
[Analysis]
method-1
這個題暴力求解應該是不那麼容易的。我首先想到的是用動態規劃的想法,用一個vector記錄以當前index爲pair chain最後一個元素的最大長度。通過對每一個index遍歷前面所有的數組,可以計算出對於以該index作爲最後一個chain元素的情況下,最大長度。但需要注意的是,題目沒有說已經排好序,所以需要自行排序。
int findLongestChain(vector<vector<int>>& pairs) {
sort(pairs.begin(), pairs.end(), [](vector<int> vec_1, vector<int> vec_2) {
return vec_1[0] < vec_2[0];
});
int len = pairs.size();
vector<int> vec(len, 1);
for (int i = 1; i < len; i++) {
int max = 1;
for (int j = 0; j < i; j++) {
if (pairs[i][0] > pairs[j][1]) {
if (vec[j] + 1 > max) {
max = vec[j] + 1;
}
}
}
vec[i] = max;
}
int max = 0;
for (int i = 0; i < len; i++) {
if (vec[i] > max) {
max = vec[i];
}
}
return max;
}
時間複雜度爲O( n^2 ),空間複雜度爲O(n)。
method-2
除了動態規劃以外,還存在更簡單的做法。假設存在[a, b], [c, d],其中b < d,此時,[a, b] 形成的最大長度一定大於等於[c, d]所能形成的最大長度,理由就是如果最大長度chain以[c, d]開始,那必然可以以[a, b]開始。於是,只需要對數組進行恰當的排序,便可以簡單地一次循環獲得最大長度。
int findLongestChain(vector<vector<int>>& pairs) {
sort(pairs.begin(), pairs.end(), [](std::vector<int> v_1, vector<int> v_2) {
return v_1[1] < v_2[1] || v_1[1] == v_2[1] && v_1[0] < v_2[0];
});
int cnt = 0;
vector<int>& pair = pairs[0];
for (int i = 0; i < pairs.size(); i++) {
if (i == 0 || pairs[i][0] > pair[1]) {
pair = pairs[i];
cnt++;
}
}
return cnt;
}
時間複雜度爲O(nlogn),快排所需要的最短時間;空間複雜度爲O(1)。