致我们终将忘记的算法(数组也疯狂)

1->在一个整数数组中,除了一个整数只出现1次外,其余所有的整数均出现了3次。请在线性时间复杂度内找出这个整数。

方法1:创建一个长度为sizeof(int)*8的数组count[sizeof(int)*8],其中count[i]表示在i位出现1的次数。如果count[i]是3的整数倍,则忽略。否则就把该位提取出来组成答案。

int singleNumber(int A[],int n){

  const int size=sizeof(int)*8;

  int *count=new int[size]();

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

    for(int j=0;j<size;j++){

      count[j]+=(A[i]>>j)&1;    count[j]%=3;

    }

  int ret=0;

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

    ret+=count[i]<<i;

  return ret;

}

方法2:用变量one记录到目前为止,二进制1出现过1次的二进制位,用two用来记录到目前为止,二进制1出现两次有哪些。当one和two同时为1时,表示某一位的二进制1出现了三次,此时one和two该位清零。最终得到的one就是所想要的结果。

int singleNumber(int A[],int n){

  int one=0,two=0,three=0;

  for(int i=0;i<n;i++){

    two|=(one&A[i]);

    one=one^A[i];

    three=~(one&two);

    one&=three;                            //若一个位出现了三次,该位清零

    two&=three;

  }

}

 

2->找出一个数组中,仅出现1次的数,其他的整数每个都出现2次。要求时间复杂度O(n).

方法:采用异或的思想,两个相同整数异或等于0.一个整数与0异或等于整数本身

int singleNumber(int A[],int n){

  int ret=0;

  for(int i=0;i<n;i++)    ret^=A[i];

  return ret;

}

 

3->给n个小孩分糖果,要求每个小孩至少分到1个糖果,并且年龄大的孩子分到的糖果不能比它左右中比他年龄小的孩子的少。求出至少得分配多少个糖果。孩子的年龄存放在数组rating内。

方法:初始每个小孩都分到1个,左右各扫描一趟,如果当前小孩的年龄比前一个大,重新给其分配。若比前一个小,则将当前要分配的糖果设为1.总的时间复杂度O(n)

int Candy(vector<int> &rating){

  const int n=rating.size();   vector<int> ret(n,1);

  for(int i=1,cur=1;i<n;i++)

      if(rating[i]>rating[i-1])   ret[i]=max(cur++,ret[i]);

      else   cur=1;

  for(int i=n-2,cur=1;i>=0;i--)

     if(rating[i]>rating[i+1])  ret[i]=max(cur++,ret[i]);

     else  cur=1;

  return accumulate(ret,ret+n);

}

 

4->有N个加油站围成一个圈,每个加油站的汽油存储量为gas[i],一辆汽车从第i个加油站行驶到下一个加油站需要cost[i]汽油,问从哪个加油站出发可以使汽车行驶完一圈,如果存在则返回i,否则返回-1.

解法:设置两个变量sum表示当前位置的有效性,total表示整个问题的有效性。

int canCompleteCircuit(vector<int>&gas,vector<int>&cost){

  int total=0,cur=-1;

  for(int i=0,sum=0;i<gas.size();i++){

     sum+=gas[i]-cost[i];   total+=gas[i]-cost[i];

     if(sum<0){cur=i;  sum=0;}

  }

  return total>0?cur+1:-1;

}

 

5->给你一个矩阵,将0元素所在的行和列的元素全部设置为0.

解法:该题按照正常思路就可以求解,即先找出0元素所在的行和列,之后将这些行和列的元素全部设置为0.算法的时间复杂度为O(m*n)

void setZeroes(vector<vector<int> >&matrix){

    const int m=matrix.size(),n=matrix[0].size();        //此处要注意matrix为NULL的时候,这题不用判断

    vector<bool> row(m,false);  vector<bool> col(n,false);

    for(int i=0;i<m;i++)

        for(int j=0;j<n;j++)

            if(matrix[i][j]==0)   row[i]=col[j]=true;

    for(int i=0;i<m;j++)

            if(row[i])  fill(matrix[i][0],matrix[i][0]+n,0);

    for(int j=0;j<n;j++)

           if(col[j])  {for(int i=0;i<m;i++)  matrix[i][j]=0;}

}

 

