Skip to content

maoqingcode/algorsPractise

 
 

Repository files navigation

一 二叉树

一 基本二叉树
1 二叉树 前序和中序的非递归便利
    public static void preOrderUnRecur(Node head) {
        //1先将头结点压入栈
        //2 弹出栈顶元素,再将该节点的左右节点压入栈(先右后左)
        //3 不断重复2 ,直到stack为空
        if (head == null)
            return;
        Stack<Node> stack = new Stack<>();
        stack.push(head);
        while (!stack.isEmpty()) {
            Node cur = stack.pop();
            System.out.println(cur.data + " ");
            if (cur.right != null) {
                stack.push(cur.right);
            }
            if (cur.left != null) {
                stack.push(cur.left);
            }

        }
    }
  public static void midOrderUnrecur(Node head) {
        //1 申请一个栈 cur=head
        // 2 将栈左子树依次压入 cur=cur.left
        // 不断重复2,直到cur==null ,弹出node 并打印,并cur=node.right ,继续重复
        if (head == null)
            return;
        Stack<Node> stack = new Stack<>();
        stack.push(head);
        while (!stack.isEmpty() || head != null) {
            if (head!= null) {
                stack.push(head);
                head = head.left;
            }else{
                Node node=stack.pop();
                System.out.println(node.data);
                head=node.right;
            }

        }
    }

使用递归方式 不理解

 
    TreeNode prev =null;// 中序遍历这棵树,并保存前驱节点至prev中
    //使用递归
    public boolean isValidBST(TreeNode root){
        if(root != null){
            if(!isValidBST(root.left))
                return false;
            if(prev!=null && root.val<=prev.val){
                return false;
             }
            prev = root;
            return isValidBST(root.right);
        }
        return true;
    }
2 二叉树的序列化和反序列化 again

1 使用递归方式

2 使用层序遍历方式 层序遍历使用了队列

  /*
     *@description:将二叉树转换为字符串
     * # 表示节点为null ,每个节点以 '!' 分割
     *@params:[head]
     *@return:java.lang.String
     *@author:Mao
     *@date:2019/4/12 8:46
     **/
    public static String seriaByPre(Node head) {
        if (head == null)
            return "#!";
        String res = head.data + "!";
        res += seriaByPre(head.left);
        res += seriaByPre(head.right);
        return res;
    }

    /*
     *@description:反序列化 将str转换为二叉树
     *@params:[str]
     *@return:com.mao.tree.Node
     *@author:Mao
     *@date:2019/4/12 8:53
     **/
    public static Node reconByPre(String str) {
        if (str == null)
            return null;
        //将str 按照'!'分割成数组
        String[] strs = str.split("!");
        Queue<String> queue = new LinkedList<>();
        for (int i = 0; i < strs.length; i++) {
            queue.offer(strs[i]);
        }
        return reconByPreOrder(queue);
    }

    /*
     *@description:前序递归
     *@params:[strs]
     *@return:com.mao.tree.Node
     *@author:Mao
     *@date:2019/4/12 8:59
     **/
    private static Node reconByPreOrder(Queue<String> queue) {
        String value = queue.poll();
        if ("#".equals(value)) {
            return null;
        }
        Node node = new Node(Integer.valueOf(value));
        // 将值与节点的左右子树相连接
        node.left = reconByPreOrder(queue);
        node.right = reconByPreOrder(queue);
        return node;
    }

  //层序遍历
    public static String seriaByLevel(Node node) {
        if (node == null)
            return "#!";
        String res = node.data + "!";
        Queue<Node> queue = new LinkedList<Node>();
        queue.offer(node);
        while (!queue.isEmpty()) {
            Node head = queue.poll();
            if (head.left != null) {
                res += head.left.data + "!";
                queue.offer(head.left);
            } else {
                res += "#!";
            }
            if (head.right != null) {
                res += head.right.data + "!";
                queue.offer(head.right);
            } else {
                res += "#!";
            }
        }
        return res;
    }

    /*
     *@description:层序遍历反序列化
     *@params:[args]
     *@return:void
     *@author:Mao
     *@date:2019/4/12 9:55
     **/
    public static Node reconByLevel(String str) {
        if (str == null)
            return null;
        String[] strs = str.split("!");
        int index = 0;
        Node head = generatedNode(strs[index++]);
        Queue<Node> queue = new LinkedList<>();
        queue.offer(head);
        Node node = null;
        while (!queue.isEmpty()&& index!=strs.length) {
            node = queue.poll();
            node.left = generatedNode(strs[index++]);
            node.right = generatedNode(strs[index++]);
            if (node.left != null) {
                queue.offer(node.left);
            }
            if (node.right != null) {
                queue.offer(node.right);
            }

        }
        return head;
    }

    private static Node generatedNode(String str) {
        if ("#".equals(str)) {
            return null;
        }
        return new Node(Integer.valueOf(str));
    }
3 判断t1树是否全部包括t2树的全部拓扑结构 agiain

注意 :需检查是否为null

1 先进行 t1树的前序遍历

2 在与t1 和t2 的每个节点进行比较

   public static boolean contains(Node t1,Node t2){
		//需检查t1 是否为null 为null则停止
        if(t1==null){
            return false;
        }
        return check(t1,t2)|| contains(t1.left,t2)|| contains(t1.right,t2);
    }

    private static boolean check(Node tt1, Node tt2) {
        if(tt2==null){
            return true;
        }
        if(tt1==null || tt2.data!=tt1.data){
            return false;
        }
        return check(tt1.left,tt2.left) && check(tt1.right,tt2.right);
    }
二、平衡二叉树

  • 难度一星
1 通过有序数组生成平衡二叉树

