算法题摘录三

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

1:判断给定数组是不是二叉搜索树的前/后序遍历序列

二叉搜索树的特点是:左子树的值<根结点值<右子树的值。而前/后序遍历序列的首/尾数是根结点,依据根结点可以把数组其余部分划分为左子树、右子树,然后根据左右子树序列又可以递归地确定父结点并划分子树......如果能递归遍历完整个数组则说明合法,否则非法。

下面是我实现的判断后序遍历序列的代码:

public boolean isSquenceOfBST(int[] nums,int start,int end){
      if(nums==null){
          return false;
      }
      //序列的end就是当前层的根结点
      int curr_root=end;
      //由根结点划分序列得到左子树的结束位
      int leftEnd=0;
      for (int i = start; i <end; i++) {
        if(nums[i+1]>nums[curr_root]){
            leftEnd=i;
            break;
        }
    }
      //由左子树结束位+1得到右子树开始位
      int rightStart=leftEnd+1;
      
      //如果左子树序列不为空,则递归左子树
      boolean isLeftOfBST=true;
      if(leftEnd>0){
       isLeftOfBST=isSquenceOfBST(nums, start, leftEnd);}
      
      //同理,先判断右子树序列是否存在,是则递归右子树
      //这里要注意右子树是rightStart到end-1!
      boolean isRightOfBST=true;
      if(rightStart<end-1){
           isRightOfBST=isSquenceOfBST(nums, rightStart, end-1);
      }     
      return isLeftOfBST&&isRightOfBST;
  }

 

2:给定一棵二叉树,判断给的序列是否对应二叉树的前/后/中序遍历序列

传统解法:先按前/后/中序遍历整个树,把每个结点的值保存到一个数组中,然后把结果数组与所给数组一一比较即可。这是由树求数组去判数组。

递归解法:比如我们判断后序遍历序列的合法性:我们从二叉树的根节点开始遍历,curr_root记录当时树的根节点。由curr_root与当前序列最后一个值比较判断当前层序列的合法性;然后由curr_root—>left值划分遍历序列得到左子树序列的最后一位leftEnd,由rightStart=leftEnd+1得到右子树序列的开始下标。然后递归左右子树,比较子树的根结点与所给序列的末尾值比较,最后返回左右子树的遍历结果即可。递归边界是:curr_root的子树为空。或者当前树的根不等于当前序列末位值。

 

3:打印二叉树中结点值和为给定值的所有路径

 从根节点开始处理每一个结点,把结点入栈,并统计当前路径耗散值。如果子节点非空,则递归处理子节点。当前结点为叶结点,则比较路径耗散值与给定和是否相等,是则打印栈中元素(路径),否则不打印;然后回溯:弹出当前结点。

Vector<MyTreeNode> path=new Vector<MyTreeNode>();//由于打印路径时需要从头打印,所以这里用Vector替代了stack。因为vector可以从头遍历,又包含了栈的抛出后入者的功能
    public void printPath(MyTreeNode currRoot,Vector<MyTreeNode> path,int currSum,int total){
        //判断第一个根结点是否为空树,是则直接返回
        if(currRoot==null){
            return;
        }
        //计算当前结点耗散值
        currSum+=currRoot.val;
        //如果当前结点是叶结点并且耗散值等于所求路径和,则打印这条路径
        if(currSum==total && currRoot.leftNode==null && currRoot.rightNode==null){
            for(int i=0;i<path.size();++i){
                System.out.print(path.get(i).val+",");
            }
        }
        //如果路径不是叶结点,并且左子结点存在,则递归左子结点
        if(currRoot.leftNode!=null){
            printPath(currRoot.leftNode, path, currSum, total);
        }
        //如果右子结点存在,递归右子结点
        if(currRoot.rightNode!=null){
            printPath(currRoot.rightNode, path, currSum, total);
        }
        //回溯:把路径上当前点删除
        path.remove(path.size()-1);
    }
 

4:复杂链表的复制

复杂链表是指:每个结点不仅有指向下一结点的指针,还有一个指向任意结点的指针。

传统解法:首先遍历一次原链表,用next指针把新链表建立起来;然后再从头到尾遍历原链表和新链表,把指向任意结点的指针一一赋值给新链表对应结点。复杂度O(n^2)

