剑指offer题目

二维数组的查找

在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
思路:从右上角开始找,这样每次能够去掉一行或者一列。

public class Solution {
    public boolean Find(int target, int [][] array) {
        if(array == null) return false;
        int row = array.length;
        if(row == 0) return false;
        int column = array[0].length;

        int i=0, j=column-1;
        while(i<=row-1 && j>=0) {
            if(target > array[i][j]) {
                i++;
            }
            else if(target < array[i][j]) {
                j--;
            }
            else
                return true;
        }
        return false;
    }
}

替换空格

请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
思路:这题本意应该是原来字符串后面有足够的空间来放下替换后的字符串,这样先遍历原来字符串一遍算出新串的长度,然后再从后往前遍历替换空格。

public class Solution {
    public String replaceSpace(StringBuffer str) {
        if(str == null) return null;

        StringBuffer ret = new StringBuffer();
        for(int i=0; i<str.length(); i++) {
            if(str.charAt(i) == ' ')
                ret.append("%20");
            else
                ret.append(str.charAt(i));
        }
        return ret.toString();
    }
}

从尾到头打印链表

输入一个链表,从尾到头打印链表每个节点的值。
思路:递归。

/**
*    public class ListNode {
*        int val;
*        ListNode next = null;
*
*        ListNode(int val) {
*            this.val = val;
*        }
*    }
*
*/
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> ret = new ArrayList<>();
        if(listNode == null) return ret;

        helper(listNode, ret);
        return ret;
    }
    public void helper(ListNode listNode, ArrayList<Integer> ret) {
        if(listNode == null) return;
        helper(listNode.next, ret);
        ret.add(listNode.val);
    }
}

用两个栈实现队列

用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
思路:一个栈用来push元素,另一个栈用来pop元素。

import java.util.Stack;

public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();

    public void push(int node) {
        stack1.push(node);
    }

    public int pop() {
        if(!stack2.empty())
            return stack2.pop();
        else {
            while(!stack1.empty())
                stack2.push(stack1.pop());
            return stack2.pop();
        }
    }
}

斐波那契数列

大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。
n<=39

public class Solution {
    public int Fibonacci(int n) {
        if(n == 0) return 0;
        if(n == 1) return 1;
        int pp = 0, p = 1, cur = 0;
        for(int i=2; i<=n; i++) {
            cur = pp + p;
            pp = p;
            p = cur;
        }
        return cur;
    }
}

跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
思路:本质就是斐波那契数列

public class Solution {
    public int JumpFloor(int target) {
        if(target == 0) return 0;
        if(target == 1) return 1;
        if(target == 2) return 2;

        int pp = 1, p = 2, cur = 0;
        for(int i=3; i<=target; i++) {
            cur = pp + p;
            pp = p;
            p = cur;
        }
        return cur;
    }
}

变态跳台阶

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
思路:f(1) = 1, f(2) = f(1) + 1 = 2, f(3) = f(2) + f(1) + 1 = 2^2, …, f(n) = 2^(n-1);

public class Solution {
    public int JumpFloorII(int target) {
        if(target == 0) return 0;

        return (int)Math.pow(2, target-1);
    }
}

矩形覆盖

我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
思路:本质就是斐波那契数列。

public class Solution {
    public int RectCover(int target) {
        if(target == 0) return 0;
        if(target == 1) return 1;
        if(target == 2) return 2;
        int pp = 1, p = 2, cur = 0;
        for(int i=3; i<=target; i++) {
            cur = pp + p;
            pp = p;
            p = cur;
        }
        return cur;
    }
}

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

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
思路:剑指offer上的原题是不要求相对位置不变,所以可以采用首尾两个指针交换的思路。但是这里不能这么做。我这里写的是冒泡排序的方法。也可以用空间换时间。

public class Solution {
    public void reOrderArray(int [] array) {
       for(int i=array.length-1;i>=0;i--){
            for(int j=0;j<i;j++){
                if(array[j]%2==0&&array[j+1]%2==1){
                    int t = array[j];
                    array[j]=array[j+1];
                    array[j+1]=t;
                }
            }
        }
    }
}

链表倒数第k个节点

输入一个链表,输出该链表中倒数第k个结点。
思路:快慢指针。

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        if(head == null || k <= 0) return null;
        int len = 0;
        ListNode tmp = head;
        while(tmp != null) {
            len += 1;
            tmp = tmp.next;
        }
        if(k > len) {
            return null;
        }

        ListNode fast = head;
        while(k != 0) {
            fast = fast.next;
            k--;
        }

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

树的子结构

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
思路:如果当前根节点值相等,则判断B接下来的对应节点是否和对应位置A的节点值相等。否则,就判断B是否是A的左子树的子结构。如果还不是,则判断B是否是A的右子树的子结构。

