《剑指Offer》Java版下篇(面试题45-66,多种解题思路)

     《剑指Offer》第45道题-第66题的Java版的解题思路和代码示例。Github的地址为:https://github.com/hzka/Sword2OfferJava
面试题53:数字在排序数组中出现的次数
题目描述:
        统计一个数字在排序数组中出现的次数。
解题思路一:
       暴力法:遍历一遍,若数组长度为零,则返回零;若相等,则进行计数器累加;若当前位置大于1且与当前元素的前一个位置的元素不相等且前一个元素等于待查找元素k,返回计数器的值。
代码示例一:

public class Solution {
    public int GetNumberOfK(int [] array , int k) {
       int count = 0;
       if(array.length== 0 ) return 0;
       for(int i = 0;i < array.length;i++){
           if(k==array[i])  count++;
           if(i>=1){
              if(array[i] != array[i-1] && k == array[i-1]) return count;
            }
       }
        return count;
    }
}

解题思路二:
       二分法解决:由于元素是有序的,所以采用二分法来查找待查找元素的开始位置和结束位置,从而相减获得排序数组该元素出现的次数。注意:二分法的递归写法和非递归写法都应该掌握。
代码示例二:

public class Solution {
    public int GetNumberOfK(int[] array, int k) {
        int length = array.length;
        if (length == 0) return 0;
        int firstk = getFirstk(array, k, 0, length - 1);
        int lastk = getLastk(array, k, 0, length - 1);
        if (firstk != -1 && lastk != -1) {
            return lastk - firstk + 1;
        }
        return 0;
    }
    //循环二分法,找到第一个k出现的位置
    private int getLastk(int[] array, int k, int start, int end) {
        int length = array.length;
        int mid = (start + end) >> 2;
        while (start <= end) {
            if (array[mid] > k) {
                end = mid - 1;
            } else if (array[mid] < k) {
                start = mid + 1;
            } else if (mid + 1 < length && array[mid + 1] == k) {
                start = mid + 1;
            } else {
                return mid;
            }
            mid = (start + end) >> 1;
        }
        return -1;
    }
    //递归二分法,找到第一个k出现的位置
    private int getFirstk(int[] array, int k, int start, int end) {
        if (start > end) return -1;
        int mid = (start + end) >> 1;
        if (array[mid] > k) {
            return getFirstk(array, k, start, mid - 1);
        } else if (array[mid] < k) {
            return getFirstk(array, k, mid + 1, end);
        } else if (mid - 1 >= 0 && array[mid - 1] == k) {//左边还有值为k的元素呢
            return getFirstk(array, k, start, mid - 1);
        } else {
            return mid;
        }
    }
}

(*)面试题54:二叉搜索树的第k个结点
题目描述:
       给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8)    中,按结点数值大小顺序第三小结点的值为4。
解题思路一:
       我们都知道,二叉搜索树的中序遍历集按照大小次序进行递增。采用中序递归遍历方法(左根右)进行遍历,设置计数器和待返回节点的TreeNode域。
代码示例一:

public class Solution {
    TreeNode KthNode(TreeNode pRoot, int k) {
          Inorder(pRoot,k);
          return returnnode;
    }
    int count = 0;
    TreeNode returnnode;
    void Inorder(TreeNode pRoot, int k) {
        if (pRoot == null) return;//判断写在最前面。
        KthNode(pRoot.left, k);
        count++;
        if (count == k) {
            returnnode = pRoot;
        }
        KthNode(pRoot.right, k);
    }
}

解题思路二:
      (**)中序遍历非递归方法进行求解,使用堆栈来实现,非递归写法应该掌握。
代码示例二:

import java.util.*;
public class Solution {
    TreeNode KthNode(TreeNode pRoot, int k) {
        if (pRoot == null || k == 0) return null;
        Stack<TreeNode> stack = new Stack<>();
        int count = 0;
        TreeNode node = pRoot;
        while (node != null || !stack.isEmpty()) {
            if (node != null) {
                stack.push(node);
                node = node.left;
            }else{
                node = stack.pop();
                count++;
                if(count == k)
                    return node;
                node = node.right;
            }
        }
        return null;
    }
}

(**)面试题55:二叉树的深度
题目描述
       输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
解题思路一:
       递归遍历:如果该树只有一个结点,它的深度为1.如果根节点只有左子树没有右子树,那么树的深度为左子树的深度加1;同样,如果只有右子树没有左子树,那么树的深度为右子树的深度加1。如果既有左子树也有右子树,那该树的深度就是左子树和右子树的最大值加1.
代码示例一:

public class Solution {
    public int TreeDepth(TreeNode root)  {
        if (root == null)
            return 0;
        int left = TreeDepth(root.left);
        int right = TreeDepth(root.right);
        return Math.max(left, right) + 1;
    }
}

解题思路二:
       使用队列来实现层次遍历:使用三个计数器来分别记录每层应有的节点数目、该层的计数器以及层数的计数器。若该层的计数器等于每层应有的节点数目,将深度加一,该层遍历结束,最后返回层数的计数器即可。
代码示例二:

public class Solution {
    public int TreeDepth(TreeNode root)  {
        if(root == null) return 0;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        int depth = 0,count = 0,nextcount = 1;
        //使用队列来实现层次遍历
        while (queue.size()!=0){
            TreeNode top = queue.remove();
            count++;
            if(top.left!=null){
                queue.add(top.left);
            }
            if(top.right!=null){
                queue.add(top.right);
            }
            if(count == nextcount){
                nextcount = queue.size();
                count = 0;
                depth++;
            }
        }
        return depth;
    }
}

面试题56:数组中只出现一次的数字
题目描述
       一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
