leetcode刷题笔记

2020年9月22日

1.监控二叉树 hard
属于递归和动态规划的结合,将节点分为三种状态,一种是自身有摄像头,一种是自身没有摄像头但是自己可以被看到,还有一种就是自身不能被看到
每个节点只要管好自身还有自己的子节点即可,最上层的根节点单独处理
主要是对于递归的掌握还是不够好,还需要多刷题,今天学了一下大佬的解法,争取下次自己能做出来

2.01背包问题 easy
虽然是easy难度的题目,但是第一次做还真是没有做出来,dp[i][j]用来表示在只能取前i个、背包容量为j时,能拿到的最大价值的货物。
关于压缩状态空间的问题,我一直没有掌握,主要还是状态空间的设置没有弄得太明白,还需要继续努力

3.对链表进行插入排序 medium
真的给我做吐了,一道media的题目给我做这么久
对于链表的操作,其实我的理解还是可以,但是链表的一些具体的细节完成得还是欠缺了很多
主要是在于边界问题,这个要还需要多加练习

4.二叉搜索树的最近公共祖先节点 easy
我发现一个巨大的问题,我对于二叉搜索树的认识一直有bug,原来子节点的左节点也是必定比自己大的,也就是说左子树的所有节点都比自己小,右子树的所有节点都比自己大,惭愧惭愧

5.迭代实现后序遍历 medium
这个关键在于要将所有父节点以及左子节点放入栈中两次,以此来判断取出该节点时,是要遍历到此节点,还是只是将其当作一个遍历右节点的工具

6.所有蚂蚁掉下来的时间
原来leetcode里面也有脑筋急转弯的题目,所有掉头的等于没掉头,直接向前走就完事了,异常简单

7.HashMap和HashSet
HashMap添加元素用put
HashSet添加元素用add

8.四数之和
本来打算回溯,但是复杂度太高,耗时过久,剪枝半天还是过不去最后一个示例,啊啊啊啊,痛苦!
好吧,还是回到了双指针,直接排序,固定两边,然后进行双指针的移动,时间复杂度变成了n的三次方。
这个方法看上去复杂,但是思路是比较简单的,双指针要好好掌握,特别是有序的列表,非常好用

9.LRU缓存机制
第一次做这种题目,其实就是自己设计一个类,包括类中的方法和参数等等
最简单的方法就是直接继承某个父类,然后调用父类中的方法,这样子比较无脑,一般要求的是手写数据结构

10.单词拆分,典型的0-1背包问题
双重循环,只要设置一个维度的DP空间,然后重复遍历就可以了,其实是同一个套路,还行的
我总是去想着用回溯法,然后超时,做DP的题目还是做得太少了
进阶版:单词拆分的第二题,又晕了,这次是要返回全部的结果,其实这种题目照样可以用DP算法,弄个List的数组进行保存即可,但是相对来说并没有比记忆回溯法优越多少

11.环形链表–找出入环节点
大力出奇迹的解法是使用listnode的set集合,第一个重复的即为入环的节点
巧妙方法:快慢双指针,这个方法是判断有无环的经典方法,而找出入环的节点还需要在快慢指针碰撞后,将慢指针重新开始,而快指针降低速度,然后就会在入环节点处相遇

12.不用乘除号实现除法
将被除数每次减去大于自身一半的数,然后一直重复这个过程,就是纯减法的一种改良
对于位运算的题目,我一直掌握得不是特别好,还需要加强这个方面

13.分割等和的子集
这个题目其实和0-1背包问题有异曲同工之妙,但是还是稍有不同
这个题目只能通过动态规划的方法解决,0-i个元素,j为目标值,这道题就可以转化为找到总和为一半的子集
DP的数据类型为Boolean类型
只能慢慢往外找,效率非常低,但是没办法

14.二叉搜索树寻找最小差
题目简单,但是学到一个新东西,可以直接给ArrayList排序,而不是写入数组再排序
Collection.sort(arr);
看到二叉搜索树 = 中序遍历!!!
Integer.MAX_VALUE,integer.MIN_VALUE!!!