/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        boolean result = false;
        if(root1 != null && root2 != null) {
            if(root1.val == root2.val)
                result = DoesTree1HaveTree2(root1, root2);
            if(!result)
                result = HasSubtree(root1.left, root2);
            if(!result)
                result = HasSubtree(root1.right, root2);
        }
        return result;
    }

    private boolean DoesTree1HaveTree2(TreeNode root1, TreeNode root2) {
        if(root2 == null) return true;
        if(root1 == null) return false;

        if(root1.val != root2.val) return false;

        return DoesTree1HaveTree2(root1.left, root2.left) && DoesTree1HaveTree2(root1.right, root2.right);
    }
}

顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下矩阵:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
思路:一次打印一圈,关键是确认起点以及什么时候终止。

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
        int row = matrix.length;
        if(row == 0) return null;
        int column = matrix[0].length;

        ArrayList<Integer> ret = new ArrayList<>();

        int start = 0;
        while(row > start * 2 && column > start * 2) {
            printCircle(matrix, row, column, start, ret);
            start++;
        }
        return ret;

    }

    private void printCircle(int[][] a, int row, int column, int start, ArrayList<Integer> ret) {
        int endX = column - 1 - start;
        int endY = row - 1 - start;
        for(int i=start; i<=endX; i++) {
            ret.add(a[start][i]);
        }
        if(start+1 <= endY) {
            for(int i=start+1; i<=endY; i++) {
                ret.add(a[i][endX]);
            }
            if(endX-1 >= start) {
                for(int i=endX-1; i>=start; i--) {
                    ret.add(a[endY][i]);
                }
                if(endY-1 >= start+1) {
                    for(int i=endY-1; i>=start+1;i--) {
                        ret.add(a[i][start]);
                    }
                }
            }
        }
    }
}

栈的压入、弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
思路:入栈直到栈顶元素等于当前弹出元素,如果已全部入栈栈顶元素都和当前弹出元素不相等,则返回false。

import java.util.Stack;

public class Solution {
    public boolean IsPopOrder(int [] pushA,int [] popA) {
        if(pushA == null || popA == null) return false;

        int nextPush = 0, nextPop = 0;
        Stack<Integer> stack = new Stack<>();

        while(nextPop < popA.length) {
            while((stack.empty() || stack.peek() != popA[nextPop])
                    && nextPush < pushA.length) {
                stack.push(pushA[nextPush++]);
            }
            if(stack.peek() != popA[nextPop])
                return false;

            stack.pop();
            nextPop++;
        }
        return true;
    }
}

二叉搜索树的后序遍历序列

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
思路:最后元素是根节点,在找到右子树起点,判断是否右子树所有节点都大于根节点。

public class Solution {
    public boolean VerifySquenceOfBST(int [] sequence) {
        if(sequence == null || sequence.length == 0) return false;
        return helper(sequence, 0, sequence.length-1);
    }

    private boolean helper(int[] a, int start, int end) {

        int root = a[end];
        // 寻找右子树的开始
        int i=start;
        for(; i<end; i++) {
            if(a[i] > root) {
                break;
            }
        }
        // 判断是否右子树的所有节点都大于root
        int j=i;
        for(; j<end; j++) {
            if(a[j] < root) {
                return false;
            }
        }
        // 检查左子树序列
        boolean left = true;
        if(i>start) {
            left = helper(a, start, i-1);
        }
        // 检查右子树序列
        boolean right = true;
        if(j<end) {
            right = helper(a, j, end-1);
        }

        return left && right;
    }
}

复杂链表的复制

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
思路:分三步。第一步,复制的节点接在原节点后面;第二步,连接随机指针;第三步,拆分复制链表和原链表。

/*
public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}
*/
public class Solution {
    public RandomListNode Clone(RandomListNode pHead) {
        if(pHead == null) return null;
        RandomListNode cur = pHead, nxt = null;
        while(cur != null) {
            nxt = cur.next;
            cur.next = new RandomListNode(cur.label);
            cur.next.next = nxt;
            cur = nxt;
        }

        cur = pHead;
        while(cur != null) {
            if(cur.random != null) {
                cur.next.random = cur.random.next;
            }
            cur = cur.next.next;
        }

        cur = pHead;
        RandomListNode fakeHead = new RandomListNode(0);
        RandomListNode copyCur = fakeHead;

        while(cur != null) {
            nxt = cur.next.next;

            copyCur.next = cur.next;
            copyCur = copyCur.next;

            cur.next = nxt;
            cur = cur.next;
        }

        return fakeHead.next;
    }
}

二叉搜索树与双向链表

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
思路:中序遍历。这个题要注意的就是在连接pre与cur的时候不要cur.right = pre。因为cur.right还未遍历到,我们接下来还有用。

import java.util.*;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree == null) return null;

        Deque<TreeNode> stack = new LinkedList<>();
        TreeNode cur = pRootOfTree;
        TreeNode fakeHead = new TreeNode(0);
        TreeNode pre = fakeHead;
        while(stack.peek()!= null || cur != null) {
            while(cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            cur = stack.pop();

            cur.left = pre;
            pre.right = cur;

            pre = cur;
            cur = cur.right;
        }
        fakeHead.right.left = null;
        return fakeHead.right;
    }
}

