31.整數(shù)中1出現(xiàn)的次數(shù)(從1到n整數(shù)中1出現(xiàn)的次數(shù))
- 題目:求出113的整數(shù)中1出現(xiàn)的次數(shù),并算出1001300的整數(shù)中1出現(xiàn)的次數(shù)蝠嘉?為此他特別數(shù)了一下1~13中包含1的數(shù)字有1破停、10、11映砖、12、13因此共出現(xiàn)6次,但是對(duì)于后面問題他就沒轍了灾挨。ACMer希望你們幫幫他,并把問題更加普遍化,可以很快的求出任意非負(fù)整數(shù)區(qū)間中1出現(xiàn)的次數(shù)(從1 到 n 中1出現(xiàn)的次數(shù))邑退。
- 思路:以下來自牛客[Duqcuid]的分析劳澄,相當(dāng)詳細(xì)
像類似這樣的問題地技,我們可以通過歸納總結(jié)來獲取相關(guān)的東西。
首先可以先分類:
個(gè)位
我們知道在個(gè)位數(shù)上秒拔,1會(huì)每隔10出現(xiàn)一次莫矗,例如1、11溯警、21等等趣苏,我們發(fā)現(xiàn)以10為一個(gè)階梯的話,每一個(gè)完整的階梯里面都有一個(gè)1梯轻,例如數(shù)字22食磕,按照10為間隔來分三個(gè)階梯,在完整階梯0-9喳挑,10-19之中都有一個(gè)1彬伦,但是19之后有一個(gè)不完整的階梯滔悉,我們需要去判斷這個(gè)階梯中會(huì)不會(huì)出現(xiàn)1,易推斷知单绑,如果最后這個(gè)露出來的部分小于1回官,則不可能出現(xiàn)1(這個(gè)歸納換做其它數(shù)字也成立)。
我們可以歸納個(gè)位上1出現(xiàn)的個(gè)數(shù)為:
n/10 * 1+(n%10!=0 ? 1 : 0)
十位
現(xiàn)在說十位數(shù)搂橙,十位數(shù)上出現(xiàn)1的情況應(yīng)該是10-19歉提,依然沿用分析個(gè)位數(shù)時(shí)候的階梯理論,我們知道10-19這組數(shù)区转,每隔100出現(xiàn)一次苔巨,這次我們的階梯是100,例如數(shù)字317废离,分析有階梯0-99侄泽,100-199,200-299三段完整階梯蜻韭,每一段階梯里面都會(huì)出現(xiàn)10次1(從10-19)悼尾,最后分析露出來的那段不完整的階梯。我們考慮如果露出來的數(shù)大于19肖方,那么直接算10個(gè)1就行了闺魏,因?yàn)?0-19肯定會(huì)出現(xiàn);如果小于10窥妇,那么肯定不會(huì)出現(xiàn)十位數(shù)的1舷胜;如果在10-19之間的,我們計(jì)算結(jié)果應(yīng)該是k - 10 + 1活翩。例如我們分析300-317烹骨,17個(gè)數(shù)字,1出現(xiàn)的個(gè)數(shù)應(yīng)該是17-10+1=8個(gè)材泄。
那么現(xiàn)在可以歸納:十位上1出現(xiàn)的個(gè)數(shù)為:
- 設(shè)k = n % 100沮焕,即為不完整階梯段的數(shù)字
- 歸納式為:(n / 100) * 10 + (if(k > 19) 10 else if(k < 10) 0 else k - 10 + 1)
百位
現(xiàn)在說百位1,我們知道在百位拉宗,100-199都會(huì)出現(xiàn)百位1峦树,一共出現(xiàn)100次,階梯間隔為1000旦事,100-199這組數(shù)魁巩,每隔1000就會(huì)出現(xiàn)一次。這次假設(shè)我們的數(shù)為2139姐浮。跟上述思想一致谷遂,先算階梯數(shù) * 完整階梯中1在百位出現(xiàn)的個(gè)數(shù),即n/1000 * 100得到前兩個(gè)階梯中1的個(gè)數(shù)卖鲤,那么再算漏出來的部分139肾扰,沿用上述思想畴嘶,不完整階梯數(shù)k199,得到100個(gè)百位1集晚,100<=k<=199則得到k - 100 + 1個(gè)百位1窗悯。
那么繼續(xù)歸納百位上出現(xiàn)1的個(gè)數(shù):
- 設(shè)k = n % 1000
- 歸納式為:(n / 1000) * 100 + (if(k >199) 100 else if(k < 100) 0 else k - 100 + 1)
后面的依次類推....
再次回顧個(gè)位
我們把個(gè)位數(shù)上算1的個(gè)數(shù)的式子也納入歸納式中
- k = n % 10
- 個(gè)位數(shù)上1的個(gè)數(shù)為:n / 10 * 1 + (if(k > 1) 1 else if(k < 1) 0 else k - 1 + 1)
完美!歸納式看起來已經(jīng)很規(guī)整了偷拔。 來一個(gè)更抽象的歸納蒋院,設(shè)i為計(jì)算1所在的位數(shù),i=1表示計(jì)算個(gè)位數(shù)的1的個(gè)數(shù)条摸,10表示計(jì)算十位數(shù)的1的個(gè)數(shù)等等悦污。
- k = n % (i * 10)
- count(i) = (n / (i * 10)) * i + (if(k > i * 2 - 1) i else if(k < i) 0 else k - i + 1)
好了,這樣從10到10的n次方的歸納就完成了钉蒲。
- sum1 = sum(count(i)),i = Math.pow(10, j), 0<=j<=log10(n)
但是有一個(gè)地方值得我們注意的彻坛,就是代碼的簡(jiǎn)潔性來看顷啼,有多個(gè)ifelse不太好,能不能進(jìn)一步簡(jiǎn)化呢昌屉? 我們可以把后半段簡(jiǎn)化成這樣钙蒙,我們不去計(jì)算i * 2 - 1了,我們只需保證k - i + 1在[0, i]區(qū)間內(nèi)就行了间驮,最后后半段可以寫成這樣
min(max((n mod (i*10))?i+1,0),i)
下面是代碼
#define max(a,b) (((a) > (b)) ? (a) : (b)) //殴幔客直接調(diào)用max,min函數(shù)存在問題
#define min(a,b) (((a) < (b)) ? (a) : (b))
class Solution {
public:
int NumberOf1Between1AndN_Solution(int n)
{
if(n <= 0)
return 0;
int count = 0;
for(long i = 1; i <= n; i *= 10){
long diviver = i * 10;
count = count+(n / diviver) * i + min (max (n % diviver - i + 1, 0), i);
}
return count;
}
};
32.把數(shù)組排成最小的數(shù)
- 題目:輸入一個(gè)正整數(shù)數(shù)組竞帽,把數(shù)組里所有數(shù)字拼接起來排成一個(gè)數(shù)扛施,打印能拼接出的所有數(shù)字中最小的一個(gè)。例如輸入數(shù)組{3屹篓,32疙渣,321},則打印出這三個(gè)數(shù)字能排成的最小數(shù)字為321323堆巧。
- 思路:最簡(jiǎn)單的方法是全排列找最小值妄荔,但全排列的種數(shù)有n!個(gè),時(shí)間太長(zhǎng)谍肤。另外組合之后的數(shù)組可能超出int的數(shù)值范圍
std::sort(first,last,cmp); 使用的范圍是[first,last)
省略 cmp啦租,使用 sort(first,last), 則默認(rèn)從 小到大排序。
使用 sort(first,last, greater<T>() ), 則 從 大到小排序荒揣。
如果是結(jié)構(gòu)體或者自定義排序規(guī)則篷角,則需要自定義cmp 函數(shù)。
相等最好返回 false乳附。
cmp函數(shù)的含義内地,如果返回值是 True伴澄,表示 要把 序列 (X,Y),X放Y前阱缓。
class Solution {
public:
static bool cmp(int a,int b){
string A="";
string B="";
A+=to_string(a);
A+=to_string(b);
B+=to_string(b);
B+=to_string(a);
return A<B;
}
string PrintMinNumber(vector<int> numbers) {
string answer="";
sort(numbers.begin(),numbers.end(),cmp);
for(int i=0;i<numbers.size();i++){
answer+=to_string(numbers[i]);
}
return answer;
}
};
33.丑數(shù)
- 題目:把只包含質(zhì)因子2非凌、3和5的數(shù)稱作丑數(shù)(Ugly Number)。例如6荆针、8都是丑數(shù)敞嗡,但14不是,因?yàn)樗|(zhì)因子7航背。 習(xí)慣上我們把1當(dāng)做是第一個(gè)丑數(shù)喉悴。求按從小到大的順序的第N個(gè)丑數(shù)。
- 思路:動(dòng)態(tài)規(guī)劃玖媚,對(duì)于第i個(gè)數(shù)箕肃,它一定是之前已存在數(shù)的2倍,3倍或5倍
class Solution {
public:
int GetUglyNumber_Solution(int index) {
if (index < 7)return index;
vector<int> res(index);
res[0] = 1;
int t2 = 0, t3 = 0, t5 = 0, i;
for (i = 1; i < index; ++i)
{
res[i] = min(res[t2] * 2, min(res[t3] * 3, res[t5] * 5));
if (res[i] == res[t2] * 2)t2++;
if (res[i] == res[t3] * 3)t3++;
if (res[i] == res[t5] * 5)t5++;
}
return res[index - 1];
}
};
34.第一個(gè)只出現(xiàn)一次的字符
- 題目:在一個(gè)字符串(0<=字符串長(zhǎng)度<=10000今魔,全部由字母組成)中找到第一個(gè)只出現(xiàn)一次的字符,并返回它的位置, 如果沒有則返回 -1(需要區(qū)分大小寫)
- 思路:
Map是鍵-值對(duì)的集合勺像,map中的所有元素都是pair,可以使用鍵作為下標(biāo)來獲取一個(gè)值错森。Map中所有元素都會(huì)根據(jù)元素的值自動(dòng)被排序殃姓,同時(shí)擁有實(shí)值value和鍵值key瓦阐,pair的第一元素被視為鍵值蜗侈,第二元素被視為實(shí)值,同時(shí)map不允許兩個(gè)元素有相同的鍵值垄分。
在單詞的第一次出現(xiàn)時(shí)宛篇,會(huì)在word_count中創(chuàng)建并插入一個(gè)以該單詞為索引的新元素,同時(shí)將它的值初始化為0薄湿。然后其值立即加1叫倍,所以每次在map中添加新元素時(shí)吆倦,所統(tǒng)計(jì)的次數(shù)正好從1開始。需要注意的是坐求,使用map創(chuàng)建的哈希表已經(jīng)按鍵值進(jìn)行了排序蚕泽,所以序列的順序已經(jīng)不再是原始的輸入順序了仔蝌。
class Solution {
public:
int FirstNotRepeatingChar(string str) {
map<char, int> mp;
for(int i = 0; i < str.size(); ++i)
mp[str[i]]++;
for(int i = 0; i < str.size(); ++i){
if(mp[str[i]]==1)
return i;
}
return -1;
}
};
35.數(shù)組中的逆序?qū)???
- 題目:在數(shù)組中的兩個(gè)數(shù)字,如果前面一個(gè)數(shù)字大于后面的數(shù)字荒吏,則這兩個(gè)數(shù)字組成一個(gè)逆序?qū)α簿]斎胍粋€(gè)數(shù)組,求出這個(gè)數(shù)組中的逆序?qū)Φ目倲?shù)P。并將P對(duì)1000000007取模的結(jié)果輸出绰更。 即輸出P%1000000007
輸入描述:
題目保證輸入的數(shù)組中沒有的相同的數(shù)字
數(shù)據(jù)范圍:
對(duì)于%50的數(shù)據(jù),size<=10^4
對(duì)于%75的數(shù)據(jù),size<=10^5
對(duì)于%100的數(shù)據(jù),size<=2*10^5
示例1
輸入
1,2,3,4,5,6,7,0
輸出
7
-
思路:
我們以數(shù)組{7,5,6,4}為例來分析統(tǒng)計(jì)逆序?qū)Φ倪^程瞧挤。每次掃描到一個(gè)數(shù)字的時(shí)候,我們不拿ta和后面的每一個(gè)數(shù)字作比較儡湾,否則時(shí)間復(fù)雜度就是O(n^2)特恬,因此我們可以考慮先比較兩個(gè)相鄰的數(shù)字。
(a) 把長(zhǎng)度為4的數(shù)組分解成兩個(gè)長(zhǎng)度為2的子數(shù)組徐钠;
(b) 把長(zhǎng)度為2的數(shù)組分解成兩個(gè)成都為1的子數(shù)組癌刽;
(c) 把長(zhǎng)度為1的子數(shù)組 合并、排序并統(tǒng)計(jì)逆序?qū)?/strong> 丹皱;
(d) 把長(zhǎng)度為2的子數(shù)組合并妒穴、排序,并統(tǒng)計(jì)逆序?qū)Γ?/p>
在上圖(a)和(b)中摊崭,我們先把數(shù)組分解成兩個(gè)長(zhǎng)度為2的子數(shù)組,再把這兩個(gè)子數(shù)組分別拆成兩個(gè)長(zhǎng)度為1的子數(shù)組杰赛。接下來一邊合并相鄰的子數(shù)組呢簸,一邊統(tǒng)計(jì)逆序?qū)Φ臄?shù)目。在第一對(duì)長(zhǎng)度為1的子數(shù)組{7}乏屯、{5}中7大于5根时,因此(7,5)組成一個(gè)逆序?qū)ΑM瑯釉诘诙?duì)長(zhǎng)度為1的子數(shù)組{6}辰晕、{4}中也有逆序?qū)Γ?,4)蛤迎。由于我們已經(jīng)統(tǒng)計(jì)了這兩對(duì)子數(shù)組內(nèi)部的逆序?qū)Γ虼诵枰堰@兩對(duì)子數(shù)組 排序 如上圖(c)所示含友, 以免在以后的統(tǒng)計(jì)過程中再重復(fù)統(tǒng)計(jì)替裆。
接下來我們統(tǒng)計(jì)兩個(gè)長(zhǎng)度為2的子數(shù)組子數(shù)組之間的逆序?qū)Α:喜⒆訑?shù)組并統(tǒng)計(jì)逆序?qū)Φ倪^程如下圖如下圖所示窘问。
我們先用兩個(gè)指針分別指向兩個(gè)子數(shù)組的末尾辆童,并每次比較兩個(gè)指針指向的數(shù)字。如果第一個(gè)子數(shù)組中的數(shù)字大于第二個(gè)數(shù)組中的數(shù)字惠赫,則構(gòu)成逆序?qū)Π鸭⑶夷嫘驅(qū)Φ臄?shù)目等于第二個(gè)子數(shù)組中剩余數(shù)字的個(gè)數(shù),如下圖(a)和(c)所示儿咱。如果第一個(gè)數(shù)組的數(shù)字小于或等于第二個(gè)數(shù)組中的數(shù)字庭砍,則不構(gòu)成逆序?qū)Τ【В鐖Db所示。每一次比較的時(shí)候怠缸,我們都把較大的數(shù)字從后面往前復(fù)制到一個(gè)輔助數(shù)組中诗轻,確保 輔助數(shù)組(記為copy) 中的數(shù)字是遞增排序的。在把較大的數(shù)字復(fù)制到輔助數(shù)組之后凯旭,把對(duì)應(yīng)的指針向前移動(dòng)一位概耻,接下來進(jìn)行下一輪比較。
過程:先把數(shù)組分割成子數(shù)組罐呼,先統(tǒng)計(jì)出子數(shù)組內(nèi)部的逆序?qū)Φ臄?shù)目鞠柄,然后再統(tǒng)計(jì)出兩個(gè)相鄰子數(shù)組之間的逆序?qū)Φ臄?shù)目。在統(tǒng)計(jì)逆序?qū)Φ倪^程中嫉柴,還需要對(duì)數(shù)組進(jìn)行排序厌杜。如果對(duì)排序算法很熟悉,我們不難發(fā)現(xiàn)這個(gè)過程實(shí)際上就是歸并排序计螺。
class Solution {
public:
int InversePairs(vector<int> data) {
int length=data.size();
if(length<=0)
return 0;
//vector<int> copy=new vector<int>[length];
vector<int> copy;
for(int i=0;i<length;i++)
copy.push_back(data[i]);
long long count=InversePairsCore(data,copy,0,length-1);
//delete[]copy;
return count%1000000007;
}
long long InversePairsCore(vector<int> &data,vector<int> ©,int start,int end)
{
if(start==end)
{
copy[start]=data[start];
return 0;
}
int length=(end-start)/2;
long long left=InversePairsCore(copy,data,start,start+length);
long long right=InversePairsCore(copy,data,start+length+1,end);
int i=start+length;
int j=end;
int indexcopy=end;
long long count=0;
while(i>=start&&j>=start+length+1)
{
if(data[i]>data[j])
{
copy[indexcopy--]=data[i--];
count=count+j-start-length; //count=count+j-(start+length+1)+1;
}
else
{
copy[indexcopy--]=data[j--];
}
}
for(;i>=start;i--)
copy[indexcopy--]=data[i];
for(;j>=start+length+1;j--)
copy[indexcopy--]=data[j];
return left+right+count;
}
};
36.兩個(gè)鏈表的第一個(gè)公共結(jié)點(diǎn)
- 題目:輸入兩個(gè)鏈表夯尽,找出它們的第一個(gè)公共結(jié)點(diǎn)。
- 思路:兩個(gè)單鏈表如果存在第一個(gè)公共結(jié)點(diǎn)登馒,則后續(xù)結(jié)點(diǎn)一定都公共匙握,因?yàn)榻Y(jié)點(diǎn)里包含next指針,如果第一個(gè)公共結(jié)點(diǎn)相同陈轿,則next必然相同圈纺,所以第一個(gè)公共結(jié)點(diǎn)后鏈表合并。
找出2個(gè)鏈表的長(zhǎng)度麦射,然后讓長(zhǎng)的先走兩個(gè)鏈表的長(zhǎng)度差蛾娶,然后再一起走
(因?yàn)?個(gè)鏈表用公共的尾部)
class Solution {
public:
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
int len1 = findListLenth(pHead1);
int len2 = findListLenth(pHead2);
if(len1 > len2){
pHead1 = walkStep(pHead1,len1 - len2);
}else{
pHead2 = walkStep(pHead2,len2 - len1);
}
while(pHead1 != NULL){
if(pHead1 == pHead2) return pHead1;
pHead1 = pHead1->next;
pHead2 = pHead2->next;
}
return NULL;
}
int findListLenth(ListNode *pHead1){
if(pHead1 == NULL) return 0;
int sum = 1;
while(pHead1 = pHead1->next) sum++;
return sum;
}
ListNode* walkStep(ListNode *pHead1, int step){
while(step--){
pHead1 = pHead1->next;
}
return pHead1;
}
};
37.數(shù)字在排序數(shù)組中出現(xiàn)的次數(shù)
-
題目:統(tǒng)計(jì)一個(gè)數(shù)字在排序數(shù)組中出現(xiàn)的次數(shù)。
例如輸入排序數(shù)組{1,2,3,3,3,3,4,5}和數(shù)字3潜秋,由于3在數(shù)組中出現(xiàn)了4才蛔琅,因此輸出4。 - 思路:使用二分法找到排序數(shù)組中所求數(shù)字的第一個(gè)和最后一個(gè)位置峻呛,即可知道所求數(shù)字的個(gè)數(shù)罗售。
因?yàn)閐ata中都是整數(shù),所以可以稍微變一下杀饵,不是搜索k的兩個(gè)位置莽囤,而是搜索k-0.5和k+0.5
這兩個(gè)數(shù)應(yīng)該插入的位置,然后相減即可切距。
class Solution {
public:
int GetNumberOfK(vector<int> data ,int k) {
return biSearch(data, k+0.5) - biSearch(data, k-0.5) ;
}
private:
int biSearch(const vector<int> & data, double num){
int s = 0, e = data.size()-1;
while(s <= e){
int mid = (e - s)/2 + s;
if(data[mid] < num)
s = mid + 1;
else if(data[mid] > num)
e = mid - 1;
}
return s;
}
};
38.二叉樹的深度
- 題目:輸入一棵二叉樹朽缎,求該樹的深度。從根結(jié)點(diǎn)到葉結(jié)點(diǎn)依次經(jīng)過的結(jié)點(diǎn)(含根、葉結(jié)點(diǎn))形成樹的一條路徑话肖,最長(zhǎng)路徑的長(zhǎng)度為樹的深度北秽。
- 思路:遞歸判斷當(dāng)前是否為深度最大值(遞歸NB)
class Solution {
public:
int TreeDepth(TreeNode* pRoot)
{
if(!pRoot) return 0 ;
return max(1+TreeDepth(pRoot->left), 1+TreeDepth(pRoot->right));
}
};
39.平衡二叉樹
- 題目:輸入一棵二叉樹,判斷該二叉樹是否是平衡二叉樹最筒。
- 知識(shí)點(diǎn):平衡二叉樹(Balanced Binary Tree)具有以下性質(zhì):它是一棵空樹或它的左右兩個(gè)子樹的高度差的絕對(duì)值不超過1贺氓,并且左右兩個(gè)子樹都是一棵平衡二叉樹。
-
思路:同上題思路床蜘,依然可以使用遞歸判斷辙培,加上子樹深度差小于1這個(gè)條件即可。
這種做法有很明顯的問題邢锯,在判斷上層結(jié)點(diǎn)的時(shí)候扬蕊,會(huì)多次重復(fù)遍歷下層結(jié)點(diǎn),增加了不必要的開銷丹擎。如果改為從下往上遍歷尾抑,如果子樹是平衡二叉樹,則返回子樹的高度蒂培;如果發(fā)現(xiàn)子樹不是平衡二叉樹再愈,則直接停止遍歷,這樣至多只對(duì)每個(gè)結(jié)點(diǎn)訪問一次护戳。
class Solution {
public:
bool IsBalanced(TreeNode *root, int & dep){
if(root == NULL){
return true;
}
int left = 0;
int right = 0;
if(IsBalanced(root->left,left) && IsBalanced(root->right, right)){
int dif = left - right;
if(dif<-1 || dif >1)
return false;
dep = (left > right ? left : right) + 1;
return true;
}
return false;
}
bool IsBalanced_Solution(TreeNode* pRoot) {
int dep = 0;
return IsBalanced(pRoot, dep);
}
};
40.數(shù)組中只出現(xiàn)一次的數(shù)字
- 題目:一個(gè)整型數(shù)組里除了兩個(gè)數(shù)字之外翎冲,其他的數(shù)字都出現(xiàn)了兩次。請(qǐng)寫程序找出這兩個(gè)只出現(xiàn)一次的數(shù)字媳荒。
- 思路:
此題考察的是異或運(yùn)算的特點(diǎn):即兩個(gè)相同的數(shù)異或結(jié)果為0府适。
此題用了兩次異或運(yùn)算特點(diǎn):
(1)第一次使用異或運(yùn)算,得到了兩個(gè)只出現(xiàn)一次的數(shù)相異或的結(jié)果肺樟。
(2)因?yàn)閮蓚€(gè)只出現(xiàn)一次的數(shù)肯定不同,即他們的異或結(jié)果一定不為0逻淌,一定有一個(gè)位上有1么伯。另外一個(gè)此位上沒有1,我們可以根據(jù)此位上是否有1卡儒,將整個(gè)數(shù)組重新劃分成兩部分田柔,一部分此位上一定有1,另一部分此位上一定沒有1骨望,然后分別對(duì)每部分求異或硬爆,因?yàn)閯澐趾蟮膬刹糠钟羞@樣的特點(diǎn):其他數(shù)都出現(xiàn)兩次,只有一個(gè)數(shù)只出現(xiàn)一次擎鸠。因此缀磕,我們又可以運(yùn)用異或運(yùn)算,分別得到兩部分只出現(xiàn)一次的數(shù)。
異或:
class Solution {
public:
void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
int diff=accumulate(data.begin(),data.end(),0,bit_xor<int>());
diff&=-diff; //即找到最右邊1-bit
*num1=0;*num2=0;
for (auto c:data) {
if ((c&diff)==0) *num1^=c;
else *num2^=c;
}
}
};
哈希表:
class Solution {
public:
void FindNumsAppearOnce(vector<int> data, int* num1, int *num2) {
unordered_map<int, int> map;
for (int i = 0; i < data.size(); i++)
map[data[i]]++;
vector<int>v;
for (int i = 0; i < data.size(); i++)
if (map[data[i]]== 1)
v.push_back(data[i]);
*num1 = v[0]; *num2 = v[1];
}
};