/*

昨天参加苏宁易购的笔试

题目有点偏向JAVA,很多东西不知所措,比如接口。好囧。

最后一道大的编程题

//求出 1 2 2 3 4 5  的所有排列 要求 4不在第三个位置,3、5不相邻

实际写代码的时候,next_permutation自己手写的。。

next_permutation 每次产生下一个排列,每次返回一个true;

当产生玩所有的排列,比如abcd 产生到dcba之后,再调用next_permutation,会重新变回排列abcd,但是这次返回false

当时笔试的时候直接用循环计数来做的,排列总是为n!    5!/2!

*/

 

 

#include <algorithm>

#include <iostream>

#include <vector>

using namespace std;

 

 

void Init(vector<int>& num)

{

num.push_back(1);

num.push_back(2);

num.push_back(2);

num.push_back(3);

num.push_back(4);

num.push_back(5);

}

 

bool Judgment(vector<int>& num)

{

if(num[2] == 4)return false;

for(int i = 0; i < num.size()-1; i++)

if( (num[i] ==3 && num[i]==5) || (num[i] == 5 && num[i] == 3))

return false;

return true;

}

 

ostream& operator<<(ostream& stream, vector<int>& n)

{

for(int i = 0 ; i < n.size(); i++)

stream<<n[i]<<” “;

return stream;

}

 

int main()

{

vector<int> num;

Init(num);

cout<<num<<endl;

 

while(next_permutation(num.begin(), num.end()))

{

if(Judgment(num)) cout<<num<<endl;

}

 

return 0;

}

 

 

next_permumtation 在stl中的实现

/*

template<class _BidIt,

class _Pr> inline

bool _Next_permutation(_BidIt _First, _BidIt _Last, _Pr _Pred)

{        // permute and test for pure ascending, using _Pred

_BidIt _Next = _Last;

if (_First == _Last || _First == –_Next)

return (false);

 

for (; ; )

{        // find rightmost element smaller than successor

_BidIt _Next1 = _Next;

if (_DEBUG_LT_PRED(_Pred, *–_Next, *_Next1))

{        // swap with rightmost element that’s smaller, flip suffix

_BidIt _Mid = _Last;

for (; !_DEBUG_LT_PRED(_Pred, *_Next, *–_Mid); )

;

_STD iter_swap(_Next, _Mid);

_STD reverse(_Next1, _Last);

return (true);

}

 

if (_Next == _First)

{        // pure descending, flip all

_STD reverse(_First, _Last);

return (false);

}

}

}

*/

 

 

2.5 寻找最大的K个数

Posted: 27th 二月 2012 by Island in C/C++, THINKING
Tags:

在很多无序的元素中需要最大的K个数。

 

最简单最直接的方法就是先排序,快速排序或者堆排序都可,然后取出前K个数。

很明显时间复杂度有些高,O(nlgn)+O(k) = O(nlgn)。

 

其实题目只是要求最大的K个数,不需要前K个数有序,也不需要后N-K个数有序。

其实我们可以利用选择排序或者交换排序的思想,把N个数中的前K个数排序出来,时间复杂度为O(NK)。

 

很明显上面两种的好坏取决于K,当K<lgN时,可以选择部分排序。

 

 

 

 

排序的方法总归不是很好,我们看第二种方法。

我们可以利用快速排序的思想随机找一个数X,然后利用X将原无序数组分为两部分:

大于等于X的S1,小于X的S2。

针对这种情况,

当S1中元素的个数小于K,S1中所有的数和S2中最大的K-|S1|个元素就是数组S中最大的K个数。

S1中的元素个数大于等于K,则需要返回S1中最大的K个元素。

这样可以不断简化问题。

平均时间复杂度为O(nlgn)。

Kbig(S, k)

{

if(k <= 0)  return NULL;

if(length S <= k)   return S;

(S1, S2) = Partition(S);

return Kbig(S1, k).Append(Kbig(S2, k – S2.length));

}

 

Partition(S)

{

S1 = NULL;

S2 = NULL;

Swap(S[1], S[random%S.length]);

P = S[1];

for(i = 2 ; i < S.length; i++)

S[i] > p ? S1.Append(S[i]) : S2.Append(S[i]);

S1.length < S2.length ? S1.Append(p) : S2.Append(p);

return (S1,S2)

}

 

 

 

还有一种方法是寻找到数组中第K大的数p,对于给定的p,可以再O(N)的时间复杂度内找出所有不小于p的数。假如N个数中最大的数位Vmax,最小的数为Vmin,那么这N个数中的第K大数一定在区间[Vmin,Vmax]之间。

二分查找代码。

while( Vmax-Vmin > delta)

{

Vmid = Vmin + (Vmax – Vmin)*0.5;

if( f(arr, N, Vmid) >= K) Vmin = Vmid;

else Vmax = Vmid;

}

其中f(arr, N, Vmid) 返回数组arr[0,…,N-1]中大于等于Vmid的数的个数。

delta的取值要比所有N个数中的任意两个不相等的元素差值之最小值小。