字符串的排列

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
输入描述:输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
思路:依次将第一个元素和后面每个元素交换,再递归,递归完后要把第一个元素和后面的元素交换回来。由于可能有重复,要注意去重。

import java.util.*;
public class Solution {
    public ArrayList<String> Permutation(String str) {
        ArrayList<String> ret = new ArrayList<String>();
        if(str == null || str.length() == 0) return ret;

        Permutation(str.toCharArray(), 0, ret);
        Collections.sort(ret);
        return ret;
    }

    private void Permutation(char[] chars, int start, ArrayList<String> ret) {
        if(start == chars.length-1) {
            ret.add(new String(chars));
            return;
        }

        char tmp = chars[start];
        for(int i=start; i<chars.length; i++) {
            if(i != start && chars[i] == tmp) {
                    continue;
            }                
            else {
                chars[start] = chars[i];
                chars[i] = tmp;

                Permutation(chars, start+1, ret);

                chars[i] = chars[start];
                chars[start] = tmp;
            }
        }       
    }
}

最小的k个数

输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
思路:set + 最大堆。set用来去重,最大堆用来保持当前最小的k个数。

import java.util.*;
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> ret = new ArrayList<Integer>();
        if(input == null || input.length < k) return ret;

        Set<Integer> set = new HashSet<>();
        PriorityQueue<Integer> pq = new PriorityQueue<>(k+1, Collections.reverseOrder());
        for(int i=0; i<input.length; i++) {
            if(!set.contains(input[i])) {
                set.add(input[i]);
                pq.offer(input[i]);
                if(set.size() > k) {
                    set.remove(pq.poll());
                }
            }
        }
        while(pq.peek()!=null) {
            ret.add(pq.poll());
        }
        return ret;
    }
}

整数中1出现的次数

求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数。
思路:
一、1的数目
编程之美上给出的规律:

  1. 如果第i位(自右至左,从1开始标号)上的数字为0,则第i位可能出现1的次数由更高位决定(若没有高位,视高位为0),等于更高位数字X当前位数的权重10^(i-1)。

  2. 如果第i位上的数字为1,则第i位上可能出现1的次数不仅受更高位影响,还受低位影响(若没有低位,视低位为0),等于更高位数字X当前位数的权重10^(i-1)+(低位数字+1)。

  3. 如果第i位上的数字大于1,则第i位上可能出现1的次数仅由更高位决定(若没有高位,视高位为0),等于(更高位数字+1)X当前位数的权重10^(i-1)。

二、X的数目
这里的 X∈[1,9],因为 X=0 不符合下列规律,需要单独计算。

首先要知道以下的规律:

从 1 至 10,在它们的个位数中,任意的 X 都出现了 1 次。
从 1 至 100,在它们的十位数中,任意的 X 都出现了 10 次。
从 1 至 1000,在它们的百位数中,任意的 X 都出现了 100 次。
依此类推,从 1 至 10i,在它们的左数第二位(右数第 i 位)中,任意的 X 都出现了 10^(i−1) 次。

这个规律很容易验证,这里不再多做说明。

接下来以 n=2593,X=5 为例来解释如何得到数学公式。从 1 至 2593 中,数字 5 总计出现了 813 次,其中有 259 次出现在个位,260 次出现在十位,294 次出现在百位,0 次出现在千位。

现在依次分析这些数据,首先是个位。从 1 至 2590 中,包含了 259 个 10,因此任意的 X 都出现了 259 次。最后剩余的三个数 2591, 2592 和 2593,因为它们最大的个位数字 3 < X,因此不会包含任何 5。(也可以这么看,3 < X,则个位上可能出现的X的次数仅由更高位决定,等于更高位数字(259)X101-1=259)。

然后是十位。从 1 至 2500 中,包含了 25 个 100,因此任意的 X 都出现了 25×10=250 次。剩下的数字是从 2501 至 2593,它们最大的十位数字 9 > X,因此会包含全部 10 个 5。最后总计 250 + 10 = 260。(也可以这么看,9>X,则十位上可能出现的X的次数仅由更高位决定,等于更高位数字(25+1)X10^(2-1)=260)。

接下来是百位。从 1 至 2000 中,包含了 2 个 1000,因此任意的 X 都出现了 2×100=200 次。剩下的数字是从 2001 至 2593,它们最大的百位数字 5 == X,这时情况就略微复杂,它们的百位肯定是包含 5 的,但不会包含全部 100 个。如果把百位是 5 的数字列出来,是从 2500 至 2593,数字的个数与百位和十位数字相关,是 93+1 = 94。最后总计 200 + 94 = 294。(也可以这么看,5==X,则百位上可能出现X的次数不仅受更高位影响,还受低位影响,等于更高位数字(2)X10^(3-1)+(93+1)=294)。

最后是千位。现在已经没有更高位,因此直接看最大的千位数字 2 < X,所以不会包含任何 5。(也可以这么看,2 < X,则千位上可能出现的X的次数仅由更高位决定,等于更高位数字(0)X104-1=0)。

