专栏首页bigsai剑指offer(47-67题)终极篇

剑指offer(47-67题)终极篇

54 字符流中第一个不重复的字符

题目描述

请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。

输出描述:

如果当前字符流没有存在出现一次的字符,返回#字符。

思路: 这题首先要理解题意吧。题目就是给了两个操作,insertFirstAppearingOnce两个函数,至于一些其他需要你自己实现。你可以选择字符数组、或者String做容器储存。这里我是用StringBuider储存。

insert肯定都没问题,但FirstAppearingOnce这个你不要每次都从头开始找,暴力枚举、肯定会炸的。你需要用个字符储存记录。也就是insert同时实现动态查找。而FirstAppearingOnce返回参数即可。

具体实现,我用的hashmap储存每个出现的次数。用index标记当前位置的出现第一个数。对于插入,如果不等于index位置的数,那么不需要改变第一个只出现次数为1的数,如果插入的是index位置字符,那么就需要从index往后查找下一个,如果index和字符串长度一样,那么就返回#

实现代码为:

import java.util.HashMap;
public class Solution {
    //Insert one char from stringstream   
    int index=0;
     StringBuilder sb=new StringBuilder();
     HashMap<String, Integer>map=new HashMap<String, Integer>();
    public void Insert(char ch)
    {
        sb.append(ch);
        if(map.containsKey(ch+"")) {//前面有该元素
            map.put(ch+"", 2);
            if((index<sb.length())&&ch==sb.charAt(index))//正是第一次出现的
            {
                for(;index<sb.length();index++)
                {
                    System.out.println(index);
                    if(map.get(sb.charAt(index)+"")==1)
                    {
                        break;
                    }
                }
            }       
        }
        else {
            map.put(ch+"", 1);
        }        
    }
  //return the first appearence once char in current stringstream
    public  char FirstAppearingOnce()
    {
        if(index==sb.length())return '#';
        else {
            return sb.charAt(index);
        }   
    }
}

55 链表中环的入口节点

题目描述

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

思路: 没想到什么更好的办法,就是没加一个暴力枚举它的下一个是否在前面。因为链表入口的确定需要比较的是地址而不是数值,判断相等只能用==比较。而链表有环那么环一定在后半部分,本来next应该指向null的那个最后节点指向前面某个节点node.这个node就是入口节点。后面如果发现更好方法会补充。

实现代码

/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
import java.util.ArrayList;
public class Solution {

      public ListNode EntryNodeOfLoop(ListNode pHead)
        {
            ArrayList<ListNode>list=new ArrayList<ListNode>();
            while (pHead.next!=null) {
                for(int i=0;i<list.size();i++)
                {
                    if(pHead.next==list.get(i))
                    {
                        return pHead.next;
                    }
                }
                ListNode pHead2=pHead;
                list.add(pHead2);
                pHead=pHead.next;
            }
            return null;
        }
}

56 删除链表中重复节点

题目描述

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

思路: 这题虽然很容易读懂题,但是处理起来如果选择方法不当可能还比较麻烦。因为是已经排好序的,只要是相同的那么节点值有相同的都要删除。但是删除时候你要考虑头节点问题。和尾部不能空指针异常(要判断是否删到头)。

对于头问题,如果头就有相等的那么这个头你就要处理一下,还有我们知道曾经链表我们引入一个带头节点的链表为了方便操作链表首。这里我们也可以这样操作。先用个node的next指向head,head先前移,最后返回head.next即可

有了这个头节点,就可以在不越界的情况下判断(node.next和node.next.next)的值是否等,如果等那么就往下删光所有值为它的节点。在这个过程要注意不要越界操作!!

实现代码为:

/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
     public static ListNode deleteDuplication(ListNode pHead)
        {
         if(pHead==null)return null;
         ListNode teamnode=new ListNode(Integer.MAX_VALUE);//当作头节点
         teamnode.next=pHead;
         pHead=teamnode;
         while (teamnode.next!=null) {
             System.out.println(teamnode.val);
            while(teamnode.next.next!=null&&teamnode.next.val==teamnode.next.next.val)
            {
                int delete=teamnode.next.val;
                while (teamnode.next!=null&&teamnode.next.val==delete) {
                    teamnode.next=teamnode.next.next;
                }
                if(teamnode.next==null)return pHead.next;
            }
            teamnode=teamnode.next;
        }
         return pHead.next;

         //先处理一下
        }
}

