中级班讲解

题目一

image

//滑动窗口法,以右边界为准,时间复杂度O(N)
public static int maxPoint(int[] arr, int L) {
    int maxLength = Integer.MIN_VALUE;
    int left = 0;
    for (int right = 0; right < arr.length; right++) {
        while (arr[right] - arr[left] > L) {
            maxLength = Math.max(maxLength, right - left);
            left++;
        }
    }
    return maxLength == Integer.MIN_VALUE ? 0 : maxLength;
}

//二分查找法,时间复杂度O(N*logN)
public static int maxPoint1(int[] arr, int L) {
    int maxLength = Integer.MIN_VALUE;
    for (int i = 0; i < arr.length; i++) {
        int index = nearest(arr, i, arr[i] - L);
        maxLength = Math.max(maxLength, i - index + 1);
    }
    return maxLength;
}

// 在arr[0..R]范围上,找满足>=value的最左位置
private static int nearest(int[] arr, int R, int value) {
    int L = 0;
    int index = R;
    while (L < R) {
        int mid = (R + L) >> 1;
        if (arr[mid] >= value) {
            index = mid;
            R = mid - 1;
        } else {
            L = mid + 1;
        }
    }
    return index;
}

题目二

image

public static int minBags(int apple) {
    if (apple < 0) {
        return -1;
    }
    int minBags6 = -1;
    int minBags8 = apple / 8;   //最多使用多少个装8个的袋子
    int rest = apple - minBags8 * 8;  //用8个的袋子装完之后剩余多少个苹果
    while (minBags8 >= 0 && rest < 24) {    //此处的24是6和8的最小公倍数
        int restBase = minBagBase6(rest);
        if (restBase != -1) {   //如果剩下的苹果可以用6的袋子装完,则退出循环,返回总共的袋子数
            minBags6 = restBase;
            break;
        }
        rest = apple - 8 * (--minBags8);
    }
    return minBags6 == -1 ? -1 : minBags6 + minBags8;
}

//如果剩余的苹果可以用装6个的袋子搞定,返回袋子数量
//不能搞定返回-1
private static int minBagBase6(int rest) {
    return rest % 6 == 0 ? rest / 6 : -1;
}

//打表法,通过输出0~100个苹果所需的袋子数找到规律
public static int minBagAwesome(int apple) {
    if ((apple & 1) != 0) {
        return -1;
    }
    if (apple < 18) {
        return apple == 0 ? 0 : (apple == 6 || apple == 8) ? 1
                : (apple == 12 || apple == 14 || apple == 16) ? 2 : -1;
    }
    return (apple - 18) / 8 + 3;
}

题目三

image

public static String Winner1(int n) {
    //  0    1   2    3    4
    // 后手 先手 后手 先手 先手
    if (n < 5) {
        return (n == 0 || n == 2) ? "羊羊" : "牛牛";
    }
    int base = 1; //最少一次吃1份
    while (base < n) {
        if (Winner1(n - base).equals("羊羊")) {   //如果剩下的青草赢家是羊羊,则最终胜者是牛牛
            return "牛牛";
        }
        if (base > n / 4) {   //防止base*4之后溢出
            break;
        }
        base *= 4;
    }
    return "羊羊";
}

public static String Winner2(int n) {
    if (n % 5 == 0 || n % 5 == 2) {
        return "yang";
    } else {
        return "niu";
    }
}

题目四

image

//数组预处理
public static int minPaint1(String s) {
    if (s == null || s.length() < 2) {
        return 0;
    }
    char[] chs = s.toCharArray();
    int[] right = new int[chs.length];    //用于记录从右边开始数有几个R
    right[chs.length - 1] = chs[chs.length - 1] == 'R' ? 1 : 0;
    for (int i = chs.length - 2; i >= 0; i--) {
        right[i] = right[i + 1] + (chs[i] == 'R' ? 1 : 0);
    }
    int res = right[0];
    int left = 0;    //用于记录从左边开始数有几个G
    for (int i = 0; i < chs.length - 1; i++) {
        left += chs[i] == 'G' ? 1 : 0;
        res = Math.min(res, left + right[i + 1]);
    }
    res = Math.min(res, left + (chs[chs.length - 1] == 'G' ? 1 : 0));
    return res;
}

题目五

image

//数组预处理,得到right和down数组
public static void setBorderMap(int[][] m, int[][] right, int[][] down) {
    int rightRow = right.length;  //right数组的行数
    int rightCol = right[0].length;   //right数组的列数
    int downRow = down.length;    //down数组的行数
    int downCol = down[0].length;     //down数组的列数
    for (int row = 0; row < rightRow; row++) {
        right[row][rightCol - 1] = m[row][rightCol - 1] == 1 ? 1 : 0;   //m数组的最后一列的数如果为1,则right数组对应位置的值就为1,否则为0
        for (int col = rightCol - 2; col >= 0; col--) {
            right[row][col] = m[row][col] == 1 ? 1 + right[row][col + 1] : 0;   //其余位置,如果当前数为1,则右边连续的1的个数为其右边位置的连续的1的数量加上1,如果当前数为0,则为0
        }
    }
    for (int col = 0; col < downCol; col++) {
        down[downRow - 1][col] = m[downRow - 1][col] == 1 ? 1 : 0;  //m数组的最后一行的数如果为1,则down数组对应位置的值就为1,否则为0
        for (int row = downRow - 2; row >= 0; row--) {
            down[row][col] = m[row][col] == 1 ? 1 + down[row + 1][col] : 0;    //其余位置,如果当前数为1,则下边连续的1的个数为其下边位置的连续的1的数量加上1,如果当前数为0,则为0
        }
    }
}

public static int getMaxSize(int[][] m) {
    int[][] right = new int[m.length][m[0].length];
    int[][] down = new int[m.length][m[0].length];
    setBorderMap(m, right, down);
    for (int size = Math.min(m.length, m[0].length); size != 0; size--) {
        if (hasSizeOfBorder(size, right, down)) {
            return size;
        }
    }
    return 0;
}

private static boolean hasSizeOfBorder(int size, int[][] right, int[][] down) {
    //遍历循环左上角点可能存在的位置
    for (int i = 0; i != right.length - size + 1; i++) {
        for (int j = 0; j != right[0].length - size + 1; j++) {
            if (right[i][j] >= size && down[i][j] >= size
                    && right[i + size - 1][j] >= size
                    && down[i][j + size - 1] >= size) { //如果当前节点的right和down数组的值都大于size,并且以当前节点为左上角,size为边长的正方形的左下角和右上角的right和down数值的值都大于size,返回true
                return true;
            }
        }
    }
    return false;
}

题目六

image

public static int rand1To7() {
    int res = 0;
    do {
        res = (rand0To1() << 2) + (rand0To1() << 1) + rand0To1();
    } while (res == 7);
    return res + 1;
}

//等概率返回0~1
public static int rand0To1() {
    int res = 0;
    do {
        res = rand1To5();
    } while (res == 3);     //如果得到的数是3,请重新生成
    return res < 3 ? 0 : 1;     //如果等得到的是1或2,返回0,如果是4或5,返回1
}

//等概率返回1~5
public static int rand1To5() {
    return (int) (Math.random() * 5) + 1;
}


public static int rand1ToM(int m) {
    return (int) (Math.random() * m) + 1;
}

public static int rand1ToN(int n, int m) {
    int[] nMSys = getMSysNum(n - 1, m);
    int[] randNum = getRanMSysNumLessN(nMSys, m);
    return getNumFromMSysNum(randNum, m) + 1;
}

public static int[] getMSysNum(int value, int m) {
    int[] res = new int[32];
    int index = res.length - 1;
    while (value != 0) {
        res[index--] = value % m;
        value = value / m;
    }
    return res;
}

public static int[] getRanMSysNumLessN(int[] nMSys, int m) {
    int[] res = new int[nMSys.length];
    int start = 0;
    while (nMSys[start] == 0) {
        start++;
    }
    int index = start;
    boolean lastEqual = true;
    while (index != nMSys.length) {
        res[index] = rand1ToM(m) - 1;
        if (lastEqual) {
            if (res[index] > nMSys[index]) {
                index = start;
                lastEqual = true;
                continue;
            } else {
                lastEqual = res[index] == nMSys[index];
            }
        }
        index++;
    }
    return res;
}

public static int getNumFromMSysNum(int[] mSysNum, int m) {
    int res = 0;
    for (int i = 0; i != mSysNum.length; i++) {
        res = res * m + mSysNum[i];
    }
    return res;
}


public static int rand01p() {
    // you can change p to what you like, but it must be (0,1)
    double p = 0.83;
    return Math.random() < p ? 0 : 1;
}

public static int rand01(){
    int res=0;
    do{
        res=(rand01p()<<1)+rand01p();
    }while(res==0||res==3);
    return res == 1 ? 0 : 1;
}

题目七

image
image

public static class Node {
    public int value;
    public Node left;
    public Node right;

    public Node(int data) {
        this.value = data;
    }
}

public static int numTrees(int n) {
    if (n < 2) {
        return 1;
    }
    int[] num = new int[n + 1];
    num[0] = 1;
    for (int i = 1; i < n + 1; i++) {
        for (int j = 1; j < i + 1; j++) {
            num[i] += num[j - 1] * num[i - j];
        }
    }
    return num[n];
}

题目八

image

/*
    思路:从左往右遍历,如果碰到(,则用一个计数器leftRest进行统计+1;如果碰到),此时leftRest不等于0,则将leftRest减1,如果leftRest等于0,说明在此之前字符串是完整的,当前字符是多出来的),用needSolveRight进行统计,最终返回needSolveRight和leftRest的和
 */
public static int needParentheses(String str) {
    int leftRest = 0;
    int needSolveRight = 0;
    for (int i = 0; i < str.length(); i++) {
        if (str.charAt(i) == '(') {
            leftRest++;
        } else {
            if (leftRest == 0) {
                needSolveRight++;
            } else {
                leftRest--;
            }
        }
    }
    return leftRest + needSolveRight;
}

题目九

image