采用递归方式实现

	public Node generateTree(int[] sortArr) {
		if (sortArr == null)
			return null;
		return generate(sortArr, 0, sortArr.length - 1);
	}

	private Node generate(int[] sortArr, int start, int end) {
		if (start > end)
			return null;
		int mid = (start + end) / 2;
		Node head = new Node(sortArr[mid]);
		head.left = generate(sortArr, start, mid - 1);
		head.right = generate(sortArr, mid + 1, end);

		return head;
	}
2 在二叉树中找到一个节点的后继节点
  • 思路一:
  • 时间复杂度O(n) 空间复杂度 O(n)
  • 1 先找到根结点
  • 2 根据根节点就行中序遍历
  • 3 遍历结果即为节点的后一个节点
3 二叉树节点间的最大距离问题
  • 找到两个节点最近的祖先
  • 思路:
  • 采用后序遍历
  • 1 如果cur 节点下 左右子树,均无法找到o1 或o2,则返回null
  • 2 若cur 节点,左右子树能找到,则返回cur
  • 3 如果有一个子树返回为null,另一个子树找到,假设不为空的记为node,则存在两种情况
  • 1)则node该节点是O1 或 O2
  • 2)node是最近的公共祖先
  • 例如遍历节点1 ,发现node节点为3 则 3是o1和o2的父节点
4 先序与中序结合构成重构二叉树 +
  • 时间复杂度: O(n) p172
  • 在中序数组找位置i过程可以用hash表来实现,复杂度为O(n)
  • 思路:
  • 1 先序数组最左边为树的头结点h,中序找到h,假设位置i。则中序
  • 数组中,i左边数组就是的头结点左子树的中序数组,长度L,则左子树
  • 的先序数组就是先序数组的h+L
  • 2 用左子树先序和中序,递归整个过程建立左子树left
  • 3 用右子树的的先序和中序数组,递归右子树right
  • 4 把head左右孩子设为lef 和 right
   public Node preInToTree(int[] preArr,int[] midArr){
        if(preArr==null || midArr==null){
            return null;
        }
        //使用HashMap将midArr数组存储
        HashMap<Integer,Integer> map=new HashMap<>();
        for (int i=0;i<midArr.length;i++){
            map.put(midArr[i],i);
        }
        return  preTree(preArr,0,preArr.length-1,midArr,0,midArr.length-1,map);
    }
    private Node preTree(int[] preArr, int preStart, int preEnd, int[] midArr, int midStart, int midEnd, Map<Integer,
                Integer> map) {
            if (preStart> preEnd){
                return null;
            }
            Node head=new Node(preArr[preStart]);
            int index=map.get(preArr[preStart]);
            head.left=preTree(preArr,preStart+1,preStart+midEnd-index,midArr,midStart,index-1,map);
            head.right=preTree(preArr,preStart+index-midStart+1,midEnd,midArr,index+1,midEnd,map);
            return head;
    }
4.2 中序与后序构成二叉树
  public Node inPosToTree(int[] mid,int[] pos){
        if(mid==null || pos==null){
            return null;
        }
        HashMap<Integer,Integer> map=new HashMap<>();
        for (int i=0;i<mid.length;i++){
            map.put(mid[i],i);
        }
        return posToTree(pos,0,pos.length-1,mid,0,mid.length-1,map);
    }

    private Node posToTree(int[] pos, int posStart, int posEnd, int[] mid, int midStart, int midEnd,
                           HashMap<Integer, Integer> map) {
        if(posStart>posEnd){
            return null;
        }
        int arr=pos[posEnd];
        int index=map.get(arr);
        Node node=new Node(arr);
        node.left=posToTree(pos,posStart,posStart+index-midStart-1,mid,midStart,index-1,map);
        node.right=posToTree(pos,posStart+index-midStart,posEnd-1,mid,index+1,midEnd,map);
        return node;
    }
    public static void main(String[] args) {
        int[] pos=new int[]{4,5,2,6,7,3,1};
        int[] in=new int[]{4,2,5,1,6,3,7};
        InPosToTree test=new InPosToTree();
        Node head=test.inPosToTree(in,pos);
        test.pre(head);
    }
    public void pre(Node head){
        if (head==null)
            return;
        System.out.print(head.data);
        pre(head.left);
        pre(head.right);
    }
5 中序与后序结合构成的二叉树 +1
  • 时间复杂度: O(n)

  • 思路:

  • 1 后序数组的最右边为树的头结点h,中序找到h,假设位置为i。

  • 则中序数组中,i 左边数组就是头结点左子树的中序数组,长度为L。

  • 则左子树的后序数组就是后序数组的h-L

  • 2 用左子树的后序和中序,递归建立左子树left

  • 3 用右子树的后序和中序,递归建立右子树right

  • 4 把head和左右子树设为left和right

    public class GetPosArray {
        // 根据先序和中序 设置后序数组的最右边的值,
        // 然后根据先序和中序的右子树设置后序数组,
        // 左子树同样
        public int[] getPostArray(int[] pre,int[] mid){
            if(pre==null||mid==null){
                return null;
            }
            int len=pre.length;
            int[] pos=new int[len];
            HashMap<Integer,Integer> map=new HashMap<>();
            for (int i=0;i<mid.length;i++){
                map.put(mid[i],i);
            }
            setPos(pre,0,pre.length-1,mid,0,mid.length-1,pos,pos.length-1,map);
            return pos;
        }
        //从右往左依次填写数组pos
        //posEnd为后序数组该填的位置
        //返回值为该填的位置
        private int setPos(int[] pre, int preStart, int preEnd, int[] mid, int midStart, int midEnd, int[] pos,
                             int posEnd, HashMap<Integer, Integer> map) {
    
            if(preStart>preEnd){
                return posEnd;
            }
            pos[posEnd--]=pre[preStart];
            int index=map.get(pre[preStart]);
            posEnd= setPos(pre,preStart+index-midStart+1,preEnd,mid,index+1,midEnd,pos,posEnd,map);
            return setPos(pre,preStart+1,preStart+index-midStart,mid,midStart,index-1,pos,posEnd,map);
        }
    
        public static void main(String[] args) {
    
            int[] pre=new int[]{1,2,4,5,3,6,7};
            int[] in=new int[]{4,2,5,1,6,3,7};
            GetPosArray arr=new GetPosArray();
            int[] pos=arr.getPostArray(pre,in);
            for (int i=0;i<pos.length;i++){
                System.out.println(pos[i]);
            }
        }
    }
