动态规划

1. 剑指 Offer 14- I. 剪绳子 I    动态规划来解

剑指offer java代码 java 剑指offer题解_剑指offer java代码

剑指offer java代码 java 剑指offer题解_i++_02

class Solution {

    public int cuttingRope(int n) {

        if (n == 2)

            return 1;

        if (n == 3)

            return 2;

        int dp[] = new int[n+1];

        // 动态规划一定有一个起始点,下面三个就是这个问题的起始点。当n>3的时候,2 3 可以不在分割,那么最大值就是他自己。

        dp[1] = 1;

        dp[2] = 2;

        dp[3] = 3;

        for(int i = 4; i <= n; i++){

            int max_value = 0;  // 记录所有可能中的最大值,然后将该值赋值给dp[i]

            for(int j = 1; j <= i/2; j++){

                if(max_value < dp[i-j] * dp[j]){

                    max_value = dp[i-j] * dp[j];

                }

            }

            dp[i] = max_value;

        }

        return dp[n];

    }

}

 

2. 剑指 Offer 14- II. 剪绳子 II,不能使用动态规划,因为存储在dp中的中间结果会溢出的(取模1000000007,int的溢出值为>2147483648) 。

class Solution {
    public int cuttingRope(int n) {
        if(n == 2) {
            return 1;
        }
        if(n == 3){
            return 2;
        }
        int mod = (int)1e9 + 7;
        long res = 1;
        while(n > 4) {
            res *= 3;
            res %= mod;    // 在过程中就对最终的结果取模,计算的结果会在1000000007~2147483648,不会大于2147483648的,此过程中不会存在数据溢出。
            n -= 3;
        }
        return (int)(res * n % mod);
    }
}

/*  

我们首先考虑对于一段长n的绳子,我们可以切出的结果包含什么?

1会包含吗? 不会,因为1 * (k - 1) < k, 只要把1和任何一个其他的片段组合在一起就有个更大的值
2可以
3可以
4可以吗? 它拆成两个2的效果和本身一样,因此也不考虑
5以上可以吗? 不可以,这些绳子必须拆,因为总有一种拆法比不拆更优,比如拆成 k / 2 和 k - k / 2

综上, 最后的结果只包含2和3(当然当总长度为2和3时单独处理), 那么很显然n >= 5时, 3*(n - 3) >= 2 * (n - 2) ,因此我们优先拆成3,最后剩余的拆成2。最后的结果一定是由若干个3和1或2个2组成.

*/

 

3. 剑指 Offer 60. n个骰子的点数 

剑指offer java代码 java 剑指offer题解_字符串_03

// 采用动态规划来求解。dp[当前骰子数][点数] = dp[当前骰子数-1][点数-1] + dp[当前骰子数-1][点数-2] + .... + dp[当前骰子数-1][点数-6]  // 点数依次减1,直到减到6,减去的点数由最后一个骰子来填充

class Solution {
    public double[] dicesProbability(int n) {
        double dp[][] = new double[n+1][6*n+1];  // 初始值全部为0;
        for(int i = 1; i < 7; i++){
            dp[1][i] = 1.0;    // 初始化dp,一个骰子,点数为i,可能的出现的次数
        }
        for(int i = 2; i <= n; i++){  // 第i个骰子
            for(int j = i; j <= 6*i; j++){  // 点数为j
                for(int k = j-1; k >= j-6 && k != 0; k--){  // 在点数j的基础上,依次减去1~6,减去的数可以由第i个骰子补上
                    dp[i][j] += dp[i-1][k];
                }
            }
        }
        double size = Math.pow(6,n);  // 三个骰子,就是6*6*6种可能
        int j = 0;
        double res[] = new double[5*n + 1];
        for(int i = n; i <= 6*n; i++){
            res[j++] = dp[n][i] / size;
        }
        return res;
    }
}

 

4.剑指 Offer 19. 正则表达式匹配

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

class Solution {
    public boolean isMatch(String s, String p) {    // p代表正则表达式
        int m = s.length() + 1, n = p.length() + 1;  
        boolean dp[][] = new boolean[m][n];  // 初始值默认全为false
        dp[0][0] = true; // s和p均为空字符串
        for(int i = 2; i < n; i = i+2){ // *如果出现在奇数位上,则其必然为false, a*bb*, 第一个b无法消除掉
            dp[0][i] = dp[0][i-2] && p.charAt(i-1) == '*';  // 当前位必须为*,且前两位必须为true,相当于当s为空的时候,只有a*b*c*这样才能符合条件
        }   // 当字符串为0或者1的时候,会直接跳过这里

        for(int i = 1; i < m; i++){      // 从s的第一个字符处开始
            for(int j = 1; j < n; j++){  // 从p的第一个字符处开始
                /* 
          将分成两种情况,一种是当前字符是否为* 
        */
                if(p.charAt(j-1) == '*'){  // 以下三种情况满足一种都可以
                    if(dp[i][j-2]) dp[i][j] = true;  // *之前的元素出现零次,且之前的两个字符串相等

                    // 最关键的一步
                    if(dp[i-1][j] && p.charAt(j-2) == s.charAt(i-1)) dp[i][j] = true;  //s当前的字符与p中*号之前的字符想等
                    // s = {b c b}, p = {b *},s中的第三个b等于p中的第一个b,但是s != p

                    if(dp[i-1][j] && p.charAt(j-2) == '.') dp[i][j] = true;  // 
                    // s = {a c b}, p = {a.*}  *是让.出现多次,而不是c出现多次
                }else{  // 当前两个字符要相等,或者j中的字符为.
                    if(dp[i-1][j-1] && s.charAt(i-1) == p.charAt(j-1)) dp[i][j] = true;  // 当前比较的两个字符之前的两个字符串要相等
                    if(dp[i-1][j-1] && p.charAt(j-1) == '.') dp[i][j] = true;  
                }
            }
        }
        return dp[m-1][n-1]; // dp  0~m-1, 0~n-1
    }
}
/*
    动态规划
*/

 