到此为止,已经计算出全部数字 5 的出现次数。

总结一下以上的算法,可以看到,当计算右数第 i 位包含的 X 的个数时:

取第 i 位左边(高位)的数字,乘以 10^(i−1),得到基础值 a。
取第 i 位数字,计算修正值:
如果大于 X,则结果为 a+10^(i−1)。
如果小于 X,则结果为 a。
如果等 X,则取第 i 位右边(低位)数字,设为 b,最后结果为 a+b+1。
相应的代码非常简单,效率也非常高,时间复杂度只有 O(log10n)。

public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        int high, cur, low, tmp, i=1;
        high = n;
        int total = 0;
        while(high != 0) {
            high = n / (int)Math.pow(10, i);
            low = n % (int)Math.pow(10, i-1);
            tmp = n % (int)Math.pow(10, i);
            cur = tmp / (int)Math.pow(10, i-1);           

            if(cur > 1)
                total += (high+1)*(int)Math.pow(10, i-1);
            else if(cur < 1)
                total += high * (int)Math.pow(10, i-1);
            else
                total += high * (int)Math.pow(10, i-1) + low + 1;
            i++;
        }
        return total;
    }
}

把数组排成最小的数

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
思路:先将整型数组转换成String数组,然后将String数组排序,最后将排好序的字符串数组拼接出来。关键就是制定排序规则。
* 排序规则如下:
* 若ab > ba 则 a > b,
* 若ab < ba 则 a < b,
* 若ab = ba 则 a = b;

import java.util.*;

public class Solution {
    public String PrintMinNumber(int [] numbers) {
        List<Integer> list = new ArrayList<>();

        for(int num : numbers) {
            list.add(num);
        }

        Collections.sort(list, new Comparator<Integer>(){
              public int compare(Integer i1, Integer i2) {
                  String s1 = i1 + "" + i2;
                  String s2 = i2 + "" + i1;

                  return s1.compareTo(s2);
              }
        });

        StringBuffer sb = new StringBuffer();
        for(int num : list) {
            sb.append(num + "");
        }

        return sb.toString();
    }
}

丑数

把只包含素因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
思路:直接计算丑数,而不在非丑数上花费时间。假设当前最大丑数为M,那么下一个丑数肯定是前面某一个丑数乘以2、3或5的结果。如果我们一直跟踪记录前面乘以2刚刚大于M的丑数的位置,乘以3刚刚大于M的丑数的位置,乘以5刚刚大于M的丑数的位置,那么下一个丑数就是这三者最小的那个。

public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if(index <= 0) return 0;
        int[] a = new int[index];
        a[0] = 1;

        int nextIndex = 1;
        int nextMul2 = 0, nextMul3 = 0, nextMul5 = 0;

        while(nextIndex < index) {
            a[nextIndex] = Math.min(Math.min(a[nextMul2] * 2, a[nextMul3] * 3), a[nextMul5] * 5);
            while(a[nextMul2] * 2 <= a[nextIndex]) {
                nextMul2++;
            }
            while(a[nextMul3] * 3 <= a[nextIndex]) {
                nextMul3++;
            }
            while(a[nextMul5] * 5 <= a[nextIndex]) {
                nextMul5++;
            }
            nextIndex++;
        }

        return a[index-1];
    }
}

第一个只出现一次的字符

