LeetCode记录

目录

1、两数之和

2、整数反转

3、回文数

4、罗马数字转整数

5、最大公共前缀

6、有效的括号(2020/11/3)

7、合并两个有序链表 (2020/11/11)

8、删除排序数组中的重复项(2020/11/12)

9、移除元素  (2020/11/20)

10、实现 strStr() 即Java中的indexOf()  (2020/11/22)  待优化

11、搜索插入位置(2020/11/23)

12、搜索旋转排序数组 --二分法(2020/11/27)

13、寻找旋转排序数组中的最小值 -- 二分法(2020/12/2)

14、外观数列

15、最大子序和

16、最后一个单词的长度

17、数组加一

18、二进制相加

19、求x的整数平方根(二分、袖珍计算器)

20、小岛问题(广度优先)

21、有效的数独

22、解数独(递归、回溯)

23、爬楼梯(递归、动态规划、斐波那契数列)

24、删除排序链表中的重复元素

25、合并两个有序数组

26、判断树是否相同

27、对称二叉树

28、求树的深度

29、将有序数组转换为二叉搜索树

30、平衡二叉树

31、二叉树最小深度

32、树路径总和

33、反转链表

34、杨辉三角

35、杨辉三角2(求指定行)

36、买股票的最佳时机

37、买股票的最佳时机二(多次买入卖出)

38、验证回文串

39、只出现一次的数字

40、两数相加

41、环形链表(双指针、快慢指针)

42、最小栈

43、相交链表

44、合并排序(归并排序)

45、Excel表列名称

46、Excel表列序号

47、多数元素

48、字符串相加(大数相加)

49、阶乘后的零

50、组合两个表

51、第二高薪水

52、超过经理收入的员工

53、查找重复的电子邮箱

54、从不订购的客户

55、TOPN 问题、第几大问题

56、LRU算法

57、链表找环

58、二叉树分层遍历

59、字符串去重

60、快排

61、十进制转换二进制

62、两个队列实现一个栈

63、两个栈实现一个队列

64、顺序/逆序遍历矩阵

65、折线图最小线段数

66、删除元素后求均值

67、删除链表的节点

68、链表中倒数第k个节点

69、合并两个排序的链表

70、调整数组顺序使奇数位于偶数前面

71、找出数组中重复的值

72、在排序数组中查找数字

73、从上到下打印二叉树

74、镜像二叉树

75、二叉搜索树的第k大节点

76、接雨水


1、两数之和

基础版

class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0;i < nums.length;i++){
            map.put(nums[i],i);
        }
        for (int i = 0;i < nums.length;i++){
            int temp = target - nums[i];
            if(map.containsKey(temp) && map.get(temp) != i){
                return new int[] {i,map.get(temp)};
            }
        }
        throw new IllegalArgumentException("No two sum solution");
    }
}

两数之和(二)

/**
 * 给定一个已按照 升序排列  的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target
 * 你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
 *
 * **重点:升序、不可重复使用元素
 * 输入:numbers = [2,7,11,15], target = 9
 * 输出:[1,2]
 */
public class TwoSum {
    /**
     * 时间复杂度O(n)    空间复杂度O(n)
     * 不满足要求:    不可重复使用元素
     */
    public int[] twoSum(int[] numbers, int target) {
        if (numbers==null || numbers.length==0) return new int[0];

        HashMap<Integer, Integer> map = new HashMap<>();    //K target-num[i]  V index
        for(int i=0;i<numbers.length;i++){
            int num = numbers[i];
            if(map.containsKey(num)) return new int[]{map.get(num)+1,i+1};
            map.put(target-num,i);
        }
        return new int[0];
    }

    /**
     * 二分法 升序       时间复杂度O(nlogn) n->for(~;~;~) logn->while(left<=right)    空间:O(1)
     */
    public int[] twoSum2(int[] numbers, int target){
        if (numbers==null || numbers.length==0) return new int[0];
        for(int i=0;i<numbers.length-1;i++){
            int small = numbers[i];
            int sub = target - small;
            int left = i+1;
            int right = numbers.length-1;   //java.lang.ArrayIndexOutOfBoundsException
            while (left<=right){
                int mid = left + ((right-left) >> 1);
                if(sub==numbers[mid]){
                    return new int[]{i+1,mid+1};
                }else if(sub>numbers[mid]){ //当前值小于差值     mid >>
                    left = mid+1;
                }else {
                    right = mid-1;
                }
            }
        }
        return new int[0];
    }

    /**
     * 双指针  [length-1]+[0]  根据和大小判断移动哪个指针
     * 时间复杂度:O(n)  空间复杂度O(1)
     */
    public int[] twoSum3(int[] numbers, int target) {
        if (numbers == null || numbers.length == 0) return new int[0];
        int left = 0;
        int right = numbers.length-1;
        while (left<=right){
            int add = numbers[right]+numbers[left];
            if (add==target){
                return new int[]{left+1,right+1};
            }else if(add<target){
                left++;
            }else {
                right--;
            }
        }
        return new int[0];
    }
}

2、整数反转

class Solution {
    public int reverse(int x) {
        int res;
        int temp = 0 ;
        while(x!=0){
            temp = temp*10+x%10;
            if((temp>2147483647 && x/10==0) || (temp>214748364 && x>7)) {
                return 0;
            }
            if((temp<-2147483648 && x/10==0) || (temp<-214748364 && x<-8)) {
                return 0;
            }
            x = x/10;
        }
        res = temp;
        return res;
    }
}

3、回文数

class Solution {
    public boolean isPalindrome(int x) {
         if(x<0){
            return false;
        }else if(x==0){
            return true;
        }else{
            byte[] bs = String.valueOf(x).getBytes();
            for (int i = 0; i < bs.length; i++) {
                if(!(bs[i]==bs[bs.length-i-1])) {
                    return false;
                }
            }
        }
        return true;
    }
}

4、罗马数字转整数

class Solution {
    public int romanToInt(String s) {
        char[] array = s.toCharArray();
        int res = 0 ;
        for (int i=0;i<array.length-1;i++) {
            if(getNum(array[i])-getNum(array[i+1])<0) {
                res-=getNum(array[i]);
            }else{
                res+=getNum(array[i]);
            }
        }
        res+=getNum(array[array.length-1]);
        return res;
    }
    public int getNum(char str){
        switch(str){
            case 'I':
                return 1;
            case 'V':
                return 5;
            case 'X':
                return 10;
            case 'L':
                return 50;
            case 'C':
                return 100;
            case 'D':
                return 500;
            case 'M':
                return 1000;
            default:
                return 0;
        }
    }
}

5、最大公共前缀

(1)横向扫描

class Solution {
    public String longestCommonPrefix(String[] strs) {
        if(strs == null || strs.length==0) {
            return "";
        }else if(strs.length==1){
            return strs[0];
        }
        String prefix=strs[0];
        for(int i=1;i<strs.length;i++) {
            int length = Math.min(prefix.length(), strs[i].length());
            int index=0;
            while(index<length && prefix.charAt(index)==strs[i].charAt(index)) {
                index++;
            }
            prefix = prefix.substring(0,index);
            if(prefix.length()==0) {break;};
        }
        return prefix;
    }
}

(2)纵向扫描

class Solution {
    public String longestCommonPrefix(String[] strs) {
        if(strs == null || strs.length==0) {
            return ""; 
        }else if(strs.length==1){
            return strs[0] ;
        }
        int str0len = strs[0].length() ;
        int index = 0;
        String prefix = strs[0] ;
        for(int i=0;i<str0len;i++) {
            for (int j = 0; j < strs.length; j++) {
                if(i==strs[j].length() || strs[j].charAt(i) != strs[0].charAt(i)){
                    return strs[0].substring(0,i);
                }
            }
        }
        return prefix;
    }
}

6、有效的括号(2020/11/3)

class Solution {
    public boolean isValid(String s) {
		int length = s.length();
		if(length %2 ==1) return false;
		Map<Character, Character> pairs = new HashMap<Character, Character>() {
			{
				put(')','(');
				put('}','{');
				put(']','[');
			}
		};
		//双端队列:具有队列和栈的性质的数据结构
		//中心思想:只包含括号,所以右元素出现,stack top必定是左元素(中间匹配的会出栈)
		Deque<Character> stack = new LinkedList<Character>();
		for(char ch : s.toCharArray()){
			if(pairs.containsKey(ch)){
				if (stack.isEmpty() || stack.peek() != pairs.get(ch)) return false;

				stack.pop();
			}else{
				stack.push(ch);
			}
		}
		//若全部匹配最后会全部出栈
		return stack.isEmpty();
    }
}

7、合并两个有序链表 (2020/11/11)

      方法一: 递归

public class Solution {

    public static void main(String[] args) {
        ListNode listNodes = mergeTwoLists(
                new ListNode(1,new ListNode(5,new ListNode(10))), //l1
                new ListNode(2,new ListNode(4,new ListNode(5)))); //l2
        System.out.println(listNodes);
    }


    //递归  tips: 前提 两个有序链表  遇到l1或l2有null后,依次return,最后返回的是第一次对比的l1或l2
    public static ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) {
            return l2;
        } else if (l2 == null) {
            return l1;
        } else if (l1.val < l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1; //最后返回
        } else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }

    public static class ListNode {
        int val;
        ListNode next;
        ListNode() {}
        ListNode(int val) { this.val = val; }
        ListNode(int val, ListNode next) { this.val = val; this.next = next; }
        @Override
        public String toString() {
            return "ListNode{" +
                    "val=" + val +
                    ", next=" + next +
                    '}';
        }
    }
}

        方法二:迭代

//迭代 即:一个列表保存合并后的数据
    // 时间复杂度 O(n+m)  空间复杂度 O(1)
    public static ListNode mergeTwoListsByDiedai(ListNode l1,ListNode l2){
        ListNode res = new ListNode(-1);
            ListNode prev = res ; //指针  弱引用,与res一个地址

            while (l1.next !=null && l2.next != null){
                if(l1.val < l2.val){
                    prev.next = l1;
                    l1 = l1.next;
                }else{
                    prev.next = l2;
                    l2 = l2.next;
                }
                prev = prev.next; //指针后移(即:prev指向当前prev/res的next,res始终不变)
        }
            //当迭代到一个listnode为空,另一个可能没迭代完成
        prev.next = l1==null?l2:l1;
        return res.next;
    }

8、删除排序数组中的重复项(2020/11/12)

    /**
     * 题目:给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。
     * 不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
      * tips:给两个指针,一个快指针、一个慢指针  i慢 j快
     * 当array[i] == array[j]时,i停滞在重复数据的下标,!= 时 自然把重复的array[i]覆盖上array[j]的值
     * 双指针法 时间复杂度 O(n) 空间O(1)
     */
    public int removeDuplicates(int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        int i = 0;
        for(int j = 1;j<nums.length;j++){  //不满足for(condition)不执行for
            if(nums[i] != nums[j] ){
                i++;
                nums[i] = nums[j] ;
            }
        }
        return i+1; // +1 是加nums[0]元素
    }

9、移除元素  (2020/11/20)

    /**
     * 题目:给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
     * 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
     */
    public static int removeElement(int[] nums, int val) {
        int i = 0;
        //快慢双指针法,找到等于val值时,慢指针不动,不等于时,慢指针位置赋值
        for(int j = 0; j<nums.length; j++){
            if(nums[j] != val){
                nums[i] = nums[j];
                i++;
            }
        }
        return i;
    }

10、实现 strStr() 即Java中的indexOf()  (2020/11/22)  待优化