public static List<List<Integer>> allPair(int[] arr, int k) {
    HashSet<Integer> set = new HashSet<>();
    for (int i : arr) {
        set.add(i);
    }
    List<List<Integer>> res = new ArrayList<>();
    for (Integer cur : set) {
        if (set.contains(cur + k)) {
            res.add(Arrays.asList(cur, cur + k));
        }
    }
    return res;
}

题目十

image

// 请保证arr1无重复值、arr2中无重复值,且arr1和arr2肯定有数字
/*
思路:若arr1和arr2的平均值相同,则直接返回0,无法执行magic操作;若arr1的平均值a1>arr2的平均值a2,则可以移动的数的范围是a2~a1,
若想要magic次数最多,则得按照a2~a1之间从小到大的顺序进行移动
 */
public static int maxOps(int[] arr1, int[] arr2) {
    int[] arrMore = null;   //用于存储和较大的数组
    int[] arrLess = null;   //用于存储和较小的数组
    double sumMore=0;       //用于存储较大和
    double sumLess=0;       //用于存储较小和
    double sum1 = 0;
    for (int value : arr1) {
        sum1 += value;
    }
    double sum2 = 0;
    for (int i : arr2) {
        sum2 += i;
    }
    if(avg(sum1,arr1.length)==avg(sum2,arr2.length)){   //若平均值相等,直接返回0
        return 0;
    }
    if(avg(sum1,arr1.length)>avg(sum2,arr2.length)){
        arrMore=arr1;
        sumMore=sum1;
        arrLess=arr2;
        sumLess=sum2;
    }else if(avg(sum1,arr1.length)<avg(sum2,arr2.length)){
        arrMore=arr2;
        sumMore=sum2;
        arrLess=arr1;
        sumLess=sum1;
    }
    HashSet<Integer> set=new HashSet<>();   //用于存储和较小的数组中的每一个数
    for (int less : arrLess) {
        set.add(less);
    }
    Arrays.sort(arrMore);   //对和较大的数组进行排序,方便拿到符合范围中的最小的那个数
    int moreLength=arrMore.length;
    int lessLength=arrLess.length;
    int op=0;
    for (int i = 0; i < arrMore.length; i++) {
        double cur=(double)arrMore[i];
        if(cur>avg(sumLess,lessLength)&&cur<avg(sumMore,moreLength)&&!set.contains(arrMore[i])){  //如果满足范围且set中不存在当前数,则可以移动
            sumMore-=cur;
            moreLength--;
            sumLess+=cur;
            lessLength++;
            set.add(arrMore[i]);
            op+=1;
        }
    }
    return op;
}

//求平均值
public static double avg(double sum, int length) {
    return  sum /(double) length;
}

题目十一

image

//判断是否是合法的括号序列
public static boolean isValid(char[] str) {
    if (str == null || str.length == 0) {
        return false;
    }
    int status = 0; //用于统计有几个左括号
    for (int i = 0; i < str.length; i++) {
        if (str[i] != ')' && str[i] != '(') {   //如果数组中存在除了左括号和右括号以外的字符,返回false
            return false;
        }
        if (str[i] == ')' && --status < 0) {    //如果当前字符是右括号,但是前面字符串已经没有多余的左括号跟当前的右括号匹配,返回false
            return false;
        }
        if (str[i] == '(') {
            status++;
        }
    }
    return true;
}

//返回最大深度
public static int deep(String s) {
    char[] str = s.toCharArray();
    if (!isValid(str)) {  //如果不是合法的括号序列,返回0
        return 0;
    }
    int count = 0;
    int max = 0;
    for (char c : str) {
        if (c == '(') {
            max = Math.max(max, ++count);
        } else {
            count--;
        }
    }
    return max;
}

//求长度最大的合法子串
/*
思路:构建一个数组dp,记录的是以当前字符为结尾的最长合法字串的长度是多少。若当前字符是左括号,则必然为0;如果是右括号,需要根据情况进行讨论。
    假设当前位置是i,如果dp[i-1]=0,则看i-1位置是不是左括号,如果是左括号,则dp[i]=2;如果不是,则dp[i]=0;
    如果dp[i-1]!=0,则看i-1-dp[i-1]位置是不是左括号,如果是左括号,则dp[i]=2+dp[i-1]+dp[i-2-dp[i-1]],如果不是,则dp[i]=0.
 */
public static int maxLength(String str) {
    if (str == null || str.equals("")) {
        return 0;
    }
    char[] chars = str.toCharArray();
    int[] dp = new int[chars.length];
    int pre = 0;
    int max = 0;
    for (int i = 0; i < chars.length; i++) {
        if (chars[i] == ')') {
            pre = i - 1 - dp[i - 1];
            if (pre >= 0 && chars[pre] == '(') {
                dp[i] = 2 + dp[i - 1] + (pre > 0 ? dp[pre - 1] : 0);
            }
        }
        max = Math.max(max, dp[i]);
    }
    return max;
}

题目十二

image

/*
准备一个栈b,从栈顶到栈底按照从小到大排列。给定栈a,将a的栈顶元素弹出,压入栈b,
若栈b为空或b的栈顶元素大于栈a的弹出元素,直接压入,若b的栈顶元素小于栈a的弹出元素,则需要先将b的栈顶元素弹出压入a,直到找到比栈a的弹出元素大的值。
 */
public static void sortStackByStack(Stack<Integer> stack) {
    Stack<Integer> help = new Stack<>();  //辅助栈,栈顶到栈底从小到大
    while (!stack.isEmpty()) {
        int pop = stack.pop();
        while (!help.isEmpty() && pop > help.peek()) {      //若b的栈顶元素小于栈a的弹出元素,则需要先将b的栈顶元素弹出压入a
            stack.push(help.pop());
        }
        help.push(pop);
    }
    while(!help.isEmpty()){     //将辅助栈中的数一个一个弹出压入到原栈,就是升序排列
        stack.push(help.pop());
    }
}

题目十三

image

/*
思路:分为2种情况讨论:
第一种,若i位置数字是0,直接返回0
第二种,若i位置数字不是0,分为两种
        1)i位置单独为一组,[i+1...]为一组,
        2)i和i+1位置若的数字拼接起来若小于27,则可以i和i+1一组,[i+2...}一组
        最终结果是上述两种情况之和
 */
public static int convertWays(int num) {
    if (num < 1) {
        return 0;
    }
    return process(String.valueOf(num).toCharArray(), 0);
}

public static int process(char[] str, int index) {
    if (index == str.length) {
        return 1;
    }
    if (str[index] == '0') {    //如果当前数字是0,则没有对应的字母,返回0
        return 0;
    }
    int res = process(str, index + 1);
    if (index == str.length - 1) {
        return res;
    }
    if ((str[index] - '0') * 10 + (str[index + 1] - '0') < 27) {
        res += process(str, index + 2);
    }
    return res;
}

//动态规划
public static int dpways(int num) {
    if (num < 1) {
        return 0;
    }
    char[] str = String.valueOf(num).toCharArray();
    int[] dp = new int[str.length + 1];
    dp[str.length] = 1;
    dp[str.length - 1] = str[str.length - 1] == '0' ? 0 : 1;
    for (int i = str.length - 2; i >= 0; i--) {
        if (str[i] == '0') {
            dp[i] = 0;
        } else {
            dp[i] = dp[i + 1]
                    + (((str[i] - '0') * 10 + str[i + 1] - '0') < 27 ? dp[i + 2]
                    : 0);
        }
    }
    return dp[0];
}

题目十四

image

public static class Node {
    public int value;
    public Node left;
    public Node right;

    public Node(int val) {
        value = val;
    }
}

public static int maxSumRecursive(Node head) {
    return process(head, 0);
}

/**
 * 用于处理以当前节点为根的最大权值和
 * @param x 当前所到达节点
 * @param pre   当前节点之前的最大权值和
 * @return  返回以当前节点为根的最大权值和
 */
public static int process(Node x, int pre) {
    if(x==null){
        return Integer.MIN_VALUE;
    }
    if(x.left==null&&x.right==null){
        return pre+x.value;
    }
    int leftMax = process(x.left, pre + x.value);
    int rightMax = process(x.right, pre + x.value);
    return Math.max(leftMax, rightMax);
}

private static int maxSumUnrecursive(Node head) {
    int max=0;
    HashMap<Node,Integer> map=new HashMap<>();
    if(head!=null){
        Stack<Node> stack=new Stack<>();
        stack.add(head);
        map.put(head,head.value);
        while(!stack.isEmpty()){
            head=stack.pop();
            if(head.left==null&&head.right==null){
                max=Math.max(max,map.get(head));
            }
            if(head.right!=null){
                map.put(head.right,head.right.value+map.get(head));
                stack.push(head.right);
            }
            if(head.left!=null){
                map.put(head.left,head.left.value+map.get(head));
                stack.push(head.left);
            }
        }
    }
    return max;
}

题目十五

image

/*
思路:从矩阵的右上角开始比较,如果小于K,则向下找,如果大于K,则向左找
 */
public static boolean isContains(int[][] matrix, int K) {
    int row = matrix.length;  //二维矩阵的行数
    int col = matrix[0].length;   //二维矩阵的列数
    int startRow = 0;   //从矩阵的右上角开始比较
    int startCol = col - 1;
    while (startRow < row && startCol >= 0) {
        if(matrix[startRow][startCol]>K){
            startCol--;
        }else if(matrix[startRow][startCol]<K){
            startRow++;
        }else{
            return true;
        }
    }
    return false;
}

题目十六

image

/*
思路:
1.先对数组进行遍历,将所有数求和,除以数组的长度,看是否能够整除,如果不能整除,直接返回-1;能整除,进行下一步。
2.假设当前位置是i,平均数是avg,我们可以计算得到[0...i-1]范围内的总和a,实际[0...i-1]的总和应为avg*i=b,同样可以求[i+1....arr.length-1]范围内的总和c,实际[i+1....arr.length-1]的总和应为avg*(arr.length-1-i)=d
3.如果a<b并且c<d,说明当前位置i的数量是多的,所以需要执行的操作数为b-a+d-c
4.如果a<b并且c>d,说明i左边需要入b-a次,i右边需要出c-d次,最终需要执行的操作数为两者的最大值
5.如果a>b并且c<d,说明i左边需要出a-b次,i右边需要入d-c次,最终需要执行的操作数为两者的最大值
6.如果a>b并且c>d,说明i左边需要出a-b次,i右边需要出c-d次,i位置少,最终需要执行的操作数为两者的最大值。
 */
