@dantangfan
2015-01-21T17:11:43.000000Z
字数 29249
阅读 2156
Node *find(Node *n,int k){
if(n==NULL) return NULL
Node *head = n;
int len=0;
while(head!=NULL){
len += 1;
head = head->next;
}
if(k>=len) return NULL;
head = n;
Node *temp = n;
for(int i=0;i<k;i++){
head = head->next;
}
while(head!=NULL){
head = head->next;
temp = temp->next;
}
return temp;
}
检测链表是否出现环路
使用两个指针,一个一次跳一步,一个一次跳两步,总会相遇
bool loop(Node *n){
Node temp1 = n;
Node temp2 = n;
if(n==NULL) return false;
while(temp->next!=NULL){
temp1 = temp1->next->next;
temp2 = temp2->next;
if(temp1==temp2) return true;
}
rerurn false;
}
环路的开头节点
两个节点slow和fast,每当slow走k个节点fast就走2k个节点。假设slow从开始到环的初始节点走了k步,这个时候fast就走了2k步,其中有k步在环内(有可能k比loopsize大得多,不过无所谓,mod(k,loopsize)效果也一样)。如果说我们认为这个时候fast在slow后面,那么他们相距loopsize-k个节点,那么需要t=loopsize-k那么长的时间在还有k步到达环开始的地方相遇。于是,我们可以确定,slow再次从strat出发,fast从相遇节点出发,一定能在环开始的地方相遇。
LinkedListNode FindBeginning(LinkedListNode head){
LinkedListNode slow = head, fast = head;
while(fast!=NULL && fast.nest!=NULL){
slow = slow.next;
fast = fast.next.next;
if(slow==fast) break;
}
if(fast==NULL||fast.next==NULL) return NULL;
slow = head;
while(slow!=fast){
slow = slow.next;
fast = fast.next;
}
return fast;
}
思考题:
检测两个无环链表是否相交
先遍历第一个链表,再把第二个表头链接到第一个表尾,标记该位置,然后第二个表从头开始遍历,如果出现环则相交;分别遍历两个链表,最后一个元素相同。
相交链表的第一个节点
分别遍历两个表,记录长度,然后先后相距k遍历,第一个相同的就是。
检测两个有环链表是否相交
先找出一个链表的环内的一个节点,在看这个节点是否在另一个链表内。
检测两个有环链表的相交节点
先检测出一个链表环的起始并记录长度,再看另一个链表到环起点的长度,最后转换成无环相交情况。
Bool LEFT = false;
bool RIGHT = false;
Node *target = NULL;
void findCommonRoot(Node *root,int a, int b, Node *target){
if(root==NULL) return;
if(root.value==a) LEFT = true;
if(root.value==b) RIGHT = true;
findCommonRoot(root.left,a,b,target);
findCommonRoot(root.right,a,b,target);
if(LEFT&&RIGHT){
LEFT = false;
RIGHT = false;
target = root;
}
}
其实就是找一个节点,他的子节点包含了两个要找的节点
Node *pRes = NULL;
int find(Node *root, Node *a, Node *b,){
if(NULL!=pRes) return 0;
int sum = 0;
if(NULL==root)return 0;
if(root==a) sum+= 1;
if(root==b) sum+=1;
sum += find(root->left,a,b);
sum += find(root->right,a,b);
if(sum==2&&NULL=pRes){
pRes = root;
return 0;
}
return sum;
}
还可以遍历两次树,分别找到两个节点的路径,然后转换成链表求节点问题。
Bool heapler(int *arr, int start, int end){
if(end-start<=1) return true;
int temp = arr[start];
for(int i=0;i<end;i++){
if(arr[i]>arr[start])
break;
}
return helper(arr,start+1,i)&&helper(arr,i,end);
}
Bool binary(int *arr, int len){
if(n==NULL) return true;
return helper(arr,0,len);
}
Bool containsTree(TreeNode t1,TreeNode T2){
if(t2==NULL) return true;
return subTree(t1,t2);
}
bool subTree(TreeNode r1,TreeNode r2){
if(r1==NULL) return false;
if(r1.data==r2.data)
if(matchTree(r1,r2))
return true;
return (subTree(r1.left),r2)||subTree(r1.right,r2);
}
bool matchTree(TreeNode r1,TreeNode r2){
if(r2==NULL&&r1==NULL) return true;
if(r1==NULL||r2==NULL) return false;
if(r1.data!=r2.data) return false;
return matchTree(r1.left,r2.left)&&matchTree(r2.right,r2.right);
}
void findSum(TreeNode node, int sum, int[] path, int level){
if(node == NULL) return;
path[level] = node.data;
int t = 0;
for(int i=level;i>=0;i--){
t += path[i];
if(t==sum)
print(path,i,level);
}
findSum(node.left,sum,path,level+1);
findSum(node.right,sum,path,level+1);
path[level] = NULL
}
Int count = 0;
while(n){
n = n&(n-1);
count++;
}
int Ugly(int length){
int *ugly = (int *)malloc(sizeof(int)*length);
ugly[0]=1;
int index = 1;
int p2=0,p3=0,p5=0;
while(index<length){
ugly[index] = min(ugly[p2]*2,ugly[p3]*3,ugly[p5]*5);
while(ugly[p2]*2<ugly[index]) p2++;
while(ugly[p3]*3<ugly[index])p3++;
while(ugly[p5]*5<ugly[index])p5++;
}
return ugly[index-1];
}
走楼梯问题,多用循环,少用递归,Fab数列
打印一个集合所有的子集。
a.如果是空集,就只有一个子集{}
b.如果有一个元素a,就有{},{a1}
c.如果有两个元素a,就有{},{a1},{a2},{a1,a2};
可以看出,如果有n个元素,子集f(n) = f(n-1)+f(n-1)·an;一共有2^n个子集。
代码可以用递归,也可以用循环
vector<string> ans;
ans.push_back(“”);
string str;
for(int i=0;i<str.length();i++){
int size = ans.size();
for(int j=0;j<size;j++){
ans.push_back(ans[size]+str[i]);
}
}
return ans;
打印全排列(递归+回溯)
void print(string str, int length, int index){
if(length==index) cout<<str<<”\t”;
else for(int i=index;i<length;i++){
swap(str[index],str[i]);
print(str,length,index+1);
swap(str[index],str[i]);
}
}//没有重复字符串
如果有重复字符串就要判断不能把相同的字符交换。如下操作
bool IsSwap(string str, int begin, int target){
for(;begin<target;begin++)
if(str[begin]==str[target])
return false;
return true;
}
void print(string str, int length, int index){
if(length==index) cout<<str<<endl;
else for(int i=index;i<length;i++)
if(IsSwap(string,index,i)){
swap(str[index],str[i])
…
…
}
}
下一个字典序
a 从右边开始找出第一个比最后一个数字小的数字i
b 从i的右边找出最小的数字j
c 交换i,j
d把j右边的数字反转
void findParems(int left, int right, int n, vector<char> v){
if(left>n||left<right) return;
if(left==n&&right==n){
for(int i=0;i<v.size();i++)
cout<<v[i];
cout<<endl;
}
else{
if(left<n){
v.push_back('(');
findParems(left+1,right,n,v);
v.pop_back();
}
if(left>right){
v.push_back(')');
findParems(left,right+1,n,v);
v.pop_back();
}
}
}
如何从40亿个正整数中找出一个不在这个文件中的数字
a.使用1G内存,也就是1*1000*1000*1000*8=80亿个比特位。那么可以使用bitset来记录扫描 一遍把存在的整数置1,然后不存在的位0
b.如果只用10M内存,也就是10*1000*1000*8=800万个位,把40亿分开成800分每份500万个数字,我们每次判断0-500万-100万-1500万的数字,分800次检测完
给定100亿个网址,找出重复网址。如果长度平均100字符,每个字符占1字节,就有1T
a.储存在磁盘。第一次扫描将地址类表拆分到100个文件中,每个1G,命名为.txt。其中
n=hast(url)%100。第二次扫描的时候就可以直接将这些单个文件载入内存,创建散列表,找出重复
给定两个排序好的数组AB,A的末端有足够的空间存放B,请合并两个数组。
如果从前面开始比较,那么每次插入都可能移动很多数据。所以我们要从两个数组的最后开始比较,然后插入到A数组的末端
int* join(int a[],int lena, int size, int b[], int lenb){
if(size-lena<lenb) return false;
int I=lena-1,j=lenb-1,index=size-1;
while(i>=0&&j>=0){
if(a[lena]>b[lenb]){
a[index] = a[i];
i--;
}
else{
a[index] = b[j];
j--;
}
index--;
}
if(i>=0)
while(i>=0)
a[index--] = a[i--];
if(j>=0)
while(j>=0)
a[index--] = b[j--];
return a+index+1;
}
Int find(int arr[], int len, int n){
int low=0,high=len-1,mid;
while(low<=high){
mid = low +(high-low)/2;
if(arr[mid]==n) return mid;
if(arr[mid]>=arr[high]){//左边有序
if(n>arr[mid]&&n<=arr[]) high = mid-1;
else low = mid+1;
}
else{
if(n>arr[low]&&n<=arr[high]) low = mid+1;
else high = mid-1;
}
}
return -1;
}
有个20G的大文件,每行一个字符串,请排序。
把文件分成x份,每份20*1000/x,能放入内存,然后对每份单独排序。最后逐一将这些文件整合在一起。
给定一个矩阵,每行列都按照升序排列,请查找某个数。
从右上角开始,大于向下,小于向左。
Bool find(int arr[][],int len,int high, int target){
int i=len-1;
int j=0;
while(i>=0&&j<high){
if(arr[j][i]==target) return true;
if(arr[j][i]>target) I--;
else j++;
}
return false;
}
法一:
a = a-b;
b = a+b;
a = b-a;
法二:
a = a^b;
b = a^b;
a = a^b;
int contZero(int num){
int count = 0;
if(num<0) return -1;
for(i = 5; num/i>0;i *= 5)
count+=num/i;
return count;
}
N!二进制中第一个1出现的最低位置
如3!=6=1010;最低位在第二位上。一个二进制数字除以二就相当于如果最后一位是0就直接右移一位,如果是1就不能除。所以,这个问题就相当于说N!能够右移次数,也就是质因子2的个数。
int lowestOne(int n){
int ret = 0;
for(i=2;n/i>0;i *=2)
ret += n/i;
return ret;
}
int maxSum(int a[],int n){
int max = a[0];
int sum = 0;
for(int i=0;i<n;i++){
if(sum>0) sum+= a[i];
else sum = a[i];
if(sum>max) max=sum;
}
return max;
}
求连续的最大积
当已经知道了前k个数字的最大,最小值,那么第k+1个数字的最大值只能是max*a[k],min*a[k],a[k]中的最大者。```c
double func(doubel *a,int n){
double *maxA = new double[n];
double *minA = new double[n];
maxA[0] = minA[0] = a[0];
double value = a[0];
for(int i=1;i
maxA[i] = max(max(a[i],maxA[i-1]*a[i]),minA[i-1]*a[i]);
minA[i] = min(min(a[i],maxA[i-1]*a[i]),minA[i-1]*a[i]);
value = max(value,maxA[i]);
}
reutrn value;
}
20. 寻找数组中出现次数超过一半的元素
可以用quick select找到中间个元素因为出现次数超过了一半,那么我们每次遇到两个不同的数字就丢掉,最后剩下的肯定只有它
```c
int find(int array[], int length){
int temp;
int times=0,i;
for(i=0;i<length;i++){
if(times==0){
temp = array[i];
times += 1;
}
else{
if(temp==array[i])
times+=1;
else
times-=1;
}
}
return temp;
}
<div class="md-section-divider"></div>
查找两个出现次数超过三分之一的数字。解题方法是一样的,可以用维护times[2]的方法搞定。推广到出现超过n/(m+1)次的数字,可以用俄罗斯方块的形式,维护一个map
int *find(int array[], int len, int m){
map<int, int> m;
for(int i=0;i<len;i++){
if(m.size()<len){
if(m.find(array[i]))
m[array[i]] += 1;
else
m[array[i]] = 1;
}
else{
for(map<int,int>::iterator it=p.begin();it!=p.end();){
it->second -= 1;
if(it->second==0){
p.erase(it++);
continue;
}
it++;
}
}
}
}
<div class="md-section-divider"></div>
void buildHeap(int arr[], int len, int index){
if(index>=len) return;
int I = index;
int c = i*2+1;
while(c<len){
if(c+1<len && arr[c]>arr[c+1]) c++;
if(arr[i]<arr[c]) break;
else{
swap(arr[i],arr[c]);
I = c;
c = 2*i+1;
}
}
}
<div class="md-section-divider"></div>
然后每次进入一个数字就与堆顶比较、重新建堆。
int gcd(int x,int y)//suppose x>y
{
while(x%y){
int temp = x%y;
x = y;
y = temp;
}
return y;
}
<div class="md-section-divider"></div>
同理,辗转相减
int gcd(int x,int y){
while(y){
int temp = x-y;
x = y;
y = temp;
}
}
<div class="md-section-divider"></div>
for(int i=0, j=n-1;i<j;){
if(a[i]+a[j]<sum) i++;
else(a[i]+a[j]>sum)j--;
else printf(“%d %d\n”,a[i],a[j]);
}
<div class="md-section-divider"></div>
1-n中随机选择和为m的几个数字
简单的办法是使用递归,回溯
void print(vector<int>v){
int s = v.size();
for(int i=0;i<s;i++)
cout<<v[i]<<”\t”;
cout<<endl;
}
void helper(int I, int n, int m, int sum, vector<int> v){
if(sum>m) return;
if(i>n) return;
if(sum==m){
print(v);
return;
}
for(;i<n;i++){
v.push_back(i);
helper(i+1,n,m,sum+i,v);
v.pop_back(i);
}
}
void find(int n, int m, vetor<int> v){
if(n>m) n = m;
int sum = 0;
int i=1;
helper(i,n,m,sum,v);
}
<div class="md-section-divider"></div>
24.数组的最长递增序列
如1,-1,2,-2,3,-3,4,-4的最长递增序列就是1,2,3,4
那可以动态规划来解决。
Int findMax(int array[], int length){
int *len = new int[length];
for(int i=0;i
len[i] = 1;
for(int j=0;j
if(array[i]>array[j] && len[j]+1>len[i])
len[i] = len[j]+1;
}
}
return max(len);
}
Void reverse(int array[],int low,int high){
for(;low<high;low++,high--)
swap(array[low],array[high]);
}
void shift(int array[], int n, int k){
k = k%n;
reverse(array,0,n-k-1);
reverse(array,n-k,n-1);
reverse(array,0,n-1);
}
<div class="md-section-divider"></div>
数组分割
把数组分割成两部分,使得凉拌和尽可能接近。也就是一半的和接近整体和的一半。
除了暴力我不会
字符串移位包含问题。
Str1 = “AABCD” str2=”CDAA”,问str2是否属于str1移位之后的字符串。
常规解法,每次移动一位,然后通过strstr(str1,str2)来比较如果返回0,则存在
空间换时间:我们观察str1如果循环移位str1.length()次之后又回到的原来样子,那我们其实可以直接保留原来移位的字符,移位length()次后开始重复。事实上,就是从AABCDAABCD中查找CDAA。
Bool find(string str1,string str2){
int len1 = str1.length();
int len2 = str2.length();
if(len2>len1) return 0;
string str = str1+str1;
int len=str.length();
int I,j;
for(i=0;i<len;i++){
for(j=0;j<len2;len++){
if(str[i+j]!=str2[j])
break;
if(j==len2) return true;
}
}
return false;
}
<div class="md-section-divider"></div>
Int func(string a,int I,string b,int j){
if(a[i-1]==b[j-1]) return 0;
return 1;
}
Int minEdit(string a, string b){
int lena=a.length(),lenb=b.length();
if(lena==0) return lenb;
if(lenb==0) return lena;
int **dest = new (int *)[lena+1];
int I,j;
for(i=0;i<lena;i++)
dest[i] = new int[lenb+1];
for(i = 0;i<lena+1;i++) dest[i][0] = i;
for(j=0;j<lenb+1;j++) dest[0][j] = j;
for(i=1;i<lena+1;i++){
for(j=1;j<lenb+1;j++){
dest[i][j] = min( min(dest[i-1][j]+1,dest[i][j-1]+1),dest[i-1][j-1]+func(a,i,b,j));
}
}
return dest[lena][lenb];
}
<div class="md-section-divider"></div>
从无头链表中删除节点。
把下一个节点值复制给当前节点,删除下一节点(不是尾节点才能这样做)。
八种常见排序算法
快速排序:时间复杂度O(nlogn)—O(n*n),空间复杂度O(logn),不稳定
void quickSort(int arr[], int low, int high){
if(low>=high) return;
int mid = (low+high)/2;
int i=j=low;
int povit = arr[low];
while(j<high){
if(arr[j]<povit){
i++;
int temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
j++;
}
else
j++;
}
int temp = arr[i];
arr[i] = arr[low];
arr[low] = temp;
quickSort(arr,low,i);
quickSort(arr,i+1,j);
}
<div class="md-section-divider"></div>
插入排序:从0到n逐个排序插入,时间复杂O(n)—O(n*n),空间O(1),稳定
for(int i=0;i<len;i++)
for(int j=0;j<i;j++){
if(arr[i]>arr[j]){
shiftRight(i,j);
}
}
<div class="md-section-divider"></div>
希尔排序:分多路的插入排序,时间O(n)—O(n^3/2)—O(n(logn)^2),不稳定
void shellSort(int *data,size_t size){
for(int gap=size/2;gap>0;gap /= 2)
for(int i=gap;i<size;i++){
int k=data[i];
int j=0;
for(j=i-gap;j>=0&&data[j>k; j-=gap]){ data[j+gap]=data[j]; data[j]=k;}
}
}
<div class="md-section-divider"></div>
选择排序:不稳定
for(i=0;i<len;i++)
{
for(j=i;j<len;j++){
chose max;
}
swap(i,max);
}
<div class="md-section-divider"></div>
归并排序:O(nlogn)—O(nlogn),O(n),稳定,常用于已排序的数组或者链表的合并
void mergeSort(int a[], int first, int last, int temp[]){
if(first<last){
int mid=(first+last)/2;
mergeSort(a,first,mid,temp);//归并左边
mergeSort(a,mid+1,last,temp);//归并右边
mergearray(a,first,mid,last,temp);//合并
}
}
<div class="md-section-divider"></div>
堆排序:O(nlogn)—O(nlogn),O(1),不稳定
主要步骤:建立堆结构,删除根节点。
//建堆
void shift(int d[], int ind, int len){
int i = ind;
int c = 2*i+1;
while(c<len){
if( (c+1<len) && d[c]<d[c+1] ) c++;
if(d[i]>d[c]) break;
else {
swap(d[i],d[c]); i=c; c=2*i+1;
}
}
}
void heap_sort(int d[], int n){
for(int i=n/2;i>=0;i++)
shift(d,i,n);
for(int j=0;j<n;j++){
swap(d[0],d[n-j-1]);
}
}
<div class="md-section-divider"></div>
计数排序:统计数字出现的次数,比如对年龄排序。适用于小规模整数排序。O(n+k),O(n)稳定
void FindNext(char str[], int next[]){
next[0] = -1;
int j=-1;
int i=0;
int len = strlen(str);
while(i<len-1){
if(j==-1||str[i]==str[j])
str[++i] = ++j;
else
j = next[j];
}
}
main(){
char p[]=”aaabababcdsre”;
char str[]=”ababc”;
int next[5];
FindNext(str,next);
int I=j=0;
int lenp=strlen(p),lens=strlen(str);
while(i<p){
while(i<lenp&&j<lens&&p[i++]==str[j++]);
if(j==lens){
cout<<i-j<<endl;
break;
}
I -= next[j];
j = next[j];
}
return 0;
}
<div class="md-section-divider"></div>
Void find(int *arr, int len, int *result){
int I,all=0,flag=1;
for(i=0;i<len,i++)
all ^= arr[i];
while(!(all&flag))
flag<<=1;
result[1] = result[0] = 0;
for(i=0;i<len;i++){
if(flag&arr[i])
result[0] ^= arr[i];
else
result[1] ^= arr[i];
}
}
<div class="md-section-divider"></div>
其他数字都出现三次,找出一个只出现一次的
可以想到,不能用异或了。但是明显有所有出现三次的在每个二进制位上的1的个数肯定能被 三整除。推广说,其他出现n次,那么二进制上每位1的个数都可以被n整除
int FindNumber(int a[], int len){
int bit[32];
int I,j;
memset(bit,0,32*sizeof(int));
for(i=0;i<n;i++)
for(j=0;j<32;j++)
bits[j] += (a[i]>>j)&1;
int result = 0;
for(j=0;j<32;j++)
if(bits[j]%3!=0)
result += (1<<j);
}
<div class="md-section-divider"></div>
int power(int x, int n){
if(n==0) return 1;
if(n==1) return x;
if(n%2)
return power(x, n/2)*power(x,n/2)*x;
return power(x,n/2)*power(x,n/2);
}
int power(int x,int n){
if(n==0) return 1;
if(n==1) return x;
int result = 1;
while(n!=0){
if((n&1)!=0) result *= x;
x *= x;
n >> 1;
}
return result;
}
<div class="md-section-divider"></div>
约瑟夫环
N个人,从第一人开始数,每次数到m的那个就推出,然后继续从下一个从一开始数
常规解法:直接建立一个环形链表,然后操作
有数学方法,没想到,懒得看
一个int数组,里面数据无任何限制,要求求出所有这样的数a[i],其左边的数都小于等于它,右边的数都大于等于它。能否只用一个额外数组和少量其它空间实现。
基:我们可以用两个数组max[i]记录位置i左边的最大值,min[i]记录i右边的最小值,由于题目只要一个额外数组,其实左边的最大数组是可以一边遍历一般判断的(如果从右到做也可以不需要min数组)
void find(int arr[], int len, vector<int> &v){
int *max = new int[len];
int max[0] = arr[0];
for(int i=1;i<len;i++){
if(arr[i]>=max[i-1]);
max[i] = arr[i];
}
int min = arr[len-1];
for(int j=len-1;j>=0;j--){
if(arr[j]<min) min = arr[j];
if(arr[i]>=max[i]&&arr[i]<=min)
v.push_back(i);
}
}
<div class="md-section-divider"></div>
求N长度整数数组中N-1个数字的乘积的最大值
法一:直接求出N-1个积,查询最大值
法二:利用N个数字的正负分布情况。扫描一遍,统计数组中出现的整数个数p,负数个数n,零个数z,绝对值最小的正复数a,b
如果z>=2,结果0,
如果z=1,当n是奇数结果为0,n是偶数结果是除0以外的乘积
如果z=0,当n是奇数,就是除开b以外的积,n是偶数就是除开a以外的积
链表的排序
快排:改变节点中的值不改变指向/改变指向不改变值
使用改变指针的时候,每次分割的时候另外开两个指针left和right,最后再将两个指针合并起来。
我们可以将链表的第一个节点使用空节点,然后每次递归传入的都是pre,left,right三个节点。
归并:也是递归形式的
插入排序:
LCS
stra, strb, ans[][]
ans[i][0]=0;ans[0][j]=0;
for(i:0->lena)
for(j:0->lenb)
if(stra[i]==strb[j]) ans[i][j] = ans[i-1][j-1]+1;
else ans[i][j]=max(ans[i-1][j],ans[i][j-1]);
<div class="md-section-divider"></div>
最大子矩阵和
从1-n列,n*n个长矩阵,把长矩阵的没一列当成一个数组,求数组连续最大和
把n个骰子扔地上,所有向上的一面的和是s,输入n,输出所有s出现的概率
问题其实就是求解每个s出现的次数从[n,6n]
思路一:利用递归回溯原理,每次扔一个骰子,扔到n次的时候记录当前和进map中
思路二:动态规划,找到最优子结构,本题的结构是F(k,n)表示k个骰子和为n的次数,有如下关系
F(k,n)=F(k-1,n-1)+F(k-1,n-2)...+F(k-1,n-6),当k=1时,F(1,1->6)=1
第一个只出现一次的字符
扫描一遍,记录每个字符出现的次数,第二遍扫描的时候如果字符对应的是1就返回
输入n打印出从0到位数为n的最大的数字。
实际上就是n个数位上从0-9的排列。递归打印
打印排列和组合,有/没有重复元素
//无重复的排列
void print(string s, int len, int index){
if(index>len) return;
if(index==len){
cout<<s<<endl;
return;
}
for(int i=index;i<len;i++){
swap(s[i],s[index]);
print(s,len,index+1);
swap(s[i],s[index]);
}
}
<div class="md-section-divider"></div>
数字的整数次方。
判断次方是不是0,1;
当次方是奇数的时候a^n = a^((n-1)/2)*a((n-1)/2)*a
当次方是偶数的时候a^n = a^(n/2)*a(n/2)
二叉树生成双向链表
数字在排序数组中出现的次数
首先二分查找,然后从节点开始两边遍历
求二叉树的深度
int deep(Node *node){
if(node==NULL) return 0;
int left = deep(node->left);
int right = deep(node->right);
return (left>right)? (left+1):(right+1);
}
<div class="md-section-divider"></div>
求二叉树是不是AVL
bool balance(Node *node){
if(node==NULL) return true;
int left = deep(node->left);
int right = deep(node->right);
int diff = (left>right)? (left-right):(right-left);
if(diff>1) return false;
return balance(node->left)&&(node->right);
}//重复计算,效率底下
<div class="md-section-divider"></div>
如果我们后续遍历,那么一遍遍历就可以得到结结果
bool balance(Node *node, int &deep){
if(node==NULL){
deep = 0;
return true;
}
int left,right;
if(balance(node->left,left)&&balance(node->right,right)){
int diff = (left>right)? (left-right):(right-left);
if(diff<=1){
deep = 1+ (left>right? Left:right);
return true;
}
}
return false;
}
<div class="md-section-divider"></div>
不用乘除,for,while,if,switch,求1-n的和
正常情况下可以用循环或者递归来实现。但是这里都不行。
构造函数求解:用两个静态变量,n和sum每次构造一个实例new boj[n]就n++,sum += n;
给一个二叉树,每个节点都是正或负整数,如何找到一个子树,它所有节点的和最大?
后续序遍历,每个节点保存当前值和当前最大值.到顶部的时候就是最大值
int find(Node *node){
if(node==NULL) return -1;
return helper(node);
}
int helper(Node *node){
int left=0,right=0;
if(node->left!=NULL)
left = helper(node->left);
if(node->right!=NULL)
right = helper(node->right);
if(node->value<0)
node->value = max(node->value,left,right);
int temp = left;
if(left<right)temp = rigtht;
if(temp>0)
node->value+=temp;
return node->value;
}
<div class="md-section-divider"></div>
DICT = { str(i+1):chr((ord('a')+ i)) for i in range(26) }
MAX_SIZE = max(len(k) for k in DICT.keys())
def parse(dt,cache,string):
if string == "":
return [""]
if string in cache:
return cache[string]
ret = []
for i in range(MAX_SIZE):
if len(string) > i and string[0:i+1] in dt:
ret += [ dt[string[0:i+1]] + j for j in parse(dt,cache,string[i+1:]) ]
else:
break
cache[string] = ret
return ret
<div class="md-section-divider"></div>
void print(vector<char> v){
int i;
for(i=0;i<v.size();i++)
cout<<v[i];//>>>>
cout<<endl;
}
void helper(char *str, int start, vector<char> v){
int len = strlen(str);
if(start == len){
print(v);
}
if(start<len&&str[start]-'0'>0&&str[start]-'0'<=9){
v.push_back(str[start]-'0'+'a');
helper(str,start+1,v);
v.pop_back();
if(start+1<len&&str[start+1]-'0'>0&&str[start+1]-'0'<=9){
int temp = str[start]-'0';
temp = temp*10+str[start+1]-'0';
if(0<temp&&26>temp){
v.push_back('a'+temp);
helper(str,start+2,v);
v.pop_back();
}
}
}
}
void find(char *str){
vector<char> v;
helper(str,0,v);
}
int main(int argc, const char *argv[])
{
char arr[] = "13264811591";
find(arr);
return 0;
}
void find(int arr[], int len){
int I=0;
int count=0;
int temp;
while(count<len){
count++;
if(arr[i]==i||arr[i]==len)
i++;
else{
temp = arr[i];
arr[i] = arr[temp];
arr[temp] = temp;
}
}
i=0;
while(i<len)
if(arr[i++]==len){
cout<<i-1<<endl;
break;
}
}
int find(int arr[], int len){
int count=0;
int index=0;
int temp;
while(count++<len){
if(arr[index]==-1)
return index;
else{
temp = arr[index];
arr[index]=arr[temp];
arr[temp]=-1;
}
}
return -1;
}
int add(int a, int b){
if(b==0) return a;
int sum = a^b;
int carry =(a&b)<<1;
return add(sum,carry);
}
void swap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
bool helper(int a, int b, int c)
{
if( (a+b)>c && (b-a)<c )
return true;
return false;
}
void Judeg(vector<int> vv)
{
vector<int> v(vv);
if(v[0]>v[1])
swap(v[0],v[1]);
if(v[1]>v[2])
swap(v[1],v[2]);
if(helper(v[0],v[1],v[2]))
if(helper(v[1],v[2],v[0]))
if(v[0],v[2],v[1])
{
for(int i=0;i<3;i++)
cout<< v[i] <<"\t";
cout<<endl;
}
}
void FindTrangLe(int array[], int index, int len, vector<int> v)
{
if( v.size()==3 )
{
Judeg(v);
return;
}
if(index>=len)
return;
for(int i=index;i<len;i++)
{
v.push_back(array[i]);
FindTrangLe(array,i+1,len,v);
v.pop_back();
}
}
void print(vector<char> v){
int j;
for(j=0;j<v.size();j++)
cout<<v[j];
cout<<endl;
}
void helper(int left, int right, int n, vector<char> v){
if(left==right&&left==n)
print(v);
if(left>n) return;
v.push_back('(');
helper(left+1,right,n,v);
v.pop_back();
if(left>right){
v.push_back(')');
helper(left,right+1,n,v);
v.pop_back();
}
}
void find(int n){
int left=0;
int right=0;
vecotr<char> v;
helper(left, right, n, v);
}
给出n对括号,判断合法性(经典做法用栈,也可以用一个计数器遇到左+1右-1)
bool judeg(char *str){
int count=0;
int len=strlen(str);
for(int j=0;j<len;j++){
if(count<0) break;
if(str[j]!='('&&str[j]!=')') return false;
if(str[j]=='(') count++;
else if(str[j]==')') count--;
}
if(count!=0)
return false;
return true;
}
int find(char *str){
int len = strlen(str);
int i;
int left,right;
if(len==1) return 1;
int max = 1;
for(i=1;i<len-1;i++){
left=i-1;
right=i+1;
if(str[left]==str[right]){
while(left>=0&&right<len&&str[left--]==str[right++]){
if(max<right-left-1)
max = right-left-1;
}
}
if(str[i]==str[right]){
right++;
while(left>=0&&right<len&&str[left--]==str[right++]){
if(max<right-left-1)
max = right-left-1;
}
}
}
return max;
}
一个小猴子边上有100 根香蕉,它要走过50 米才能到家,每次它最多搬50 根香蕉,每走1 米就要吃掉一根,请问它最多能把多少根香蕉搬到家里。
设折返一次,折返点距离出发点x,在出发点带50个,到达折返点放下50-2x个,返回出发点带上剩下的50个,再次到达扳返点时有(50-2x)+(50-x)=100-3x,剩下50-x米,到家时还剩(100-3x)-(50-x)=50-2x,显然x越小,结果越大,但是需要满足100-3x<=50,因此结果为50/3=16。
大数加减乘除
两个软硬程度一样但未知的鸡蛋,它们有可能都在一楼就摔碎,也可能从一百层楼摔下来没事。有座100层的建筑,要你用这两个鸡蛋通过最少的次数确定哪一层是鸡蛋可以安全落下的最高位置。可以摔碎两个鸡蛋
基于动态规划的方法
前面提到,若要采用动态规划的方法,最重要的是要找到子问题。做如下的分析,假设f{n}表示从第n层楼扔下鸡蛋,没有摔碎的最少尝试次数。第一个鸡蛋,可能的落下位置(1,n),第一个鸡蛋从第i层扔下,有两个情况:
碎了,第二个鸡蛋,需要从第一层开始试验,有i-1次机会
没碎,两个鸡蛋,还有n-i层。这个就是子问题了f{n-i} 所以,当第一个鸡蛋,由第i个位置落下的时候,要尝试的次数为1 + max(i - 1, f{n - i}),那么对于每一个i,尝试次数最少的,就是f{n}的值。状态转移方程如下: f{n} = min(1 + max(i - 1, f{n - 1}) ) 其中: i的范围为(1, n), f{1} = 1 完毕。
推广
动态规划的方法,可以推广为n层楼,m个鸡蛋。如下分析: 假设f{n,m}表示n层楼、m个鸡蛋时找到最高楼层的最少尝试次数。当第一个鸡蛋从第i层扔下,如果碎了,还剩m-1个鸡蛋,为确定下面楼层中的安全楼层,还需要f{i-1,m-1}次,找到子问题;不碎的话,上面还有n-i层,还需要f[n-i,m]次,又一个子问题。 状态转移方程如下: f{n, m} = min(1 + max(f{n - 1, m - 1}, f{n - i, m}) ) 其中: i为(1, n), f{i, 1} = 1
基于数学方程的方法
假设最少尝试次数为x,那么,第一个鸡蛋必须要从第x层扔下,因为:如果碎了,前面还有x - 1层楼可以尝试,如果没碎,后面还有x-1次机会。如果没碎,第一个鸡蛋,第二次就可以从x +(x - 1)层进行尝试,为什么是加上x - 1,因为,当此时,第一个鸡蛋碎了,第二个鸡蛋还有可以从x+1 到 x + (x - 1) - 1层进行尝试,有x - 2次。如果还没碎,那第一个鸡蛋,第三次从 x + (x - 1) + (x - 2)层尝试。碎或者没碎,都有x - 3次尝试机会,依次类推。那么,x次的最少尝试,可以确定的最高的楼层是多少呢? x + (x - 1) + (x - 2) + … + 1 = x(x+1) / 2 那反过来问,当最高楼层是100层,最少需要多少次呢?x(x+1)/2 >= 100, 得到x>=14,最少要尝试14次。
排序只有1,2,3三个元素的数组,不能统计1,2,3的个数。
方法一:假设,我们有三个指针:p1、p2、p3.p1从左侧开始,指向第一个非1的数字;p3从右侧开始,指向第一个非3的数字。p2从p1开始遍历,如果是2,p2继续遍历,直到p2遇到1或者3。
方法二:先替换成质数1=2,2=3,3=5.然后把他们相乘,比如211332=322553,乘积是900,然后除以2,3,5;能整除多少次就有多少个235。
给定一个数组,里面只包含0.1两个数字,请找到一个最长的子序列,使得其中的0,1个数是相同的。
比如:010101->答案就是本身,1101000->答案是110100
利用动态规划思想,可以把0换成-1,那么题目就转化成求和为0的子序列了。
如果子序列是从数组头开始,那么直接记录就可以得到结果;
如果不是从数组头开始,比如对1101100来说
那么DP[0]=DP[2]=DP[6],所以,最大长度就是6-0=6。
为了求解,我们可以使用一个map来保存k=和,value=位置vector,然后求解最大差值
void find(int arr[], int len){
map<int,vector<int> > ans;
int j=0;
int sum=0;
for(;j<len;j++){
if(arr[j]==0){
sum+=-1;
ans[sum].pusb_back(j);
}
else{
sum += 1;
ans[sum].push_back(j);
}
}
int begin=0,end=0;
int step=0;
for(map<int,vector<int> >::iterator it=ans.begin();it!=ans.end();it++){
if((it->second)[(it->second).size()-1]-(it->second)[0]>step){
step = (it->second)[(it->second).size()-1]-(it->second)[0];
begin = (it->second)[0];
end = (it->second)[(it->second).size()-1];
}
}
cout<<begin<<endl<<end<<endl<<step<<endl;
//值得注意的是,begin是开始的前一个,end是结束的那个
}
//我们需要注意,快排的时候,当a>=b的时候i向前,j不移动。
//验证的时候用一个数字、数字结尾、等边界条件
int compare(int a,int b){
int b1 = b;
int a1 = a;
while( (b1=b1/10)>0) a1 *= 10;
a1 = a1*10+b;
int b2 = b;
int a2 = a;
while((a2=a2/10)>0) b2 *= 10;
b2 = b2*10+a;
cout<<a1<<"\t"<<b2<<endl;//>>>>>>
if(a1>=b2) return 1;
return 0;
}
void swap(int &a, int &b){
int temp = a;
a = b;
b = temp;
}
void sort(int arr[], int begin, int end){
if(begin>end) return;
cout<<begin<<"\t"<<end<<"\t"<<arr[0]<<endl;//>>>>>>>>
int j=begin,i=begin;
while(i<=end){
if(compare(arr[begin],arr[i])){
//if(arr[i]<=arr[begin]){
i++;
}
else{
j++;
swap(arr[j],arr[i]);
i++;
}
}
swap(arr[begin],arr[j]);
sort(arr,begin,j-1);
sort(arr,j+1,end);
}
一批日志中,在线性时间内找到查询次数多余n/3的查询。
通用的办法是hashmap,排序,快速选择
我们做过多余一半的查询——每次删除两个不一样的,剩下的就是结果。
这个办法可以推广到一般情况,比如查询次数超过n/m,每次删除m个不同的查询。实现办法可以参考俄罗斯方块的实现。我们申请m大小的map,开始遍历日志,如果
a.遇到一个不在map中的查询,则插入map,设置为1(相当于落下一个新方块)
b.遇到一个map中的查询,则加1
c.当map大小为m时候,所有值都减一,然后去掉值为0的部分
直到遍历完成,对map中剩下的每个查询进行检测(因为不一定每个都是结果)。map 的查询时间是logM,可以当成常数
给定一个数组,我们可以找到两个不相交的、并且是连续的子数组A和B,A中的数字和为sum(A), B中的元素和为sum(B)。找到这样的A和B,满足sum(A) - sum(B)的绝对值是最大的。 例如:[2, -1 -2, 1, -4, 2, 8]划分为A=[-1, -2, 1, -4], B=[2, 8], 最大的值为16
我们前面做过从数组中找到最大的字数组,那么这个问题我们可以这样来做:首先确定一个位置i,使得[0—i],[1+1,n]两个数组的字数组的最大值与最小值的差的绝对值最大,问题就解决了。
总结整个方法的流程如下:
1.从左向右遍历数组,计算max_left和min_left数组,O(n)时间复杂度
2.从右向左遍历数组,计算max_right和min_right数组,O(n)时间复杂度
3.然后对于每一个i,i从1开始到n-1,计算max_left[i - 1] - min_right[i], max_right[i] - min_left[i - 1]。选取绝对值最大的。
52张牌,四张A,随机打乱后问,从左到右1张一张翻直到出现第一张A,请问平均要翻几张牌?
摸到第一张A之前的都是其他的牌,那么,之前会有多少种可能呢? 之前可能会有0张,1张。。。。48张。考虑4张A在牌中的位置,他们把其他牌分成了5份(四个点把直线分成五段)。每一份的个数从0-48不等,完全随机的情况下,每份的平均长度为48/5=9.6,摸完这9.6张后,接下来的就是第一张A,故平均需要摸9.6+1=10.6张,即11张
n根长度不一的棍子,判断是否有三根棍子可以构成三角形,并且找到周长最长的三角形。
方法,直接对棍子排序,然后从大到校开始查找连续的三个数是否能组成三角形(最长棍子的长度 < 另外两根棍子的长度和),第一个成功的就是最佳答案
从1到n,n个数字,每个数字只出现一次。现在,随机拿走一个数字,请给出方法,找到这个数字。如果随机拿走两个数字呢?如果随机拿走k个数字呢?
如果是一个数字,那么就直接把1-n加起来减去当前数字
如果是两个数字把1-n加起来减去当前数字,这是等式1;把平方加起来相减是等式2;解方程组
如果是k个数字,那么就是k阶方程组
有这样一个数组A,大小为n,相邻元素差的绝对值都是1.如: A={4,5,6,5,6,7,8,9,10,9}。 现在,给定A和目标整数t,请找到t在A中的位置。除了依次遍历,还有更好的方法么?
比如说t=8
那么第一次k=4,8-4=4,于是第二次跳到[4]=6,8-6=2,跳到[4+2]=8,成功
求解最长会文串
法一:常规解法,两层循环得到一个字串,第三层判断是否是回文
法二:从1——n-2,分别作为回文的中心,分两次判断回文长度为奇数和偶数的情况
char *palSubstr(char *str){
int len = strlen(str);
int i,left,right;
int low=0,high=0;
int maxLen = 0;
for(i=1;i<len-1;i++){
left = I-1;
right = i+1;
while(left>=0&&right<len&&str[left--]==str[right++]);
if(right-left-1>maxLen){
low = left+1;
high = right -1;
maxLen = right-left-1;
}
left = I;
rith = I+1;
while(left>=0&&right<len&&str[left--]==str[right++]);
if(right-left-1>maxLen){
low = left+1;
high = right -1;
maxLen = right-left-1;
}
}
return str[low to high];
}
```c
23. 删除字符串中的“b”和“ac”,需要满足如下的条件:只能遍历一次,不能使用额外空间,而且aaccac删除完了就是ac
基本思路:abacbcdc
找到第一个b,把前面的a直接覆盖b,从a开始,遇到ac,把a向前移动到c,从a开始遇到b…然后以此类推。
方法二:用状态鸡
有两种状态one, two。two表示前面一个字符是a,其他情况用one表示
如果我们的当前状态是one,则拷贝;如果当前状态满足如下情况,则不拷贝
1)当前是b,因为要删除b
2)当前是a,我们要考虑下一个状态
3)当前是two,如果当前不是c则先拷贝前面一个a,如果当前不是b、a则拷贝字符
<div class="md-section-divider"></div>
```c
void stringFilter(char *str){
int state = one;
for(int i=0;str[i]!='\0';i++){
if(state==one&&str[i]!='a'&&str[i]!='b')
str[j++] = str[i];
if(state==two&&str[i]!='c'){
str[j++] = 'a';
if(str[i]!='a'&&str[i]!='b')
str[j++] = str[i];
}
}
if(state==two)
str[j++] = 'a';
return;
}
int find(char *arr[]){
map<char,int> ans;
int j;
int minlen = len;
int nowlen;
len = strlen(arr);
for(j=0;j<len;j++){
ans[arr[j]]=0;
}
int begin = 0, end = 0;
while(begin<end&&end<len){
if(!judeg(ans)){
ans[arr[end]++;
end++;
}
else{
nowlen = end-begin;
if(nowlen<minlen)
minlen = nowlen;
ans[arr[begin]]--;
begin++;
}
}
return minlen;
}
给定两个字符串A和B,判断A中是否包含由B中字符重新排列成的新字符串。例如:A=abcdef, B=ba,结果应该返回true。因为ba的排列ab,是A的子串。
首先把B排序得到D,然后对A有m-n+1个长度为n的子串,对每个长度为n 的字串单独排序比较,就可以得到结果
给定k个数组,每个数组有k个整数。每个数组中选取一个整数,一共k个整数,取其和,一共可以得到k^k个和。给出方法,求得这k^k个和中,最小的k个。
使用分置的思想,首先求出两个数组的前k大和, 在和滴三个数组求,以此类推
但是要求出两个数组的和的前K大也不简单。
比如说a[k]b[k]
方法一:排序求前面k个,复杂度k*k+k*klog(k*k)
方法二:开辟一个新的数组index[k],初始化为0,用来记录每个ai加上bj的bj的下标,每次扫描一遍选出最小的和并且把index[i]++,if(indexi[i]>=k) index[i]=-1;如此直到有k个和或者index数组全都是-1.这样每次都要计算k个和,复杂度是k*k
方法三:?
给定两个数组X和Y,元素都是正数。请找出满足如下条件的数对的数目:x^y>y^x,x来自X,y来自Y
分析:对两边取对数,ylogx>xlogy,再同时除以xy.(logx)/x>(logy)/y,就有了单调性
给定字符串,以及一个字典,判断字符串是否能够拆分为字段中的单词。例如,字段为{hello,world},字符串为hellohelloworld,则可以拆分为hello,hello,world,都是字典中的单词。
最简单办法就是直接递归查找
bool find(string str){
int size = str.size();
if (size==0) return true;
for(int I =1;i<=size;i++){
if(dictionary.contains( str.substr(0,i)) && find(str.substr(i,size-i)))
return true;
}
return false;
}
但是这样做空间时间太大,有很多需要重复计算的地方,于是就有了动态规划。动态规划是解决递归重复计算的好办法
bool find(string str){
int size = str.length();
if(size==0) return ture;
bool wb[size+1];
meset(wb,0,sizeof(wb));
for(int i=1;i<=size;i++){
if(wb[i]==false && dictionary.contains( str.substr(0,i)))
wb[i] = true;
if(wb[i]==true){
if(i==size) return true;
for(int j=i+1;j<=size;j++){
if(wb[j]==false && dictionary.contains(str.substr(i,j-i)))
wb[j] = true;
if(j==size&&wb[j]==true)
return true;
}
}
}
return true;
}
String longestSubstr(string str){
int start = 0;
int end = str.length();
int pos[] = new int[26];
int I ;
fot(i=0;i<26;i++)
pos[i] = -1;
int maxLen = -1;
for(i=0;i<str.length;i++){
if(pos[str.charAt(i)-'a']<0)
pos[str.charAt(i)-'a'] = I;
else{
int length = I-pos[str.charAt(i)-'a'];
if(length>maxLen){
start = pos[str.charAt(i)-'a'];
end = I;
maxLen = length;
}
pos[str.charAt(i)-'a'] = I;
}
}
return str.substr[start,end];
}
String deleteNum(string input, int k){
int k_orig = k;
int len = input.size();
while(k){
int i=0;
for(;i<len-1;i++)
if(input[i]>input[i+1])
break;
for(;i<len-1;i++)
input[i] = input[i+1];
k--;
len--;
}
return input.substr(0,input.size()-k_orig);
}
void FindNext(char *str){
int len=strlen(str),i;
char temp = str[len-1];
for(i=len-1;i>=0;i++)
if(str[i]<str[len-1]){
swap(str[i],str[len-1]);
break;
}
if(i<0)return;
for(int j=len-1,i=i+1;i<j;i++,j--)
swap(str[i],str[j]);
}