15.寻找旋转数组中的最小值
这个题目的话呢,考察的就是一个二分法,以前总觉得二分法的mid值、左右边界值非常之难搞,但是再次做这种题目,我悟了
二分法就把左右看成两个模式,左模式是left、mid、(left+right)/2;右模式是mid+1、right、(left+right+1)/2
反正左模式已经自然而然地往左边偏了,那么右模式也得往右边偏

16.手动实现最小栈类
通过辅助栈实现常数时间内找到栈中最小的数
每次有元素进入实际栈中,都往辅助栈中加入一个元素,该元素是栈顶和新元素的较小者
每次出栈,直接将辅助栈栈顶元素去除
非常巧妙的辅助栈方法

17.对于类对象数组的收获,类对象数组可以一次性声明,不能一次性创建
StringBuffer[] stringBuffers = new StringBuffer[10];
这只是声明了 10个 StringBuffer 的变量,但是并没有创建对象,stringBuffers[0]还是null
stringBuffers[i] = new StringBuffer(); 这才是给变量创建对象

18.回文链表
这个题用数组存储的方法其实非常简单,但是其实有更好的方法,可以将空间复杂度降到O(1)
先用快慢指针找到中间节点,然后使用反转链表反转后半部分节点,就可以完成比较
快慢指针我掌握得可以,但是反转链表作为一个基础知识,我还是不太熟练
而且linkedlist是queue接口的实现类,而不是stack的

19.O(1)时间的插入、删除和获取唯一元素
(1)迭代器:iterator,用得非常不熟练,不应该
Iterator it = set.iterator();
int i = it.next();
(2)总结设计题的规律之一:
题目经常在时间复杂度出难题,而我们就是要敢于用空间!用就完事了,不要怕用多了!!
Map<Integer, Set> idx = new HashMap<Integer, Set>();
hashmap中有别的数据结构,别的结构中有hashmap,各种数据结构嵌套,敢用敢想,其实难度并不高,只要你敢用,你就做出来了!

20.使用Arrays.sort()来对二维数组进行排序
Arrays.sort(nums, (v1,v2)-> v1[0]- v2[0]);
这样子就可以实现对nums二维数组的基于第一个变量的排序,这个与应用Comparator类的排序恰好相反,前减后是降序,后减前是升序,运用的是lambda表达式,和传入比较器接口是一样的,我错了

21.魔塔-英雄救公主(无后效性)
这个题目要找一个路径可以以最小的血量流失救公主,可以很简单想到DP算法,但是最大的扣除、当前剩余的血,这两个条件对后面都会产生作用,那么就没有一个无法进行一个取舍,到底是先考虑前者,还是考虑后者呢?这么一来,这道题就做不出来了,这就不满足无后效性,就是我现在的决定只与我走过的路相关,与之后无关。
因此这就涉及到了一个反向DP,从终点走向起点,差别在于我不用考虑路径问题,单纯的就是我当前要多少血就肯定能到重点?这样子和后面就无关了,好好体会

22.重构Arrays.sort函数,传入比较器接口

Arrays.sort(people, new Comparator<int[]>(){
       public int compare(int[] a,int[] b){
             if(a[0] != b[0])
             	//升序
                 return a[0] - b[0];
             else
             	//降序
                 return b[1] - a[1];
            }
});

这个就是用sort方法对于二维数组进行排序,先对第一个元素升序,再对第二个元素降序,前减后是升序,后减前是降序

23.反转链表(快速手写是基本操作)
ListNode pre = null, p = head;
while(p != null){
ListNode later = p.next;
p.next = pre;
pre = p;
p = later;
}
return pre;

24.快速排序,非常直观的实现,直指快排的思路,非常重要,下次忘了就来看看

private static void quickSort(int[] arr, int low, int high) {
	if (low < high) {
		int index = getIndex(arr, low, high);
		quickSort(arr, low, index - 1);
		quickSort(arr, index + 1, high);
	}
}

private static int getIndex(int[] arr, int low, int high) {
	int tmp = arr[low];
	while (low < high) {
		while (low < high && arr[high] >= tmp) {
			high--;
		}
		arr[low] = arr[high];
		while (low < high && arr[low] <= tmp) {
			low++;
		}
		arr[high] = arr[low];
	}
	arr[low] = tmp;
	return low;
}

25.归并排序,基本思路和快排完全相反,先化为最小再合到一起。要花费很多空间,但是实现要比快排更简单

