牛客网刷题笔记篇

字符串篇

字符串翻转

链接地址

import java.util.*;

public class Solution {
    public String trans(String s, int n) {
        // write code here
        if (n == 0) return s;
        StringBuffer stringBuffer = new StringBuffer();
        char step = 'a' -'A';
        for(int i = 0;i<n; i++) {
            char c = s.charAt(i);
            if(c >= 'a' && c <= 'z') {
                stringBuffer.append((char) (c - step));
            } else if (c >= 'A' && c <= 'Z') {
                stringBuffer.append((char) (c + step));
            } else {
                stringBuffer.append(c);
            }
        }

        stringBuffer = stringBuffer.reverse();
        for(int i = 0; i< n;i++) {
            int j = i;
            while(j <n && stringBuffer.charAt(j) != ' ') {
                j ++;
            }
            String temp = stringBuffer.substring(i, j);
            StringBuffer sb = new StringBuffer(temp);
            temp = sb.reverse().toString();
            stringBuffer.replace(i, j, temp);
            i = j;
        }
        return stringBuffer.toString();
        
    }
    
}

思路: 先将字符串大小写转换; 然后翻转整个字符串,再以空格为界限,翻转每一个词
用到的java 的基本能力:

  • StringBuffer 的reverse函数, 翻转一个字符串;
  • 取字符串某个位置的字符: s.charAt(index). String 和StringBuffer 都有此方法
  • 截取字符串,sb.substring(i, j)。 包括i不包括j。String和StringBuffer都有此方法
  • 某区间字符串替换,StringBuffer 才有,sb.replace(i, j, temp);

    Sting 的单个字符替换 或者 子串替换。s.replace('a', 'c'), 将s中的a替换为c。
    replacing "aa" with "b" in the string "aaa" will result in "ba" rather than "ab".

  • 大小写转换 相差‘a’ -‘A’; 小写数值较大, c >= 'a' && c <= 'z' 则是小写

最长公共前缀,注意关键字 前缀

题目地址

给你一个大小为 n 的字符串数组 strs ,其中包含n个字符串 , 编写一个函数来查找字符串数组中的最长公共前缀,返回这个公共前缀。
注意是前缀, 这个比最长公共字符串简单得多!!

import java.util.*;
public class Solution {
    public String longestCommonPrefix (String[] strs) {
        int n = strs.length;
        //空字符串数组
        if (n == 0)
            return "";
        //遍历第一个字符串的长度
        for (int i = 0; i < strs[0].length(); i++) {
            char temp = strs[0].charAt(i);
            //遍历后续的字符串
            for (int j = 1; j < n; j++)
                //比较每个字符串该位置是否和第一个相同
                if (i == strs[j].length() || strs[j].charAt(i) != temp)
                    //不相同则结束
                    return strs[0].substring(0, i);
        }
        //后续字符串有整个字一个字符串的前缀
        return strs[0];
    }
}

找出一个字符串中的最长连续重复字符

比如:“abcccdeef” 中,c连续出现3次,所以该字符串最长连续重复字符为c,长度为3.

// 最长连续重复子串
    // abcccdeef 的最长连续子串为ccc,长度为3
    // 用一个字符记录 对应的字符,一个数字记录出现次数
    public static void maxRepeatCharLen() {
        String s = "bcaddddefg";
        int n = s.length();
        int max = 1;
        char targetChar = s.charAt(0);
        int[] dp = new int[n];
        dp[0] = 1;
        for(int i = 1; i < n;i++) {
            if(s.charAt(i) == s.charAt(i-1)) {
                dp[i] = dp[i-1] + 1;
                if(dp[i] > max) {
                    max = dp[i];
                    targetChar = s.charAt(i);
                }
            } else {
                dp[i] = 1;
            }
        }
        StringBuffer sb = new StringBuffer();
        for(int i =0;i<max;i++) sb.append(targetChar);
        Logger.i(" target chars is: " + sb + ", len: " + max);
    }

