剑指offer-Java实现

面试题2:实现Singleton模式

题目:设计一个类,我们只能生成该类的一个实例。

一、加同步锁前后两次判断实例是否存在(懒汉)

public class Singleton {
    private Singleton(){}
    private volatile static Singleton instance;
    public static Singleton getInstance(){
        if(instance == null) {
            synchronized(Singleton.class) {
                if(instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

二、利用静态构造函数(饿汉)

public class Singleton {
    private Singleton(){}
    private static Singleton instance = new Singleton();
    public static Singleton getInstance() {
        return instance;
    }
}

三、静态类内部加载

public class Singleton {
    private Singleton() {}
    public static Singleton getInstance() {
        return SingletonFactory.instance;
    }
    private static class SingletonFactory{
        private static Singleton instance = new Singleton();
    }
}

面试题3:数组中重复的数字

题目:在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。

方法一:不需要额外的数组或者hash table来保存,题目里写了数组里数字的范围保证在0 ~ n-1 之间,所以可以利用现有数组设置标志,当一个数字被访问过后,可以设置对应位上的数 + n,之后再遇到相同的数时,会发现对应位上的数已经大于等于n了,那么直接返回这个数即可。

public class Solution {
    // Parameters:
    //    numbers:     an array of integers
    //    length:      the length of array numbers
    //    duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
    //                  Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
    //    这里要特别注意~返回任意重复的一个,赋值duplication[0]
    // Return value:       true if the input is valid, and there are some duplications in the array number
    //                     otherwise false
    public boolean duplicate(int numbers[],int length,int [] duplication) {
        for ( int i= 0 ; i<length; i++) {
            int index = numbers[i];
            if (index >= length) {
                index -= length;
            }   
            if (numbers[index] >= length) {
                duplication[0] = index;
                return true;
            }   
            numbers[index] = numbers[index] + length;
        }   
        return false ; 
    }
}

方法二://boolean只占一位,所以还是比较省的

public class Solution {
    public boolean duplicate(int numbers[], int length, int[] duplication) {
        boolean[] k = new boolean[length];
        for (int i = 0; i < k.length; i++) {
            if (k[numbers[i]] == true) {
            duplication[0] = numbers[i];
            return true;
            }
            k[numbers[i]] = true;
        }
        return false;
    }
}

方法三:剑指offer法不使用格外空间

public class Solution {
    // Parameters:
    //    numbers:     an array of integers
    //    length:      the length of array numbers
    //    duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
    //                  Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
    //    这里要特别注意~返回任意重复的一个,赋值duplication[0]
    // Return value:       true if the input is valid, and there are some duplications in the array number
    //                     otherwise false
    public boolean duplicate(int numbers[], int length, int[] duplication) {
        if(numbers == null || length < 2) {
            return false;
        }
        for(int i = 0; i < length; i++) {
            if(numbers[i] < 0 || numbers[i] > length - 1)
                return false;
        }
        for(int i = 0; i < length; i++) {
            while(numbers[i] != i) {
                if(numbers[i] == numbers[numbers[i]]) {
                    duplication[0] = numbers[i];
                    return true;
                }
                int temp = numbers[i];
                numbers[i] = numbers[temp];
                numbers[temp] = temp;
            }
        }
        return false;
    }
}

面试题4:二维数组中的查找

题目:在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

public class Solution {
    public boolean Find(int target, int [][] array) {
        boolean found = false;
        if(array != null) {
            int row = 0;
            int col = array[0].length - 1;
            while(row < array.length && col >= 0) {
                if(array[row][col] == target) {
                    found = true;
                    break;
                } else if(array[row][col] > target) {
                    col--;
                } else if(array[row][col] < target) {
                    row++;
                }
            }
        }
        return found;
    }
}

面试题5:替换空格

题目:请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

public class Solution {
    public String replaceSpace(StringBuffer str) {
    	int spaceNum = 0;
        for(int i = 0; i < str.length(); i++) {
            if(str.charAt(i) == ' ') {
                spaceNum++;
            }
        }
        int oldIndex = str.length() - 1;
        int newIndex = str.length() + 2 * spaceNum - 1;
        str.setLength(newIndex + 1);
        for(; oldIndex >= 0 && newIndex >= 0 && spaceNum > 0; oldIndex--) {
            if(str.charAt(oldIndex) == ' ') {
                str.setCharAt(newIndex--, '0');
                str.setCharAt(newIndex--, '2');
                str.setCharAt(newIndex--, '%');
                spaceNum--;
            } else {
                str.setCharAt(newIndex--, str.charAt(oldIndex));
            }
        }
        return str.toString();
    }
}

面试题6:从尾到头打印链表

题目:输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。

方法一:使用栈

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*
*/
import java.util.*;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> arrayList = new ArrayList<Integer>();
        if(listNode == null) 
            return arrayList;
        Stack<Integer> stack = new Stack<Integer>();
        while(listNode != null) {
            stack.push(listNode.val);
            listNode = listNode.next;
        }
        while(!stack.empty()) {
            arrayList.add(stack.pop());
        }
        return arrayList;
    }
}

递归版本:

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*
*/
import java.util.*;
public class Solution {
    ArrayList<Integer> arrayList = new ArrayList<Integer>();
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        if(listNode != null) {
            if(listNode.next != null) {
                printListFromTailToHead(listNode.next);
            }
            arrayList.add(listNode.val);
        }
        return arrayList;
    }
}

面试题7:重建二叉树

题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        if(pre == null || in == null || pre.length != in.length)
            return null;
        return constructCore(pre, 0, pre.length - 1, in, 0, in.length);
    }
    private TreeNode constructCore(int[] pre, int ps, int pe, int[] in, int is, int ie) {
        if(ps > pe) return null;
        int rootValue = pre[ps];
        int index = is;
        while(in[index] != rootValue) {
            index++;
        }
        TreeNode node = new TreeNode(rootValue);
        node.left = constructCore(pre, ps + 1, ps + index - is, 
                                  in, is, index - 1);
        node.right = constructCore(pre, ps + index - is + 1, pe,
                                  in, index + 1, ie);
        return node;
    }
}

面试题8:二叉树的下一节点

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

/*
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 == null) return null;
        TreeLinkNode pNext = null;
        if(pNode.right != null) {
            pNext = pNode.right;
            while(pNext.left != null) {
                pNext = pNext.left;
            }
        } else if(pNode.next != null) {
            TreeLinkNode pCurrent = pNode;
            TreeLinkNode pParent = pNode.next;
            while(pParent != null && pParent.right == pCurrent) {
                pCurrent = pParent;
                pParent = pParent.next;
            }
            pNext = pParent;
        }
        return pNext;
    }
}

面试题9:用两个栈实现队列

题目:用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

import java.util.Stack;

public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
    
    public void push(int node) {
        stack1.push(node);
    }
    
    public int pop() {
        if(stack2.isEmpty()) {
            while(!stack1.isEmpty()) {
                stack2.push(stack1.pop());
            }
        }
        return stack2.pop();
    }
}

用队列实现栈:

class MyStack {
    LinkedList queue1; 
    LinkedList queue2;
    
    /** Initialize your data structure here. */
    public MyStack() {
        queue1 = new LinkedList();
        queue2 = new LinkedList(); 
    }
    
    /** Push element x onto stack. */
    public void push(int x) {
        queue1.add(x);
    }
    
    /** Removes the element on top of the stack and returns that element. */
    public int pop() {
        while(queue1.size() > 1) {
            queue2.add(queue1.remove());
        }
        int temp = (int)queue1.remove();
        LinkedList tempQueue = queue1;
        queue1 = queue2;
        queue2 = tempQueue;
        return temp;
    }
    
    /** Get the top element. */
    public int top() {
        while(queue1.size() > 1) {
            queue2.add(queue1.remove());
        }
        int temp = (int)queue1.remove();
        queue2.add(temp);
        LinkedList tempQueue = queue1;
        queue1 = queue2;
        queue2 = tempQueue;
        return temp;
    }
    
    /** Returns whether the stack is empty. */
    public boolean empty() {
        return queue1.isEmpty();
    }
}

/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack obj = new MyStack();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.top();
 * boolean param_4 = obj.empty();
 */

面试题10:斐波那契数列

题目:大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。

n<=39

递归版:

public class Solution {
    public int Fibonacci(int n) {
        if(n <= 0) return 0;
        if(n == 1) return 1;
        return Fibonacci(n - 1) + Fibonacci(n - 2);
    }
}

非递归版:

public class Solution {
    public int Fibonacci(int n) {
        if(n == 0) return 0;
        if(n == 1) return 1;
        int prePreNum = 0;
        int preNum = 1;
        int count = 0;
        for(int i = 2; i <= n; i++) {
            count = prePreNum + preNum;
            prePreNum = preNum;
            preNum = count;
        }
        return count;
    }
}

跳台阶:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

对于本题,前提只有 一次 1阶或者2阶的跳法。

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为整数)

1

2

3

4

5

6

7

8

9

10

11

12

13

public class Solution {

    public int JumpFloor(int target) {

        if (target <= 0) {

            return -1;

        } else if (target == 1) {

            return 1;

        } else if (target ==2) {

            return 2;

        } else {

            return  JumpFloor(target-1)+JumpFloor(target-2);

        }

    }

}

从前到后遍历法:

public class Solution {
    public int JumpFloor(int target) {
        if(target == 1 || target == 2) {
            return target;
        }
        // 第一阶和第二阶考虑过了,初始当前台阶为第三阶,向后迭代
        // 思路:当前台阶的跳法总数=当前台阶后退一阶的台阶的跳法总数+当前台阶后退二阶的台阶的跳法总数
        int jumpSum = 0;// 当前台阶的跳法总数
        int jumpSumBackStep1 = 2;// 当前台阶后退一阶的台阶的跳法总数(初始值当前台阶是第3阶)
        int jumpSumBackStep2 = 1;// 当前台阶后退二阶的台阶的跳法总数(初始值当前台阶是第3阶)
        for(int i = 3; i <= target; i++) {
            jumpSum= jumpSumBackStep1 + jumpSumBackStep2;
            jumpSumBackStep2 = jumpSumBackStep1;// 后退一阶在下一次迭代变为后退两阶
            jumpSumBackStep1 = jumpSum;                   // 当前台阶在下一次迭代变为后退一阶
        }
        return jumpSum;
    }
}

变态跳台阶:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

关于本题,前提是n个台阶会有一次n阶的跳法。分析如下:

f(1) = 1

f(2) = f(2-1) + f(2-2)         //f(2-2) 表示2阶一次跳2阶的次数。

f(3) = f(3-1) + f(3-2) + f(3-3) 

...

f(n) = f(n-1) + f(n-2) + f(n-3) + ... + f(n-(n-1)) + f(n-n) 

 

说明: 

1)这里的f(n) 代表的是n个台阶有一次1,2,...n阶的 跳法数。

2)n = 1时,只有1种跳法,f(1) = 1

3) n = 2时,会有两个跳得方式,一次1阶或者2阶,这回归到了问题(1) ,f(2) = f(2-1) + f(2-2) 

4) n = 3时,会有三种跳得方式,1阶、2阶、3阶,

    那么就是第一次跳出1阶后面剩下:f(3-1);第一次跳出2阶,剩下f(3-2);第一次3阶,那么剩下f(3-3)

    因此结论是f(3) = f(3-1)+f(3-2)+f(3-3)

5) n = n时,会有n中跳的方式,1阶、2阶...n阶,得出结论:

    f(n) = f(n-1)+f(n-2)+...+f(n-(n-1)) + f(n-n) => f(0) + f(1) + f(2) + f(3) + ... + f(n-1)

    

6) 由以上已经是一种结论,但是为了简单,我们可以继续简化:

    f(n-1) = f(0) + f(1)+f(2)+f(3) + ... + f((n-1)-1) = f(0) + f(1) + f(2) + f(3) + ... + f(n-2)

    f(n) = f(0) + f(1) + f(2) + f(3) + ... + f(n-2) + f(n-1) = f(n-1) + f(n-1)

    可以得出:

    f(n) = 2*f(n-1)

    

7) 得出最终结论,在n阶台阶,一次有1、2、...n阶的跳的方式时,总得跳法为:

              | 1       ,(n=0 ) 

f(n) =     | 1       ,(n=1 )

              | 2*f(n-1),(n>=2)

1

2

3