public static int[] mergeSort(int[] nums, int l, int h) {
    if (l == h)
        return new int[] { nums[l] };

    int mid = l + (h - l) / 2;
    int[] leftArr = mergeSort(nums, l, mid); //左有序数组
    int[] rightArr = mergeSort(nums, mid + 1, h); //右有序数组
    int[] newNum = new int[leftArr.length + rightArr.length]; //新有序数组
  
    int m = 0, i = 0, j = 0; 
    while (i < leftArr.length && j < rightArr.length) {
        newNum[m++] = leftArr[i] < rightArr[j] ? leftArr[i++] : rightArr[j++];
    }
    while (i < leftArr.length)
        newNum[m++] = leftArr[i++];
    while (j < rightArr.length)
        newNum[m++] = rightArr[j++];
    return newNum;
}

26.前缀树设计,非常有意思,让我对于树的理解有了很大的进步

public class Trie {
	private boolean is_string=false;
	private Trie next[]=new Trie[26];
	public Trie(){}

public void insert(String word){//插入单词
    Trie root=this;
    char w[]=word.toCharArray();
    for(int i=0;i<w.length;++i){
        if(root.next[w[i]-'a']==null)root.next[w[i]-'a']=new Trie();
        root=root.next[w[i]-'a'];
    }
    root.is_string=true;
}

public boolean search(String word){//查找单词
    Trie root=this;
    char w[]=word.toCharArray();
    for(int i=0;i<w.length;++i){
        if(root.next[w[i]-'a']==null)return false;
        root=root.next[w[i]-'a'];
    }
    return root.is_string;
}

public boolean startsWith(String prefix){//查找前缀
    Trie root=this;
    char p[]=prefix.toCharArray();
    for(int i=0;i<p.length;++i){
        if(root.next[p[i]-'a']==null)return false;
        root=root.next[p[i]-'a'];
    }
    return true;
}
}

27.用O(n)的时间复杂度排序:(1)基数排序(2)桶排序,前者是从位数出发,后者是将每个小区间都提出来建立空间
学到个小技巧,挺重要的
int minVal = Arrays.stream(nums).min().getAsInt();
int maxVal = Arrays.stream(nums).max().getAsInt();

28.二分法寻找峰值
啊!我是废物,我好烦哦,为什么有些规律那么简单但是又那么难想到呢,唉
二分法果然还是得敢用,总是踌躇不前的就更加做不出来,先写着再说,二分法也没多少变种,写着写着就理清了

29.四数之和为零的变种题
我发现我自己真的是个呆逼,这种题目做一个小时?
这种几个数几个数之和的题目用哈希准没错,几个数之和为0的题目说到底还是个查找问题,查找就是某个数的相反数罢了,0换成x也是一样做的

30.基数排序,学习到了这个排序方法的实现,原理简单但是实现起来并不是那么无脑的,有细节的的,需要好好理解代码

long exp = 1;
    int[] buf = new int[n];
    int maxVal = Arrays.stream(nums).max().getAsInt();
    while (maxVal >= exp) {
    	//cnt用来记录当前位数下,每个数集合的最大排名,比如22、13、32、5,那么cnt[5]就是4,而cnt[2]就是2,cnt[3]是3,这个地方顺序不重要
        int[] cnt = new int[10];
        for (int i = 0; i < n; i++) {
            int digit = (nums[i] / (int) exp) % 10;
            cnt[digit]++;
        }
        for (int i = 1; i < 10; i++) {
            cnt[i] += cnt[i - 1];
        }
        //参照每个数的最大排名,往里面填数,这个必须是逆序,因为比如对于三位数的相同十位,个位是有影响的,前面已经排好了,个位数大的放在了前面,这个时候也让前面低位大的先坐高排名,并且排名减一
        for (int i = n - 1; i >= 0; i--) {
            int digit = (nums[i] / (int) exp) % 10;
            buf[cnt[digit] - 1] = nums[i];
            cnt[digit]--;
        }
        //进行复制,因为此时buf已经按低位排完序了,nums保存buf这时候的结果
        System.arraycopy(buf, 0, nums, 0, n);
        exp *= 10;
    }