在一个字符串(1<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置。如果字符串为空,返回-1
思路:hashmap,空间换时间。

import java.util.*;
public class Solution {
    public int FirstNotRepeatingChar(String str) {
        Map<Character, Integer> map = new HashMap<>();

        for(int i=0; i<str.length(); i++) {
            char tmp = str.charAt(i);
            if(!map.containsKey(tmp)) {
                map.put(tmp, 0);
            }
            map.put(tmp, map.get(tmp) + 1);
        }

        for(int i=0; i<str.length(); i++) {
            if(map.get(str.charAt(i)) == 1)
                return i;
        }
        return -1;
    }
}

数组中的逆序数对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007。
思路:归并排序的思路,要注意merge的时候从后向前,这样方便统计逆序数。

public class Solution {
    public int InversePairs(int [] array) {
        int[] tmp = new int[array.length];
        return helper(array, 0, array.length-1, 0, tmp)%1000000007;
    }

    public int helper(int[] array, int start, int end, int ret, int[] tmp) {
        if(start >= end)
            return 0;

        int mid = (start + end) >> 1;

        int left = helper(array, start, mid, ret, tmp)%1000000007;
        int right = helper(array, mid+1, end, ret, tmp)%1000000007;

        int count = merge(array, start, mid, end, tmp)%1000000007;
        return (left + right + count) % 1000000007;
    }

    private int merge(int[] array, int start, int mid, int end, int[] tmp) {

        int left = mid, right = end, cur = end, count = 0;

        while(left >= start && right >= mid + 1) {
            if(array[left] > array[right]) {
                tmp[cur--] = array[left--];
                count = (count + right - mid)%1000000007;
            }
            else {
                tmp[cur--] = array[right--];
            }
        }
        while(left >= start)
            tmp[cur--] = array[left--];
        while(right >= mid + 1)
            tmp[cur--] = array[right--];

        for(int i=start; i<=end; i++) {
            array[i] = tmp[i];
        }
        return count;
    }
}

数字在排序数组中出现的次数

统计一个数字在排序数组中出现的次数。
思路:二分查找,两次分别找到第一次出现的位置和最后一次出现的位置。

public class Solution {
    public int GetNumberOfK(int [] array , int k) {
        if(array == null || array.length == 0) return 0;

        int ret = 0;

        int left = 0, right = array.length-1, mid;
        int startK = -1, endK = -1;
        while(left <= right) {
            mid = (left+right) / 2;
            if(array[mid] > k)
                right = mid-1;
            else if(array[mid] < k)
                left = mid+1;
            else {
                startK = mid;
                right = mid-1;
            }
        }

        left = 0;
        right = array.length - 1;
        while(left <= right) {
            mid = (left+right) / 2;
            if(array[mid] > k)
                right = mid-1;
            else if(array[mid] < k)
                left = mid+1;
            else {
                endK = mid;
                left = mid+1;
            }
        }
        if(endK != -1 && startK != -1)
            ret = endK - startK + 1;
        return ret;
    }
}

和为S的连续正数序列

输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序。
思路:两个指针。前面的指针负责在不够的时候扩充,后面的指针负责在多余的时候缩减。当前面的指针指向的元素等于S的一半的时候就可以停止。

import java.util.ArrayList;
public class Solution {
    public ArrayList<ArrayList<Integer>> FindContinuousSequence(int sum) {
        ArrayList<ArrayList<Integer>> ret = new ArrayList<ArrayList<Integer>>(); 

        int small = 1, big = 2, curSum = 3, mid = (sum+1)/2;

        while(small < mid) {
            if(curSum < sum) {
                big++;
                curSum += big;
            }
            else if(curSum > sum) {
                curSum -= small;
                small++;
            }
            else {
                ArrayList<Integer> subList = new ArrayList<Integer>();
                for(int i=small; i<=big; i++) {
                    subList.add(i);
                }
                ret.add(subList);
                big++;
                curSum += big;
            }
        }
        return ret;
    }
}

和为S的两个数字

输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
输出描述:
对应每个测试案例,输出两个数,小的先输出。
思路:由于数组有序,可以使用两个指针。找到的第一对数就是满足条件的,可以证明乘积最小。

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
        ArrayList<Integer> ret = new ArrayList<Integer>();
        if(array == null) return ret;

        int left = 0, right = array.length-1;
        while(left < right) {
            if(array[left] + array[right] == sum) {
                ret.add(array[left]);
                ret.add(array[right]);
                break;
            }
            else if(array[left] + array[right] > sum)
                right--;
            else
                left++;
        }
        return ret;
    }
}

左旋转字符串

汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

public class Solution {
    public String LeftRotateString(String str,int n) {
        String s = "";
        if(str == null || str.length() < n) return s;

        char[] chars = str.toCharArray();
        reverse(chars, 0, n-1);
        reverse(chars, n, chars.length-1);
        reverse(chars, 0, chars.length-1);

        StringBuilder sb = new StringBuilder();
        for(char c : chars) {
            sb.append(c);
        }
        return sb.toString();
    }

    private void reverse(char[] chars, int start, int end) {
        while(start < end) {
            char tmp = chars[start];
            chars[start++] = chars[end];
            chars[end--] = tmp;
        }
    }
}

翻转单词顺序列

牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?
思路:这个是不去多余空格的版本,比较简单。

public class Solution {
    public String ReverseSentence(String str) {
        int start = 0, end = 0;
        char[] chars = str.toCharArray();
        while(start < chars.length) { // 一定要按照start来判断循环终止条件,要是用end会少一次反转
            if(chars[start] == ' ') {
                start++;
                end++;
            }
            else if(end == chars.length || chars[end] == ' ') {
                reverse(chars, start, end - 1);
                start = end;
            }
            else {
                end++;
            }
        }
        reverse(chars, 0, chars.length-1);

        StringBuilder sb = new StringBuilder();
        for(char c : chars) {
            sb.append(c);
        }
        return sb.toString();
    }

    private void reverse(char[] chars, int start, int end) {
        while(start < end) {
            char tmp = chars[start];
            chars[start++] = chars[end];
            chars[end--] = tmp;
        }
    }
}

扑克牌顺子

LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)…他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子…..LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何。为了方便起见,你可以认为大小王是0。
思路:将数组排序。非0的牌有重复肯定不是顺子。