public static int MinOps(int[] arr) {
    int arrSum = 0;
    for (int i : arr) {
        arrSum += i;
    }
    if (arrSum % arr.length != 0) {
        return -1;
    }
    int avg = arrSum / arr.length;  //每个位置应该放多少
    int leftSum = 0;  //用于计算当前位置左边的总数
    int ans = 0;
    for (int i = 0; i < arr.length; i++) {
        int diffLeft = leftSum - i * avg;     //如果是负数,说明左侧需要入,否则需要出
        int diffRight = (arrSum - arr[i] - leftSum) - (arr.length - i - 1) * avg; //如果是负数,说明右侧需要入,否则需要出
        if (diffLeft < 0 && diffRight < 0) {
            ans = Math.max(ans, Math.abs(diffLeft) + Math.abs(diffRight));
        } else {
            ans = Math.max(ans, Math.max(Math.abs(diffLeft), Math.abs(diffRight)));
        }
        leftSum += arr[i];
    }
    return ans;
}

题目十七

image

/*
思路:
1.每条斜线为一组,对斜线的两端进行固定。设置一个变量,用于声明是从左下开始打印还是从右上开始打印,一开始是从左下开始打
2.直到斜线的端点超过了矩阵的边界,停止打印。
 */
public static void printMatrixZigZag(int[][] matrix) {
    int startRow = 0;     //斜线的一个端点的横坐标
    int startCol = 0;     //斜线的一个端点的列坐标
    int endRow = 0;       //斜线的一个端点的横坐标
    int endCol = 0;       //斜线的一个端点的列坐标
    int row = matrix.length - 1;  //矩阵的高度
    int col = matrix[0].length - 1;   //矩阵的宽度
    boolean fromup = false;     //用于声明是从左下开始打印还是从右上开始打印
    while (startRow != row + 1) {
        printLevel(matrix, startRow, startCol, endRow, endCol, fromup);
        startRow = startCol == col ? startRow + 1 : startRow;
        startCol = startCol == col ? startCol : startCol + 1;
        endCol = endRow == row ? endCol + 1 : endCol;
        endRow = endRow == row ? endRow : endRow + 1;
        fromup = !fromup;
    }
}

private static void printLevel(int[][] matrix, int startRow, int startCol, int endRow, int endCol, boolean fromup) {
    if (fromup) {     //从右上开始打
        while (startRow != endRow + 1) {
            System.out.println(matrix[startRow++][startCol--] + " ");
        }
    } else {      //从左下开始打
        while (endRow != startRow - 1) {
            System.out.println(matrix[endRow--][endCol++] + " ");
        }
    }
}

题目十八

image

/*
思路:
1.从最外围的矩形开始,固定好左上角和右下角的位置,从左上角开始打印,直至一圈打印完成
2.左上角向右下方移,右下角向左上方移,周而复始第一步,直至右下角跑到左上角的上面结束。
 */
public static void spiralOrderPrint(int[][] matrix) {
    int startRow=0; //左上角的行
    int startCol=0; //左上角的列
    int endRow=matrix.length-1; //右下角的行
    int endCol=matrix[0].length-1; //右下角的列
    while(startRow<=endRow&&startCol<=endCol){
        process(matrix, startRow++, startCol++, endRow--, endCol--);
    }
}

private static void process(int[][] matrix, int startRow, int startCol, int endRow, int endCol) {
    if(startRow==endRow){   //左上角和右下角在同一行
        for(int i=startCol;i<=endCol;i++){
            System.out.println(matrix[startRow][i]+" ");
        }
    }else if(startCol==endCol){   //左上角和右下角在同一列
        for(int i=startRow;i<=endRow;i++){
            System.out.println(matrix[i][startCol]+" ");
        }
    }else{
        int curC=startCol;
        int curR=startRow;
        while(curC!=endCol){    //打印上行
            System.out.println(matrix[curR][curC]+" ");
            curC++;
        }
        while(curR!=endRow){    //打印右列
            System.out.println(matrix[curR][curC]+" ");
            curR++;
        }
        while(curC!=startCol){  //打印下行
            System.out.println(matrix[curR][curC]+" ");
            curC--;
        }
        while(curR!=startRow){  //打印左列
            System.out.println(matrix[curR][curC]+" ");
            curR--;
        }
    }
}

题目十九

image

/*
思路:
1.从最外围的正方形开始,记录左上角和右下角,将数字分为边长-1组,例如题中0、3、15、12为一组,1、7、14、8一组,2、11、13、4一组,每一次只需要对组内的数字进行交换即可。
2.左上角向右下方移,右下角向左上方移,重复第一步的操作,直至右上角在左上角的左上方停止。
 */
public static void rotate(int[][] matrix) {
    int startRow = 0; //左上角的行
    int startCol = 0; //左上角的列
    int endRow = matrix.length - 1; //右下角的行
    int endCol = matrix[0].length - 1; //右下角的列
    while (startRow < endRow) {
        process(matrix, startRow++, startCol++, endRow--, endCol--);
    }
}

private static void process(int[][] matrix, int startRow, int startCol, int endRow, int endCol) {
    int times = endRow - startRow;  //将数字分为几组
    for (int i = 0; i < times; i++) {
        int tmp = matrix[startRow][startCol + i];   //临时变量存储上行的数
        matrix[startRow][startCol + i] = matrix[endRow - i][startCol];  //将左列的数放入上行
        matrix[endRow - i][startCol] = matrix[endRow][endCol - i];  //将下行的数放入左列
        matrix[endRow][endCol - i] = matrix[startRow + i][endCol];  //将右列的数放入下行
        matrix[startRow + i][endCol] = tmp; //将上行的数放入右列
    }
}

题目二十

image

/*
思路:当n是质数的时候,只需要执行n-1次第二种操作就是最小的步骤数;当n是合数的时候,将其拆分成多个质数乘积的形式,n=a*b*c*d,最少操作步骤数就是(a+b+c+d-4)
 */
//判断一个数是不是质数
public static boolean isPrime(int n) {
    if (n < 2) {
        return false;
    }
    for (int i = 2; i <= Math.sqrt(n); i++) {
        if (n % i == 0) {
            return false;
        }
    }
    return true;
}

// 请保证n不是质数
// 返回:
// 0) 所有因子的和,但是因子不包括1
// 1) 所有因子的个数,但是因子不包括1
public static int[] divsSumAndCount(int n) {
    int sum = 0;  //所有因子的和
    int count = 0;    //因子的个数
    for (int i = 2; i < n; i++) {
        while (n % i == 0) {
            sum += i;
            count++;
            n /= i;
        }
    }
    return new int[]{sum, count};
}

public static int minOps(int n) {
    if (n < 2) {
        return 0;
    }
    if (isPrime(n)) {
        return n - 1;
    }
    int[] ints = divsSumAndCount(n);
    return ints[0] - ints[1];
}

题目二十一

image

//利用小根堆来存储次数最多的前K个
public static class Node{
    public String str;
    public int times;

    public Node(String s,int t){
        this.str=s;
        this.times=t;
    }
}

public static class NodeComparator implements Comparator<Node>{

    @Override
    public int compare(Node o1, Node o2) {
        return o1.times-o2.times;
    }
}

public static void printTopKAndRank(String[] arr,int topK){
    if(arr==null||arr.length==0||topK<1){
        return;
    }
    HashMap<String,Integer> map=new HashMap<>();    //用于存储每个字符串出现的次数
    for (String s : arr) {
        if(!map.containsKey(s)){
            map.put(s,0);
        }
        map.put(s,map.get(s)+1);
    }
    topK=Math.min(arr.length,topK);
    PriorityQueue<Node> heap=new PriorityQueue<>(new NodeComparator());
    for (Map.Entry<String, Integer> entry : map.entrySet()) {
        Node cur=new Node(entry.getKey(),entry.getValue());
        if(heap.size()<topK){
            heap.add(cur);
        }else{
            if(heap.peek().times<cur.times){
                heap.poll();
                heap.add(cur);
            }
        }
    }
    while(!heap.isEmpty()){
        System.out.println(heap.poll().str);
    }
}

题目二十二

image

public static int getWater1(int[] arr) {
    if (arr == null || arr.length < 3) {
        return 0;
    }
    int value = 0;
    for (int i = 1; i < arr.length - 1; i++) {  //最左和最右无需考虑,因为最左的数无左边界无法存储,最右的数无右边界无法存储
        int leftMax = 0;  //用于记录当前位置的左边的最大值
        int rightMax = 0; //用于记录当前位置的右边的最大值
        for (int l = 0; l < i; l++) {
            leftMax = Math.max(arr[l], leftMax);
        }
        for (int r = i + 1; r < arr.length; r++) {
            rightMax = Math.max(arr[r], rightMax);
        }
        value += Math.max(0, Math.min(leftMax, rightMax) - arr[i]); //能存储的最大数为当前位置的左右边界中最大值中的较小值与当前位置数值的差,如果当前位置的数比左右边界中的最大值还要大,则无法存储
    }
    return value;
}

public static int getWater2(int[] arr){
    if (arr == null || arr.length < 3) {
        return 0;
    }
    int n=arr.length-2;
    int[] leftMaxs=new int[n];
    leftMaxs[0]=arr[0];
    for(int i=1;i<n;i++){
        leftMaxs[i]=Math.max(leftMaxs[i-1],arr[i]);
    }
    int[] rightMaxs=new int[n];
    rightMaxs[n-1]=arr[n+1];
    for(int i=n-2;i>=0;i--){
        rightMaxs[i]=Math.max(rightMaxs[i+1],arr[i+2]);
    }
    int value=0;
    for (int i = 1; i <= n; i++) {
        value += Math.max(0, Math.min(leftMaxs[i - 1], rightMaxs[i - 1]) - arr[i]);
    }
    return value;
}

题目二十三

image