4

5

6

7

8

9

10

11

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);

        }

    }

}

矩形覆盖:我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

思路:

依旧是斐波那契数列

2*n的大矩形,和n个2*1的小矩形

其中target*2为大矩阵的大小

有以下几种情形:

1⃣️target <= 0 大矩形为<= 2*0,直接return 1;

2⃣️target = 1大矩形为2*1,只有一种摆放方法,return1;

3⃣️target = 2 大矩形为2*2,有两种摆放方法,return2;

4⃣️target = n 分为两步考虑:

        第一次摆放一块 2*1 的小矩阵,则摆放方法总共为f(target - 1)

       
       


第一次摆放一块1*2的小矩阵,则摆放方法总共为f(target-2)

因为,摆放了一块1*2的小矩阵(用√√表示),对应下方的1*2(用××表示)摆放方法就确定了,所以为f(targte-2)

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

 

面试题11:旋转数组的最小数字

题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        if(array == null) return 0;
        int index1 = 0;
        int index2 = array.length - 1;
        int mid = 0;
        while(array[index1] >= array[index2]) {
            if(index2 - index1 == 1) {
                mid = index2;
                break;
            }
            mid = (index1 + index2) / 2;
            //顺序遍历
            if(array[index1] == array[index2] &&
               array[index1] == array[mid]) {
                int min = array[index1];
                for(int i = index1 + 1; i <= index2; i++) {
                    if(min > array[i]) {
                        min = array[i];
                    }
                }
                return min;
            }
            if(array[mid] >= array[index1]) {
                index1 = mid;
            } else if(array[mid] <= array[index2]) {
                index2 = mid;
            }
        }
        return array[mid];
    }
}

面试题12:矩阵中的路径

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

public class Solution {
    public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
    {
        if(matrix == null || rows < 1 || cols < 1 || str == null)
            return false;
        boolean[] visited = new boolean[rows * cols];
        for(int row = 0; row < rows; row++) {
            for(int col = 0; col < cols; col++) {
                if(hasPath(matrix, rows, cols, str, row, col,
                              visited, 0)) {
                    return true;
                }
            }
        }
        return false;
    }
    public boolean hasPath(char[] matrix, int rows, int cols, char[] str,
                          int row, int col, boolean[] visited, int pathLength) {
        if(pathLength == str.length) {
            return true;
        }
        boolean hasPath = false; 
        if(row >= 0 && row < rows && col >= 0 && col < cols && 
           !visited[row * cols + col] && 
           str[pathLength] == matrix[row * cols + col]) {
            visited[row * cols + col] = true;
            pathLength++;
            hasPath = hasPath(matrix, rows, cols, str, row + 1, col,
                              visited, pathLength) ||
                hasPath(matrix, rows, cols, str, row - 1, col,
                              visited, pathLength) ||
                hasPath(matrix, rows, cols, str, row, col + 1,
                              visited, pathLength) ||
                hasPath(matrix, rows, cols, str, row, col - 1,
                              visited, pathLength);
            if(!hasPath) {
                pathLength--;
                visited[row * cols + col] = false;
            }
        }
        return hasPath;
    }

}

面试题13、机器人的运动范围

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

public class Solution {
    public int movingCount(int threshold, int rows, int cols) {
		if (threshold < 0)
			return 0;
		boolean[] visited = new boolean[rows * cols];
		int count = movingCountCore(threshold, rows, cols, 0, 0, visited);
		return count;
	}
 
	private int movingCountCore(int threshold, int rows, int cols, int row, int col, boolean[] visited) {
		int count = 0;
		if (check(threshold, rows, cols, row, col, visited)) {
			visited[row * cols + col] = true;
			count = 1 + movingCountCore(threshold, rows, cols, row + 1, col, visited)
					+ movingCountCore(threshold, rows, cols, row - 1, col, visited)
					+ movingCountCore(threshold, rows, cols, row, col + 1, visited)
					+ movingCountCore(threshold, rows, cols, row, col - 1, visited);
		}
		return count;
	}
	
	/*
	 * 判断数字是否满足条件
	 */
	private boolean check(int threshold, int rows, int cols, int row, int col, boolean[] visited) {
		if (row >= 0 && row < rows && col >= 0 && col < cols && visited[row * cols + col] == false
				&& getDigitSum(col) + getDigitSum(row) <= threshold)
			return true;
		return false;
	}
	/*
	 * 获得数字的数位之和
	 */
	private int getDigitSum(int col) {
		int sum=0;
		while(col!=0){
			sum+=col%10;
			col=col/10;
		}
		return sum;
	}
}

面试题14:剪绳子

给你一根长度为n的绳子,请把绳子剪成m段(m,n都是大于1 的整数)。每段绳子长度的可能最大乘积是多少。

分析问题的时候,很容易就可以得出这个问题的递归式:记长度为n的最大乘积为f(n),易知f(n)=max{ f(i)*f(n-i) },其中0<i<n。但是递归求解过程中,有很多子问题会重复求解,效率不高。这种子问题重叠较多的递归式,适合采用动态规划的方式求解。这儿就是使用自底向上的方式求解问题的,采用额外的存储空间,记录子问题的解,后续就可以直接使用,提高时间效率。动态规划实际上就是用空间效率换区时间效率的一种做法。

首先,计算出最基本的几个子问题的解,然后根据递归式向上求解较大问题,重要的是,过程中的解都得记录下来。

public static int maxProductAfterCutting(int len){
		if(len<2)
			return 0;
		if(len==2)
			return 1;
		if(len==3)
			return 2;
		//存储长度从 0-len 的最大结果
		int[] result=new int[len+1];
		result[0]=0;
		result[1]=1;
		result[2]=2;
		result[3]=3;
		
		//自底向上开始求解
		int max=0;
		for(int i=4;i<=len;i++){
			max=0;
			for(int j=1;j<=i/2;j++){
				int tempResult=result[j]*result[i-j];
				if(max<tempResult)
					max=tempResult;
				result[i]=max;
			}
		}
		max=result[len];
		return max;
	}

贪婪:

public static int maxProductWithGreedy(int len){
		if(len<2)
			return 0;
		if(len==2)
			return 1;
		if(len==3)
			return 2;
		//啥也不管,先尽可能减去长度为3的段
		int timeOfThree=len/3;
		
		//判断还剩下多少,再进行处理
		if(len-timeOfThree*3==1)
			timeOfThree-=1;
		int timeOfTwo=(len-timeOfThree*3)/2;
		
		return (int) ((Math.pow(3, timeOfThree))*(Math.pow(2, timeOfTwo)));
	}

面试题15:二进制中1的个数

题目:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

解法一:

public class Solution {
    public int NumberOf1(int n) {
        int count = 0;
        int flag = 1;
        while(flag != 0) {
            if((flag&n) != 0) {
                count++;
            }
            flag = flag << 1;
        }
        return count;
    }
}

解法二:

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

面试题16:数值的整数次方

题目:给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

常规解法:

public class Solution {
    public double Power(double base, int exponent) {
        if(base == 0) return 0.0;
        int absExponent = exponent;
        if(exponent < 0) absExponent = -exponent;
        double result = handle(base, absExponent);
        if(exponent < 0) result = 1.0/result;
        return result;
  }
    private double handle(double base, int absExponent) {
        double result = 1.0;
        for(int i = 0; i < absExponent; i++) {
            result = result * base;
        }
        return result;
    }
}

优化解法:

public class Solution {
    public double Power(double base, int exponent) {
        if(base == 0) return 0.0;
        int absExponent = exponent;
        if(exponent < 0) absExponent = -exponent;
        double result = handle(base, absExponent);
        if(exponent < 0) result = 1.0/result;
        return result;
  }
    private double handle(double base, int absExponent) {
        if(absExponent == 0) return 1;
        if(absExponent == 1) return base;
        //除半
        double result = handle(base, absExponent >> 1);
        result = result * result;
        //是否奇数
        if((absExponent & 1) == 1) result = result * base;
        return result;
    }
}

面试题17:打印从1到最大的n位数

题目:输入数字n,按顺序打印出从1到最大的n位十进制数,比如输入3,则打印出1、2、3一直到最大的3位数999.

	/**字符串上模拟加法
	 * 
	 * @param n
	 */
	public void printToMax(int n){
		if(n < 0)
			return;
		char[] number = new char[n];
//		初始化		
//		for(int i = 0; i < n; i++)
//			number[i] = '0';
		Arrays.fill(number, '0');
		while(!increment(number)){
			printNumber(number);
		}
		return;
		
	}
	public boolean increment(char[] num){
		boolean isOverflow = false;
		int size = num.length;
		int carry = 0;
		for(int i = size - 1; i >= 0; i--){
			int temp = num[i] - '0' + carry;
			if(i == size - 1)
				temp++;
			if(temp >= 10){
				if(i == 0) //最高位溢出
					isOverflow = true;
				else{
					temp -= 10;
					carry = 1;
					num[i] = (char) ('0' + temp);
				}
			}else{
				num[i] = (char)('0' + temp);
				break;
			}
		}
		return isOverflow;
	}
	public void printNumber(char[] num){
		int size = num.length;
		int i = 0;
		while(i < size && num[i] == '0') //i < size在前,否则越界
			i++;
		//char[] printNum = new char[size - i];
		//System.arraycopy(num, i, printNum, 0, size - i);//复制数组
		if(i == size)//不打印全0
			return;
		char[] printNum = Arrays.copyOfRange(num, i, size);//复制数组
		System.out.println(printNum);
	}
	/**字符每一位进行全排列
	 * 
	 * @param n
	 */
	public void printToMax2(int n){
		if(n <= 0) return;
		char[] number = new char[n];
		Arrays.fill(number, '0');
		printOrder(number,n,0);
	}
	public void printOrder(char[] number, int length, int index){
		if(index == length) return;
		for(int i = 0; i <= 9; i++){
			number[index] = (char)('0' + i);
			if(index == length - 1){
				printNumber(number);
			}
			printOrder(number, length, index+ 1);
		}
	}

面试18:删除链表中重复的节点:

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

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

    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public ListNode deleteDuplication(ListNode pHead)
    {
        if(pHead == null || pHead.next == null) return pHead;
        ListNode head = new ListNode(0);
        head.next = pHead;
        ListNode preNode = head;
        ListNode node = head.next;
        while(node != null) {
            if(node.next != null && node.val == node.next.val) {
                while(node.next != null && node.val == node.next.val) {
                    node = node.next;
                }
                preNode.next = node.next;
                node = node.next;
            } else {
                preNode = node;
                node = node.next;
            }
        }
        return head.next;
    }
}

面试题19:正则表达式匹配

题目:请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配。

思路:

当模式中的第二个字符不是“*”时:

1、如果字符串第一个字符和模式中的第一个字符相匹配,那么字符串和模式都后移一个字符,然后匹配剩余的。

2、如果 字符串第一个字符和模式中的第一个字符相不匹配,直接返回false。

 

而当模式中的第二个字符是“*”时:

如果字符串第一个字符跟模式第一个字符不匹配,则模式后移2个字符,继续匹配。如果字符串第一个字符跟模式第一个字符匹配,可以有3种匹配方式:

1、模式后移2字符,相当于x*被忽略;

//2、字符串后移1字符,模式后移2字符;

3、字符串后移1字符,模式不变,即继续匹配字符下一位,因为*可以匹配多位;


public class Solution {
    public boolean match(char[] str, char[] pattern) {
    if (str == null || pattern == null) {
        return false;
    }
    int strIndex = 0;
    int patternIndex = 0;
    return matchCore(str, strIndex, pattern, patternIndex);
}
  
public boolean matchCore(char[] str, int strIndex, char[] pattern, int patternIndex) {
    //有效性检验:str到尾,pattern到尾,匹配成功
    if (strIndex == str.length && patternIndex == pattern.length) {
        return true;
    }
    //pattern先到尾,匹配失败
    if (strIndex != str.length && patternIndex == pattern.length) {
        return false;
    }
    //模式第2个是*,且字符串第1个跟模式第1个匹配,分3种匹配模式;如不匹配,模式后移2位
    if (patternIndex + 1 < pattern.length && pattern[patternIndex + 1] == '*') {
        if ((strIndex != str.length && pattern[patternIndex] == str[strIndex]) || (pattern[patternIndex] == '.' && strIndex != str.length)) {
            return matchCore(str, strIndex, pattern, patternIndex + 2)//模式后移2,视为x*匹配0个字符
                  //  || matchCore(str, strIndex + 1, pattern, patternIndex + 2)//视为模式匹配1个字符
                    || matchCore(str, strIndex + 1, pattern, patternIndex);//*匹配1个,再匹配str中的下一个
        } else {
            return matchCore(str, strIndex, pattern, patternIndex + 2);
        }
    }
    //模式第2个不是*,且字符串第1个跟模式第1个匹配,则都后移1位,否则直接返回false
    if ((strIndex != str.length && pattern[patternIndex] == str[strIndex]) || (pattern[patternIndex] == '.' && strIndex != str.length)) {
        return matchCore(str, strIndex + 1, pattern, patternIndex + 1);
    }
    return false;
    }
}