一群字符串中的最长公共字符串(动态规划)

题目地址

import java.util.*;

public class Solution {
    /**
     * longest common substring
     * @param str1 string字符串 the string
     * @param str2 string字符串 the string
     * @return string字符串
     */
    public String LCS (String str1, String str2) {
        // write code here
        // 动态规划: dp[i +1][j+1] = if(s[i] == s[j]) dp[i][j] + 1
        int m = str1.length();
        int n = str2.length();
        int maxLen = 0, targetRightIndex = -1;
        int[][] dp = new int[m +1][n+1];
        for(int i = 0;i<m;i++)
        for(int j = 0;j<n;j++) {
            if(str1.charAt(i) == str2.charAt(j)) {
                dp[i+1][j+1] = dp[i][j] +1;
                if(maxLen < dp[i+1][j+1]) {
                    maxLen = dp[i+1][j+1];
                    targetRightIndex = i;
                }
            }
        }
        if(targetRightIndex == -1) return "";

        return str1.substring(targetRightIndex - maxLen +1, targetRightIndex +1);

    }
}

最小覆盖子串

链接地址

  • 描述
    给出两个字符串 s 和 t,要求在 s 中找出最短的包含 t 中所有字符的连续子串。数据范围:0 \le |S|,|T| \le100000≤∣S∣,∣T∣≤10000,保证s和t字符串中仅包含大小写英文字母
    要求:进阶:空间复杂度 O(n)O(n) , 时间复杂度 O(n)O(n)
    例如:
    S ="XDOYEZODEYXNZ"S="XDOYEZODEYXNZ"
    T ="XYZ"T="XYZ"
    找出的最短子串为"YXNZ""YXNZ".

注意:
如果 s 中没有包含 t 中所有字符的子串,返回空字符串 “”;
满足条件的子串可能有很多,但是题目保证满足条件的最短的子串唯一。

  • 解题要点
  1. s中必须包含t中的所有字符,t中同一个字符出现两次,在s中也至少出现2次。如s = "ababa", t="aa"; 则结果为“aba”,而不是“a”。
  2. 既然有出现次数要求,最好使用map来统计一下t中字符出现的次数
  3. 配合滑动窗口法,在满足条件的子串中找最短的。
  4. 使用三个变量分别记录目标区间上下界,区间长度; 使用两个移动下标表示动态窗口的下标
import java.util.*;
public class Solution {
    /**
     *
     * @param S string字符串
     * @param T string字符串
     * @return string字符串
     */
    //检查是否有小于0的
    boolean checkPass(int[] exist) {
        for (int i = 0; i < exist.length; i++) {
            if (exist[i] < 0)
                return false;
        }
        return true;
    }
    public String minWindow (String S, String T) {
        int n = S.length();
        int[] tSet = new int[128];
        int targetLeft = -1, targetRight = 0,
            targetLen = n + 1; // 最优结果下标及长度
        int fast = 0, slow = 0; // 动态窗口 下标
        for (int i = 0; i < T.length(); i++)
            tSet[T.charAt(i)] -= 1;
        for (fast = 0; fast < n; fast++) {
            tSet[S.charAt(fast)] += 1;
            // 在满足条件的区间内缩小范围
            while (checkPass(tSet)) {
                if (targetLen > fast - slow + 1) {
                    // 更新最短区间及长度
                    targetLen = fast - slow + 1;
                    targetLeft = slow;
                    targetRight = fast;
                }
                // 左移区间之前,需要将对应位置的标记减一
                tSet[S.charAt(slow)]--;
                slow ++;
            }
        }

        if (targetLeft == -1) {
            return "";
        } else {
            return S.substring(targetLeft, targetRight + 1);
        }
    }
}

括号匹配-求最长合法括号长度