import java.util.*;
public class Solution {
    public boolean isContinuous(int [] numbers) {
        if(numbers == null || numbers.length == 0) return false;

        Arrays.sort(numbers);

        int numberZero = 0;
        int diff = 0;
        for(int i=0; i<numbers.length-1; i++) {
            if(numbers[i] != 0) {
                if(numbers[i] == numbers[i+1]) return false;
                diff += numbers[i+1] - numbers[i] - 1; 
            }
            else {
                numberZero++;
            }
        }

        if(numberZero < diff)
            return false;
        return true;
    }
}

圆圈中最后剩下的数字

0,1,…,n-1这n个数字排成一个圆圈,从数字0开始每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。
思路1:暴力模拟。

public static int findLastNumber(int n,int m){
        if(n<1||m<1) return -1;
        int[] array = new int[n];
        int i = -1,step = 0, count = n;
        while(count>0){   //跳出循环时将最后一个元素也设置为了-1
            i++;          //指向上一个被删除对象的下一个元素。
            if(i>=n) i=0;  //模拟环。
            if(array[i] == -1) continue; //跳过被删除的对象。
            step++;                     //记录已走过的。
            if(step==m) {               //找到待删除的对象。
                array[i]=-1;
                step = 0;
                count--;
            }        
        }
        return i;//返回跳出循环时的i,即最后一个被设置为-1的元素
    }

思路2:总结规律,总结递推式。
假设从0-n-1中删除了第m个数字,则下一轮的数字排列为m,m+1,…..n,1,2,3…m-2,将该数字排列重新映射为0~n-2,则为

m    0

m+1   1  

….    ….

n-1   n-1-m

0    n-m

1    n-m+1

…    ….

m-2   n-2

可以看出从右往左的映射关系为left=(right+m)%n,即0~n-1序列中最后剩下的数字等于(0~n-2序列中最后剩下的数字+m)%n,很明显当n=1时,只有一个数,那么剩下的数字就是0.

问题转化为动态规划问题,关系表示为:

f(n)=(f(n-1)+m)%n; 当n=1,f(1)=0;

public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if(n < 1 || m < 1) return -1;

        int last = 0;
        for(int i=2; i<=n; i++) {
            last = (last + m) % i;
        }

        return last;
    }
}

求1+2+3+…+n

求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
思路:利用条件判断符的短路作用

public class Solution {
    public int Sum_Solution(int n) {
        int res = n;
        boolean b = n > 0 && (res += Sum_Solution(n-1)) > 0;
        return res;
    }
}

不用加减乘除做加法

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
思路:位运算,第一步只对应位相加不进位(异或),第二步只算进位(与并左移一位),然而二者相加。如此循环直到进位为0。

public class Solution {
    public int Add(int num1,int num2) {
        int sum = 0, carray = 0;
        do {
            sum = num1 ^ num2;
            carray = (num1 & num2) << 1;

            num1 = sum;
            num2 = carray;
        } while(num2 != 0);

        return sum;
    }
}

数组中重复的数字

在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是重复的数字2或者3。
思路:既然数字限定了范围,所以可以用数组中的元素的正负来表示对应等于下标的数是否在之前出现过。

public class Solution {
    // Parameters:
    //    numbers:     an array of integers
    //    length:      the length of array numbers
    //    duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
    //                  Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
    //    这里要特别注意~返回任意重复的一个,赋值duplication[0]
    // Return value:       true if the input is valid, and there are some duplications in the array number
    //                     otherwise false
    public boolean duplicate(int numbers[],int length,int [] duplication) {
        if(numbers == null || length == 0) return false;

        for(int i=0; i<numbers.length; i++) {
            int index = Math.abs(numbers[i]);
            if(numbers[index] > 0) {
                numbers[index]  = - numbers[index];
            }
            else if(numbers[index] < 0){
                duplication[0] = index;
                return true;
            }
        }
        return false;
    }
}

字符流中第一个不重复的字符

请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符”go”时,第一个只出现一次的字符是”g”。当从该字符流中读出前六个字符“google”时,第一个只出现一次的字符是”l”。
输出描述:
如果当前字符流没有存在出现一次的字符,返回#字符。
思路:hashmap,key存字符,value存第一次出现的位置,如果不是第一出现,value置-1。

import java.util.*;
public class Solution {
    Map<Character, Integer> map = new HashMap<>();
    int index = 0;
    //Insert one char from stringstream
    public void Insert(char ch)
    {
        if(!map.containsKey(ch)) {
            map.put(ch, index);
        }
        else {
            map.put(ch, -1);
        }
        index++;
    }
    //return the first appearence once char in current stringstream
    public char FirstAppearingOnce()
    {
        char c = '#';
        int minIndex = Integer.MAX_VALUE;
        for(char key : map.keySet()) {
            if(map.get(key) >= 0 && map.get(key) < minIndex) {
                c = key;
                minIndex = map.get(key);
            }
        }
        return c;
    }
}

二叉树的下一个节点

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
思路:分两种情况讨论。一种是右子树非空,则下一个节点是右子树的最左边的叶节点。另一种是右子树为空,则下一个节点是第一个左父节点。