//给定一个 haystack 字符串和一个 needle 字符串,在 haystack字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回  -1。    
    /**
     *  method One:子串逐一比较 - 线性时间复杂度   窗口滑动比较
     */
    public static int strStrMethod(String haystack, String needle) {
        System.out.println("预期值:"+haystack.indexOf(needle));
        int sourceCount = haystack.length(),targetCount = needle.length();
        // sourceCount<targetCount时:for循环不成立,不执行
        // j++ 和 ++j {}内j值相等
        for (int j=0;j<sourceCount+1-targetCount;++j){
            if (needle.equals(haystack.substring(j,j+targetCount))) return j;
        }
        return -1;
    }

11、搜索插入位置(2020/11/23)

方法一:逐一比较法

//给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。 如果目标值不存在于数组中,返回它将会被按顺序插入的位置。你可以假设数组中无重复元素。
public static int SearchInsertMethod(int[] nums,int target){
        for(int i=0;i<nums.length;i++){
            if(nums[i] == target || nums[i] > target) return i;
            if (i == nums.length-1) return i+1;
        }
        return 0;
}

方法二:二分查找

public static int twoDvideMeth(int[] nums,int target){
        int res = nums.length,left=0,right=nums.length-1; //res是最终答案,先默认是数组内没有该元素
        while (left <= right){
            int mid = ((right-left) >> 1) +left; //中间值
            if(target <= nums[mid]){
                res = mid;    //因为target <= nums[mid],所以每次设置res是最大的mid,直到遍历结束
                right = mid-1;
            }else{
                left = mid+1;
            }
        }
        return res;
    }

12、搜索旋转排序数组 --二分法(2020/11/27)

//Topic: 给你一个整数数组 nums ,和一个整数 target.请你在数组中搜索 target ,
//        如果数组中存在这个目标值,则返回它的索引,否则返回 -1
public static int search(int[] num,int target){
        if(num == null || num.length == 0) return -1;
        int start = 0;
        int end = num.length-1;
        int mid ;
        while(start +1 < end){
            mid = start + ((end-start) >>1) ; // >> 运算顺序和加法一个等级
            if(num[mid] == target){
                return mid;
            }
            if (num[start] < num[mid]){//start-mid排序正常
                //upper
                if(target >= num[start] && target<=num[mid]){
                    end = mid;
                }else{
                    start = mid;
                }
            }else{
                // lower start-mid 是旋转数。包含两个分段排序数组,mid-end是正常排序
                // 还会继续把旋转数组分成 旋转数组+sort array 以此类推
                if (target >= num[mid] && target<=num[end]) {
                    start = mid;
                }else{
                    end = mid;
                }
            }
        }
        if(num[start] == target) return start;
        if(num[end] == target) return end;
        return -1;
    }

13、寻找旋转排序数组中的最小值 -- 二分法(2020/12/2)

// 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
// [3,4,5,1,2]
// 虽然旋转了,但是仍然是两段式有序。我们采用二分生序对比,最终会归结到最小值那一段的索引0,1

public int minArray(int[] numbers) {
        if(numbers==null || numbers.length==0){
            return 0;
        }

        int left=0;
        int right = numbers.length-1;
        while(left < right) {
            int pivot = left;
            if(numbers[right] > numbers[pivot]) {
                right = pivot;
            }
            else if(numbers[right] < numbers[pivot]) {
                left = pivot+1;
            }
            else {
                right -= 1;
            }
        }
        return numbers[left];

        // 遍历
        //for(int i=0;i<numbers.length-1;i++){
        //    if(numbers[i] > numbers[i+1]) {
        //        return numbers[i+1];
        //    }
        //}
        //return numbers[0];
    }

14、外观数列

/**
 * Date: 2020/12/25
 * Topic: 外观数列:是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述
 * 1.     1
 * 2.     11
 * 3.     21
 * 第一项是数字 1
 * 描述前一项,这个数是 1 即 “ 一 个 1 ”,记作 "11"
 * 描述前一项,这个数是 11 即 “ 二 个 1 ” ,记作 "21"
 */
public static String countAndSay(int n){ //n 迭代次数
        String result = "1";  //初始值1
        for(int i=1; i<n; i++){
            result = intToApperArray(result);
        }
        return result;
    }

    public static String intToApperArray(String n) {//随机数字转外观数列
        char[] array = n.toCharArray();
        int count = 0;        // current exist count char
        StringBuilder result = new StringBuilder(); // final result
        for(int i = 0; i<array.length; i++){
            if(i != 0 && array[i] != array[i-1]){ // array[now] != array[now-1]
                result.append(count).append(array[i-1]) ;
                count = 1;
            }else {
                count++;
            }
            if(i == array.length-1) result.append(count).append(array[i]) ; //last node
        }
        return result.toString();
    }

15、最大子序和


/**
 * Created by xinBa.
 * User: 辛聪明
 * Date: 2020/12/25
 * Topic: 最大子序和
 * 给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
 * 输入: [-2,1,-3,4,-1,2,1,-5,4]
 * 输出: 6
 * 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
 */

/**
     * 方法一: 动态规划:动态规划算法用于求解具有某种最优性质的问题
     *         算法思想:将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解
     *         本题思想:变量pre存放 当前最大子序和  maxAns存放 最大子序和
     * 复杂度: 时间复杂度:O(n):其中 n 为 nums 数组的长度,我们只需要遍历一遍数组即可求得答案。
     *         空间复杂度:O(1):我们只需要常数空间存放若干变量
     */
    static int maxSubArray(int[] nums){
        if(nums == null || nums.length==0) return 0;
            int pre = 0, maxAns = nums[0];
            for (int x : nums) {
                pre = Math.max(pre + x, x);
                maxAns = Math.max(maxAns, pre);
            }
        return maxAns;
    }

    /**
     * 方法二:分治法 :[分而治之]把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题……
     *              直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并.
     * 步骤:1. 划分步  2. 治理步  3. 组合步
     * 复杂度:时间复杂度:O(n)
     *        空间复杂度:O(logn)
     */
    static int DivideAndConquer(int[] nums){
        return getInfo(nums,0,nums.length-1).mSum;
    }

    static class Status{
        int lSum,rSum,iSum,mSum;
        public Status(int lSum, int rSum, int iSum, int mSum) {
            this.lSum = lSum;   //左端点的最大子段和
            this.rSum = rSum;   //右端点的最大子段和
            this.iSum = iSum;   //区间和
            this.mSum = mSum;   //整个最大子段和
        }
    }

    static Status getInfo(int[] nums,int left,int right){
        if (left == right) {
            return new Status(nums[left],nums[left],nums[left],nums[left]);    //递归遍历到最后左右相等
        }
        int mid = left + ((right-left) >> 1) ;
        Status s1 = getInfo(nums,left,mid);
        Status s2 = getInfo(nums,mid+1,right);
        return option(s1,s2);
    }

    static Status option(Status s1,Status s2){
        int lSum = Math.max(s1.lSum,s1.iSum+s2.lSum);
        int rSum = Math.max(s2.rSum,s1.rSum+s2.iSum);
        int iSum = s1.iSum + s2.iSum;
        int mSum = Math.max(Math.max(s1.mSum,s2.mSum),s1.rSum + s2.lSum);
        return new Status(lSum,rSum,iSum,mSum);
    }

16、最后一个单词的长度

    public static int lastWordMethod1(String word){
        if(word == null || word.length()==0) return 0;
        String[] words = word.split(" ");
        return words[words.length-1].length();
    }

    public static int lastWordMethod2(String word){
        if(word == null || word.length()==0) return 0;
        String trim = word.trim();
        int i = trim.lastIndexOf(" ");
        if(i>0){
            int length = trim.substring(i).length()-1;
            return length<0?0:length;
        }else{
            return trim.length();
        }
    }

    public static int lastWordMethod3(String word){
        if(word == null || word.length()==0) return 0;
        int res = 0;
        int temp = word.length()-1;
        while (temp>=0 && word.charAt(temp) == ' ') temp--;
        while (temp>=0 && word.charAt(temp) != ' ') {
            temp -- ;
            res ++ ;
        }
        return res;
    }

17、数组加一

/**
 * Created by xinBa.
 * User: 辛聪明
 * Date: 2021/2/18
 * topic:给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。
 * 输入:digits = [1,2,3]
 * 输出:[1,2,4]
 * 1 <= input.length <= 100
 * 0 <= input[i] <= 9
 */    
    if(input == null || input.length==0) return null;
        int len = input.length-1;
        input[len] = input[len]+1;
        while (len>0 && input[len] > 9){  //逐位+1
            input[len] = input[len]-10;
            input[len-1] = input[len-1]+1;
            len--;
        }
        if(input[0]>9){ //如果存在10的情况
            input = new int[input.length+1];
            input[0]=1;
        }
        return input;

18、二进制相加

/**
 * Created by xinBa.
 * User: 辛聪明
 * Date: 2021/2/19
 * Topic: 二进制数相加
 * 输入: a = "11", b = "1"
 * 输出: "100"
 */
/**
     * 方法一:自带高精度运算   缺陷: 如果字符串超过 33 位,不能转化为 Integer 报错NumberFormatException
     * 如果 a 的位数是 n,b 的位数为 m,这个算法的渐进时间复杂度为 O(n + m)
     */
    static String addBinary(String a,String b){
        int aBinary = Integer.parseInt(a,2);
        int bBinary = Integer.parseInt(b,2);
        return Integer.toBinaryString(aBinary+bBinary);
    }

    /**
     * 方法二:「列竖式」的方法,末尾对齐,逐位相加。在十进制的计算中「逢十进一」,二进制中我们需要「逢二进一」
     */
    static String addBinary2(String a, String b) {
        StringBuffer ans = new StringBuffer();
        int n = Math.max(a.length(), b.length()), carry = 0;//carry 存放进位值 即 1 or 0
        for (int i = 0; i < n; ++i) {
            /**
             * +'0' -'0' 都是转换操作
             * eg:1+'0' 是隐式转换 '0'自动向上转换成 int类型 49  so: 1+'0'=49 -> int类型
             *  '1'-'0' = 1 (int)  任何char类型 -'0' 都输出 char的对等值 int 类型数值
             *  (char)(1+'0') 是强制转换 int -> char 輸出是1
             */
            carry += i < a.length() ? (a.charAt(a.length() - 1 - i) - '0') : 0;//等值转换int
            carry += i < b.length() ? (b.charAt(b.length() - 1 - i) - '0') : 0;
            ans.append((char) (carry % 2 + '0'));
            carry /= 2; //逢二进一,除以二
        }

        if (carry > 0) {//最前位存在进位情况
            ans.append('1');
        }
        ans.reverse();//StringBuffer append 倒置存放
        return ans.toString();
    }

    static String addBinary3(String a, String b){
        StringBuffer result = new StringBuffer();
        int length = Math.max(a.length(),b.length()), carry=0;
        for (int i=0;i<length;i++){
            carry += i<a.length() ? Integer.parseInt(a.charAt(a.length()-1-i)+"") : 0;
            carry += i<b.length() ? Integer.parseInt(b.charAt(b.length()-1-i)+"") : 0;
            result.append((char)(carry%2 + 48));
            carry/=2;
        }
        if(carry>0) result.append('1');
        return result.reverse().toString();
    }

19、求x的整数平方根(二分、袖珍计算器)

 * Topic: 计算并返回 x 的平方根,其中 x 是非负整数
 * 输入: 8
 * 输出: 2    

    //二分法
    static int mySqrt(int input){
        if(input<1) return 0;
        int left = 1;
        int right = input;
        while(left <= right){
            int mid = ((right - left) >> 1) + left;
            if((long)mid*mid>input) {
                right = mid;
            }else {
                if(((long)(mid+1)*(mid+1)>input) || (long)mid*mid==input) return mid;
                left = mid;
            }
        }
        return -1;
    }

    /**
     * 袖珍计算器算法: 用指数函数 exp 和对数函数 ln 代替平方根函数的方法。
     * exp(x) = (e^(lnx))^0.5 = e^(0.5*lnx)
     */
    static int mySqrt2(int x) {
        if (x == 0)  return 0;
        int ans = (int) Math.exp(Math.log(x) * 0.5);
        //而指数函数和对数函数的参数和返回值均为浮点数,因此运算过程中会存在误差。
        //所以得到结果的整数部分ans后,我们应当找出ans与ans+1 中哪一个是真正的答案
        return (long) (ans + 1) * (ans + 1) <= x ? ans + 1 : ans;
    }

