1. 題目列表
- 十進制整數的反碼(進制轉換)
- 總持續時間可被 60 整除的歌曲(數組hash以及簡單的排列組合)
- 在 D 天內送達包裹的能力(二分查找,貪心策略,)重要
- 至少有 1 位重復的數字(問題轉換,排列組合)重要
2. 十進制整數的反碼
簡單進制轉換。
代碼:
class Solution {
public:
int bitwiseComplement(int N) {
if (N == 0) return 1;
vector<int> bits;
while (N){
bits.push_back(N % 2);
N /= 2;
}
for (int i = 0; i < bits.size(); i++)
bits[i] = bits[i] ? 0 : 1;
int res = 0, base = 1;
for (int i = 0; i < bits.size(); i++){
res += bits[i] * base;
base *= 2;
}
return res;
}
};
3. 總持續時間可被 60 整除的歌曲
先用hashtable記錄每個整數出現的次數,再用set記錄所有的不重復整數。
遍歷set,對于元素e,枚舉e ~ 500所有的整數i,判斷(e + i)是否能被60整除,如果能整除,計算整數對數。
整數對數的討論:
當e == i,對數 = C(n,2), n為元素e出現的次數
當e != i,對數 = C(n1,1) + C(n2,1),n1和n2分別為e和i出現的次數。
代碼:
class Solution {
public:
int numPairsDivisibleBy60(vector<int>& time) {
int hashtable[510];
unordered_set<int> t;
memset(hashtable, 0, sizeof(hashtable));
for (int i = 0; i < time.size(); i++){
hashtable[time[i]]++;
t.insert(time[i]);
}
int res = 0;
unordered_set<int>::iterator iter;
for (iter = t.begin(); iter != t.end(); iter++){
int e = *iter;
for (int i = e; i <= 500; i++){
if ((e + i) % 60 == 0){
if (i == e)
res += (hashtable[i] * (hashtable[i] - 1)) / 2;
else
res += hashtable[i] * hashtable[e];
}
}
}
return res;
}
};
4. 在 D 天內送達包裹的能力
二分搜索 + 貪心模擬:
要有敏銳的嗅覺,輸出答案存在明顯的范圍,很有可能可用二分查找。
二分查找的關鍵是:如何尋找貪心策略。本題的貪心是盡可能在D天內使得船的載重量最小,船的載重范圍[max(weights), sum(weights)]
這樣,思路很明了,給定船的當前載重為mid,判斷能否在D天內裝滿所有的貨物,如果能,則搜索[l,mid],否則搜索[mid + 1, r]
代碼:
class Solution {
public:
int shipWithinDays(vector<int>& weights, int D) {
int sum = 0, MAX = -1;
for (int i = 0; i < weights.size(); i++){
sum += weights[i];
MAX = max(MAX, weights[i]);
}
int l = MAX, r = sum, mid;
while (l < r){
mid = (l + r) / 2;
int temp = 0, days = 1;
for (int i = 0; i < weights.size(); i++){
temp += weights[i];
if (temp > mid){
days++;
temp = weights[i];
}
}
if (days <= D){
r = mid;
}else l = mid + 1;
}
return r;
}
};
5. 至少有 1 位重復的數字
排列組合:
注意轉換思路,當討論重復位過于復雜時,轉換問題為計算1~N的不重復位數有多少個。
具體步驟:
假設N的位數為p,先計算1~p-1位的不重復位的個數。如97783,p = 5,設位數為i
i = 1時, 共9種
i = 2時, 共9 * 9 = 81種
i = 3時, 共9 * 9 * 8種
i = 4時, 共9 * 9 * 8 * 7種
i = 5時, 需要逐位討論,設討論的當前位為j
j = 1時,計算第一位小于N[1]的個數,_ _ _ _ _ 共8 * 9 * 8 * 7 * 6種
j = 2時,計算第二位小于N[2]的個數,9_ _ _ _ _ 共 7 * 8 * 7 * 6種,
j = 3時,計算第三位小于N[3]的個數,97_ _ _ 共7 * 7 * 6種,
j = 4時,計算第四位小于N[4]的個數,977_ _ _,此時出現重復位,則不用計算剩下的位數。
res = N - sum
注意:特判N < 10,return 0
代碼:
class Solution {
public:
int numDupDigitsAtMostN(int N) {
if (N < 10) return 0;
int a = N; // 拷貝N
int p = 0;
vector<int> digits;
while (N){
p++;
digits.push_back(N % 10);
N /= 10;
}
int sum = 0;
for (int i = 1; i < p; i++){
int cnt = 9;
for (int j = 1; j < i; j++)
cnt = cnt * (10 - j);
sum += cnt;
}
reverse(digits.begin(), digits.end());
int len = digits.size();
int hash[11];
memset(hash, 0, sizeof(hash));
for (int i = 0; i < len; i++){
int cnt = 0;
if (i == 0){
cnt = digits[i] - 1;
hash[digits[i]] = 1;
if (cnt <= 0) continue;
for (int j = 1; j <= len - 1; j++)
cnt *= (10 - j);
sum += cnt;
}else if(i == len - 1){
for (int j = 0; j <= digits[i]; j++){
if (!hash[j]) cnt++;
}
sum += cnt;
}else{
int cf = 0; // 定義當前位與前導位重復位的個數
for (int j = 0; j < digits[i]; j++){
if (hash[j]) cf++;
}
cnt = digits[i] - cf; // 當前位的可選個數 = 0 ~ (digits[i] - 1) - 重復位個數
for (int j = i + 1; j < len; j++){
cnt *= (10 - j); // 剩余位可選擇的個數
}
sum += cnt;
if (hash[digits[i]] == 1){ // 如果當前位與前導位存在重復位,那么后續位不存在滿足條件的數
break;
}
hash[digits[i]] = 1; // 記錄當前位已存在
}
}
return a - sum;
}
};