解题思路一:
       快速排序+前后元素比较。先使用快速排序对整形数组进行排序;如果前一个元素等于后一个元素,则跳过相等的元素;如果不等于后一个元素,则将其放入待返回数组1中,设立标志位,观察数组1中有没有填充元素,若填充了,则下一个不相等的元素放入待返回数组2中。注意:需要判断最后一个元素和倒数第二个元素相等于否,若不等,则将最后一个元素放在待返回数组2中。
代码示例一:

public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        Arrays.sort(array);
        boolean flag = false;
        for(int i = 0;i<array.length-1;i++){
            if(array[i] == array[i+1]){ 
                i++;
            }else if(!flag){
                num1[0] = array[i];
                flag = true;
            }else{
                num2[0] = array[i];
            }
        }
        if(array[array.length-1] != array[array.length-2]){
               num2[0] = array[array.length-1];
        }
    }
}

解题思路二:
       使用哈希表来实现:建立基于链表LinkedList的Queue用于存储不等元素。使用HashMap来存储该元素(key)与该元素出现的次数(Value)。遍历该数组,如果HashMap未包含(containsKey)这个元素,则将其次数置为1;否则读取HashMap中该元素的次数,并加一。进而判断该元素出现的次数是否等于一,若相等,则将其添加至Queue中,依次remove出来即可。
代码示例二:

public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
        Queue<Integer> arr = new LinkedList<>();
        HashMap<Integer,Integer> hashMap = new HashMap<>();
        for(int i = 0;i<array.length;i++){
            if(!hashMap.containsKey(array[i])){
                hashMap.put(array[i],1);
            }else{
                hashMap.put(array[i],hashMap.get(array[i])+1);
            }
        }
        for(int i = 0;i<array.length;i++){
            if(hashMap.get(array[i])==1)
                arr.add(array[i]);
        }
        num1[0] = arr.remove();
        num2[0] = arr.remove();
    }
}

解题思路三:
       位与法:首先,位运算中异或的性质:两个相同数字异或=0,一个数和0异或还是它本身。当只有一个数出现一次时,我们把数组中所有的数,依次异或运算,最后剩下的就是落单的数,因为成对儿出现的都抵消了。依照这个思路,我们来看两个数(我们假设是AB)出现一次的数组。我们首先还是先异或,剩下的数字肯定是A、B异或的结果,这个结果的二进制中的1,表现的是A和B的不同的位。我们就取第一个1所在的位数,假设是第3位,接着把原数组分成两组,分组标准是第3位是否为1。如此,相同的数肯定在一个组,因为相同数字所有位都相同,而不同的数,肯定不在一组。然后把这两个组按照最开始的思路,依次异或,剩余的两个结果就是这两个只出现一次的数字。
代码示例三:

public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[])    {
        if (array == null && array.length <= 1) {
            num1[0] = num2[0] = 0;
        }
        int len = array.length, index = 0, sum = 0;
        for (int i = 0; i < len; i++) {
            sum ^= array[i];
        }
        //sum的二进制表示中,1的位数,表示的是
        // 两个唯一数字二进制表示中不同的位,我们就找出第一个1所在的位数(index)
        for(index = 0;index<32;index++){
            if((sum & (1<<index))!=0)
                break;
        }
        //按照这个位将数组分成两个子数组,分组标准是数字在这个位上的值是否为1
        // (此时数字相同的各位也相同,在同一个组中;不同数字,也就不在同一组里)。
        // 之后按照异或分别找出那两个唯一数即可。
        for(int i =0;i<len;i++){
            if((array[i] & (1<<index))!=0){
                num2[0] ^= array[i];
            }else{
                num1[0] ^= array[i];
            }
        }
    }
}

面试题57:和为S的连续正数序列
题目描述
       小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
解题思路一:
       暴力破解法:(1)根据题意,连续正数序列的区间在2个~总和/2+1个。(2)计算中间位置的数值。假设当前的正数序列为偶数个,其最小值为中间位置+(-i / 2) + 1;最大值为中间位置的数值+i / 2。计算我们所有数字的总和是否等于总和。假设当前的正数序列为奇数个,其最小值为中间位置+(-i / 2);最大值为中间位置的数值+i / 2。计算我们所有数字的总和是否等于总和。(3)如果等于的话,将其放于Arraylist数组中,否则,下一个。(4)冒泡排序,比较每个数组的第一个元素,序列间按照开始数字从小到大的顺序。
代码示例一:

public class Solution {
    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
        ArrayList<ArrayList<Integer>> allLists = new ArrayList<>();
        if(sum<=2)  return allLists;
        for (int i = 2; i <= sum/2+1; i++) {
            ArrayList<Integer> arrayList = new ArrayList<>();
            int midpos = sum / i;
            int testsum = 0;
            //如果是偶数的话
            if (i % 2 == 0) {
                for (int j = (-i / 2) + 1; j <= i / 2; j++) {
                    arrayList.add(midpos + j);
                    testsum += (midpos + j);  
                     if(midpos + j <= 0) break;
                }
                if (testsum == sum) {
                    allLists.add(arrayList);
                }
            }else{
                for (int j = (-i / 2); j <= i / 2; j++) {
                    arrayList.add(midpos + j);
                    testsum += (midpos + j);
                     if(midpos + j <= 0) break;
                }
                if (testsum == sum) {
                    allLists.add(arrayList);
                }
            }
        }
        for (int i = 0; i < allLists.size() - 1; i++) {
            for (int j = i + 1; j < allLists.size(); j++) {
                if (allLists.get(i).get(1) > allLists.get(j).get(1)) {
                    ArrayList<Integer> arrayList_01 = new ArrayList<>();
                    arrayList_01 = allLists.get(i);
                    allLists.set(i, allLists.get(j));
                    allLists.set(j, arrayList_01);
                }
            }
        }
        return allLists;
    }
}

解题思路二:
       双指针技术:双指针技术,就是相当于有一个窗口,窗口的左右两边就是两个指针,我们根据窗口内值之和来确定窗口的位置和宽度。非常牛逼的思路,虽然双指针或者所谓的滑动窗口技巧还是蛮常见的,但是这一题还真想不到这个思路。