20、小岛问题(广度优先)

/**
 * Created by xinBa.
 * User: 辛聪明
 * Date: 2021/3/16
 * 小岛问题:广度优先算法
 *
 * 输入:grid = [
 *   ["1","1","0","0","0"],
 *   ["1","1","0","0","0"],
 *   ["0","0","1","0","0"],
 *   ["0","0","0","1","1"]
 * ]
 * 输出:3   当1的上下左右都不是0的时候,此岛屿孤立为一个岛,求共有多少个岛屿
 *
 */
public class IslandCount {

    public static void main(String[] args) {
        char[][] chars = {{'1','1','0','0','0'},{'1','1','0','0','0'},{'0','0','1','0','0'},{'0','0','0','1','1'}};
        char[][] char2 = {{'1','1','1','1','0'},{'1','1','0','1','0'},{'1','1','0','0','0'},{'0','0','0','0','0'}};
        IslandCount islands = new IslandCount();
        System.out.println(islands.numIslands(char2));
    }

    public int numIslands(char[][] grid) {
        boolean[][] rows = new boolean[grid.length][grid[0].length]; //用于记录是否已经访问
        int count = 0;  //岛屿数量
        for (int i=0;i<grid.length;i++){
            for(int j=0;j<grid[0].length;j++){
                if(grid[i][j] == '1' && !rows[i][j]) {  //岛屿
                    execute(grid,i,j,rows);
                    count ++;
                }
            }
        }
        return count;
    }

    private void execute(char[][] grid, int i, int j,boolean[][] row){
        if(grid[i][j] == '1') {  //岛屿
            row[i][j] = true;
            int[] x = {0,0,-1,1};  //记录上下左右的行在当前i的什么位置
            int[] y = {1,-1,0,0};  //记录上下左右的列在当前i的什么位置
            Queue<Integer> qx = new LinkedList<Integer>();  //记录行队列
            Queue<Integer> qy = new LinkedList<Integer>();  //记录列队列
            qx.offer(i);
            qy.offer(j);
            while (!qx.isEmpty()){
                int curX = qx.poll();
                int curY = qy.poll();
                for (int k=0;k<4;k++){
                    int m = curX+x[k], n = curY+y[k];
                    if(m>=0 && n>=0 && m<grid.length && n<grid[0].length && !row[m][n]){
                        if(grid[m][n] == '1'){
                            qx.offer(m);
                            qy.offer(n);
                            row[m][n] = true;
                        }
                    }
                }
            }
        }
    }
}

21、有效的数独

/**
 * Created by xinBa.
 * User: 辛聪明
 * Date: 2021/3/11
 * 根据给出的数字判断9*9数独的有效性,是否满足行、列、组内只有一个
 */
public class ValidateSuDoKu {
    public boolean validate(char[][] input){
        HashMap<Integer,Integer>[] rows = new HashMap[9];
        HashMap<Integer,Integer>[] columns = new HashMap[9];
        HashMap<Integer,Integer>[] groups = new HashMap[9];

        //初始化
        for(int i=0;i<rows.length;i++){
            rows[i] = new HashMap<Integer, Integer>();
            columns[i] = new HashMap<Integer, Integer>();
            groups[i] = new HashMap<Integer, Integer>();
        }
        for (int i=0;i<rows.length;i++){
            for (int j=0;j<rows.length;j++){
                char unit = input[i][j];
                if(unit != '.'){
                    int cell = (int) unit;
                    rows[i].put(cell,rows[i].getOrDefault(cell,0)+1);
                    columns[j].put(cell,columns[j].getOrDefault(cell,0)+1);
                    int groupIndex = (j/3)*3 + i/3;  //格子序号
                    groups[groupIndex].put(cell,groups[groupIndex].getOrDefault(cell,0)+1);

                    if(rows[i].get(cell)>1 || columns[j].get(cell)>1 || groups[groupIndex].get(cell)>1) return false;
                }
            }
        }
        return true;
    }
}

22、解数独(递归、回溯)

public class Sudoku {
    private static boolean[][] row = new boolean[9][9];             //记录行已存在的数字,即row[0][1]:第一行已存在数值1
    private static boolean[][] column = new boolean[9][9];          //记录列已存在的数字
    private static boolean[][][] group = new boolean[3][3][9];      //记录3*3棋盘已存在的数字 即:组
    private static List<int[]> cells = new ArrayList<int[]>();      //存放空白cell
    private static boolean valid = false;                           //execute全局终止依据
    private int[][] plate = new int[9][9];  //整个棋盘

    public int[][] start (){
        //将棋盘单元格放进list
        for (int i=0; i<plate.length; i++) {
            for (int j=0; j < plate.length; ++j) {   //遍历每一个单元格
                cells.add(new int[]{i, j});
            }
        }
        execute(cells,0);
        return plate;
    }


    private void execute(List<int[]> input,int index){
        if(index == input.size()) {
            valid = true; //跳出整个循环
            return;
        }
        int i = input.get(index)[0], j = input.get(index)[1];
            for (int num=0; num<9 && !valid; num++) {   //进行赋值,范围{1~9}
                //行、列、组不可重复,满足笼大小范围内
                if (row[i][num] || column[j][num] || group[i / 3][j / 3][num]
                        || (getLongTarget(i,j)-getLongCurrentSum(i,j)-num)<=0) continue;

                row[i][num] = column[j][num] = group[i / 3][j / 3][num] = true;
                plate[i][j] = num + 1; //赋值
                execute(input, index+1); //递归
                //迭代完都没有满足条件值
                row[i][num] = column[j][num] = group[i / 3][j / 3][num] = false;
            }
    }
    //已有方法:当前所在笼的所需的数字和
    private int getLongTarget(int i,int j){
        return 0;
    }
    //已有方法:当前所在笼已填入数字的加和
    private int getLongCurrentSum(int i,int j){
        return 0;
    }
}

23、爬楼梯(递归、动态规划、斐波那契数列)

/**
 * Created by xinBa.
 * User: 辛聪明
 * Date: 2021/3/14
 * 递归  爬楼梯
 *      假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
 *      每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
 *      输入: 2
 *      输出: 2
 *      解释: 有两种方法可以爬到楼顶。
 *      1.  1 阶 + 1 阶
 *      2.  2 阶
 */
public class Recursion {
    public static void main(String[] args) {
        Recursion rec = new Recursion();
//        int res = rec.factorial(10);
//        System.out.println(res);
        int res = rec.fibonacci(10);
//        1 2 3 5 8 13 21 34 55 89
        System.out.println(res);
    }

    /**
     * 求阶乘
     */
    public int factorial(int num){
        if(num == 1) return 1;
        return num * factorial(num-1);
    }

    /**
     * 斐波那契数列求迭代num次返回的值
     */
    public int fibonacci(int num){
//        if(num <= 1) return 1;
//        return fibonacci(num-1)+fibonacci(num-2);
        /**
         * 时间复杂度On  在复杂度情况下优化了空间复杂度O1
         */
        if (num <= 3) {
            return num;
        }
        int[] record = new int[3];
        record[0] = 1;
        record[1] = 2;
        for (int i=2;i<num;i++){
            record[i%3] = record[(i-1)%3] + record[(i-2)%3];
        }
        return record[(num-1)%3];
    }


     /*
      * DFS 深度优先  递归
      */
      public int climbStairsDFS(int n) {
        // n == 1 -> 1
        // n == 2 -> 2
        // n == 0 -> 0
        if (n < 3) {
            return n;
        }
        return climbStairs(n - 1) + climbStairs(n - 2);
    }

    /*
     * DP  广度优先  动态规划
     */
      public int climbStairs(int n) {
        if (n < 3) {
            return n;
        }
        int[] steps = new int[n + 1];
        steps[1] = 1;
        steps[2] = 2;
        for (int i = 3; i <= n; i++) {
            steps[i] = steps[i - 1] + steps[i - 2];
        }
        return steps[n];
    }
}

24、删除排序链表中的重复元素

/**
 * Created by xinBa.
 * User: 辛聪明
 * Date: 2021/3/19
 * Topic: 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
 */
public class DelListNodes {
    public static void main(String[] args) {
        // 1 2 2 4 4 7 8
        ListNode listNode = new ListNode(1,new ListNode(2,new ListNode(2,new ListNode(4,new ListNode(4,new ListNode(7,new ListNode(8,null)))))));
        DelListNodes listNodes = new DelListNodes();
        System.out.println(listNode);
        ListNode result = listNodes.deleteDuplicates(listNode);
        System.out.println(result);
        System.out.println(listNode);
        System.out.println(listNode==result);  //true
    }
    public ListNode deleteDuplicates(ListNode head) {
        if(head==null || head.next==null) return head;
        ListNode item = head;  //这是一个指针
        while (item.next != null && item.next != null){
            if(item.val == item.next.val){
                item.next = item.next.next ;
            }else {
                item = item.next;  //将指针在listNode上移动到下一个位置
            }
        }
        return head;
    }

    public static class ListNode {
        int val;
        ListNode next;
        ListNode() {
        }
        ListNode(int val) {
            this.val = val;
        }
        ListNode(int val, ListNode next) {
            this.val = val;
            this.next = next;
        }
        @Override
        public String toString() {
//            return "ListNode{" +
//                    "val=" + val +
//                    ", next=" + next +
//                    '}';
            return   "{val=" + val +
                    ", next=" + next +
                    '}';
        }
    }
}

25、合并两个有序数组

/**
 * Created by xinBa.
 * User: 辛聪明
 * Date: 2021/3/20
 * 给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
 * 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。你可以假设 nums1 的空间大小等于 m + n,这样它就有足够的空间保存来自 nums2 的元素。
 * 输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
 * 输出:[1,2,2,3,5,6]
 */
public class MergeSortArray {

    // 方法一:双指针 时间复杂度O(n),空间复杂度O(n)
    public int[] merge2(int[] nums1, int m, int[] nums2, int n) {

        // 临时变量存储排序后的数组
        int[] temp = new int[nums1.length];
        // 双指针用于记录nums1,nums2两个数组下标
        int p=0, q=0;

        for (int i = 0; i < temp.length; i++) {
            // nums1指针移动结束
            if (p > m-1) {
                temp[i] = nums2[q];
                q++;
            }
            // nums2指针移动结束
            else if (q>n-1) {
                temp[i] = nums1[p];
                p++;
            }
            // 用于nums1和nums2连个对比,谁小赋值给temp
            else {
                if (nums1[p] >= nums2[q]) {
                    temp[i] = nums2[q];
                    q++;
                }
                else {
                    temp[i] = nums1[p];
                    p++;
                }
            }
        }
        return temp;
    }

    // 方法二:先将nums2放到num1,然后快排。时间复杂度O(nlogn),空间复杂度O(logn)
    public void merge(int[] nums1, int m, int[] nums2, int n) {

        for (int i = 0; i < n; i++) {
            nums1[m+i] = nums2[i];
        }

        quickSort(nums1, 0, nums1.length-1);
    }

    private void quickSort(int[] nums, int start, int end) {
        if (nums == null || nums.length == 0) {
            return;
        }

        int left = start;
        int right = end;
        int pivot = left + ((right - left) >> 1);

        while (left <= right) {

            while (left <= right && nums[left] < nums[pivot]) {
                left ++;
            }

            while (left <= right && nums[right] > nums[pivot]) {
                right --;
            }

            if (left <= right) {
                int temp = nums[left];
                nums[left] = nums[right];
                nums[right] = temp;
                left++;
                right--;
            }

            quickSort(nums, left, end);
            quickSort(nums, start, right);
        }
    }
}

