LeetCode整理----合集一

一:LeetCode[2]  两个数相加

          1:需要考虑进位的问题,需要使用一个变量存储进位标识,每一次都去判断,而且在一个链表判断完成之后,另外一个链表,也是需要单独考虑进位问题的

          2:在计算结束后,需要再次判断进位问题,如果有进位,则需要进行处理。

          3:需要维护返回的指针和移动的当前指针,返回的指针确定之后就不再变化,而移动的当前指针会随着操作而变动,最好每次判断返回指针是否为null,这样防止空指针异常。

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        //进位标识
        int flag = 0;
        ListNode res = null;
        ListNode next = null;

        int val1 = 0;
        int val2 = 0;
        while(l1 != null || l2 != null){
            val1 = l1 == null ? 0 : l1.val;
            val2 = l2 == null ? 0 : l2.val;

            int i = val1 + val2 + flag;

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

            if(i > 9){
                flag = i/10;
                i = i%10;
            }else {
                flag = 0;
            }

            if(res == null){
                res = new ListNode(i);
                next = res;
            }else {
                next.next = new ListNode(i);
                next = next.next;
            }
        }

        if(flag == 1){
            next.next = new ListNode(1);
        }

        return res;
    }
}

二:LeetCode[19]   删除链表的倒数第 n 个节点

        1.使用双指针方进行解决,先将一个指针向前移动n位,此时再另两个指针同时移动,当快的那个指针移动到尾节点,那么慢的指针会移动到倒数第n个节点,此时再进行删除

        2.编程时,需要注意的是,当快指针移动到尾部,慢指针所在的位置,要保证慢指针再倒数第n + 1个,这样才能使用slow.next  =  slow.next.next,这一点要特别注意。

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode fast = head;
        ListNode slow = head;
        for (int i = 0; i < n; i++) {
            fast = fast.next;
        }

        if (fast == null){
            return slow.next;
        }

        while(fast.next != null){
            fast = fast.next;
            slow = slow.next;
        }

        slow.next  =  slow.next.next;
        return head;
    }
}

三:LeetCode[21] 合并两个有序链表

           1.需要维护返回的指针和移动的当前指针,返回的指针确定之后就不再变化,而移动的当前指针会随着操作而变动,最好每次判断返回指针是否为null,这样防止空指针异常。

           2.在新链表的下一个节点确定后,他的下下一个节点要置为null

           2.清楚指针的维护关系

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1 == null) return l2;
        if(l2 == null) return l1;

        ListNode res = null;
        ListNode current = null;

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

        if(l1 != null) current.next = l1;
        if(l2 != null) current.next = l2;

        return res;
    }
}

 

四:LeetCode[206]  反转链表

          1.需要使用3个指针进行维护,因为在反转的过程中,是有两条链的

class Solution {
    public ListNode reverseList(ListNode head) {
        if(head == null || head.next == null) return head;

        ListNode current = head;
        ListNode next = null;
        ListNode before = null;

        while(current != null){
            next = current.next;
            current.next = before;

            before = current;
            current = next;
        }

        return before;
    }
}

 

五:LeetCode[24]  两两交换链表中的节点

               1.需要维护返回的节点,每次判断返回的节点是否为空,为空则进行赋值

               2.两两交换,这样的话,指针每次会移动两位,所以要记录 next 和 third指针

               3.维护一个current节点的前指针,因为在需要在交换之后,将前指针的下一个指向进行修改

class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null) return head;

        ListNode res = null;
        ListNode current = head;
        ListNode before = null;
        ListNode next = null;
        ListNode third = null;
        //c   n   t
        //1 - 2 - 3 - 4 - 5 - 6
        while(current != null && current.next != null){
            next = current.next;

            third = current.next.next;
            current.next = third;
            next.next = current;

            if(res == null){
                res = next;
            }else {
                before.next = next;
            }

            before = current;
            current = third;
        }

        return res;
    }
}

二刷两两交换链表

class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }

        //结果返回的节点
        ListNode res = null;
        //前一个节点
        ListNode per = null;
        //当前节点
        ListNode current = null;
        ListNode next = null;
        ListNode third = null;
        //             n    c    t
        //每次移动两位  2 -> 1    3 -> 4
        while(head != null && head.next != null){
            current = head;
            next = head.next;
            third = head.next.next;

            next.next = current;
            current.next = null;
            //确定返回的最终值
            if(res == null){
                res = next;
            }
            //连接到之前的链表中,并维护per指针
            if(per != null){
                per.next = next;
                per = current;
            }else{
                per = current;
            }

            head = third;
        }

        if(head != null){
            per.next = head;
        }

        return res;
    }
}

 

六:剑指Offer[06]  从尾到头打印链表

       1.使用递归来解决问题,需要记录递归的深度,用于创建数组

       2.递归的结束条件为当前所查节点为null,此时需要创建数组,然后在递归返回时,进行赋值

class Solution {
    public int[] reversePrint(ListNode head) {
        return rec(head, 0);
    }

    //1 -> 3 -> 2
    public int[] rec(ListNode current, int num){
        if(current == null){
            return new int[num];
        }
        int[] ints = rec(current.next, ++num);

        int length = ints.length;
        //num在rec(current.next, ++num)递归时,已经加1了,此时不用再加1了
        ints[length - num] = current.val;

        return ints;
    }
}

七: LeetCode[328]  奇偶链表

           1.使用两个链表来进行保存奇偶链表,每次循环向前移动两位指针指向

           2.需要记录奇偶链表的头结点和当前访问的节点,当前节点用于拼接奇偶链表的下一个节点,会进行移动

           3.在最后,奇链表的尾部链接偶链表的头部即可

class Solution {
    public ListNode oddEvenList(ListNode head) {
        if(head == null || head.next == null) return head;

        ListNode even = null;
        ListNode odd = null;

        ListNode evenHead = null;
        ListNode oddHead = null;

        while (head != null){
            if(odd == null){
                odd = head;
                oddHead = head;
            }else {
                odd.next = head;
                odd = odd.next;

            }
            head = head.next;
            //需要将下一节点置为null,防止最后一个不设置而出现循环的情况,而且
            //需要在head = head.next之后,不然节点置为null时,下个节点找不到
            odd.next = null;
            
            if(head == null){
                break ;
            }

            if(even == null){
                even = head;
                evenHead = head;
            }else {
                even.next = head;
                even = even.next;
            }
            head = head.next;
            //需要将下一节点置为null,防止最后一个不设置而出现循环的情况,而且
            //需要在head = head.next之后,不然节点置为null时,下个节点找不到
            even.next = null;
        }
        odd.next = evenHead;

        return oddHead;
    }
}

八: 剑指Offer[22]  链表中倒数第K个节点

          1.双指针法,先让快的节点移动k位,再让两个指针同时移动,当快的到尾部时,慢的就是倒数第k为

class Solution {
//  1->2->3->4->5, 和 k = 2.
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode slow = head;
        ListNode fast = head;
        for (int i = 0; i < k; i++) {
            fast = fast.next;
        }

        while(fast != null){
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }
}

九:Leetcode[300]  最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。 
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。 

示例 1: 
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

题解: 

使用动态规划进行处理,最长递增子序列的长度是和前面的子集有关系的,
如求第n个数的最长子序列,那么你就需要求出在 0 - n-1 之间的最长子序列,然后再 + 1,即可求出当前第n个数的最长子序列

在求第n个数的最长子序列, 依次遍历0 - n-1 之间的最长子序列,而且需要是数字小于第n位时,才能生效

public int lengthOfLIS(int[] nums) {
        int length = nums.length;

        //新建数组,保存第n位他的最长子序列
        int[] dp = new int[length];

        //存放最终返回的值
        int res = 1;

        for (int i = 0; i < length; i++) {
            if(i == 0){
                dp[i] = 1;
                continue;
            }

            //依次从0 - n-1 找到比n小的数的最大的最长子序列
            int max = 0;
            for(int j = 0; j < i; j++){
                if(nums[j] < nums[i] && dp[j] > max){
                    max = dp[j];
                }
            }
            dp[i] = max + 1;

            //进行返回值的更新
            if(dp[i] > res){
                res = dp[i];
            }
        }
        return res;
    }