优化解法:既然一个结点对应一个任意指针,那么我们在第一次遍历原链表建立新链表时把每个结点对应的任意指针存到一个map中,然后再遍历新链表为每个结点通过map.get(当前结点)获取对应的任意指针进行赋值。

5:把一颗二叉搜索树转换成排好序的双向链表,要求只能改变树的指针,不能新建任何结点

由二叉搜索树的中序遍历是递增序列可知,要把BST转换成排好序的双向链表其实就是中序遍历BST并修改指针的过程。

观察可得:如果一个结点n的左右子树非空,那么n的前指针指向左子树排序后序列的尾结点,n的后指针执行右子树排列后序列的头结点。而对于n的左右子树序列的得出,毫无疑问就是递归。

6:求一个字符串所有字符的全排列

首先,求出第一个字符的所有可能情况:其实就是把字符串的第一个字符依次和后面的字符交换;

然后,在第一个字符确定的情况下,把第二个字符依次和后面的字符交换;其实就是递归:把从第二个字符起的字符串传进递归函数进行处理;

......直到递归到了每种情况下的字符串的最后一个字符,结束递归,输出此时的字符串。回溯:把当前步的交换复位。

public void Permutation(char[] chars,int begin){
        if(chars==null){
            return;
        }
        //如果以及递归到最后一个字符了,则得到一个排列情况,进行输出
        if(begin==chars.length-1){
            System.out.println(chars);
        }else{//否则,与后面的字符逐个交换,并在交换后修改begin位置进行递归
            for (int i = begin; i < chars.length; i++) {
                char temp=chars[i];
                chars[i]=chars[begin];
                chars[begin]=temp;
                Permutation(chars, begin+1);
                //回溯
                temp=chars[i];
                chars[i]=chars[begin];
                chars[begin]=temp;
            }
        }        
    }

7:找出数组中出现次数超过数组长度一半的元素

map解法:这种映射关系的题,第一时间想到map——遍历整个数组,把数字与其出现次数关联起来。然后把map中value值最大的key输出即可。

优化解法:我们可以设定一个活动开关:每当当前数与保存数相同,则开关值+1;否则开关值-1;检查开关值,如果开关值为0,则把当前数设置为保存数,并把开关值置1,统计新保存数的出现情况。由于出现次数超过数组一半,那么遍历完后开关值大于等于1时对应的保存数就是数组中出现次数大于一半的元素。

8:输入一个数组,找出最小的K个数

传统解法:排序,输出前K个数。复杂度O(nlogn)

优化解法:最大堆的使用。首先用输入的数组的前K个数建立其一个有K个结点的最大堆。然后每输入一个数,先与堆头比较,若小于堆头,则把堆头的值替换成新输入的值,然后对堆头进行下沉操作使结点处于合适位置从而更新堆头最大值。在数组输入完后,得到的K结点最大堆就是数组最小的K个数。

9:求连续子数组最大和

这种问题的解与前后步情况相关的问题,用动态规划来做:f(i)=Max(nums[i],f(i-1)+nums[i]),f(i)表示以第i个数字结尾的子数组最大和。

虽然动态规划常用递归的形式去描述,但是最终我们都会用循环来编码。

public int Max_Son(int[] nums,int n){
        //用一个结果数组存放以i结尾的子数组的最大连续和
        int[] res=new int[n];
        //初始化
        res[0]=nums[0];
        //用currMax保存最大连续和数组中的最大值
        int currMax=res[0];
        //用循环求以i结尾的最大连续和
        for(int i=1;i<n;++i){
            if(res[i-1]<=0){
                res[i]=nums[i];
            }else{
                res[i]=res[i-1]+nums[i];
            }
            //更新最大连续和数组中的最大值
            currMax=currMax>res[i]?currMax:res[i];
        }
        //返回最大的连续和
        return currMax;
    }

10:从1数到n,求数字1出现的次数(在任意位上)

传统解法:定义一个函数专门用于统计一个数n的各位上1的个数。然后循环遍历 i:1~n ,count+=SumOfOne(i),最后count就是所求。缺点是容易超时。

优化:找规律来求。

转载于:https://www.cnblogs.com/ygj0930/p/6429971.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值