当所有元素都是整数,delta可以取值0.5。

整个循环做完之后,得到一个区间(Vmin,Vmax),这个区间仅包含一个元素,或者多个值相等的元素。即第K大的元素。

算法时间复杂度为O(N*lg(|Vmax- Vmin|/delta)),在数据分布平均的情况下,整个算法的时间复杂度为O(NlgN)。

 

还有一个比较好的思想,就是在整数的情况下,假设所有整数的大小都在[0,2m-1]之间,

所有整数在二进制中都用m bit来表示(从低位到高位,分别用0,1,…,m-1)标记。

此刻我们可以从第m-1位开始,按0,1分成两部分。如果为1的那部分的个数大于等于K,那么在往下继续寻找最大的K个。否则就在为0的那部分找其余的最大的K – 为1的个数 个。

思路类似上面的浮点数的情况。

 

其实平时在做算法题目的时候,给的数据都非常大,不能全部载入内存。

可以每次都遍历一次文件,把新解所在的区间元素写入新的文件,下次不再须要遍历全部的文件。

最坏情况下,遍历文件的次数为2*lg(|Vmax – Vmin|/delta)。

在所有元素能全部载入内存之后,就可以不再通过读写文件的方式来操作了。

 

做过算法的都知道,寻找K个数中的第K大的数,是一个经典问题。

理论上,这个问题存在线性算法,不过这个线性算法的常数项比较大,在实际应用中效果有时并不好。

 

 

 

 

如上面所说,当数据非常大的时候,应该如何做呢。

 

有一个简单的思想,当在N个数中找最大的K个数。我们可以先取N的前K个数(当N<K,即直接范围这N个数,在这里不讨论),然后依次观察第k+1个数,第k+2个数,每次观察的时候,都比较当前这个数与前K大个数中最小的那个数Kmin,如果大于Kmin,则替换条Kmin,如果小于,则继续一次观察。寻找Kmin需要扫一遍这K个数,很明显所耗费的时间为O(N*K)。

 

其实我们可以将K个数存放在最小堆,每次考虑一个新的数X,如果X比堆顶的元素Y大,则用X来替换Y。更新过程花费的时间复杂度为O(lgK)。

 

if(X > h[0])

{

h[0] = X;

p = 0;

while(p < K)

{

q = 2*p + 1;

if(q >= K) break;

if((q < k – 1) && (h[q+1] < h[q])) q = q+1 ;

if(h[q] < h[p])

{

t = h[p];

h[p] = h[q];

h[q] = t;

p = q;

}

else

break;

}

}

 

整个算法的时候复杂度为O(N*lgK)。只需要扫描一次数据。

当K也过大,无法放入内存的时候,我们需要将K分块,每次寻找K’。

那么我们需要扫描所有数据ceil(K/K’)次。

 

 

 

 

第二种类似快排的方法平均时间复杂度是线性的。

其实我们可以利用桶的思想来做。

N个数都是整数,范围为(0,MAXN)。

那么我们只需要利用一个数组count[maxn]来记录每个整数出现的个数。

然后我们就可以寻找到第K大的数 A,其实前K大的数已经存在count[A]之后的数中了。

for(sumCount = 0 , v = MAXN – 1; v >= 0; v–)

{

sumCount += count[v];

if(sumCount >= K) break;

}

return v;

当N个整数各不相同,我们其实只需要一个bit来存储这个整数是否存在。

 

当N个数不符合都是正整数,且取之范围不太大这个条件时。

假设N个数中最大的数为Vmax,最小的数为Vmin,我们可以把这个区间[Vmin,Vmax]分成M块,每个小区间的跨度为d=(Vmax – Vmin) /M,然后我们扫描整个所有元素,统计各个小区间中的元素个数。我们我们找到第K大的数所在区间,继续分块处理。

时间复杂度为O((N+M)*lgM(|Vmax – Vmin|/delta))。

遍历文件的次数为2*lgM(|Vmax – Vmin|/delta)。

算法介于第三种解法与类计数排序方法之间。

我们需要找到一个尽可能大的M,M受限于内存。

 

 

扩展问题

 

  1. 寻找N个数中最大的K个不同的浮点数。

 

这里不再仅仅是最大的K个数,而且是不同的。

这个时候可以利用排序,或者桶的哈希思想。

注意判断两个浮点数大小的方法。

 

  1. 寻找第k到m大的数。

 

其实问题可以转换为m-k+1个第k大问题,可以利用堆或哈希来做。

 

  1. 在搜索引擎中,网络上的每个网页都有“权威性”权重,如page rank。如果我们须要寻找权重最大的K个网页,而网页的权重会不断地更新,那么算法要如何变动以达到最快更新(incremental update)并及时返回权重最大的K个网页?

 

提示: 堆排序?当每一个网页权重更新的时候,更新堆。还有更好的方法吗?

 

看到一个很好的思想,映射二分堆的方法。