/*
public class TreeLinkNode {
    int val;
    TreeLinkNode left = null;
    TreeLinkNode right = null;
    TreeLinkNode next = null;

    TreeLinkNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    public TreeLinkNode GetNext(TreeLinkNode pNode)
    {
        if(pNode == null) return null;

        if(pNode.right != null) {
            pNode = pNode.right;
            while(pNode.left != null)
                pNode = pNode.left;
            return pNode;
        }

        TreeLinkNode parent = pNode.next;       

        while(parent != null && pNode == parent.right) {
            pNode = parent;
            parent = parent.next;
        }

        return parent;
    }
}

按之字顺序打印二叉树

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
思路:设置两个标志,两个栈,一个栈存本层节点,另一栈存下层节点。根据标志交替先存左节点和先存右节点。

import java.util.*;

/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer> > ret = new ArrayList<ArrayList<Integer> >();
        if(pRoot == null) return ret;

        Deque[] levels = new LinkedList[2];
        levels[0] = new LinkedList<TreeNode>();
        levels[1] = new LinkedList<TreeNode>();

        int current = 0, next = 1;

        levels[current].push(pRoot);

        while(levels[0].peek() != null || levels[1].peek() != null) {
            ArrayList<Integer> subList = new ArrayList<>();
            while(levels[current].peek() != null) {
                TreeNode cur = (TreeNode)levels[current].pop();
                subList.add(cur.val);
                if(current == 0) {
                    if(cur.left != null)
                        levels[next].push(cur.left);
                    if(cur.right != null)
                        levels[next].push(cur.right);
                }
                else {
                    if(cur.right != null)
                        levels[next].push(cur.right);
                    if(cur.left != null)
                        levels[next].push(cur.left);
                }
            }
            ret.add(subList);
            current = 1 - current;
            next = 1 - next;
        }
        return ret;
    }

}

序列化二叉树

请实现两个函数,分别用来序列化和反序列化二叉树
思路:先序遍历的顺序。

import java.util.*;
/*
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;

    }

}
*/
public class Solution {
    String Serialize(TreeNode root) {
        if(root == null) return null;
        StringBuilder sb = new StringBuilder();
        buildString(root, sb);

        return sb.toString();

    }

    void buildString(TreeNode root, StringBuilder sb) {
        if(root == null) {
            sb.append("$,");
        }
        else {
            sb.append(root.val + ",");
            buildString(root.left, sb);
            buildString(root.right, sb);
        }
    }

    TreeNode Deserialize(String str) {
        if(str == null) return null;

        Queue<String> queue = new LinkedList<>();
        queue.addAll(Arrays.asList(str.split(",")));
        return DeserializeHelper(queue);
    }

    TreeNode DeserializeHelper(Queue<String> queue) {
        String val = queue.poll();
        if(val.equals("$"))
            return null;

        TreeNode root = new TreeNode(Integer.valueOf(val));
        root.left = DeserializeHelper(queue);
        root.right = DeserializeHelper(queue);
        return root;      
    }
}

数据流中的中位数

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
思路:两个堆。一个最大堆用来存前一半数,最小堆用来存后一半数。

import java.util.*;
public class Solution {
    private PriorityQueue<Integer> minHeap = new PriorityQueue<>();
    private PriorityQueue<Integer> maxHeap = new PriorityQueue<>(100, Collections.reverseOrder());

    public void Insert(Integer num) {
        if(((minHeap.size() + maxHeap.size()) & 1) == 0) {
            if(maxHeap.size() > 0 && maxHeap.peek() > num) {
                maxHeap.offer(num);
                num = maxHeap.poll();
            }
            minHeap.offer(num);
        }
        else {
            if(minHeap.size() > 0 && minHeap.peek() < num) {
                minHeap.offer(num);
                num = minHeap.poll();
            }
            maxHeap.offer(num);
        }
    }

    public Double GetMedian() {
        if(((minHeap.size() + maxHeap.size()) & 1) == 0) {
            return (minHeap.peek() + maxHeap.peek()) / 2.0;
        }
        else {
            return minHeap.peek()*1.0;
        }
    }


}

滑动窗口的最大值

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
思路:我们并不把滑动窗口的每个数值都存入队列中,而只把有可能成为滑动窗口最大值的数值存入到一个两端开口的队列。接着以输入数字{2,3,4,2,6,2,5,1}为例一步分析。

数组的第一个数字是 2,把它存入队列中。第二个数字是3.由于它比前一个数字 2 大,因此 2不可能成为滑动窗口中的最大值。2 先从队列里删除,再把3存入到队列中。此时队列中只有一个数字 3。针对第三个数字 4 的步骤类似,最终在队列中只剩下一个数字 4。此时滑动窗口中已经有 3 个数字,而它的最大值 4 位于队列的头部。

接下来处理第四个数字 2。2 比队列中的数字 4 小。当 4 滑出窗口之后 2 还是有可能成为滑动窗口的最大值,因此把 2 存入队列的尾部。现在队列中有两个数字 4 和 2,其中最大值 4 仍然位于队列的头部。