题目地址

  • 描述
    给出一个长度为 n 的,仅包含字符 '(' 和 ')' 的字符串,计算最长的格式正确的括号子串的长度。

例1: 对于字符串 "(()" 来说,最长的格式正确的子串是 "()" ,长度为 2 .
例2:对于字符串 ")()())" , 来说, 最长的格式正确的子串是 "()()" ,长度为 4 .
例子: "(((()())" ,最长正确格式子串为"(()())" ,长度为6

  • 分析:
    这个问题是匹配问题,完全可以使用栈的结构,左括号入栈,右括号出栈;如果右括号时栈为空,说明括号非法,此位置之前得已经完成匹配了,加上这个肯定不是合法得,所以起始位置需要从下一个位置开始。
    如果右括号时非空,则pop栈顶,说明之前肯定有左括号。
import java.util.*;

public class Solution {
    /**
     * 
     * @param s string字符串 
     * @return int整型
     */
    public int longestValidParentheses (String s) {
        // write code here
        int n = s.length();
        int maxLen = 0;
        int start = -1;
        Stack<Integer> stack = new Stack<Integer>();
        for(int i = 0;i<n;i++) {
            if(s.charAt(i) == '(') {
                stack.push(i);
            } else {
                //非法括号,括号不可能是右括号开头
                if(stack.isEmpty()) {
                    start = i;
                } else {
                    stack.pop();
                    if(stack.isEmpty())
                        maxLen = Math.max(maxLen, i-start);
                    else
                        maxLen = Math.max(maxLen, i-stack.peek());
                }
            }
        }
        return maxLen;
    }
}

滑动窗口法

最长‘无重复’子数组,最长无重复子串

题目地址
最长无重复子数组,最长无重复子串,都是类似, 使用两个指针维持一个窗口.
用一个集合来存储当前不重复的子数组,对于每一个新元素:
如果新元素不在集合出现,则将其加入集合,right 右移, 并更新最长的长度maxLen和对应的右边界下标;否则,left 右移,集合中删掉最左边的数据,也就是从窗口左边界右移。

//滑动窗口法,维护两个指针
    public static void maxUniqueSubArrLen() {
        int[] arr = {1,2,4,5,2,4,6,7,2,1};
        int n = arr.length;
        int maxLen = 0, endIndex = 0;
        HashSet<Integer> set = new HashSet<>();
        int left = 0,right = 0;
        while(right < n) {
            if(set.contains(arr[right])) {
                set.remove(arr[left]);
                left++;
            } else {
                set.add(arr[right++]);
                // 这样还可以记录下最后的目标数组结果
                if( maxLen < set.size()) {
                    maxLen = set.size();
                    endIndex = right; // 记录最长串时的右下标
                }
            }
        }
        Logger.i(" len: " + maxLen);
        StringBuffer sb = new StringBuffer();
        for (int i = endIndex - maxLen; i< endIndex;i++){
            sb.append(arr[i] +" ");
        }
        Logger.i(" sub arr is: " + sb);
    }

动态规划

楼梯系列

青蛙跳楼梯

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

题目地址

public class Solution {
    public int jumpFloor(int target) {
        // 递推公式:f(n) = f(n-1) + f(n-2)
        // 第n个台阶要么是从n-1 + 1 而来; 要么是n-2 +2
        if(target < 3) return target;
        int f1= 1, f2 = 2, f3 = 3;
        for(int i=3;i<=target;i++) {
            f3 = f1 + f2;
            f1 = f2;
            f2 = f3;
        }
        return f3;
    }
}

最小花费爬楼梯

描述
给定一个整数数组 , cost[i] 是从楼梯第i个台阶向上爬需要支付的费用,下标从0开始。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
题目地址

思路: dp问题,

  1. 首先找到递推公式 fn = min(f(n-1) + cost[n-1], f(n-2) + cost[n-2]);
  2. 找到初始值 和终止条件
  3. 循环递推