代码示例二:

public class Solution {
    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
        //存放结果
        ArrayList<ArrayList<Integer>> result = new ArrayList<>();
        //两个起点,相当于动态窗口的两边,根据其窗口内的值的和来确定窗口的位置和大小
        int plow = 1, phigh = 2;
        while (phigh > plow) {
            //由于是连续的,差为1的一个序列,那么求和公式是(a0+an)*n/2
            int cur = (phigh + plow) * (phigh - plow + 1) / 2;
            //相等,那么就将窗口范围的所有数添加进结果集
            if (cur == sum){
                ArrayList<Integer> list = new ArrayList<>();
                for(int i = plow;i<=phigh;i++){
                    list.add(i);
                }
                result.add(list);
                plow++;
                //如果当前窗口内的值之和小于sum,那么右边窗口右移一下
            }else if (cur<sum){
                phigh++;
            }else{
                //如果当前窗口内的值之和大于sum,那么左边窗口右移一下
                plow++;
            }
        }
        return result;
    }
}

面试题58:左旋转字符串
题目描述
       汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!
解题思路一:
       StringBuilder方法:将原始字符串第n位到str.length位截取(substring)下来。添加(append)进StringBuilder中。再将原始字符串的第0位到第n位截取下来,添加进StringBuilder中。最后返回StringBuilder的toString方法。
代码示例一:

    public static String LeftRotateString(String str, int n) {
        if (n >= str.length()) return str;
        StringBuilder returnstr = new StringBuilder();
        returnstr.append(str.substring(n, str.length()));
        returnstr.append(str.substring(0, n));
        return returnstr.toString();
}

解题思路二:
        这道题考的核心是应聘者是不是可以灵活利用字符串翻转。假设字符串abcdef,n=3,设X=abc,Y=def,所以字符串可以表示成XY,如题干,问如何求得YX。假设X的翻转为XT,XT=cba,同理YT=fed,那么YX=(XTYT)T,三次翻转后可得结果。但问题在于Java的String是不可变的,你只能将其转为字符数组中再行反转,同样占用了空间,效率并不高。
代码示例二:

import java.util.*;
public class Solution {
    public String LeftRotateString(String str,int n)  {
        char[] chars = str.toCharArray();
        if(chars.length<n) return "";
        reverse(chars,0,n-1);
        reverse(chars,n,chars.length-1);
        reverse(chars,0,chars.length-1);
        StringBuilder sb = new StringBuilder(chars.length);
        for(char c:chars){
            sb.append(c);
        }
        return sb.toString();
    }
    private void reverse(char[] chars, int low, int high) {
        char temp;
        while (low<high){
            temp = chars[low];
            chars[low] = chars[high];
            chars[high] = temp;
            low++;
            high--;
        }
    }
}

面试题61:扑克牌顺子
题目描述
       LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)...他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子.....LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。
解题思路一:
       快速排序+顺序遍历比较。首先进行快速排序,随后再使用计数器统计大小王的个数。从不为零开始遍历,假设后一个数字不等于前一个数字,检查计数器是否大于零,若大于零,计数器减一,将number[i]更新为number[i]+1,然后使用continue进行下一次检查。否则返回false即可。若遍历结束前仍未返回false,则返回true。
代码示例一:

import java.util.*;
public class Solution {
    public boolean isContinuous(int [] numbers) {
        if(numbers.length<=4) return false;
        Arrays.sort(numbers);
        int count = 0;
        for (int i = 0; i < numbers.length - 1; i++) {
            if (numbers[i] == 0)  count++;
            if (numbers[i] != 0 && numbers[i] + 1 != numbers[i + 1]){
                if(count>0){
                    count--;
                    numbers[i] = numbers[i] + 1;
                    i--;
                    continue;
                }else{
                    return false;
                }
            }
        }
        return true;
    }
}

解题思路二:
       寻找规律:考虑到顺子的特性,最大值和最小值之差绝对为4,然而又有大小王的存在,所以a[4]-a[jokers] <=4。max记录最大值;min记录最小值;min ,max都不记0。
       满足条件:1.max - min <5;2.除0外没有重复的数字(牌),3.数组长度为5。
代码示例二:

import java.util.*;
public class Solution {
    public boolean isContinuous(int [] numbers) {
        if(numbers.length<=4) return false;
        int[] d = new int[14];//每个数字出现的次数的计数器
        d[0] = -5;
        int len = numbers.length;
        int max = -1, min = 14;
        for (int i = 0; i < len; i++) {
            d[numbers[i]]++;
            if (numbers[i] == 0) continue;
            if (d[numbers[i]] > 1) {
                return false;
            }
            max = numbers[i] > max ? numbers[i] : max;
            min = numbers[i] < min ? numbers[i] : min;
        }
        if(max - min<5)return true;
        else return false;
    }
}

面试题62:孩子们的游戏(圆圈中最后剩下的数)
题目描述
       每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去....直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
解题思路一:
       使用Arraylist的插入删除来模拟游戏过程:首先判断小朋友的数量和指定的数不为零;其次使用Arraylist存储每一个小朋友的下标,计算第一个不再回到圆圈的小朋友的位置下标,应该为index = (m - 1) % arraylist.size()。假设数组的长度大于一,执行删除不再回到圈中的小朋友的位置下标,计算下一个待删除小朋友的下标index = index + (m - 1)) % (arraylist.size()),直到Arraylist的长度等于一为止。返回该动态数组中下标为零的元素。
代码示例一:

import java.util.*;
public class Solution {
    ArrayList<Integer> arraylist = new ArrayList<>();
    public int LastRemaining_Solution(int n, int m)  {
        if(n==0 || m== 0) return -1;
        for (int i = 0; i < n; i++) {
            arraylist.add(i);
        }
        int index = (m - 1) % arraylist.size();
        while (arraylist.size() > 1) {
            arraylist.remove(index);
            index = (index + (m - 1)) % (arraylist.size());
        }
        return arraylist.get(0);
    }
}