面试题20:表示数值的字符串

题目:请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。

public class Solution {
    public boolean isNumeric(char[] str) {
        boolean sign = false, decimal = false, hasE = false;
        for (int i = 0; i < str.length; i++) {
            if (str[i] == 'e' || str[i] == 'E') {
                if (i == str.length - 1) return false; // e后面一定要接数字
                if (hasE) return false;  // 不能同时存在两个e
                hasE = true;
            } else if (str[i] == '+' || str[i] == '-') {
                // 第二次出现+-符号,则必须紧接在e之后
                if (sign && str[i-1] != 'e' && str[i-1] != 'E') return false;
                // 第一次出现+-符号,且不是在字符串开头,则也必须紧接在e之后
                if (!sign && i > 0 && str[i-1] != 'e' && str[i-1] != 'E') return false;
                sign = true;
            } else if (str[i] == '.') {
              // e后面不能接小数点,小数点不能出现两次
                if (hasE || decimal) return false;
                decimal = true;
            } else if (str[i] < '0' || str[i] > '9') // 不合法字符
                return false;
        }
        return true;
    }
}

面试题21:调整数组顺序使奇数位于偶数前面

题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

思路:用两个指针,第一个指针从头开始找,直到找到偶数,第二个指针从尾开始找,直到找到奇数,把两数交换,继续寻找,直到两指针相遇。

public class Solution {
    public void reOrderArray(int [] array) {
        if(array == null) return;
        int low = 0;
        int high = array.length - 1;
        while(low < high) {
            while(low < high && array[low] % 2 != 0)
                low++;
            while(low < high && array[high] % 2 == 0)
                high--;
            if(low < high) {
                int temp = array[low];
                array[low] = array[high];
                array[high] = temp;
            }
        }
    }
}

面试题22:链表中倒数第k个节点

题目:输入一个链表,输出该链表中倒数第k个结点。

思路:定义两个指针,第一个指针先走k-1步,然后两个指针一起走,当第一个指针的下一个节点为null时,第二个指针到了倒数第k个节点。

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

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        if(head == null || k <= 0) return null;
        ListNode pA = head;
        ListNode pB = head;
        for(int i = 0; i < k - 1; i++) {
            if(pA.next != null) {
                pA = pA.next;
            } else {
                return null;
            }
        }
        while(pA.next != null) {
            pA = pA.next;
            pB = pB.next;
        }
        return pB;
    }
}

题目23:链表中环的入口节点

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

思路一:如果链表中 有n个结点,指针P1在链表上向前移动n步,然后两个指针以相同的速度向前移动。
 当第二个指针指向环的入口结点时,第一个指针已经围绕着环走了一圈又回到了入口结点。

所以首先要得到环中结点的数目

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

    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    private ListNode meetingNode(ListNode pHead) {
        if(pHead == null)
            return null;
        ListNode pSlow = pHead.next;
        if(pSlow == null)
            return null;
        ListNode pFast = pHead.next.next;
        while(pFast != null && pSlow != null) {
            if(pFast == pSlow)
                return pFast;
            pSlow = pSlow.next;
            pFast = pFast.next;
            if(pFast == null) 
                return null;
            pFast = pFast.next;
        }
        return null;
    }
    
    public ListNode EntryNodeOfLoop(ListNode pHead)
    {
        ListNode meetingNode = meetingNode(pHead);
        if(meetingNode == null)
            return null;
        //得到环中节点的数目
        int nodeNum = 1;
        ListNode pNode1 = meetingNode;
        while(pNode1.next != meetingNode) {
            nodeNum++;
            pNode1 = pNode1.next;
        }
        //先移动pNode1,次数为环中节点的数目
        pNode1 = pHead;
        for(int i = 0; i < nodeNum; i++) {
            pNode1 = pNode1.next;
        }
        //再移动pNode1和pNode2
        ListNode pNode2 = pHead;
        while(pNode1 != pNode2) {
            pNode1 = pNode1.next;
            pNode2 = pNode2.next;
        }
        return pNode1;
    }
}

思路二:

两个指针,慢指针每次走一步,快指针一次走两步。相遇时,慢指针走了a+b,快指针走了a+b+c+b,又因为快指针走的步数是慢指针的两倍所以2*(a+b)=a+b+c+b,即a=c,此时只要把快指针从头开始走并且一次走一步,当快慢指针相遇时就到了环的入口。

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

    ListNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public ListNode EntryNodeOfLoop(ListNode pHead){
        if(pHead == null || pHead.next == null|| pHead.next.next == null) 
            return null;
        ListNode pSlow=pHead.next;
        ListNode pFast = pHead.next.next;
        //先判断有没有环
        while(pFast != pSlow){
            if(pFast.next != null && pFast.next.next != null){
                pFast = pFast.next.next;
                pSlow = pSlow.next;
            }else{
                //没有环,返回
                return null;
            }
        }
        //循环出来的话就是有环,且此时fast==slow.
        pFast = pHead;
        while(pFast != pSlow){
            pFast = pFast.next;
            pSlow = pSlow.next;
        }
        return pSlow;
    }
}

面试题24:反转链表

题目:输入一个链表,反转链表后,输出新链表的表头。

思路:三个指针,pPre指向已经反转好的链表,pNode指向要转的链表,pNext指向未反转的链表。每次把pNode的next指向pPre,然后更新三个指针。当pNode为空时反转结束。

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

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode ReverseList(ListNode head) {
        if(head == null)
            return null;
        ListNode pPre = null;
        ListNode pNode = head;
        while(pNode != null) {
            ListNode pNext = pNode.next;
            pNode.next = pPre;
            pPre = pNode;
            pNode = pNext;
        }
        return pPre;
    }
}

面试题25:合并两个排序的链表

题目:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

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

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1 == null) 
            return list2;
        if(list2 == null)
            return list1;
        ListNode node = null;
        if(list1.val <= list2.val) {
            node = list1;
            node.next = Merge(list1.next, list2);
        } else {
            node = list2;
            node.next = Merge(list1, list2.next);
        }
        return node;
    }
}
/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1 == null){
            return list2;
        }
        if(list2 == null){
            return list1;
        }
        ListNode mergeHead = null;
        ListNode current = null;     
        while(list1!=null && list2!=null){
            if(list1.val <= list2.val){
                if(mergeHead == null){
                   mergeHead = current = list1;
                }else{
                   current.next = list1;
                   current = current.next;
                }
                list1 = list1.next;
            }else{
                if(mergeHead == null){
                   mergeHead = current = list2;
                }else{
                   current.next = list2;
                   current = current.next;
                }
                list2 = list2.next;
            }
        }
        if(list1 == null){
            current.next = list2;
        }else{
            current.next = list1;
        }
        return mergeHead;
    }
}

面试题26:树的子结构

题目:输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

思路:先递归找到根节点相等的点再进行递归判断是不是树的子结构。

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

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

    }

}
*/
public class Solution {
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        boolean result = false;
        if(root1 != null && root2 != null) {
            if(equal(root1.val, root2.val)) {
                result = doesTree1HaveTree2(root1, root2);
            }
            if(!result)
                result = HasSubtree(root1.left, root2);
            if(!result)
                result = HasSubtree(root1.right, root2);
        }
        return result;
    }
    private boolean doesTree1HaveTree2(TreeNode root1, TreeNode root2) {
        if(root2 == null)
            return true;
        if(root1 == null)
            return false;
        if(!equal(root1.val, root2.val)) 
            return false;
        return doesTree1HaveTree2(root1.left, root2.left) &&
            doesTree1HaveTree2(root1.right, root2.right);
    }
    private boolean equal(int num1, int num2) {
        return num1 == num2;
    }
}

面试题27:树的镜像

题目:操作给定的二叉树,将其变换为源二叉树的镜像。

输入描述:

二叉树的镜像定义:源二叉树 
    	    8
    	   /  \
    	  6   10
    	 / \  / \
    	5  7 9 11
    	镜像二叉树
    	    8
    	   /  \
    	  10   6
    	 / \  / \
    	11 9 7  5

思路:递归交换二叉树的左右节点

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

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

    }

}
*/
public class Solution {
    public void Mirror(TreeNode root) {
        if(root == null)
            return;
        if(root.left == null && root.right == null)
            return;
        TreeNode temp = root.left;
        root.left = root.right;
        root.right = temp;
        if(root.left != null)
            Mirror(root.left);
        if(root.right != null)
            Mirror(root.right);
    }
}

面试题28:对称的二叉树

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

/*
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)
    {
        return isSymmetrical(pRoot, pRoot);
    }
    private boolean isSymmetrical(TreeNode pRoot1, TreeNode pRoot2) {
        if(pRoot1 == null && pRoot2 == null)
            return true;
        if(pRoot1 == null || pRoot2 == null)
            return false;
        if(pRoot1.val != pRoot2.val)
            return false;
        return isSymmetrical(pRoot1.left, pRoot2.right) &&
            isSymmetrical(pRoot1.right, pRoot2.left);
    }
}

面试题29:顺时针打印矩阵

题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
       if(matrix == null)
           return null;
        int rows = matrix.length;
        int cols = matrix[0].length;
        int start = 0;
        ArrayList<Integer> list = new ArrayList<>();
        while(rows > 2 * start && cols > 2 * start) {
            print(list, matrix, rows, cols, start);
            start++;
        }
        return list;
    }
    private void print(ArrayList<Integer> list, int[][] matrix, int rows,
                      int cols, int start) {
        int endX = cols - 1 - start;
        int endY = rows - 1 - start;
        //从左到右打印一行
        for(int i = start; i <= endX; i++) {
            list.add(matrix[start][i]);
        }
        //从上到下打印一行
        if(start < endY) {
            for(int i = start + 1; i <= endY; i++) {
                list.add(matrix[i][endX]);
            }
        }
        //从右到左打印一行
        if(start < endX && start < endY) {
            for(int i = endX - 1; i >= start; i--) {
                list.add(matrix[endY][i]);
            }
        }
        //从下到上打印一行
        if(start < endX && start < endY - 1) {
            for(int i = endY - 1; i >= start + 1; i--) {
                list.add(matrix[i][start]);
            }
        }
    }
}

面试题30:包含min函数的栈

题目:定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。

思路:用两个栈,其中的辅助存入当前栈最小的值,弹出时两个栈一起弹出。

import java.util.Stack;

public class Solution {
    Stack<Integer> data = new Stack<>();
    Stack<Integer> min = new Stack<>();
    
    public void push(int node) {
        data.push(node);
        if(min.size() == 0 || min.peek() > node) {
            min.push(node);
        } else {
            min.push(min.peek());
        }
    }
    
    public void pop() {
        data.pop();
        min.pop();
    }
    
    public int top() {
        return data.peek();
    }
    
    public int min() {
        return min.peek();
    }
}

面试题31:栈的压入、弹出序列

题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)

思路:用一个栈,每次入栈后,循环判断栈顶元素是否和出栈元素一样,一样就出栈并向右移动指针。

import java.util.ArrayList;
import java.util.*;

public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        boolean bPossible = false;
        if(pushA.length == 0 || popA.length == 0){
            bPossible = false;
        }
        Stack<Integer> stack = new Stack<Integer>();
        int popIndex = 0;
        for(int i = 0; i < pushA.length; i++) {
            stack.push(pushA[i]);
            while(!stack.isEmpty() && stack.peek() == popA[popIndex]) {
                stack.pop();
                popIndex++;
            }
        }
        if(stack.isEmpty() && popA.length == popIndex) {
            bPossible =true;
        } else {
            bPossible =false;
        }
        return bPossible;
    }
}

面试题32:从上往下打印二叉树

题目:从上往下打印出二叉树的每个节点,同层节点从左至右打印。

思路:用队列,每次从队列中取出一元素并把它左右儿子放进去。

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

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

    }

}
*/
public class Solution {
    public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
        if(root == null) 
            return new ArrayList<Integer>();
        ArrayList<Integer> list = new ArrayList<Integer>();
        Queue<TreeNode> queue = new LinkedList<TreeNode>();
        queue.add(root);
        while(!queue.isEmpty()) {
            TreeNode node = queue.poll();
            list.add(node.val);
            if(node.left != null) {
                queue.add(node.left);
            } 
            if(node.right != null){
                queue.add(node.right);
            }
        }
        return list;
    }
}