6->返回一个整数的GrayCode序列。其中GrayCode定义为 00-----0   01------1  11------1  10------2

解法:

unsigned int binary_to_gray(unsigned int n){           //整数n的grayCode求解方法

    return n^(n>>1);

}

vector<int> grayCode(int n){

    vector<int> reslt;

    const int size=1<<n;                //一个整数的GrayCode序列的个数为2的n次方

    result.resize(size);

    for(int i=0;i<size;i++)   result.push_back(binary_to_gray(i));

    return result;

}

 

7->爬楼梯问题,一次只能爬1个台阶或者2个台阶,问n个台阶总共有多少种爬法。时间复杂度不能太高

解法:这是一个斐波那契数列。可以采用迭代的方式搞定

int climbStairs(int n){

    int pre=0,cur=1;

    for(int i=1;i<=n;i++){ int tmp=cur;  cur+=pre;  pre=tmp;}

    return cur;

}

 

8->给你一个整数数组代表一个整数,给这个整数加1

解法:高精度加法问题,逐位进位加

void add(vector<int>digits,int digit){

    int c=1;             //表示进位

    for(int i=digits.size()-1;i>=0;i--){

        digits[i]+=c;

        c=digits[i]/10;

        digits[i]%=10;

    }

    if(c>0){digits.inset(digits.begin(),1);}

}

vector<int> plusOne(vector<int>& digits){

    add(digits,1);   return digits;

}

 

9->顺时针旋转一个n*n矩阵90度

解法:先沿着副对角线翻转,然后再沿着水平中轴翻转,即可得到顺时针旋转90度的效果

void rotate(vector<vector<int> >&matrix){

    const int n=matrix.size();

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

        for(int j=0;j<n-i;j++)

            swap(matrix[i][j],matrix[n-1-j][n-1-i]);         //沿着副对角线翻转

    for(int i=0;i<n/2;i++)

        for(int j=0;j<n;j++)

            swap(matrix[i][j],matrix[i][n-1-j]);               //沿着水平中轴翻转

}

 

10->求一组柱状图形成的水槽的最大蓄水能力

解法:对于每个柱子,找到其左右两边最高的柱子,该柱子能容纳的面积就是min(max_left,max_right)-height.所以从左往右,每个柱子的左边最大值,从右往左扫描求出每个柱子的右边最大值,再扫描一遍把每个柱子的面积相加。

int trap(int A[],int n){

    int *max_left=new int[n]();   int *max_right=new int[n]();

    for(int i=1;i<n;i++){ max_left[i]=max(max_let[i-1],A[i-1]);} 

    for(int i=n-1;i>=0;i--){ max_right[i-1]=max(max_right[i],A[i]);}

    int sum=0;

    for(int i=0;i<n;i++){

        int height=min(max_left[i],max_right[i]);

        if(height>A[i]) sum+=height-A[i];

    } 

    retun sum;

}


11->数独是否合法问题,给出一个9*9并且包含9个3*3的数独,数独被部分填充,判断数独的合法性。


解题方法:没有特殊的技巧,先判断9*9的行列包含的数字是否符合数独的要求,再分别判断9个小的矩阵描述是否符合数独的要求。其中要判断一个矩阵是否符合数独的要求,引入一个辅助数组used

bool check(char ch,bool used[]){

    if(ch=='.') return true;

    if(used[ch-'1'])    return false;

    return used[ch-'1']=true;

}

bool isValidSudoku(const vector<vector<char> >&board){

    bool used[9];

    for(int i=0;i<9;i++){

        fill(used,used+9,false);

        for(int j=0;j<9;j++)                                       //检查行的合法性

            if(!check(board[i][j],used))    return false;

        fill(used,used+9,false);

        for(int j=0;j<9;j++)                                      //检查列的合法性

            if(!check(board[j][i],used))    return false;

    }

    for(int r=0;r<3;r++)              //检查9个格子的合法性

        for(int c=0;c<3;c++){

            fill(used,used+9,false);

            for(int i=3*r;i<r*3+3;i++)

                for(int j=c*3;j<c*3+3;j++)

                      if(!check(board[i][j],used))    return false;

    }

    return true;

}


12->给出一个数组例如{1,2,3}求出它的下一个排列{1,3,2}