解题思路二:
       参考:https://blog.csdn.net/crazy__chen/article/details/45115911
       要得到n个数字的序列的最后剩下的数字,只需要得到n-1个数字的序列的最后剩下的数字,并可以依此类推。当n=1时,也就是序列中开始只有一个数字0,那么很显然最后剩下的数字就是0。我们把这种关系表示为:

         0               n=1
f(n,m)={
         [f(n-1,m)+m]%n  n>1

代码实现二:

 public static int LastRemaining_Solution(int n, int m) {
        if(n==0)
            return -1;
        if(n==1)
            return 0;
        else
            return (LastRemaining_Solution(n-1,m)+m)%n;
}

面试题64:求1+2+3+...+n
题目描述
       求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
解题思路一:
       短路求值与递归计算:既然无法用(a0+an)*n/2,那么便使用递归吧。因为无法用if,所以必须想到短路求值。1.需利用逻辑与的短路特性实现递归终止。 2.当n==0时,(n>0)&&((sum+=Sum_Solution(n-1))>0)只执行前面的判断,为false,然后直接返回0;3.当n>0时,执行sum+=Sum_Solution(n-1),实现递归计算Sum_Solution(n)。注意:“||”运算符表示“或”,有一个为真则全部为真;前半部分判断出来是真的,后半部分就不再进行运算了。同理对于“&&”运算符,前一项为假则整个表达式为假,我们利用这个性质可以进行递归运算或者达到整洁代码的目的。
代码示例一:

public class Solution {
    public int Sum_Solution(int n){
        int ans = n;
        boolean flag = (ans>0) && ((ans+=Sum_Solution(n-1))>0);
        return ans;
    }
}

解题思路二:
       Math.pow的API:利用Math实现n(n+1),其实本质上用了乘法。
代码示例二:

    public int Sum_Solution(int n) {
        return (int) (Math.pow(n, 2) + n) >> 1;
    }

(**)面试题65:不用加减乘除做加法
题目描述
       写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
解题思路一:
        二进制级别相加,使用位操作符号,按位异或进行的是不进位加法,按位与进行的判断是是否有进位。注意:异或两者相等的话置为零。

                                                     
       首先看十进制是如何做的: 5+7=12,三步走:
       第一步:相加各位的值,不算进位,得到2。
       第二步:计算进位值,得到10. 如果这一步的进位值为0,那么第一步得到的值就是最终结果。
       第三步:重复上述两步,只是相加的值变成上述两步的得到的结果2和10,得到12。
       同样我们可以用三步走的方式计算二进制值相加: 5-101,7-111 第一步:相加各位的值,不算进位,得到010,二进制每位相加就相当于各位做异或操作,101^111。第二步:计算进位值,得到1010,相当于各位做与操作得到101,再向左移一位得到1010,(101&111)<<1。第三步重复上述两步,各位相加 010^1010=1000,进位值为100=(010&1010)<<1。继续重复上述两步:1000^100 = 1100,进位值为0,跳出循环,1100为最终结果。
代码示例一:

public class Solution {
    public int Add(int num1,int num2) {
        while (num2 != 0) {
            int temp = num1 ^ num2;//异或相加各位的值,不算进位。相等记为0,不等记为1。
            num2 = (num1 & num2)<<1;//与操作计算进位值。
            num1 = temp;
        }
        return num1;
    }
}

解题思路二:
        BigInteger的add方法:Java中自带BigInteger类,可以把int数值转为String类型;然后使用BigInteger的 public BigInteger(String val)构造方法new出BigInteger对象;调用BigInteger的add方法,然后把结果转为int类型。
代码示例二:

import java.math.*;
public class Solution {
    public int Add(int num1,int num2) {
        BigInteger b1 = new BigInteger(String.valueOf(num1));
        BigInteger b2 = new BigInteger(String.valueOf(num2));
        return b1.add(b2).intValue();
    }
}

解题思路三:
       这个解法是真的骚。自增自减。
代码示例三:

    public static int Add(int num1, int num2) {
        if (num1 > 0) {
            while (num1-- != 0)
                num2++;
        } else if (num1 < 0) {
            while (num1++ != 0)
                num2--;
        }
        return num2;
}

(*)面试题66:构建乘积数组
题目描述
       给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。
解题思路一:
       首先肯定是除法,由于题目条件,自己能想到的只有O(n^2)复杂度的暴力破解法。
代码示例一:

import java.util.ArrayList;
public class Solution {
    public int[] multiply(int[] A) {
       int [] B = new int [A.length];
       for(int i = 0;i<A.length;i++){
           B[i]=1;
       } 
       for(int i =0;i<A.length;i++){
           for(int j = 0;j<A.length;j++){
               if(i!=j){
                   B[i] = B[i] * A[j];
               }
           }
       }
        return B;
    }
}

解题思路二:
       下三角连乘和上三角连乘:B[i]的值可以看作下图的矩阵中每行的乘积。下三角用连乘可以很容求得,上三角,从下向上也是连乘。因此我们的思路就很清晰了,先算下三角中的连乘,即我们先算出B[i]中的一部分,然后倒过来按上三角中的分布规律,把另一部分也乘进去。

                                       
       B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。从左到右算 B[i]=A[0]*A[1]*...*A[i-1],从右到左算B[i]*=A[i+1]*...*A[n-1]。
代码示例二:

import java.util.ArrayList;
public class Solution {
    public int[] multiply(int[] A) {
        int length = A.length;
        int[] B = new int[length];
        if (length != 0) {
            B[0] = 1;
            //计算下三角连乘
            for (int i = 1; i < length; i++) {
                B[i] = B[i - 1] * A[i - 1];
            }
            int temp = 1;
            //计算上三角连乘
            for (int j = length - 2; j >= 0; j--) {
                temp *= A[j + 1];
                B[j] *= temp;
            }
        }
        return B;
    }
}

解题思路三:
       多个数组存储下三角和上三角的乘积:思路三与思路二本质相同,只是表达方式和理解上思路三比思路二好一些。分两步:1.计算前i - 1个元素的乘积,及后N - i个元素的乘积分别保存在两个数组中。这可以用动态规划实现。2.计算B[i]的值。
代码示例三:

import java.util.ArrayList;
public class Solution {
    public int[] multiply(int[] A){
        int len = A.length;
        int forword[] = new int[len];
        int backword[] = new int[len];
        int B[] = new int[len];
        forword[0] = 1;
        backword[0] = 1;
        for (int i = 1; i < len; i++) {
            forword[i] = A[i - 1] * forword[i - 1];
            backword[i] = A[len - i] * backword[i - 1];
        }
        for (int i = 0; i < len; i++) {
            B[i] = forword[i] * backword[len - i - 1];
        }
        return B;
    }
}

面试题67:把字符串转换成整数
题目描述
       将一个字符串转换成一个整数(实现Integer.valueOf(string)的功能,但是string不符合数字要求时返回0),要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。
解题思路:
       读取每个字符并计算ASCII的差值来求整数。读取每个字符(str.chatAt(i)),若字符不在0-9范围之内,则返回零,若在此范围之内,看与字符0(ASCII为48)的差值求出该字符所代表整数。计算总数。注意需要判断字符串的第一位是正是负。需要注意:边界条件:数据上下溢出、空字符串、只有正负号、有无正负号、错误标志输出。
代码示例:

public class Solution {
    public int StrToInt(String str)  {
        if(str.length() == 0) return 0;
        int results = 0;
        boolean flag = true;
        for(int i =0;i<str.length();i++){
            if(str.charAt(i) == '-' && i == 0) {
                flag = false;
                continue;
            }else if(str.charAt(i) == '+' && i == 0){
                continue;
            }
            if(str.charAt(i)>='0' && str.charAt(i)<='9'){
                int number = str.charAt(i) - '0';
                results = results * 10 +number;
            }else{
                return 0;
            }
        }
        if(flag){
            return results;
        }else{
            return -results;
        }
    }
}

牛客问题1:跳台阶
题目描述
       一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
解题思路一:
       斐波那契数列递归方法:也不清楚为啥,只是手动计算的序列和斐波那契序列类似,直接采用其递归方法解决。
       大佬说明:a.如果两种跳法,1阶或者2阶,那么假定第一次跳的是一阶,那么剩下的是n-1个台阶,跳法是f(n-1);b.假定第一次跳的是2阶,那么剩下的是n-2个台阶,跳法是f(n-2);c.由a、b假设可以得出总跳法为: f(n) = f(n-1) + f(n-2) ;d.然后通过实际的情况可以得出:只有一阶的时候 f(1) = 1 ,只有两阶的时候可以有 f(2) = 2;e.可以发现最终得出的是一个斐波那契数列:
             | 1, (n=1)
f(n) =     | 2, (n=2)
             | f(n-1)+f(n-2) ,(n>2,n为整数)
代码示例一:

    public int JumpFloor(int target) {
        if(target == 1)  return 1;
        if(target == 2)  return 2;
        return JumpFloor(target-1) + JumpFloor(target-2);
}

解题思路二:
       斐波那契数列非递归方法。
代码示例二:

   public static int JumpFloor(int target) {
        if (target < 1) return 0;
        if (target == 1) return 1;
        if (target == 2) return 2;
        int t1 = 1, t2 = 2;
        for (int i = 3; i <= target; i++) {
            int tmp;
            tmp = t2;
            t2 += t1;
            t1 = tmp;
        }
        return t2;
}

牛客问题2:变态跳台阶
题目描述
       一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
解题思路一:
       正向列出n个台阶的跳法寻找规律:我们列出跳的情况F(1)=1;F(2)=2;F(3)=4;F(4)=8;F(5)=16....;其实已经可以看出规律了:当n>3后,F(n)=F(1)+F(2)+...+F(n-1);简化该公式,当n>=1后,F(n) = 2*F(n-1);从反向去理解:由于已知条件,第一步每次可以跳1格,可以跳2格,也可以跳n格,因此F(n)= F(1)+F(2)+...+F(n-1);F(n-1)= F(1)+F(2)+...+F(n-2);因此F(n) = 2F(n-1);递归结束条件应当是F(1)=1;F(0)=-1。
代码示例一:

public class Solution {
    public int JumpFloorII(int target) {
        if(target <= 0)  return 0;
        int result = 1;
        for(int i = 1;i<target;i++){
            result *= 2;
        }
        return result;
    }
}

解题思路二:
在n阶台阶,一次有1、2、...n阶的跳的方式时,总得跳法为:
             | 1       ,(n=0 ) 
f(n) =     | 1       ,(n=1 )
             | 2*f(n-1),(n>=2)
代码示例二:

public class Solution {
    public int JumpFloorII(int target) {
        if (target <= 0) {
            return -1;
        } else if (target == 1) {
            return 1;
        } else {
            return 2 * JumpFloorII(target - 1);
        }
    }
}

牛客问题3:矩形覆盖
题目描述
       我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
解题思路一:
       找规律,F(1)=1,F(2)=2,F(3)=3,F(4)=5...,由此可见,与变态跳台阶问题一致,直接把代码抄过来即可,注意判断n==0的情况。

                          

                                        
代码示例一:

public class Solution {
    public int RectCover(int target) {
        if (target < 1) return 0;
        if (target == 1) return 1;
        if (target == 2) return 2;
        int t1 = 1, t2 = 2;
        for (int i = 3; i <= target; i++) {
            int tmp;
            tmp = t2;
            t2 += t1;
            t1 = tmp;
        }
        return t2;
}
}

解题思路二:
       递归解法。
代码示例二:

public class Solution {
    public int RectCover(int target) {
        if(target==0)  return 0;
        if(target==1)  return 1;
        if(target==2)  return 2;
        return RectCover(target-1)+RectCover(target-2);
    }
}

牛客问题4:平衡二叉树
题目描述
        输入一棵二叉树,判断该二叉树是否是平衡二叉树。
解题思路一:
       平衡二叉树具有的性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。遍历每个结点,借助一个获取树深度的递归函数,根据该结点的左右子树高度差判断是否平衡,然后递归地对左右子树进行判断。
代码示例一:

public class Solution {
    public boolean IsBalanced_Solution(TreeNode root) {
        if (root == null) {
            return true;
        }
        return Math.abs(maxDepth(root.left) - maxDepth(root.right)) <= 1 &&
                IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right);
    }

    private int maxDepth(TreeNode root) {
        if(root ==null)
            return 0;
        return 1+Math.max(maxDepth(root.left),maxDepth(root.right));
    }
}