映射二分堆与普通堆的确别:节点存指向数据单元的指针,避免交换父子节点时,拷贝大量数据所消耗的时间。删除单元可以根据索引删除。

用O(n)的方法对原数组建最大堆。然后取K次。

 

  1. 在实际应用中,还有一个“精确度”的问题。我们可能并不须要返回严格意义上的最大的K个元素,在边界位置允许出现一些误差。当用户输入一个query的时候,对于每一个文档d来说,它跟这个query之间都有一个相关性衡量权重f(query,d)。搜索引擎须要返回给用户的就是相关性权重最大的K个网页。如果每页10个网页,用户不会关心第1000页开外搜索结果的“精确度”,稍有误差是可以接受的。比如我们可以返回相关性第10 001大的网页,而不是第9999大的。在这种情况下,算法该如何改进才能更快更有效率呢?网页的数目可能大到一台机器无法容忍得下,这时怎么办呢?

 

提示:归并排序?如果每台机器都返回最相关的K个文档,那么所有机器上最相关K个文档的并集肯定包含全集中最相关的K个文档。如果边界情况并不需要非常精确,如果每台机器返回最好的K’个文档,那么K’应该如何取值,以达到我们返回最相关的90%*K个文档是完全精确的,或者最终返回的最相关的K个文档精确度超过90%(最相关的K个文档中90%以上在全集中相关性排在前K),或者最终返回的相关性的K个文档最差的相关性排序没有超出110%*K。

 

正如提示所说,可以让每台机器返回最相关的K’个文档,然后利用归并排序的思想,得到所有文档中最相关的K个。

最好情况:这K个文档在所有机器中平均分布,这时每台机器只要K’ = K/n(n为机器总数)。

最坏情况:最相关的K个文档只出现在其中一台机器上,这时K’需近似等于K了。

在每台机器上维护一个堆,堆顶实行归并排序。算法是完全精确的,但其实不是题目的答案,题目需要90%精确度的高效率做法。

 

怎么做呢。

 

 

 

  1. 如第4点所说,对于每个文档d,相对于不同的关键字q1,q2,…,qm,分别有相关性权重f(d, q1),f(d,q2),…,f(d,qm)。如果用户输入关键字qi之后,我们已经获得了最相关的K个文档,而已知关键字qj跟关键字qi相似,文档跟这两个关键字的权重大小比较靠近,那么关键字qi的相关性的K个文档,对寻找qj最相关的K个文档有没有帮助呢?

 

在搜索关键字qj相关的K个文档时,可以在qj的”近义词”相关文档中搜索部分,然后再全局的所有文档中在搜索部分。

 

 

看了几个比较好的博客,以后写博文简洁点,不啰里啰唆的了。

2.4 1的数目

Posted: 10th 二月 2012 by Island in C/C++, THINKING
Tags:

给定一个十进制正整数N,写下从1开始,到N的所有整数,然后数一下其中出现的所有“1”的个数。

例如:

N = 2,写下1,2。这样只出现了1个“1”。

N = 12,我们会写下1,2,3,4,5,6,7,8,9,10,11,12。这样,1的个数是5。

问题是:

  1. 写一个函数f(N),返回1到N之间出现的“1”的个数,比如f(12) = 5;
  2. 满足条件“f(N) = N”的最大的N是多少?

 

 

对于问题1,最简单最直接的方法就是从1开始遍历到N,将其中每一个数中含有“1”的个数加起来。

 

ULONGLONG Count1InAInteger(ULONGLONG n)

{

ULONGLONG iNum = 0;

while(n != 0)

{

iNum += (n % 10 == 1) ? 1 : 0;

n /= 10;

}

return iNum;

}

 

ULONGLONG f(ULONGLONG n)

{

ULONGLONG iCount = 0;

for(ULONGLONG i = 1; i <= n; i++)

{

iCount += Count1InAInteger(i);

}

return iCount;

}

思路很简单,但是算法的时间复杂度为

O(N) * 计算一个整数数字里面“1”的个数的复杂度 = O(N*lgN)。

这样的算法,应该是直接抛弃的…

 

换一个角度,可以分析“小于N的数在每一位上可能出现1的次数”之和来求解。

 

对于1位数的情况,

如果N = 3,那么从1到3的所有数字:1、2、3,只有个位数字上可能出现1,而且只出现1次,进一步可以发现如果N是个位数,且N>=1, 那么f(N)都等于1。

对于2位数的情况,

个位数只有1、11、21、31等这样的情况

显然,

如果N的个位数大于等于1,则个位出现1的次数为十位数的数字+1;

如果N的个位数为0,则个位数出现1的次数为十位数的数字。

十位数上出现1的情况只有10、11、12、13等这样的情况

显然,

如果十位数字等于1,则十位数上出现1的次数为个位数的数字加1;

如果十位数字大于1,则十位数上出现1的次数为10。

按照同样的规则进行推理3、4、5位数的情况。

 

对于一般情况下,从N得到f(N)的计算方法。

假设N = abcde,这里a、b、c、d、e分别是十进制数N的各个数位上的数字。