31.堆排序,对于堆的理解不够深刻导致于这个算法的难以理解,堆就是一个完全二叉树,可以用数组表示,其中的特殊情况又有大根堆、小根堆、或者大顶堆、小顶堆这样子的。虽然是数组,但是要结合树的形象去理解这个算法,那么就不难了

public int findKthLargest(int[] nums, int k) {
    int heapSize = nums.length;
    buildMaxHeap(nums, heapSize);
    for (int i = nums.length - 1; i >= nums.length - k + 1; --i) {
        swap(nums, 0, i);
        --heapSize;
        maxHeapify(nums, 0, heapSize);
    }
    return nums[0];
}

public void buildMaxHeap(int[] a, int heapSize) {
    for (int i = heapSize / 2; i >= 0; --i) {
        maxHeapify(a, i, heapSize);
    } 
}

public void maxHeapify(int[] a, int i, int heapSize) {
    int l = i * 2 + 1, r = i * 2 + 2, largest = i;
    if (l < heapSize && a[l] > a[largest]) {
        largest = l;
    } 
    if (r < heapSize && a[r] > a[largest]) {
        largest = r;
    }
    if (largest != i) {
        swap(a, i, largest);
        maxHeapify(a, largest, heapSize);
    }
}

public void swap(int[] a, int i, int j) {
    int temp = a[i];
    a[i] = a[j];
    a[j] = temp;
}

32.树状数组(低配线段树)

class BIT {
int[] tree;
int n;

public BIT(int n) {
    this.n = n;
    this.tree = new int[n + 1];
}

public static int lowbit(int x) {
    return x & (-x);
}

public void update(int x, int d) {
    while (x <= n) {
        tree[x] += d;
        x += lowbit(x);
    }
}

public int query(int x) {
    int ans = 0;
    while (x != 0) {
        ans += tree[x];
        x -= lowbit(x);
    }
    return ans;
}
}

33.又被单调栈教做人,其实也就是去除k个数字,求最后数字极值的问题,这种问题去双重遍历就太蠢了,弄个单调栈进行存储,直接一边扫描就过了

class Solution {
public int[] mostCompetitive(int[] nums, int k) {

    Stack<Integer> stack = new Stack<>();
    stack.add(-1);
    int len = nums.length;

    for (int i = 0; i < len; i++) {
        //当前元素比队尾元素小,将队尾元素出栈
        //此处需要另外判断数组剩余长度够不够填满栈,不然最后答案长度可能会小于k
        while (nums[i] < stack.peek() && k - stack.size() + 1 < len - i) {
            stack.pop();
        }
        if (stack.size() < k + 1) {
            stack.add(nums[i]);
        }
    }

    int[] ret = new int[k];

    while (k > 0) {
        ret[--k] = stack.pop();
    }

    return ret;
}
}

34.按位异或^,就是这个符号,其次,这里学到一个新的方法,Math.pow(x,n),也就是x的n次方,记下来

35.辗转相除法,用来求最大公约数,别问,记就完事,最大公倍数用大数往上叠加直到为小数的倍数,即为最大公倍数
还有一个重要的性质,就是最大公倍数乘以最小公约数等于两数的乘积,可以反过来求两者

public int gcd(int a, int b) {
    int remainder = a % b;
    while (remainder != 0) {
        a = b;
        b = remainder;
        remainder = a % b;
    }
    return b;
}

36.在矩阵图中走路的问题,直接用数组将每个步表示出来

    int[] dr = new int[]{0, 1, 0, -1};
    int[] dc = new int[]{1, 0, -1, 0};

比如说上面这个样子,难道不比设一堆Boolean变量方便得多?

37.将一个非常大的二进制数转为十进制数再对10e9+7取余
我总是对于大数的问题非常恐惧,因为我总是怕他越界,Integer.MAX_VALUE的大小是2e32-1,十进制下是二十多亿,先掌握大概数值,这样子不会心慌
对于这种问题,如果将高位的数直接释放出来,那么越界是必然的,那么如何是好呢?
这种情况下,就应该自顶向下而不是自底向上,也就是从高位开始计算总的值,不断将自己乘大,并且同时取余,如果还是会越界,那么直接转为long一下,就肯定不会越界了。重中之重在于自顶向下!自顶向下!