解题思路二:
      (更优)在判断上层结点的时候,会多次重复遍历下层结点,增加了不必要的开销。如果改为从下往上遍历,如果子树是平衡二叉树,则返回子树的高度;如果发现子树不是平衡二叉树,则直接停止遍历,这样至多只对每个结点访问一次。
代码示例二:

public class Solution {
    private boolean isBalanced = true;
    public boolean IsBalanced_Solution(TreeNode root) {
        getDepth(root);
        return isBalanced;
    }
    private int getDepth(TreeNode root) {
        if (root == null) return 0;
        int left = getDepth(root.left);
        int right = getDepth(root.right);
        if(Math.abs(left - right)>1){
            isBalanced = false;
        }
        return right>left?right+1:left+1;
    }
}

牛客问题5:平和为S的两个数字
题目描述
       输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
解题思路一:
       双指针技术及正方形最大面积最大原理:设立双指针放在第零处位置和最后一处位置。假设双指针位置元素之和小于S,则没有判断的必要,直接将第一个指针加一。如果双指针位置元素在之和等于S,则根据正方形最大体积原理,两个元素之差最大即面积最小,因为是有序数组,所以第一个得到的和为S的两个数必然乘积最小。
代码示例一:

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
        ArrayList<Integer> arraylist = new ArrayList<>();
        for(int i = 0;i<array.length;i++){
            for(int j = array.length-1;j>i;j--){
                if(array[i]+array[j] < sum) break;
                if(array[i] + array[j] ==sum){
                    arraylist.add(array[i]);
                    arraylist.add(array[j]);
                    return arraylist;
                }
            }
        }
        return arraylist;
    }
}