import java.util.*;

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param cost int整型一维数组 
     * @return int整型
     */
    public int minCostClimbingStairs (int[] cost) {
        // write code here
        // fn = min(f(n-1) + cost[n-1], f(n-2) + cost[n-2])
        int n = cost.length;
        int f0 = 0, f1 = 0, f2 = Math.min(f1 + cost[1], f0 + cost[0]);
        for(int i = 2; i<=n; i++) {
            f2 = Math.min(f1 + cost[i-1], f0 + cost[i-2]);
            f0 = f1;
            f1 = f2;
        }
        return f2;
    }
}

偷东西1,不能偷连续两间房

题目地址

  • 描述
    你是一个经验丰富的小偷,准备偷沿街的一排房间,每个房间都存有一定的现金,为了防止被发现,你不能偷相邻的两家,即,如果偷了第一家,就不能再偷第二家;如果偷了第二家,那么就不能偷第一家和第三家。
    给定一个整数数组nums,数组中的元素表示每个房间存有的现金数额,请你计算在不被发现的前提下最多的偷窃金额。

数据范围:数组长度满足 1≤n≤2×10^5,数组中每个值满足 1≤num[i]≤5000.

  • 分析
    这是一个典型得动态规划得问题,其实大体有取有舍得这种问题,大体都可以认定为动态规划问题。
    我们来分析有n分房间的最大值:f(n), 它要么是f(n-2) 上来的,要么是f(n-1) 直接放弃偷n。
    所以递推公式: f(n) = max(f(n-2) + a[n], f(n-1))
import java.util.*;
public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param nums int整型一维数组 
     * @return int整型
     */
    public int rob (int[] nums) {
        // write code here
        //f(n) = max(f(n-1), f(n-2) + a[n])
        int sum = 0;
        int n = nums.length;
        if(n == 1) return nums[0];
        if(n == 2) return Math.max(nums[0], nums[1]); 
        int f1 = nums[0];
        int f2 = Math.max(nums[0], nums[1]);
        int f3 = 0;
        for(int i =2;i<n;i++) {
            f3 = Math.max(f1 + nums[i], f2);
            f1 = f2;
            f2 = f3;
        }
        return f3;
    }
}

偷东西2,相邻不能同时偷,首尾相邻

  • 描述
    你是一个经验丰富的小偷,准备偷沿湖的一排房间,每个房间都存有一定的现金,为了防止被发现,你不能偷相邻的两家,即,如果偷了第一家,就不能再偷第二家,如果偷了第二家,那么就不能偷第一家和第三家。沿湖的房间组成一个闭合的圆形,即第一个房间和最后一个房间视为相邻。
    给定一个长度为n的整数数组nums,数组中的元素表示每个房间存有的现金数额,请你计算在不被发现的前提下最多的偷窃金额。
    数据范围:数组长度满足 1≤n≤2×10^5,数组中每个值满足 1≤num[i]≤5000.

  • 分析
    这个题和上面一个是一样的,区别在于这题多了个条件: 首尾不能同时偷。
    所以我们把情况分为两种:

    • 偷了首家的,此时它不能偷最后一家,所以循环时需要把最后一家去掉,相当于求f(n-1)
    • 没偷首家,此时它能够偷最后一家,那么循环时带上最后一家就行了,相当于从第二项开始到最后求最大值。

然后两种情况中取较大者就行了。

import java.util.*;

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param nums int整型一维数组 
     * @return int整型
     */
    public int rob (int[] nums) {
        // write code here
        // 这个是典型的动态规划问题,dp[n] = max(dp[n-1], dp[n-2] + a[n-1])
        //但是多了一个条件是:首位不能同时选,那么我分为两种情况选择出最大的就行了
       
        int n = nums.length;
        int maxValue = 0;
        int [] dp = new int[n+1];
        // 选择了选择了首个的
        dp[1] = nums[0];
        for(int i =2; i< n;i++) {
            dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i-1]);
        }
        maxValue = dp[n-1];
        Arrays.fill(dp,0);
        // 不选首个的
        dp[1] = 0;
        for(int i = 2;i<=n;i++) {
            dp[i] = Math.max(dp[i-1], dp[i-2] + nums[i-1]);
        }
        maxValue = Math.max(maxValue, dp[n]);
        return maxValue;
    }
}