38.虽然堆排序还没有完全搞懂,但是不影响我用轮子呀!Java中的大顶堆和小顶堆的使用,记住哦

    A = new PriorityQueue<>(); // 小顶堆,保存较大的一半
    B = new PriorityQueue<>((x, y) -> (y - x)); // 大顶堆,保存较小的一半
	PriorityQueue<Integer> maxheap = new PriorityQueue<Integer>(new Comparator(){
		public int compare(int x, int y){
			//这就是大顶堆;
			return y-x;
		}
	});

39.快速幂算法,快速求解a的b次方,以logn的时间复杂度,而不是n

ll fpow(ll a, ll b, ll mod) {
    ll ans = 1 % mod;
    while(b) {
        if(b & 1)
            ans  = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}

40.大顶堆的创建还有比较器的写法,以及普通的方法

    PriorityQueue<int[]> pq = new PriorityQueue<int[]>(new Comparator<int[]>() {
        public int compare(int[] pair1, int[] pair2) {
            return pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1];
        }
    });
    pq.offer(new int[]{nums[i], i});
    pq.poll();

41.并查集
这个数据结构第一次见,主要用于解决动态连通性一类的问题

private class UnionFind {

    private int[] parent;

    /**
     * 指向的父结点的权值
     */
    private double[] weight;


    public UnionFind(int n) {
        this.parent = new int[n];
        this.weight = new double[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;
            weight[i] = 1.0d;
        }
    }

    public void union(int x, int y, double value) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX == rootY) {
            return;
        }

        parent[rootX] = rootY;
      	// 关系式的推导请见「参考代码」下方的示意图
        weight[rootX] = weight[y] * value / weight[x];
    }

    /**
     * 路径压缩
     *
     * @param x
     * @return 根结点的 id
     */
    public int find(int x) {
        if (x != parent[x]) {
            int origin = parent[x];
            parent[x] = find(parent[x]);
            weight[x] *= weight[origin];
        }
        return parent[x];
    }

    public double isConnected(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX == rootY) {
            return weight[x] / weight[y];
        } else {
            return -1.0d;
        }
    }
}

42.split、isDigit、compareTo的使用

    Arrays.sort(logs, (log1, log2) -> {
        String[] split1 = log1.split(" ", 2);
        String[] split2 = log2.split(" ", 2);
        boolean isDigit1 = Character.isDigit(split1[1].charAt(0));
        boolean isDigit2 = Character.isDigit(split2[1].charAt(0));
        if (!isDigit1 && !isDigit2) {
            int cmp = split1[1].compareTo(split2[1]);
            if (cmp != 0) return cmp;
            return split1[0].compareTo(split2[0]);
        }
        return isDigit1 ? (isDigit2 ? 0 : 1) : -1;
    });

43.hashmap的map.getOrDefault(T key, int default)方法,如果不存在这个键值对,就取默认值,如果存在就取出,这样子不用去判断是否存在这个键值对

mp.put(pre, mp.getOrDefault(pre, 0) + 1);

44.对于一个十进制数,如何计算它二进制下的1的数量?通过x&(x-1),可以将最低的为1的位置改为0,而其他的不变

45.二维数组的严格递增子序列的求解

public int maxEnvelopes(int[][] es) {
    int n = es.length;
    int[] f = new int[n];
    //这个地方可以注意一下,对最重要的变量升序,而对于次变量降序,这样可以保证严格递增,排除主变量相等的情况
    Arrays.sort(es, (e1, e2) -> {
        if(e1[0] == e2[0]) return e2[1] - e1[1];
        return e1[0] - e2[0];
    });
    int res = 0;
    //一个简单的动态规划,其实非常简单,不要慌
    for(int i = 0; i < n; i++){
        f[i] = 1;
        for(int j = 0; j < i; j++){
            if(es[j][1] < es[i][1]){
                f[i] = Math.max(f[i], f[j] + 1);
            }
        }
        res = Math.max(res, f[i]);
    }
    return res;
}

最小生成树:在一个无向图中,遍历所有的节点所形成的树叫做生成树,而所有边权值加起来最小的生成树叫做最小生成树
在一个无向图中生成最小生成树的算法有两种
prim算法:
从一个点作为树节点开始,一直找和树节点相连的最小权值的边,直到遍历到所有的节点,适合稠密图
kruskal算法:
这个算法要使用到并查集,如果有n个节点,直接排序找到权值最小的n-1条边,但是不能形成环,如果加上一条边之后会形成环,那么就跳过这条边