26、判断树是否相同

/**
 * 给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。
 * 如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
 */
public class SameTree {

    List<Integer> pList = new LinkedList<Integer>();
    List<Integer> qList = new LinkedList<Integer>();

    public boolean isSameTree(TreeNode p, TreeNode q) {
        //方法一:遍历树放到2个数组,在对比值
        loop(p,pList);
        loop(q,qList);
        return pList.equals(qList);
    }

    public void loop(TreeNode tree , List<Integer> list){
        if (tree == null) return ;
        list.add(tree.val);
        if(tree.left != null) {
            loop(tree.left,list);
        }else {
            list.add(null);
        }
        if(tree.right != null) {
            loop(tree.right,list);
        }else {
            list.add(null);
        }
    }

    //方法二:深度优先搜索    两棵树没向下搜索一次进行一次对比,减少不必要的遍历,时间复杂度 O(min(m,n)))
    public boolean loopCompare(TreeNode p, TreeNode q){
        //同时为空的时候符合规范返回true,因为最后是所有节点对比取 &&,
        if(p==null & q==null) return true;
        if (p == null || q == null) return false;
        if (p.val != q.val) return false;
        return loopCompare(p.left,q.left) && loopCompare(p.right,q.right); //重点
    }

    public class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
        TreeNode() {}
        TreeNode(int val) { this.val = val; }
        TreeNode(int val, TreeNode left, TreeNode right) {
            this.val = val;
            this.left = left;
            this.right = right;
        }
    }
}

27、对称二叉树

/**
 * 给定一个二叉树,检查它是否是镜像对称的。
 */
public class isSymmetric {

    public boolean isSymmetric(TreeNode root) {
        if (root == null) return false;
        return compare(root.left,root.right);
    }

    public boolean compare(TreeNode left , TreeNode right){
        if(left == null && right == null) return true;
        if(left == null || right == null || left.val != right.val) return false;
        return compare(left.left,right.right) && compare(left.right,right.left);  //左跟右比较
  
}

28、求树的深度

public int maxDepth2(TreeNode root) {//深度优先
    if (root == null) {
         return 0;
    } else {
         int leftHeight = maxDepth(root.left);
         int rightHeight = maxDepth(root.right);
         return Math.max(leftHeight, rightHeight) + 1;
    }
}

29、将有序数组转换为二叉搜索树

/**
 * 给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
 * 高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
 *       数组已排序,中位数可以直接提根节点,然后分段提中位数由上到下完成BST树构建,递归
 *       容易遗漏点:递归终止条件, node.left 来连接而不是直接传node下去
 */
public class SortedArrayToBST {
    public TreeNode sortedArrayToBST(int[] nums) {
        int left = 0;
        int right = nums.length-1;
        return convert(nums,left,right);
    }

    private TreeNode convert(int[] array,int left,int right){
        if(left>right) return null;
        int mid = left + ((right-left) >> 1);
        TreeNode node = new TreeNode(array[mid]);
        node.left = convert(array,left,mid-1);
        node.right = convert(array,mid+1,right);
        return node;
    }
}

30、平衡二叉树

/**
 * 给定一个二叉树,判断它是否是高度平衡的二叉树。
 * 本题中**,一棵高度平衡二叉树定义为:
 * 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 
 */
public class BalanceTree {

    /**
     * 方法一:自顶向下,前序遍历,嵌套递归,每一个节点都要求出来层数,时间复杂度O(n2) n是节点个数
     */
    public boolean isBalanced(TreeNode root) {
        if (root==null) return true;
        return Math.abs(height(root.left)-height(root.right)) <= 1
                && isBalanced(root.left) && isBalanced(root.right); //递归每一个节点
    }

    //计算当前节点的深度
    private int height(TreeNode node){//递归求出层数
        if(node==null) return 0;
        return Math.max(height(node.left),height(node.right))+1; // +1 代表层数
    }

    /**
     * 方法二:自低向下,后序遍历,当有不满足情况返回-1,一直传递到根节点,时间复杂度O(n) n是节点个数
     */
    public boolean isBalanced2(TreeNode root) {
        return height2(root) >= 0;
    }

    //计算当前节点的深度,如果有不满足高度平衡二叉树的情况就返回-1
    private int height2(TreeNode node){//递归求出层数
        if(node==null) return 0;
        int lHeight = height2(node.left);
        int rHeight = height2(node.right);
        if(lHeight == -1 || rHeight==-1 || Math.abs(lHeight-rHeight)>1){//不满足高度平衡二叉树
            return -1;
        }
        return Math.max(lHeight,rHeight)+1; // +1 代表层数
    }
}

31、二叉树最小深度

/**
 * 给定一个二叉树,找出其最小深度。
 * 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
 */
public class TreeMinDepth {
    //深度优先算法一
    public int minDepth(TreeNode root) {
        if(root == null) return 0;
        return dfs(root,0);
    }
    private int dfs(TreeNode node,int depth){//时间复杂度On
        if (node==null) return Integer.MAX_VALUE;  //只有叶子节点到根节点才算深度
        if (node.left == null && node.right==null) return depth+1;
        int left = dfs(node.left,depth+1);
        int right = dfs(node.right,depth+1);
        return Math.min(left,right);
    }
    //深度优先算法二   
    private int minDepth2(TreeNode root) {
        if (root == null) {
            return 0;
        }

        if (root.left == null && root.right == null) {
            return 1;
        }

        int min_depth = Integer.MAX_VALUE;
        if (root.left != null) {
            min_depth = Math.min(minDepth2(root.left), min_depth);
        }
        if (root.right != null) {
            min_depth = Math.min(minDepth2(root.right), min_depth);
        }
        return min_depth + 1;
    }
}

32、树路径总和

/**
 * 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在 根节点到叶子节点 的路径,
 * 这条路径上所有节点值相加等于目标和 targetSum 。
 */
public class TreeHasPathSum {
    /**
     * 深度优先  时间复杂度On
     */
    public boolean hasPathSum(TreeNode root, int targetSum) {
         if(root == null) return false; //有节点值负数情况
         targetSum -= root.val;
         if(targetSum==0 && root.left==null && root.right==null) return true;//刚好=sum && 叶子节点
         return hasPathSum(root.left,targetSum) || hasPathSum(root.right,targetSum);
    }
}

33、反转链表

/**
 * 反转单链表
 * 输入: 1->2->3->4->5->NULL
 * 输出: 5->4->3->2->1->NULL
 */
public class ReverseList {
    /**
     * 方法一:利用栈性质        时间复杂度On   空间复杂度On
     */
    public ListNode reverseList(ListNode head) {
        if (head==null || head.next==null) return head;
        Stack<ListNode> stack = new Stack<>();  //栈  先进后出
        ListNode node = head;
        ListNode temp ;
        for (;;){
            if (node.next==null) break;
            stack.push(node);
            node = node.next;
        }
        temp = node;
        while (!stack.isEmpty()){
            temp.next = stack.pop();
            temp=temp.next;
        }
        temp.next = null;
        return node;
    }

    /**
     * 方法二:迭代     时间复杂度:On  空间复杂度O1
     */
    public ListNode reverseList2(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode next = curr.next;  //临时节点
            curr.next = prev;           //将指针反转
            prev = curr;                //将前一个指针后移
            curr = next;                //将当前指针后移
        }
        return prev;
    }
}

反转单词

        String str = "you are how";
        String[] split = str.split(" ");
        StringBuilder result = new StringBuilder();
        for(int i=split.length-1;i<split.length;i--){
            result.append(split[i]+" ");
        }
        result.toString().trim();

反转指定两个下标位置

/**
 * 百度算法面试题
 */
public class ReverseIndexList {
    /**
     * 反转链表
     * 给定头指针head和left right下标(left<right) ,将两个位置反转
     * 输入: [12345] left:2 right:4
     * 輸出: [14325]
     */
    public ListNode sweap(ListNode head,int left,int right){
        ListNode node = head; //临时指针
        ListNode leftNode = null; //存储left前一个位置指针
        for (int i=1;i<=right;i++){
            if(i==left-1) {
                leftNode = node;
            }else if(i==right-1){
                ListNode tempLeft = leftNode.next;  //左节点
                ListNode tempRight = node.next;     //右节点

                leftNode.next = tempRight;          //改变指针的指向,思路画个图就清晰
                node.next = tempLeft;
                node.next.next = tempRight.next;
                tempRight.next = node;
                return head;
            }
            node = node.next;
        }
        return null;
    }
}

34、杨辉三角

/**
 * 给定一个非负整数 numRows,生成杨辉三角的前 numRows 行。
 * 在杨辉三角中,每个数是它左上方和右上方的数的和。
 * 输入: 5
 * 输出:
 * [
 *      [1],
 *     [1,1],
 *    [1,2,1],
 *   [1,3,3,1],
 *  [1,4,6,4,1]
 * ]
 */
public class YangTriangle {
    /**
     * 时间复杂度:O(numRows2)。
     * 空间复杂度:O(1)。不考虑返回值的空间占用。
     */
    public List<List<Integer>> generate(int numRows) {
        List<List<Integer>> list = new LinkedList<>();
        if(numRows<1) return list;
        for (int i=0;i<numRows;i++){
            LinkedList<Integer> rowList = new LinkedList<Integer>();
            for (int j=0;j<=i;j++){
                if (j==0 || j == i) {
                    rowList.add(1);
                } else {
                    rowList.add(list.get(i-1).get(j-1) + list.get(i-1).get(j));
                }
            }
            list.add(rowList);
        }
        return list;
    }
}

35、杨辉三角2(求指定行)

/**
 * 给定一个非负索引 k,其中 k ≤ 33,返回杨辉三角的第 k 行。
 * 在杨辉三角中,每个数是它左上方和右上方的数的和。
 * 输入: 3
 * 输出: [1,3,3,1]
 */
public class YangTriangleGetRow {

    //增加一个公共变量存储第几行的数据,下次获取就不需要在递归到第0层
    Map<Integer,List<Integer>> triangle = new HashMap<Integer,List<Integer>>();

    public List<Integer> getRow(int rowIndex) {
        List<Integer> list = new LinkedList<>();
        if(triangle.containsKey(rowIndex)) return triangle.get(rowIndex);
        if(rowIndex == 0) {
            list.add(1);
        }else {
            for (int i=0;i<=rowIndex;i++){
                if(i==0 || i==rowIndex) {
                    list.add(1);//第一位和最后一位是1,否则数组越界
                }else {
                    List<Integer> rowFont = getRow(rowIndex-1);
                    triangle.put(rowIndex-1,rowFont);
                    list.add(rowFont.get(i-1)+rowFont.get(i));
                }
            }
        }
        return list;
    }
}

36、买股票的最佳时机

/**
 * 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
 * 你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
 * 返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
 * 输入:[7,1,5,3,6,4]
 * 输出:5
 * 输入:prices = [7,6,4,3,1]
 * 输出:0
 */
public class MaxProfit {
    /**
     * 思路:最低点买入,然后遍历在之后的最高点是最大利润
     * 最低点是动态的,最大利润也是动态的,遍历一次求出最终的
     */
    public static int maxProfit(int[] prices){
        int minPrice = Integer.MAX_VALUE;
        int maxProfile = 0;
        for(int i=0;i<prices.length;i++){
            if(prices[i]<minPrice){
                minPrice = prices[i];
            }
            if(maxProfile<(prices[i]-minPrice)){
                maxProfile = prices[i]-minPrice;
            }
        }
        return maxProfile;
    }
}

37、买股票的最佳时机二(多次买入卖出)