public static int maxABS1(int[] arr) {
    int res=Integer.MIN_VALUE;
    int maxLeft=0;  //用于记录左部分中的最大值
    int maxRight=0; //用于记录右部分中的最大值
    for (int i = 0; i != arr.length - 1; i++) {
        maxLeft = Integer.MIN_VALUE;
        for (int j = 0; j != i + 1; j++) {
            maxLeft = Math.max(arr[j], maxLeft);
        }
        maxRight = Integer.MIN_VALUE;
        for (int j = i + 1; j != arr.length; j++) {
            maxRight = Math.max(arr[j], maxRight);
        }
        res = Math.max(Math.abs(maxLeft - maxRight), res);
    }
    return res;
}

public static int maxABS2(int[] arr) {
    int[] lArr = new int[arr.length];
    int[] rArr = new int[arr.length];
    lArr[0] = arr[0];
    rArr[arr.length - 1] = arr[arr.length - 1];
    for (int i = 1; i < arr.length; i++) {
        lArr[i] = Math.max(lArr[i - 1], arr[i]);
    }
    for (int i = arr.length - 2; i > -1; i--) {
        rArr[i] = Math.max(rArr[i + 1], arr[i]);
    }
    int max = 0;
    for (int i = 0; i < arr.length - 1; i++) {
        max = Math.max(max, Math.abs(lArr[i] - rArr[i + 1]));
    }
    return max;
}

public static int maxABS3(int[] arr) {
    int max = Integer.MIN_VALUE;
    for (int i = 0; i < arr.length; i++) {
        max = Math.max(arr[i], max);
    }
    return max - Math.min(arr[0], arr[arr.length - 1]);
}

题目二十四

image

/*
思路:先判断a和b长度是否相等,如果不相等直接返回false。
如果相等,则让b拼接上b形成一个新的字符串b2,接下来问题就转化为a是否是b2的子串,可以利用kmp算法。
 */
public static boolean isRotation(String a, String b) {
    if (a == null || b == null || a.length() != b.length()) {
        return false;
    }
    String b2 = b + b;
    return getIndexOf(b2, a) != -1;
}

/**
 * 用于返回子串在原串中的起始位置
 
 * @param s 原串
 * @param m 子串
 * @return 返回子串在原串中的起始位置
 */
public static int getIndexOf(String s, String m) {
    if (s.length() < m.length()) {
        return -1;
    }
    char[] ss = s.toCharArray();
    char[] mm = m.toCharArray();
    int si = 0;
    int mi = 0;
    int[] next = getNextArray(mm);
    while (si < ss.length && mi < mm.length) {
        if (ss[si] == mm[mi]) {
            si++;
            mi++;
        } else if (next[mi] == -1) {
            si++;
        } else {
            mi = next[mi];
        }
    }
    return mi == mm.length ? si - mi : -1;
}

//用于返回next数组
public static int[] getNextArray(char[] mm) {
    if (mm.length == 1) {
        return new int[]{-1};
    }
    int[] next = new int[mm.length];
    next[0] = -1;
    next[1] = 0;
    int pos = 2;
    int cn = 0;
    while (pos < next.length) {
        if (mm[pos - 1] == mm[cn]) {
            next[pos] = cn++;
        } else if (cn > 0) {
            cn = next[cn];
        } else {
            next[pos++] = 0;
        }
    }
    return next;
}

题目二十五

image

/*
思路:将数组中的数分为三类,一类是奇数,记为o,一类是只有1个因子为2的数,记为s,一类是多个因子为2的数,记为m。
假设o有a个,s有b个,m有c个,分为两种情况讨论。
第一种b==0,则排放顺序为omomom..的顺序,当a=1时,c≥1;a=2时,c≥1;a=3时,c≥2。规律为a=1时,c≥1;a=n时,c≥n-1
第二个b≠0,则排放顺序为sssssmomomomomo....,先把s全部摆完,接着按顺序放m和o,当a=n时,c≥n。
 */
public static boolean nearMultiple4Times(int[] arr) {
    int oddNum = 0;   //奇数的个数
    int singleTwo = 0;    //只有1个因子为2的数的个数
    int multiTwo = 0;     //多个因子为2的数的个数
    for (int value : arr) {
        if (value % 2 != 0) {
            oddNum++;
        } else {
            if (value % 4 == 0) {
                multiTwo++;
            } else {
                singleTwo++;
            }
        }
    }
    return singleTwo == 0 ? (multiTwo >= oddNum - 1) : multiTwo >= oddNum;
}

题目二十六

image

//利用斐波那契数列套路求斐波那契数列
public static int getNum(int n) {
    if (n == 0 || n == 1) {
        return 1;
    }
    int[][] base = {{1, 1}, {1, 0}};
    int[][] res = matrixPow(base, n - 2);
    return res[0][0] + res[1][0];
}

//实现矩阵的阶乘运算
private static int[][] matrixPow(int[][] base, int p) {
    int[][] res = new int[base.length][base[0].length];   //构造一个单位矩阵
    for (int i = 0; i < res.length; i++) {
        res[i][i] = 1;
    }
    int[][] tmp = base;
    for (; p != 0; p >>= 1) {
        if ((p & 1) != 0) {   //如果p与1不等于0,说明p当前位置上是1,需要res乘tmp
            res = multiMatrix(res, tmp);
        }
        tmp = multiMatrix(tmp, tmp);
    }
    return res;
}

private static int[][] multiMatrix(int[][] tmp, int[][] tmp1) {
    int[][] res = new int[tmp.length][tmp1[0].length];
    for (int i = 0; i < tmp.length; i++) {
        for (int j = 0; j < tmp1[0].length; j++) {
            for (int k = 0; k < tmp1.length; k++) {
                res[i][j] += tmp[i][k] * tmp1[k][j];
            }
        }
    }
    return res;
}

题目二十七

image

/*
思路:假设N=i,第一位必须是1,假设第二位是1,则情况跟F(i-1)一样;
假设第二位是0,则第三位必须是1,则情况跟F(i-2)一样,所以F(i)=F(i-1)+F(i-2),跟斐波那契数列是一样的规律
*/
public static int getNum1(int n) {
    if (n < 1) {
        return 0;
    }
    return process(1, n);
}

public static int process(int i, int n) {
    if (i == n - 1) {
        return 2;
    }
    if (i == n) {
        return 1;
    }
    return process(i + 1, n) + process(i + 2, n);
}

public static int getNum2(int n) {
    if (n < 1) {
        return 0;
    }
    if (n == 1) {
        return 1;
    }
    int pre = 1;
    int cur = 1;
    int tmp = 0;
    for (int i = 2; i < n + 1; i++) {
        tmp = cur;
        cur += pre;
        pre = tmp;
    }
    return cur;
}

public static int getNum3(int n) {
    if (n < 1) {
        return 0;
    }
    if (n == 1 || n == 2) {
        return n;
    }
    int[][] base = { { 1, 1 }, { 1, 0 } };
    int[][] res = matrixPower(base, n - 2);
    return 2 * res[0][0] + res[1][0];
}

public static int[][] matrixPower(int[][] m, int p) {
    int[][] res = new int[m.length][m[0].length];
    for (int i = 0; i < res.length; i++) {
        res[i][i] = 1;
    }
    int[][] tmp = m;
    for (; p != 0; p >>= 1) {
        if ((p & 1) != 0) {
            res = muliMatrix(res, tmp);
        }
        tmp = muliMatrix(tmp, tmp);
    }
    return res;
}

public static int[][] muliMatrix(int[][] m1, int[][] m2) {
    int[][] res = new int[m1.length][m2[0].length];
    for (int i = 0; i < m1.length; i++) {
        for (int j = 0; j < m2[0].length; j++) {
            for (int k = 0; k < m2.length; k++) {
                res[i][j] += m1[i][k] * m2[k][j];
            }
        }
    }
    return res;
}

题目二十八

image

/*
思路:本题的本质就是求1~N中的数有多少个数是斐波那契数列中的数,只要去掉除了斐波那契数列中的数,就永远组不成三角形。
*/
public static int minDelete(int m) {
    if (m < 4) {
        return 0;
    }
    int k_2 = 2;
    int k_1 = 3;
    int num = 3;
    while (k_2 + k_1 <= m) {
        num++;
        k_1 += k_2;
        k_2 = k_1 - k_2;
    }
    return m - num;
}

题目二十九

image

/*
思路:定义一个二维数组,dp[i][j]的意思是利用arr[0...i]之间的物品有多少种方法能够满足j,dp[i][j]=dp[i-1][j]+dp[i-1][j-arr[i]](一种是不用arr[i],一种是用arr[i])
 */
public static int ways(int[] arr, int w) {
    if (arr == null || arr.length == 0 || w < 0) {
        return 0;
    }
    int[][] dp=new int[arr.length][w+1];  //定义一个二维数组,dp[i][j]的意思是利用arr[0...i]之间的物品有多少种方法能够满足j
    for(int i=0;i<dp.length;i++){   //容量为0的时候也是一种方法
        dp[i][0]=1;
    }
    for (int j = 1; j <= w; j++) {  //当使用第一个物品的时候,只要背包的容量比它大,则方法数为2
        dp[0][j] = j >= arr[0] ? 2 : 1;
    }
    for (int i = 1; i < arr.length; i++) {
        for (int j = 1; j <= w; j++) {
            dp[i][j] = dp[i - 1][j];    //不采用arr[i]时的方法数
            if (j - arr[i] >= 0) {  //如果容量比arr[i]大,则也需要加上采用arr[i]时的方法数
                dp[i][j] += dp[i - 1][j - arr[i]];
            }
        }
    }
    return dp[arr.length - 1][w];
}

题目三十

image

/*
思路:将job类型的数据按照难度进行升序排列,若难度一样按照报酬降序排列,最终只留下每一个难度中报酬最高的job,剩下的job中如果难度变大了,但是报酬降低了,直接将节点删除。
例如:(1,3),(2,5),(3,4),(4,10)中可以把(3,4)节点删除。
 */
public static class Job {
    public int money;
    public int hard;

    public Job(int money, int hard) {
        this.money = money;
        this.hard = hard;
    }
}