面试题32.2:分行从上到下打印二叉树

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

import java.util.*;


/*
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>> result = new ArrayList<>();
        if(pRoot == null)
            return result;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(pRoot);
        while(!queue.isEmpty()) {
            int count = queue.size();
            ArrayList<Integer> list = new ArrayList<>();
            while(count > 0) {
                TreeNode node = queue.poll();
                list.add(node.val);
                if(node.left != null) {
                    queue.offer(node.left);
                }
                if(node.right != null) {
                    queue.offer(node.right);
                }
                count--;
            }
            result.add(list);
        }
        return result;
    }
}

面试题32.3:之字形打印二叉树

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

import java.util.*;

/*
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>> result = new ArrayList<ArrayList<Integer>>();
        if (pRoot == null) {
            return result;
        }
        Stack<TreeNode> currLevel = new Stack<TreeNode>();
        Stack<TreeNode> nextLevel = new Stack<TreeNode>();
        Stack<TreeNode> tmp;
        currLevel.push(pRoot);
        boolean normalOrder = true;
        while (!currLevel.isEmpty()) {
            ArrayList<Integer> currLevelResult = new ArrayList<Integer>();
            while (!currLevel.isEmpty()) {
                TreeNode node = currLevel.pop();
                currLevelResult.add(node.val);
                if (normalOrder) {
                    if (node.left != null) {
                        nextLevel.push(node.left);
                    }
                    if (node.right != null) {
                        nextLevel.push(node.right);
                    }
                } else {
                    if (node.right != null) {
                        nextLevel.push(node.right);
                    }
                    if (node.left != null) {
                        nextLevel.push(node.left);
                    }
                }
            }
            result.add(currLevelResult);
            tmp = currLevel;
            currLevel = nextLevel;
            nextLevel = tmp;
            normalOrder = !normalOrder;
        }
        return result;
    }
}

面试题33:二叉搜索树的后序遍历序列

题目:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        if(sequence.length == 0)
            return false;
        if(sequence.length == 1)
            return true;
        return ju(sequence, 0, sequence.length - 1);
         
    }
     
    public boolean ju(int[] a,int start,int root){
        if(start >= root)
            return true;
        int i = root;
        //从后面开始找
        while(i > start && a[i - 1] > a[root])
            i--;//找到比根小的坐标
        //从前面开始找 star到i-1应该比根小
        for(int j = start; j < i - 1; j++)
            if(a[j] > a[root])
                return false;;
        return ju(a, start, i - 1)&&ju(a, i, root - 1);
    }
}

面试题34:二叉树中和为某一值的路径

题目:输入一颗二叉树的跟节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的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 {
    private ArrayList<ArrayList<Integer>> listAll = new ArrayList<ArrayList<Integer>>();
    private ArrayList<Integer> list = new ArrayList<Integer>();
    
    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
        if(root == null) return listAll;
        list.add(root.val);
        target = target - root.val;
        if(target == 0 && root.left == null && root.right == null)
            listAll.add(new ArrayList<Integer>(list));
        FindPath(root.left, target);
        FindPath(root.right, target);
        list.remove(list.size() - 1);
        return listAll;
    }
}

面试题35:复杂链表的复制

题目:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

hash法

/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/

import java.util.HashMap;
 
public class Solution {
    public RandomListNode Clone(RandomListNode pHead)
    {
        if(pHead == null) return null;
        HashMap<RandomListNode, RandomListNode> map = new HashMap<RandomListNode, RandomListNode>();
        RandomListNode newHead = new RandomListNode(pHead.label);
        RandomListNode pre = pHead, newPre = newHead;
        map.put(pre, newPre);
          
        while(pre.next != null){
            newPre.next = new RandomListNode(pre.next.label);
            pre = pre.next;
            newPre = newPre.next;
            map.put(pre, newPre);
        }
        pre = pHead;
        newPre = newHead;
        while(newPre != null){
            newPre.random = map.get(pre.random);
            pre = pre.next;
            newPre = newPre.next;
        }
        return newHead;
    }
}

不使用辅助空间:

/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/

/*
*解题思路:
*1、遍历链表,复制每个结点,如复制结点A得到A1,将结点A1插到结点A后面;
*2、重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;
*3、拆分链表,将链表拆分为原链表和复制后的链表
*/
public class Solution {
    public RandomListNode Clone(RandomListNode pHead) {
        if(pHead == null) {
            return null;
        }
         
        RandomListNode currentNode = pHead;
        //1、复制每个结点,如复制结点A得到A1,将结点A1插到结点A后面;
        while(currentNode != null){
            RandomListNode cloneNode = new RandomListNode(currentNode.label);
            RandomListNode nextNode = currentNode.next;
            currentNode.next = cloneNode;
            cloneNode.next = nextNode;
            currentNode = nextNode;
        }
         
        currentNode = pHead;
        //2、重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;
        while(currentNode != null) {
            currentNode.next.random = currentNode.random == null ? null : currentNode.random.next;
            currentNode = currentNode.next.next;
        }
         
        //3、拆分链表,将链表拆分为原链表和复制后的链表
        currentNode = pHead;
        RandomListNode pCloneHead = pHead.next;
        while(currentNode != null) {
            RandomListNode cloneNode = currentNode.next;
            currentNode.next = cloneNode.next;
            cloneNode.next = cloneNode.next == null ? null : cloneNode.next.next;
            currentNode = currentNode.next;
        }
        return pCloneHead;
    }
}

面试题36:二叉搜索树与双向链表

题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

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

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

    }

}
*/
public class Solution {
    TreeNode head = null;
    TreeNode realHead = null;
    public TreeNode Convert(TreeNode pRootOfTree) {
        ConvertSub(pRootOfTree);
        return realHead;
    }
     
    private void ConvertSub(TreeNode pRootOfTree) {
        if(pRootOfTree==null) return;
        ConvertSub(pRootOfTree.left);
        if (head == null) {
            head = pRootOfTree;
            realHead = pRootOfTree;
        } else {
            head.right = pRootOfTree;
            pRootOfTree.left = head;
            head = pRootOfTree;
        }
        ConvertSub(pRootOfTree.right);
    }
}

面试题37:序列化二叉树

题目:请实现两个函数,分别用来序列化和反序列化二叉树

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

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

    }

}
*/
/*
    算法思想:根据前序遍历规则完成序列化与反序列化。所谓序列化指的是遍历二叉树为字符串;所谓反序列化指的是依据字符串重新构造成二叉树。
    依据前序遍历序列来序列化二叉树,因为前序遍历序列是从根结点开始的。当在遍历二叉树时碰到Null指针时,这些Null指针被序列化为一个特殊的字符“#”。
    另外,结点之间的数值用逗号隔开。
*/
public class Solution {
    int index = -1;   //计数变量
  
    String Serialize(TreeNode root) {
        StringBuilder sb = new StringBuilder();
        if(root == null){
            sb.append("#,");
            return sb.toString();
        }
        sb.append(root.val + ",");
        sb.append(Serialize(root.left));
        sb.append(Serialize(root.right));
        return sb.toString();
  }
    TreeNode Deserialize(String str) {
        index++;
        String[] strr = str.split(",");
        TreeNode node = null;
        if(!strr[index].equals("#")){
            node = new TreeNode(Integer.valueOf(strr[index]));
            node.left = Deserialize(str);
            node.right = Deserialize(str);
        }
        return node;
  }
}

面试题38:字符串的排序

题目:输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。

方法一:

import java.util.*;

public class Solution {
    public ArrayList<String> Permutation(String str) {
        List<String> resultList = new ArrayList<>();
        if(str.length() == 0)
            return (ArrayList)resultList;
        //递归的初始值为(str数组,空的list,初始下标0)
        fun(str.toCharArray(),resultList,0);
        Collections.sort(resultList);
        return (ArrayList)resultList;
    }
     
    private void fun(char[] ch,List<String> list,int i){
        //这是递归的终止条件,就是i下标已经移到char数组的末尾的时候,考虑添加这一组字符串进入结果集中
        if(i == ch.length-1){
            //判断一下是否重复
            if(!list.contains(new String(ch))){
                list.add(new String(ch));
                return;
            }
        }else{
            //这一段就是回溯法,这里以"abc"为例
             
            //递归的思想与栈的入栈和出栈是一样的,某一个状态遇到return结束了之后,会回到被调用的地方继续执行
             
            //1.第一次进到这里是ch=['a','b','c'],list=[],i=0,我称为 状态A ,即初始状态
            //那么j=0,swap(ch,0,0),就是['a','b','c'],进入递归,自己调自己,只是i为1,交换(0,0)位置之后的状态我称为 状态B 
            //i不等于2,来到这里,j=1,执行第一个swap(ch,1,1),这个状态我称为 状态C1 ,再进入fun函数,此时标记为T1,i为2,那么这时就进入上一个if,将"abc"放进list中
            /-------》此时结果集为["abc"]
             
            //2.执行完list.add之后,遇到return,回退到T1处,接下来执行第二个swap(ch,1,1),状态C1又恢复为状态B
            //恢复完之后,继续执行for循环,此时j=2,那么swap(ch,1,2),得到"acb",这个状态我称为C2,然后执行fun,此时标记为T2,发现i+1=2,所以也被添加进结果集,此时return回退到T2处往下执行
            /-------》此时结果集为["abc","acb"]
            //然后执行第二个swap(ch,1,2),状态C2回归状态B,然后状态B的for循环退出回到状态A
             
            //             a|b|c(状态A)
            //               |
            //               |swap(0,0)
            //               |
            //             a|b|c(状态B)
            //             /  \
            //   swap(1,1)/    \swap(1,2)  (状态C1和状态C2)
            //           /      \
            //         a|b|c   a|c|b
             
            //3.回到状态A之后,继续for循环,j=1,即swap(ch,0,1),即"bac",这个状态可以再次叫做状态A,下面的步骤同上
            /-------》此时结果集为["abc","acb","bac","bca"]
             
            //             a|b|c(状态A)
            //               |
            //               |swap(0,1)
            //               |
            //             b|a|c(状态B)
            //             /  \
            //   swap(1,1)/    \swap(1,2)  (状态C1和状态C2)
            //           /      \
            //         b|a|c   b|c|a
             
            //4.再继续for循环,j=2,即swap(ch,0,2),即"cab",这个状态可以再次叫做状态A,下面的步骤同上
            /-------》此时结果集为["abc","acb","bac","bca","cab","cba"]
             
            //             a|b|c(状态A)
            //               |
            //               |swap(0,2)
            //               |
            //             c|b|a(状态B)
            //             /  \
            //   swap(1,1)/    \swap(1,2)  (状态C1和状态C2)
            //           /      \
            //         c|b|a   c|a|b
             
            //5.最后退出for循环,结束。
             
            for(int j=i;j<ch.length;j++){
                swap(ch,i,j);
                fun(ch,list,i+1);
                swap(ch,i,j);
            }
        }
    }
     
    //交换数组的两个下标的元素
    private void swap(char[] str, int i, int j) {
            if (i != j) {
                char t = str[i];
                str[i] = str[j];
                str[j] = t;
            }
        }
    }

面试题39:数组中出现次数超过一半的数字

题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

时间复杂度O(n)

方法一:用hashMap

import java.util.*;
public class Solution {
    public int MoreThanHalfNum_Solution(int [] array) {
        if(array == null || array.length == 0)
            return 0;
        if(array.length == 1)
            return array[0];
        HashMap<Integer,Integer> map = new HashMap<Integer,Integer>();
        for(int i=0;i<array.length;i++){
            if(!map.containsKey(array[i])){
               map.put(array[i],1);
            }else{
                int count = map.get(array[i]);
                count = count + 1;
                if(count > array.length / 2) 
                    return array[i];
                map.put(array[i], count);
            }
        }
        return 0;
    }
}