47.逆波兰表达式解决任何计算问题,()优先级为0,±优先级为1,*/优先级为2,核心在于不能让大优先级放在低优先级底下
① 在表达式字符串的末尾加一个代表结束的辅助符,比如”#”。
② 从头开始扫描表达式,并判断当前的每一个字符。
③ 取当前的一个字符,如果当前字符是代表数字,则进逆波兰式的栈,如果是运算符,则转入④,如果是“#“,则结束。
④ 比较当前运算符与临时栈中的栈顶运算符,如果栈顶运算符比当前运算符优先级高,则弹出一个运算符放进逆波兰式栈中,并继续④。否则把当前运算符进临时栈,转入②. 后缀表达式由一系列运算符和运算数组成。

48.如何快速得到一个数字的位数?
将数字转为字符串,求长度即可

int num = 15649849;
int length = (a + "").length();

如何求一个数组中的只出现过一次的数,而其他的数全部出现两次?
答:将所有数组的数全部异或,最后得到的就是结果
如何求一个数组中只出现一次的两个数呢,而其他数全部出现两次?
答:先全部异或一遍,得到的结果找到一个位值位1,说明这个不一样,根据这个不一样将数组分组,在两个组内部分全部异或,得到两个数即为结果

50.morris遍历

morris遍历的实现原则
记作当前节点为cur。
如果cur无左孩子,cur向右移动(cur=cur.right)
如果cur有左孩子,找到cur左子树上最右的节点,记为mostright
如果mostright的right指针指向空,让其指向cur,cur向左移动(cur=cur.left)
如果mostright的right指针指向cur,让其指向空,cur向右移动(cur=cur.right)
实现以上的原则,即实现了morris遍历。

morris遍历的实质
建立一种机制,对于没有左子树的节点只到达一次,对于有左子树的节点会到达两次

class Solution {
    public void recoverTree(TreeNode root) {
        TreeNode x = null, y = null, pred = null, predecessor = null;
        while (root != null) {
            if (root.left != null) {
                // predecessor 节点就是当前 root 节点向左走一步,然后一直向右走至无法走为止
                predecessor = root.left;
                while (predecessor.right != null && predecessor.right != root) {
                    predecessor = predecessor.right;
                }
                // 让 predecessor 的右指针指向 root,继续遍历左子树
                if (predecessor.right == null) {
                    predecessor.right = root;
                    root = root.left;
                }
                // 说明左子树已经访问完了,我们需要断开链接
                else {
                    if (pred != null && root.val < pred.val) {
                        y = root;
                        if (x == null) {
                            x = pred;
                        }
                    }
                    pred = root;
                    predecessor.right = null;
                    root = root.right;
                }
            }
            // 如果没有左孩子,则直接访问右孩子
            else {
                if (pred != null && root.val < pred.val) {
                    y = root;
                    if (x == null) {
                        x = pred;
                    }
                }
                pred = root;
                root = root.right;
            }
        }
        swap(x, y);
    }
    public void swap(TreeNode x, TreeNode y) {
        int tmp = x.val;
        x.val = y.val;
        y.val = tmp;
    }
}

51.区间型 dp 一般用 dp[i][j] ,i 代表左端点,j代表右端点

52.不用加法运算符实现加法
其实是计算机组成里面的内容,但是我不是很懂这个,今天学习了
两者异或得到不进位的结果,相与得到只有结果的进位部分
再把进位与不进位相加,将新的进位结果异或加上去,直到进位结果为0
值得注意的是,每次每位的进位结果都在自己身上,而很明显,结果应该是要放在更高一位的,所以每次计算进位结果之后,都要将进位结果左移

    while (true) {
        lower = a^b;    // 计算低位
        carrier = a&b;  // 计算进位
        if (carrier==0) break;
        a = lower;
        b = carrier<<1;
    }

53.随机数的写法,用random是取不到上界的,所以想要取到上界,就用random.nextInt(max + 1),还是别用Math.random()吧,比较麻烦,直接用random是很方便的,也比较直观

Random random = new Random();
int r= random.nextInt(max + 1);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值