57 二叉树的下一个节点

题目描述

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

思路: 这里他给的是一个节点(左右和父节点)。这题的处理方法虽然还是蛮多的,但是有的不一定好。关于二叉树的几种遍历以前记过可以看看。

比如你可以根据这个节点找到根节点吧?根节点知道的二叉树的非递归中序遍历前面讲过吧,根据顺序可以找到下一个。但是这样真的太低效了。

分析二叉树和分析这个节点的位置。 分类讨论就完全ojbk

  • 右侧有儿子:这个节点只要有右侧节点那么它的下一个节点肯定在右侧节点,因为二叉树中序是左中右。只需要找到右侧最左的那个节点就ok。
  • 右侧无儿子找到上面第一个是父亲左节点的节点。因为这个节点没右儿子。就要看这个节点所在的这个子树的第一个中间节点了。就是[左侧区域最右]-&gt;中的这个查找过程。当然,如果它的父节点祖先节点都不存在是左儿子的情况,那么它就是最右侧的节点,返回null就行了。

实现代码为:

/*
public class TreeLinkNode {
    int val;
    TreeLinkNode left = null;
    TreeLinkNode right = null;
    TreeLinkNode next = null;

    TreeLinkNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public TreeLinkNode GetNext(TreeLinkNode pNode)
        {
            if(pNode.right!=null)
            {
                pNode=pNode.right;
                while (pNode.left!=null) {
                    pNode=pNode.left;
                }
            }
            else if (pNode.next!=null&&pNode.next.left==pNode) {
                pNode=pNode.next;
            }
            else {
                while (pNode.next!=null&&pNode.next.right==pNode) {
                    pNode=pNode.next;
                }
                if(pNode.next!=null)pNode=pNode.next;
                else {
                    pNode=null;
                }
            }
            return pNode;
        }
}

58 对称的二叉树

题目描述

请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

思路: 看看对不对称,照照镜子就行!看看镜子里你伸右手它是不是伸左手。而二叉树是不是对称的,你只需要根据某种遍历方式同时进行左右顺序颠倒的对比,查看节点的结构(有无左右孩子)和节点数值是否相等,如果有一点不一样直接停止返回即可!而这里笔者使用两个队列进行层序遍历一个规则是从左向右,另一个规则是从右向左 比较到最后就行了。

实现代码为:

import java.util.ArrayDeque;
import java.util.Queue;
/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    boolean isSymmetrical(TreeNode pRoot)
        {
        if(pRoot==null)return true;
            Queue<TreeNode>q1=new ArrayDeque<>();//策略是左右
            Queue<TreeNode>q2=new ArrayDeque<>();//策略是右左
            q1.add(pRoot);
            q2.add(pRoot);
            while (!q1.isEmpty()&&!q2.isEmpty()) {
                TreeNode t1=q1.poll();
                TreeNode t2=q2.poll();
                if(t1.val!=t2.val)return false;
                if(!(t1.left==null)^(t2.right==null)&&!(t1.right==null)^(t2.left==null))//左右子树结构相同
                {
                    if(t1.left!=null) {q1.add(t1.left);q2.add(t2.right);}           
                    if(t1.right!=null) {q1.add(t1.right);q2.add(t2.left);}          
                }   
                else {
                    return false;
                }
            }
          return true;

        }
}

59 按之字形顺序打印二叉树

题目描述

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

思路: 就是一个特殊的层序遍历。更换层的时候需要更换节点顺序,这需要我们用两个内存空间配合达到分清奇偶的目的。这里有的是从左到右,有的是从右到左,理论上可以借助栈将集合的元素反转但是没必要。我用两个List集合直接刚就行了。 首先进行分析:

  • 第一行从左到右,第二行从右到左,第三行从左到右。两个list装的是节点而还需要每次遍历根据奇数和偶数的特性将节点装起来
  • (普遍方法)你可以全部按照正常的顺序分层装起来,只不过如果偶数层遍历的时候从右往左加进结果集合。比较好想,容易操作,但是偶数层在添加节点时候不能同时遍历。
  • 但是笔者瞎搞发现一个规律。全部从右往左遍历。只不过在奇数行先添加(左后右)。而偶数行进行右左添加,相当于这个顺序操作一次被颠倒一次,每次添加节点都可以直接访问而不需要单独的访问。(这个方法可能复杂了上面一条其实就可以了)

实现代码(需要自己画图理解):

import java.util.ArrayList;

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {

         ArrayList<ArrayList<Integer>>list=new ArrayList<ArrayList<Integer>>();
        if(pRoot==null)return list;
         ArrayList<TreeNode>nodelist1=new ArrayList<TreeNode>();//用来模拟堆栈用
         ArrayList<TreeNode>nodelist2=new ArrayList<TreeNode>();
         nodelist1.add(pRoot);
         int num=1;//做奇数偶数
         while (!nodelist1.isEmpty()||!nodelist2.isEmpty()) {
             ArrayList<Integer>team=new ArrayList<Integer>();
            if(num%2==1) {
                for(int i=nodelist1.size()-1;i>=0;i--)
                {

                    TreeNode teamNode=nodelist1.get(i);
                    team.add(teamNode.val);
                    if(teamNode.left!=null)nodelist2.add(teamNode.left);
                    if(teamNode.right!=null)nodelist2.add(teamNode.right);
                }
                nodelist1.clear();
            }
            else {
                for(int i=nodelist2.size()-1;i>=0;i--)
                {

                    TreeNode teamNode=nodelist2.get(i);
                    team.add(teamNode.val);
                    if(teamNode.right!=null)nodelist1.add(teamNode.right);
                    if(teamNode.left!=null)nodelist1.add(teamNode.left);

                }
                nodelist2.clear();
            }
            list.add(team);
            num++;
        }
        return list;

        }

}

60 把二叉树打印成多行

题目描述

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

思路: 这题和上面59思路差不多,甚至更容易一些,对于层序遍历。需要分层,所以用2个队列进行区别一下层就可以了。

实现代码为:

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Queue;

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
  ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {            
         ArrayList<ArrayList<Integer>>list=new ArrayList<ArrayList<Integer>>();
            if(pRoot==null)return list;
            Queue<TreeNode>q1=new ArrayDeque<TreeNode>();
            Queue<TreeNode>q2=new ArrayDeque<TreeNode>();
            q1.add(pRoot);
            while (!q1.isEmpty()||!q2.isEmpty()) {
                ArrayList<Integer>list2=new ArrayList<Integer>();
                while (!q1.isEmpty()) {
                    TreeNode team=q1.poll();
                    list2.add(team.val);
                    if(team.left!=null)q2.add(team.left);
                    if(team.right!=null)q2.add(team.right);                 
                }
                list.add(list2);
                q1.addAll(q2);q2.clear();//把q2全部加进q1。然后clear掉
            }
            return list;            
        }  
}

61 序列化二叉树

题目描述

请实现两个函数,分别用来序列化和反序列化二叉树 二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 !表示一个结点值的结束(value!)。 二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。

思路: 对于序列化的概念,大家肯定会想起面向对象啦,json之类的开发相关,序列化反序列化就是能够将一种结构变成字符存储,而反序列化就是能够根据制定的某种规则构造出原结构相同的对象

那么这题个人有两个想法吧,但是实现只实现了一个。

思路一: 二叉树是可以用数组存储的,虽然用数组遇到不规则的二叉树空间利用效率会很低,但是是可以存的。对于第n个节点。它的两个儿子是2n2n+1(对应数组的物理地址)。

  • 所以实现上序列化你建立一个TreeNode[10000]数组,然后从根节点向后遍历,如果有儿子放到对应位置。(当然这个实现上有点盲目性,不太好)。返回字符串如果有值返回对应值没值的话可以用个特殊字串占位置。
  • 而反序列化你只需要把字符串先放到对应数组中,然后建立TreeNode[10000]数组与上面的遍历关联,如果有值那么就在这个位置建立TreeNode节点。再次遍历的时候如果第n个节点的left指向2n,right指向2n+1位置.最终返回第1个位置的节点即可!反序列化完成。

思路二: 我们知道:一个中序带着前序或者后序可以确定一个二叉树!好了,我们就用前序可中序完成。前面讲过二叉树的几种遍历。在非递归遍历的前序和中序可以共用一个过程!详细可以参考数据结构专栏哈!

  • 序列化:给你个根节点,非递归前序和中序序列可以搞出来吧?但是记住字符串要有个东西分割,不能直接加。比如19 8中间必须有个东西分隔开来,这个要注意。最后直接将两个字符相加返回即可。
  • 反序列化: 给你带有前序和中序的字符串。分割出前序和中序序列。前面第四题是构建二叉树就是用前序和中序构建二叉树的,就是递归的妙用,至于还原具体的思路还请还对应剑指offer的题解

实现代码为:

import java.util.Stack;
/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
           static  String Serialize(TreeNode root) {
          if(root==null)return "";
            String qian="";
            String zhong="";

            Stack<TreeNode>stack=new Stack<TreeNode>();

            while (!stack.isEmpty()||root!=null) {//前序的
                if(root!=null)
                {
                    qian+=" "+root.val;
                    stack.push(root);
                    root=root.left;
                }
                else {
                    root=stack.pop();
                    zhong+=" "+root.val;
                    root=root.right;
                }
            }
            return qian.substring(1)+zhong;     

      }
       static TreeNode Deserialize(String str) {
            if(str.equals(""))return null;
            String value[]=str.split(" ");
            int len=value.length;
            int qian[]=new int [len/2];
            int hou[]=new int [len/2];
            for(int i=0;i<len/2;i++)
            {
                qian[i]=Integer.parseInt(value[i]);
            }
            for(int i=0;i<len/2;i++)
            {
                hou[i]=Integer.parseInt(value[i+len/2]);
            }
            return creat(qian,hou,0,len/2-1,0,len/2-1);        
      }
        private static TreeNode creat(int[] pre, int[] in, int preleft, int preright, int inleft, int inright) {
            if(preleft>preright||inleft>inright)return null;
            TreeNode node=new TreeNode(pre[preleft]);
            int mid=0;
            for(int i=inleft;i<=inright;i++)
            {
                if(pre[preleft]==in[i])
                {
                    mid=i;
                }
            }
            node.left=creat(pre, in, preleft+1, preleft+(mid-inleft), inleft, mid-1);
            node.right=creat(pre, in, preleft+(mid-inleft)+1, preright, mid+1, inright);
            return node;
        }   
}

参考评论区: 有个递归方法挺好的,值得参考:

在这里插入图片描述

链接:https://www.nowcoder.com/questionTerminal/cf7e25aa97c04cc1a68c8f040e71fb84?answerType=1&f=discussion
来源:牛客网

public class SerializeTree {

    int index = -1;
    /**
     * 分别遍历左节点和右节点,空使用#代替,节点之间,隔开
     *
     * @param root
     * @return
     */
    public String Serialize(TreeNode root) {
        if (root == null) {
            return "#";
        } else {
            return root.val + "," + Serialize(root.left) + "," + Serialize(root.right);
        }
    }
    /**
     * 使用index来设置树节点的val值,递归遍历左节点和右节点,如果值是#则表示是空节点,直接返回
     *
     * @param str
     * @return
     */
    TreeNode Deserialize(String str) {
        String[] s = str.split(",");//将序列化之后的序列用,分隔符转化为数组
        index++;//索引每次加一
        int len = s.length;
        if (index > len) {
            return null;
        }
        TreeNode treeNode = null;
        if (!s[index].equals("#")) {//不是叶子节点 继续走 是叶子节点出递归
            treeNode = new TreeNode(Integer.parseInt(s[index]));
            treeNode.left = Deserialize(str);
            treeNode.right = Deserialize(str);
        }
        return treeNode;
    }

    public static void main(String[] args) {
        TreeNode treeNode1 = new TreeNode(1);
        TreeNode treeNode2 = new TreeNode(2);
        TreeNode treeNode3 = new TreeNode(3);
        TreeNode treeNode4 = new TreeNode(4);
        TreeNode treeNode5 = new TreeNode(5);
        TreeNode treeNode6 = new TreeNode(6);

        treeNode1.left = treeNode2;
        treeNode1.right = treeNode3;

        treeNode2.left = treeNode4;
        treeNode3.left = treeNode5;
        treeNode3.right = treeNode6;

        SerializeTree serializeTree = new SerializeTree();

        String str = serializeTree.Serialize(treeNode1);
        TreeNode treeNode = serializeTree.Deserialize(str);
    }
}