6 通过先序和中序数组生成后序数组

要求:不要重建整个树,直接生成后序数组

  • ​ 1 根据当前的先序和中序,设置后序数组的最右边值
  • ​ 2 划分出左子树的先序、中序数组;
  • ​ 右子树的先序、中序数组。
  • ​ 3 根据右子树划分设置好后序数组,再根据左子树划分
7 判断是否为二叉树 +1

判断是否为平衡二叉树 左右子树高度差不大于1

//boolean res 相当于全局变量
/* 当不满足时立刻返回
	if(!res[0])
	{
      return level;
    }
  */

public static boolean isBanlance(Node head){
        boolean[] res=new boolean[1];
        res[0]=true;
        System.out.println("height:"+getHeight(head,1,res));
        return res[0];
    }

    private static int getHeight(Node head, int level, boolean[] res) {
        if(head==null)
            return level;
       int lh= getHeight(head.left,level+1,res);
        if(!res[0]){
            return level;
        }
        int rh=getHeight(head.right,level+1,res);
        if(!res[0])
            return level;
        if(Math.abs(lh-rh)>1){
            res[0]=false;
        }
        return Math.max(lh,rh);
    }

    public static void main(String[] args) {
        String str = "1!2!#!4!#!#!#!";
        Node head = reconByLevel(str);
        System.out.println( isBanlance(head));
    }
8 根据后序数组判断是否为二叉树搜索树
  • 根据数组 判断是否为二叉搜索树

    **性质:**依据二叉树后序遍历性质 头结点一定是数组的最后一个元素

    根据平衡二叉树的性质 比头结点大的数组一定在二叉树的右边,

    比头结点小的在二叉树的左边

    思路:

    1 声明less 和 more 表示 左右子树的右边界 和 左边界

    2 从start 到 end 开始遍历 找到less 和more

    3 如果 less==-1 或者 more==end 说明 只存在左子树或右子树

    4 若 less==more-1 说明当前左右子树符号条件 分别判断左子树和右子树内部是否满足

    5 终止条件 当 起始位置和终止位置 相等时 则证明符号条件

public class IsPostArray {
    public boolean isPostArray(int[] arrs){
        if(arrs==null ||arrs.length==0)
            return false;
        return  isPost( arrs, 0 , arrs.length-1);
    }
    private boolean isPost(int[] arrs, int start, int end) {

        // 整个递归的终止条件
        if(start==end){
            return true;
        }
        int less=-1; //找到左子树右边界临界点
        int more=end; //找到右子树最左边
        //找到左子树的右边界,右子树的左边界
        for(int i=start;i<end;i++){
            if(arrs[end]>arrs[i]){
                less=i;
            }else {
                //找到第一个比根节点大的索引
                more=more==end?i:more;
            }
        }
        //只存在左子树或只存在右子树
        if(less==-1 || more==end){
            return isPost(arrs,start,end-1);
        }
        //若左边界和右边界-1 相等 则继续遍历,不等直接返回false
        if(less!=more-1){
            return false;
        }
        //进行左子树和右子树的遍历
        return isPost(arrs,start,less)&& isPost(arrs,more,end-1);
    }
}
9 根据后序数组重构二叉树
    public Node postArrayToBST(int[] arrs){
        if(arrs==null)
            return null;
        return  posToBST(arrs,0,arrs.length-1);
    }
    private Node posToBST(int[] arrs, int start, int end) {
            if(start>end)
                return null;
            int less=-1;
            int more=end;
            Node node=new Node(arrs[end]);
            for (int i=0;i<arrs.length;i++){
                if(arrs[end]>arrs[i]){
                    less=i;
                }else {
                    more=more==end?i:more;
                }
            }
            node.left=posToBST(arrs,start,less);
            node.right=posToBST(arrs,more,end);
            return node;
    }
10 判断一个二叉树是否为完全二叉树

完全二叉树性质 每层节点都得填满 ,运行最后一层右节点为null

思路:

  • 1 按照层序遍历

  • 2 若当前节点有右子树,无左子树 则 返回false

  • 3 若当前节点并不是左右子树都有(有左子树无右子树, 均为左右子树),则后续节点必须为叶子节点

    public boolean isCBT(Node head){
          if(head==null)
              return false;
          Queue<Node> queue=new LinkedList<>();
  
          boolean leaf=true;
          Node left=null;
          Node right=null;
  
          queue.offer(head);
          while(!queue.isEmpty()){
              head=queue.poll();
              left=head.left;
              right=head.right;
              
              if(leaf &&(left!=null || right!=null) || (left==null&&right!=null)){
                  return false;
              }
              if(left!=null){
                  queue.offer(left);
              }
              if(right!=null){
                  queue.offer(right);
              }else{
                  leaf=true;
              }
          }
          return true;
      }
11 判断一个二叉树是否为二叉搜索树

leetcode 98

Leetcode 参考资料

思路:进行中序遍历 则搜索二叉树节点值为升序

注意: 搜索二叉树的性质 BST

​ 非递归的中序遍历

 public boolean isValidBST(TreeNode root) {
          Stack<TreeNode> stack=new Stack();
        double inOrder=-Double.MAX_VALUE;
        while(!stack.isEmpty() || root!=null){
            while(root!=null){
                stack.push(root);
                root=root.left;
            }
            root=stack.pop();
            if(root.val<=inOrder){
                return false;
            }
            inOrder=root.val;
            root=root.right;
        }
        return true;
    }
