力扣題解《卑鄙的異鄉(xiāng)人碎乃,巧妙的動態(tài)規(guī)劃(圖解過程))》
方法一:暴力計算
思路
- 從 至 遍歷姊扔,將每一個數(shù)字轉(zhuǎn)換成二進(jìn)制,轉(zhuǎn)換同時計算包含幾個
- 這里采用了模擬短除的方法梅誓,對數(shù)字不斷做除法恰梢,保存余數(shù)佛南,并將數(shù)字用商替換。
復(fù)雜度分析
時間復(fù)雜度 :遍歷需要 嵌言,將每個數(shù)轉(zhuǎn)二進(jìn)制需要 嗅回,其中 表示一個十進(jìn)制數(shù)N,轉(zhuǎn)換為二進(jìn)制后的長度呀页。
空間復(fù)雜度 :最終返回的列表需要占用 空間
快樂小視頻
具體代碼
class Solution {
public:
// 搞一個新的函數(shù)妈拌,來將10進(jìn)制轉(zhuǎn)換成2進(jìn)制數(shù)拥坛。
int trans2bit(int num){
int ans = 0;
// 直到num全部轉(zhuǎn)換完成蓬蝶,才退出循環(huán)
while(num > 0){
// 判斷當(dāng)前位是0還是1
// 無論是0還是1都添加到答案中
ans += num % 2;
// 不斷除以2,計算下一位
num /= 2;
}
// 返回二進(jìn)制表示時猜惋,1的個數(shù)
return ans;
}
vector<int> countBits(int num) {
vector<int> ans(num + 1);
// 0的結(jié)果就是0丸氛,因此從1開始循環(huán)
for(int i = 1; i <= num; i++){
ans[i] = trans2bit(i);
}
return ans;
}
};
方法二:簡單的動態(tài)規(guī)劃
思路
這是一道很模板的線性動態(tài)規(guī)劃。個人認(rèn)為線性動態(tài)規(guī)劃就是前人栽樹后人乘涼著摔。每個新結(jié)果的誕生缓窜,都可以依賴于前面已經(jīng)確定的結(jié)果線性變化得出。
-
狀態(tài)定義:
- 表示數(shù)字 對應(yīng)的二進(jìn)制表示中 的數(shù)量谍咆。
-
狀態(tài)轉(zhuǎn)移:
-
當(dāng) 為偶數(shù)時:由二進(jìn)制表示規(guī)則我們可以知道禾锤,二進(jìn)制表示的最后一位為0,也就是說刪去這個0摹察,對 也不會有任何影響恩掷。因此
dp[i] = dp[i / 2]
-
當(dāng) 為奇數(shù)時:由二進(jìn)制表示規(guī)則我們可以知道,二進(jìn)制表示的最后一位為1供嚎,也就是說數(shù)字 因為這個1黄娘,比數(shù)字 最終結(jié)果多1。因此
dp[i] = dp[(i - 1) / 2] + 1
-
當(dāng) 為偶數(shù)時:由二進(jìn)制表示規(guī)則我們可以知道禾锤,二進(jìn)制表示的最后一位為0,也就是說刪去這個0摹察,對 也不會有任何影響恩掷。因此
轉(zhuǎn)移方程:
-
初始狀態(tài):
- 所有元素置0克滴,當(dāng)計算后才進(jìn)行賦值逼争。
-
返回值:
- 返回 列表。
復(fù)雜度分析
時間復(fù)雜度 :遍歷 數(shù)組需要 劝赔,計算每個 需要 誓焦。
空間復(fù)雜度 :最終返回的列表需要占用 空間
快樂小視頻
具體代碼
class Solution {
public:
vector<int> countBits(int num) {
// 預(yù)先開好空間
vector<int> dp(num + 1);
// 同樣不需要從0開始,因為dp[0] = 0
for(int i = 1; i <= num; i++){
if(i % 2 == 0){ // 偶數(shù)
dp[i] = dp[i / 2];
}
else{ // 奇數(shù)
dp[i] = dp[(i - 1) / 2] + 1;
}
}
// dp數(shù)組即為所求
return dp;
}
};
方法三:卑鄙的異鄉(xiāng)人着帽,巧妙的動態(tài)規(guī)劃
思路
-
這里用到了兩個位運算操作:
-
x & 1
:用于判斷奇偶性 -
x & (x-1)
:用于刪除二進(jìn)制右數(shù)第一個
原理證明1: 和 做按位與罩阵,相當(dāng)于與 做按位與,當(dāng) 為奇數(shù)時启摄, 的二進(jìn)制末尾為 稿壁,此時得到的按位與結(jié)果為 , 否則的話按位與結(jié)果為
原理證明2: 和 做按位與歉备。假設(shè) 形如 的形式傅是。則 形如 ,右數(shù)第一個 被借位,變?yōu)?喧笔。按位與后帽驯,會發(fā)現(xiàn)做到了刪除右數(shù)第一個 的效果。
-
使用位運算
x & 1
书闸,我們可以替換方法一和方法二中尼变,判斷奇偶的操作。即將所有的x % 2
替換為x & 1
浆劲。但需要注意的是嫌术,位運算的優(yōu)先級較低,判斷時需要加括號:(x & 1) == 0
使用位運算
x & (x-1)
牌借,我們可以優(yōu)化方法一中的循環(huán)條件度气,每次刪除最右側(cè)的1,直到數(shù)字變?yōu)?膨报。使用位運算
x & (x-1)
磷籍,我們還可以優(yōu)化方法二中的動態(tài)規(guī)劃。由于該位運算的操作效果是刪除二進(jìn)制右數(shù)第一個 现柠,則顯然處理后的結(jié)果 比原結(jié)果 要小院领,且二者的關(guān)系滿足dp[x] = dp[y] + 1
-
狀態(tài)定義:
- 表示數(shù)字 對應(yīng)的二進(jìn)制表示中 的數(shù)量。
-
狀態(tài)轉(zhuǎn)移:
- 當(dāng) 為0時:顯然dp[0]=0
-
當(dāng) 非0時:按位與之后的結(jié)果够吩,比原數(shù)字的二進(jìn)制值的1的數(shù)目少1比然。因此
dp[i] = dp[i & i - 1)] + 1
-
轉(zhuǎn)移方程:
-
初始狀態(tài):
- 所有元素置0,當(dāng)計算后才進(jìn)行賦值废恋。
-
返回值:
- 返回 列表谈秫。
復(fù)雜度分析
時間復(fù)雜度 :遍歷 數(shù)組需要 ,計算每個 需要 鱼鼓。
空間復(fù)雜度 :最終返回的列表需要占用 空間
快樂小視頻
具體代碼
- 方法一優(yōu)化
class Solution {
public:
// 搞一個新的函數(shù)拟烫,計算數(shù)字num二進(jìn)制表示有幾個1。
int trans2bit(int num){
int ans = 0;
// 直到num位0迄本,才退出循環(huán)
while(num > 0){
// num不為零硕淑,說明至少還有一個1
ans++;
// 刪去num最右側(cè)的1
num &= num - 1;
}
// 返回二進(jìn)制表示時,1的個數(shù)
return ans;
}
vector<int> countBits(int num) {
vector<int> ans(num + 1);
// 0的結(jié)果就是0嘉赎,因此從1開始循環(huán)
for(int i = 1; i <= num; i++){
ans[i] = trans2bit(i);
}
return ans;
}
};
- 方法二優(yōu)化
class Solution {
public:
vector<int> countBits(int num) {
// 預(yù)先開好空間
vector<int> dp(num + 1);
// 同樣不需要從0開始置媳,因為dp[0] = 0
for(int i = 1; i <= num; i++){
// 轉(zhuǎn)移方程:
dp[i] = dp[i & (i - 1)] + 1;
}
// dp數(shù)組即為所求
return dp;
}
};