如果要计算百位上出现1的次数,它将会受到三个因素的影响:

百位上的数字,百位以下(低位)的数字,百位(更高位)以上的数字。

 

如果百位上的数字为0,那么百位上可能出现1的次数由更高位决定。

百位上可能出现1的情况 100~199,1100~1199,2100~2199,很明显,等于更高位的数字 * 当前的位数。

 

如果百位上的数字为1,显然,百位上可能出现1的次数不紧受更高位的影响,还受低位影响,高位影响同上,等于高位数字 * 当前位数;受低位影响,比如12113,等于低位数字+1 (13+1)。

 

如果百位上数字大于1,很明显,百位数字上没有提供1,不过却间接的又多提供了当前的位数个1。因此百位数出现1的情况等于 更高位数字加1,再乘以当前位数。

 

LONGLONG Sun1s(ULONGLONG n)

{

ULONGLONG iCount = 0;

ULONGLONG iFactor = 1;

ULONGLONG iLowerNum = 0;

ULONGLONG iCurrNum = 0;

ULONGLONG iHigherNum = 0;

while( n / iFactor != 0)

{

iLowerNum = n – ( n / iFactor) * iFactor;

iCurrNum = ( n / iFactor) % 10;

iHigherNum = n / (iFactor * 10);

switch(iCurrNum)

{

case 0: iCount += iHigherNum * Factor; break;

case 1: iCount += iHigherNum * Factor + iLowerNum + 1; break;

default: iCount += (iHigherNum + 1) * iFactor; break;

}

iFacotr *= 10;

}

return iCount;

}

 

时间复杂度为 O(strlen(N));N的长度

 

 

对于问题2,一个简单的分析

9以下为:               1个;

99以下为:            1*10 + 10 * 1 = 20个 //十位数出现1的个数+个位数出现1的个数

999以下为:           1*100 + 10 * 20 = 300个;

9999以下为:        1*1000 + 10 * 300 = 4000个;

归纳的 f(10n – 1) = n * 10 n-1。当n = 1010 – 1时,f(n)=1010的值大于n。

我们可以猜想,当n大于某一个数N时,f(n)会始终比n大。

也就是说,N是最大满足条件f(n) = n 的一个上界。

下面证明满足条件f(n) = N 的数存在一个上界。

 

当n增加10时,f(n)至少增加1;

当n增加100时,f(n)至少增加20;

当n增加1000时,f(n)至少增加300;

当n增加10 000时,f(n)至少增加4 000;

……

f(10) = 2;

f(100) = 1 + 1 * 10 + 10 * 1 = 21;

f(1000) = 1 + 1 * 100 + 10 * 10 + 100 * 1 = 301;

拿10来说,本身含两个1,一般情况下一个n+1多增加一位,除了0、10这样能增加2或10位的。。抛出1这个数字与特殊情况,当增加1-时,f(n)至少增加1;

 

类似,当n增加10k时,f(n)至少增加k*10k-1

当n按十进制展开,n = a*10k + b*10k-1 + …,

f(n) = f(0 + n) = f (0 + a*10k + b*10k-1 + …) > a*k*10k-1 + b*(k-1)*10k-2

又 n = a*10k + b*10k-1 + … < a*10k + (b+1)*10k-1

现在我们想证f(n)>n。

如果a*k*10k-1 + b*(k-1)*10k-2 >= a*10k + (b+1)*10k-1即得证。

即  10*a*k + b *k – b – 100 *a – 10*b – 10 >=0 ;

(10 * a + b )*k >= b + 10 + 100*a+10*b ;

k >= 10 + (b + 10)/(b + 10 *a);

k>11

即n的整数位大于等于12时,f(n) > n 恒成立。

因此我们可以取N = 10^11这个上界。

计算这个最大数n,令N = 10^11 – 1 = 99 999 999 999,让n从N往0递减,依次判断f(n)是否等于n,得出n = 1 111 111 110是满足f(n) = n的最大整数。

 

扩展问题

 

对于其他进制表达方式,也可以试一试,看有什么规律。例如二进制:

f(1) = 1

f(10) = 10 (因为 01, 10 有两个1)

f(11) = 100 (因为01,10,11有四个1)

其实思路还是一样的,还是分别考虑右侧第一位上出现1的次数、右侧第二位上出现1的次数…依次类推

 

先考虑最低位的1,显然,第一位上的1每两个数出现一次。    N/2 + N%2 + 1 – 2/2

再考虑低2位的1,每四个数出现两个1。                     N/4 *2+ N%4 + 1 – 4/2

依次类推,低三位,每八个数出现四个1,低四位,每十六个数出现八个1。   N/8 *4+ N%8 + 1 – 8/2

00 01 10 11 100 101 110 111 1000

 

公式已经出来了  低k位上1的各位为 N/2^k * 2^(k-1) + N%(2^k) + 1 – 2^k/2

其中+1是因为从0开始算起的,在取模的时候需要+1右移一下。

 

一个有意思的现象, 对于N = 111…110这样的数,即a个1加一个0组成的数,