public boolean isValidBST(TreeNode root) {
          Stack<TreeNode> stack=new Stack();
        double inOrder=-Double.MAX_VALUE;
        while(!stack.isEmpty() || root!=null){
           if(root!=null){
                stack.push(root);
                root=root.left;
            }else{
            root=stack.pop();
            if(root.val<=inOrder){
                return false;
            }
            inOrder=root.val;
            root=root.right;
           }
        }
        return true;
    }
12 通过有序数组生成平衡搜索二叉树

思路 : 数组的中间数为二叉树的头结点 ,左侧 为左子树 右侧为右子树

public class GenerateBSTByOrderArr {
    public static Node generateBST(int[] arr){
        if(arr==null){
            return  null;
        }
        return  getBst(arr,0,arr.length-1);   
    }
    /*
    *@description: 使用递归生成平衡搜索二叉树
    *@params:[arr, i, i1]
    *@return:com.mao.tree.Node
    *@author:Mao
    *@date:2019/4/23 8:31
    **/
    private static Node getBst(int[] arr, int start, int end) {
        if(start> end){
            return null;
        }
        int mid=(start+end)/2;
        Node node=new Node(arr[mid]);
        node.left=getBst(arr,start,mid-1);
        node.right=getBst(arr,mid+1,end);
        return node;
    }
13 找到两个节点的最近公共祖先节点 +

不懂

使用后序遍历

      //使用后序遍历
        /*
            1
          /  \
         2     3
       / \    / \
      4   5  6   7
                /
               8
         */
    public Node lowestAncestors(Node head,Node o1,Node o2){
        if(head==null || head==o1 || head==o2){
            return head;
        }
        Node left=lowestAncestors(head.left,o1,o2);
        Node right=lowestAncestors(head.right,o1,o2);
        if(left!=null && right!=null){
            return head;
        }
        //若其中有一个为null 不为null记为node 
        //说明node要么是o1 要么是o2 
        //或者 是o1 和o2的最近祖先
        return left!=null?left:right;
    }

二 字符串

1 判断两个字符是否互为变形

若两个字符串str1和str2,如果str1和str2出现字符种类一样且每种字符出现次数一样,

则str1和str2互为变形词。

如 str1=123 str2=132 返回true

str1=123 str2=1223 返回false

思路:

  • 字符串转化为char[] ,使用 toCharArray();
  • 假设字符串编码在 0到255范围内,用来统计每个字符出现的次数,记为map
  • 通过str1确定出现次数
  • str2来减少map内字符出现次数,若出现小于0,则不匹配
2 给定一个字符串str,求全部数字串所代表的数字之和

​ 要求:1 忽略小数点

​ 2 若左侧出现‘-’,当连续为奇数时,则数字为负,相反则为偶数

例:str="ab-----1111223a------1111c34"

思路要点:

  • 1 采用3个变量 res 结果 num 当前数字 isN 是否为正负,cur表示当前值
  • 2 连续数字 num=num*10+( isN ? cur : -cur)
  • 3 如何判断正负 ,判断 “-” 之前的是否为 “-”
3 去掉字符串中连续出现k个0的子串如

如 str=“AB00DF” k=2 str="ABDF"

注意:

1 记录首次出现位置 start=start==-1 ? i : start

​ 若为-1 ,表示重新开始记录0出现的首次位置

​ 若不为-1,则一直保持首次出现位置

2 ASCII NULL值 是 0

思路要点:

  • 记录0出现的次数,以及首次出现位置
  • 当当前字符非0时,检查0出现的次数是否满足
  • 字符串最后一个未检查,需遍历完后检查最后是否满足
4 判断两个字符串是否互为旋转词
  • 将两个a拼接起来成A,则A的子串就包含旋转词
  • 复杂度若为O(n), 则需要kmp算法
  • 使用 jdk 自带的 str.indexOf() , 返回某个指定的字符串值在字符串中首次出现的位置,不是则返回-1
5 替换字符串中连续出现的指定字符串

给定str form to三个字符串,把str中所有from的自串全部替换成to字符串,

对连续出现的 from部分要求只替换成一个to字符串,返回最终的结果。

思路:

  • 1 将str包含to中的替换为ascii中的0值
  • 2 将非0的字符串开始合并

具体过程:

1 遍历,判断str的位置和chaf位置是否相等

​ 1.1 若相等,继续遍历from后继位置,判断from的长度与匹配长度一致

​ 1.1.1 若长度相等则替换为0

​ 1.2 若字符不配,则更新match,重新匹配

2 替换后的str进行转换,拼接

​ 2.1 非0直接拼接

​ 2.2 若为0,则且前一个位置为非0

5.1 补充问题

给定字符串统计字符串,以及一个整数index,返回cstr的原始字符串上的第index个字符

如 a_1_b_100 第0个位啊 第50位b。

tip:

1 获得连续出现字母数量 num = num * 10 + strs[i] - '0'

2 切换连续字母状态 stage=!stage

3 最后一个需单独计算

6 判断字符数组中是否所有字符都只出现过一次

使用hashSet 或者数组 boolean[] iscontain=new boolean[255];

7 字符串的调整与替换

char[] 数组中的空格字符,替换为%20

1 统计非空格字符和字符长度

2 从后往前遍历,将空格字符替换为 “%20”

8 反转字符串

时间复杂度: 0(N) 空间复杂度o(1)

举例:dog love pig 反转 pig love dog

思路:先将字符整体反转,然后将每个单词反转

提示:

start = i == 0 || chars[i - 1] == ' ' ? i : start;
end = i == chars.length - 1 || chars[i + 1] == ' ' ? i : end;
9 反转字符串二

​ 给定一个字符串类型的数组chars 和一个整数size,请把大小为size的半区整体移到右边区,右半区整体移到左边

思路:先反转部分,在整体反转

10 括号字符串的有效性和最长有效长度

题目:给定一个字符串str,判断是不是有整体有效的括号字符串

思路:1 从左到右依次遍历字符串,判断每一个字符是不是‘(’ 或‘)’,不是直接返回false

​ 2 依次遍历每一个,检查'(' 和 ')' 的数量,如果‘)’更多则直接返回