十:剑指Offer[38]  字符串的排列

输入一个字符串,打印出该字符串中字符的所有排列。 
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。 

示例: 
输入:s = "abc"
输出:["abc","acb","bac","bca","cab","cba"]

题解:

把字符串分为两部分:一部分是字符串的第一个字符,另一部分是第一个字符以后的所有字符。

第一步是求所有可能出现在第一个位置的字符,即把第一个字符和后面所有字符交换。(for循环、交换操作)
第二步是固定住第一个字符,求后面所有字符的排列。(递归)如何求剩余字符的排列:将剩余字符的第一个字符依次和后面的字符进行交换,
而“求后面所有字符的排列”即可按照上面的思路递归进行。
递归出口:求剩余字符的排列时只剩下一个字符
实现借助一个char[],通过交换元素得到不同的排列,在递归返回时将其装入ArrayList。

如下图所示,有两点需要注意:

在每个分支进行完后,要将交换过的元素复位,从而不会影响其他分支。
因为有“Swap A with A”这样的操作,并且题目指出可能有字符重复,所以分支末尾可能有重复的序列,在加入ArrayList要进行去重判断,不重复再加入。
以 abc为例,图解过程如下:

class Solution {
    public String[] permutation(String s) {
        //1.先转换为数组,然后再使用递归思想,进行求解,每次固定第一个数
        if(s == null || "".equals(s)){
            return new String[]{};
        }
        //使用hashset进行存储,判断是否重复,如使用list,则会超时
        HashSet<String> set = new HashSet<>();

        //当前已遍历的点
        addString(s.toCharArray(), 0, set);

        return set.toArray(new String[]{});
    }

    private void addString(char[] chars, int index, HashSet<String> set) {
        //如判断到最后,则添加并返回
        if(index == chars.length - 1){
            String value = String.valueOf(chars);
            if(! set.contains(value)){
                set.add(value);
            }
            return;
        }

        for (int i = index; i < chars.length; i++) {
            //交换数组第i个字符和第index个字符
            swap(i, index, chars);
            addString(chars, index + 1, set);
            //再次交换数组第i个字符和第index个字符,保证回到此次for循环前字符数组的状态,不影响字符数组进行下一次for循环
            swap(index, i, chars);
        }
    }

    private void swap(int i, int index, char[] chars) {
        char temp = chars[i];
        chars[i] = chars[index];
        chars[index] = temp;
    }
}

十一:[LeetCode] 165. 比较版本号

题目描述:

比较两个版本号 version1 和 version2。 如果 version1 > version2 返回 1,如果 version1 < version2 返回 -1, 除此之外返回 0。

你可以假设版本字符串非空,并且只包含数字和 . 字符。

. 字符不代表小数点,而是用于分隔数字序列。

例如,2.5 不是“两个半”,也不是“差一半到三”,而是第二版中的第五个小版本。

你可以假设版本号的每一级的默认修订版号为 0。例如,版本号 3.4 的第一级(大版本)和第二级(小版本)修订号分别为 3 和 4。其第三级和第四级修订号均为 0。

示例:

示例 1:

输入: version1 = "0.1", version2 = "1.1"
输出: -1

示例 2:

输入: version1 = "1.0.1", version2 = "1"
输出: 1

题解:

       先使用逗号进行分割,然后将字符串转为int型进行比较,需要注意一点的是,两个字符串使用逗号分割之后的长度可能不同,这个需要进行一下特殊处理。

class Solution {
    public int compareVersion(String version1, String version2) {
        String[] split1 = version1.split("\\.");
        String[] split2 = version2.split("\\.");

        int length1 = split1.length;
        int length2 = split2.length;

        //找到最大的长度
        int maxLength = length1 > length2 ? length1 : length2;

        for (int i = 0; i < maxLength; i++) {
            String splitSub1 = null;
            String splitSub2 = null;

            //判断是否达到最大,防止数组越界
            if(i < length1){
                splitSub1 = split1[i];
            }

            if(i < length2){
                splitSub2 = split2[i];
            }

            //都在的情况
            if(splitSub1 != null && splitSub2 != null){
                //都转为int然后再进行比较
                Integer integer1 = Integer.valueOf(splitSub1);
                Integer integer2 = Integer.valueOf(splitSub2);

                if(integer1 > integer2){
                    return 1;
                }else if(integer1 < integer2){
                    return -1;
                }
            }else if(splitSub1 == null){
                Integer integer2 = Integer.valueOf(splitSub2);
                if(integer2 != 0){
                    return -1;
                }
            }else {
                Integer integer1 = Integer.valueOf(splitSub1);
                if(integer1 != 0){
                    return 1;
                }
            }
        }
        return 0;
    }
}

十二:[LeetCode] 11. 盛最多水的容器

题目描述:

给定 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (iai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (iai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器,且 n 的值至少为 2。

图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

示例:

输入: [1,8,6,2,5,4,8,3,7]
输出: 49

题解:

   设立两个指针,一个从头一个从尾,相向而行遍历数组,每次舍弃较短边

(1)计算面积最大值的初值,该初值以数组中的第一个元素和最后一个元素构成两边。

(2)设置首尾两个指针,首指针i指向数组中的第一个元素,尾指针j指向数组中的最后一个元素。

(3)当首指针i小于尾指针j时,一直循环计算其面积。若计算所得的当前面积大于(1)步骤中所计算得的面积最大值,则更新最大值。每一次循环都舍弃索引i和索引j中较短的那一条边。

为什么每一次循环舍弃索引i和索引j中较短的那一条边,我们最终得到的结果就会是最大的面积值呢?

反证法:

假设我们现在遍历到了height数组中第i和第j个元素,且height[i] < height[j],如果我们的面积最大值中取了第i个元素,那么构成我们的面积最大值的两个元素一定是i和j,因为j继续减小的话长方形的宽肯定一直在减小,而其高最多只能是height[i],不可能比height[i]更大,因此我们在继续遍历的过程中,继续保持i不变而减小j是没有意义的。我们可以直接舍弃i,从i + 1开始去继续遍历。

由于整个过程只遍历了一次数组,因此时间复杂度为O(n),其中n为数组height的长度。而使用空间就是几个变量,故空间复杂度是O(1)。

class Solution {
    public int maxArea(int[] height) {
        if(height == null || height.length == 1){
            return 0;
        }

        int length = height.length;

        int right = length-1;
        int left = 0;

        int with = length-1;

        int res = 0;
        while (left < right){
            int minHeight = height[left] > height[right] ? height[right] : height[left];

            int temp = minHeight * with;
            if(temp > res){
                res = temp;
            }

            if(height[left] > height[right]){
                right--;
            }else {
                left++;
            }
            with--;
        }
        return res;
    }
}

十三:[LeetCode] 26. 删除排序数组中重复项

给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。

示例 1:

给定数组 nums = [1,1,2],

函数应该返回新的长度 2, 并且原数组 nums 的前两个元素被修改为 1, 2。

你不需要考虑数组中超出新长度后面的元素。
示例 2:

给定 nums = [0,0,1,1,1,2,2,3,3,4],

函数应该返回新的长度 5, 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4。

你不需要考虑数组中超出新长度后面的元素。
说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}

题解:

官方题解:双指针法

数组完成排序后,我们可以放置两个指针i 和j,其中i 是慢指针,而j 是快指针。只要 nums[i] = nums[j],我们就增加 j 以跳过重复项。
当我们遇到 nums[j] != nums[i],跳过重复项的运行已经结束,因此我们必须把它(nums[j])的值复制到 nums[i + 1]。然后递增 i,接着我们将再次重复相同的过程,直到 j 到达数组的末尾为止。

复杂度分析
时间复杂度:O(n),假设数组的长度是 n,那么 i 和 j 分别最多遍历 n 步。
空间复杂度:O(1)

class Solution {
    public int removeDuplicates(int[] nums) {
        if(nums == null) return 0;
        if(nums.length == 1) return 1;

        int res = 0;
        for(int i = 1; i<nums.length; i++){
            //不想等时进行处理,先使res + 1,然后再进行数据的替换,不需要考虑数组中超出新长度后面的元素
            if(nums[res] != nums[i]){
                res++;
                nums[res] = nums[i];
            }
        }
        return res + 1;
    }
}

十四:[LeetCode] 104. 二叉树最大深度

给定一个二叉树,找出其最大深度。 

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 

说明: 叶子节点是指没有子节点的节点。 

示例: 
给定二叉树 [3,9,20,null,null,15,7], 
    3
  / \
 9  20
   /  \
  15   7 
返回它的最大深度 3 。 
Related Topics 树 深度优先搜索 递归

题解:

       直接使用递归判断就行,每次判断一个子树的左节点和右节点哪个深度最大,并再次基础上加1即可。

class Solution {
    public int maxDepth(TreeNode root) {
        if(root == null) return 0;

        //进行左右节点的递归工作
        int left = maxDepth(root.left, 1);
        int right = maxDepth(root.right, 1);

        return Math.max(left, right);
    }

    private int maxDepth(TreeNode root, int depth){
        if(root == null){
            return depth;
        }

        return Math.max( maxDepth(root.left, depth + 1),  maxDepth(root.right, depth + 1));
    }
}

十五:[LeetCode] 104. 重建二叉树

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 

例如,给出 

前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7] 

 返回如下的二叉树: 
     3
   / \
  9  20
    /  \
   15   7 

 限制: 
 0 <= 节点个数 <= 5000 