队列、栈操作

1. 剑指 Offer 59 - II. 队列的最大值

class MaxQueue {
    Queue<Integer> queue;
    Deque<Integer> deque;

    public MaxQueue() {
        queue = new LinkedList<Integer>();
        deque = new LinkedList<Integer>();
    }
    
    public int max_value() {
        if(deque.isEmpty()) return -1;
        return deque.peekFirst().intValue(); 
    }
    
    public void push_back(int value) {
        queue.add(value);
        while(deque.peekLast() != null && deque.peekLast() < value){
            deque.removeLast();
        }
        deque.addLast(new Integer(value));  // 双端队列deque中的元素会按照从大到小排列
    }
    
    public int pop_front() {
        if(queue.isEmpty()) return -1;
        int res = queue.poll().intValue();
        if(deque.getFirst().intValue() == res){
            deque.pollFirst();
        }
        return res;
    }
}

/*
本算法基于问题的一个重要性质:当一个元素进入队列的时候,它前面所有比它小的元素就不会再对答案产生影响。

举个例子,如果我们向队列中插入数字序列 1 1 1 1 2,那么在第一个数字 2 被插入后,数字 2 前面的所有数字 1 将不会对结果产生影响。因为按照队列的取出顺序,数字 2 只能在所有的数字 1 被取出之后才能被取出,因此如果数字 1 如果在队列中,那么数字 2 必然也在队列中,使得数字 1 对结果没有影响。

*/

 

 

 

其他算法

1. 剑指 Offer 20. 表示数值的字符串  有限状态机

剑指offer java代码 java 剑指offer题解_i++_04

class Solution {
    public boolean isNumber(String s) {
        Map[] states = {
            new HashMap<>() {{put(' ', 0); put('s', 1); put('d', 2); put('.', 4); }},  
            // 0  起始符号
            new HashMap<>() {{put('d', 2); put('.', 4);}},  
            // 1  前一位是符号,那么现在这位的状态就会与集合1中的数比较。符号后可以是点
            new HashMap<>() {{put('d', 2); put('.', 3); put('e', 5); put(' ', 8); }},  
            // 2  前一位是数字(小数点前的数字),那么现在这位的状态就会与集合2中的数比较
            new HashMap<>() {{put('d', 3); put('e', 5); put(' ', 8); }},  
            // 3  前一位是点,或者前一位是数字(小数点后的数字)。 12.也算数字
            new HashMap<>() {{put('d', 3);}},  
            // 4  前一位是点,小数点后必须有数字。 .3也算数字
            new HashMap<>() {{put('s', 6); put('d', 7);}},  
            // 5  前一位是e
            new HashMap<>() {{put('d', 7);}},  
            // 6  前一位是符号位
            new HashMap<>() {{put('d', 7); put(' ', 8);}},  
            // 7  前一位是数字。很好奇为什么指数中不能有小数???
            new HashMap<>() {{put(' ', 8);}}   
            // 8
        };
        char chs[] = s.toCharArray();
        int p = 0;
        char tmp;
        for(char i: chs){
            if(i >= '0' && i <= '9') tmp = 'd';
            else if(i == '+' || i == '-') tmp = 's';
            else if(i == '.') tmp = '.';
            else if(i == 'e' || i == 'E') tmp = 'e';
            else if(i == ' ') tmp = ' ';
            else tmp = '?';
            if(states[p].get(tmp) == null) return false;
            p = (int) states[p].get(tmp);
        }
        return p == 2 || p == 3 || p == 7 || p == 8;
    }
}

 

2. 剑指 Offer 41. 数据流中的中位数    利用堆排序

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

class MedianFinder { 
    Queue<Integer> A, B;    // B在左,A在右
    public MedianFinder() {
        A = new PriorityQueue<>(); // 小顶堆,保存较大的一半
        B = new PriorityQueue<>((x, y) -> (y - x)); // 大顶堆,保存较小的一半
    }

    public void addNum(int num) {
        if(A.size() != B.size()) {  //奇数的情况
            A.add(num);
            B.add(A.poll());  // 如果直接将元素压入B中,那么当元素很大的时候,会导致B堆顶的元素会大于A中的元素
        } else {
            B.add(num);
            A.add(B.poll());  // 先给B,在从B中获取最大的元素值扔给A,由此完成排序过程
        }
    }

    public double findMedian() {
        return A.size() != B.size() ? A.peek() : (A.peek() + B.peek()) / 2.0;
    }
}

/* 
这题的时间复杂度可以这么理解,假设插入50000次,那么一共有50000个数组,每个数组都要排序,但是每个数组都是基本有序的,所以它就是直接插入排序时间最省为o(n),所以总的时间复杂度应该为o(n*n), 有多少个数组*每个数组排序的时间复杂度。
直接插入排序能通过,但是会时间复杂度为o(n*n),如果采用堆来求解的话,时间复杂度为o(n*logn)
*/

剑指offer java代码 java 剑指offer题解_动态规划_05

剑指offer java代码 java 剑指offer题解_字符串_06