62 二叉搜索树第K个节点

题目描述

给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。

思路: 首先,二叉搜索树是有序的!二叉搜索树的中序遍历是有序序列!(基础知识和常识了)。所以你主要非递归中序把节点释放到第K个就可以返回结束了。(也可使用递归版本)

实现代码为:

import java.util.Stack;
/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
     TreeNode KthNode(TreeNode pRoot, int k)
        {
            if(k==0)return null;
            Stack<TreeNode>stack=new Stack<TreeNode>();
            while (!stack.isEmpty()||pRoot!=null) {
                if(pRoot!=null)
                {
                    stack.push(pRoot);
                    pRoot=pRoot.left;
                }
                else {
                    pRoot=stack.pop();
                    k--;
                    if(k==0)return pRoot;
                    pRoot=pRoot.right;
                }
            }
            if(k>0)return null;
            return pRoot;           
        }
}

63 数据流中的中位数

题目描述

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

思路: 构造类型的题,中位数,也就是要我们维护一个有序数列取中间数。其实实现的方法也比较多吧。比如你可以用Arraylist。每次加入快速排序。也可以用优先队列进行堆排序维护。但是快排对已经有序序列效率不高,队列取数是比较麻烦的。

当然,注意这个序列是你一手构造的,从开始为0,并且每次加入节点的序列都是有序的。其实插入排序更好,这个步骤就相当于插入排序的最后一部一样。把最后一个找到合适位置插入就可以了。而序列是有序的,二分法+直接插入是一种很不错的解法!