f(N) = a个a(a+1)。 如f(1110) = 444, f(11111110) = 8888888。

2.3 寻找发帖“水王”

Posted: 7th 二月 2012 by Island in C/C++, THINKING
Tags:

Tango是微软亚洲研究院的一个试验项目。研究员的员工和实习生们都很喜欢在Tango上面交流灌水。传说,Tango有一大“水王”,他不但喜欢发帖,还会回复其他ID发的每个帖子。坊间风闻该“水王”发帖数目超过了帖子总数的一半。如果你有一个当前论坛上所有帖子(包括回帖)的列表,其中帖子作者的ID也在表中,你能快速找出这个传说中的Tango水王吗?

 

最直接的方法,对所有ID排序,那么在拍好序的ID列表中第N/2项(从0开始编号)一定会是这个ID。

 

但排序,毕竟还是很慢的,有什么高校的方法能避免排序么。

 

设想一下,如果每次删除两个不同的ID(不管是否包含“水王”的ID),那么,在剩下的ID列表中,“水王”ID出现的次数仍然超过总数的一半。可以通过不断重复这个过程,把ID列表中的ID总数降低(转化为更小的问题),从而得到答案。新的思路,避免了排序这个耗时的步骤,总的时间复杂度只有O(N),且只需要常数的额外内存。

 

Type Find(Type* ID, int N)

{

Type candidate;

int nTimes, i;

for(i = nTimes = 0; i < N; i++)

{

if(nTimes == 0)

{

candidate = ID[i], nTimes = 1;

}

else

{

if(candidate == ID[i])

nTimes++;

else

nTimes–;

}

}

return candidate

}

我的读码能力有所减弱啊…

首先就是因为每次删除两个不同的ID,因此要必然会扫描一遍整个列表。

设计一个变量nTimes,如果nTimes 为 0,表示扫描到当前位置,没有留下任何ID,如果不为0,则为当前ID的个数,然后通过不断的 ++ — 来操作维持这个变量。知道扫到最后,candidate便是所求的ID。

 

 

这个题目体现了计算机科学中很普遍的思想,就是如何把一个问题转化为规模较小的若干个问题。

分治、递推和贪心等都是基于这样的思路。

在转化过程中,小的问题跟原问题本质上一致。同样我们可以将小问题转化为更小的问题。

因此,转化过程是很重要的。

 

 

扩展问题

随着Tango的发展,管理员发现,“超级水王”没有了。统计结果表明,有3个发帖很多的ID,他们的发帖数目都超过了帖子总数目N的1/4 。你能从发帖ID列表中快速找出他们的ID吗?

 

思想大致相同,就是每次删除四个,剩下的三个肯定是发帖最多了。

伪代码,不想写成数组,返回值就没写。

 

Type Find(Type* ID, int N)

{

Type candidate;

int nTimes, i;

for(i = nTimes = 0; i < N; i++)

{

if(nTimes == 0)

{

candidate = ID[i], nTimes = 1;

}

else

{

if(candidate == ID[i])

nTimes++;

else

nTimes–;

}

}

return candidate

}

 

Type Find(Type* ID, int N)

{

Type candidate_1, candidate_2, candidate_3;

int nTimes_1 = 0, nTimes_2 = 0, nTimes_3 = 0, i;

for(i = 0; i < N ; ++)

{

if(nTimes_1 == 0)

{

candidate_1 = ID[i];

nTimes_1++;

}

else if(nTimes_2 == 0)

{

candidate_2 = ID[i];

nTimes_2++;

 

}

else if(nTimes_3 == 0)

{

candidate_3 = ID[i];

nTimes_3++;

}

else if(candidate_1 == ID[i])

nTimes_1++;

else if(candidate_2 == ID[i])

nTimes_2++;

else if(candidate_3 == ID[i])

nTimes_3++;

else

{

nTimes_1–;

nTimes_1–;

nTimes_1–;

}

}

return ;

}

2.2 不要被阶乘吓倒

Posted: 6th 二月 2012 by Island in C/C++, THINKING
Tags: ,

说实话,阶乘的题我都做了不晓得多少了…

可惜都是一两年前的事了,竟然忘得也差不多了。

我们来看看两个与阶乘相关的问题。

  1. 给定一个整数N,那么N的阶乘N!末尾有多少个0呢? 例如:N =10, N! = 3 628 800, N!的末尾有两个0。
  2. 求N!的二进制表示中最低位1的位置。

 

对于问题1

最直接的方法,N!中的0肯定是由因子中的2*5得到,而2一定比5多,因此就是求N!中5的个数。

 

ret = 0;

for(i = 1; i <= N; i++)

{

j = i;

while(j % 5 ==0)

{

ret++;

j /= 5;

}

}

 

还有一个更加高效的算法

公式: 5的个数 = [N/5] + [N/5^2] + [N/5^3] + ……(不用担心这会是一个无穷的运算,因为总存在一个K,使得5^k > N, [N/5^K] = 0)

 