/**
 * 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
 * 设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
 * 注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
 * 输入: [7,1,5,3,6,4]
 * 输出: 7
 * 解释: 第二天买,第三天卖,第四天买,第五天卖
 */
public class MaxProfitPlus {
    /**
     * 贪心算法:整体最优解等于不相关的子区间最优解   时间复杂度:O(n)   时间复杂度:O(1)
     */
    public int maxProfit(int[] prices){
        int maxVal = 0;
        for (int i=1;i<prices.length;i++){
            maxVal += Math.max(0,prices[i]-prices[i-1]);
        }
        return maxVal;
    }

    /**
     * 动态规划:多阶段解决,解决子区间重叠问题   时间复杂度:O(n)   时间复杂度:O(n)
     */
    public int maxProfitDP(int[] prices){
        int[][] record = new int[prices.length][2];
        record[0][0] = 0;               //当天没有买股票的最高收入
        record[0][1] = -prices[0];      //当天买入股票的最高收入
        for (int i=1;i<prices.length;i++){
            record[i][0] = Math.max(record[i-1][0],record[i-1][1]+prices[i]);  //昨天[0]和今天[0]的最大
            record[i][1] = Math.max(record[i-1][1],record[i][0]-prices[i]);    //昨天[1]和今天[1]的最大
        }
        return record[prices.length-1][0];
    }

    /**
     * 动态规划 + 滚动数组优化    时间复杂度:O(n)   时间复杂度:O(1)
     */
    public int maxProfitDPScroll(int[] prices){
        int[][] record = new int[2][2];
        record[0][0] = 0;               //当天没有买股票的最高收入
        record[0][1] = -prices[0];      //当天买入股票的最高收入
        for (int i=1;i<prices.length;i++){
            record[i%2][0] = Math.max(record[(i-1)%2][0],record[(i-1)%2][1]+prices[i]);  //昨天[0]和今天[0]的最大
            record[i%2][1] = Math.max(record[(i-1)%2][1],record[i%2][0]-prices[i]);    //昨天[1]和今天[1]的最大
        }
        return record[(prices.length-1)%2][0];
    }
}

38、验证回文串

/**
 * 给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
 * 说明:本题中,我们将空字符串定义为有效的回文串。
 * 示例 1:
 * 输入: "A man, a plan, a canal: Panama"
 * 输出: true
 */
public class IsPalindrome {
    /**
     * 双指针  时间复杂度O(n)
     */
    public boolean isPalindrome(String s) {
        if(s==null || s.equals("")) return true;
        char[] chars = s.toCharArray();
        int len = chars.length;
        int rIndex = chars.length-1;
        for (int i=0;i<len;i++){
            char left = chars[i];
            if (!Character.isLetterOrDigit(left)) continue; //移动前指针
            while (!Character.isLetterOrDigit(chars[rIndex])) {
                --rIndex;   //移动后指针
            }
            if (i>rIndex) return true;
            char right = chars[rIndex];
            if(left != right){
                if(!Character.isLetter(left) || !Character.isLetter(right) || 
                        Math.abs(left-right)!=32) return false; //如果不是字母且忽略大小写不等,返回false
            }
            --rIndex;   //移动后指针
        }
        return true;
    }

    /**
     * 方法二:筛选+判断     时间复杂度O(n)
     */
    public boolean isPalindrome2(String s){
        StringBuilder just = new StringBuilder();
        for (int i=0;i<s.length();i++){
            char at = s.charAt(i);
            if(Character.isLetterOrDigit(at)) just.append(Character.toLowerCase(at));   //数字也可以转大小写,不变
        }
        StringBuilder reverse = new StringBuilder(just).reverse(); //不new的话把just也反转了
        return just.toString().equals(reverse.toString());
    }
}

39、只出现一次的数字

/**
 * 给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
 * 说明:
 * 你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
 * 示例 1:
 * 输入: [2,2,1]
 * 输出: 1
 */
public class SingleNumber {
    /**
     * 异或运算符(^)
     * 参加运算的两个数,按二进制位进行“异或”运算。
     * 运算规则:参加运算的两个数,如果两个相应位为“异”(值不同),则该位结果为1,否则为0。
     * 即 0 ^ 0=0  , 0 ^ 1= 1  , 1 ^ 0= 1  , 1 ^ 1= 0 。
     * 例: 2 ^ 4 即 00000010 ^ 00000100 =00000110 ,所以 2 ^ 4 的值为6 。
     *
     * 总结:1、任何数和 0 做异或运算,结果仍然是原来的数,即 a^0=a。
     *      2、任何数和其自身做异或运算,结果是 0,即 a^a=0。
     *      3、异或运算满足交换律和结合律,即 a^b^a=b^a^a=b^(a^a)=b^0==b。
     *  即:十进制下 相同数字 ^ 会抵消
     *  
     *  时间复杂度:O(n),其中 n 是数组长度。只需要对数组遍历一次。
     * 空间复杂度:O(1)
     */
    public int singleNumber(int[] nums) {
        int single = 0;
        for (int num : nums) {
            single ^= num;
        }
        return single;
    }
}

40、两数相加

/**
 * 给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
 *
 * 请你将两个数相加,并以相同形式返回一个表示和的链表。
 * 你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
 */
public class SumAdd {
    public static void main(String args[]) {
        ListNode l1 = new ListNode(2,new ListNode(4,new ListNode(3)));
        ListNode l2 = new ListNode(5,new ListNode(6,new ListNode(4)));
        /**
         *   342 + 465 = 807
         */
        SumAdd sumAdd = new SumAdd();
        System.out.println(sumAdd.addTwoNumbers(l1,l2));
    }

    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode head = null, tail = null;   //head是结果的头节点  用于返回
        int carry = 0;
        while (l1 != null || l2 != null) {  //都是null的时候才结束
            int n1 = l1 != null ? l1.val : 0;
            int n2 = l2 != null ? l2.val : 0;
            int sum = n1 + n2 + carry;
            if (head == null) { //第一次
                head = tail = new ListNode(sum % 10);
            } else {
                tail.next = new ListNode(sum % 10);
                tail = tail.next;
            }
            carry = sum / 10;   //进位数值
            if (l1 != null) {
                l1 = l1.next;
            }
            if (l2 != null) {
                l2 = l2.next;
            }
        }
        if (carry > 0) {
            tail.next = new ListNode(carry);
        }
        return head;
    }

    public static class ListNode {
      int val;
      ListNode next;
      ListNode() {}
      ListNode(int val) { this.val = val; }
      ListNode(int val, ListNode next) { this.val = val; this.next = next; }

        @Override
        public String toString() {
            return "ListNode{" +
                    "val=" + val +
                    ", next=" + next +
                    '}';
        }
    }
}

41、环形链表(双指针、快慢指针)

/**
 * 给定一个链表,判断链表中是否有环。
 * 环:链表next指向之前的节点,形成死循环
 */
public class HasCycle {
    /**
     * 时间复杂度:O(N)       空间复杂度:O(N)
     */
    public boolean hasCycle(ListNode head) {
        if(head==null || head.next == null) return false;
        Set<ListNode> set = new HashSet<ListNode>();
        ListNode node = head;
        while (node.next != null){
            if(!set.add(node)) return true;     //如果此 set 已包含该元素,则该调用不更改 set 并返回 false。
            node = node.next;
        }
        return false;
    }

    /**
     * 时间复杂度:O(N)       空间复杂度:O(1)
     * 快慢指针 如果是环快慢指针肯定会相遇
     */
    public boolean hasCycle2(ListNode head) {
        if (head == null || head.next == null) {
            return false;
        }
        ListNode slow = head;
        ListNode fast = head.next;
        while (slow != fast) {
            if (fast == null || fast.next == null) {
                return false;
            }
            slow = slow.next;
            fast = fast.next.next;
        }
        return true;
    }
}

42、最小栈

/**
 * 辅助栈
 * 时间复杂度均为 O(1)。因为栈的插入、删除与读取操作都是 O(1),我们定义的每个操作最多调用栈操作两次。
 * 空间复杂度:O(n),其中 n 为总操作数。
 */
class MinStack {

    //存放实际数据
    Deque<Integer> stack = null;
    //辅助栈 用于存放最小数字
    Deque<Integer> minStack = null;


    public MinStack() {
        stack = new LinkedList<>();
        minStack = new LinkedList<>();
        minStack.push(Integer.MAX_VALUE);
    }

    public void push(int val) {
        stack.push(val);
        minStack.push(Math.min(minStack.peek(),val));    //辅助栈的栈顶永远是最小的
    }

    public void pop() {
        stack.pop();
        minStack.pop();
    }

    public int top() {
        return stack.peek();
    }

    public int getMin() {
        return minStack.peek();
    }
}

43、相交链表

/**
 * 相交链表:找到两个单链表相交的起始节点。
 * 前提条件:值相等的node是单例,只要node相等就是相交,无视后续节点
 * 输入:listA = [4,1,8,4,5], listB = [5,0,1,8,4,5]
 * 返回 [8,4,5]
 */
public class IntersectLinked {
    public static void main(String[] args) {
        ListNode headA = new ListNode(3);
        ListNode node1 = new ListNode(4);
        ListNode node2 = new ListNode(5);
        ListNode node3 = new ListNode(6);
        ListNode node4 = new ListNode(7);
        headA.next = node1;
        headA.next.next = node2;
        ListNode headB = new ListNode(2);
        headB.next = node4;
        headB.next.next = node3;        // next指向同一个地址

        IntersectLinked linked = new IntersectLinked();
        System.out.println(linked.getIntersectionNode3(headA,headB));
    }

    /**
     * 暴力法:遍历,时间复杂度O(m*n) mn是两个链表的长度    空间复杂度O(1)
     */
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA==null || headB==null) return null;
        ListNode pointA = headA;
        ListNode pointB = headB;
        while(pointA != null){
            while (pointB != null){
                if (pointA==pointB) return pointA;
                pointB = pointB.next;
            }
            pointA = pointA.next;
            pointB = headB;
        }
        return null;
    }

    /*
     * 哈希表法:遍历A每一个节点地址存到哈希表,遍历B,如果相等则是B此节点
     * 时间复杂度 : O(m+n)。空间复杂度 : O(m) 或 O(n)。
     */
    public ListNode getIntersectionNode2(ListNode headA, ListNode headB) {
        if(headA==null || headB==null) return null;
        Set<ListNode> set = new HashSet<>();

        while(headA!=null){
            set.add(headA);
            headA = headA.next;
        }
        while (headB!=null){
            if (set.contains(headB)) return headB;
            headB = headB.next;
        }
        return null;
    }

    /**
     * 双指针:一个指针遍历A,一个遍历B。当A或B遍历完了,就调换遍历,如果相交,总有相遇的时候
     * 时间复杂度:O(mn)  空间复杂度O(1)
     */
    public ListNode getIntersectionNode3(ListNode headA, ListNode headB) {
        // 特判
        if (headA == null || headB == null) return null;
        ListNode head1 = headA;
        ListNode head2 = headB;
        while (head1 != head2) {    //如果不相交   head1\head2同时为null时结束
            if (head1 != null) {
                head1 = head1.next;
            } else {
                head1 = headB;
            }
            if (head2 != null) {
                head2 = head2.next;
            } else {
                head2 = headA;
            }
        }
        return head1;
    }
}

44、合并排序(归并排序)

/**
 * 合并排序(归并排序)   核心:分治
 * 先将无序序列利用二分法划分为子序列,直至每个子序列只有一个元素(单元素序列必有序),
 * 然后再对有序子序列逐步(两两)进行合并排序
 */