public static class JobComparator implements Comparator<Job> {

    @Override
    public int compare(Job o1, Job o2) {
        return o1.hard != o2.hard ? o1.hard - o2.hard : o2.money - o1.money;    //如果难度不一样,按照难度升序,如果难度一样,按照报酬降序
    }
}

public static int[] getMoneys(Job[] job, int[] ability) {
    Arrays.sort(job, new JobComparator());
    TreeMap<Integer, Integer> map = new TreeMap<>();   //有序的存储难度和报酬
    map.put(job[0].hard, job[0].money);
    Job pre = job[0];    //记录上一个job,便于后面将符合条件的job加入map
    for (int i = 1; i < job.length; i++) {
        if (job[i].hard != pre.hard && job[i].money > pre.money) {  //如果难度不一样,且难度大的报酬也比难度小的报酬高,就放入map
            pre = job[i];
            map.put(job[i].hard, job[i].money);
        }
    }
    int[] res = new int[ability.length];  //存储每一个人的最大报酬
    for (int i = 0; i < ability.length; i++) {
        Integer value = map.floorKey(ability[i]);   //返回小于等于给定值的最大值
        res[i] = value != null ? map.get(value) : 0;    //如果value为空,则报酬为0;如果value不为空,则在map中获取对应的报酬
    }
    return res;
}

题目三十一

image

/*
将正数按照负数来做,防止是最小值的时候会溢出
 */
public static int convert(String str) {
    if (str == null || str.equals("")) {  //字符串为空,不能转换,返回0
        return 0;
    }
    char[] chars = str.toCharArray();
    if (!isValid(chars)) {    //如果不符合日常书写格式,不能转换,返回0
        return 0;
    }
    int min = Integer.MIN_VALUE;
    int minq = min / 10;     //拿到最小的数整除10后的商,便于判断是否会溢出
    int minr = min % 10;     //拿到最小的数除以10后的余数,便于判断是否会溢出
    int res = 0;  //用于计算最后的结果
    boolean neg = chars[0] == '-';  //用于判断是正数还是负数
    for (int i = chars[0] == '-' ? 1 : 0; i < chars.length; i++) {    //如果第一位是-号,从第二位开始处理,如果第一位不是-号,从第一位开始处理
        int cur = '0' - chars[i];      //将每一个数字按照负数处理
        if (res < minq || (res == minq && cur < minr)) {  //如果res < minq,则res*10就小于Integer.MIN_VALUE,越界了;如果res==minq,res*10不会越界,但是cur<minr,res*10+cur之后就越界了
            return 0;
        }
        res = res * 10 + cur;
    }
    if (!neg && res == min) { //如果是个正数,但是按照负数处理后等于最小值,则将其取反后会溢出,不能转换,返回0
        return 0;
    }
    return neg ? res : -res;
}

//用于判断给定的字符串是否符合日常书写格式
/*
日常书写格式:
1.数字之外只许有“-”
2.如果有“-”,只能在开头且出现一次,其后面有数字但不能是0
3.如果开头为0,后续必无数字
*/
public static boolean isValid(char[] chas) {
    if (chas[0] == '0' && chas.length > 1) {    //开头为0,且其后面还有数字,返回false
        return false;
    }
    if (chas[0] == '-' && (chas.length == 1 || chas[1] == '0')) {     //开头为-,但是其后面是0或者只有一个-,返回false
        return false;
    }
    if (chas[0] != '-' && (chas[0] < '0' || chas[0] > '9')) {   //开头不为-,但也不是0~9之间的数,返回false
        return false;
    }
    for (int i = 1; i < chas.length; i++) {
        if (chas[i] < '0' || chas[0] > '9') {
            return false;
        }
    }
    return true;
}

题目三十二

image

/*
思路:利用前缀树的思想
 */
//定义节点类,用于存储每一个节点的字符串及其后续相邻的节点
public static class Node {
    public String name;
    public TreeMap<String, Node> map = new TreeMap<>();     //按顺序存储后续相邻的节点

    public Node(String name) {
        this.name = name;
        map = new TreeMap<>();
    }
}

public static void print(String[] folderPaths) {
    if (folderPaths == null || folderPaths.length == 0) {
        return;
    }
    Node head = generateFolderTree(folderPaths);
    printProcess(head, 0);
}

//生成前缀树
public static Node generateFolderTree(String[] folderPaths) {
    Node head = new Node("");     //定义一个头结点
    for (String folderPath : folderPaths) {
        String[] paths=folderPath.split("\\\\");    //对路径按照\进行分割
        Node cur=head;
        for(int i=0;i<paths.length;i++){
            if(!cur.map.containsKey(paths[i])){ //如果当前节点不包含它的后续节点,将其后续节点创建出来放入当前节点的map中
                cur.map.put(paths[i],new Node(paths[i]));
            }
            cur=cur.map.get(paths[i]);  //cur移动到下一个节点
        }
    }
    return head;
}

//按照题目要求格式打印
public static void printProcess(Node head, int level) {
    if (level != 0) {
        System.out.println(get2nSpace(level) + head.name);
    }
    for (Node next : head.map.values()) {
        printProcess(next, level + 1);
    }
}

//得到每个字符之前需要打印几个空格,n代表字符是哪一层的
public static String get2nSpace(int n) {
    StringBuilder res = new StringBuilder("");
    for (int i = 1; i < n; i++) {
        res.append("  ");
    }
    return res.toString();
}

题目三十三

image

//定义树节点的结构
public static class Node {
    public int value;
    public Node left;
    public Node right;

    public Node(int value) {
        this.value = value;
    }
}

public static class ReturnType {
    public Node start;  //转换成链表之后的头结点
    public Node end;    //转换成链表之后的尾节点

    public ReturnType(Node start, Node end) {
        this.start = start;
        this.end = end;
    }
}

public static ReturnType process(Node head) {
    if (head == null) {
        return new ReturnType(null, null);
    }
    ReturnType leftList = process(head.left);     //得到左子树返回的头结点和尾节点
    ReturnType rightList = process(head.right);   //得到右子树返回的头结点和尾节点
    if (leftList.end != null) {     //如果左子树的尾节点不为空,将尾节点的right指针指向head
        leftList.end.right = head;
    }
    head.left = leftList.end;
    head.right = rightList.start;
    if (rightList.start != null) {      //如果右子树的头节点不为空,将头节点的left指针指向head
        rightList.start.left = head;
    }
    return new ReturnType(leftList.start != null ? leftList.start : head, rightList.end != null ? rightList.end : head);
}

public static Node convert(Node head) {
    if (head == null) {
        return null;
    }
    return process(head).start;
}

题目三十四

image

/*
思路:树形dp套路,每个节点返回其左子树和右子树的最大值,最小值,节点数和其最大搜索二叉树的头结点
 */

//定义节点类型
public static class Node {
    public int value;
    public Node left;
    public Node right;

    public Node(int value) {
        this.value = value;
    }
}

public static Node getMaxBST(Node head) {
    return process(head).maxBSTHead;
}

//定义每一个节点需要返回的类型
public static class ReturnType {
    public int min;     //最小值
    public int max;     //最大值
    public Node maxBSTHead;     //最大搜索二叉树的头结点
    public int maxBSTSize;      //最大搜索二叉树的节点个数

    public ReturnType(int min, int max, Node maxBSTHead, int maxBSTSize) {
        this.min = min;
        this.max = max;
        this.maxBSTHead = maxBSTHead;
        this.maxBSTSize = maxBSTSize;
    }
}

public static ReturnType process(Node x) {
    if (x == null) {    //节点为空,返回的最小值是int类型的最大值是为了判断当前节点小于右子树的最小值成立,返回的最大值是int类型的最小值是为了判断当前节点大于左子树的最大值成立
        return new ReturnType(Integer.MAX_VALUE, Integer.MIN_VALUE, null, 0);
    }
    ReturnType left = process(x.left);
    ReturnType right = process(x.right);
    int max = Math.max(x.value, Math.max(left.max, right.max));
    int min = Math.min(x.value, Math.min(left.min, right.min));
    int maxBSTSize = Math.max(left.maxBSTSize, right.maxBSTSize);
    Node maxBSTHead = left.maxBSTSize > right.maxBSTSize ? left.maxBSTHead : right.maxBSTHead;  //哪个子树的节点多,则将头结点设为谁
    if (left.max < x.value && right.min > x.value) {    //如果左子树的最大值小于当前节点,右子树的最小值大于当前节点,则可以将当前节点也加入搜索二叉树
        maxBSTSize = left.maxBSTSize + 1 + right.maxBSTSize;  //最大节点树就为左子树的节点树加上右子树的节点数再加上当前节点
        maxBSTHead = x;   //最大搜索二叉树的头结点设为x
    }
    return new ReturnType(min, max, maxBSTHead, maxBSTSize);
}

题目三十五

image

/*
思路: 定义两个变量,按照下述步骤进行计算
1.cur每次加arr[i]
2.判断cur和max的大小,max为两者之间较大的那个
3.判断cur,如果cur小于0,则将cur重新赋值为0,如果cur大于等于0就不变。
 */

/*
这里提到了一个套路:假设答案法。为什么可以按照上述思路做,这里做一下证明。
假设arr[i...j]是子数组中累加和最大,长度也是最长的。则可以得到arr[i....j-1]范围内,任意的从i开始的子数组的累加和一定大于等于0,arr[0....i-1]范围内任意的以i-1结尾的子数组的累加和一定小于0。
所以从i位置开始从0开始累加是没问题的。
 */

public static int maxSum(int[] arr) {
    int cur = 0;
    int max = 0;
    for (int i : arr) {
        cur += i;
        max = Math.max(max, cur);
        cur = Math.max(cur, 0);
    }
    return max;
}

题目三十六

image

public static int maxSum(int[][] arr) {
    if (arr == null || arr.length == 0 || arr[0].length == 0) {
        return 0;
    }
    int max = 0;
    for (int i = 0; i < arr.length; i++) {
        int[] temp = new int[arr[0].length];
        for (int j = i; j < arr.length; j++) {
            for(int k=0;k<temp.length;k++){
                temp[k]+=arr[j][k];     //进行矩阵压缩
            }
            max=Math.max(max,maxSumArr(temp));
        }
    }
    return max;
}