解题思路二:
        双指针技术及夹逼准则:别人的思路遍历一次即可。
代码示例二:

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
        ArrayList<Integer> arraylist = new ArrayList<>();
        if(array ==null ||array.length <2) return arraylist;
        int i = 0,j = array.length-1;
        while (i<j){
            if(array[i]+array[j] == sum){
                arraylist.add(array[i]);
                arraylist.add(array[j]);
                return arraylist;
            }else if (array[i]+array[j] > sum){
                j--;
            }else{
                i++;
            }
        }
        return arraylist;
    }
}

牛客问题6:翻转单词顺序列
题目描述
       牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一 天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?
解题思路一:
       使用String字符串的API处理,先按照” ”进行分割,然后再反向将字符串数组添加进StringBuilder中,最后再截取0到length-1的数。注意两个条件:(1)假设字符串长度为零,返回””;(2)假设去掉头尾空字符串后(trim)后与””相等,则返回当前字符串。
代码示例一:

    public static String ReverseSentence(String str) {
        if(str.length()== 0 ) return "";
        //去掉字符串首尾的空格
        if(str.trim().equals("")){
            return str;
        }
        StringBuilder sb = new StringBuilder();
        String[] allstyrings = str.split(" ");
        for(int i = allstyrings.length-1;i>=0;i--){
            sb.append(allstyrings[i]+" ");
        }
        int str_length = sb.toString().length();
        return sb.toString().substring(0,str_length-1);
}

解题思路二:
       时间效率还可以,空间效率太低,先翻转整个句子,然后,依次翻转每个单词。依据空格来确定单词的起始和终止位置。与面试题58:左旋转字符串有点类似。反转句子后逐个反转单词。
代码示例二:

public class Solution {
    public String ReverseSentence(String str) {
        char[] chars = str.toCharArray();
        reverse(chars, 0, chars.length - 1);
        int blank = -1;
        for (int i = 0; i < chars.length; i++) {
            if (chars[i] == ' ') {
                int nextBlank = i;
                reverse(chars, blank + 1, nextBlank - 1);
                blank = nextBlank;
            }
        }
        reverse(chars,blank+1,chars.length-1);
        return new String(chars);
    }
    private void reverse(char[] chars, int low, int high) {
        while (low < high) {
            char temp = chars[low];
            chars[low] = chars[high];
            chars[high] = temp;
            low++;
            high--;
        }
    }
}

牛客问题7:字符流中第一个不重复的字符
题目描述
       请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
解题思路一:
       两次遍历,使用HashMap存储读取。使用String来存储每次添加进来的字符,第一次遍历字符串,再使用HashMap存储当前的字符和该字符出现的次数。使用containsKey这个API来判断该字符有没有出现过,使用get获取出现的次数。使用put更新字符出现的次数。第二次遍历字符串,找到value值为1的字符,并返回,若没有,则返回’#’。我的解法有点头重脚轻,可以将插入部分提出来放到Insert方法中。
代码示例一:

import java.util.*;
public class Solution {
    String str = new String(); 
    //Insert one char from stringstream
    public void Insert(char ch)
    {
        str+=ch;
    }
  //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
       HashMap<Character,Integer> hashmap = new HashMap<>();
       for(int i = 0;i<str.length();i++){
           if(!hashmap.containsKey(str.charAt(i))){
               hashmap.put(str.charAt(i),1);
           }else{
               int numbers = hashmap.get(str.charAt(i));
               hashmap.put(str.charAt(i),++numbers);
           }
       } 
       for(int i = 0;i<str.length();i++){
           if(hashmap.get(str.charAt(i))==1){
               return str.charAt(i);
           }
       }
        return '#';
    }
}

解题思路二:
       我们知道,ASCII码一共只有128个字符,那么我可以直接定义一个长度为128的数组,空间复杂度为O(n),时间复杂度控制在常数级别,虽然我获取第一个只出现一次的元素需要一个while循环,但是这个循环不可能超过128,一般很快就可以拿到。使用基于LinkedList的Queue来添加、移除元素。
代码示例二:

import java.util.LinkedList;
public class Solution {
    //英文字符不会逃出128个ascii码的范围,所以定义这个长度的数组
    //第一个ASCII码是一个空字符,所以我都是相对于` `进行一一排列
    //比如数字'0'是30,那'0'-''等于30,就存在tmp[30]这个地方即可
    //注意,tmp存的是出现的子树,即'0'出现了两次,那么tmp[30]就是2
    int[] tmp = new int[128];
    //维护一个队列,只保存一次进来的元素,重复的丢掉
    LinkedList<Character> queue = new LinkedList<>();
    //Insert one char from stringstream
    public void Insert(char ch)
    {
        //第一次进来的元素放进队列尾部
        if(tmp[ch-' '] == 0){
            queue.add(ch);
        }
        //进来一次,就对相应坐标加一,统计出出现次数
        tmp[ch-' ']++;
    }
  //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
        //取得时候是从队列得头部取,因为头部是比较早的数据
        //出现次数大于等于2的话就不断丢弃,知道找到第一个出现次数为1的字符跳出循环
        while(!queue.isEmpty() && tmp[queue.getFirst()-' ']>=2){
            queue.removeFirst();
        }
        //拿到这个第一个只出现一次的字符
        if(!queue.isEmpty()){
            return queue.getFirst();
        }
        //拿不到了,说明没有只出现一次的字符,那么就返回#
        return '#';
    }
}

(**)牛客问题8:按之字形顺序打印二叉树
题目描述
       请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
解题思路一:
       层数和双栈进行控制。一个栈存储偶数层节点,一个栈存储奇数层节点。stack1 填充元素到stack2,结束stack1;然后stack2 填充元素到stack1。重复此过程。递归结束条件为栈空。

                                 
代码示例一:

public class Solution {
    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        int layer = 1;
        //存奇数层节点
        Stack<TreeNode> s1 = new Stack<>();
        s1.push(pRoot);
        //存偶数层节点
        Stack<TreeNode> s2 = new Stack<>();
        ArrayList<ArrayList<Integer>> list = new ArrayList<>();
        while (!s1.empty() || !s2.empty()) {
            //对于奇数层的处理
            if (layer % 2 != 0) {
                ArrayList<Integer> temp = new ArrayList<>();
                while (!s1.empty()) {
                    TreeNode node = s1.pop();
                    if (node != null) {
                        temp.add(node.val);
                        s2.push(node.left);
                        s2.push(node.right);
                    }
                }
                if(!temp.isEmpty()){
                    list.add(temp);
                    layer++;
                }
            } else {
                ArrayList<Integer> temp = new ArrayList<>();
                while (!s2.isEmpty()) {
                    TreeNode node = s2.pop();
                    if (node != null) {
                        temp.add(node.val);
                        s1.push(node.right);
                        s1.push(node.left);
                    }
                }
                if(!temp.isEmpty()){
                    list.add(temp);
                    layer++;
                }
            }
        }
        return list;
    }
}

解题思路二:
       LinkedList实现的Queue层次遍历+层数判断+迭代器。类似层次遍历,加个判断条件,奇数层正序遍历,偶数层倒序遍历队列。Iterator<TreeNode> it = queue.iterator();正向遍历;queue.descendingIterator();反向遍历。
代码示例二:

public class Solution {
    public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> results = new ArrayList<>();
        if (pRoot == null)
            return results;
        LinkedList<TreeNode> queue = new LinkedList<>();
        queue.add(pRoot);
        int depth = 0;
        while (!queue.isEmpty()) {
            depth++;
            ArrayList<Integer> layer = new ArrayList<Integer>();
            int cur = 0, size = queue.size();
            if ((depth & 1) == 0) {//偶数层倒序添加。
                Iterator<TreeNode> it = queue.descendingIterator();
                while (it.hasNext()) {
                    layer.add(it.next().val);
                }
            } else {//奇数层顺序添加
                Iterator<TreeNode> it = queue.iterator();
                while (it.hasNext()) {
                    layer.add(it.next().val);
                }
            }
            while (cur < size) {
                TreeNode node = queue.pop();
                if (node.left != null) {
                    queue.add(node.left);
                }
                if (node.right != null) {
                    queue.add(node.right);
                }
                cur++;
            }
            results.add(layer);
        }
        return results;
    }
}

牛客问题9:把二叉树打印成多行
题目描述
       从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
解题思路一:
       基于Linkedlist的队列层次遍历+迭代器实现,在层次遍历的基础之上加上一层迭代器的正向遍历,因为Linkedlist已经实现了Iterator的接口,所以直接获取it.next().val即可。
代码示例一:

​
public class Solution {
    ArrayList<ArrayList<Integer> > Print(TreeNode pRoot){
        ArrayList<ArrayList<Integer>> results = new ArrayList<>();
        if (pRoot == null)
            return results;
        LinkedList<TreeNode> queue = new LinkedList<>();
        queue.add(pRoot);
        int depth = 0;
        while (!queue.isEmpty()) {
            depth++;
            ArrayList<Integer> layer = new ArrayList<Integer>();
            int cur = 0, size = queue.size();
            Iterator<TreeNode> it = queue.iterator();
            while (it.hasNext()) {
                layer.add(it.next().val);
            }
            while (cur < size) {
                TreeNode node = queue.pop();
                if (node.left != null) {
                    queue.add(node.left);
                }
                if (node.right != null) {
                    queue.add(node.right);
                }
                cur++;
            }
            results.add(layer);
        }
        return results;
    }
}

解题思路二:
       递归先序遍历,扩容进层退层。利用递归的方法进行先序遍历,传递深度,递归深入一层扩容一层数组,先序遍历又保证了同层节点按从左到右入数组,假如现在元素是[1,2,3],当进入2时会创建一个 arraylist,此时 depth = 2,size=2;当2遍历完后会进入3,此时3 就不用创建 arraylist 了,因为 2,3是同一层的,并且此时 depth==size 。这个判断是用来让最左的元素创建 arraylist 就行了,而同一层后边的元素共用这个 arraylist。
代码示例二:

public class Solution {
    ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> list = new ArrayList<>();
        depth(pRoot, 1, list);
        return list;
    }
    private  void depth(TreeNode pRoot, int depth, ArrayList<ArrayList<Integer>> list) {
        if (pRoot == null) return;
        if (depth > list.size()) list.add(new ArrayList<>());
        list.get(depth - 1).add(pRoot.val);
        depth(pRoot.left, depth + 1, list);
        depth(pRoot.right, depth + 1, list);
    }
}

解题思路三:
       层次遍历+自行判断当前Queue容量,与解法思路一本质一样。
代码示例三:

  public class Solution {
        ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
            ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
            if(pRoot == null){
                return result;
            }
            Queue<TreeNode> layer = new LinkedList<TreeNode>();
            ArrayList<Integer> layerList = new ArrayList<Integer>();
            layer.add(pRoot);
            int start = 0, end = 1;
            while(!layer.isEmpty()){
                TreeNode cur = layer.remove();
                layerList.add(cur.val);
                start++;
                if(cur.left!=null){
                    layer.add(cur.left);
                }
                if(cur.right!=null){
                    layer.add(cur.right);
                }
                if(start == end){
                    end = layer.size();
                    start = 0;
                    result.add(layerList);
                    layerList = new ArrayList<Integer>();
                }
            }
            return result;
        }
}

(**)牛客问题10:滑动窗口的最大值
题目描述
       给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{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]}。
解题思路一:
       模拟遍历,控制好不越界。暴力破解,时间复杂度为O(数组长度*滑动窗口大小),更新最大值,注意越界。
代码示例一:

public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size)
    {
        ArrayList<Integer> result = new ArrayList<>();
        if(size==0 || num.length ==0) return result;
        for(int i = 0;i<num.length-size+1;i++){
            int MAX = Integer.MIN_VALUE;
            for(int j = i;j<i+size;j++){
                MAX = (MAX>num[j]?MAX:num[j]);
            }
            result.add(MAX);
        }
        return result;
    }
}

解题思路二:
       使用双端队列解决。思路:滑动窗口应当是队列,但为了得到滑动窗口的最大值,队列序可以从两端删除元素,因此使用双端队列。
       原则:对新来的元素k,将其与双端队列中的元素相比较。1)前面比k小的,直接移出队列(因为不再可能成为后面滑动窗口的最大值了!);2)前面比k大的X,比较两者下标,判断X是否已不在窗口之内,不在了,直接移出队列。队列的第一个元素是滑动窗口中的最大值。

        我们可以使用一个双端队列deque。我们可以用STL中的deque来实现,接下来我们以数组{2,3,4,2,6,2,5,1}为例,来细说整体思路。数组的第一个数字是2,把它存入队列中。第二个数字是3,比2大,所以2不可能是滑动窗口中的最大值,因此把2从队列里删除,再把3存入队列中。第三个数字是4,比3大,同样的删3存4。此时滑动窗口中已经有3个数字,而它的最大值4位于队列的头部。第四个数字2比4小,但是当4滑出之后它还是有可能成为最大值的,所以我们把2存入队列的尾部。下一个数字是6,比4和2都大,删4和2,存6。就这样依次进行,最大值永远位于队列的头部。但是我们怎样判断滑动窗口是否包括一个数字?应该在队列里存入数字在数组里的下标,而不是数值。当一个数字的下标与当前处理的数字的下标之差大于或者相等于滑动窗口大小时,这个数字已经从窗口中滑出,可以从队列中删除。
        整体过程示意图:https://cuijiahua.com/blog/2018/02/basis_64.html              
代码示例二:

import java.util.*;
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size)
{
        ArrayList<Integer> ret = new ArrayList<>();
        if (num == null || num.length < size || size < 1) {
            return ret;
        }
        LinkedList<Integer> indexDeque = new LinkedList<>();
        for (int i = 0; i < size - 1; i++) {
            while (!indexDeque.isEmpty() && num[i] > num[indexDeque.getLast()]) {
                indexDeque.removeLast();
            }
            indexDeque.addLast(i);
        }
        for (int i = size - 1; i < num.length; i++) {
            //前面比k小的,直接移出队列(因为不再可能成为后面滑动窗口的最大值了!),
            while (!indexDeque.isEmpty() && num[i] > num[indexDeque.getLast()]) {
                indexDeque.removeLast();
            }
            indexDeque.addLast(i);
            //前面比k大的X,比较两者下标,判断X是否已不在窗口之内,不在了,直接移出队列
            //队列的第一个元素是滑动窗口中的最大值
            if (i - indexDeque.getFirst() + 1 > size) {
                indexDeque.removeFirst();
            }
            ret.add(num[indexDeque.getFirst()]);
        }
        return ret;
    }
}

        至此,《剑指Offer》告一段落,加油!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张云瀚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值