公式中,[N/5]表示不大于N的数中5的倍数贡献一个5,[N/5^2]表示不大于N的数中5^2的倍数再贡献一个5……

ret = 0;

while(N)

{

ret += N / 5;

N /= 5;

}

 

 

对于问题2,可以想象一下,对于一个数N的二进制,那么二进制中低位的所有0均是由2的累加得到的,因此在求解N!的二进制最低位的1的位置,即等于N!含有质因数2的个数加1。

 

int lowestOne(int N)

{

int Ret = 0;

while(N)

{

N >>= 1;

Ret += N;

}

return Ret;

}

 

还有一个方法,有那么一条巧妙地规律

即 N!含有质因数2的个数,还等于N减去N的二进制表示中1的数目。

 

通过一个例子来说明一下这个规律。

假设N = 11011,那么N!中含有质因数2的个数为 [N/2] + [N/4] + [N/8] + [N/16] + …

即:

1101 + 110 + 11 + 1

= (1000 + 100 + 1)

+  (100 + 10)

+  ( 10 + 1)

+  1

=(1000 + 100 + 10 + 1) + (100 + 10 + 1) +1

= 1111 + 111 + 1

=(10000 -1) + (1000 -1) + (10 -1) + (1-1)

=11011 – (N二进制表示中1的个数)

To me. What a amazing!

 

另外有一个相关题目

给定整数n,判断它是否为2的方幂

2的方幂,即n的二进制是否为高位一个1低位全是0组成。

(n>0 && ( ( n& (n-1) ) ) ==0)

 

 

小结,N!末尾0的各位与二进制最低位1的位置。以及一个经典的规律。

位运算的一些基础操作

Posted: 6th 二月 2012 by Island in C/C++, THINKING
Tags:

运算方法有六种:

& 与运算
| 或运算
^ 异或运算
~ 非运算(求补)
>> 右移运算
<< 左移运算

运用这些基本的运算,我们可以解决acm所需的各种运算,给Bit赋1,赋0,给他的值取反,还有好多段操作。如下:

功能 | 示例 | 位运算
———————-+—————————+——————–
去掉最后一位 | (101101->10110) | x >> 1
在最后加一个0 | (101101->1011010) | x < < 1
在最后加一个1 | (101101->1011011) | x < < 1+1
把最后一位变成1 | (101100->101101) | x | 1
把最后一位变成0 | (101101->101100) | x | 1-1
最后一位取反 | (101101->101100) | x ^ 1
把右数第k位变成1 | (101001->101101,k=3) | x | (1 < < (k-1))
把右数第k位变成0 | (101101->101001,k=3) | x & ~ (1 < < (k-1))
右数第k位取反 | (101001->101101,k=3) | x ^ (1 < < (k-1))
取末三位 | (1101101->101) | x & 7
取末k位 | (1101101->1101,k=5) | x & ((1 < < k)-1)
取右数第k位 | (1101101->1,k=4) | x >> (k-1) & 1
把末k位变成1 | (101001->101111,k=4) | x | (1 < < k-1)
末k位取反 | (101001->100110,k=4) | x ^ (1 < < k-1)
把右边连续的1变成0 | (100101111->100100000) | x & (x+1)
把右起第一个0变成1 | (100101111->100111111) | x | (x+1)
把右边连续的0变成1 | (11011000->11011111) | x | (x-1)
取右边连续的1 | (100101111->1111) | (x ^ (x+1)) >> 1
去掉右起第一个1的左边 | (100101000->1000) | x & (x ^ (x-1))
判断奇数 (x&1)==1
判断偶数 (x&1)==0
取右边第一个1所在位置 x&-x

2.1 求二进制数中1的个数

Posted: 6th 二月 2012 by Island in C/C++, THINKING
Tags: , ,

对于一个字节(8 bit)的无符号整形变量,求其二进制表示中“1”的个数,要求算法的执行效率尽可能高。

 

比较简单的方法 例如 简单的位移就不罗列了。

 

一个比较高效的方法是只考虑1的情况。

时间复杂度只与所输入的v中1的个数有关。

每次 v&(v-1) 可以将v中处于低位的1给置0.

这样只需要进行 v 中1的个数次操作即可求得二进制数中1的个数。

 

Int Count(BYTE v)

{

Int num = 0;

while(v)

{

v &= (v-1);

num++;

}

return num;

}

 

Read the rest of this entry »

零碎的揪起七月的尾巴

Posted: 31st 七月 2011 by Island in 与青春有关的日子
Tags:

在家中,就是各种低迷各种颓废各种乱。每天中午睡到4点多,更甚睡到7点的情况。让我情何以堪。当然也不乏去学了一下吉他,由于种种原因,学了一天之后决定暂停学习,等到学校再学。

先把学会的记下来…..

学的话当然是民谣吉他,一个月出山的那种。
首先是买吉他,星辰的DG120C和DG220C, Gibson Kramer K420C也很不错,Johnson 6100貌似也很火, Johnson这个牌子不熟悉,另外红棉的吉他也很不错。吉他分品位,买21品的就行了。