解题方法:该题的实现比较简单,就是调用algorithm中的next_permutation库函数即可实现,不过要注意的是,如果当前排列已经是最后一个排列,那么函数返回的应该是数组的第一个排列。

void nextPermutation(vector<int> &num){

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

        return ;

    else  sort(num.begin(),num.end());

}


13->求一个数组的第K个排列。例如{1,2,3}的排列序列:“123”,“132”,“213”,“231”,“312”,“321”,其中第3个排列为“213”

解题方法:暴力破解法,借助next_permutation库函数,枚举出第K个排列

string getPermutation(int n,int k){

    string s(n,0);

    for(int i=0;i<n;i++)    s[i]+=i+1;

    for(int i=0;i<k-1;i++)  next_permutation(s.begin(),s.end());

    return s;

}


14->删除数组中所有等于给定的元素,并返回新的数组的长度

解题方法:设置两个指针,一个遍历原来的数组,一个指示新的数组。

int removeElement(int A[],int n,int elem){

    int index=0;

    for(int i=0;i<n;i++){

        if(A[i]!=elem)  A[index++]=A[i];

    }

    return index;

}


15->4Sum 在数组中找出任意4个数之和的target的数,如果存在多对,同时返回。

解题方法:与求3Sum类似,先排序,后左右夹逼

vector <vector<int> > fourSum(vector<int>& num,int target){

    vector<vector<int> > ret;

    sort(num.begin(),num.end());

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

        if(i!=0 && num[i]==num[i-1])  continue;

        for(int j=i+1;j<num.size();j++){

             if(j!=i+1 && num[j]==num[j-1])   continue;

             int k1=j+1,k2=num.size()-1;

             while(k1<k2){

                  if(k1>j+1 && num[k1]==num[k1-1]) {k1++;continue;}

                  if(k2<num.size()-1 && num[k2]==num[k2+1]) {k2--;continue;}

                  int sum=num[i]+num[j]+num[k1]+num[k2];

                  if(sum==target)  {ret.push_back(i,j,k1,k2);k1++;k2--;}

                  else if(sum>target) k2--;

                  else  k1++;

             }

        }

    }

    return ret;

}


16->3Sum Closest 即找出数组中三个数之和最接近target的数。

解题方法:先排序后左右夹逼,时间复杂度为O(n^2)

int threeSumClost(vector<int>& num,int target){

    int retsult=0;  int min_gap=INT_MAX;

    sort(num.begin(),num.end());

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

        int k1=i+1,k2=num.size()-1;

        while(k1<k2){

             int sum=num[i]+num[k1]+num[k2];

             if(abs(sum-target)<abs(min_gap-target))   min_gap=sum;

             if(sum==target) return sum;

             else if(sum>taget) k2--;

             else k1++;

        }

    }

    return min_gap;

}


17->3Sum

解题方法同上:

vector<vector<int> > threeSum(vector<int> &num){

    vector<vector<int> > retsult;

    if(num.size()<3) return result;

    sort(num.begin(),num.end());

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

        if(i!=0 && num[i]==num[i-1])  continue;

        int k1=i+1,k2=num.size()-1;

        while(k1<k2){

           if(k1>i+1 && num[k1]==num[k1-1])  continue;

           if(k2<num.size()-1 && num[k2]==num[k2+1])  continue;

           int sum=num[i]+num[k1]+num[k2];

           if(sum==target){result.push(i,k1,k2);k1++;k2--;}

           else if(sum>target) k2--;

           else k1++;

       }

    }

    return result;

}


18->2Sum,要求的是对应数值的下标

解题方法:构造新的数值结构,排序,左右夹逼。时间复杂度O(nlogn),为了减低总体的时间复杂度,可以引入hashmap

vector<int> twoSum(vector<int> &num,int target){

    map<int,int> vmap;

    vector<int> result;

    for(int i=0;i<num.szie();i++)  vmap[num[i]]=i;

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

        const int grap=target-num[i];

        if(vmap.find(grap)!=vmap.end() &&vmap[grap]>i){result.push(i+1);result.push(vmap[grap]+1);break;}

    }

    return result;

}