public static int maxSumArr(int[] arr) {
    int cur = 0;
    int max = 0;
    for (int i : arr) {
        cur += i;
        max = Math.max(cur, max);
        cur = Math.max(0, cur);
    }
    return max;
}

题目三十七

image

//贪心策略,潜台词:i-1位置之前不会影响当前i位置的摆放
public static int minLight(String s) {
    char[] chars = s.toCharArray();
    int index = 0;
    int light = 0;
    while (index < chars.length) {
        if (chars[index] == 'X') {  //如果当前位置是X,直接后移
            index++;
        } else {  //当前位置是.
            light++;
            if (index + 1 == chars.length) {  //如果下一个位置越界了,直接退出
                break;
            } else {
                if (chars[index + 1] == 'X') {  //如果下一个位置是X,直接跳到下下个位置考虑
                    index = index + 2;
                } else {       //如果下一个位置是.,则当前灯就影响了i,i-1,i+1三个位置,i+2位置不用管,因为i+3位置放置灯可以照亮i+2位置
                    index = index + 3;
                }
            }
        }
    }
    return light;
}

题目三十八

image

public static void buildTree(int[] pre, int[] in, Stack<Integer> stack) {
    if (pre.length == 0 || in.length == 0) {
        return;
    }
    int rootValue = pre[0];
    stack.push(rootValue);
    for (int i = 0; i < in.length; i++) {
        if (in[i] == rootValue) {   //在中序遍历数组中找到根节点,其左边是左子树,右边是右子树
            int[] preLeft = Arrays.copyOfRange(pre, 1, i + 1);   //先序遍历中的左子树
            int[] preRight = Arrays.copyOfRange(pre, i + 1, pre.length);  //先序遍历中的右子树
            int[] inLeft = Arrays.copyOfRange(in, 0, i);    //中序遍历中的左子树
            int[] inRight = Arrays.copyOfRange(in, i + 1, in.length);     //中序遍历中的右子树
            buildTree(preRight, inRight, stack);
            buildTree(preLeft, inLeft, stack);
        }
    }
}

public static int[] getPos(int[] pre, int[] in){
    Stack<Integer> stack=new Stack<>();
    buildTree(pre,in,stack);
    int[] pos=new int[stack.size()];
    int index=0;
    while(!stack.isEmpty()){
        pos[index++]=stack.pop();
    }
    return pos;
}
public static int[] getPosArray(int[] pre, int[] in) {
    if (pre == null || in == null) {
        return null;
    }
    int len = pre.length;
    int[] pos = new int[len];
    HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
    for (int i = 0; i < len; i++) {
        map.put(in[i], i);
    }
    setPos(pre, 0, len - 1, in, 0, len - 1, pos, len - 1, map);
    return pos;
}

public static int setPos(int[] p, int pi, int pj, int[] n, int ni, int nj, int[] s, int si, HashMap<Integer, Integer> map) {
    if (pi > pj) {
        return si;
    }
    s[si--] = p[pi];
    int i = map.get(p[pi]);
    si = setPos(p, pj - nj + i + 1, pj, n, i + 1, nj, s, si, map);
    return setPos(p, pi + 1, pi + i - ni, n, ni, i - 1, s, si, map);
}

题目三十九

image

//处理1-9之间的中文表示
public static String num1To9(int num) {
    if (num < 1 || num > 9) {
        return "";
    }
    String[] nums = {"一", "二", "三", "四", "五", "六", "七", "八", "九"};
    return nums[num - 1];
}

public static String num1To99(int num, boolean hasBai) {
    if (num < 1 || num > 99) {
        return "";
    }
    if (num < 10) {     //若是1-9,之间调用上面的方法
        return num1To9(num);
    }
    int shi = num / 10;
    if (shi == 1 && !hasBai) {    //如果十位是1,且没有百位,读法为十一、十二、十三等等
        return "十" + num1To9(num % 10);
    } else {
        return num1To9(shi) + "十" + num1To9(num % 10);
    }
}

public static String num1To999(int num) {
    if (num < 1 || num > 999) {
        return "";
    }
    if (num < 100) {
        return num1To99(num, false);
    }
    int bai = num / 100;
    int rest = num % 100;
    if (rest == 0) {
        return num1To9(bai) + "百";
    } else if (rest < 10) {
        return num1To9(bai) + "百零" + num1To9(rest);
    } else {
        return num1To9(bai) + "百" + num1To99(num % 100, true);
    }
}

public static String num1To9999(int num) {
    if (num < 1 || num > 9999) {
        return "";
    }
    if (num < 1000) {
        return num1To999(num);
    }
    String res = num1To9(num / 1000) + "千";
    int rest = num % 1000;
    if (rest == 0) {
        return res;
    } else if (rest < 100) {
        res += "零" + num1To99(rest, false);
    } else {
        res += num1To999(rest);
    }
    return res;
}

public static String num1To99999(int num) {
    if (num < 1 || num > 99999) {
        return "";
    }
    if (num < 9999) {
        return num1To9999(num);
    }
    String res = num1To9(num / 10000) + "万";
    int rest = num % 10000;
    if(rest==0){
        return res;
    }else if(rest<100){
        res+="零"+num1To99(rest,false);
    }else if(rest<1000){
        res+="零"+num1To999(rest);
    }else{
        res+=num1To9999(rest);
    }
    return res;
}

public static String num1To99999999(int num){
    if(num<1||num>99999999){
        return "";
    }
    int wan=num/10000;
    int rest=num%10000;
    if(wan==0){
        return num1To9999(rest);
    }
    String res=num1To9999(wan)+"万";
    if(rest==0){
        return res;
    }else if(rest<1000){
        res+="零"+num1To999(rest);
    }else{
        res+=num1To9999(rest);
    }
    return res;
}

public static String getNumChiExp(int num){
    if(num==0){
        return "零";
    }
    String res=num<0?"负":"";    //判断正负
    int yi=Math.abs(num/100000000);
    int rest=Math.abs(num%100000000);
    if(yi==0){
        return num1To99999999(rest);
    }
    res+=num1To9999(yi)+"亿";
    if(rest<10000000){
        res+="零"+num1To99999999(rest);
    }else{
        res+=num1To99999999(rest);
    }
    return res;
}
public static String num1To19(int num) {
    if (num < 1 || num > 19) {
        return "";
    }
    String[] names = { "One ", "Two ", "Three ", "Four ", "Five ", "Six ",
            "Seven ", "Eight ", "Nine ", "Ten ", "Eleven ", "Twelve ",
            "Thirteen ", "Fourteen ", "Fifteen ", "Sixteen ", "Sixteen ",
            "Eighteen ", "Nineteen " };
    return names[num - 1];
}

public static String num1To99(int num) {
    if (num < 1 || num > 99) {
        return "";
    }
    if (num < 20) {
        return num1To19(num);
    }
    int high = num / 10;
    String[] tyNames = {"Twenty ", "Thirty ", "Forty ", "Fifty ",
            "Sixty ", "Seventy ", "Eighty ", "Ninety "};
    return tyNames[high - 2] + num1To19(num % 10);
}

public static String num1To999(int num) {
    if (num < 1 || num > 999) {
        return "";
    }
    if (num < 100) {
        return num1To99(num);
    }
    int high = num / 100;
    return num1To19(high) + "Hundred " + num1To99(num % 100);
}

public static String getNumEngExp(int num) {
    if (num == 0) {
        return "Zero";
    }
    String res = "";
    if (num < 0) {
        res = "Negative, ";
    }
    if (num == Integer.MIN_VALUE) {
        res += "Two Billion, ";
        num %= -2000000000;
    }
    num = Math.abs(num);
    int high = 1000000000;
    int highIndex = 0;
    String[] names = { "Billion", "Million", "Thousand", "" };
    while (num != 0) {
        int cur = num / high;
        num %= high;
        if (cur != 0) {
            res += num1To999(cur);
            res += names[highIndex] + (num == 0 ? " " : ", ");
        }
        high /= 1000;
        highIndex++;
    }
    return res;
}

题目四十

image

public static class Node {
    int value;
    Node left;
    Node right;

    public Node(int value) {
        this.value = value;
    }
}

//获取以x为头结点的数的高度
public static int getTreeHeight(Node x, int level) {
    while (x != null) {
        level++;
        x = x.left;
    }
    return level - 1;
}

/**
 * 返回以node为头结点的完全二叉树的节点个数
 *
 * @param node 头结点
 * @param l    node节点在第几层
 * @param h    总深度
 * @return 返回节点个数
 */
public static int bs(Node node, int l, int h) {
    if (l == h) {
        return 1;
    }
    if (getTreeHeight(node.right, l + 1) == h) {   //如果当前节点的右子树的高度等于整棵树的高度,说明当前节点的左子树是满二叉树
        return (1 << (h - l)) + bs(node.right, l + 1, h);    //满二叉树的节点个数是2^h-1,加上当前节点,所以节点个数是2^h,再加上当前节点的右子树的节点个数
    } else {  //如果当前节点的右子树的高度不等于整棵树的高度,说明当前节点的右子树是满二叉树
        return (1 << (h - l - 1)) + bs(node.left, l + 1, h);
    }
}

public static int nodeNum(Node head) {
    if (head == null) {
        return 0;
    }
    return bs(head, 1, getTreeHeight(head, 1));
}

题目四十一

image