题解:

递归思想!
     使用前序和中序遍历可以确认唯一的二叉树,通过前序可以得知根节点为1,然后通过中序可以得知左子树为{4, 7, 2},右子树为{5, 3, 8, 6}。接下来通过递归思想,{4, 7, 2}中4为根节点,再继续分左右子树,对{5, 3, 8, 6}也做同样操作,直至遍历结束。

在二叉树的前序遍历序列中,第一个数字总是树的根结点的值。但在中序遍历序列中,根结点的值在序列的中间,左子树的结点的值位于根结点的值的左边,而右子树的结点的值位于根结点的值的右边。因此我们需要扫描中序遍历序列,才能找到根结点的值。

前序遍历序列的第一个数字1就是根结点的值。扫描中序遍历序列,就能确定根结点的值的位置。根据中序遍历特点,在根结点的值1前面的3个数字都是左子树结点的值,位于1后面的数字都是右子树结点的值。

在二叉树的前序遍历和中序遍历的序列中确定根结点的值、左子树结点的值和右子树结点的值的步骤如下图所示:

自己的理解:

         1.递归时,要注意,是先构建好子树,然后再构建好外层的树,我开始就是从外部开始构建的,也就是说是先构建好根节点,然后将跟根节点传递给子节点,并且判断当前是左字树,还是右子树,然后再进行其他操作,这样的话,违背了规则的原则,也是一个错误的思想,子树的构建,不应该牵连到父类是什么样子的。

        2.在子树递归时,需要时刻注意前根遍历的开始下标和结束下标,这个一定要判断好,而对于中根遍历的的话,因为根在中间,正好可以平均分配,而前根就不一样了,这是能做出提的关键。

class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if(preorder == null || inorder == null){
            return null;
        }

        return recurseBuildTree(preorder, 0, preorder.length-1, inorder, 0, inorder.length-1);
    }

    public TreeNode recurseBuildTree(int[] preorder, int perLeft, int perRight, int[] inorder, int inLeft, int inRight){
        if(perLeft > perRight || inLeft > inRight){
            return null;
        }

        TreeNode root = new TreeNode(preorder[perLeft]);

        for(int i=inLeft; i<=inRight; i++){
            if(inorder[i] == root.val){
                //(i-inLeft)为左子树的个数,在加上perLeft,那么就是左子树的结束
                root.left = recurseBuildTree(preorder, perLeft+1, i-inLeft+perLeft, inorder, inLeft, i-1);

                //(i-inLeft)为左子树的个数,在加上perLeft,那么就是左子树的结束,再加1,那么就是右子树的开始
                root.right = recurseBuildTree(preorder, i-inLeft+perLeft+1, perRight, inorder, i+1, inRight);
            }
        }
        return root;
    }
}

十六:[LeetCode] 70. 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 
 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 

 注意:给定 n 是一个正整数。 
 
示例 1: 
输入: 2
输出: 2

解释: 有两种方法可以爬到楼顶。
1.  1 阶 + 1 阶
2.  2 阶 

示例 2: 
输入: 3
输出: 3
 
Related Topics 动态规划

题解:

使用动态规划,你爬第n阶楼梯时,可以从n-1上来,也可以从n-2阶楼梯上来,所以你上楼梯的方法是在n-1和n-2的总和

class Solution {
    public int climbStairs(int n) {
        if(n==1) return 1;
        if(n==2) return 2;

        int[] dp = new int[n];
        dp[0] = 1;
        dp[1] = 2;

        for(int i=2; i<n;i++){
            dp[i] = dp[i-1] + dp[i-2];
        }
        return dp[n-1];
    }
}

十七:[LeetCode] 189. 旋转数组

给定一个数组,将数组中的元素向右移动 k 个位置,其中 k 是非负数。 

 进阶: 
 尽可能想出更多的解决方案,至少有三种不同的方法可以解决这个问题。 
 你可以使用空间复杂度为 O(1) 的 原地 算法解决这个问题吗? 

示例 1:
输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]

解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]
 

 示例 2: 
 
输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]

解释: 
向右旋转 1 步: [99,-1,-100,3]
向右旋转 2 步: [3,99,-1,-100] 

题解:

      解法一:可以增加一个数组保存新移动的数据,这个简单

      解法二:先进行整体的反转,然后根据右移数,进行两次局部的反转

 

class Solution {
    public void rotate(int[] nums, int k) {
        //先进行整体的反转,再进行局部的反转,
        int length = nums.length;

        k %= length;

        //整体反转 如nums = [1,2,3,4,5,6,7], k = 3
        recurseArray(nums, 0, length-1); //[7, 6, 5, 4, 3, 2, 1]
        //局部的反转
        recurseArray(nums, 0, k-1);     //[5, 6, 7, 4, 3, 2, 1]
        recurseArray(nums, k, length-1);  //[5, 6, 7, 1, 2, 3, 4]
    }

    private void recurseArray(int[] nums, int i, int j){
        //两两比较
        if(i>j){
            return;
        }

        while(i<j){
            swap(nums, i, j);
            i++;
            j--;
        }
    }

    private void swap(int[] nums, int i, int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

十八:[LeetCode] 15. 三数之和

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
示例 2:

输入:nums = []
输出:[]
示例 3:

输入:nums = [0]
输出:[]
 
提示:

0 <= nums.length <= 3000
-105 <= nums[i] <= 105

题解:

      可以先对数组进行排序,使用Arrays.sort进行排序,然后数组是从到大进行排序的了,然后使用 a+b = -c,先固定一个数,然后使用双指针法进行数组的查找,写代码时,要注意不能出现重复的数。

需要注意的点:

      1.每次都需要进行判断,当前下标值和上一个下标值是否一样,一样则跳过

      2.数组只需要遍历到当前下标值>=0就行,如果都大于1,显然不满足条件

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        if(nums == null){
            return null;
        }
        Arrays.sort(nums);

        List<List<Integer>> list = new ArrayList<>();

        for(int i=0; i<nums.length-2 && nums[i]<=0; i++){
            //去除重复的元素
            if(i!=0 && nums[i]==nums[i-1]){
                continue;
            }

            int left = i+1;
            int right = nums.length-1;
            twoSum(list, nums[i], left, right, nums);
        }
        return list;
    }
    private void twoSum(List<List<Integer>> list, int target, int left, int right, int[] nums){
        int pre = nums[left] - 1; //保证第一次pre不能和nums[left]相同,之后会用这个判断是否发生了重复
        while(left < right){
            if(pre == nums[left]){
                left++;
                continue;
            }

            int temp = 0 - (nums[left] + nums[right]);
            if(target == temp){
                pre = nums[left]; //这个要相同之后,再进行一个判断,防止重复

                List<Integer> subList = new ArrayList<>();
                subList.add(nums[left]);
                subList.add(nums[right]);
                subList.add(target);
                list.add(subList);

                left++;right--;  //这步不能忘
            }else if(target > temp){
                right--;
            }else{
                left++;
            }
        }
    }
}