19->最长连续子序列。例如数值【100,4,200,1,3,2】,其中最长的连续子序列为【1,2,3,4】返回的长度为4。并且要求时间复杂度为O(n)

解题方法:如果该题允许时间复杂度为nlogn的话,我们可以先排序,然后在O(n)的时间内求出最长连续子序列。由于数值内的数值是无序的,又要求时间复杂度为O(n)很自然的想到hash。用一个hash表记录每个元素是否使用,对每个元素以该元素为中心左右扩展,直到不连续为止,记录下最长的长度

int longestConsecutive(const vector<int> &num){

    map<int,bool> used;

    for(int i=0;i<num.size();i++) used[num[i]]=false;

    int longest=0;

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

        if(used[num[i]]) continue;

        int length=1;  used[num[i]]=true;

        for(int j=i+1;used.find(num[j])!=used.end();j++){used[j]=true; length++;}

        for(int j=i-1;used.find(num[j])!=used.end();j--){used[j]=true;length++;}

        longest=max(longest,length);

    }

    return longest;

}


20->给定两个已经排序好的数组,找到两者所有元素的中位数。时间复杂度要求O(m+n)

解题方法:这道题更通用的形式为,找出两个已经排序好的数组的合并数组的第K大元素。如果不要求时间复杂度,自然很容易想到。该题采用一种类似二分查找的算法。先比较K/2处的A[k/2]和B[k/2],如果A[k/2]>B[k/2]的话,那么第K大元素在B中,反之则在A中,可以用递归的方法实现。注意边界条件控制

int find_Kth(int A[],int m,int B[],int n,int k){

    if(m>n) find_Kth(B,n,A,m,k);

    if(m==0)  return B[k-1];

    if(k==1)  return min(A[0],B[0]);

    int ia=min(k/2,m),ib=k-ia;

    if(A[ia-1]<B[ib-1])   return find_Kth(A+ia,m-ia,B,n,k-ia);

    else if(A[ia-1]>B[ib-1])  return find_Kth(A,m,B+ib,n-ib,k-ib);

    else return A[ia-1];

}

double findMedianSortedArrays(int A[],int m,int B[],int n){

    int total;  

    if(total &0x1)   return find_kth(A,m,B,n,total/2+1);

    else return (find_Kth(A,m,B,n,total/2)+find_Kth(A,m,b,n,total/2+1))/2.0;

}


21->删除数组中的重复数值,数组已经排序。例如【1,1,2】 返回数组长度为2,数组变为【1,2】

解题方法:比较简单不作具体分析。

int removeDuplicate(int A[],int n){

    if(n==0) return 0;

    int index=0;

    for(int i=1;i<n;i++){

        if(A[index]!=A[i])

             A[++index]=A[i];

    }

    return index+1;

}


22->删除已经排序的数组的两个以上的重复元素。例如【1,1,1,2,2,3】返回新的数组长度5,数组变成【1,1,2,2,3】

解题方法:与上一题类似

int removeDuplcates(int A[],int n){

    if(n<=2) return n;

    int index=2;

    for(int i=2;i<n;i++)

          if(A[i]!=A[index-2])  A[index++]=A[i];

    return index;

}


23->旋转数组的元素查找,s数组中没有重复元素

解题方法:当然不可以用顺序查找方式

int search(int A[],int n,int target){

    int low=0,high=n;

    while(low!=high){

        cont int mid=(low+high)/2;

        if(A[mid]==target)    return mid;

        if(A[low]<=A[mid]){if(A[low]<=target && target<A[mid])   high=mid;   else low=mid+1;}

        else{if(A[mid]<target && target<=A[high-1])   low=mid+1; else high=mid;}

    }

    return -1;

}


24->包含重复元素的旋转数组查找

解题方法:和上一题类似,就是当A[low]==A[mid]时不能确定该段的递增情况

bool search(int A[],int n,int target){

    int low=0,high=n;

    while(low!=high){

        const int mid=(low+high)/2;

        if(A[mid]==target)   return true;

        if(A[low]<A[mid]){if(A[low]<=target && target<A[mid]) high=mid; else low=mid+1;}

        else if(A[low]>A[mid]){if(A[mid]<target && target<=A[high]) low=mid+1; else high=mid;}

        else low++;

    }

    return false;

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值