• 算法题摘录一


    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6424179.html

    1:二维数组查数

    输入一个行、列均是递增的二维数组,和一个数。求该数是否在二维数组中。

    解法一:这种查找类题目,第一时间想到用map、list等。此处可以先对二维数组遍历,把  每个元素——i+“,”+j(行列坐标) 作为映射转存到Hashmap中,然后对所查的数用

    map.get(num)即可。

    解法二:由于行列都是递增的,我们就利用这个规律来解题。首先用num与右上角元素比:若相等,则返回true;若num大于这个元素,则说明num在这个元素的下方,而一行的最右元素是该行的最大值了,所以第一行排除掉了,数组行数减一,继续查找;若num小于右上角元素,则说明num在右上角元素的左边,而一列的第一个是该列的最大值,所以最右列排除,列数减一,继续查找......重复以上三步,每步要么减一行要么减一列缩小查找范围,直至找到或者遍历完整个数组。

    boolean find(int[][] nums,int num){
        boolean res=false;
        int rows=nums.length;
        int cols=nums[0].length;
        while(rows>=0 && clos>=0){
        if(nums[rows-1][cols-1]==num){
            res=true;
            break;
         }else if(nums[rows-1][cols-1]>num){
             cols--;
         }else{
             --rows;
          }
         }
       return res;     
    }

    2.替换字符串中空格(或其他东西)

    此题,要求把字符串中的空格替换成别的东西,比如 %20 这样的字符。

    法一:无空间限制,不在原字符串上操作的解法。

    用Java来做的话,我们可以新开一个字符串数组,用split(str," ")把原字符串按空格拆分,保存到字符串数组中。然后遍历这个数组,用一个builder来append个个字符串,两两之间插入 %20 取代空格即可。这里要注意考虑特殊情况:原字符串开头、结尾有无空格。可以设置headnull,lastnull两个变量来先统计原字符串头尾的空格数。

    法二:有空间限制,只能在原字符串上操作。

    设计到内存方面的操作,此法用C++来做。首先遍历原字符串,统计空格数目count;然后计算出取代内容需要多少空间,得出替换后的总空间;p2指针指向替换后字符串末尾,p1指向替换前字符串末尾,p1每遍历一个字符,就移动到p2处,然后p1,p2前移动;若p1遍历到空格,则p2从后往前依次写入0,2,%(每写一个p2前一1位)......直到p1==p2==字符串首地址。

    3:倒序输出链表值

    链表一般都是顺序遍历比较容易,这里需要倒序输出链表值。如果真的把链表指针反转方向重新构造链表就真是太死脑筋了。我们只需顺序遍历链表,把每个结点的值先存放在数组或栈中。然后再按照倒序输出即可。这种 后进先出  的典型题目应该第一时间就想到栈。

    4:由前序遍历,中序遍历重建二叉树

    首先重温二叉树的遍历方式:

    前序遍历:根左右

    中序遍历:左根右

    后序遍历:左右根

     由上面规律可得,知道前序遍历,便可以知道根节点的值,由根节点的值可以划分中序遍历得到左右子树,而左右子树的前序遍历的第一位又是子树的根......依次往复,相互求取即可重建二叉树。所以,用递归。

    public TreeNode rebuidTree(int[] pre,int[] in,int preStart,int preEnd,int inStart,int inEnd){
           //由前序遍历第一个数值建立当前结点
           TreeNode newNode=new TreeNode(pre[preStart]);
           //递归边界判断:如果前序遍历序列开始等于结束说明到了叶子层了,直接返回这个结点作为叶子
           if(preStart==preEnd && inStart==inEnd){
               return newNode;
           }
           //未到底层,则递归。
           int currRoot = -1;
           //先求出当前的前序遍历序列的根结点在中序遍历序列中的位置
           for(int i=inStart;i<=inEnd;++i){
               if(in[i]==pre[preStart]){
                   currRoot=i;
                   break;
               }
           }
           //从而划分中序遍历得当前结点的左右子树。并求出左右子树序列的长度
           int leftLength=currRoot-inStart;
           int rightLength=inEnd-currRoot;
           //如果左子树序列不为0,则递归建立左子树
           if(leftLength>0){
               //序列仍然是一开始的序列,子树的序列是通过下标来控制的
           newNode.left=rebuidTree(pre, in, preStart+1, preStart+leftLength, inStart, currRoot-1);
           }
           //右子树序列不为0,递归建立右子树
           if(rightLength>0)
           {
           newNode.right=rebuidTree(pre, in, preStart+leftLength+1, preEnd, currRoot+1, inEnd);
           }
           //把当前结点返回上一层作为上一层的子节点
           return newNode;
       }

    后序遍历+中序遍历重建二叉树同样解法,只是每次由后序遍历的最后一个元素得当前根结点。

    5:用两个栈实现一个队列,要求完成“先入先出”功能

    由于栈是“后入先出”的,要实现先入先出就需要 后入先出再后入先出。我们来模拟一下两个栈的工作情景:

    增加元素,则入栈s1;

    删除第一个入的元素:则把s1中元素全部pop()出,依次存入s2。此时,s2中元素的顺序与原来在s1时相反,栈顶的元素是先入s1的元素。此时把s2栈顶元素pop()出,即模拟了队列的先入先出。而如果期间有增加的话,入s1栈,因为s2中是比s1的早入栈的,所以执行删除的话还是s2弹出。直到s2为空,才从s1依次弹出再压入s2。

    6:旋转数组的最小值

    把一个递增数组的前若干个元素挪到数组的末尾,得到旋转数组:1,2,3,4,5——3,4,5,1,2。给定一个选择数组,求数组中最小值。

    法一:暴力搜索,用min记录当前最小值直至遍历结束得到数组最小值。

    法二:用Java的Arrays.sort(int[])把数组排序,第一个元素就是。

    法三:真正底层的解法——双指针(双下标夹迫法)。用index1指向数组第一个元素,index2指向最后一个,midindex=(index1+index2)/2。若nums[index1]<nums[midindex],说明nums[midindex]位于前面的递增数组中,则最小值在minindex~index2之间,令index1=midindex,重复以上步骤;如果nums[index1]>nums[midindex],说明nums[index]在后面的递增数组中,最小值位于index1~midindex之间,令index2=midindex,重复以上步骤;如果index1+1=index2了,说明index1与index2以及迫近到最小值以及其相邻值了,取两下标所指最小的就是最小值。

    7:优化斐波那契数列解法

    我们知道,传统的递归实现斐波那契数列容易出现的一个问题就是重复求值,当n越大重复的次数越多。

    优化方法一:传统解法的问题是重复求值,那我们不让他重复求就行了——递归过程中求出的f(n)存入数组中,在每次递归时,先从数组中取f(n),有则返回,无才递归求值,并且在得到返回值后把f(n)存如数组以供下一次递归到这个值时直接取用。

    优化方法二:我们知道斐波那契数列是f(n)=f(n-1)+f(n-2),而f(0)=0,f(1)=1。那么对于f(n),我们完全可以从0,1开始逐步直到f(n-1)+f(n-2)从而得到f(n)。

    public int betterFibonacci(int n){
           int res=0;
           if(n==0){
               res=0;
               return res;
           }
           if(n==1){
               res=1;
               return res;
           }
           int n_2=0;
           int n_1=1;
           for(int i=2;i<=n;++i){
               res=n_1+n_2;//当前项等于前两项之和       
               //更新位置,当前项作为下一N的前一项
               n_2=n_1;
               n_1=res;           
           }
           return res;
       }

    8:统计二进制数中1的个数

    传统解法:每次用最低位&1判断最低位是不是1,是则count++,然后右移。这种解法只能在整数是正整数时可用。如果是负整数,右移的时候左边补1,到后面会导致全部是1而导致死循环。

    优化解法:传统解法引起死循环的原因是负数右移的时候左边补1。那我们不让他右移就好了——我们把 1 逐位左移与所求二进制数各位取&来求1的个数。

    public int count_1(int n){
           int count=0;
           int index_of_1=1;
              //32位机的就循环32次
           for(int i=1;i<=32;i++){
               if(index_of_1 & n){
                   count++;
               }
               index_of_1<<=1;
           }
           return count;
       }

    最优解法:n&(n-1)等同于消去n的二进制数的最右边的1。

    如果一个数的二进制的最低位是1,那么n-1就是把二进制数最低位取反为0,前面的不变;

    如果最低位是0,那么n-1就是n的二进制数从次低位开始借1,直到借到1,被借的位1变0,然后从被借位之后的全部取反。

    那么我们可以看到:n与n-1的二进制数的区别在于:从被借位(包括被借位)开始往后的全是相反的。那么n&(n-1)的话就是把n的二进制数的最后一个1(包括这个1)往后的位全部与自身的非做与运算,结果就是n的二进制数的最后一个1以及其后的位全部取0,而后面的位本就是0,那么真正的变化只是最低位的1变成了0而已。也就是说,执行一次n&(n-1)就相当于把n的最右边的1消去。所以统计n的二进制表示的1的个数,只需统计n&(n-1)这个操作做了几次即可。

    public int best_count_1(int n){
           int count=0;
           while(n!=0){
               n=n&(n-1);
               ++count;
           }
           return count;
       }

    9:求一个数的n次方

    传统解法:按最原始的方法做n次乘法。

    优化解法:类似于上面优化斐波那契数列的做法,把已经求过的方幂保存起来,在后面用到的时候直接取方幂即可,不用再重复做乘法运算。

    递归解法:如果是偶次方n,我们可以看到:num^n=num^(n/2)*num(n/2),也就是说可以直接递归求  num^(n/(2^x)),x=1,2...   即可。如果n是奇数,也相当于num*num^(n-1)而已,同样可以用递归来求。

    double pow(double num,int n){
           if(n==0){
               return 1;
           }
           if(n==1){
               return num;
           }
           
           //用n>>1作为递归的指数。相当于n/2,但右移比除法快.
           double half_pow=pow(num, n>>1);
           
           double res=half_pow*half_pow;
           //此时对n次方作奇偶判断:如果为奇数,还需要再乘以一次num才是结果
           //这里用位运算来判断:直接看最后一位是不是1即可直到奇偶。这比n%2==1快4倍
           if((n & 1)==1){
               res*=num;
           }
           return res;
       }

    10:打印1到n位最大数

    传统解法:n位最大数就是10^n-1。比如3位最大数就是10^3-1=999。for循环1~(10^n-1)打印即可。问题在于:n很大时,int或者long都存放不了这么大的数,会溢出。

    优化解法:题目要求的是打印出数而不是求出这些数,我们可以用字符串来模拟出每一个数。我们知道,n位十进制数其实就是n长的0~9序列的全排列而已。我们用代码模拟出各位0~9出现的情况即可。至于前导0在输出时不打印即可。

    public void Print_1_to_MaxOfN(int n){
            if(n<=0){
                return;
            }
            //定义一个n位的char数组来模拟n位数的全排列
            char[] numbers=new char[n];
            
            //最高位的的可能情况:0~9
            for(int i=0;i<10;++i){
                numbers[0]=(char) (i+'0');
                //最高位已定,告知下一位当前位curr_index已确定,递归地模拟下一位的可能情况
                Print_Ditgit_Recursively(numbers,n,0);
            }
        }
        public void Print_Ditgit_Recursively(char[] numbers,int length,int curr_index){
            if(curr_index==length-1){//index从0开始,所以length-1就是最末位了
                //当前位就是最后一位了,则可以对numbers进行输出了
                PrintNumbers(numbers);
                return;
            }
            //若还不是最后一位,则对下一位进行确定,并递归下去
            for(int i=0;i<10;++i){
                numbers[curr_index+1]=(char) (i+'0');
                Print_Ditgit_Recursively(numbers, length, curr_index+1);
            }
        }
        public void PrintNumbers(char[] numbers){
            boolean isStart=false;
            for(char ch:numbers){
                //前导零不打印,直到遇到非0的即开始
                if(!isStart && ch!='0'){
                    isStart=true;
                }
                if(isStart){
                    System.out.print(ch);
                    System.out.print("	");
                }
                
            }
        }

    这里注意: numbers[curr_index+1]=(char) (i+'0');这种要对数组的下一个坐标赋值的情况,不能贪方便用 nums[++curr_index],这样的话for循环的条件要修改成i<length-2才行。如果是i<length-1,则最好一个是++curr_index就越界了。切记切记

     
  • 相关阅读:
    MySQL 对于千万级的大表要怎么优化?
    Spring Cloud中文社区
    什么是QPS,PV
    http://www.rabbitmq.com/documentation.html
    redis
    MySQL分区表
    linux命令综合
    Python-MRO
    Python3 错误和异常
    装饰器
  • 原文地址:https://www.cnblogs.com/ygj0930/p/6424179.html
Copyright © 2020-2023  润新知