实现代码为:

import java.util.ArrayList;
import java.util.List;
public class Solution {
    List<Integer> list = new ArrayList<Integer>();
   public void Insert(Integer num) {
            int l=0,r=list.size()-1;
            int mid=(l+r)/2;
            while (l<r) {
                if(list.get(mid)<num)
                {
                    l=mid+1;
                    mid=(l+r)/2;
                }
                else if (list.get(mid)>num) {
                    r=mid;
                    mid=(l+r)/2;
                }
                else {
                    l=r=mid;
                }
            }
            list.add(l, num);
        }
    public Double GetMedian() {
            if(list.size()%2==1)
            {
                return (double)list.get(list.size()/2);
            }
            else {
                return ((double) (list.get((list.size()-1)/2)+list.get(list.size()/2))/2);
            }
        }
}

64 滑动窗口的最大值

题目描述

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5};针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个:{[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

思路: 这题有技巧的,可以直接查找但是可能混乱一点。这题有兴趣的可以了解线段树,感觉有些思想有点像。虽然蛮干和我的题解在效率差别不大,但是这个思想感觉可以体会一下。

  • (1)如果区间为1,那么最大的就是每个数。
  • (2)如果区间为2,就是相邻的2个进行比较滑动取最大。
  • (3)如果区间为3,就是相邻的3个进行比较!还是相邻的(2)中的相邻两个最大两个滑动。
  • (4)如果区间为4,就是相邻的4个进行比较!还是相邻的(3)中的相邻两个最大两个滑动。
  • -----------

比如序列2 3 4 2 6 2 5 1

  • 区间为1:2 3 4 2 6 2 5 1
  • 区间为2:(23)3 (34)4 (42)4 (26)6 (62)6 (25)5 (51)5 7个窗口
  • 区间为3:(234)3 (342)4 (426)6 (262)6 (625)6 (251)5 6个滑动。也等价于 (2334)3 (3442)4 (4226)6 (2662)6 (6225)6 (2551)5(区间为2的相互比较)。

当然在空间利用方面,其实不需要开辟新的数组,直接重复利用一个数组就行了,算完一轮进行下一轮,每次长度你记住少一就行了。

实现代码为:

import java.util.ArrayList;
public class Solution {
   public static ArrayList<Integer> maxInWindows(int [] num, int size)
    {
         ArrayList<Integer>list=new ArrayList<Integer>();
        if(size==0)return list;
        int len=num.length;
        while (size-->1) {
            for(int i=0;i<len-1;i++)
            {
                num[i]=Math.max(num[i], num[i+1]);
            }
            len--;
        }
        System.out.println(len);

        for(int i=0;i<len;i++)
        {
            list.add(num[i]);
        }
        return list;
    }

}

参考评论区: 评论区的双队列方法:

链接:https://www.nowcoder.com/questionTerminal/1624bc35a45c42c0bc17d17fa0cba788?f=discussion
来源:牛客网

/**
     * 双队列方法
     * 滑动窗口的最大值总是保存在队列首部,队列里面的数据总是从大到小排列。
     *
     * [@param num
     * @param size
     * @return](/profile/547241) */
    public ArrayList maxInWindows(int[] num, int size) {
        ArrayList res = new ArrayList();
        if (num == null || num.length == 0 || size == 0 || size > num.length) {
            return res;
        }
        Deque deque = new LinkedList();
        for (int i = 0; i < num.length; i++) {
            if (!deque.isEmpty()) {
                // 如果队列头元素不在滑动窗口中了,就删除头元素
                if (i >= deque.peek() + size) {
                    deque.pop();
                }
                // 如果当前数字大于队列尾,则删除队列尾,直到当前数字小于等于队列尾,或者队列空
                while (!deque.isEmpty() && num[i] >= num[deque.getLast()]) {
                    deque.removeLast();
                }
            }
            deque.offer(i); // 入队列
            // 滑动窗口经过一个滑动窗口的大小,就获取当前的最大值,也就是队列的头元素
            if (i + 1 >= size) {
                res.add(num[deque.peek()]);
            }
        }
        return res;
    }

65 矩阵中的路径

题目描述

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。例如 a b c e s f c s a d e e 矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。

思路: 基础迷宫类dfs搜素题。它给的是一维数组,我们要根据位置对应转换成二维的数组储存地图。为了方便我们进行搜素。另外要建立X[]Y[]表示上下左右四个移动方向。

而对于这类题我们的一般做法是:

  • 二维的迷宫,用个boolean[][]数组判断对应位置在当前是否走过。
  • 本题要求找到这么一串符合的,我们可以遍历首字母相同的,从首字母相同的进行深度优先搜索,按照方向符合就往下搜索,到整个串路径存在即可。
  • 本题的搜索是属于试探回溯的。所以在dfs搜索走完一个位置如果满足条件,那么向四个满足条件(不越界没走过等)方向进行搜索,同时标记此位置在这条路径不能再次使用。而这条搜索完需要将boolean数组还原。至于dfs和回溯如果没基础的可以百度学习一下。

另外就是注意下特殊值,或者不满足的要判断排除。

实现代码为:

public class Solution {
     boolean judge=false;//判断是否存在
     int x[]= {-1,0,1,0};
     int y[]= {0,1,0,-1};

     public  boolean hasPath(char[] matrix, int rows, int cols, char[] str)
        {
         if(str.length==0||matrix.length==0)return false;
            char map[][]=new char[rows][cols];
            boolean jud[][]=new boolean[rows][cols];
            for(int i=0;i<matrix.length;i++)//转到二维数组
            {
                map[i/cols][i%cols]=matrix[i];
            }
            for(int i=0;i<rows;i++)
            {
                for(int j=0;j<cols;j++)
                {
                    if(map[i][j]==str[0])//第一个相同
                    {
                        dfs(map,jud,i,j,0,str);
                        if(judge)return true;
                    }
                }
            }
            return false;       
        }
    private  void dfs(char[][] map, boolean[][] jud, int m, int n, int index, char[] str) {
        if(index==str.length-1) {if(map[m][n]==str[index])judge=true;}//有成功的
        else if(map[m][n]!=str[index]) {}//回溯停止
        else {//map[n][n]==str[index]
            jud[m][n]=true;
            for(int i=0;i<4;i++)
            {
                if(m+x[i]>=0&&m+x[i]<map.length&&n+y[i]>=0&&n+y[i]<map[0].length&&map[m+x[i]][n+y[i]]==str[index+1])
                {
                    if(!jud[m+x[i]][n+y[i]])
                    {
                        dfs(map, jud, m+x[i], n+y[i], index+1, str);
                    }
                }
            }
            jud[m][n]=false;
        }       
    }
}

66 机器人的运动范围

题目描述

地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

思路: 这题也是简单搜索题,但是处理方式可以用bfs,也可以用dfs主要考察迷宫的通达性。所以你用dfs千万不要将Boolean判断数组复原,走过的地方只能走一次。至于这个规则我想很容易判断,求余10和除10下去就可以计算每个位置是否满足规则。

但是如果迷宫和K足够大的话,那么横纵坐标都这要这么计算n次,效率是不高的。你可以预处理的,每一行每一列先加上对应行和列。这样搜索时候不需要一个个计算直接比较就可以了。当然如果K小或者迷宫小可能会造成一些无用计算,这里了解下就可以了(并未采用)。

实现代码为:

public class Solution {
   int X[]= {-1,0,1,0};
      int Y[]= {0,1,0,-1};
      int num=0;
      public int movingCount(int threshold, int rows, int cols)
        {
          boolean jud[][]=new boolean[rows][cols];
          dfs(0,0,rows,cols,threshold,jud);
          return num;
        }
    private void dfs(int x, int y, int rows, int cols, int threshold, boolean[][] jud) {
        int count=nadd(x,y);    
        if(count>threshold) { }
        else {
            num++;
            jud[x][y]=true;
            for(int i=0;i<4;i++)
            {
                if(x+X[i]>=0&&x+X[i]<rows&&y+Y[i]>=0&&y+Y[i]<cols&&!jud[x+X[i]][y+Y[i]])//不越界
                {
                    dfs(x+X[i], y+Y[i], rows, cols, threshold, jud);
                }
            }
        }
    }
    private int nadd(int x, int y) {//计算个位数字之和
        int num=0;
        while (x>0) {
            num+=x%10;x/=10;
        }
        while (y>0) {
            num+=y%10;y/=10;
        }
        return num;
    }

}

67 剪绳子

题目描述

给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],…,k[m]。请问k[0]xk[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。 输入描述: 输入一个数n,意义见题面。(2 <= n <= 60)

输出描述:

输出答案。 示例1 输入 8 输出 18

思路: 数据范围很小,还没看题解过得,盲猜贪心思路!谈谈灵感吧(不一定对)!

  • 如果分成两份,两个相同的乘积应该最大
  • 如果分成n份,n个相同乘积应该最大
  • 分成几份?我猜对于一个数N,m^m^=n这个应该是最优等分。但是m只能取整数,所以一定在m取整的左右之间!好了,n<60这个范围太小。很好搞。也就分成几个区间而已.甚至2和3段分法能数出来是7.3*4=12,2*3*3=12.所以7到27肯定3段。27-60在3和4不确定,比较一下就行了。

知道分段n均分会求吧!这里有个小技巧可以参考代码。

 int a[]= {1,4,27,128};//1 2*2 3*3*3 4*4*4*4    2-7分两个  7-27分3   27-60分3或者4尝试

实现代码为:

public class Solution {
  public int cutRope(int target) {
          int a[]= {1,4,27,128};//1 2*2  3*3*3  4*4*4     2-7分两个     7-27分3    27-60分3或者4尝试
          if(target<=7)
              return (target/2)*(target-target/2);
          else if (target<=27) {
            return (target/3)*((target+1)/3)*((target+2)/3);//看看余数要分配给几个数
        }
          else {
            return Math.max((target/3)*((target+1)/3)*((target+2)/3), (target/4)*((target+1)/4)*((target+2)/4)*((target+3)/4));
        }       
      }
}

参考评论区 看了评论区才发现原来这题分析的正解是什么,前面的自己做法虽然A了但可能是错误,但是这个想法仍有部分参考价值吧

主流解法是贪心和dp吧。个人更推荐贪心感觉更容易理解。

链接:https://www.nowcoder.com/questionTerminal/57d85990ba5b440ab888fc72b0751bf8?f=discussion
来源:牛客网

//
// Created by yuanhao on 2019-9-3.
//

#include <iostream>
#include <cmath>

using namespace std;

/**
 * 题目分析:
 * 先举几个例子,可以看出规律来。
 * 4 :2*2
 * 5 :2*3
 * 6 :3*3
 * 7 :2*2*3 或者4*3
 * 8 :2*3*3
 * 9 :3*3*3
 * 10:2*2*3*3 或者4*3*3
 * 11:2*3*3*3
 * 12:3*3*3*3
 * 13:2*2*3*3*3 或者4*3*3*3
 *
 * 下面是分析:
 * 首先判断k[0]到k[m]可能有哪些数字,实际上只可能是2或者3。
 * 当然也可能有4,但是4=2*2,我们就简单些不考虑了。
 * 5<2*3,6<3*3,比6更大的数字我们就更不用考虑了,肯定要继续分。
 * 其次看2和3的数量,2的数量肯定小于3个,为什么呢?因为2*2*2<3*3,那么题目就简单了。
 * 直接用n除以3,根据得到的余数判断是一个2还是两个2还是没有2就行了。
 * 由于题目规定m>1,所以2只能是1*1,3只能是2*1,这两个特殊情况直接返回就行了。
 *
 * 乘方运算的复杂度为:O(log n),用动态规划来做会耗时比较多。
 */
long long n_max_3(long long n) {
    if (n == 2) {
        return 1;
    }
    if (n == 3) {
        return 2;
    }
    long long x = n % 3;
    long long y = n / 3;
    if (x == 0) {
        return pow(3, y);
    } else if (x == 1) {
        return 2 * 2 * (long long) pow(3, y - 1);
    } else {
        return 2 * (long long) pow(3, y);
    }
}

//给你一根长度为n的绳子,请把绳子剪成m段(m、n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],...,k[m]。请问k[0]xk[1]x...xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
//
//输入描述:
//输入一个数n,意义见题面。(2 <= n <= 100)
//
//
//输出描述:
//输出答案。
//示例1
//输入
//8
//输出
//18
int main() {
    long long n = 0;
    cin >> n;
    cout << n_max_3(n) << endl;
    return 0;
}
链接:https://www.nowcoder.com/questionTerminal/57d85990ba5b440ab888fc72b0751bf8?f=discussion
来源:牛客网

 /**
     * @param target
     * @return
     *
     * 分类:最值型、划分型
     *
     * 考虑最后一步:
     * 必然有一个点,把target分成两段,两段分别构成最小子问题。
     * 两段的最大值的乘积,也是target所求的最大值。
     * 设划分点为i,f[i]表示长度为i的绳子的乘积最大值。
     *
     * 转移方程:
     * f[i] = MAX{f[j]*f[i-j]}|0<j<i
     *
     * 那么我们求的就是f[target]
     *
     * 初始值:
     * f[0]=1
     * f[1]=1
     *
     * 边界情况:
     * 无
     *
     * 计算顺序:
     * i从1到target
     * j从1到i
     */
    public int cutRope(int target) {
        int[] f = new int[target+1];
        //初始化
        f[0] = 0;
        f[1] = 1;
        for (int i = 1; i <= target; i++) {
            /**
             * 处理不分割的情况,因为绳子不能不被分割
             */
            if(i==target) {
                f[i] = 1;
            }else {
                f[i] = i;
            }
            for (int j = 1; j < i; j++) {
                f[i] = Math.max(f[i],f[j]*f[i-j]);
            }
        }
        return f[target];
    }

本文分享自微信公众号 - bigsai(bigsai),作者:bigsai

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-02-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 剑指offer(41-53题)题解

    思路: 这题几个要求:大于等于两个序列,正整数,连续。至于对list长度返回的要求直接重写个排序接口即可。

    bigsai
  • 剑指offer(01-15题)优化题解

    思路: 选定一个维度(行或列)先找到需要查找的元素所在的行(列),再从该行(列)找到该元素的该元素具体的列(行)位置。复杂度O(n)。

    bigsai
  • 基础数论总结

    从右往左。可以一直递推,然后到最后一项,然后快速幂求矩阵,矩阵最终的结果就是所求结果。更新:java的矩阵通用乘法可以表示为,可以将下列代码替换道ac代码中:

    bigsai
  • 大数据:新的自然资源

    大数据文摘
  • 关关的刷题日记84 – Leetcode 231. Power of Two

    关关的刷题日记84 – Leetcode 231. Power of Two 题目 Given an integer, write a function to ...

    WZEARW
  • E Hilbert Sort 分治

    用户2965768
  • BZOJ1211: [HNOI2004]树的计数(prufer序列)

    Time Limit: 10 Sec  Memory Limit: 162 MB Submit: 2987  Solved: 1111 Description ...

    attack
  • PDF转图片,在线PDF转JPG/PNG

    使用pdf.js预览图片,pdf.js将pdf通过canvas将每一页渲染出来,然后我们通过canvas的toDataURL方法保存为jpg或png格式。

    vivec
  • 【Netty 专栏】深入浅出 Netty read

    boss线程主要负责监听并处理accept事件,将socketChannel注册到work线程的selector,由worker线程来监听并处理read事件,本...

    芋道源码
  • Linux系统LVM逻辑卷工作原理,必看~

    其实在Linux操作系统中,磁盘管理机制和windows上的差不多,绝大多数都是使用MBR(Master Boot Recorder)都是通过先对一个硬盘进行分...

    马哥linux运维

扫码关注云+社区

领取腾讯云代金券