//时间复杂度O(n^2)
public static int lengthOfLIS(int[] nums) {
    int[] dp = new int[nums.length];
    Arrays.fill(dp, 1);
    for (int i = 0; i < dp.length; i++) {
        for (int j = 0; j < i; j++) {
            if (nums[i] > nums[j]) {
                dp[i] = Math.max(dp[i], dp[j] + 1);
            }
        }
    }
    int res = 0;
    for (int i = 0; i < dp.length; i++) {
        res = Math.max(res, dp[i]);
    }
    return res;
}
//时间复杂度O(nlogn)
public static int lengthOfLIS(int[] nums) {
    if(nums.length==1){
        return 1;
    }
//        int[] dp=new int[nums.length];  //dp[i]的含义是以arr[i]结尾的最长递增子序列的长度
    int[] ends=new int[nums.length];    //ends[i]的含义是所有长度为i+1的递增子序列中最小的结尾值
//        Arrays.fill(dp,Integer.MIN_VALUE);  //全部初始化为int的最小值,防止产生歧义
    Arrays.fill(ends,Integer.MIN_VALUE);    //全部初始化为int的最小值,防止产生歧义
    ends[0]=nums[0];
    int max=Integer.MIN_VALUE;  //用于存储最长的递增子序列
    for (int i = 1; i < nums.length; i++) {
        int[] num = getNum(ends, nums[i]);
//            for (int i1 : num) {
//                System.out.println(i1);
//            }
//            System.out.println();
        max=Math.max(max,findNum(num, nums[i]));
    }
    return max;
}

/**
 * 用于找到比要插入的数大,且最靠左边的数,把它覆盖掉,然后返回
 * @param arr   要进行比较的数组
 * @param k 当前要插入的数
 * @return  返回处理后的数组
 */
public static int[] getNum(int[] arr,int k){
    int index=0;    //用于表示arr数组中的有效范围
    while(arr[index]!=Integer.MIN_VALUE){
        index++;
    }
    //利用二分法找到比k大,且最靠左的数
    int left=0;
    int right=index-1;
    int candidate=0;    //比k大,最靠左的数位置
    boolean hasBig=false;   //用于标识数组中是否有比当前值大的数
    while(left<=right){
        int mid=left+((right-left)>>1);
        if(arr[mid]<k){
            left=mid+1;
        }else{
            hasBig=true;
            candidate=mid;
            right=mid-1;
        }
    }
    if(hasBig){
        arr[candidate]=k;
    }else{
        arr[index]=k;
    }
    return arr;
}

/**
 * 用于找到在arr中num前面有几个数
 * @param arr   所给数组
 * @param num   要查找的数
 * @return  返回在arr中num前面有几个数
 */
public static int findNum(int[] arr,int num){
    int length=0;
    for (int value : arr) {
        if (value <= num&&value!=Integer.MIN_VALUE) {
            length++;
        } else {
            break;
        }
    }
    return length;
}

题目四十二

image

/*
思路:
判断一个数能不能被3整除,等价于一个数的每位之和能否被3整除。刚开始想打表,但发现数据量是1e9,一维数组最多只能开到1e8.所以就纯暴力判断了,
不过数据是有规律的,第一个数是1、第二个数是12,第三个数是123,所以只用判断n*(n+1)/2%3即可。因为数量太大了,所以用long long
 */
public static int getNum(int l,int r){
    int sum=0;
    for(int i=l;i<=r;i++){
        long tmp = (long) (i + 1) * (long) i / 2L;
        if(tmp%3==0){
            sum++;
        }
    }
    return sum;
}

题目四十三

image

/*
思路:确保数组中i的位置存储的数为i+1,最终哪个位置上的数不符合条件,就是不存在的数
 */
public static void printNumberNoInArray(int[] arr) {
    if(arr==null||arr.length==0){
        return;
    }
    for (int i : arr) {
        modify(i,arr);
    }
    for (int i = 0; i < arr.length; i++) {
        if(arr[i]!=i+1){
            System.out.println(i+1);
        }
    }
}

//将数组中的数按照i位置存储1+1的方式进行元素移动
public static void modify(int val,int[] arr){
    while(arr[val-1]!=val){     //不满足条件,就一直交换
        int temp=arr[val-1];
        arr[val-1]=val;
        val=temp;
    }
}

题目四十四

image

public static int minCcoins1(int add, int times, int del, int start, int end) {
    if (start > end) {
        return -1;
    }
    return process(0, end, add, times, del, start, end * 2, ((end - start) / 2) * add);
}

//pre   之前已经花了多少钱  可变
//aim   目标  固定
//add、times、del  固定
//finish   此时来到的人气  可变
//limitAim   人气大到什么程度就不需要尝试了  固定
//limitCoin   已经花费的钱大到什么程度就不需要尝试了  固定
public static int process(int pre, int aim, int add, int times, int del, int finish, int limitAim, int limitCoin) {
    if (pre > limitCoin) {
        return Integer.MAX_VALUE;
    }
    if (aim < 0) {
        return Integer.MAX_VALUE;
    }
    if (aim > limitAim) {
        return Integer.MAX_VALUE;
    }
    if (aim == finish) {
        return pre;
    }
    int min = Integer.MAX_VALUE;
    int p1 = process(pre + add, aim - 2, add, times, del, finish, limitAim, limitCoin);
    if (p1 != Integer.MAX_VALUE) {
        min = p1;
    }
    int p2 = process(pre + del, aim + 2, add, times, del, finish, limitAim, limitCoin);
    if (p2 != Integer.MAX_VALUE) {
        min = Math.min(min, p2);
    }
    if ((aim & 1) == 0) {    //如果aim是偶数
        int p3 = process(pre + times, aim / 2, add, times, del, finish, limitAim, limitCoin);
        if (p3 != Integer.MAX_VALUE) {
            min = Math.min(min, p3);
        }
    }
    return min;
}

public static int minCcoins2(int add, int times, int del, int start, int end) {
    if (start > end) {
        return -1;
    }
    int limitCoin = ((end - start) / 2) * add;
    int limitAim = end * 2;
    int[][] dp = new int[limitCoin + 1][limitAim + 1];
    for (int pre = 0; pre <= limitCoin; pre++) {
        for (int aim = 0; aim <= limitAim; aim++) {
            if (aim == start) {
                dp[pre][aim] = pre;
            } else {
                dp[pre][aim] = Integer.MAX_VALUE;
            }
        }
    }
    for (int pre = limitCoin; pre >= 0; pre--) {
        for (int aim = 0; aim <= limitAim; aim++) {
            if (aim - 2 >= 0 && pre + add <= limitCoin) {
                dp[pre][aim] = Math.min(dp[pre][aim], dp[pre + add][aim - 2]);
            }
            if (aim + 2 <= limitAim && pre + del <= limitCoin) {
                dp[pre][aim] = Math.min(dp[pre][aim], dp[pre + del][aim + 2]);
            }
            if ((aim & 1) == 0) {
                if (aim / 2 >= 0 && pre + times <= limitCoin) {
                    dp[pre][aim] = Math.min(dp[pre][aim], dp[pre + times][aim / 2]);
                }
            }
        }
    }
    return dp[0][end];
}

题目四十五

image
image

/**
 * 获取到的最大利益
 * @param allTime   所给的有限时间
 * @param revenue   每个活动所给报酬
 * @param times     每个活动所需要的时间
 * @param dependents    活动之间的依赖关系
 * @return  返回在有限时间内能获得的最大利益
 */
public static int[] maxRevenue(int allTime, int[] revenue, int[] times, int[][] dependents) {
    int size=revenue.length;
    HashMap<Integer, ArrayList<Integer>> parents=new HashMap<>();   //用于存储节点之间的依赖关系
    for (int i = 0; i < size; i++) {
        parents.put(i,new ArrayList<>());
    }
    int end=-1;     //用于标识最后一个活动节点是哪个
    for (int i = 0; i < dependents.length; i++) {
        boolean allZero=true;
        for (int j = 0; j < dependents[0].length; j++) {
            if(dependents[i][j]!=0){
                parents.get(j).add(i);      //反着存,便于后面天数和报酬的处理
                allZero=false;
            }
        }
        if(allZero){
            end=i;
        }
    }
    HashMap<Integer, TreeMap<Integer,Integer>> nodeCostRevenueMap=new HashMap<>();  //TreeMap存储天数和对应的报酬,HashMap存储到了当前节点的活动所需要消耗的天数和报酬
    for (int i = 0; i < size; i++) {
        nodeCostRevenueMap.put(i,new TreeMap<>());
    }
    nodeCostRevenueMap.get(end).put(times[end],revenue[end]);   //从最后一个活动节点开始往前推
    LinkedList<Integer> queue=new LinkedList<>();
    queue.add(end);
    while(!queue.isEmpty()){
        int cur=queue.poll();
        for (int last : parents.get(cur)) {
            System.out.println(last);
            for (Map.Entry<Integer, Integer> entry : nodeCostRevenueMap.get(cur).entrySet()) {
                int lastCost=entry.getKey()+times[last];
                int lastRevenue=entry.getValue()+revenue[last];
                TreeMap<Integer,Integer> lastMap=nodeCostRevenueMap.get(last);
                if(lastMap.floorKey(lastCost)==null||lastMap.get(lastMap.floorKey(lastCost))<lastRevenue){
                    lastMap.put(lastCost,lastRevenue);
                }
            }
            queue.add(last);
        }
    }
    TreeMap<Integer, Integer> allMap = new TreeMap<>();
    for (TreeMap<Integer, Integer> curMap : nodeCostRevenueMap.values()) {
        for (Map.Entry<Integer, Integer> entry : curMap.entrySet()) {
            int cost = entry.getKey();
            int reven = entry.getValue();
            if (allMap.floorKey(cost) == null || allMap.get(allMap.floorKey(cost)) < reven) {
                allMap.put(cost, reven);
            }
        }
    }
    return new int[] { allMap.floorKey(allTime), allMap.get(allMap.floorKey(allTime)) };
}

题目四十六

image

//判断当前字符串是否有效
public static boolean isValid(char[] exp) {
    if ((exp.length & 1) == 0) {  //表达式的长度一定是奇数,如果跟1与之后为0,说明表达式的长度为偶数,返回false
        return false;
    }
    for (int i = 0; i < exp.length; i += 2) {
        if (exp[i] != '0' && exp[i] != '1') {   //如果偶数位不是0或者1,返回false
            return false;
        }
    }
    for (int i = 1; i < exp.length; i += 2) {
        if (exp[i] != '&' && exp[i] != '|' && exp[i] != '^') {   //如果奇数位不是&、^、|,返回false
            return false;
        }
    }
    return true;
}

public static int num1(String express, boolean desired) {
    if (express == null || express.equals("")) {
        return 0;
    }
    char[] chars = express.toCharArray();
    if (!isValid(chars)) {
        return 0;
    }
    return p(chars, desired, 0, chars.length - 1);
}