​ 3 遍历后检查检查'(' 和 ')' 的数量

思路2: 使用动态规划

11 找到被指的新字符类型

​ ASCII 大写字母 65-90

​ 小写字母 97-122

题目: 指出新的字符, 小写字母 一个 大写字母 两个 包括 AA Ab型

思路1:从前遍历,遍历时,划分

思路2 :从k位置向前遍历,并记录大写字母个数num,直到遇到小写字母停止

  •  若num为奇数,则为 k-1,k
    
  •  若num为偶数,且k为大写字母,则 (K,K+1)
    
  •  且K为小写字母,则为 k
    

三 大数据和空间限制

Hash函数概念介绍:(4个性质)

位运算

1 不用任何额外变量交换两个整数的值?

如果给定整数a和b,则可以如下设置:

a=a^b; 记录了a和b整数位信息的所有不同信息

b=a^b;

a=a^b;

2 不用任何比较判断找出两个数中较大的数(四星难度)

题目:给定两个32位整数a和b,返回a和b中较大的

要求:不要做任何比较判断

四 数组

1 转圈打印矩阵

思路:直到起点 (tl,tr) 和终点 (dl,dr) tl++ tr++ dl-- dr--

2 将正方形顺时转到90度

矩阵颠倒

3 矩阵之字形打印

保证对角线打印 分为上下两个坐标,分别移动 p335

4 需要排序的最短子数组
  • 需要排序的最短子数组长度
  • 从右向左 找出最小的位置minIndex
  • 从左向右 记录最大值,以及maxIndex
  • 范围为(minIndex,maxIndex)
5 在行列都排好序的矩阵中找数
  • 思路:从右上角开始找 目标k
  • 若当前值小于K 则行row+1
  • 若当前值大于K,则列col-1
6 子数组累加和

arr=[1,-2,3,5,-2,6,-1] ,则 [3,5,-2,6]累加最大值为12

累加称为负数清零,重新累加

7 不包含本位置的累乘数组
  • 思路一:
  • 使用除法,重点在零的个数
  • 当非零,则除当前数
  • 当一个零,则,非零位置,均为零,零位置为其他乘积
  • 当大于一个零,则均为零

思路二:

不使用除法,分为左右两个组累乘 lr[] 和rl[]

若不能申请额外空间,则复用res[]

8 数组的partiotion调整(重点 以经第二遍)P382

资料

分为左区域无重复区域 右侧重复区域

当当前值不等于左侧区域最后一个数时,将当前值放大左侧区域

还有很多变种问题

如: 给定一个数组arr,其中包含0,1,2 实现arr排序

有一个数组,其中有红、蓝黄 ,红色左边蓝色中间黄色右边

五 其他问题

1 设计一个setAll函数的HashMap (泛型理解不深刻)

put get isContain setAll 要求时间复杂度都为O(1)

需要考虑泛型的使用方法,泛型类和泛型方法 (泛型数组?)

思路,设计时间戳

2 从N个数中等概论打印M个数

题目: 给定一个长度为N且没有重复元素的数组arr和一个整数n,实现函数等概论随机打印arr中的M个数 时间复杂度o(m) 空间复杂度 O(1) 思路一:使用hash表来记录是否打印 思路二:与最后一个元素位置交换

3 判断一个数是否是回文数

给定一个32位整数,判断是否是回文数

如 121 22 -121 -22

注意:32位整数最小值为 -2147483648 绝对值会产生溢出

"/" 获得高位 "%"获得低位

六 栈和队列

1 设计一个有getMin功能的栈 (again)
  • 题目: 实现一个特殊的栈,在栈的基础功能上,返回栈中最小元素
  • 思路:使用两个栈,一个用来存储数据,一个用来存储当前最小值
  • 需要考虑栈入栈和出栈规则
  • 1 入栈 stack_min 中与栈顶比较,若比当前小或者相等则入栈,否则不变
  • 2 出栈时,stackMin元素总是小于等stackData栈顶元素 相等则都出,min小于data则只出data
2 猫狗队列 (again)
  • add
  • pollAll
  • pollDog
  • pollCat
  • isEmpty
  • 思路: 使用时间戳 添加PetEnter类 最后在CatDogQueue内实现
3 用一个栈实现另一个栈

题目: 一个栈中元素的类型为整型,现将该栈从顶到底按从大到小顺序排序,只允许申请一个额外栈。可申请额外变量,但不能申请额外数据结构,如何排序。

思路:排序栈为stack 申请辅助栈为help stack上执行pop,弹出元素为pop

​ 1 若cur小于 或等于 help的栈顶元素,直接压入help

​ 2 若大于 则将help元素逐一弹出,逐一压入stack,直到cur <= help 的栈顶元素

	while (!s1.isEmpty()) {
			int tmp = s1.pop();
        	// 此处不能有等于 会无限循环
			while (!s2.isEmpty() && tmp < s2.peek()) {
				s1.push(s2.pop());
			}
			s2.push(tmp);
		}

七 链表问题

1 打印两个有序链表的公共部分

题目:给定两个有序链表的头指针head1和head2 ,打印公共部分

思路 head1 <head2 head1 往下移动 同理

注意:加强如何构建链表,如何打印链表,以及打印完链表指针后移的要素

2 删除倒数第K个节点 (again)