曾经我还以为有什么拿姿势可谈,后来老师说只要你先坐着舒服就行了。吉他分为是6线谱,在谈吉他的时候,从上到下依次为654321。吉他也分品,从吉他的顶部到尾部从1品开始。第一天学习了兰花草这个曲目,其实从什么都不会,到谈兰花草这个曲子,只需要半天的功夫,能对入门增加很大的信心。

在网上搜的吉他谱都是只有AM和EM和铉,正常的至少缺少G和铉,可能是因为初学者简便。总之等回学校在弄吧。那还有一帮一起的伙伴~

在回家的火车上看了《少有人走的路》,感觉很不错,至少我一度迷失,一度暴躁,各种事情我也找到了一缕思路,加之后来多日的转转反侧,对于我的心态,行为也找到了较为合理的答案。总之,我知道我需要作什么,需要增么来调整了。

开始背英语单词了,不过今天的计划又没有实现,哎。数学也不想拉下,当然偶尔也会看看数论,接下来的日子里,如要写写有关考研和数论的东西吧,这次不是想做什么ACM,纯粹是为了学点什么。

期待回学校,其实我特想明天就去买票回学校,无奈因为私人原因,不想回学校。在挺挺吧。其实这段时间纠结过去上海或者深圳,后来想了想还是留下来陪陪父母吧,毕竟下次回家又不知道什么时候了,其实我不承认我是一个恋家的孩子,可能随时随地都不会回家。

总之,一切安好吧,在家还能学学钢琴,还有三国杀,不杀了,再杀就累眼了。回家各种乱,都没有写过博客,快点开学吧。我也要赶快回去。

我的未来还很空洞,需要我去为之付出,而不是坐以待毙。

刚才和几个好友一顿乱侃,有良友无不快哉,最好再能有壶美酒。

Acm所有算法

Posted: 16th 七月 2011 by Island in acm _我 的 选 择 . 走 下 去
Tags: ,

ACM 所有算法

数据结构
  • 栈,队列,链表
  • 哈希表,哈希数组
  • 堆,优先队列
    双端队列
    可并堆
    左偏堆
  • 二叉查找树
    Treap
    伸展树
  • 并查集
    集合计数问题
    二分图的识别
  • 平衡二叉树
  • 二叉排序树
  • 线段树
    一维线段树
    二维线段树
  • 树状数组
    一维树状数组
    N维树状数组
  • 字典树
  • 后缀数组,后缀树
  • 块状链表
  • 哈夫曼树
  • 桶,跳跃表
  • Trie树(静态建树、动态建树)
  • AC自动机
  • LCA和RMQ问题
  • KMP算法
图论
  • 基本图算法图
    广度优先遍历
    深度优先遍历
    拓扑排序
    割边割点
    强连通分量
    Tarjan算法
    双连通分量
    强连通分支及其缩点
    图的割边和割点
    最小割模型、网络流规约
    2-SAT问题
    欧拉回路
    哈密顿回路
  • 最小生成树
    Prim算法
    Kruskal算法(稀疏图)
    Sollin算法
    次小生成树
    第k小生成树
    最优比例生成树
    最小树形图
    最小度限制生成树
    平面点的欧几里德最小生成树
    平面点的曼哈顿最小生成树
    最小平衡生成树
  • 最短路径
    有向无环图的最短路径->拓扑排序
    非负权值加权图的最短路径->Dijkstra算法(可使用二叉堆优化)
    含负权值加权图的最短路径->Bellmanford算法
    含负权值加权图的最短路径->Spfa算法
    (稠密带负权图中SPFA的效率并不如Bellman-Ford高)
    全源最短路弗洛伊德算法Floyd
    全源最短路Johnson算法
    次短路径
    第k短路径
    差分约束系统
    平面点对的最短路径(优化)
    双标准限制最短路径
  • 最大流
    增广路->Ford-Fulkerson算法
    预推流
    Dinic算法
    有上下界限制的最大流
    节点有限制的网络流
    无向图最小割->Stoer-Wagner算法
    有向图和无向图的边不交路径
    Ford-Fulkerson迭加算法
    含负费用的最小费用最大流
  • 匹配
    Hungary算法
    最小点覆盖
    最小路径覆盖
    最大独立集问题
    二分图最优完备匹配Kuhn-Munkras算法
    不带权二分匹配:匈牙利算法
    带权二分匹配:KM算法
    一般图的最大基数匹配
    一般图的赋权匹配问题
  • 拓扑排序
  • 弦图
  • 稳定婚姻问题
搜索
  • 广搜的状态优化
    利用M进制数存储状态
    转化为串用hash表判重
    按位压缩存储状态
    双向广搜
    A*算法
  • 深搜的优化
    位运算
    剪枝
    函数参数尽可能少
    层数不易过大
    双向搜索或者是轮换搜索
    IDA*算法
  • 记忆化搜索