private static int p(char[] chars, boolean desired, int L, int R) {
    if (L == R) {   //base case
        if (chars[L] == '1') {  //如果当前字符是1,且最后要求为true,则有1种方式
            return desired ? 1 : 0;
        } else if (chars[L] == '0') {    //如果当前字符是0,且最后要求为false,则有1种方式
            return desired ? 0 : 1;
        }
    }
    //L.....R  不止一个字符,且一定是奇数个字符         R和L位置一定是数字
    int res = 0;
    if (desired) {    //期待为true
        //尝试L....R范围内的每一个逻辑符号,都是最后结合的
        for (int i = L + 1; i < R; i += 2) {
            switch (chars[i]) {
                case '&':   //当前符号是&,要求其前后两个表达式都为true
                    res += p(chars, true, L, i - 1) * p(chars, true, i + 1, R);
                    break;
                case '|':   //当前符号是|,要求其前后两个表达式至少一个表达式为true
                    res += p(chars, true, L, i - 1) * p(chars, true, i + 1, R);
                    res += p(chars, true, L, i - 1) * p(chars, false, i + 1, R);
                    res += p(chars, false, L, i - 1) * p(chars, true, i + 1, R);
                    break;
                case '^':   //当前符号是^,要求其前后两个表达式的值不一致
                    res += p(chars, true, L, i - 1) * p(chars, false, i + 1, R);
                    res += p(chars, false, L, i - 1) * p(chars, true, i + 1, R);
                    break;
            }
        }
    } else {  //期待为false
        //尝试L....R范围内的每一个逻辑符号,都是最后结合的
        for (int i = L + 1; i < R; i += 2) {
            switch (chars[i]) {
                case '&':   //当前符号是&,要求其前后两个表达式至少一个为false
                    res += p(chars, true, L, i - 1) * p(chars, false, i + 1, R);
                    res += p(chars, false, L, i - 1) * p(chars, false, i + 1, R);
                    res += p(chars, false, L, i - 1) * p(chars, true, i + 1, R);
                    break;
                case '|':   //当前符号是|,要求其前后两个表达式都为false
                    res += p(chars, false, L, i - 1) * p(chars, false, i + 1, R);
                    break;
                case '^':   //当前符号是^,要求其前后两个表达式的值一致
                    res += p(chars, true, L, i - 1) * p(chars, true, i + 1, R);
                    res += p(chars, false, L, i - 1) * p(chars, false, i + 1, R);
                    break;
            }
        }
    }
    return res;
}

private static int num2(String express, boolean desired) {
    if (express == null || express.equals("")) {
        return 0;
    }
    char[] chars = express.toCharArray();
    if (!isValid(chars)) {
        return 0;
    }
    int[][] t = new int[chars.length][chars.length];    //二维数组的左下半部分是不需要考虑的
    int[][] f = new int[chars.length][chars.length];    //二维数组的左下半部分是不需要考虑的
    t[0][0] = chars[0] == '1' ? 1 : 0;
    f[0][0] = chars[0] == '0' ? 1 : 0;
    for (int i = 2; i < t.length; i += 2) {
        t[i][i] = chars[i] == '1' ? 1 : 0;  //对角线位置
        f[i][i] = chars[i] == '0' ? 1 : 0;  //对角线位置
    }
    //从下往上,从左往右
    for (int row = chars.length - 3; row >= 0; row -= 2) {    //最后一行填完了,倒数第二行不需要填,所以从倒数第三行开始填
        for (int col = row + 2; col < chars.length; col += 2) {
            for (int i = row + 1; i < col; i += 2) {
                switch (chars[i]) {
                    case '&':
                        t[row][col] += t[row][i - 1] * t[i + 1][col];
                        f[row][col] += t[row][i - 1] * f[i + 1][col] + f[row][i - 1] * t[i + 1][col] + f[row][i - 1] * f[i + 1][col];
                        break;
                    case '|':
                        t[row][col] += t[row][i - 1] * f[i + 1][col] + f[row][i - 1] * t[i + 1][col] + t[row][i - 1] * t[i + 1][col];
                        f[row][col] += f[row][i - 1] * f[i + 1][col];
                        break;
                    case '^':
                        t[row][col] += t[row][i - 1] * f[i + 1][col] + f[row][i - 1] * t[i + 1][col];
                        f[row][col] += t[row][i - 1] * t[i + 1][col] + f[row][i - 1] * f[i + 1][col];
                }
            }
        }
    }
    return desired ? t[0][chars.length - 1] : f[0][chars.length - 1];
}

题目四十七

image

/*
思路:以i位置结尾的最长子串有两个瓶颈,一个是以i-1位置结尾的最长子串,一个是i位置字符在前面的字符串出现的位置,哪个位置离i近,就取哪个。
 */
public static int maxUnique(String str) {
    if(str==null||str.equals("")){
        return 0;
    }
    char[] chars = str.toCharArray();
    int[] map=new int[256];
    Arrays.fill(map, -1);   //初始化数组值全为-1,全部字符没出现过
    int len=0;
    int pre=-1;
    int cur=0;
    for(int i=0;i<chars.length;i++){
        pre=Math.max(pre,map[chars[i]]);    //判断当前字符有没有出现过
        cur=i-pre;
        len=Math.max(len,cur);
        map[chars[i]]=i;    //把当前字符标记一下,说明已经出现过了
    }
    return len;
}

题目四十八

image

public static int minCost1(String str1, String str2, int ic, int dc, int rc) {
    if (str1 == null || str2 == null) {
        return 0;
    }
    char[] chars1 = str1.toCharArray();
    char[] chars2 = str2.toCharArray();
    int row = chars1.length + 1;
    int col = chars2.length + 1;
    int[][] dp = new int[row][col];
    //
    for (int i = 1; i < row; i++) {
        dp[i][0] = i * dc;
    }
    for (int i = 1; i < col; i++) {
        dp[0][i] = i * ic;
    }
    for (int i = 1; i < row; i++) {
        for (int j = 1; j < col; j++) {
            if (chars1[i - 1] == chars2[j - 1]) {   //如果当前位置的字符相等,则最小编辑距离与前一个位置的字符的编辑距离相等
                dp[i][j] = dp[i - 1][j - 1];
            } else {  //不相等,则需要加一次替换操作
                dp[i][j] = dp[i - 1][j - 1] + rc;
            }
            dp[i][j] = Math.min(dp[i][j], dp[i][j - 1] + ic);  //dp[i][j-1]+ic的意思是让str1的i个字符形成str2的j-1个字符,最后加一个插入操作
            dp[i][j] = Math.min(dp[i][j], dp[i - 1][j] + dc);  //dp[i-1][j]+dc的意思是让str1的i-1个字符形成str2的j个字符,最后加一个删除操作
        }
    }
    return dp[row - 1][col - 1];
}

题目四十九

image

public static String removeDuplicateLetters(String s){
    char[] chars = s.toCharArray();
    // 如果counts[i] > -1,则代表ascii码值为i的字符的出现次数
    // 如果counts[i] == -1,则代表ascii码值为i的字符不再考虑
    int[] counts=new int[26];   //都是小写字母,只需要创建一个长度为26的数组用于存储每个字符的个数
    for (char aChar : chars) {
        counts[aChar-'a']++;
    }
    char[] res=new char[26];    //存储最终结果
    int index=0;
    int L=0;
    int R=0;
    while(R!=chars.length){
        // 如果当前字符是不再考虑的,直接跳过
        // 如果当前字符的出现次数减1之后,后面还能出现,直接跳过
        if(counts[chars[R]-'a']==-1||--counts[chars[R]-'a']>0){
            R++;
        }else{  //当前字符要考虑且之后不会再出现了
            //在chars[L....R]上所有需要考虑的字符中,找到ascii最小的位置
            int pick=-1;
            for(int i=L;i<=R;i++){
                if(counts[chars[i]-'a']!=-1&&(pick==-1||chars[i]<chars[pick])){
                    pick=i;
                }
            }
            //把ascii码最小的字符放到挑选结果中
            res[index++]=chars[pick];
            // 在上一个的for循环中,str[L..R]范围上每种字符的出现次数都减少了
            // 需要把str[pick + 1..R]上每种字符的出现次数加回来
            for(int i=pick+1;i<=R;i++){
                if(counts[chars[i]-'a']!=-1){
                    counts[chars[i]-'a']++;
                }
            }
            // 选出的ascii码最小的字符,以后不再考虑了
            counts[chars[pick] - 'a'] = -1;
            // 继续在str[pick + 1......]上重复这个过程
            L = pick + 1;
            R = L;
        }
    }
    return String.valueOf(res,0,index);
}

题目五十

image

//长度为len的字符串有多少个
public static int f(int len) {
    int sum = 0;
    for (int i = 1; i <= 26; i++) {
        sum += g(i, len);
    }
    return sum;
}

//以第i个字符开头,长度为len的所有字符串中的第一个字符串是第几个
public static int g(int i, int len) {
    int sum = 0;
    if (len == 1) {     //如果长度为1,只有1种可能
        return 1;
    }
    for (int j = i + 1; j <= 26; j++) {
        sum += g(j, len - 1);
    }
    return sum;
}

public static int kth(String s) {
    char[] chars = s.toCharArray();
    int length = s.length();
    int sum = 0;
    //先把长度小于length的算出来
    for (int i = 1; i < length; i++) {
        sum += f(i);
    }
    int first = chars[0] - 'a' + 1;
    //将同长度,但是第一个字符小于当前第一个字符的字符串算出来
    for (int i = 1; i < first; i++) {
        sum += g(i, length);
    }
    int pre = first;
    //将第2个及之后位置的字符小于当前字符串对应位置的字符串算出来
    for (int i = 1; i < length; i++) {
        int cur = chars[i] - 'a' + 1;
        for (int j = pre + 1; j < cur; j++) {
            sum += g(j, length - i);
        }
        pre = cur;
    }
    return sum + 1;
}
posted @ 2024-04-26 15:19  死不悔改奇男子  阅读(8)  评论(0编辑  收藏  举报