面试总结之算法题
在一个多月的面试过程中,有很多需要讲思路或实现的算法或其他题目,这里总结下作为复习。后面不断更新。
算法题
1. 翻转链表
题目描述:
给你一个链表,使用方法返回一个翻转的链表,如1->2->3->4->5,返回的结果为5->4->3->2->1。
思路
自己的思路:
先使用一个list存储链表的所有节点值,再倒序遍历,拼接一个链表即可。
/**
* 先按自己想法实现一遍
* 存到数组再从后往前遍历
* @param head
* @return
*/
public ListNode ReverseList(ListNode head) {
ArrayList<Integer> list = new ArrayList();
while (head != null) {
list.add(head.val);
head = head.next;
}
System.out.println("list: " + list);
int len = list.size();
ListNode node = new ListNode(list.get(len - 1));
for (int i = len - 2; i >= 0; i --) {
// 再循环遍历出node的最后一个节点,并拼接进去
ListNode tmp = node;
while (true) {
if (tmp.next == null) {
tmp.next = new ListNode(list.get(i));
break;
}
tmp = tmp.next;
}
}
return node;
}
其他思路:
如何让后一个节点指向前一个节点!在下面的代码中定义了一个 next 节点,该节点主要是保存要反转到头的那个节点,防止链表 “断裂”。
// 反转链表
public ListNode ReverseList(ListNode head) {
ListNode next = null;
ListNode pre = null;
while (head != null) {
// 保存要反转到头的那个节点
next = head.next;
// 要反转的那个节点指向已经反转的上一个节点(备注:第一次反转的时候会指向null)
head.next = pre;
// 上一个已经反转到头部的节点
pre = head;
// 一直向链表尾走
head = next;
}
return pre;
}
测试:
public static void main(String[] args) {
ListNode a = new ListNode(1);
ListNode b = new ListNode(2);
ListNode c = new ListNode(3);
ListNode d = new ListNode(4);
ListNode e = new ListNode(5);
a.next = b;
b.next = c;
c.next = d;
d.next = e;
ListNode pre = new Demo().ReverseList(a);
//
// while (e != null) {
// System.out.println(e.val);
// e = e.next;
// }
// ListNode{val=5, next=ListNode{val=4, next=ListNode{val=3, next=ListNode{val=2, next=ListNode{val=1, next=null}}}}}
System.out.println(pre);
}
2. 解析字符串中数学表达式
题目描述:
给你String,类似于“1 + 2 * (3 - 4 * 2) / 4” 这样的字符串,你怎么输出最后结果,说说你的思路。
思路
先将这个后缀表达式利用栈将其转化为后缀表达式(不包含括号):
从左到右遍历中缀表达式,遇到操作数,输出,遇到操作符,当前操作符的优先级大于栈顶操作符优先级,进栈,否则,弹出栈顶优先级大于等于当前操作符的操作符,当前操作符进栈。
这里还说下后缀表达式的好处,其中不包含括号,运算符是放在两个操作数之后,不用考虑运算符的优先级了。
接下来从左到右遍历后缀表达式,如果遇到操作数,则将其压入栈,遇到运算符,则将栈顶两个元素计算完后压入栈即可,直到遍历完后,栈中的最后数据即为结果。
3. 阻塞队列实现
题目描述:
利用Java编写一个阻塞队列,你的思路是怎么样的?
思路
生产者消费者模型就是当队列为空时消费者停止消费,当队列满时生产者停止生产。利用wait 和 notify 协调生产者线程和消费者线程的关系,再加上一个数组作为队列的容器,生产者的偏移量以及消费者的偏移量就可以完成一个简单的消费者生产者模型。
阻塞队列如下,使用的是ArrayList作为容器:
package top.hellolsy.offer;
import java.util.ArrayList;
/**
* 自己动手实现一个有界阻塞队列
* 可以使用synchronize及wait notify
* 也可以使用Lock的condition
*
* 这里使用的是Arraylist作为阻塞队列
*
*
* 生产者消费者模型就是当队列为空时消费者停止消费,当队列满时生产者停止生产。利用wait 和 notify 协调生产者线程和消费者线程的关系,
* 再加上一个数组作为队列的容器,生产者的偏移量以及消费者的偏移量就可以完成一个简单的消费者生产者模型。
* 阻塞队列原理就是如此
*
*/
public class MyArrayBlockQuene {
/**
* 队列容器
*/
private ArrayList<Integer> container = new ArrayList<>();
/**
* 元素个数
* 且使用volatile保证可见性
*/
private volatile int size;
/**
* 容量
* 且使用volatile保证可见性
*/
private volatile int capacity;
/**
* 锁的对象
*/
private Object lock = new Object();
/**
* 传入容量
* @param capacity
*/
public MyArrayBlockQuene(int capacity) {
this.capacity = capacity;
}
/**
* 生产者生产
* @param data
*/
public void put(int data) {
synchronized (lock) {
// 队列满了
while (size == capacity) {
try {
System.out.println("队列已经满了,需要等消费者消费...");
// 这里等待同时会释放锁
// wait() 方法调用后会使得当前所在的线程暂停运行
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 队列没满
System.out.println("生产了:" + data);
container.add(data);
size ++;
// 通知可以取数据
lock.notifyAll();
}
}
/**
* 消费者消费
* @return 返回消费的数据
*/
public int take() {
int result = 0;
synchronized (lock) {
// 队列空了
while (size == 0) {
try {
System.out.println("队列已经空了,需要等生产者生产...");
// 这里等待同时会释放锁
// wait() 方法调用后会使得当前所在的线程暂停运行
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 队列没满 序号是从0开始,所以需要size - 1
result = container.remove(size - 1);
size --;
// 通知可以生产数据
lock.notifyAll();
}
return result;
}
}
测试类:
package top.hellolsy.offer;
public class MyBlockQueueTest {
public static void main(String[] args) {
// 队列容量为5
MyArrayBlockQuene queue = new MyArrayBlockQuene(5);
Thread put_thread = new Thread(() -> {
for (int i = 0; i < 40; i ++) {
queue.put(i + 1);
try {
Thread.sleep(500);
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
Thread take_thread = new Thread(() -> {
for(;;){
System.out.println("消费者开始工作,消费:" + queue.take());
try {
Thread.sleep(800);
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
put_thread.start();
take_thread.start();
}
}
运行结果:
另外,还有可以使用lock中的Condition实现,由于很少使用lock,我按照我熟悉的synchronized 的等待,通知机制实现的。
4. 排序查找问题
这里包含了常见的选择、冒泡、插入、快速排序及二分查找。
package top.hellolsy.offer.others;
import java.util.Arrays;
/**
* 手写排序还有查找的几个常见方法
*
* 稳定排序:冒泡 插入 归并 基数
* 不稳定:选择 快速 希尔 堆
*/
public class Sort {
public static void main(String[] args) {
int[] arr = new int[]{6,4,2,8,6,0,9,1};
int[] arr1 = new int[]{12,45,23,67,7,1,5,21};
int[] arr2 = new int[]{12,45,23,7,7,1,5,21};
int[] arr3 = new int[]{12,45,1,67,67,23,5,21};
System.out.println("未排序之前arr : " + Arrays.toString(arr));
System.out.println("未排序之前arr1 : " + Arrays.toString(arr1));
System.out.println("未排序之前arr2 : " + Arrays.toString(arr2));
System.out.println("未排序之前arr3 : " + Arrays.toString(arr3));
// select_sort(arr);
// bubble_sort(arr);
// insert_sort(arr);
quick_sort(arr,0, arr.length - 1);
quick_sort(arr1,0, arr1.length - 1);
quick_sort(arr2,0, arr2.length - 1);
quick_sort(arr3,0, arr3.length - 1);
System.out.println("排序之后arr : " + Arrays.toString(arr));
System.out.println("排序之后arr1 : " + Arrays.toString(arr1));
System.out.println("排序之后arr2 : " + Arrays.toString(arr2));
System.out.println("排序之后arr3 : " + Arrays.toString(arr3));
int result = binary_search(arr3, 67);
System.out.println(result);
}
/**
* 选择排序
* 每次选择最小的放在前面
* @param arr
*/
public static void select_sort(int[] arr) {
int len = arr.length;
int min = 0;
for (int i = 0; i < len; i ++) {
min = i;
for (int j = i + 1; j < len; j ++) {
if (arr[j] < arr[min]) {
min = j;
}
}
// 交换
int tmp = arr[min];
arr[min] = arr[i];
arr[i] = tmp;
}
}
/**
* 冒泡
* 两两比较
* 每一趟选出一个最大值到后面
* @param arr
*/
public static void bubble_sort(int[] arr) {
int len = arr.length;
// 冒泡趟数
for (int i = 0; i < len - 1; i ++) {
// 也是从0开始
for (int j = 0; j < len - i - 1; j ++) {
if (arr[j + 1] < arr[j]) {
// 交换
int tmp = arr[j + 1];
arr[j + 1] = arr[j];
arr[j] = tmp;
}
}
}
}
/**
* 插入排序
* 假设数组前部分是已经排序好的
* @param arr
*/
public static void insert_sort(int[] arr) {
int len = arr.length;
// 从第二个元素开始,因为默认第一个元素已排序
for (int i = 1; i < len; i ++) {
// 一步步与已排序好的数组的最后一个元素进行比较
// 注意 是arr[j]与arr[j - 1]进行比较
for (int j = i; j > 0; j --) {
if (arr[j] < arr[j - 1]) {
// 交换
int tmp = arr[j];
arr[j] = arr[j - 1];
arr[j - 1] = tmp;
}
}
}
}
/**
* 快排
* 最关键在于i < j
* @param arr
* @param start
* @param end
*/
public static void quick_sort(int[] arr, int start, int end) {
// 先判断
if (start > end) {
return;
}
// 基准数
int key = arr[start];
int i = start;
int j = end;
while (i < j) {
// 这里注意都是大于等于小于等于 还有都判断是否i < j
// 判断i < j 是防止i 移动到j 的后面 或者j 移动到i前面 最多能移动到相邻
while (arr[j] >= key && i < j) {
j --;
}
while (arr[i] <= key && i < j) {
i ++;
}
// 减少相等时的交换
// 在交换前无论如何arr[j]基本小于key,除了在i和j的区间已经找不到比key小的值了(这个时候交换好像就不行了?)
if (i < j && arr[i] != arr[j]) {
// 交换i和j
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
// 交换key与i值
arr[start] = arr[i];
arr[i] = key;
// 递归之前将数组根据基准值key分为两部分,左边小于基准值,右边大于
// 递归调用
quick_sort(arr, start, j - 1);
quick_sort(arr, j + 1, end);
}
/**
* 二分查找
* @param arr 已排序的数组
* @param pos 要查找的数字
* @return
*/
public static int binary_search(int[] arr, int pos) {
// 先检查是否越界
if(pos<arr[0] || pos>arr[arr.length-1]){
return -1;
}
int begin = 0;
int end = arr.length - 1;
int mid = 0;
while (begin <= end) {
mid = (begin + end) / 2;
if (pos > arr[mid]) {
begin = mid + 1;
} else if (pos < arr[mid]) {
end = mid - 1;
} else {
return mid;
}
}
// 默认返回-1即没找到
return -1;
}
}
设计模式类型
抽象工厂
静态、动态代理手写
SQL编写
本文地址:https://blog.csdn.net/CodingNO1/article/details/107365282
上一篇: 微软Cortana发布iOS和Android版 仅中美两国可用
下一篇: 关于大数据的五大见解
推荐阅读