矩阵的最小路径和

题目地址

  • 题目描述
    给定一个 n * m 的矩阵 a,从左上角开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,输出所有的路径中最小的路径和。

要求:时间复杂度 O(nm)O(nm)

例如:当输入[[1,3,5,9],[8,1,3,4],[5,0,6,1],[8,8,4,0]]时,对应的返回值为12,
所选择的最小累加和路径如下图所示:
image

  • 分析
    动态规划问题,先拆解子问题,第i,j 位置的最小路径, 只能是它上一个来的,或者左边来的。所以递推公式: dp[i][j] = a[i][j] + min(dp[i-1][j], dp[i][j-1]);
    注意初始条件:
    第一行:dp[0][i] = sum(a[0][0]...a[0][i])
    第一列:dp[j][0] = sum(a[0][0]...a[j][0])
import java.util.*;


public class Solution {
    /**
     * 
     * @param matrix int整型二维数组 the matrix
     * @return int整型
     */
    public int minPathSum (int[][] matrix) {
        // write code here
        //f(i,j) = f(i-1,j-1) + min( a[i-1,j],  a[i][j-1])

        int m = matrix.length;
        int n = matrix[0].length;
        int[][] dp = new int[m][n];
        int sum = 0;
        for(int i = 0;i<m;i++) {
            sum+= matrix[i][0];
            dp[i][0] = sum;
        }
        sum = 0;
        for(int j=0;j<n;j++){
            sum +=  matrix[0][j];
            dp[0][j] = sum;
        }
        
        for(int i = 1;i<m;i++)
        for(int j = 1;j<n;j++) {
            dp[i][j] = matrix[i][j] + Math.min(dp[i-1][j], dp[i][j-1]);
        }
        return dp[m-1][n-1];
    }
}

最长、最短子序列系列

最长严格上升子序列

给定一个长度为 n 的数组 arr,求它的最长严格上升子序列的长度。
所谓子序列,指一个数组删掉一些数(也可以不删)之后,形成的新数组。例如 [1,5,3,7,3] 数组,其子序列有:[1,3,3]、[7] 等。但 [1,6]、[1,3,5] 则不是它的子序列。
我们定义一个序列是 严格上升 的,是指严格递增。
题目地址

import java.util.*;


public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 给定数组的最长严格上升子序列的长度。
     * @param arr int整型一维数组 给定的数组
     * @return int整型
     */
    public int LIS (int[] arr) {
        // write code here
        // 一个元素是严格上升子序列,只需不断往里面加元素且保证是严格上升子序列就行
        // 对于每一个到i结尾的子数组,如果遍历过程中遇到元素j小于i处元素,说明以j元素结尾的子序列加上子数组末尾元素也是严格递增的,因此转移方程: dp[i] = dp[j] +1
        int n = arr.length;
        int[] dp = new int[n];
        Arrays.fill(dp, 1);
        int maxLen = n < 2 && n >= 0 ? n : 0; // 边界条件
        for (int i = 1; i < n; i++) {
            for (int j = 0; j < i; j++) {
                // 说明递增,且i 拼接在j处达到最长
                if (arr[i] > arr[j] && dp[i] < dp[j] + 1) {
                    dp[i] = dp[j] + 1;
                    maxLen = Math.max(maxLen, dp[i]);
                }
            }
        }
        return maxLen;
    }
}
posted @ 2022-12-26 23:08  吾日三省吾身-学习  阅读(89)  评论(0)    收藏  举报