方法二:基于快排思想

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

public class Solution {

    public int MoreThanHalfNum_Solution(int [] array) {

   if(array.length<=0)

            return 0;

         

        int start = 0;

        int length = array.length;

        int end  = length-1;

        int middle = length>>1;

         

        int index = Partition(array,start,end);

         

         

        while(index!=middle){

             

            if(index>middle){

                index = Partition(array,start,index-1);

            }

            else{

                index = Partition(array,index+1,end);

            }

        }

        int result = array[middle];

         

        int times = 0;

        for(int i=0;i<length;++i){

            if(array[i] == result)

                times++;

        }

        if(times*2<length){

            System.out.println(times);

            return 0;

        }else{

            return result;

        }

    }

       public int Partition(int[] array,int start,int end){

        int flag = (array[start]+array[end])/2;

         

        while(start<end){

            while(array[end]>flag){

                end--;

            }

            swap(array,start,end);

            while(array[start]<=flag){

                start++;

            }

            swap(array,start,end);

        }

        return start;

    }

    public void swap(int[] array,int num1,int num2){

        int temp =array[num1];

        array[num1] =array[num2];

        array[num2] =temp;

    }

}

方法三:基于数组特点

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

public class Solution {

    public int MoreThanHalfNum_Solution(int [] array) {

      if(array.length<=0){

            return 0;

        }

        int result = array[0];

        int times = 1;

         

        for(int i=0;i<array.length;i++){

            if(times==0){

                result = array[i];

                times =1;

            }else if(array[i]==result)

                times++;

             else

                times--;

        }

        int time = 0;

        for(int i=0;i<array.length;++i){

            if(array[i] == result)

                time++;

        }

        if(time*2<array.length){

            System.out.println(time);

            return 0;

        }else{

            return result;

        }

    }

}

面试题40:最小的k个数

题目:输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。

import java.util.ArrayList;
import java.util.PriorityQueue;
import java.util.Comparator;
public class Solution {
   public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
       ArrayList<Integer> result = new ArrayList<Integer>();
       int length = input.length;
       if(k > length || k == 0){
           return result;
       }
        PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(k, new Comparator<Integer>() {
 
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2.compareTo(o1);
            }
        });
        for (int i = 0; i < length; i++) {
            if (maxHeap.size() != k) {
                maxHeap.offer(input[i]);
            } else if (maxHeap.peek() > input[i]) {
                Integer temp = maxHeap.poll();
                temp = null;
                maxHeap.offer(input[i]);
            }
        }
        for (Integer integer : maxHeap) {
            result.add(integer);
        }
        return result;
    }
}

 

面试题41:数据流中的中位数

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

import java.util.*;
public class Solution {
    private int count = 0;
    private PriorityQueue<Integer> minHeap = new PriorityQueue<>();
    private PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(15, new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2 - o1;
        }
    });

    public void Insert(Integer num) {
        if (count %2 == 0) {//当数据总数为偶数时,新加入的元素,应当进入小根堆
            //(注意不是直接进入小根堆,而是经大根堆筛选后取大根堆中最大元素进入小根堆)
            //1.新加入的元素先入到大根堆,由大根堆筛选出堆中最大的元素
            maxHeap.offer(num);
            int filteredMaxNum = maxHeap.poll();
            //2.筛选后的【大根堆中的最大元素】进入小根堆
            minHeap.offer(filteredMaxNum);
        } else {//当数据总数为奇数时,新加入的元素,应当进入大根堆
            //(注意不是直接进入大根堆,而是经小根堆筛选后取小根堆中最大元素进入大根堆)
            //1.新加入的元素先入到小根堆,由小根堆筛选出堆中最小的元素
            minHeap.offer(num);
            int filteredMinNum = minHeap.poll();
            //2.筛选后的【小根堆中的最小元素】进入大根堆
            maxHeap.offer(filteredMinNum);
        }
        count++;
    }

    public Double GetMedian() {
        if (count %2 == 0) {
            return new Double((minHeap.peek() + maxHeap.peek())) / 2;
        } else {
            return new Double(minHeap.peek());
        }
    }
}

面试题42:连续子数组的最大和

题目:HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)

方法一:

public class Solution {
     public int FindGreatestSumOfSubArray(int[] array) {
         if (array.length==0 || array==null) {
             return 0;
         }
         int curSum=0;
         int greatestSum=0x80000000;
         for (int i = 0; i < array.length; i++) {
             if (curSum<=0) {
                 curSum=array[i]; //记录当前最大值
             }else {
                 curSum+=array[i]; //当array[i]为正数时,加上之前的最大值并更新最大值。
             }
             if (curSum>greatestSum) {
                 greatestSum=curSum; 
             }
         }
         return greatestSum;
     }
 }

方法二:

/**
使用动态规划
F(i):以array[i]为末尾元素的子数组的和的最大值,子数组的元素的相对位置不变
F(i)=max(F(i-1)+array[i] , array[i])
res:所有子数组的和的最大值
res=max(res,F(i))

如数组[6, -3, -2, 7, -15, 1, 2, 2]
初始状态:
    F(0)=6
    res=6
i=1:
    F(1)=max(F(0)-3,-3)=max(6-3,3)=3
    res=max(F(1),res)=max(3,6)=6
i=2:
    F(2)=max(F(1)-2,-2)=max(3-2,-2)=1
    res=max(F(2),res)=max(1,6)=6
i=3:
    F(3)=max(F(2)+7,7)=max(1+7,7)=8
    res=max(F(2),res)=max(8,6)=8
i=4:
    F(4)=max(F(3)-15,-15)=max(8-15,-15)=-7
    res=max(F(4),res)=max(-7,8)=8
以此类推
最终res的值为8
*/
public class Solution {
    public  int FindGreatestSumOfSubArray(int[] array) {
            int res = array[0]; //记录当前所有子数组的和的最大值
            int max=array[0];   //包含array[i]的连续数组最大值
            for (int i = 1; i < array.length; i++) {
                max=Math.max(max+array[i], array[i]);
                res=Math.max(max, res);
            }
            return res;
    }
 }

面试题43:1~n整数中1出现的次数

题目:求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

方法一:每个数字求1的个数然后加起来

public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        int number = 0;
        
        for(int i = 1; i <= n; i++)
            number = number + numberOf1(i);
        return number;
    }
    private int numberOf1(int n) {
        int number = 0;
        while(n != 0) {
            if(n % 10 == 1)
                number++;
            n = n / 10;
        }
        return number;
    }
}

面试题44:数字序列中某一位的数字

题目:数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从0开始计数)是5,第13位是1,第19位是4,等等。请写一个函数求任意位对应的数字。

主要思路: 
举例分析,比如找第1001位数字, 
1)1位数的数值有10个:0~9,数字为10×1=10个,显然1001>10,跳过这10个数值,在后面找第991(1001-10)位数字。 
2)2位数的数值有90个:10~99,数字为90×2=180个,且991>180,继续在后面找第811(991-180)位数字。 
3)3位数的数值有900个:100~999,数字为900×3=2700个,且811<2700,说明第811位数字在位数为3的数值中。由于811=270×3+1,所以第811位是从100开始的第270个数值即370的第二个数字,就是7。 
按此规律,可求出其他数字。
 

public class DigitsInSequence
{
    public static void main(String[] args)
    {
        System.out.println(digitAtIndex(9)); //9
        System.out.println(digitAtIndex(189)); //数字99的最后一位:9
        System.out.println(digitAtIndex(190)); //数字100的第一位:1
    }

    private static int digitAtIndex(int index)
    {
        if (index < 0) return -1;
        int digits = 1;
        while (true)
        {
            int digitNumbers = countOfNumbersFor(digits); //当前位数的数值个数
            //数值乘上它的位数等于数字个数,
            //比如,两位数有90个(10~99),每个数值有2个数字,总数字个数为180
            int countOfNumbers = digitNumbers * digits;
            if (index < countOfNumbers)
            {
                return digitAtIndex(index, digits);
            } else
            {
                //在下一位中查找
                index = index - countOfNumbers;
                digits++;
            }
        }
    }

    //digits位数的数字个数,
    //两位数有9*10=90个(10~99),三位数有9*100=900个(100~999)
    private static int countOfNumbersFor(int digits)
    {
        if (digits == 1)
            return 10;

        int count = (int) Math.pow(10, digits - 1);//阶层
        return 9 * count;
    }

    private static int digitAtIndex(int index, int digits)
    {
        //对应的数值
        int number = beginNumberFor(digits) + index / digits;
        //从数值右边开始算的位置
        int indexFromRight = digits - index % digits;
        //去除右边的indexFromRight-1个数字
        for (int i = 1; i < indexFromRight; i++)
            number /= 10;
        //求个位数字
        return number % 10;
    }

    //digits位数的第一个数字,两位数从10开始,三位数从100开始
    private static int beginNumberFor(int digits)
    {
        if (digits == 1)
            return 0;

        return (int) Math.pow(10, digits - 1);
    }
}

面试题45:把数组排成最小的数

题目:输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class Solution {
    public String PrintMinNumber(int [] numbers) {
        String s = "";
        ArrayList<Integer> list= new ArrayList<Integer>();
        
        for(int i = 0; i < numbers.length; i++){
            list.add(numbers[i]);
        }
        Collections.sort(list, new Comparator<Integer>(){
            
            public int compare(Integer str1,Integer str2){
                String s1=str1+""+str2;
                String s2=str2+""+str1;
                return s1.compareTo(s2);
            }
        });
        for(int j : list){
            s = s + j;
        }
        return s;
    }
}

面试47:礼物的最大价值

题目:在一个m×n的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格直到到达棋盘的右下角。给定一个棋盘及其上面的礼物,请计算你最多能拿到多少价值的礼物?

主要思路:使用动态规划,f(i,j)表示到达坐标[i,j]时能拿到的最大礼物总和。则当前格子f(i,j)可由左边格子f(i-1,j)或f(i,j-1)上面格子到达。因此,递归式子为: 
f(i,j)=max(f(i−1,j),f(i,j−1))+gift[i,j],
f(i,j)=max(f(i−1,j),f(i,j−1))+gift[i,j],

其中,gift[i,j]=坐标[i,j]格子里的礼物

public class MaxValueOfGifts
{
    public static void main(String[] args)
    {
        int[][] values = {
                {1, 2, 3},
                {4, 5, 6},
                {7, 8, 9}};
        System.out.println(getMaxPathValue(values));  //29
        int[][] values1 = {{1, 10, 3, 8}};
        System.out.println(getMaxPathValue(values1));  //22
        int[][] values2 = {
                {1},
                {1},
                {5},
                {3}};
        System.out.println(getMaxPathValue(values2));  //10
    }

    private static int getMaxPathValue(int[][] values)
    {
        if (values == null) return 0;

        int rows = values.length;
        if (rows <= 0) return 0;
        int cols = values[0].length;
        if (cols <= 0) return 0;

        int[][] maxValues = new int[rows][cols];
        for (int i = 0; i < rows; ++i)
        {
            for (int j = 0; j < cols; ++j)
            {
                int fromLeft = 0; //左边
                int fromUp = 0; //上面

                if (i > 0)
                    fromUp = maxValues[i - 1][j];
                if (j > 0)
                    fromLeft = maxValues[i][j - 1];

                maxValues[i][j] = Math.max(fromLeft, fromUp) + values[i][j];
            }
        }
        return maxValues[rows - 1][cols - 1];
    }
}

面试题48:最长不含重复字符的子字符串

题目:在一个m×n的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格直到到达棋盘的右下角。给定一个棋盘及其上面的礼物,请计算你最多能拿到多少价值的礼物?

主要思路:使用动态规划,f(i,j)表示到达坐标[i,j]时能拿到的最大礼物总和。则当前格子f(i,j)可由左边格子f(i-1,j)或f(i,j-1)上面格子到达。因此,递归式子为: 
f(i,j)=max(f(i−1,j),f(i,j−1))+gift[i,j],
f(i,j)=max(f(i−1,j),f(i,j−1))+gift[i,j],

其中,gift[i,j]=坐标[i,j]格子里的礼物
 

//题目:请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子
//字符串的长度。假设字符串中只包含从'a'到'z'的字符。
 
