ps:博主非科班,今天是2021.3.30,结束春招了。
可能以后不会有多少考察手撕代码的机会了,但是坚持刷一下或许依然有用,所以每天刷一点,然后记录总结。
【逆向】Java笔试漏洞
【问】基本数据类型?
大的往小的转叫做强制转换。
【问】字符:
char是基本数据类型,而Character是一个类。
-
Character.isLowerCase() //判断是否为小写字符
-
Character.isUpperCase() //判断是否为大写字符
-
Character.isLetterOrDigit() //判定是否为字母或数字
-
Character.toLowerCase() //将字符转换为小写,输入单个字符,有的话返回小写, 没小写返回本身。
【问】泛字符串:
字符串>字符,字符是基本数据类型,字符串不是。
1、String类(s是一个String对象)
(final修饰,不可变;更改s会生成新的实例对象,在堆中产生垃圾)
-
s.length() //s字符串的长度
-
+ //将两个字符串连接,尽量不要用
-
s.charAt(i) //i索引处的字符
-
s.subString(i,j) //子串
-
s.toCharArray() //把字符串变成char构成的数组(char代表一个字母,String代表字符串) ;
//遍历字符串字符时,先用toCharArray转换为数组,然后可以用增强for循环
-
s.indexOf(ch) //第一次出现ch的索引
-
s.lastIndexOf(ch) //最后一次出现ch的索引
-
s.contains(ss) //是否包含字符串ss
2、StringBuilder类(sb是一个StringBuilder对象)
(可变,适合更改;更改sb,不会产生垃圾)
-
sb.append(ss) //增
-
sb.delete(start,end) //删
-
sb.insert(i) //在i位置之前插入字符
-
sb.setCharAt(i,ch) //把i位置改成ch
-
sb.charAt(i) //查询,与String一样
-
sb.reverse() //反转
String和StringBuilder互相转换:
- StringBuilder sb=new StringBuilder(s) //用String的内容初始化一个StringBuilder
- sb.toString() //StringBuilder回归String类型
3、StringBuffer类(sbf是一个StringBuffer对象)
线程安全的,加了Synchronized;用法和StringBuilder差不多。
【问】数组、ArrayList:
观察题目,做结果则用数组,中间量则用List。
1、数组 int[]
- int[] arr={1,2,3}; //初始化方法1
- int[] arr=new int[n]; //初始化方法2
- arr[i] //得到i索引处的值
- arr[i]=a; //把i处索引的值改为a
Arrays工具类操作数组:
- Arrays.copyOfRange(arr, i, j); //子数组
- Arrays.sort(arr) //从小到大排序数组(只能是数组,对列表操作则必须把列表变成数组);
- Arrays.equals(arr1,arr2); //比较两个数组内容是否一样(数组不能直接equals,没有重写,相当于“==”)
- Arrays.toString(arr) ; //打印静态数组之前先转换为字符串,静态数组不能直接打印出来
- Arrays.fill(arr,-1); //用-1填充arr数组的每个位置
2、ArrayList
构建:
- List<String> list = new ArrayList<String>(){{add("1"); add("2");}}; //带参初始化,后面双大括号接添加语句
- List<List<String>> res = new ArrayList<String>(); //构建二维列表,后面无具体泛型,也没有二维
方法:
- list.subList(i,j) //子列表
- list.add(e) //在集合末尾新增一个元素
- list.add(i, e) //在指定位置添加元素
- addAll(集合) //把集合的元素加到末尾
- list.remove(i) //删除指定位置的元素,必须要传入参数i
- list.indexOf(e) //第一个该元素的索引
- list.get(i) //获取指定位置的元素
- list.set(int index, E element) //设置指定位置的元素值
- list.size() //返回长度,s、sb、arr都是length,list是size。
- list.lastIndexOf(e) //最后一个该元素的索引
3、数组和ArrayList互转
(1)数组转ArrayList:
ArrayList<Integer> list = new ArrayList(Arrays.asList(arr)); //找索引的时候可以用,或者直接遍历找吧
(2)ArrayList转数组:
Object[] arr=list.toArray(); //会把元素类型变为Object
【问】链表节点类:
public class ListNode {
int val;
ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
【问】队列、栈(左边是头,右边是尾)
核心方法:push()、pop()、addLast()、removeLast()、peek()、peekLast()
//队列,头进尾出
Deque<> queue=new LinkedList<>();
//栈,头进头出
Deque<> stack=new LinkedList<>();
【问】堆(优先队列):
(1)tok问题:
K个最大,则建立小顶堆,堆顶为“入门券”:先用K个元素建立小顶堆;剩余N-K个元素与堆顶相比,比堆顶大则poll堆顶,add此元素。最后的优先队列就是所求。
(2)构造优先队列(PriorityQueue):
//优先队列默认为升序(小顶堆)
PriorityQueue<> pq = new PriorityQueue<>(
//降序加上Collections.reverseOrder()
)
- pq.add(E e); //在队尾添加元素,并调整堆结构
- pq.remove(); //在队头删除元素,并返回,再调整堆结构
- pq.element(); //返回队头元素(不删除)
- pq.isEmpty(); //判断队列是否为空
- pq.size(); //获取队列中元素个数
- pq.contains(Object o); //判断队列中是否包含指定元素(从队头到队尾遍历)
【问】哈希表HahMap
(1)创建哈希表并初始化:
a、一次性初始化
Map<Integer,Integer> map=new HashMap<Integer, Integer>(){{
put(1,1);
put(2,2);
}};
b、后加入键值对(HashMap不可能有重复的键)
Map<Integer,Integer> map=new HashMap<Integer, Integer>();
map.put(1,1);
map.put(2,2);
map.put(2,3); //有此键的话改值
(2)相关方法:
- get() //获取键对应的值
- put() //加入键值对或者更改值
- entrySet() ,entry.getKey(),entry.getValue() //返回entry组成的Set;Set也是集合,也可以遍历
- keySet() //key组成的Set
- values() //返回value集合,可以重复
- getOrDefault(),containsKey(),containsValue(),isEmpty(),putAll(),remove(),size()
(3)遍历HashMap,一般就用遍历entry:
遍历entry:
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
System.out.println("Key = " + entry.getKey() + ", Value = " + entry.getValue());
}
遍历键key或值value:
// 迭代键
for (Integer key : map.keySet()) {
System.out.println("Key = " + key);
}
// 迭代值
for (Integer value : map.values()) {
System.out.println("Value = " + value);
}
【问】树的节点类:
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {}
TreeNode(int val) { this.val = val; }
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
【问】java笔试输入、输出:
牛客这个网址可以练习一下:牛客竞赛_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞赛OJ
1、单组:
输入输出写在Main类里的main方法里面,类中可以加上自己写的辅助方法(赛码网样例):
public class Main {
public static void main(String args[]) {
Scanner sc = new Scanner(System.in) ; //创建一个输入类
int n = sc.nextInt(); //以空格或换行符为间隔,返回一个int
String s = sc.next(); //以空格或换行符为间隔,返回一个char
String s = sc.nextLine(); //只以换行符为间隔,返回一个char
//sc.hasNext(),判断是否有下一个输入
//while(sc.hasNext()){...},一直保持输入;使用情形:不知道组数,输入一组,输出一次。
System.out.print(); //不换行输出
System.out.println(); //换行输出
}
}
2、多组:
知道组数用for,不知道组数用while(sc.hasNext());
//不确定组数
while(sc.hasNext()){
}
//确定组数
m=sc.nextInt();
for(int i=0;i<m;i++){
}
3、读取字符串“1,2,3,4”,转换得到整数组成的数组:
split(",");//拆分得到字符串组成的数组:
replace("旧符号","新符号");
Scanner sc = new Scanner(System.in);
//返回字符串元素的数组
String[] line = sc.nextLine().split(",");
//得到整数组成的数组
int n = line.length;
int[] intArr = new int[n];
for(int i=0;i<n;i++){
int ii=Integer.valueOf(intArr[i]);
intArr[i]=ii;
}
4、输入数组
(1)一维数组
先创建制定大小的空数组,然后给指定位置赋值。
Scanner sc = new Scanner(System.in) ;
while(sc.hasNext()){
int m = sc.nextInt() ;
int[] arr = new int[m] ;
for(int i=0;i<m;i++){
arr[i] = sc.nextInt();
}
System.out.println(Arrays.toString(arr));
}
(2)输入一个二维数组(矩阵)
先创建指定大小的二维数组,然后给指定位置赋值。
Scanner sc = new Scanner(System.in);
while(sc.hasNext()){
int m = sc.nextInt();
int n = sc.nextInt() ;
int [][] arr = new int[m][n] ;
for (int i=0 ; i<m ; i++) {
for (int j = 0; j < n; j++) {
arr[i][j] = sc.nextInt();
}
}
//System.out.println(Arrays.toString(arr[0]));
}
【问】进制转换:
(1)16进制String-->>10进制int
String s="A";
int m= Integer.valueOf(s,16)
(2)10进制int-->>16进制String
int a=11;
//长度为1的十六进制字符串
String aa=String.format("%01X",a);
String.format:
【问】二分查找:
//查找:在arr中找到key。
//不用递归更加舒服:
public static int binarySearch(int[] arr,int key){
int low=0;
int high=arr.length-1;
if(key < arr[low] || key > arr[high] || low > high){
return -1;
}
int middle = 0;
//移动low和high,夹出key的索引,low和high相撞才能退出循环
while(low <= high){
middle = (low + high) / 2;
if(arr[middle] > key){
high = middle - 1;
}else if(arr[middle] < key){
low = middle + 1;
}else{
return middle;
}
}
return -1; //最后仍然没有找到,则返回-1
}
【问】排序:
稳定排序:排序后,相等元素的位置顺序保持不变;
不稳定排序:排序后,相等元素的位置发生变化。
1、稳定排序算法:
a、归并排序(nlogn)
public class MergeSort {
//递归,所以要把边界作为参数传入
public static void mergeSort(int[] arr, int start, int end) {
//当子序列中只有一个元素时结束递归
if(start>end){return;}
//归并排序
if (start < end) {
//划分子序列
int mid = (start + end) / 2;
//对左侧子序列进行递归排序
mergeSort(arr, start, mid);
//对右侧子序列进行递归排序
mergeSort(arr, mid + 1, end);
//合并,三个参数
merge(arr, start, mid, end);
}
}
//合并
public static void merge(int[] arr, int left, int mid, int right) {
//存放数组
int[] tmp = new int[arr.length];
//p1、p2是比较指针,k是存放指针
//把小的存入tmp
int p1 = left, p2 = mid + 1, k = left;
while (p1 <= mid && p2 <= right) {
if (arr[p1] <= arr[p2]) {
tmp[k++] = arr[p1++];
} else {
tmp[k++] = arr[p2++];
}
}
//如果第一个序列未检测完,直接将后面所有元素加到合并的序列中
while (p1 <= mid) tmp[k++] = arr[p1++];
//同上
while (p2 <= right) tmp[k++] = arr[p2++];
//复制回原素组
for (int i = left; i <= right; i++)
arr[i] = tmp[i];
}
public static void main(String[] args) {
int[] a = {49, 38, 65, 97, 76, 13, 27, 50};
mergeSort(a, 0, a.length - 1);
System.out.println("排好序的数组:");
for (int e : a) {
System.out.print(e + " ");
}
}
}
b、冒泡排序(n2)
public static int[] bubbleSort(int[] arr){
int n=arr.length;
//冒泡,把大的冒泡到j的位置;相邻交换实现冒泡
for(int j=n-1;j>=1;j--){
for(int i=0;i<j;i++){
if(arr[i]>arr[i+1]){
int temp=arr[i];
arr[i]=arr[i+1];
arr[i+1]=temp;
}
}
}
return arr;
}
c、插入排序(n2)
d、基数排序
2、不稳定排序算法:
a、快速排序(nlogn)
//快排要做递归,所以要把两个边界start、end作为参数
public static int[] quickSort(int[] arr, int start, int end) {
if (start >= end) {
return arr;
}
//一个定值mid,比mid大的放右边,比mid小的放左边
int mid = arr[left];
//两个指针
int left = start;
int right = end;
//让left和right相遇
while (left < right) {
//右边比mid大直接--,小则换
while (left < right && mid <= arr[right]) {
right--;
}
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
//左边比mid小直接++,大则换
while (left < right && mid >= arr[left]) {
left++;
}
temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
}
//递归
quickSort(arr, start, left);
quickSort(arr, left + 1, end);
return arr;
}
b、堆排序(nlogn)
public static int[] dui_sort(int[] arr){
//默认为小顶堆
PriorityQueue<Integer> dui = new PriorityQueue<>(
//Collections.reverseOrder()
);
for(int i=0;i<arr.length;i++){
dui.add(arr[i]);
}
for(int i=0;i<arr.length;i++){
arr[i]=dui.poll();
}
return arr;
}
c、选择排序(n2)
d、希尔排序
【问】树的遍历(非递归)-----前=DFS、中、后、BFS:
遍历:把所有节点按照某种顺序全部输出。
笔试直接用递归,面试可以装逼写一下非递归。
//树的节点类
class Node{
int val=0;
Node left=null;
Node right=null;
Node() {}
Node(int val) { this.val = val; }
Node(int val, Node left, Node right) {
this.val = val;
this.left = left;
this.right = right;
}
}
public class TreeSearch {
//前序遍历(while栈不为空{加第一个启动;取出输出temp;加右左})
public static void preOrder(Node node) {
System.out.println("前序遍历:");
if(node==null){
return;
}
Deque<Node> stack=new LinkedList<>();
stack.push(node);
while(!stack.isEmpty()){
Node temp=stack.pop();
System.out.println(temp.val);
if(temp.right!=null){
stack.push(temp.right);
}
if(temp.left!=null){
stack.push(temp.left);
}
}
}
//中序遍历(while栈不为空||节点均不为空{1.入栈:加根一直到最左边(node自更新);2.取出输出;3.更新:往右边})
public static void midOrder(Node node) {
System.out.println("中序遍历:");
if(node==null){
return;
}
Deque<Node> stack=new LinkedList<>();
while(!stack.isEmpty() || node!=null){
while(node!=null){
stack.push(node);
node=node.left;
}
node=stack.pop();
System.out.println(node.val);
node=node.right;
}
}
//后序遍历(while栈不为空||节点不为空{1.入栈:加根有左加左无左加右启动;2.取出输出;3.若栈非空且栈顶为根则更新到右,否则更新为空})
public static void backOrder(Node node) {
System.out.println("后序遍历:");
if(node==null){
return;
}
Deque<Node> stack=new LinkedList<>();
while(!stack.isEmpty() || node!=null){
while(node!=null){
stack.push(node);
if(node.left!=null){
node=node.left;
}else{
node=node.right;
}
}
node=stack.pop();
System.out.println(node.val);
if(!stack.isEmpty() && stack.peek().left==node){
node=stack.peek().right;
}else{
node=null;
}
}
}
public static void bfs(Node root){
Queue<Node> queue = new LinkedList<Node>();
if(root==null){
return ;
}
queue.add(root);
while (!queue.isEmpty()){
Node t = queue.remove();
System.out.println(t.val);
if(t.left!=null){
queue.add(t.left);
}
if(t.right!=null){
queue.add(t.right);
}
}
return;
}
public static void main(String[] args) {
Node node2=new Node(2);
Node node3=new Node(3);
Node node1=new Node(1,node2,node3);
preOrder(node1);
midOrder(node1);
backOrder(node1);
System.out.println("BFS:");
bfs(node1);
}
}
【问】异常:
1、try{}catch(e){}finally{}; 捕获异常并处理
2、throw 抛出异常不处理,语句使用
3、throws 抛出异常不处理,方法使用
【问】用哈希表统计数组nums中各元素出现的次数:
map.put(num,map.getOrDefault(num,0)+1);
//构建次数记录的哈希表
Map<Integer,Integer> map=new HashMap<Integer, Integer>();
//for遍历所有元素
for(int num:nums){
map.put(num,map.getOrDefault(num,0)+1);
}
【问】面向对象程序设计:
面向对象进行程序设计时时候,要划分明确的类(按照物品类别,如扑克、人等,然后划分出明确的属性和方法),不要在Main函数里搞出不知道来源的变量。
【问】比较器Comparator:
// 1、一维数组从大到小排序:
Integer[] arr1= new Integer[]{1,3,2};
Arrays.sort(arr1,new Comparator<Integer>() {
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
System.out.println("1、一维数组从大到小排序:");
System.out.println(Arrays.toString(arr1));
// 2、二维数组按照每一行第二个数值大小从小到大排序:
int[][] arr2 = {{10, 16}, {2, 8}, {100, 6}, {6, 12}};
Arrays.sort(arr2, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[1]-o2[1];
}
});
System.out.println("2、二维数组按照每一行第二个数值大小从小到大排序:");
for(int[] arr:arr2){
System.out.println(Arrays.toString(arr));
}
【问】位运算
位运算符 | 说明 |
---|---|
& | 与:对两个整型操作数中对应位执行布尔代数,两个位都为1时输出1,否则0 |
| | 或:对两个整型操作数中对应位执行布尔代数,两个位中只要有一个为1就输出1,否则为0 |
^ | 异或:对两个整型操作数中对应位执行布尔代数,两个位相等则为0,不相等则为1 |
~ | 非:按位取反运算符翻转操作数的每一位,即0变成1,1变成0 |
>> | 右移:符号左侧数值 按位右移 符号右侧数值指定的位数,若为正数则高位补0,若为负数则高位补1 |
<< | 左移:符号左侧数值 按位左移 符号右侧数值指定的位数,并在低位处补0 |
【问】动态规划经典-----背包问题。
100是背包总容量,5是物品种类数量。
以下5行是每一个物品消耗的空间与价值。
输出可以取得的最大物品价值。
输入:
100
5
77 92
22 22
29 36
50 46
99 90
输出:
114
public class Main {
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
// 总成本V 和 物品总数N
int V = sc.nextInt();
int N = sc.nextInt();
// 消耗数组和产出数组
int[] cost = new int[N + 1] ;
int[] value = new int[N + 1] ;
for (int i=1 ; i <= N ; i++){
cost[i] = sc.nextInt();
value[i] = sc.nextInt();
}
// 要获取dp[N][V],必须+1
// dp[i][j]代表i个物品、V元钱能买到的最大价值
int[][] dp = new int[N+1][V+1];
dp[0][0] = 0;
// 动态方程
for(int i = 1; i <= N; i++){
for(int j = 0; j <= V; j++){
// 1.空间够(可以取或不取)
if(j >= cost[i]){
// 不取或取一个的较大值
dp[i][j] = Math.max(dp[i-1][j]/*不取*/, dp[i][j-cost[i]] + value[i]/*取一个*/);
}
// 2.不够取(只能不取)
else{
dp[i][j] = dp[i-1][j];
}
}
}
System.out.println(dp[N][V]);
}
}
【问】单例模式
双重检验锁+懒汉。
加了volatile之后,就保证new
不会被指令重排序。
//私有构造+成员单例变量,公共方法里new对象
public class LazyMan {
private LazyMan() {
}
private static volatile LazyMan lazyman;
public static LazyMan getInstance() {
if (lazyman == null) {
synchronized (LazyMan.class) {
if (lazyman == null) {
LazyMan lazyMan = new LazyMan();
}
}
}
return lazyman;
}
}
【问】三个线程轮换打印出“ABCABCABC”:
(1)synchronized +wait()+notifyAll() +value 来控制排队:
public class SynchronizedPrintABC {
private int value = 1;
synchronized void printA() throws InterruptedException {
while (value != 1) {
wait();
}
System.out.println(Thread.currentThread().getName() + ":A");
value = 2;
notifyAll();
}
synchronized void printB() throws InterruptedException {
while (value != 2) {
wait();
}
System.out.println(Thread.currentThread().getName() + ":B");
value = 3;
notifyAll();
}
synchronized void printC() throws InterruptedException {
while (value != 3) {
wait();
}
System.out.println(Thread.currentThread().getName() + ":C");
value = 1;
notifyAll();
}
public static void main(String[] args) {
SynchronizedPrintABC printABC = new SynchronizedPrintABC();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
printABC.printA();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "线程1").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
printABC.printB();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "线程2").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
printABC.printC();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "线程3").start();
}
}
(2)用单线程线程池来做(这个不太好,严格意义并不算三个线程):
public class SingleThreadExecutorPrint {
public static void main(String[] args) throws InterruptedException {
//用单线程线程池来实现 ,3个线程加入线程池
ExecutorService pool = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
pool.submit(() -> System.out.print("AAA"));
pool.submit(() -> System.out.print("BBB"));
pool.submit(() -> System.out.print("CCC"));
}
pool.shutdown();
}
}
(3)volatile 控制flag在并发环境中可见
(4)AtomicInteger 保证原子操作
(5)Lock ReentrantLock Condition(await(),signalAll()) 控制
(6)信号量 semaphore(.acquire() .release())
【问】数据流的中位数?
public class Solution {
//维护一个大顶堆+小顶堆+个数count
int count = 0;
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
//反着减就是大顶堆
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
public void Insert(Integer num) {
//小顶堆,从小到大;大顶堆,从大到小
//count为偶数时,先插入小顶堆,再推入大顶堆;count为奇数时,先推入大顶堆,再推入小顶堆
if ((count & 0x1) == 0) {
minHeap.add(num);
maxHeap.add(minHeap.poll());
} else {
maxHeap.add(num);
minHeap.add(maxHeap.poll());
}
count++;
}
public Double GetMedian() {
//count为奇数时,取大顶堆顶;否则取大小顶堆堆顶均值
if ((count & 0x1) == 1) {
return (double) maxHeap.peek();
} else {
return (maxHeap.peek() + minHeap.peek()) / 2.0;
}
}
}
【问】将B数组融合到A数组?(归并排序的归并部分即可)
class Solution {
public void merge(int[] A, int m, int[] B, int n) {
// 先确保将其中一个数组中的数字遍历完
while (m > 0 && n > 0) {
// 对比选出较大的数放在 m + n - 1 的位置,并将选出此数的指针向前移动
A[m + n - 1] = A[m - 1] > B[n - 1] ? A[--m] : B[--n];
}
// 1.如果 m 不为 0,则 A 没遍历完,都已经在 A 中不用再管
// 2.如果 n 不为 0,则 B 没遍历完,直接全移到 A 中相同的位置
while (n > 0) {
A[n - 1] = B[n - 1];
n--;
}
}
}