public class MergeSorted {
    public static void mergeSort(int[] array){
        int length=array.length;
        int middle=length/2;
        if(length>1){
            int[]left= Arrays.copyOfRange(array,0,middle);//拷贝数组array的左半部分  [0,mid)
            int[]right=Arrays.copyOfRange(array,middle,length);//拷贝数组array的右半部分   [mid.len)
            mergeSort(left);//递归array的左半部分
            mergeSort(right);//递归array的右半部分
            merge(array,left,right);//数组左半部分、右半部分合并到Array
        }
    }

    //合并数组,升序
    private static void merge(int[]result,int[]left,int[]right){
        int i=0,l=0,r=0;
        while(l<left.length&&r<right.length){
            if(left[l]<right[r]){
                result[i]=left[l];
                i++;
                l++;
            }else{
                result[i]=right[r];
                i++;
                r++;
            }
        }
        while(r<right.length){//如果右边剩下合并右边的
            result[i]=right[r];
            r++;
            i++;
        }
        while(l<left.length){
            result[i]=left[l];
            l++;
            i++;
        }
    }
}

45、Excel表列名称

/**
 * 给定一个正整数,返回它在 Excel 表中相对应的列名称。
 *  1 -> A
 *     2 -> B
 *     3 -> C
 *     ...
 *     26 -> Z
 *     27 -> AA
 *     28 -> AB
 *     ...
 * 输入: 1
 * 输出: "A"
 * -----------
 * 输入: 28
 * 输出: "AB"
 * -----------
 * 输入: 701
 * 输出: "ZY"
 */
public class StaticProxy {
    public String convertToTitle(int columnNumber) {
        if(columnNumber<1) return "";
        StringBuilder res = new StringBuilder();
        while (columnNumber>0){ //int 除数小于1的时候转0
            columnNumber--;     //因为模余是以0开始,所以先减1
            res.append((char)('A'+columnNumber % 26));
            columnNumber /= 26;
        }
        return res.reverse().toString();
    }
}

46、Excel表列序号

/**
 * 给定一个Excel表格中的列名称,返回其相应的列序号。
 *     A -> 1
 *     B -> 2
 *     C -> 3
 *     ...
 *     Z -> 26
 *     AA -> 27
 *     AB -> 28
 *     ...
 *     输入: "A"
 *     输出: 1
 *     -------------
 *     输入: "ZY"
 *     输出: 701
 */
public class ExcelTitleToNum {
    /**
     * 对高位每个位依次转为十进制数 再相加
     * 时间复杂度:O(n2)
     */
    public int titleToNumber(String columnTitle) {
        char[] title = columnTitle.trim().toCharArray();
        int num =0;
        int len = title.length;
        for (int i=0;i<len;i++){
            char ch = title[i];
            num += Math.pow(26,(len-1-i))*((ch-'A')+1);   //公式:26的 (位数-1) 次方 * 当前数值
        }
        return num;
    }

    /**
     * 对高位每个位与下一位 转为十进制数    最后转换完毕
     * 时间复杂度:O(n)
     */
    public int titleToNumber2(String s) {
        int ans = 0;
        for(int i=0;i<s.length();i++) {
            int num = s.charAt(i) - 'A' + 1;
            ans = ans * 26 + num;
        }
        return ans;
    }
}

47、多数元素

/**
 * 给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
 * 进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。
 */
public class MajorityElement {

    /**
     * 方法一:哈希
     * 时间复杂度:O(n)   空间O(n)
     */
    public int majorityElement(int[] nums) {
        int len = nums.length;
        int stand = len >> 1;
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int i=0;i<len;i++){
            int key = nums[i];
            map.put(key,map.getOrDefault(key,0)+1);     //不存在key返回 0
            if (map.get(key)>stand) return key;
        }
        return -1;
    }

    /**
     * 算法2:排序
     * arrays.sort()  采用分策略算法  len<47 -> insert sort    len<286 ->quick sort    len>286 merge sort
     * 时间复杂度:O(nlogn)。
     * 空间复杂度:O(logn)。    自己写排序算法可以达到O(1)
     */
    public int majorityElement2(int[] nums) {
        Arrays.sort(nums);
        return nums[nums.length >> 1];  // 多数元素>(n/2)   so 排序后 n/2 的位置比是多数元素
    }

    /**
     * 算法3:随机化
     * 因为超过 n/2的数组下标被众数占据了,这样我们随机挑选一个下标对应的元素并验证,有很大的概率能找到众数。
     * 最坏时间复杂度:O(无限)
     */

    /**
     * 算法4:摩尔投票法思路
     * 一次遍历,以当前元素做为候选人,遇到等值元素count+1,否则-1,当count==0时,当前元素替换候选人
     * 显然最后和大于 0,从结果本身我们可以看出众数比其他数多。
     * 时间复杂度:O(n)   空间复杂度:O(1)
     */
    public int moerSulotion(int[] nums){
        int count = 1;  //计数器
        int curVal = nums[0];   //候选人
        for (int i=1;i<nums.length;i++){
            if(count==0) curVal = nums[i];
            if(curVal == nums[i]) {
                count++;
            }else{
                count--;
            }
        }
        return curVal;
    }
}

48、字符串相加(大数相加)

/**
 * 大数相加
 *      给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和。
 *     提示:
 *     你不能使用任何內建 BigInteger 库, 也不能直接将输入的字符串转换为整数形式
 */
public class BigNumAdd {
    /**
     * 算法:模拟 [竖式加法]
     * 时间复杂度:O(max(len1,len2))   空间复杂度O(1)
     */
    public String addStrings(String num1, String num2) {
        StringBuilder result = new StringBuilder();
        char[] arr1 = num1.toCharArray();
        char[] arr2 = num2.toCharArray();

        int len1 = arr1.length;
        int len2 = arr2.length;
        int add = 0;

        for (int i=1;;i++){ //倒序遍历  最后一位len-1
            if (i > len1 && i> len2) break;     //两个数组都遍历结束
            int num = 0;                        //进位
            if(i> len1) num = (arr2[len2-i]-'0') + add ;    //arr1遍历结束
            if(i> len2) num = (arr1[len1-i]-'0') + add ;
            if(i<=len1 && i<=len2) num = (arr1[len1-i]-'0') + (arr2[len2-i]-'0') + add ;
            add = num/10;                       //int   小数转整型强制转换
            result.append(num%10);              //取余
        }
        if (add != 0) result.append(add);   //存在情况 最后进1 而循环结束
        return result.reverse().toString();
    }
}

49、阶乘后的零

/**
 * 阶乘后0的个数
 * 给定一个整数 n,返回 n! 结果尾数中零的数量
 * 输入: 3        3! = 6
 * 输出: 0
 *--------------------------
 * 输入: 5        5! = 120
 * 输出: 1
 */
public class TrailingZeroes {
    /**
     * 算法一:时间复杂度:O(N)
     */
    public static int trailingZeroes(int n) {
        if(n==0) return 0;
        BigInteger result = BigInteger.ONE;
        while(n>1){
            result = result.multiply(BigInteger.valueOf(n--));
        }
        int zeroCount=0;
        while (result.mod(BigInteger.TEN).equals(BigInteger.ZERO)) {
            result = result.divide(BigInteger.TEN);
            zeroCount++;
        }
        return zeroCount;
    }

    /**
     * 算法二:计算因子 5
     * 因为计算0的个数,即是10的几次方
     * 阶乘是乘法,2*5=10,1*10=1*2*5=10
     * 所以计算0的个数即是算有多少个5的因子
     * 时间复杂度:O(n)   空间 O(1)
     */
    public static int executeZeroNum(int n){
        int zeroNum = 0;

        for (int i=5;i<=n;i+=5){    //只有5的公倍数才有因子5
            int val = i;
            while(val%5==0){
                zeroNum ++;
                val /= 5;     //像25这样有两个因子5
            }
        }
        return zeroNum;
    }

    /**
     * 算法三:高效的因子5个数
     * 我们不必每次尝试 5 的幂,而是每次将 n 本身除以 5,两种操作等价
     * 时间复杂度:O(logn)   空间复杂度:O(1)
     */
    public int calculateZeroNum(int n) {
        int zeroCount = 0;
        long currentMultiple = 5;
        while (n >= currentMultiple) {
            zeroCount += (n / currentMultiple);
            currentMultiple *= 5;
        }
        return zeroCount;
    }
}

50、组合两个表

##编写一个 SQL 查询,满足条件:无论 person 是否有地址信息,都需要基于上述两表提供 person 的以下信息:

 表1: Person
+-------------+---------+
| 列名         | 类型     |
+-------------+---------+
| PersonId    | int     |
| FirstName   | varchar |
| LastName    | varchar |
+-------------+---------+
PersonId 是上表主键
表2: Address
+-------------+---------+
| 列名         | 类型    |
+-------------+---------+
| AddressId   | int     |
| PersonId    | int     |
| City        | varchar |
| State       | varchar |
+-------------+---------+
AddressId 是上表主键

##左外连接    不是每个人都有address信息
select tb1.FirstName,tb1.LastName,tb2.City,tb2.State 
from
Person as tb1
left join 
Address tb2
on
tb1.PersonId = tb2.PersonId; 

51、第二高薪水

# 编写一个 SQL 查询,获取 Employee 表中第二高的薪水Salary,没有第二返回null

# 方法一:limit
# 重点:distinct去重,如果没有这样的第二最高工资,这个解决方案将被判断为 “错误答案”,
# 因为本表可能只有一项记录。为了克服这个问题,我们可以将其作为临时表。
select (
    select distinct Salary 
    from Employee
    order by Salary desc
    limit 1 offset 1
) SecondHighestSalary;

# 方法二:IFNULL
select (
    ifnull(
        (select distinct Salary 
        from Employee
        order by Salary desc
        limit 1 offset 1),
        null
    )
) SecondHighestSalary;

52、超过经理收入的员工

#给定 Employee 表,编写一个 SQL 查询,该查询可以获取收入超过他们经理的员工的姓名

#内连接    返回两个表条件符合的数据行
select a.Name as Employee
from 
Employee a, Employee b
where a.ManagerId = b.Id
and a.Salary > b.Salary;

#join 内连接 更加有效
select a.Name as Employee
from 
Employee a join
Employee b
on a.ManagerId = b.Id
and a.Salary > b.Salary;

53、查找重复的电子邮箱

#编写一个 SQL 查询,查找 Person 表中所有重复的电子邮箱。
# group having
select  Email from Person
group by Email
having count(Email)>1;

# 左外连接
select distinct a.Email from 
Person a left join Person b
on a.Email = b.Email
and a.Id != b.Id
where b.id is not null;

#group where
select Email from
(select  Email,count(Email) num from Person
group by Email) tb
where tb.num > 1;

54、从不订购的客户

# not in
select a.Name as Customers from Customers a
where a.Id not in (
    select distinct CustomerId from Orders 
);

# left join     性能较好
select a.Name as Customers from Customers a
left join Orders b
on a.Id = b.CustomerId
where b.CustomerId is null;

55、TOPN 问题、第几大问题

 /**
   * 给定一个list 找到其中第k大的元素
   * 面试官大多数不想要这个答案,可以用快排
   */
public Integer findTop (Integer[] num, int k){
    // PriorityQueue 优先级队列 ,升序:小顶堆  降序:大顶堆
    // num.length 是队列长度,超出长度则把末端的挤掉,用于求top n ,则长度直接设置 n
    PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(num.length, (o1, o2) -> {
        return o2 - o1;
    });
    for (int i=0; i<num.length; i++){
        priorityQueue.add(num[i]);
    }
    for (int i=0; i<k-1; i++){
        //弹出、丢掉
        priorityQueue.poll();
    }
    //取值,不弹出
    return priorityQueue.peek();
}

56、LRU算法

/**
 * @author xinjianan
 * @Description     Lru 最近最少使用,常用的淘汰算法
 * @createTime 2021年07月22日 17:26:00
 * @Description: 实现思路:双向链表 + hashmap  链表用于存储数据,hashmap用于存储指定key的节点,查找O(1)时间复杂度
 */