public class LongestSubstringWithoutDup {
    public static int  maxLength(String str) {
        if(str==null || str.length()<=0)
            return 0;
        int preLength=0;  //即f(i-1)
        int curLength=0;  //即f(i)
        int maxLength=0;
        int[] pos= new int[26];  //用于存放字母上次出现的位置
        for(int i=0;i<pos.length;i++)
            pos[i]=-1;
        for(int i=0;i<str.length();i++) {
            int letterNumber = str.charAt(i)-'a';
            if(pos[letterNumber]<0 || i-pos[letterNumber]>preLength) {
                curLength=preLength+1;
            }else {
                curLength=i-pos[letterNumber];
            }
            pos[letterNumber]=i;
            if(curLength>maxLength)
                maxLength=curLength;           
            preLength=curLength;
        }
        return maxLength;
    }
     
    public static void main(String[] args) {
        System.out.println(maxLength("arabcacfr")==4);
        System.out.println(maxLength("a")==1);
        System.out.println(maxLength("aaa")==1);
        System.out.println(maxLength("abcdef")==6);
        System.out.println(maxLength("")==0);
        System.out.println(maxLength(null)==0);
    }
}

面试题49:丑数

题目:把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if(index < 7)//前6个丑数1,2,3,4,5,6
            return index;
        int[] res = new int[index];
        res[0] = 1;
        int t2 = 0, t3 = 0, t5 = 0;//保存最小丑数的下标
        for(int i = 1; i < index; i++) {
            res[i] = Math.min(res[t2] * 2, Math.min(res[t3] * 3, res[t5] * 5));
            if(res[i] == res[t2] * 2)t2++;
            if(res[i] == res[t3] * 3)t3++;
            if(res[i] == res[t5] * 5)t5++;
        }
        return res[index - 1];
    }
}

面试题50:第一个只出现一次的字符

题目一:字符串中第一个只出现一次的字符

在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).

public class Solution {
    public int FirstNotRepeatingChar(String str) {
        if(str == null)
            return -1;
        char[] array = str.toCharArray();
        int[] table = new int[256];
        for(int i = 0; i < array.length; i++) {
            table[array[i]]++;
        }
        for (int i = 0; i < array.length; i++){
            if (table[array[i]] == 1){
                return i;
            }
        }
        return -1;
    }
}

题目二:字符流中第一个只出现一次的字符

题目描述

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

思路:

利用一个int型数组表示256个字符,这个数组初值置为-1.

没读出一个字符,将该字符的位置存入字符对应数组下标中。

若值为-1标识第一次读入,不为-1且》0表示不是第一次读入,将值改为-2.

之后在数组中找到>0的最小值,该数组下标对应的字符为所求。

public class Solution {
    //Insert one char from stringstream
    private int[] occurence = new int[256];
    private int index;
    
    public Solution(){
        for(int i=0;i<256;i++){
            occurence[i] = -1;
        }
        index = 0;
    }
    void Insert(char ch)
    {
        if(occurence[ch] == -1)
            occurence[ch] = index;
        else if(occurence[ch] >= 0)
            occurence[ch] = -2;
        
        index++;
    }
  //return the first appearence once char in current stringstream
    char FirstAppearingOnce()
    {
        char ch = '\0';
        int minIndex = Integer.MAX_VALUE;
        for(int i=0;i<256;i++){
            if(occurence[i] >=0 && occurence[i]<minIndex){
                ch = (char)i;
                minIndex = occurence[i];
            }
        }
        if(ch == '\0')
            return '#';
        return ch;
    }
}

面试题51:数组中的逆序对

题目:在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

public class Solution {
    int cnt;
 
    public int InversePairs(int[] array) {
        cnt = 0;
        if (array != null)
            mergeSortUp2Down(array, 0, array.length - 1);
        return cnt;
    }
 
    /*
     * 归并排序(从上往下)
     */
    public void mergeSortUp2Down(int[] a, int start, int end) {
        if (start >= end)
            return;
        int mid = (start + end) >> 1;
 
        mergeSortUp2Down(a, start, mid);
        mergeSortUp2Down(a, mid + 1, end);
 
        merge(a, start, mid, end);
    }
 
    /*
     * 将一个数组中的两个相邻有序区间合并成一个
     */
    public void merge(int[] a, int start, int mid, int end) {
        int[] tmp = new int[end - start + 1];
 
        int i = start, j = mid + 1, k = 0;
        while (i <= mid && j <= end) {
            if (a[i] <= a[j])
                tmp[k++] = a[i++];
            else {
                tmp[k++] = a[j++];
                cnt = cnt + mid - i + 1;  //当左大于右时,右指针会继续右移一位(所以count为左指针到中间的个数之和的和)
                cnt = cnt % 1000000007;
            }
        }
        while (i <= mid)
            tmp[k++] = a[i++];
        while (j <= end)
            tmp[k++] = a[j++];
        for (k = 0; k < tmp.length; k++)
            a[start + k] = tmp[k];
    }
}

面试题52:两个链表的第一个公关节点

题目:输入两个链表,找出它们的第一个公共结点。

链接:https://www.nowcoder.com/questionTerminal/6ab1d9a29e88450685099d45c9e31e46
来源:牛客网

//方法一:运用HasnMap的特性
import java.util.HashMap;
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode current1 = pHead1;
        ListNode current2 = pHead2;
 
 
        HashMap<ListNode, Integer> hashMap = new HashMap<ListNode, Integer>();
        while (current1 != null) {
            hashMap.put(current1, null);
            current1 = current1.next;
        }
        while (current2 != null) {
            if (hashMap.containsKey(current2))
                return current2;
            current2 = current2.next;
        }
 
        return null;
 
    }
}
 
 
 
 
//方法2:
 
public ListNode FindFirstCommonNodeII(ListNode pHead1, ListNode pHead2) {
        ListNode current1 = pHead1;// 链表1
        ListNode current2 = pHead2;// 链表2
        if (pHead1 == null || pHead2 == null)
            return null;
        int length1 = getLength(current1);
        int length2 = getLength(current2);
        // 两连表的长度差
         
        // 如果链表1的长度大于链表2的长度
        if (length1 >= length2) {
            int len = length1 - length2;
            // 先遍历链表1,遍历的长度就是两链表的长度差
            while (len > 0) {
                current1 = current1.next;
                len--;
            }
 
        }
        // 如果链表2的长度大于链表1的长度
        else if (length1 < length2) {
            int len = length2 - length1;
            // 先遍历链表1,遍历的长度就是两链表的长度差
            while (len > 0) {
                current2 = current2.next;
                len--;
            }
 
        }
        //开始齐头并进,直到找到第一个公共结点
        while(current1!=current2){
            current1=current1.next;
            current2=current2.next;
        }
        return current1;
 
    }
 
    // 求指定链表的长度
    public static int getLength(ListNode pHead) {
        int length = 0;
 
        ListNode current = pHead;
        while (current != null) {
            length++;
            current = current.next;
        }
        return length;
    }

面试题53:在排序数组中查找数字

题目:统计一个数字在排序数组中出现的次数。

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;
    }
    //递归写法
    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){
            return getFirstK(array, k, start, mid-1);
        }else{
            return mid;
        }
    }
    //循环写法
    private int getLastK(int [] array , int k, int start, int end){
        int length = array.length;
        int mid = (start + end) >> 1;
        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;
    }
}

题目二:0~n-1中缺失的数字

一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0到n-1之内。在范围0到n-1的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

思路

  分析易知,数组形式如下:

  如果从头到尾依次比较值与小标是否相等,时间复杂度为O(n),效率低。

  由于是排序数组,我们继续考虑使用二分查找算法,结合上图可知:

    当中间数字等于其下标时,我们在后半部分查找;

    当中间数字不等于其下标时,

    1)如果中间数字的前一个数字也不等于其下标,则在前半部分查找;

    2)如果中间数字的前一个数字等于其下标,则说明中间数字的下标即为我们所要找的数字。

Java代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

//题目:一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字

//都在范围0到n-1之内。在范围0到n-1的n个数字中有且只有一个数字不在该数组

//中,请找出这个数字。

 

public class MissingNumber {

    public int getMissingNumber(int[] arr) {

        if(arr==null || arr.length<=0)

            return -1;

        int low=0;

        int high=arr.length-1;

        while(low<=high) {

            int mid=(low+high)>>1;

            if(arr[mid]!=mid) {

                if(mid==0 || arr[mid-1]==mid-1)

                    return mid;

                high=mid-1;

            }else {

                low=mid+1;

            }

        }

        return -1;

    }

}

题目三:数组中数值和下标相等的元素 
假设一个单调递增的数组里的每个元素都是整数并且是唯一的。请编程实现一个函数找出数组中任意一个数值等于其下标的元素。例如,在数组{-3, -1, 1, 3, 5}中,数字3和它的下标相等。

主要思路:数组是递增有序的,使用二分查找。先获取数组的中间数,若中间数的值和下标相等,则找到一个满足条件的数;若中间数的值大于它的下标,则根据数组递增的特性可知:中间数以后的元素的值至少每个比前面大1,同时它们的下标每次也是增加1,从而右边的这些元素的值都大于它们的下标,因此需要继续在左边寻找。同理,若中间数的值小于它的下标,则中间数左边的那些元素的值也都小于它们的下标,因此需要继续在右边寻找。

关键点:二分查找,数值和下标的关系

时间复杂度:O(logn)

public class NumberOfValueEqualIndex
{
    public static void main(String[] args)
    {
        int[] data = {-3, -1, 1, 3, 5};
        System.out.println(findValueEqualIndex(data)); //3
        System.out.println(findValueEqualIndex(new int[]{-1, 0, 1, 2, 4})); //4
        System.out.println(findValueEqualIndex(new int[]{-1, 0})); //-1
        System.out.println(findValueEqualIndex(null)); //-1
    }

    //数组中数值和下标相等的元素
    private static int findValueEqualIndex(int[] data)
    {
        if (data == null) return -1;

        int left = 0;
        int right = data.length - 1;
        //二分查找的变形
        while (left <= right)
        {
            int middle = left + (right - left) / 2;

            if (data[middle] == middle) return middle;
            else if (data[middle] > middle) right = middle - 1; //在左边查找
            else if (data[middle] < middle) left = middle + 1;  //在右边查找
        }
        return -1;
    }
}

面试题54:二叉搜索树的第k大节点

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

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

    public TreeNode(int val) {
        this.val = val;
    }
}
*/
//思路:二叉搜索树按照中序遍历的顺序打印出来正好就是排序好的顺序。
//     所以,按照中序遍历顺序找到第k个结点就是结果。
public class Solution {
   int index = 0; //计数器
    TreeNode KthNode(TreeNode root, int k)
    {
        if(root != null){ //中序遍历寻找第k个
            TreeNode node = KthNode(root.left,k);
            if(node != null)
                return node;
            index ++;
            if(index == k)
                return root;
            node = KthNode(root.right,k);
            if(node != null)
                return node;
        }
        return null;
    }
}

面试题55:二叉树的深度

题目一:二叉树的深度

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

非递归写法:层次遍历

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

import java.util.Queue;

import java.util.LinkedList;

 

public class Solution {

    public int TreeDepth(TreeNode pRoot)

    {

        if(pRoot == null){

            return 0;

        }

        Queue<TreeNode> queue = new LinkedList<TreeNode>();

        queue.add(pRoot);

        int depth = 0, count = 0, nextCount = 1;

        while(queue.size()!=0){

            TreeNode top = queue.poll();

            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;

    }

}

递归写法,比较简单,不解释:

1

2

3

4

5

6

7

8

9

10

11

12

import java.lang.Math;

public class Solution {

    public int TreeDepth(TreeNode pRoot)

    {

        if(pRoot == null){

            return 0;

        }

        int left = TreeDepth(pRoot.left);

        int right = TreeDepth(pRoot.right);

        return Math.max(left, right) + 1;

    }

}

题目二:平衡二叉树

输入一棵二叉树,判断该二叉树是否是平衡二叉树。

public class Solution {
    public boolean IsBalanced_Solution(TreeNode root) {
        return getDepth(root) != -1;//-1代表不平衡
    }
     