原因:没有考虑边界问题 删除时可以利用之前的K

  • 边界问题 删除倒数K节点是否超出数组范围,以及头结点和其他节点的不同的删除方式
  • 思路: 每移动一次k就减一,根据K是否大于0来判断
  • K>0 证明 超出范围
  • k=0 恰好头结点,删除头结点
  • k<0 则正常情况,删除该节点需获取前一个节点
public static Node removeLastKthNode(Node head, int k,int length) {
		if (head == null) {
			return head;
		}
		Node cur = head;
		int k2=k;
		while (cur != null) {
			cur = cur.next;
			k--;
		}
		Node cur2 = head;
		if (k < 0) {
			int tmp=k2-length;
			//直接使用K  while(++k!=o)
			while (tmp++ != 0) {
				cur2 = cur2.next;
			}
			Node next = cur.next;
			cur2.next = next.next;
			next.next = null;
			return head;

		} else
		if (k == 0) {
			head = head.next;
			return head;
		} else {
			return head;
		}
3 删除链表的中间节点 (again)
  • 进阶问题 a/b处的节点(后序完成)

  • 原因:如何删除前一个节点 让h2开始就从头节点移动两次(比h1多移动一次) 此时h1就是前一个节点

  • 思路:申请两个节点h1,h2 h1每次只移动一次,h2移动两次

  • 当h2的下一次为null 则返回当前h1

public static Node removeMiddleNode(Node node) {
		if (node == null) {
			return node;
		}
		if (node.next.next == null) {
			return node.next;
		}
		Node pre = node;
		Node cur = node.next.next;
		while (cur.next != null && cur.next.next != null) {

			pre = pre.next;
			cur = cur.next.next;

		}
		pre.next = pre.next.next;
		return node;
	}
4 反转单向链表和双向链表 (again)

注意:还可以使用递归方式

https://blog.csdn.net/wxm349810930/article/details/46724691

思路:将头结点从链表拆除,然后对剩余节点逆序,最后将表头节点连接到新链表尾部

4.1 反转单向链表

  • 思路:声明两个节点next和pre

  • next 保存头结点后面的节点

  • pre 保存前一个节点

  • head.next=pre //此事head指向pre 已经和原先节点断链

  • pre=head //更新pre节点

  • head=next //将保存后序节点在重新给head

  • 最后pre就是头结点head此时为空

  • public static Node reverse(Node head) {
    		Node next = null;
    		Node pre = null;
    		while (head != null) {
    			next = head.next;// 保存头结点后面的节点
    			head.next = pre; // 此时head指向pre 已经和原先节点断链
    			pre = head;
    			head = next;// 将保存后序节点在重新给head
    
    		}
    		// 返回pre而不是head?
    		return pre;
    	}

4.2 反转双向链表

public static DoubleNode reverseDouble(DoubleNode node) {
		DoubleNode next = null;
		DoubleNode pre = null;
		while (node != null) {
			next = node.next;
			node.next = pre;
			node.pre = next;
			pre = node;
			node = next;
		}		
		return pre;
5 反转部分链表 (again agian)

难点:如何获得要反转部分的前一个及最后一个,以及如何正确连接节点

思路:

1 先获得要反转节点from的前一个 preFrom 和反转to的后一个postTo

2 先将反转的第from位置节点和postTo连接 (注意此时断链,需要声明一个节点保存后序信息)

3 反转部分节点

4 若preFrom为null 则是反转了头结点,若反转不为空,返回新节点,

(此时head节点已不是头结点 而是指向了postTo位置节点

	public static Node partReverse(Node head, int from, int to) {
		// 找到要反转节点的前一个节点,且不是头结点
		int i = 0;
		Node h1 = head;
		Node preFrom = null;
		Node postTo = null;
		// 获得链表from前一个preFrom.to的后一个postTo
		while (h1 != null) {
			i++;
			preFrom = i == from - 1 ? h1 : preFrom;
			postTo = i == to + 1 ? h1 : postTo;
			h1 = h1.next;
		}		
		//拿到要反转的第一个节点
		h1 = preFrom == null ? head : preFrom.next;	
		//连接翻转后的尾节点	
		Node h2 = h1.next;
		h1.next = postTo;
		// 反转节点
		Node next = null;
		int j = to - from;
		while (h2 != postTo) {
			next = h2.next;
			h2.next = h1;
			h1 = h2;
			h2 = next;
		}
		// h1.next = pre;
		if (preFrom != null) {
			preFrom.next = h1;
			// 返回旧节点
			return head;
		}
		// 返回新节点
		return h1;
	}
6 环形单链表约瑟夫问题 (again)

注意:掌握循环链表,如何找到最后一个节点 注意循环链表的判断条件

  • 单向环形链表

  • 每m个就删除当前节点,并重新计数

  • 直到剩下最后一个

    public static Node jsoepuseKill(Node head, int m) {
    		if (head == null || head.next == head || m < 1)
    			return head;
    		// 找到last节点
    		Node last = head;
    		while (last.next != head) {
    			last = last.next;
    		}
    
    		int count = 0;
    		while (head != last) {
    			if (++count == m) {
    				last.next = head.next;
    				count = 0;
    			} else {
    				last = last.next;
    			}
    			head = head.next;
    		}
    		return head;
    
    	}
7 判断是否为回文结构
  • 注意:思路二中移动时 Node cur = head.next;

    ​ 在移动后cur就是右链的第一个节点(这块出错)

  • 思路1 :使用栈(没想到),入栈顺序和出栈顺序一致

  • 思路2:同样使用栈,只讲链表的一半压入栈,比较左链和右链是否相等

    ​ 需要进行奇数偶数判断。

8 两个链表相加

注意: 1 需考虑当两个栈都为空,且还存在进位时

​ 2 如何更新节点

​ 3 相加时精简写法

    val1=s1.isEmpty()?0:s1.pop();
    val2=2.isEmpty()?0:s2.pop();
	val3=val1+val2+cal;
	cal=val3/10; //进位
    pre=cur;
	cur=new Node(cal%10);
	cur.next=pre;

     	pre = cur;
		cur = new Node(val3);
		cur.next = pre;
  • 思路1:加完在生成链表 ,有可能相加时会存在溢出
  • 思路2:利用栈,需考虑进位
public static Node addList1(Node head1, Node head2) {
		if (head1 == null || head2 == null) {
			return head1 == null ? head1 : head2;
		}
		Stack<Integer> s1 = new Stack<Integer>();
		Stack<Integer> s2 = new Stack<Integer>();
		while (head1 != null || head2 != null) {
			if (head1 != null) {
				s1.push(head1.data);
				head1 = head1.next;
			}
			if (head2 != null) {
				s2.push(head2.data);
				head2 = head2.next;
			}
		}
		Node pre = null;
		Node cur = null;
		int val1 = 0;
		int val2 = 0;
		int cal = 0;
		while (!s1.isEmpty() || !s2.isEmpty()) {

			if(!s1.isEmpty()){
				val1 = s1.pop();
			} else {
				val1 = 0;
			}
			if(!s2.isEmpty()){
				val2=s2.pop();
			} else {
				val2 = 0;
			}

			int val3 = val1 + val2 + cal;
			if (val3 >= 10) {
				cal = 1;
				val3 = val3 % 10;
			} else {
				cal = 0;
			}
			pre = cur;
			cur = new Node(val3);
			cur.next = pre;
		}
		// 当两个栈都为空,且还存在进位时
		if (cal == 1) {
			Node n = new Node(cal);
			n.next = cur;
			return n;
		}

		return cur;
	}
9 删除重复节点 (again 思路二未实现)
  • 难点:思路1中推荐使用hashset
  • 思路1 使用hashMap 时间复杂度O(n) 空间复杂度O(n)
  • 思路2 使用选择排序 时间复杂度O(n2) 空间复杂度O(1)
public static Node removeRepeat(Node head) {
		if(head==null)
			return null;
		HashMap<Integer, Integer> map=new HashMap<Integer, Integer>();
		map.put(head.data, 1);
		Node n1 = head;
		Node pre = head;
		Node cur = head.next;
		while (cur != null) {
			if (map.containsKey(cur.data)) {
				pre.next = cur.next;
				cur = cur.next;

			} else {
				map.put(cur.data, 1);
				pre = cur;
				cur = cur.next;
			}


		}
		return n1;
	}
10 删除指定节点

难点 :思路二连接链表存在更简单做法

 stack.peek().next=head;
 head=stack.pop();
  • 思路1 直接删除 时间复杂度O(N) 空间复杂度O(1)
  • 思路2 借助容器 将值不等的节点收集起来然后重新连接(没想到)
//直接删除
public static Node removeSpecialValue(Node head, int num) {
		if(head==null)
			return head;
		Node h = head;
		Node pre = head;
		Node cur = head.next;
		while (cur != null) {
			if (cur.data == num) {
				pre.next = cur.next;
			} else {
				pre = pre.next;
			}
			cur = cur.next;
		}
		return h;
	}
//使用栈
public static Node removeSpecialValueByStack(Node head, int num) {
		Stack<Node> s = new Stack<Node>();
		while (head != null) {
			if (head.data != num) {
				s.push(head);
			}
			head = head.next;
		}
		Node pre = null;
		Node cur = null;
		while (!s.isEmpty()) {
			cur = s.pop();
			cur.next = pre;
			pre = cur;
		}	
		return cur;
	}
11 实现单链表的选择排序

要求:额外空间复杂度O(1)

选择排序要点:从未排序的部分中找到最小值,然后放在排好序的尾部

  • 获取最小节点的前一个节点
// 获得最小节点的前一个节点
	private static Node getSmallPre(Node head) {
		Node smallPre = null;
		Node small = head;
		Node pre = head;
		Node cur = head.next;

		while (cur != null) {
			if(cur.data<pre.data){
				smallPre=pre;
				small = cur;
			}
			pre = cur;
			cur = cur.next;

		}
		return smallPre;
	}
  • 使用链表进行选择排序

    	public static Node selectionSort(Node head) {
    		if (head == null) {
    			return null;
    		}
    		Node tail = null; //排序部分的尾部
    		Node cur=head; //未排序的尾部
    		Node smallPre = null; // 最小节点的前一个节点
    		Node small=null; //最小节点
    		while (cur != null) {
    			small = cur;
    			// 找到最小节点
    			smallPre = getSmallPre(cur);
    			if (smallPre != null) {
    				small = smallPre.next;
    				smallPre.next = small.next;
    			}
    			// cur==small 则cur节点就是最小节点,然后继续遍历
    			// cur!=small 则cur节点不是,需继续从cur遍历找到整个未排序部分的最小节点
    			cur = small == cur ? cur.next : cur;
    			if (tail == null) {
    				tail = small;
    			} else {
    				tail.next = small;
    
    			}
    			tail = small;
    		}
    		return head;
    	}
    12 一种诡异的删除节点方式

    只给定一个节点node ,不给的头结点 要求删除该节点

    当该节点为null,不能使用复制方式,无法删除

    public static void removeNodeWired(Node node) {
    		if (node == null) {
    			return;
    		}
    		if (node.next == null) {
    			throw new RuntimeException("无法删除");
    		}
    		//把node.next的节点复制给node 删除node.next
    		node.data = node.next.data;
    		node.next = node.next.next;
    
    	}
13 向有序的环形单链表中插入新节点
  • 特殊情况 当num 遍历完后 ,没有找到插入的地方,会有两种情况

1 该节点比头结点小

2该节点比头结点大

  • 在环形链表中遍历的 cur !=head
  • pre=head cur=head.next;
//经典实现
public static Node insertByCircle2(Node head, Node num) {
		if (head == null) {
			num.next = num;
			return num;
		}

		Node pre = head;
		Node cur = head.next;
		while (cur != head) {
			if (pre.data <= num.data && num.data <= cur.data) {
				break;
			}
			pre = cur;
			cur = cur.next;
		}
		pre.next = num;
		num.next = cur;
		return head.data > num.data ? num : head;
	}
//自己实现
public static Node insertByCircle(Node head, Node num) {
		if (head == null)
			return null;

		
		Node cur = head;
		Node tail = null;
		// 获取taile节点
		while (cur.next != head) {
			cur = cur.next;
		}
		tail = cur;
		cur = tail.next;
		while (cur != tail) {
			if (cur.data >= num.data && cur.next.data <= num.data) {
				//插入节点
				Node next=cur.next;
				cur.next=num;
				num.next=next.next;
						
			}
			cur = cur.next;

		}
		if (num.data < head.data ) {
			tail.next = num;
			num.next = head;
			head = num;
		}
		if (num.data > tail.data) {
			tail.next=num;
			num.next=head;
			tail = num;
		}
		return head;
	}
14 合并两个有序链表 (again)
public static Node merge(Node head1, Node head2) {
		if (head1 == null || head2 == null) {
			return head1 == null ? head2 : head1;
		}
		// 头结点为两个中最小的一个
		Node head = head1.data < head2.data ? head1 : head2;
		Node cur1 = head == head1 ? head1 : head2;
		Node cur2 = head == head1 ? head2 : head1;
		Node pre=null;
    	//书上 Node next=null;
		while (cur1 != null && cur2 != null) {
			if(cur1.data<cur2.data){
				pre=cur1;
				cur1 = cur1.next;
			} else {
				Node next = cur2.next;
				pre.next = cur2;
				cur2.next = cur1;
				cur2 = next;
				pre = cur2;
			}
		}

		pre.next = cur1 == null ? cur2 : cur1;
		return head;

	}
15 左右半区方式重新组合链表

思路:1首先要划分出左右半区 需要声明一个mid变量

​ 2 组合左右两半区

  public static Node relocate(Node head){
        if(head==null)
            return null;
        //将链表划分左右两半区
        Node mid=head;
        Node right=head.next;
        while(right.next!=null && right.next.next!=null){
            mid=mid.next;
            right=right.next.next;
        }
        System.out.println("node:"+mid.next.data);
        Node cur=head;
        right=mid.next;
      // 标准写法 
      // mid.next=null;
      //while(cur.next!=null)
        while(cur!=mid){
            Node next_left=cur.next;
            Node next_right=right.next;
            cur.next=right;
            right.next=next_left;
            right=next_right;
            cur=next_left;
        }
        cur.next=right;
        return head;
    }
八 动态规划
1 爬梯子问题

leetcode 62

参考资料

segmentfault参考

只能右爬和下爬,输入各自行数和列数,输出需要多种可能性

public class UniquePath{
    
    public int uniquePath(int m,int n){
        int[][] res=new int[m-1][n-1];
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
               if(i==0 ||j==0){
                    res[i][j]=1;
                }else{
                     res[i][j]=res[i-1][j]+res[i][j-1];
                }
            }
        }
        return res[m-1][n-1];
    }
    
}
  • 使用递归方式 保存已有信息(记忆化搜索)
public class UniquePath{
  
     public int uniquePaths(int m, int n) {
        int[][] memo=new int[m][n];
        return dfs(m-1,n-1,memo);
    }
    public int dfs(int m,int n,int[][] memo){
        if(memo[m][n]!=0){
            return memo[m][n];
        }
        if( m==0 || n==0){
            return 1;
        }
        memo[m][n]=dfs(m-1,n,memo)+dfs(m,n-1,memo);
        return memo[m][n];
    }
}
1.1 爬梯子问题2

leetcode 70

题目描述: 有n阶台阶 可以爬一阶,也可以爬2阶,计算有多少不同方法

与斐波那契数方法一致

2 斐波那契数

Leetcode 509

  • 使用递归
 public int fib(int n){
        int[] memo=new int[n+1];
        return  cal(n,memo);
    }
    private  int cal(int n ,int[] memo){
        if(n==0)
            return 0;
        if(n==1){
            return 1;
        }
        if(memo[n]!=0){
            return memo[n];
        }
        return cal(n-1,memo)+cal(n-2,memo);
    }

    public static void main(String[] args) {
        Fibonacci sol=new Fibonacci();
        System.out.println(sol.fib(4));
    }
3 0-1背包问题
4 股票问题

leetcode 121 Best Time to Buy and Sell Stock

 //暴力破解 时间复杂度为O(n2)
    public int maxProfit(int[] prices) {
        int max=0;
        for(int i=0;i<prices.length;i++){
            for (int j=i+1;j<prices.length;j++){
                int tmp=prices[j]-prices[i];
                if (tmp>max){
                    max=tmp;
                }

            }
        }
        return max;
    }
    //时间复杂度为O(n)
    public int maxProfit2(int[] prices){
        int minPrice=Integer.MAX_VALUE; //最低价格
        int maxPro=0;//最大利润
        for (int i=0;i<prices.length;i++){
            if(prices[i]<minPrice){
                minPrice=prices[i];
            }else if(prices[i]-minPrice>maxPro){
                maxPro=prices[i]-minPrice;
            }
        }
        return maxPro;
    }
    //考虑使用动态规划??
    public int maxProfit3(int[] prices) {
        if(prices.length == 0 || prices.length == 1) return 0;

        int maxProfit = 0;
        int min = prices[0];

        for(int i = 0; i < prices.length; i++) {
            min = Math.min(min, prices[i]);
            maxProfit = Math.max(maxProfit, prices[i] - min);
        }

        return maxProfit;
    }
5 股票问题2

csdn参考

About

数据结构与算法每日练习题

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published