十九:[LeetCode] 3. 无重复字符的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:
输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

示例 4:
输入: s = ""
输出: 0

提示:
0 <= s.length <= 5 * 104
s 由英文字母、数字、符号和空格组成

题解:

      使用滑动窗口进行求解,使用map进行存储已经存在的元素,字符 ->  数组下标 的形势进行保存,循环时,每次都判断,当前字符是否出现过,如果出现过,那么就要更新滑动窗口的开始位置,开始位置的判断是本题的解题关键

解题注意点:

       1.需要到了HashMap的containsKey方法,这个要会写

       2.在map.get(c)时,可能字符串在map中,但是出现的位置在滑动窗口的左边,此时这个值是不能算是在滑动窗口出现过的,这个需要特别注意,比如: kababkc,当判断数组下标为5, 字符'k'时,此时的滑动窗口的开始下标为3, 此时k已经在map中,下标为1 ,所以对于下标开始的判断很关键

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        Map<Character, Integer> map = new HashMap<>(); 

        int res = 0;
        int start = 0;
        for (int j = 0; j < n; j++) {
            //取出该元素值
            char c = s.charAt(j);
            if(map.containsKey(c)){
                //得到字符串的下一个位置,这个位置可能会成功滑动窗口的开始位置
                int start2 = map.get(c)+1;
                //取出一个最大的当做开始,为什么需要这么做,因为这个map是没有删除的,也就是说map中存放的元素,有可能在
                //滑动窗口的左边,这时取得值都已经作废了
                //比如: kababkc,当判断数组下标为5, 字符'k'时,此时的滑动窗口的开始下标为3, 此时k已经在map中,下标为1
                //所以对于下标开始的判断很关键
                start = Math.max(start, start2);
            }
            map.put(c, j);

            //进行值的更新
            if(res < (j-start+1)){
                res = j-start+1;
            }
        }
        return res;
    }
}

二十:[LeetCode] 128. 最长连续序列

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

进阶:你可以设计并实现时间复杂度为 O(n) 的解决方案吗?

示例 1:

输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。

示例 2:

输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9

提示:

  • 0 <= nums.length <= 104
  • -109 <= nums[i] <= 109

题解:

       使用哈希表进行解决,依次遍历整个数组进行查找,而且在查找时,我们只从开始位置进行查询,也就是说,当我们从 2查找时,1不能存在,1存在的话,就会出现重复,导致我们查找的出现重复。

注意点:

       1.使用HashSet将整个数组进行保存,去重

        2.依次遍历数组的每个元素,如果该元素减1,不存放于数组中,那么它就是一个开始位置,我们从这个依次+1,去HashSet中进行查找。

class Solution {
    public int longestConsecutive(int[] nums) {
        //先将数都放到HashSet中
        HashSet<Integer> set = new HashSet<>();
        for(int i=0;i<nums.length;i++){
            set.add(nums[i]);
        }

        int res = 0;
        for(int i=0; i<nums.length; i++){
            int num = nums[i];

            //set中不包含它的一个数的前驱,说明它就是开始,那么以它为开始节点,进行搜索
            if(!set.contains(num-1)){

                //以当前节点为开始,进行一个搜索,如果set中也存放num++,那么长度+1
                int current = 0;
                while(set.contains(num+1)){
                    num++;
                    current++;
                }

                res = Math.max(res, current+1);
            }
        }
        
        return res;
    }
}

二十一:[LeetCode] 17. 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2:

输入:digits = ""
输出:[]
示例 3:

输入:digits = "2"
输出:["a","b","c"]
 

提示:

0 <= digits.length <= 4
digits[i] 是范围 ['2', '9'] 的一个数字。

题解:

      全排列的一般都是使用递归,此时需要注意的是怎么递归,结束条件是什么

注意:

       1.对于每个数字对应的字符进行映射,使用Map<Character, String>进行存储

       2.获取到数组对应的String数组,因为每个数字对应一个数组,所以是String,这里依次从String的第一个开始,然后进行递归

       3.使用StringBuilder进行保存,可以进行恢复

class Solution {
    public List<String> letterCombinations(String digits) {
        if(digits == null || digits.length() == 0){
            return new ArrayList<>();
        }
        Map<Character, String> map = new HashMap<>();
        map.put('2',"abc");
        map.put('3',"def");
        map.put('4',"ghi");
        map.put('5',"jkl");
        map.put('6',"mno");
        map.put('7',"pqrs");
        map.put('8',"tuv");
        map.put('9',"wxyz");
        
        String[] strArr = new String[digits.length()];
        for(int i=0; i<digits.length(); i++){
            char c = digits.charAt(i);
            strArr[i] = map.get(c);
        }

        List<String> list =  new ArrayList<>();
        //从第0个开始,每次递归调用
        recurseCombinations(strArr, 0, list, new StringBuilder());

        return list;
    }

    private void recurseCombinations(String[] strArr, int index, List<String> list, StringBuilder sb){
        //当遍历完成后,转为String,并放到集合中
        if(strArr.length == index){
            list.add(sb.toString());
            return;
        }

        char[] chars = strArr[index].toCharArray();
        //依次遍历当前的每一个字符
        for(int i=0; i<chars.length; i++){
            sb.append(chars[i]);
            recurseCombinations(strArr, index+1, list, sb);
            sb.deleteCharAt(index); //进行元素的复原操作
        }
    }
}

二十二:[LeetCode] 22. 括号生成

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例 1:

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
示例 2:

输入:n = 1
输出:["()"]

提示:

1 <= n <= 8

题解:

       使用递归解决,并且关于字符串使用StringBuilder, 使用两个变量都为n, 一个代表左括号的剩余数量left,一个代表右括号的剩余数量right,  初始都为n, 当左括号、右括号剩余数量都为0

时,进行生成字符串。

      递归的返回条件为left<0  ||  right< 0 || left > right,其中对于left > right表示的是左括号剩余的大于右括号,如())这样的就不满足条件,直接返回。

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> list = new ArrayList<>();
        StringBuilder sb = new StringBuilder();
        recurseGenerate(n, n, sb, list);

        return list;
    }

    private void recurseGenerate(int left, int right, StringBuilder sb, List<String> list){
        if(left > right || left < 0 || right < 0){ //left > right,表示如果左括号剩余的多,说明本次是违规的操作,比如))()((,这样的输出不合法
            return;
        }
        if(left == 0 && right == 0){
            list.add(sb.toString());
        }

        recurseGenerate(left-1, right, sb.append("("), list);
        sb.deleteCharAt(sb.length()-1);

        recurseGenerate(left, right-1, sb.append(")"), list);
        sb.deleteCharAt(sb.length()-1);
    }
}

二十三:[LeetCode] 200. 岛屿数量

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

示例 1:

输入:grid = [
  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
输出:1
示例 2:

输入:grid = [
  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]
输出:3
 

提示:

m == grid.length
n == grid[i].length
1 <= m, n <= 300
grid[i][j] 的值为 '0' 或 '1'

题解:

       使用DFS进行求解,如果当前遍历的坐标符合要求,则将坐标位置置为0

注意点:

       1.递归的结束条件

class Solution {
    public int numIslands(char[][] grid) {
        int n = grid.length;
        int m = grid[0].length;

        int res = 0;
        for(int i=0; i<n; i++){
            for(int j=0; j<m; j++){
                if(grid[i][j] == '1'){
                    res++;
                    recurseNumIslands(grid, i, j);
                }
            }
        }
        return res;
    }

    private void recurseNumIslands(char[][]grid, int i, int j){
        //递归结束条件要清楚,特别是grid[i][j] == '0',不然就会出错
        if(i<0 || i>= grid.length || j<0 || j>=grid[0].length || grid[i][j] == '0'){
            return;
        }
        //设置为0
        grid[i][j] = '0';
        //进行递归
        recurseNumIslands(grid, i+1 ,j);
        recurseNumIslands(grid, i-1 , j);
        recurseNumIslands(grid, i , j+1);
        recurseNumIslands(grid, i , j-1);
    }
}

二十四:[LeetCode] 543. 二叉树的直径

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。 

示例 : 
给定二叉树 
           1
         / \
        2   3
       / \     
      4   5    
 返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。 

 注意:两结点之间的路径长度是以它们之间边的数目表示。 
 Related Topics 树

题解:

使用递归 + 动态规划解决问题

 递归调用每个节点,而且每个节点保存两个值,一个是当前节点的最大深度, 一个是以当前节点为一个子树的内部的最大深度,
 使用数组进行保存,int[2]{maxDiameterWithCurrentRootNode, maxDeep }

 当前节点的使用current[],左节点使用leftTemps[],右节点使用RightTemps[],求当前节点时,左右节点的值均已求出

 current[0] = max(leftTemps[0],  RightTemps[0],  leftTemps[1] + RightTemps[1] + 1), 其中leftTemps[1] + RightTemps[1] + 1为已当前节点为根的最大直径
 current[1] = max(leftTemps[1] ,  RightTemps[1]) + 1

class Solution {
    public int diameterOfBinaryTree(TreeNode root) {
        //使用递归解决问题,每次保存一个数组,第一个下标为以当前节点为根的最大直径,
        //第二个就是以当前节点的的最大深度
        //使用动态规划 + 递归运算

        if(root == null){
            return 0;
        }

        int[] temps = recurseDiameter(root);

        return Math.max(temps[0], temps[1]) - 1;
    }


    private int[] recurseDiameter(TreeNode root){
        if(root == null){
            return new int[]{0,0};
        }
        //leftTemps[0] 代表以root.left为根的最大直径, leftTemps[1]代表以root.left中的最大深度
        int[] leftTemps = recurseDiameter(root.left);
        int[] rightTemps = recurseDiameter(root.right);

        //当前节点的最大深度
        int maxdeep = Math.max(leftTemps[1], rightTemps[1]) + 1;

        //以当前节点为根的最大直径, 它的取值为Math(左节点为根的最大直径, 有节点为根的最大直径, 当前节点为根的最大直径),取最大的一个
        int max = Math.max(leftTemps[0], rightTemps[0]);
        max = Math.max(max, leftTemps[1] + rightTemps[1] + 1);

        return new int[]{max, maxdeep};
    }
}

二十五:[LeetCode] 146. LRU缓存机制

运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。 
 
实现 LRUCache 类: 
 
LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存 
int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。 
void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上
限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。 

进阶:你是否可以在 O(1) 时间复杂度内完成这两种操作? 

示例: 
输入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
输出
[null, null, null, 1, null, -1, null, -1, 3, 4]

解释
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1);  缓存是 {1=1}
lRUCache.put(2, 2);  缓存是 {1=1, 2=2}
lRUCache.get(1);     返回 1
lRUCache.put(3, 3);  该操作会使得关键字 2 作废,缓存是 {1=1, 3=3}
lRUCache.get(2);     返回 -1 (未找到)
lRUCache.put(4, 4);  该操作会使得关键字 1 作废,缓存是 {4=4, 3=3}
lRUCache.get(1);     返回 -1 (未找到)
lRUCache.get(3);     返回 3
lRUCache.get(4);     返回 4

 提示: 
 1 <= capacity <= 3000 
 0 <= key <= 3000 
 0 <= value <= 104 
 最多调用 3 * 104 次 get 和 put 

题解:

      使用LinkedHashMap可以很轻松的实现,但是你这样的话,没有起到自己设计的目的,只是自己调用了API,所以,这里使用HashMap进行设计。

注意点:

      1.LRU需要使用链表结构进行保存,就像LinkedHashMap一样,构造虚拟节点内部类,保存到HashMap的value中

      2.HashMap中value保存的是虚拟的链表节点,如果value直接存放值的话,工作量太大

      3.构建出虚拟的头结点和尾节点,只是用来指明头尾的位置,这样的不用考虑空指针异常的问题,这个能想到的话,做题很快的

      4.明白LUR的含义,在get时,将访问的当前节点移动到头部去,当put时,先查看是否存在,如不存在,则创建虚拟节点进行包装后,放入,再判断是否超过最大容量,超过时,则需要删除。

class LRUCache {
    class CacheLinked{
        int key;
        int val;
        CacheLinked next;
        CacheLinked pre;
        public CacheLinked(){}
        public CacheLinked(int key, int val){
            this.key = key;
            this.val = val;
        }
    }
    //size存放当前元素数, capacity存放最大元素数
    private int size;
    private int capacity;

    private CacheLinked head;

    private CacheLinked tail;

    Map<Integer, CacheLinked> map = new HashMap<>();

    public LRUCache(int capacity) {
        this.capacity = capacity;

        //构建头尾的虚拟节点,不存放任何元素,只是为了操作简单,不用进行空指针的判断,如果不构建
        //虚拟节点有你受的了,光空指针的判断就很麻烦
        head = new CacheLinked();
        tail = new CacheLinked();

        head.next = tail;
        tail.pre = head;
    }
    
    public int get(int key) {
        CacheLinked cache = map.get(key);
        if(cache == null){
            return -1;
        }
        //更新lru,移除元素,并添加到首部
        moveCache(cache);
        addHead(cache);

        return cache.val;
    }
    
    public void put(int key, int value) {
        CacheLinked cache = map.get(key);
        //分两种情况,缓存已存在,和缓存不存在,
        if(cache == null){
            CacheLinked newCache = new CacheLinked(key, value);
            addHead(newCache);
            //判断容量大小,如果超过了,那么就将最就的那个从hashmap中删除
            size++;
            if(size > capacity){
                CacheLinked oldTail = moveTail();
                //移除元素
                map.remove(oldTail.key);
            }
            map.put(key, newCache);
        }else{
            //更新值
            cache.val = value;
            //更新lru,移除元素,并添加到首部
            moveCache(cache);
            addHead(cache);
        }
    }

    private void addHead(CacheLinked cache){
        CacheLinked oldHead = head.next;
        head.next = cache;
        cache.next = oldHead;

        oldHead.pre = cache;
        cache.pre = head;
    }

    private void moveCache(CacheLinked cache){
        //因为有虚拟节点的存在,根本不用判断空指针的问题
        cache.pre.next = cache.next;
        cache.next.pre = cache.pre;
        //释放元素,防止内存遗漏
        cache.next = null;
        cache.pre = null;
    }

    //移除尾部元素,并将尾部元素进行返回
    private CacheLinked moveTail(){
        CacheLinked oldTail = tail.pre;
        CacheLinked newTail = oldTail.pre;

        newTail.next = tail;
        tail.pre = newTail;

        oldTail.next = null;
        oldTail.pre = null;

        return oldTail;
    }
}

二十六:[LeetCode] 337. 打家劫舍

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“
房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。 

计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。 

示例 1: 
输入: [3,2,3,null,3,null,1]

     3
    / \
   2   3
    \   \ 
     3   1
输出: 7 
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7. 

示例 2: 
输入: [3,4,5,1,3,null,1]

     3
    / \
   4   5
  / \   \ 
 1   3   1
输出: 9
解释: 小偷一晚能够盗取的最高金额 = 4 + 5 = 9.
 
Related Topics 树 深度优先搜索

题解:

      使用递归 + 动态规划进行求解,因为每个节点都可以选也可以不选,如果当前节点选择的话,他的两个子节点都不能选择了,所以,我们可以使用数组保存当前节点的两个状态,即[0]不选当前节点的最大值, [1]选择了当前节点的最大值

注意:
      1.对于选择当前节点还好,直接左右节点不选 + 当前节点的value
       而对于不选当前节点,那么左节点和右节点都可以选或者是不选,所以,我们要查出最大的左节点和最大的右节点,如下面这样情况

         3
      /     \
     4       5
   /   \        \
1  10000    1
   当遍历到根节点3时,我们看根据是不选4这个节点,他的整体会更大一点