    private int getDepth(TreeNode root) {
        if (root == null) return 0;
        int left = getDepth(root.left);
        if (left == -1) return -1;
        int right = getDepth(root.right);
        if (right == -1) return -1;
        return Math.abs(left - right) > 1 ? -1 : 1 + Math.max(left, right);
    }
}

面试题56:数组中数字出现的次数

题目一:数组中只出现一次的两个数字

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
public class Solution {
    public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
       if(array==null ||array.length<2)
           return ;
       int temp = 0;
       for(int i=0;i<array.length;i++)
           temp ^= array[i];
        
       int indexOf1 = findFirstBitIs(temp);
       for(int i=0;i<array.length;i++){
           if(isBit(array[i], indexOf1))
               num1[0]^=array[i];
           else
               num2[0]^=array[i];
       }
   }
   public int findFirstBitIs(int num){
       int indexBit = 0;
       while(((num & 1)==0) && (indexBit)<8*4){
           num = num >> 1;
           ++indexBit;
       }
       return indexBit;
   }
   public boolean isBit(int num,int indexBit){
       num = num >> indexBit;
       return (num & 1) == 1;
   }
}

题目二:数组中唯一只出现一次的数字

 在一个数组中除了一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。

回到顶部

思路

  这道题中数字出现了三次,无法像56-1) 数组中只出现一次的两个数字一样通过利用异或位运算进行消除相同个数字。但是仍然可以沿用位运算的思路。

  将所有数字的二进制表示的对应位都加起来,如果某一位能被三整除,那么只出现一次的数字在该位为0;反之,为1。

Java代码

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

//题目:在一个数组中除了一个数字只出现一次之外,其他数字都出现了三次。请

//找出那个只出现一次的数字。

 

public class NumberAppearingOnce {

    public static int findNumberAppearingOnce(int[] arr) {

        if(arr==null || arr.length<=0)

            throw new RuntimeException();

        int[] bitSum = new int[32];

        for(int i=0;i<32;i++)

            bitSum[i]=0;

        for(int i=0;i<arr.length;i++) {

            int bitMask=1;

            for(int j=31;j>=0;j--) {

                int bit=arr[i]&bitMask;  //注意arr[i]&bitMask不一定等于1或者0,有可能等于00010000

                if(bit!=0)

                    bitSum[j]+=1;

                bitMask=bitMask<<1;

            }

        }

        int result=0;

        for(int i=0;i<32;i++) {

            result=result<<1;

            result+=(bitSum[i]%3);

            //result=result<<1;  //不能放在后面,否则最前面一位就没了

        }

        return result;

    }

}

面试题57:和为s的数字

题目一:和为s的两个数字

链接:https://www.nowcoder.com/questionTerminal/390da4f7a00f44bea7c2f3d19491311b
来源:牛客网

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

题目二:和为s的连续正数序列

小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!

import java.util.ArrayList;
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:翻转字符串

题目一:翻转单词顺序

方法一:

链接:https://www.nowcoder.com/questionTerminal/3194a4f4cf814f63919d0790578d51f3
来源:牛客网

/*
    算法思想:先翻转整个句子,然后,依次翻转每个单词。
    依据空格来确定单词的起始和终止位置
*/
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);
         
    }
    public void reverse(char[] chars,int low,int high){
        while(low < high){
            char temp = chars[low];
            chars[low] = chars[high];
            chars[high] = temp;
            low++;
            high--;
        }
    }
}

方法二:

链接:https://www.nowcoder.com/questionTerminal/3194a4f4cf814f63919d0790578d51f3
来源:牛客网

/*
    算法思想:先翻转整个句子,然后,依次翻转每个单词。
    依据空格来确定单词的起始和终止位置
*/
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);
         
    }
    public void reverse(char[] chars,int low,int high){
        while(low < high){
            char temp = chars[low];
            chars[low] = chars[high];
            chars[high] = temp;
            low++;
            high--;
        }
    }
}

题目二:左旋转字符串

汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

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);
        return new String(chars);
    }
     
    public 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--;
        }
    }
}

面试题59:队列的最大值

题目一:滑动窗口的最大值

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{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]}。

import java.util.*;
/**
用一个双端队列,队列第一个位置保存当前窗口的最大值,当窗口滑动一次
1.判断当前最大值是否过期
2.新增加的值从队尾开始比较,把所有比他小的值丢掉
*/
public class Solution {
   public ArrayList<Integer> maxInWindows(int [] num, int size)
    {
        ArrayList<Integer> res = new ArrayList<>();
        if(size == 0) return res;
        int begin; 
        ArrayDeque<Integer> q = new ArrayDeque<>();
        for(int i = 0; i < num.length; i++){
            begin = i - size + 1;
            if(q.isEmpty())
                q.add(i);
            else if(begin > q.peekFirst())
                q.pollFirst();
         
            while((!q.isEmpty()) && num[q.peekLast()] <= num[i])
                q.pollLast();
            q.add(i);  
            if(begin >= 0)
                res.add(num[q.peekFirst()]);
        }
        return res;
    }
}

题目二:队列的最大值

请定义一个队列并实现函数max得到队列里的最大值,要求函数max、push_back和pop_front的时间复杂度都是O(1)。

思路

  与滑动窗口的最大值一题相似,利用一个双端队列来存储当前队列里的最大值以及之后可能的最大值。

  在定义题目要求功能的队列时,除了定义一个队列data存储数值,还需额外用一个队列maxmium存储可能的最大值;此外,还要定义一个数据结构,用于存放数据以及当前的index值,用于删除操作时确定是否删除maxmium中最大值。

  具体实现见代码,代码进行了一些测试,应该没有什么问题。

 

测试算例 

  尾部插入不同大小数字,删除头部数字。插入删除同时获取最大值。

回到顶部

Java代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

import java.util.ArrayDeque;

 

//题目:请定义一个队列并实现函数max得到队列里的最大值,要求函数max、

//push_back和pop_front的时间复杂度都是O(1)。

 

public class QueueWithMax {

    private ArrayDeque<InternalData>  data = new ArrayDeque<InternalData>();

    private ArrayDeque<InternalData> maximum = new ArrayDeque<InternalData>();

    private class InternalData{

        int number;

        int index;

        public InternalData(int number,int index) {

            this.number=number;

            this.index=index;

        }

    }

    private int curIndex;

     

    public void push_back(int number) {

        InternalData curData = new InternalData(number,curIndex);

        data.addLast(curData);

         

        while(!maximum.isEmpty() && maximum.getLast().number<number)

            maximum.removeLast();

        maximum.addLast(curData);

         

        curIndex++;  //别漏了这句

    }

     

    public void pop_front() {

        if(data.isEmpty()) {

            System.out.println("队列为空,无法删除!");

            return;

        }

        InternalData curData = data.removeFirst();

        if(curData.index==maximum.getFirst().index)

            maximum.removeFirst();

    }

     

    public int max() {

        if(maximum==null){

            System.out.println("队列为空,无法删除!");

            return 0;

        }

        return maximum.getFirst().number;

    }

     

    public static void main(String[] args) {

        QueueWithMax testQueue = new QueueWithMax();

        // {2}

        testQueue.push_back(2);

        System.out.println(testQueue.max()==2);

        // {2, 3}

        testQueue.push_back(3);

        System.out.println(testQueue.max()==3);

        // {2, 3, 4}

        testQueue.push_back(4);

        System.out.println(testQueue.max()==4);

        // {2, 3, 4, 2}

        testQueue.push_back(2);

        System.out.println(testQueue.max()==4);

        // {3, 4, 2}

        testQueue.pop_front();

        System.out.println(testQueue.max()==4);

        // {4, 2}

        testQueue.pop_front();

        System.out.println(testQueue.max()==4);

        // {2}

        testQueue.pop_front();

        System.out.println(testQueue.max()==2);

        // {2, 6}

        testQueue.push_back(6);

        System.out.println(testQueue.max()==6);

        // {2, 6, 2}

        testQueue.push_back(2);

        System.out.println(testQueue.max()==6);

        // {2, 6, 2, 5}

        testQueue.push_back(5);

        System.out.println(testQueue.max()==6);

        // {6, 2, 5}

        testQueue.pop_front();

        System.out.println(testQueue.max()==6);

        // {2, 5}

        testQueue.pop_front();

        System.out.println(testQueue.max()==5);

        // {5}

        testQueue.pop_front();

        System.out.println(testQueue.max()==5);

        // {5, 1}

        testQueue.push_back(1);

        System.out.println(testQueue.max()==5);    

    }

}

面试题60:n个骰子的点数

原题:把n个骰子扔到地上,所有骰子朝上一面的点数之后为s. 输入n,打印出s所有可能的值出现的概率。(每个骰子6个面,点数从1到6)

解法一:基于递归,时间效率不高

递归的思想一般是分而治之,把n个骰子分为第一个和剩下的n-1个。先计算第一个骰子每个点数出现的次数,再计算剩余n-1个骰子出现的点数之和。求n-1个骰子的点数之的方法和前面讲的一样,即再次把n-1个骰子分成两堆------第一个和剩下的n-2个。n个骰子,每个骰子6个面,总共有6n个组合。这6n个组合之中肯定有重复的,我们知道其范围是n~6n,对于每种情况我们可以用缓存机制记录下来,每当其发生一次我们令其对应的单元加1。

我们定义一个长度为6n-n+1的数组,和为s的点数出现的次数保存到数组第s-n个元素里。为什么是6n-n+1呢?因为n个骰子的和最少是n,最大是6n,介于这两者之间的每一个情况都可能会发生,总共6n-n+1种情况。下面是java源码:

 1     private static final int g_maxValue = 6;
 2     //基于递归求骰子点数,时间效率不高
 3     public static void PrintProbability(int number){
 4         if(number<1) return;
 5         int maxSum = number*g_maxValue;
 6         int[] pProbabilities = new int[maxSum-number+1];
 7         //初始化,开始统计之前都为0次
 8         for(int i=number;i<=maxSum;i++){
 9             pProbabilities[i-number] = 0;
10         }
11         double total = Math.pow(g_maxValue,number);
12         //probability(number,pProbabilities);这个函数计算n~6n每种情况出现的次数
13         probability(number,pProbabilities);
14         for(int i=number;i<=maxSum;i++){
15             double ratio = pProbabilities[i-number]/total;
16             System.out.println("i: "+i+" ratio: "+ratio);
17         }
18     }
19     public static void probability(int number,int[] pProbabilities){
20         for(int i=1;i<=g_maxValue;i++){//从第一个骰子开始
21             probability(number,number,i,pProbabilities);
22         }
23     }
24     //总共original个骰子,当前第 current个骰子,当前的和,贯穿始终的数组
25     public static void probability(int original,int current,int sum,int[] pProbabilities){
26         if(current==1){
27             pProbabilities[sum-original]++;
28         }else{
29             for(int i=1;i<=g_maxValue;i++){
30                 probability(original,current-1,sum+i,pProbabilities);
31             }
32         }
33     }

这种方法思路非常简洁,但是递归实现会存在子问题重复求解的情况发生,所以当number很大的时候,其性能会慢的让人不能接受。

解法二:基于循环,时间性能好

递归一般是自顶向下的分析求解,而基于循环的方法则是自底向上。基于循环的一般需要更少的空间和更少的时间,性能较好,但是一般代码比较难懂。

书上的讲解比较简单,代码没有注释,这里本人用java实现了书本上的方法,注释比较详细。

 1  //基于循环求骰子点数
 2     public static void PrintProbability_1(int number){
 3         if(number<1){
 4             return;
 5         }
 6         int[][] pProbabilities = new int[2][g_maxValue*number +1];
 7         for(int i=0;i<g_maxValue;i++){//初始化数组
 8              pProbabilities[0][i] = 0;
 9              pProbabilities[1][i] = 0;
10         }
11         int flag = 0;
12         for(int i=1;i<=g_maxValue;i++){//当第一次抛掷骰子时,有6种可能,每种可能出现一次
13             pProbabilities[flag][i] = 1;
14         }
15         //从第二次开始掷骰子,假设第一个数组中的第n个数字表示骰子和为n出现的次数,
16         //在下一循环中,我们加上一个新骰子,此时和为n的骰子出现次数应该等于上一次循环中骰子点数和为n-1,n-2,n-3,n-4,n-5,
17         //n-6的次数总和,所以我们把另一个数组的第n个数字设为前一个数组对应的n-1,n-2,n-3,n-4,n-5,n-6之和
18         for(int k =2;k<=number;k++){
19             for(int i=0;i<k;i++){//第k次掷骰子,和最小为k,小于k的情况是不可能发生的!所以另不可能发生的次数设置为0!
20                 pProbabilities[1-flag][i] = 0;
21             }
22             for(int i=k;i<=g_maxValue*k;i++){//第k次掷骰子,和最小为k,最大为g_maxValue*k
23                 pProbabilities[1-flag][i] = 0;//初始化,因为这个数组要重复使用,上一次的值要清0
24                 for(int j=1;j<=i&&j<=g_maxValue;j++){
25                     pProbabilities[1-flag][i] += pProbabilities[flag][i-j];
26                 }
27             }
28             flag = 1-flag;
29         }
30         double total = Math.pow(g_maxValue, number);
31         for(int i=number;i<=g_maxValue*number;i++){
32             double ratio = pProbabilities[flag][i]/total;
33             System.out.println("sum: "+i+" ratio: "+ratio);
34         }
35     }