下一个数字是 6。由于它比队列中已有的数字 4 和 2 都大,因此这时 4 和 2 已经不可能成为滑动窗口中的最大值。先把 4 和 2 从队列中删除,再把数字 6 存入队列。这个时候最大值 6 仍然位于队列的头部。

第六个数字是 2。由于它比队列中已有的数字 6 小,所以 2 也存入队列的尾部。此时队列中有两个数字,其中最大值 6 位于队列的头部。

接下来的数字是 5。在队列中已有的两个数字 6 和 2 里,2 小于 5,因此 2 不可能是一个滑动窗口的最大值,可以把它从队列的尾部删除。删除数字 2 之后,再把数字 5 存入队列。此时队列里剩下两个数字 6 和 5,其中位于队列头部的是最大值 6。

数组最后一个数字是 1,把 1 存入队列的尾部。注意到位于队列头部的数字 6 是数组的第 5 个数字,此时的滑动窗口已经不包括这个数字了,因此应该把数字 6 从队列删除。那么怎么知道滑动窗口是否包括一个数字?应该在队列里存入数字在数组里的下标,而不是数值。当一个数字的下标与当前处理的数字的下标之差大于或者等于滑动窗口的大小时,这个数字已经从滑动窗口中滑出,可以从队列中删除了。

import java.util.*;
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size)
    {
        ArrayList<Integer> ret = new ArrayList<Integer>();
        if(num == null || num.length == 0 || num.length < size || size <= 0) return ret;

        Deque<Integer> deque = new LinkedList<>();
        for(int i=0; i<size; i++) {
            while(!deque.isEmpty() && num[i] >= num[deque.peekLast()]) {
                deque.pollLast();
            }
            deque.offerLast(i);
        }

        for(int i=size; i<num.length; i++) {
            ret.add(num[deque.peekFirst()]);
            while(!deque.isEmpty() && num[i] >= num[deque.peekLast()]) {
                deque.pollLast();
            }
            if(!deque.isEmpty() && deque.peekFirst() <= (i-size))
                deque.pollFirst();
            deque.offerLast(i);
        }
        ret.add(num[deque.peekFirst()]);
        return ret;
    }
}

矩阵中的路径

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如[a b c e s f c s a d e e]是3*4矩阵,其包含字符串”bcced”的路径,但是矩阵中不包含“abcb”路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
思路:回溯法。

public class Solution {
    public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
    {
        boolean[] visited = new boolean[rows*cols];

        int pathLength = 0;
        for(int i=0; i<rows; i++) {
            for(int j=0; j<cols; j++) {
                if(hasPathCore(matrix, rows, cols, i, j, str, pathLength, visited))
                    return true;
            }
        }
        return false;
    }

    private boolean hasPathCore(char[] matrix, int rows, int cols, int row, int col, 
                                char[] str, int pathLength, boolean[] visited) {
        if(pathLength == str.length) return true;

        boolean hasPath = false;
        if(row >= 0 && row < rows && col >=0 && col < cols
          && matrix[row * cols + col] == str[pathLength]
          && !visited[row * cols + col]) {
            pathLength++;
            visited[row * cols + col] = true;

            hasPath = hasPathCore(matrix, rows, cols, row, col-1, str, pathLength, visited) ||
                        hasPathCore(matrix, rows, cols, row-1, col, str, pathLength, visited) ||
                            hasPathCore(matrix, rows, cols, row, col+1, str, pathLength, visited) ||
                                hasPathCore(matrix, rows, cols, row+1, col, str, pathLength, visited);
            if(!hasPath) {
                pathLength--;
                visited[row * cols + col] = false;
            }
        }
        return hasPath;
    }


}

机器人的运动范围

地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
思路:DFS。

public class Solution {
    public int movingCount(int threshold, int rows, int cols)
    {
        boolean[] visited = new boolean[rows * cols];

        return movingCountCore(threshold, rows, cols, 0, 0, visited);
    }

    private int movingCountCore(int threshold, int rows, int cols, int row, int col, boolean[] visited) {
        int count = 0;
        if(check(threshold, rows, cols, row, col, visited)) {
            visited[row * cols + col] = true;

            count = 1 + movingCountCore(threshold, rows, cols, row-1, col, visited)
                    + movingCountCore(threshold, rows, cols, row, col-1, visited)
                    + movingCountCore(threshold, rows, cols, row+1, col, visited)
                    + movingCountCore(threshold, rows, cols, row, col+1, visited);
        }
        return count;
    }

    private boolean check(int threshold, int rows, int cols, int row, int col, boolean[] visited) {
        if(row >= 0 && row < rows && col >=0 && col<cols
          && getDigitSum(row) + getDigitSum(col) <= threshold
          && !visited[row * cols + col]) {
            return true;
        }
        return false;
    }

    private int getDigitSum(int num) {
        int sum = 0;
        while(num > 0) {
            sum += num % 10;
            num /= 10;
        }
        return sum;
    }
 }
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值