class Solution {
    public int rob(TreeNode root) {
        if(root == null){
            return 0;
        }

        int[] res = recurseReachMaxRob(root);

        return Math.max(res[0], res[1]);
    }

    public int[] recurseReachMaxRob(TreeNode root) {
        if(root == null){
            return new int[]{0, 0};
        }

        //[0]不选当前节点的最大值, [1]选择了当前节点的最大值
        int[] leftMaxJob = recurseReachMaxRob(root.left);
        int[] rightMaxJob = recurseReachMaxRob(root.right);

        //选择了当前节点,那么它的左右孩子都不能选
        int selectCurrent = leftMaxJob[0] + rightMaxJob[0] + root.val;

        //不选择当前节点的话,那么左右节点都可以选,也都可以不选,这样要看做大值
        //这个是解题的关键,当前节点不选的,他的子节点可以选,也可以不选,这是,就要取最大的一个,
        //        3
        //      /   \
        //     4     5
        //   /   \     \
        //  1  10000    1
        //当遍历到根节点3时,我们看根据是不选4这个节点,他的整体会更大一点
        int noSelectCurrent = Math.max(leftMaxJob[0], leftMaxJob[1]) + Math.max(rightMaxJob[0], rightMaxJob[1]);

        return new int[]{noSelectCurrent, selectCurrent};
    }
}

二十七:[LeetCode] 101. 对称二叉树

给定一个二叉树,检查它是否是镜像对称的。 

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。 
     1
   / \
  2   2
 / \ / \
3  4 4  3

但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的: 
     1
   / \
  2   2
   \   \
   3    3

进阶: 
你可以运用递归和迭代两种方法解决这个问题吗? 
Related Topics 树 深度优先搜索 广度优先搜索

题解:
        首先分析下这个对称二叉树,也就是一个二叉树中间对称。所以我们可以使用递归的思想,首先以根节点以及其左右子树,左子树的左子树和右子树的右子树相同,左子树的右子树和右子树的左子树相同。两个条件都要符合,所以我们第一个传根节点的左子树和右子树,先判断左右子树根结点的比较。然后分辨对左子树的左子树和右子树的右子树。左子树的右子树和右子树的左子树进行判断。只有两个条件都满足则返回的是true,一层一层递归进入,则可以得到结果。
注意:
        1.主要递归进行判断,如果值不相同的话,那么也不是一个镜像二叉树
        2.只要有一个返回为false,那么整个结果就会返回false

class Solution {
    public boolean isSymmetric(TreeNode root) {
        if(root == null){
            return false;
        }

        return recurseJudgeSymmetric(root.left, root.right);
    }

    private boolean recurseJudgeSymmetric(TreeNode left, TreeNode right){
        if(left == null){
            //left为null时,判断right是否为null
            //right == null,成立返回true
            return right == null;
        }

        //此时走到这,说明left不为空了,
        if(right == null){
            return false;
        }

        //值不相等,则也返回false
        if(left.val != right.val){
            return false;
        }

        boolean symmetric1 = recurseJudgeSymmetric(left.left, right.right);
        boolean symmetric2 = recurseJudgeSymmetric(left.right, right.left);

        //返回他们之间的 && 运算,只要有一个为false那么就为false
        return symmetric1 && symmetric2;
    }
}

二十八:[LeetCode] 98. 验证二叉搜索树

给定一个二叉树,判断其是否是一个有效的二叉搜索树。 

假设一个二叉搜索树具有如下特征: 

节点的左子树只包含小于当前节点的数。 
节点的右子树只包含大于当前节点的数。 
所有左子树和右子树自身必须也是二叉搜索树。 
 

示例 1: 
输入:
    2
   / \
  1   3
输出: true
 

示例 2: 
输入:
    5
   / \
  1   4
      / \
    3   6
输出: false
解释: 输入为: [5,1,4,null,null,3,6]。
根节点的值为 5 ,但是其右子节点值为 4 。

题解:
        先使用中序遍历,二叉搜索树的话,对于中序遍历来说,整个数组是有序的,所以可以依次判断数组的后一个是否比前一个数组大,如果小的话,则不满足。
        
 

class Solution {
    public boolean isValidBST(TreeNode root) {
        if(root == null){
            return false;
        }

        //中序遍历,肯定是递增的
        List<Integer> list = new ArrayList<>();

        //先进行中序遍历
        recurseValidBST(root, list);

        //如果是二叉搜索树的话,那么数据是有序的,进行遍历就行
        for(int i=1; i<list.size(); i++){
            if(list.get(i-1) >= list.get(i)){
                return false;
            }
        }
        return true;
    }