动态规划
  • 四边形不等式理论
  • 不完全状态记录
    青蛙过河问题
    利用区间dp
  • 背包类问题
    0-1背包,经典问题
    无限背包,经典问题
    判定性背包问题
    带附属关系的背包问题
    + -1背包问题
    双背包求最优值
    构造三角形问题
    带上下界限制的背包问题(012背包)
  • 线性的动态规划问题
    积木游戏问题
    决斗(判定性问题)
    圆的最大多边形问题
    统计单词个数问题
    棋盘分割
    日程安排问题
    最小逼近问题(求出两数之比最接近某数/两数之和等于某数等等)
    方块消除游戏(某区间可以连续消去求最大效益)
    资源分配问题
    数字三角形问题
    漂亮的打印
    邮局问题与构造答案
    最高积木问题
    两段连续和最大
    2次幂和问题
    N个数的最大M段子段和
    交叉最大数问题
  • 判定性问题的dp(如判定整除、判定可达性等)
    模K问题的dp
    特殊的模K问题,求最大(最小)模K的数
    变换数问题
  • 单调性优化的动态规划
    1-SUM问题
    2-SUM问题
    序列划分问题(单调队列优化)
  • 剖分问题(多边形剖分/石子合并/圆的剖分/乘积最大)
    凸多边形的三角剖分问题
    乘积最大问题
    多边形游戏(多边形边上是操作符,顶点有权值)
    石子合并(N^3/N^2/NLogN各种优化)
  • 贪心的动态规划
    最优装载问题
    部分背包问题
    乘船问题
    贪心策略
    双机调度问题Johnson算法
  • 状态dp
    牛仔射击问题(博弈类)
    哈密顿路径的状态dp
    两支点天平平衡问题
    一个有向图的最接近二部图
  • 树型dp
    完美服务器问题(每个节点有3种状态)
    小胖守皇宫问题
    网络收费问题
    树中漫游问题
    树上的博弈
    树的最大独立集问题
    树的最大平衡值问题
    构造树的最小环

数学

数论
  • 中国剩余定理
  • 欧拉函数
  • 欧几里得定理
  • 欧几里德辗转相除法求GCD(最大公约数)
  • 扩展欧几里得
  • 大数分解与素数判定
  • 佩尔方程
  • 同余定理(大数求余)
  • 素数测试
    一千万以内:筛选法
    一千万以外:米勒测试法
  • 连分数逼近
  • 因式分解
  • 循环群生成元
  • 素数与整除问题
  • 进制位.
  • 同余模运算
组合数学
  • 排列组合
  • 容斥原理
  • 递推关系和生成函数
  • Polya计数法
    Polya计数公式
    Burnside定理
  • N皇后构造解
  • 幻方的构造
  • 满足一定条件的hamilton圈的构造
  • Catalan数
  • Stirling数
  • 斐波拉契数
  • 调和数
  • 连分数
  • MoBius反演
  • 偏序关系理论
  • 加法原理和乘法原理
计算几何
  • 基本公式
    叉乘
    点乘
    常见形状的面积、周长、体积公式
    坐标离散化
  • 线段
    判断两线段(一直线、一线段)是否相交
    求两线段的交点
  • 多边形
    判定凸多边形,顶点按顺时针或逆时针给出,(不)允许相邻边共线
    判点在凸多边形内或多边形边上,顶点按顺时针或逆时针给出
    判点在凸多边形内,顶点按顺时针或逆时针给出,在多边形边上返回0
    判点在任意多边形内,顶点按顺时针或逆时针给出
    判线段在任意多边形内,顶点按顺时针或逆时针给出,与边界相交返回1
    多边形重心
    多边形切割(半平面交)
    扫描线算法
    多边形的内核
  • 三角形
    内心
    外心
    重心
    垂心
    费马点

  • 判直线和圆相交,包括相切
    判线段和圆相交,包括端点和相切
    判圆和圆相交,包括相切
    计算圆上到点p最近点,如p与圆心重合,返回p本身
    计算直线与圆的交点,保证直线与圆有交点
    计算线段与圆的交点可用这个函数后判点是否在线段上
    计算圆与圆的交点,保证圆与圆有交点,圆心不重合
    计算两圆的内外公切线
    计算线段到圆的切点
    点集最小圆覆盖
  • 可视图的建立
  • 对踵点
  • 经典问题
    平面凸包
    三维凸包
    Delaunay剖分/Voronoi图
计算方法
  • 二分法
    二分法求解单调函数相关知识
    用矩阵加速的计算
  • 迭代法
  • 三分法
  • 解线性方程组
    LUP分解
    高斯消元
  • 解模线性方程组
  • 定积分计算
  • 多项式求根
  • 周期性方程
  • 线性规划
  • 快速傅立叶变换
  • 随机算法
  • 0/1分数规划
  • 三分法求解单峰(单谷)的极值
  • 迭代逼近
  • 矩阵法
博弈论
  • 极大极小过程
  • Nim问题

文章中的算法可能有些冗余以及不全之处还望指正,多多修改。
为的是在学习算法的时候能有一个好的指引。