public class LRUCache {
    // 头节点放冷数据,即最少使用的,尾节点放最常使用的
    // 节点
    class CacheNode{
        int key;
        int val;
        CacheNode prev;
        CacheNode next;
        public CacheNode(){}
        public CacheNode(int key, int val){
            this.key = key;
            this.val = val;
        }
    }
    // 容量
    private int capacity;
    //用于查找
    private Map<Integer, CacheNode> map = new HashMap<>();
    //哨兵节点 指向头尾
    private CacheNode head, tail;
    //初始化  也可以给一个默认的 capacity
    public LRUCache(int capacity){
        this.capacity = capacity;
        // 头尾相连,0容量链表(除哨兵)
        this.head.next = tail;
        this.tail.prev = head;
    }

    public void put(int key, int val){
        if(map.containsKey(key)){
            map.get(key).val = val;
            return ;
        }
        //先判断是否达到最大长度,达到则删除
        if(map.size() == capacity){
            head.next = head.next.next;
            head.next.prev = head;
        }
        CacheNode node = new CacheNode(key,val);
        //把数据放在尾部
        moveToTail(node);
        //同步在hashmap
        map.put(key,node);
    }

    public int get(int key){
        if(!map.containsKey(key)){
            return -1;
        }
        CacheNode node = map.get(key);
        //将此节点脱离出来
        node.prev.next = node.next;
        node.next.prev = node.prev;
        moveToTail(node);
        return node.val;
    }

    //把数据节点放在尾部
    private void moveToTail(CacheNode node){
        node.prev = this.tail.prev;
        this.tail.prev.next = node;
        node.next = tail;
        tail.prev = node;
    }

57、链表找环

//方法一
MyNode findRedNode(MyNode head) {
        MyNode point = head;
        Set<MyNode> set = new HashSet<>();
        while(point.next != null) {
            if(set.contains(point)) {
                break;
            }
            set.add(point);
            point = point.next;
        }
        return point;
    }
}


/**
 * 方法二:快慢指针,快指针与慢指针是否相遇作为判断是否闭环的条件
 */
public MyNode isLoop(){
    MyNode fast = first , slow = first;//定义两个指针,分别是快和慢
    //遍历一遍链表,如果最后是null的话就代表没有环
    while (fast != null && fast.next != null){
        fast = fast.next.next;
        slow = slow.next;
        //如果俩相遇了,代表有环
        if (fast == slow){
            return fast;
        }
    }
    return null;
}

58、二叉树分层遍历

/**
 *    1    
​ *    2 3
​ *    4 5 6 7
​ *    print: 1234567
 */
public List<Integer> levelOrder(TreeNode root){
        List<Integer> result = new ArrayList<>();
        if(root == null){
            return result;
        }
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()){
            TreeNode head = queue.remove();
            result.add(head.val);
            if(head.left != null){
                queue.add(head.left);
            }
            if(head.right != null){
                queue.add(head.right);
            }
        }
        return result;
    }

59、字符串去重

//字符串去重,保证字母顺序,比如:zabcdefabcz -> zabcdef。
public static String distinct(String str){
        StringBuilder sb = new StringBuilder();
        for(int i=0; i< str.length(); i++){
            if(sb.toString().indexOf(str.charAt(i))>-1){
                continue;
            }
            sb.append(str.charAt(i));
        }
        return sb.toString();
    }

60、快排

private void quickSort(int[] num, int start, int end) {
        if (num == null || num.length == 0 || start >= end) {
            return;
        }

        int left = start;
        int right = end;
        int pivot = num[start];

        while (left <= right) {
            while (left <= right && num[left] < pivot) {
                left ++;
            }
            while (left <= right && num[right] > pivot) {
                right --;
            }

            if (left <= right) {
                int temp = num[left];
                num[left] = num[right];
                num[right] = temp;
                left++;
                right--;
            }
        }

        quickSort(num, left, end);
        quickSort(num,start, right);
    }

61、十进制转换二进制

    //方法一:Integer自带操作   Integer.toBinaryString(255)
    
    //竖式法,记录每一次的余数
    public String toBinary(Long num){
        StringBuilder sb = new StringBuilder();
        while (!"0".equals(num.toString())){
            sb.append(num % 2);
            num /= 2;
        }
        return sb.reverse().toString();
    }

    //位移法
    public static void binaryToDecimal(int n){
         for(int i = 31;i >= 0; i--){
             System.out.print(n >>> i & 1);
         }
    }

62、两个队列实现一个栈

//1、 一个队列加入元素,弹出元素时,需要把队列中的 元素放到另外一个队列中,删除最后一个元素
//2、 两个队列始终保持只有一个队列是有数据的
public class StackByQueue<T> {
    private Queue<T> queue1 = new LinkedList<>();
    private Queue<T> queue2 = new LinkedList<>();

    /**
     * 入栈
     */
    public boolean push(T t){
        if (queue1.isEmpty()){
            return queue2.offer(t);
        }else {
            return queue1.offer(t);
        }
    }

    /**
     * 出栈
     */
    public T pop(){
        if(queue1.isEmpty() && queue2.isEmpty()){
            throw new RuntimeException("queue is empty!");
        }
        if(queue1.isEmpty() && !queue2.isEmpty()){
            //取出size-1数据放入另一个队列,把最后一个元素删除返回
            while (queue2.size() > 1){
                queue1.offer(queue2.poll());
            }
            return queue2.poll();
        }
        if(!queue1.isEmpty() && queue2.isEmpty()){
            while (queue1.size() > 1){
                queue2.offer(queue1.poll());
            }
            return queue1.poll();
        }
        return null;
    }
}

63、两个栈实现一个队列

/**
 * 栈:先进后出
 * 队列:先进先出
 * 
 * in 专门用于存放队列增加的元素,是队列的倒序, 即 offer 123, 在in里存放是 321
 * out 专门用于出栈使用,当out为空时,将in数据转换到out,即 in(321) => out(123),所以队列出列就是out的pop
 *
 * 时间复杂度:appendTail为 O(1),deleteHead为均摊 O(1)。对于每个元素,至多入栈和出栈各两次,故均摊复杂度为 O(1)。
 * 空间复杂度:O(n)。其中 n 是操作总数。对于有 n 次 appendTail 操作的情况,队列中会有 n 个元素,故空间复杂度为 O(n)。
 * 
 */
public class CQueue {

    private final Stack<Integer> in;

    private final Stack<Integer> out;

    public CQueue() {
        in = new Stack<>();
        out = new Stack<>();
    }

    public void appendTail(int value) {
        in.push(value);
    }

    public int deleteHead() {
        if (in.isEmpty() && out.isEmpty()) {
            return -1;
        }

        if (out.isEmpty()) {
            while (!in.isEmpty()) {
                out.push(in.pop());
            }
        }

        return out.pop();
    }
}

64、顺序/逆序遍历矩阵

/**
 * 从外围逆时针遍历矩阵-默认非空
 * 3 6 0 7 8
 * 3 5 9 1 0
 * 0 6 7 8 9
 * 1 2 3 4 5
 * @param matrix 矩阵
 */
public static void forEach(int[][] num){
        //行
        int n = num.length;
        //列
        int m = num[0].length;
        //临界值
        int left = 0, right = m-1, up = 0, down = n-1;
        //存放结果的数组、对应坐标
        int[] result = new int[m*n];
        int resIndex = 0;

        while(left <= right && up<= down){
            // 左上 -> 左下
            for (int i=up; i<= down; i++){
                result[resIndex] = num[i][left];
                resIndex ++;
            }
            // 左下 -> 右下
            for (int i=left+1; i<= right; i++){
                result[resIndex] = num[down][i];
                resIndex ++;
            }
            // 存在-1 情况 所以等号情况不成立
            if(up<down && left < right) {
                // 右下 -> 右上
                for (int i = down - 1; i >= up; i--) {
                    result[resIndex] = num[i][right];
                    resIndex++;
                }
                // 右上 -> 左上
                for (int i=right-1; i> left; i--){
                    result[resIndex] = num[up][i];
                    resIndex ++;
                }
            }
            //边界值迭代
            left ++; right--; up++; down--;
        }
        // 7,打印最终结果
        for(int i = 0 ;i <m*n;i++){
            System.out.print(result[i]+" ");
        }
    }
    /**
     * 顺时针打印矩阵
     *
     * 输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
     * 输出:[1,2,3,6,9,8,7,4,5]
     *
     * 时间复杂度O(mn),空间复杂度O(1)
     */
    public static int[] spiralOrder(int[][] matrix) {
        if (matrix == null || matrix.length == 0) {
            return new int[0];
        }

        int row = matrix.length;
        int column = matrix[0].length;
        int[] temp = new int[row*column];

        int up=0, down=row-1, left=0, right=column-1;
        int index=0;

        while (left<=right && up<= down) {

            // 左上 >> 右上
            for (int i = left; i <= right; i++) {
                temp[index] = matrix[up][i];
                index ++;
            }

            // 右上 >> 右下
            for (int i = up+1; i <= down; i++) {
                temp[index] = matrix[i][right];
                index ++;
            }

            // 左右,上下仅有一次等于情况,两次等于就出现重复值了
            if (up < down && left < right) {
                // 右下 >> 左下
                for (int i = right-1; i >= left; i--) {
                    temp[index] = matrix[down][i];
                    index ++;
                }

                // 左下 >> 左上
                for (int i = down-1; i > up; i--) {
                    temp[index] = matrix[i][left];
                    index ++;
                }
            }

            left ++; right--; up++; down--;
        }

        return temp;
    }

65、折线图最小线段数

/**
 * leetCode: 2280
 * 时间复杂度:O(nlogn)
 * 空间复杂度:O(1)
 */
class Solution {
    // CORE: 斜率变化次数 (y2-y1)(x3-x2)=(y3-y2)(x2-x1)
    public static int minimumLines(int[][] stockPrices) {
        if (stockPrices == null) {
            return 0;
        }

        int length = stockPrices.length;
        if (length < 2) {
            return 0;
        }

        int count = 1;
        // 给定的点不一定按顺序,顺序混乱可能会算多
        Arrays.sort(stockPrices, (o1, o2) -> o1[0] - o2[0]);

        for (int i = 0; i < length-2; i++) {
            int x0 = stockPrices[i+1][0] - stockPrices[i][0];
            int y0 = stockPrices[i+1][1] - stockPrices[i][1];
            int x1 = stockPrices[i+2][0] - stockPrices[i+1][0];
            int y1 = stockPrices[i+2][1] - stockPrices[i+1][1];

            if (y1 * x0 != x1 * y0) {
                count++;
            }
        }

        return count;
    }
}

66、删除元素后求均值

// 题干:leetcode 1619 给你一个整数数组 arr ,请你删除最小 5% 的数字和最大 5% 的数字后,剩余数字的平均值。
import java.util.Arrays;

class Solution {
    public double trimMean(int[] arr) {
        Arrays.sort(arr);

        int count=0;
        int offset = (int) (arr.length * 0.05);

        for (int i = offset; i < arr.length - offset; i++) {
            System.out.print(arr[i] + "  ");
            count += arr[i];
        }

        // return sum / (n * 0.9);  // TODO 0.9是固定的
        return (double) count/(arr.length-offset*2);
    }
}

67、删除链表的节点

/**
 * 删除链表的节点
 * 
 * 输入: head = [4,5,1,9], val = 5
 * 输出: [4,1,9]
 */
public class DeleteNodeVal {

    public ListNode deleteNode(ListNode head, int val) {
        if (head == null) {
            return null;
        }

        ListNode prev = null;
        ListNode curr = head;

        while (curr != null){
            ListNode next = curr.next;
            if (val == curr.val && prev != null) {
                prev.next = next;
            }
            prev = curr;
            curr = next;
        }

        while (head.val == val) {
            head = head.next;
        }
        return head;
    }