    private void recurseValidBST(TreeNode root, List<Integer> list){
        if(root == null){
            return;
        }

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

二十九:[LeetCode] 64. 最小路径和

给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。 

说明:每次只能向下或者向右移动一步。 

示例 1: 
输入:grid = [[1,3,1]
            ,[1,5,1],
             [4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。
 
示例 2: 
输入:grid = [[1,2,3],[4,5,6]]
输出:12

提示: 
递归的话,结束条件时什么
到达右下角,则结束,  结束之后返回值,因为只能向左或者是向右,则进行比较,左右做小值
 m == grid.length 
 n == grid[i].length 
 1 <= m, n <= 200 
 0 <= grid[i][j] <= 100 
 
 Related Topics 数组 动态规划
 
 题解:
两种解法:  
        一种是递归,会超时.....................,每次递归判断向左向右的最小值,并进行求值
        二种是动态规划,第dp[i][j]代表走到i,j所走的最小路径,那么这个最小路径和和dp[i-1][j],dp[i][j-1]有关系的,
             具体的最小值为dp[i][j] = Math.max(dp[i-1][j],  dp[i][j-1]) + grid[i][j];

注意:

        对于临界点的判断,因为i=0的话,你再i-1就会出错了,这个要特别注意

class Solution {
    public int minPathSum(int[][] grid) {
        if(grid == null){
            return 0;
        }
        int n = grid.length;
        int m = grid[0].length;

        int[][] dp = new int[n][m];

        for(int i=0; i<n; i++){
            for(int j=0; j<m; j++){
                if(i == 0 && j == 0){
                    dp[i][j] = grid[i][j];
                }else if(i == 0) {
                    //进入这里,i==0,那么j肯定不为0
                    dp[i][j] = dp[i][j-1]+grid[i][j];
                }else if(j == 0){
                    //进入这里,j==0,那么i肯定不为0
                    dp[i][j] = dp[i-1][j]+grid[i][j];
                }else{
                    dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + grid[i][j];
                }
            }
        }
        return dp[n-1][m-1];
    }


    // return recurseReachMinPathSum(grid, 0, 0);
    //这里解法会超时的,不用看
    public int recurseReachMinPathSum(int[][]grid, int i, int j){
        //结束条件
        if(i==grid.length-1 && j==grid[0].length-1){
            return grid[i][j];
        }

        int down = Integer.MAX_VALUE;
        int right = Integer.MAX_VALUE;

        //条件判断,剪纸
        if(i < grid.length-1){
            down = recurseReachMinPathSum(grid, i+1, j);
        }

        if(j < grid[0].length-1){
            right = recurseReachMinPathSum(grid, i, j+1);
        }

        return Math.min(down, right) + grid[i][j];
    }
}

三十:[LeetCode] 115. 最小栈

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。 

push(x) —— 将元素 x 推入栈中。 
pop() —— 删除栈顶的元素。 
top() —— 获取栈顶元素。 
getMin() —— 检索栈中的最小元素。 
 
示例: 

输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],        [-2],   [0],   [-3],    [],    [],   [],     []]

输出:
[null,       null,   null,  null,   -3,    null,  0,     -2]

解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.

提示: 
pop、top 和 getMin 操作总是在 非空栈 上调用。 
Related Topics 栈 设计

题解:

       使用额外的一个栈保存当前最小的数,每当一个元素进栈时,都会在两个栈中各存放一个元素,最小栈中存放的是和之前的进行比较,看哪个小,就将小的放入栈中,具体官方题解有动态图,这个看一下:https://leetcode-cn.com/problems/min-stack/solution/zui-xiao-zhan-by-leetcode-solution/

 

class MinStack {
    private LinkedList<Integer> stack;
    private LinkedList<Integer> minStack;

    /** initialize your data structure here. */
    public MinStack() {
        stack = new LinkedList<>();
        minStack = new LinkedList<>();
    }
    
    public void push(int x) {
        //如果为空,则直接存放进去
        if(stack.size() == 0){
            minStack.addFirst(x);
        }else{
            //不为空,取出最小栈中的头部,并进行判断大小,而且每次都会将比较
            //之后最小的,放入到最小栈中
            int temp = minStack.getFirst();
            if(temp > x){
                minStack.addFirst(x);
            }else{
                minStack.addFirst(temp);
            }
        }
        stack.addFirst(x);
    }
    
    public void pop() {
        stack.removeFirst();
        minStack.removeFirst();

    }
    
    public int top() {
        return stack.getFirst();
    }
    
    public int getMin() {
        return minStack.getFirst();
    }
}

三十一:[LeetCode] 5. 最长回文子串

给你一个字符串 s,找到 s 中最长的回文子串。

 

示例 1:

输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。
示例 2:

输入:s = "cbbd"
输出:"bb"
示例 3:

输入:s = "a"
输出:"a"
示例 4:

输入:s = "ac"
输出:"a"
 

提示:

1 <= s.length <= 1000
s 仅由数字和英文字母(大写和/或小写)组成

题解:
    

先给出官方题解,然后写一下自己的理解,需要注意和深刻理解的点:
        1.求dp[i][j]是否是回文串时,需要dp[i+1][j-1]的状态已知,而对于dp[i+1][j-1]的状态也是需要它上面的状态才行,所以,在进行求值时,我们需要先根据内部的才能求出外部的,在
求解时,需要根据长度进行求取,先求出所有的回文子串长度为1的,然后再求出长度为2的,再求为3的
        2.所有循环时,不是对i和j进行循环,外层对长度进行循环,内层对开始位置i进行循环,根据i和长度来确定j的位置,这样进行求解

class Solution {
    public String longestPalindrome(String s) {
        String res = "";

        int length = s.length();

        boolean[][] dp = new boolean[length][length];

        //len代表当前所遍历的长度,因为只有dp[i][j]判断的条件是,dp[i+1][j-1]的状态已知,而对于dp[i+1][j-1]的
        //状态怎么得知的,所以,在进行求值时,是根据长度进行的,先求出长度为3的,上面的那个循环已经求得了长度为1和2是不是
        //回文串了,那就此时就从3开始判断了
        for(int len=1; len<=length; len++){
            //我们判断时,是从i开始的,如果i+len如果大于length,则说明本次已不满足条件了,因为,我们遍历时,是先根据长度进行循环的,
            for(int i=0; i+len<=length; i++){
                //i+len-1,表示当前遍历的j在哪个位置,因为,本次判断的长度为len,我们开始位置为i,那么j的位置就是i+len了,因为下标
                //从0开始,则需要-1,就找到了j的位置
                //始终记住dp[i][j],表示的是从第i为到第j为是不是一个回文串,他们之间的间隔为len
                int j = i + len - 1;

                if(len == 1){
                    dp[i][j] = true;
                }else if(len == 2){
                    dp[i][j] = (s.charAt(i) == s.charAt(j));
                }else if(s.charAt(i)==s.charAt(j) && dp[i+1][j-1]){
                    dp[i][j] = true;
                }

                //满足条件,则查看是否需要更新最大值
                if(dp[i][j] && res.length()<len){
                    res = s.substring(i, j+1);
                }
            }
        }

        return res;
    }
}

三十二:[LeetCode] 647. 回文子串

给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。 

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。 

示例 1: 
输入:"abc"
输出:3
解释:三个回文子串: "a", "b", "c"

示例 2: 
输入:"aaa"
输出:6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa" 

提示: 
输入的字符串长度不会超过 1000 。 
Related Topics 字符串 动态规划

题解:
        需要使用动态规划,思路和leetcode第5题思路是一样的,主要的还是对于动态转移方式的判断,和条件

需要注意的点:
        1.dp[i][j]表示从数组下标i到数组下标j是否是一个回文串,它的转移方程为dp[i][j] = (arr[i] == arr[j] && dp[i+1][j-1] == true)
        2.求解时,根据长度进行求解,让回文字串不断增大,先从长度为1 到长度为length进行判断

class Solution {
    public int countSubstrings(String s) {
        if(s == null || s.length() == 0){
            return 0;
        }
        int length = s.length();

        int res = 0;
        boolean[][] dp = new boolean[length][length];

        //len表示当前的长度,先从1开始判断,也就是判断abc中的a是不是回文串
        //等长度为1的判断完成后,判断2的,如ab,bc是不是,再判断长度为3的
        //如abc是不是
        for(int len=1; len<=length; len++){
            for(int i=0; i+len<=length; i++){
                //i+len-1,表示结束位置j
                int j = i + len -1;

                if(len == 1){
                    dp[i][j] = true;
                }else if(len == 2){
                    dp[i][j] = s.charAt(i) == s.charAt(j);
                }else if(s.charAt(i) == s.charAt(j) && dp[i+1][j-1]){
                    dp[i][j] = true;
                }

                if(dp[i][j]){
                    res++;
                }
            }
        }

        return res;
    }
}

三十三:[LeetCode] 33. 搜索旋转排序数组

整数数组 nums 按升序排列,数组中的值 互不相同 。 

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[
k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2
,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。 

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的索引,否则返回 -1 。 

示例 1: 
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4

示例 2: 
输入:nums = [4,5,6,7,0,1,2], target = 3
输出:-1 

示例 3: 
输入:nums = [1], target = 0
输出:-1

提示: 
1 <= nums.length <= 5000 
-10^4 <= nums[i] <= 10^4 
nums 中的每个值都 独一无二 
nums 肯定会在某个点上旋转 
-10^4 <= target <= 10^4 

进阶:你可以设计一个时间复杂度为 O(log n) 的解决方案吗? 
Related Topics 数组 二分查找

题解:

       使用二分进行查询,当将数组一分为二时,肯定有一份是从小到大排序的,这个是可以肯定的,因为此题有个前提-------数组中的值互不相同,所以,我们解题的关键是找到哪一半是升序的,然后依次判断这个target是否在这个升序排列中,如果不存在,那么就在另一份了,所以此题也是二分的思想

class Solution {
    public int search(int[] nums, int target) {
        if(nums == null || nums.length == 0){
            return -1;
        }
        if(nums.length == 1){
            return nums[0] == target ? 0 : -1;
        }

        int left = 0;
        int right = nums.length-1;
        int mid = 0;

        while(left <= right){
            mid = (left + right)/2;
            if(nums[mid] == target){
                return mid;
            }

            //从有序的一方进行判断
            if(nums[left] <= nums[mid] ){
                //左边有序
                if(nums[left] <= target && nums[mid] > target){
                    right = mid - 1;
                }else{
                    left = mid + 1;
                }
            }else{
                if(nums[mid] < target && nums[right] >= target){
                    left = mid + 1;
                }else{
                    right = mid - 1;
                }
            }
        }
        return -1;
    }
}

三十四:[LeetCode] 56. 合并区间

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返
回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。 

示例 1: 
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
 
示例 2: 
输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。 

提示: 
1 <= intervals.length <= 104 
intervals[i].length == 2 
0 <= starti <= endi <= 104 
 
Related Topics 排序 数组

题解:

      进行合并的区域,需要先进行排序,因为,可能给的数组不是从小到达排序的,排序时,按照开始节点进行排序,例如给的用例为[[2,3],[4,5],[6,7],[8,9],[1,10]],此时就需要先进行排序了,再排序之后我们就可以发现规律了,如

[[1,10],[2,3],[4,5],[6,7],[8,9]],此时如果需要进行排序,那么肯定是当前遍历元素的开始比前一个元素的结束要小,此时就要进行合并,而且,再查找一个最大值,作为结束。

class Solution {
    public int[][] merge(int[][] intervals) {
        if(intervals == null || intervals.length == 1){
            return intervals;
        }

        ArrayList<int[]> list = new ArrayList<>();

        //从小到大进行排序,按照起始节点进行排序
        Arrays.sort(intervals, (a,b) -> a[0] - b[0]);

        list.add(intervals[0]);

        for(int i=1; i<intervals.length; i++){
            //取出最后一个放入的元素,进行判断
            int[] temp = list.get(list.size() - 1);

            //如果当前遍历的开始,比列表中最后一个要小,也就是列表中最后一个更大,
            //此时需要加入到其中,并且更新列表中最后一个数的结尾,查看哪个数更大一点
            if(intervals[i][0] <= temp[1]){
                temp[1] = Math.max(intervals[i][1], temp[1]);
            }else{
                list.add(intervals[i]);
            }
        }

        return list.toArray(new int[][]{});
    }
}

三十五:[LeetCode] 160. 相交链表

编写一个程序,找到两个单链表相交的起始节点。

例如,下面的两个链表:

A:          a1 → a2
                   ↘
                     c1 → c2 → c3
                   ↗            
B:     b1 → b2 → b3
在节点 c1 开始相交。

注意:

如果两个链表没有交点,返回 null.
在返回结果后,两个链表仍须保持原有的结构。
可假定整个链表结构中没有循环。
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
思路:如果没有 O(n) 时间复杂度,且仅用 O(1) 内存的条件,这个题很容器写,但是因为对于时间复杂度和空间复杂度有要求,所以,我们应该再做思考,我们知道,如果两个链表相交,从相交点开始,以后的元素都是相同的,我们设A链表中,不重复的为listA,设B链表中,不重复的为listB,相同的那部分为AequalB。

        那么就有listA+AequalB+listB=listB+AequalB+listA,当然这也是显然易见的,所以我们就可以找到交点了,

        例如,对于A链表,我们a1 → a2 → c1→c2 → c3→b1 → b2 → b3→c1

                   对于B链表,我们b1 → b2 → b3→c1 → c2 → c3→a1 → a2→c1

         可以发现,我们从A链表开始,当A链表遍历完后,再从B链表头节点找, 对于B,我们从B链表开始,当B链表遍历完后,再从A链表头节点找,这样会在相交的地方,两值相等。
 

注意:

       分为两种情况,如果A和B链一样长,那么第一次遍历时,就直接a == b == null了,如果A和B链不一样长,那么第一次遍历时,就直接a == null时,b一定不是null,那么就需要进行a = headB; b = headA的判断

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA==null || headB==null){
            return null;
        }
        //分为两种情况: 如果A和B链一样长,那么第一次遍历时,就直接a == b == null了
        //              如果A和B链不一样长,那么第一次遍历时,就直接a == null时,b一定不是null,那么就需要进行a = headB; b = headA的判断
        //a -> b
        //c -> d,  如果A和B链一样长,那么第一次遍历时,就直接a == b == null了,所以直接退出,如果A,B链不想等,那么的话,才需要进行二次遍历

        //如a -> b
        //c -> d -> e, 只有A,B链不同时为null时,才会走扫描第二次,也就是a = headB; b = headA;才有用
        ListNode a = headA;
        ListNode b = headB;

        while(a != b){
            if(a == null){
                a = headB;
            }else{
                a = a.next;
            }

            if(b == null){
                b = headA;
            }else{
                b = b.next;
            }
        }

        return a;
    }
}