面试题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。

/*
max 记录 最大值
min 记录  最小值
min ,max 都不记0
满足条件 1 数组长度 为5
        2 除0外没有重复的数字(牌)
        3 max - min <5
*/
public class Solution {
    public boolean isContinuous(int [] numbers) {
        if(numbers == null || numbers.length != 5)
            return false;
        int[]d = new int[14]; 
        int len = numbers.length; 
        int max = -1; 
        int min = 14; 
        for(int i = 0; i < len; i++){
            d[numbers[i]]++; 
            if(numbers[i] == 0){
                continue; 
            }
            if(d[numbers[i]] >1 ){
                return false; 
            }
            if(numbers[i] > max){
                max = numbers[i]; 
            } 
            if(numbers[i] < min){
                min = numbers[i]; 
            }
        }
        if(max - min<5){
            return true; 
        }
        return false;
    }
}

面试题62:圆圈中最后剩下的数字

题目:每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去....直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)

思路:如果只求最后一个报数胜利者的话,我们可以用数学归纳法解决该问题,为了讨      论方便,先把问题稍微改变一下,并不影响原意:

 问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人 继续从0开始报数。求胜利者的编号。

 我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新      的约瑟夫环(以编号为k=m%n的人开始):

        k  k+1  k+2  ... n-2, n-1, 0, 1, 2, ... k-2并且从k开始报0。

现在我们把他们的编号做一下转换:

k     --> 0

k+1   --> 1

k+2   --> 2

...

...

k-2   --> n-2

k-1   --> n-1

变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解: 例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情 况的解吗?!!变回去的公式很简单,相信大家都可以推出来:x'=(x+k)%n。

令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n]。

递推公式

f[1]=0;

f[i]=(f[i-1]+m)%i;  (i>1)

有了这个公式,我们要做的就是从1-n顺序算出f[i]的数值,最后结果是f[n]。 因为实际生活中编号总是从1开始,我们输出f[n]+1。

public class Solution {
    public 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;
    }
}

方法二:

 /*
    *这道题我用数组来模拟环,思路还是比较简单,但是各种下标要理清
*/
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if(n < 1 || m < 1) 
            return -1;
        int[] array = new int[n];
        int i = -1, step = 0, count = n;
        while(count > 0){   //跳出循环时将最后一个元素也设置为了-1
            i++;          //指向上一个被删除对象的下一个元素。
            if(i>=n)
                i=0;  //模拟环。
            if(array[i] == -1)
                continue; //跳过被删除的对象。
            step++;                     //记录已走过的。
            if(step == m) {               //找到待删除的对象。
                array[i] = -1;
                step = 0;
                count--;
            }        
        }
        return i;//返回跳出循环时的i,即最后一个被设置为-1的元素
    }
}

面试题63:股票的最大利润

题目:

一、股票的最大利润(一次卖出)

一个数组代表股票每天的价格,可以选择从某一天买入,然后之后的一天卖出,求能够获得的最大收益。

例如,一只股票在某些时间节点的价格为{9,11,8,5,7,12,16,14}。在价格为5的时候买入并在价格为16时卖出,则能获得最大的利润为11.

思路:记录当前最小值和最大差值

          1.最大利润就是计算后面的数字减去前面的数字得到的一个最大的差值;

          2.求总体的最大差值,需要的数据:当前的最小值,当前的最大差值;遍历求解即可。

public class GuPiao {
    public static void main(String[] args){
        int[] prices={9,11,8,5,7,12,16,14};
        int result=new GuPiao().maxProfit(prices);
        System.out.println("最大收益为:"+result);
    }
    public int maxProfit(int[] arr){
        int len=arr.length;
        if(len<2)
            return 0;
        int minPrice=arr[0];
        int maxDiff=arr[1]-minPrice;
        for(int i=2;i<arr.length;i++){
            if(arr[i-1]<minPrice)
                minPrice=arr[i-1];
            int curDiff=arr[i]-minPrice;
            if(curDiff>maxDiff)
                maxDiff=curDiff;
        }
        return maxDiff;
    }
}


二、股票的最大利润(至多2次交易)

1. 分段考虑

    以i为分界线,前i天的最大和i天后面的最大,分两段进行每次的一个交易;两段的最大和,则为最大的利润;
 

//9,11,8,5,7,12,16,14
public static int maxProfit_2(int[] arr){
        int len=arr.length;
        if(len<=1)
            return 0;
        int[] maxFormer=new int[len];
        int[] maxLatter=new int[len];
        int day,diff,minPrice,maxPrice,maxProfit;
        //计算某一天及其之前所有时间内的最大利益
        minPrice=arr[0];
        maxProfit=0;
        maxFormer[0]=0;
        for(day=1;day<len;day++){
            diff=arr[day]-minPrice;
            if(diff<0)
                minPrice=arr[day];  
            else if(diff>maxProfit)
                maxProfit=diff;     
            maxFormer[day]=maxProfit;
        }
        //计算某一天及其之后的所有时间内最大利益
        maxPrice=arr[len-1];
        maxProfit=0;
        maxLatter[len-1]=0;
        for(day=len-2;day>-1;day--){
            diff=maxPrice-arr[day];
            if(diff<0)
                maxPrice=arr[day];
            else if(diff>maxProfit)
                maxProfit=diff;
            maxLatter[day]=maxProfit;
        }
        //计算最大收益
        int sum;
        maxProfit=0;
        for(day=0;day<len;day++){
            sum=maxFormer[day]+maxLatter[day];
            if(sum>maxProfit)
                maxProfit=sum;
        }
        return maxProfit;
 
    }

 

三、股票的最大利润(可以对股票进行多次的买入和卖出)

思路:前一天买入,盈利则第二天卖出

         1.如果第二天的价格高于第一天的价格就可以以第二天的价格卖出,则卖出后立即再次买入;

         2.如果第二天的价格低于第一天的价格,那么就在第一天结束前就卖出,相当于不盈利。

            所以,通过逐个相邻两数进行比较即可,如果后面的大,则记为利润。

   

public int maxProfit_m(int[] arr){
        int len=arr.length;
        if(len<2)
            return 0;
        int maxProfit=0;
        for(int i=0;i<len-1;i++){
            //贪心法
            int profit=arr[i+1]-arr[i];
            if(profit>0)
                maxProfit+=profit;
        }
        return maxProfit;
    }

面试题64:求1+2+...+n

题目:求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

/*
解题思路:
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 sum = n;
        boolean ans = (n>0)&&((sum+=Sum_Solution(n-1))>0);
        return sum;
    }
}

面试题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;
            num2 = (num1&num2)<<1;
            num1 = temp;
        }
        return num1;
    }
}

面试题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]。不能使用除法。

剑指的思路:

B[i]的值可以看作下图的矩阵中每行的乘积。

下三角用连乘可以很容求得,上三角,从下向上也是连乘。

因此我们的思路就很清晰了,先算下三角中的连乘,即我们先算出B[i]中的一部分,然后倒过来按上三角中的分布规律,把另一部分也乘进去。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

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;

    }

}

面试题67:把字符串转换成整数


public class Solution {
    public static boolean flag;
    public static int StrToInt(String str) {
        flag = false;
        //判断输入是否合法
        if (str == null || str.trim().equals("")) {
            flag = true;
            return 0;
        }
        // symbol=0,说明该数为正数;symbol=1,该数为负数;start用来区分第一位是否为符号位
        int symbol = 0;
        int start = 0;
        char[] chars = str.trim().toCharArray();
        if (chars[0] == '+') {
            start = 1;
        } else if (chars[0] == '-') {
            start = 1;
            symbol = 1;
        }
        int result = 0;
        for (int i = start; i < chars.length; i++) {
            if (chars[i] > '9' || chars[i] < '0') {
                flag = true;
                return 0;
            }
            int sum= result * 10 + (int) (chars[i] - '0');
             
             
            if((sum-(int) (chars[i] - '0'))/10!=result){
                flag=true;
                return 0;
            }
            result=result * 10 + (int) (chars[i] - '0');
            /*
             * 本人认为java热门第一判断是否溢出是错误的,举个反例
             * 当输入为value=2147483648时,在计算机内部的表示应该是-2147483648
             * 显然value>Integer.MAX_VALUE是不成立的
            */
        }
        // 注意:java中-1的n次方不能用:(-1)^n .'^'异或运算
        // 注意,当value=-2147483648时,value=-value
        result = (int) Math.pow(-1, symbol) * result;
        return result;
    }
}

面试题68:树中两个节点的最低公共祖先

题目:输入两个树节点,求它们的最低公共祖先节点。

反问:这棵树是不是二叉树?

面试官:是二叉树,并且是二叉搜索树。

思路:

二叉搜索树是经过排序的,位于左子树的节点都比父节点小,位于右子树的节点都比父节点大。既然要找最低的公共祖先节点,我们可以从根节点开始进行比较。

若当前节点的值比两个节点的值都大,那么最低的祖先节点一定在当前节点的左子树中,则遍历当前节点的左子节点;

反之,若当前节点的值比两个节点的值都小,那么最低的祖先节点一定在当前节点的右子树中,则遍历当前节点的右子节点;

这样,直到找到一个节点,位于两个节点值的中间,则找到了最低的公共祖先节点。


/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
public class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root==null||root==p||root==q)
            return root;
        if(root.val>p.val&&root.val>q.val)
            return lowestCommonAncestor(root.left,p,q);
        if(root.val<p.val&&root.val<q.val)
            return lowestCommonAncestor(root.right,p,q);
        else
            return root;
    }
}

面试官:如果只是普通的二叉树呢?

反问:树的节点中有没有指向父节点的指针?

面试官:为什么需要指向父节点的指针?

答:如果存在parent指针,则分别从输入的p节点和q节点指向root根节点,其实这就是两个单链表。问题转化为求两个单链表相交的第一个公共节点

面试官:那如果不存在parent指针呢?
递归的解法如下:

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root==null||root==p||root==q)
            return root;
        TreeNode left = lowestCommonAncestor(root.left,p,q);
        TreeNode right = lowestCommonAncestor(root.right,p,q);
        if (left == null) 
            return right;
        if (right == null) 
            return left;
        return root;
    }

迭代解法:
需要我们保存下由root根节点到p和q节点的路径,并且将路径存入list中,则问题转化为求两个list集合的最后一个共同元素。

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {  
        if(root==null || p==null || q==null) 
            return null;
        List<TreeNode> pathp = new ArrayList<>();  
        List<TreeNode> pathq = new ArrayList<>();  
        pathp.add(root);  
        pathq.add(root);  
          
        getPath(root, p, pathp);  
        getPath(root, q, pathq);  
          
        TreeNode lca = null;  
        for(int i=0; i<pathp.size() && i<pathq.size(); i++) {  
            if(pathp.get(i) == pathq.get(i)) 
                lca = pathp.get(i);  
            else 
                break;  
        }  
        return lca;  
    }  
      
    private boolean getPath(TreeNode root, TreeNode n, List<TreeNode> path) {  
        if(root==n)   
            return true;  
        
        if(root.left!=null) {  
            path.add(root.left);  
            if(getPath(root.left, n, path))
                return true;  
            path.remove(path.size()-1);  
        }  
          
        if(root.right!=null) {  
            path.add(root.right);  
            if(getPath(root.right, n, path)) 
                return true;  
            path.remove(path.size()-1);  
        }  
        return false;  
    }

参考:https://blog.csdn.net/qq_25827845/article/details/74612786 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值