    public static class ListNode {
        int val;
        ListNode next;

        ListNode(int x) {
            val = x;
        }

        public ListNode(int val, ListNode next) {
            this.val = val;
            this.next = next;
        }
    }
}

68、链表中倒数第k个节点

public ListNode getKthFromEnd(ListNode head, int k) {
        if (head == null || k <= 0) {
            return null;
        }
        
        Stack<ListNode> stack = new Stack<>();
        while (head != null) {
            stack.push(head);
            head = head.next;
        }

        for (int i = 0; i < k; i++) {
            ListNode pop = stack.pop();
            if (i == k - 1) {
                return pop;
            }
        }
        
        return null;
    }

69、合并两个排序的链表

 public ListNode mergeTwoLists(ListNode l1, ListNode l2) {

        ListNode result = new ListNode(-1);
        ListNode temp = result;

        while (l1 != null && l2 != null) {
            if (l1.val > l2.val) {
                temp.next = l2;
                l2 = l2.next;
            } else {
                temp.next = l1;
                l1 = l1.next;
            }
            temp = temp.next;
        }

        if (l1 != null) {
            temp.next = l1;
        }
        if (l2 != null) {
            temp.next = l2;
        }

        return result.next;
    }

70、调整数组/链表顺序使奇数位于偶数前面

 public int[] exchange(int[] nums) {

        // 双指针 时间复杂度O(n).空间复杂度O(1)
        int i=0;
        int j=nums.length-1;
        while (i<j) {
            while (i<j && nums[i] % 2 != 0) {
                i++;
            }
            while (i<j && nums[j] % 2 != 1) {
                j--;
            }

            if (i < j) {
                int temp = nums[i];
                nums[i] = nums[j];
                nums[j] = temp;
            }
        }
        return nums;


        // 双指针 时间复杂度O(n).空间复杂度O(n)
//        int[] temp = new int[nums.length];
//        int index=0;
//
//        for (int i = 0; i < nums.length; i++) {
//            if (nums[i] % 2 == 1) {
//                temp[index]=nums[i];
//                index ++;
//            }
//        }
//        for (int i = 0; i < nums.length; i++) {
//            if (nums[i] % 2 == 0) {
//                temp[index]=nums[i];
//                index ++;
//            }
//        }
//
//        return temp;
    }

    /**
     * 重拍链表奇偶,让奇数在前、偶数在后
     */
    public static ListNode oddEvenList3(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }

        ListNode odd = head;
        ListNode even = head.next;
        ListNode evenHead = even;

        while (even != null && even.next != null) {
            odd.next = even.next;
            odd = odd.next;

            even.next = odd.next;
            even = even.next;
        }

        odd.next = evenHead;

        return head;
    }

71、找出数组中重复的值

    /**
     * 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,
     * 也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
     *
     * 输入:
     * [2, 3, 1, 0, 2, 5, 3]
     * 输出:2 或 3
     */
    public int findRepeatNumber(int[] nums) {
        // 时间复杂度O(n) 空间复杂度O(1)
        // 条件是所有数字都在0~n-1范围内,如果要求空间复杂度O(1),就需要考虑这一因素
        // 因此,元素i与nums[i]是一对一的,如果出现n个元素i对应一个索引i,即重复
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == i) {
                continue;
            }

            if (nums[nums[i]] == nums[i]) {
                return nums[i];
            }

            int temp = nums[i];
            nums[i] = nums[temp];
            nums[temp] = temp;
            i--;
        }

        return -1;
    }

72、在排序数组中查找数字

    // 二分法 时间复杂度最好情况O(logn),最坏O(n)。空间复杂度o1
    public static int search(int[] nums, int target) {
        if (nums == null || nums.length == 0) {
            return 0;
        }

        // 1 先查询出target其中之一元素的索引
        int left = 0;
        int right = nums.length-1;
        Integer index = null;
        while (left <= right && index == null) {
            int pivot = left + ((right-left) >> 1);
            if (nums[pivot] == target) {
                index = pivot;
            } else if (nums[pivot] > target) {
                right = pivot-1;
            } else {
                left = pivot + 1;
            }
        }
        if (index == null) {
            return 0;
        }

        // 排序数组,遍历索引前后计算target个数,不等target时可以直接break 
        int count = 0;
        for (int i = index; i >= 0; i--) {
            if (nums[i] == target) {
                count ++;
            } else {
                break;
            }
        }
        for (int i = index+1; i<nums.length; i++) {
            if (nums[i] == target) {
                count ++;
            } else {
                break;
            }
        }

        return count;
    }

73、从上到下打印二叉树

    /**
     * 广度优先搜索
     * 例如:
     * 给定二叉树: [3,9,20,null,null,15,7],
     *
     *     3
     *    / \
     *   9  20
     *     /  \
     *    15   7
     * 返回其层次遍历结果:
     *
     * [
     *   [3],
     *   [9,20],
     *   [15,7]
     * ]
     */
    public List<List<Integer>> levelOrder(TreeNode root) {
        if (root == null) {
            return new ArrayList<>();
        }

        // 存放最终结果
        List<List<Integer>> list = new ArrayList<>();
        // 存放已遍历完节点的子节点
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()) {

            // 暂存当前层的数据
            List<Integer> temp = new ArrayList<>();
            Queue<TreeNode> tempQueue = new LinkedList<>();

            while (!queue.isEmpty()) {
                TreeNode poll = queue.poll();
                temp.add(poll.val);
                if (poll.left != null) {
                    tempQueue.add(poll.left);
                }
                if (poll.right != null) {
                    tempQueue.add(poll.right);
                }
            }

            list.add(temp);
            queue = tempQueue;
        }

        return list;
    }


    public class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;

        TreeNode(int x) {
            val = x;
        }
    }

74、镜像二叉树

/**
     * 镜像二叉树 时间复杂度O(n) 空间复杂度O(1)
     * 
     * 例如输入:
     *
     *      4
     *
     *    /   \
     *
     *   2     7
     *
     *  / \   / \
     *
     * 1   3 6   9
     * 镜像输出:
     *
     *      4
     *
     *    /   \
     *
     *   7     2
     *
     *  / \   / \
     *
     * 9   6 3   1
     */
    public TreeNode mirrorTree(TreeNode root) {
        if (root == null) {
            return root;
        }

        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);

        while (!queue.isEmpty()) {
            TreeNode poll = queue.poll();
            TreeNode right = poll.right;
            poll.right = poll.left;
            poll.left = right;
            if (poll.left != null) {
                queue.offer(poll.left);
            }
            if (poll.right != null) {
                queue.offer(poll.right);
            }
        }

        return root;
    }

    // 递归算法 时间复杂度O(n) 空间复杂度O(n)
    public TreeNode mirrorTree2(TreeNode root) {
        if (root == null) {
            return null;
        }
        TreeNode left = mirrorTree(root.left);
        TreeNode right = mirrorTree(root.right);
        root.left = right;
        root.right = left;
        return root;
    }

75、二叉搜索树的第k大节点

/**
     * 输入: root = [3,1,4,null,2], k = 1
     *    3
     *   / \
     *  1   4
     *   \
     *    2
     * 输出: 4
     *
     * 二叉搜索树的第 k 大节点
     * 此树是中序遍历,即 左-根-右... 方式排序
     * 时间复杂度O(n) 空间复杂度O(n)
     */
    public int kthLargest(TreeNode root, int k) {

        // 额外使用空间存储排序的k值
        List<Integer> list = new ArrayList<>();
        dfs(root, list, k);

        return list.size() >= k ? list.get(k-1) : -1;
    }

    private void dfs(TreeNode root, List<Integer> list, int k) {
        // 判空和提前返回
        if (root == null || k == list.size()) {
            return;
        }

        dfs(root.right, list, k);
        list.add(root.val);
        dfs(root.left, list, k);
    }

76、接雨水

/**
     * 题目:接雨水
     * 输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
     * 输出:6
     * 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
     * 
     * 解法:动态规划、单调栈、双指针,后两种算法待补充
     */

    /**
     * 动态规划
     * nums[i]能接到的水 = min(左边最高列,右边最高的列)-nums[i]的列高
     */
    public static int trap1(int[] height) {
        if (height == null || height.length == 0) {
            return 0;
        }

        // 动态规划一: 时间复杂度O(n^2) 空间复杂度O(1)
//        int count = 0;
//        for (int i = 1; i < height.length-1; i++) {
//
//            int left = 0;
//            for (int p=0; p < i; p++) {
//                left = Math.max(left, height[p]);
//            }
//            int right= 0;
//            for (int q=height.length-1; q > i; q--) {
//                right = Math.max(right, height[q]);
//            }
//
//            int num = Math.min(right, left)-height[i];
//            count += Math.max(num, 0);
//        }

        // 动态规划一: 时间复杂度O(n) 空间复杂度O(n)
        int[] leftMax = new int[height.length];
        for (int i = 0; i < height.length; i++) {
            if (i == 0) {
                leftMax[i] = height[i];
            } else {
                leftMax[i] = Math.max(height[i], leftMax[i-1]);
            }
        }

        int[] rightMax = new int[height.length];
        for (int i = height.length-1; i >=0; i--) {
            if (i == height.length-1) {
                rightMax[i] = height[i];
            } else {
                rightMax[i] = Math.max(height[i], rightMax[i+1]);
            }
        }

        int count=0;
        for (int i = 1; i < height.length-1; i++) {
            // leftMax,rightMax 取i不用处理sum等于负数情况
            int sum = Math.min(leftMax[i], rightMax[i]) - height[i];
            count += sum;
        }


        return count;
    }

77、三个线程交替打印0-100

    public static void main(String[] args) throws InterruptedException {
        Thread worker1 = new Thread(new Worker(0));
        Thread worker2 = new Thread(new Worker(1));
        Thread worker3 = new Thread(new Worker(2));

        worker1.start();
        worker2.start();
        worker3.start();
    }
    
// 方法一:synchronized+wait/notify
public class Worker implements Runnable{

    // static线程共享
    private static Object lock = new Object();
    private static int count = 0;

    private int no;

    public Worker(int no) {
        this.no = no;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (lock) {
                if (count > 100) {
                    break;
                }
                if (count % 3 == this.no) {
                    System.out.println(this.no + "--->" + count);
                    count++;
                } else {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                lock.notifyAll();
            }
        }
    }
}

// 方法二:重入锁
public class ReentrantLockWorker implements Runnable{

    static ReentrantLock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();
    static int count=0;

    private int no;

    public ReentrantLockWorker(int no) {
        this.no = no;
    }

    @Override
    public void run() {

        lock.lock();
        while (true) {
            if (count > 100) {
                break;
            }

            if (count % 3 == no) {
                System.out.println(this.no + " ---> " + count);
                count ++;
                condition.signalAll();
            } else {
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        lock.unlock();
    }
}

78、重排链表

    /**
     * 给定一个单链表 L 的头节点 head ,单链表 L 表示为:
     * L0 → L1 → … → Ln - 1 → Ln
     * 请将其重新排列后变为:
     * L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
     * 
     * 线性表+双指针,时间复杂度On,空间复杂度On
     */
    public static void reorderList(ListNode head) {
        if (head == null || head.next == null) {
            return;
        }

        List<ListNode> listNodes = new ArrayList<>();
        while (head != null) {
            listNodes.add(head);
            head = head.next;
        }

        int left = 0;
        int right = listNodes.size()-1;
        while (left < right) {
            listNodes.get(left).next = listNodes.get(right);
            listNodes.get(right).next = listNodes.get(left+1);

            left++;
            right--;
        }
        listNodes.get(left).next = null;
    }

未完待续。。。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值