三十六:[LeetCode] 31. 下一个排列

实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。 

如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。 

必须 原地 修改,只允许使用额外常数空间。 

示例 1: 
输入:nums = [1,2,3]
输出:[1,3,2]
 
示例 2: 
输入:nums = [3,2,1]
输出:[1,2,3]
 
示例 3:  
输入:nums = [1,1,5]
输出:[1,5,1]
 
示例 4: 
输入:nums = [1]
输出:[1]
 
提示: 
1 <= nums.length <= 100 
0 <= nums[i] <= 100 
Related Topics 数组

题解:

         首先你要知道什么是字典树,其次你要很清楚怎么根据一个字典树,找到他的下一个排列的字典树。这两点清楚的话,这题并不难,主要的话,还是要理解字典树,详细的可以参考这篇:https://blog.csdn.net/qq_33594380/article/details/82377923

1.字典序值

         字典序值就是当前序列在字典序中的排列位置。那给定一个排列,我们应该如何求出它的字典序值呢?

为了求排列对应的序号,只要该序列到第一个序列即1,2,3…n 所需要移动的次数。

移动原则是a[i] 从大到小移动,对于每一个数字a[i],若i前面比a[i] 小的个数正好是a[i] - 1 个,则这个数不需要向后移动以达到目标序列,否则i 后面必然有a[i] - t - 1 (此处t 的值为a[i] 之前比a[i] 小的数字的个数)个比a[i] 小的数,只要把a[i] 移动到比自己小的数后面才能使得移动中的序列正向目标前进。

因此只要求出每个数的移动次数,然后相加就是该序列的位置。即每个数到正确位置需要移动的次数为

—— 这一段引自 https://blog.csdn.net/hello_tomorrow_111/article/details/78696294 并略作优化

啥意思,有点模糊,举个栗子来解释一下。我们就以第一点中提到的[1, 2, 3] 的字典序为例,我现在想知道321 在序列中的位置应该怎么计算呢?

(1)找到该序列的第一个序列,即123

(2)从序列左边开始,查找每个值应该移动的位数。

首先来看3,与第一个序列相比,3 之前本来应该有两个元素12,但是现在它前面没有比它小的元素,所以它要移动3 - 1 - 0 (a[i] - t - 1),即两位到达正序位置。
接着来看2,2 之前应该是有一个元素1,但是它前面也没有比它小的元素,所以它要向后移动2 - 1 - 0,即一位来达到正序。
对于1 来说,它是排列中最小的元素,它之前不应该有元素,而事实也是如此,所以它不需要移动。
将3 和2 移动的次数相加就是321 在字典序中的字典序值。那么3 和2 分别需要移动多少次才能移动两位或者一位呢,以3 为例来看一下:

321 中的3 往后移动两位需要经历以下流程(回退):231、213、132、123。

2. 下一个序列
字典序值说完了,说说下一个序列,下一个序列求解的思想非常简单:

(1)从右向左找到第一个左临小于右临的位置,左临的位置标记为i

(2)从右向左找到第一个大于a[i] 的数值,位置为j

(3)交换a[i] 与a[j] 的值

(4)将i 右边的部分排成正序

如果看这个流程看不懂的话,建议去看Leetcode 上的动图,保证看几遍就懂:

https://leetcode-cn.com/problems/next-permutation/solution/

Say sth more (简称SM): 为什么要这么做呢,提供一个栗子结合理解,假设我们要求15499 + 1 的和,这个应该怎么算呢?

解法是:从右向左找到第一个不为9 的数值,将该数值加1,然后该数值以后所有的9 都变成0。此例可以与求解下一个序列的过程结合理解。

class Solution {
    public void nextPermutation(int[] nums) {
        if(nums == null || nums.length == 1){
            return;
        }

        int length = nums.length;

        int ans = -1;

        //1   2   3   8   7   6   5   4
        //   i-1  i

        //1,  2,  3
        //   ans
        //第一步.从后向前找第一个,a[i] > a[i-1]的数
        for(int i=length-1; i>0; i--){
            if(nums[i] > nums[i-1]){
                ans = i-1;
                //break语句不能忘记,不然就错了......
                break;
            }
        }

        if(ans == -1){
           //此时不存在下一个更大的排列,则将数字重新排列成最小的排列
            reverseArray(nums, 0, length-1);
        }else{
            for(int i=length-1; i>=ans; i--){
                if(nums[i] > nums[ans]){
                    //第二步,元素的交换
                    swapNum(nums, i, ans);

                    //第三步,指定的数据反转
                    reverseArray(nums, ans+1, length-1);
                    break;
                }
            }
        }
    }

    //交换元素
    private void swapNum(int[] nums, int i, int j){
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

    //反转指定下标范围内的元素
    private void reverseArray(int[] nums, int l, int r){
        while(l<r){
            swapNum(nums, l, r);
            l++;
            r--;
        }
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值