记录本人开始阶段准备刷题的过程
2022.02.20
0.复杂度(最坏情况下)
时间复杂度:数据操作的总次数(取最高次幂),同次幂需实际跑代码比较
空间复杂度:所需额外空间的总数。
1.位运算符
// 在排序算法的swap中,^异或不用临时变量
// 位运算比算数运算快
public static void swap(int[] arr,int i,int j){
if(i!=j){
//不能两个值指向同一地址
arr[i]=arr[i]^arr[j];
arr[j]=arr[i]^arr[j];//就是arr[i]^arr[j]^arr[j]就表示a
arr[i]=arr[i]^arr[j];//表示arr[i]^arr[j]^arr[i]^arr[j]^arr[j]就是b
}
}
| 符号 | 描述 | 运算规则 |
|---|---|---|
| & | 与 | 两个位都为1时,结果才为1 |
| | | 或 | 两个位都为0时,结果才为0 |
| ^ | 异或 | 两个位相同为0,相异为1。可理解为不进位相加。满足交换律结合律 |
| ~ | 取反 | 0变1,1变0 |
| << | 左移 | 各二进位全部左移若干位,高位丢弃,低位补0。左移是变大->相当于1位*2 |
| >> | 右移 | 各二进位全部右移若干位,对无符号数,高位补0,有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移)。右移是变小->相当于1位/2 |
2.取出一个数最右边1的位置
int mostRightOne = pos & (~pos + 1);
// mostRightOne值在二进制位上的位次就是pos得最右第一个1的位置(源码&补码)
3.对数器
对数器的概念和使用 1,有一个你想要测的方法a 2,实现复杂度不好但是容易实现的方法b 3,实现一个随机样本产生器 4,把方法a和方法b跑相同的随机样本,看看得到的结果是否一样。 5,如果有一个随机样本使得比对结果不一致,打印样本进行人工干预,改对方法a或者 方法b 6,当样本数量很多时比对测试依然正确,可以确定方法a已经正确。
好处:
- 验证方法对不对
- 可以很快找到错误case(几千几万case中)
- 判断贪心对不对,具体实现(例如测试冒泡排序方法是否正确):
- 想要测试冒泡排序方法a(判断该方法是否正确):
import java.util.Arrays;
public class Code03_InsertionSort {
public static void insertionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 1; i < arr.length; i++) {
for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
swap(arr, j, j + 1);
}
}
}
public static void swap(int[] arr, int i, int j) {
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
// 对数器: for test
public static void comparator(int[] arr) {
Arrays.sort(arr);
}
// 生成指定长度随机数组,for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
//Math.random() ->[0,1)所有的小数,等概率返回一个
// Math.random()*N ->[0,N-1)所有的小数,等概率返回一个
//(int)(Math.random()*N) ->[0,N-1]所有的整数,等概率返回一个
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
return arr;
}
// 深拷贝,for test
public static int[] copyArray(int[] arr) {
if (arr == null) {
return null;
}
int[] res = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
res[i] = arr[i];
}
return res;
}
// 判定,for test
public static boolean isEqual(int[] arr1, int[] arr2) {
if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
return false;
}
if (arr1 == null && arr2 == null) {
return true;
}
if (arr1.length != arr2.length) {
return false;
}
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
// for test
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// for test
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 100;
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int[] arr1 = generateRandomArray(maxSize, maxValue);//生成随机数组
int[] arr2 = copyArray(arr1);
insertionSort(arr1);
comparator(arr2);
if (!isEqual(arr1, arr2)) {
succeed = false;
break;
}
}
System.out.println(succeed ? "Nice!" : "Fucking fucked!");
int[] arr = generateRandomArray(maxSize, maxValue);
printArray(arr);
insertionSort(arr);
printArray(arr);
}
}
4.选择排序
时间复杂度O(N^2), 不稳定的排序
- 在0~~~N-1范围内找一个最小的值,放到0位置;
- 在1~~~N-1范围内找一个最小的值,放到1位置;
- 一直重复到结束; (每次过一遍范围都找到最小的,然后放到范围内的首位置)
public static void selectionSort(int[] arr) { //范围每次缩小1,从前往后缩
if (arr == null || arr.length < 2)
return;
for (int i = 0; i < arr.length - 1; i++) {
int minIndex = i;//找范围内最小值,最开始默认是第一个
for (int j = i + 1; j < arr.length; j++) {
minIndex = arr[j] > arr[minIndex] ? j : minIndex;
//是否比目前最小值还小,如果是,则交换,否则不交换;
}
swap(arr, i, minIndex); //将最小值放到范围的第一个数
}
}
private static void swap(int[] arr, int i, int j) {
if (i == j)
return;
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
5.插入排序
时间复杂度O(N^2),稳定的排序
- 将数组中的所有元素依次跟前面已经排好的元素相比较,如果选择的元素比已排序的元素小,则交换,直到全部元素都比较过为止。
public static void insertSort(int[] arr) {
if (arr == null || arr.length < 2)
return;
for (int i = 1; i < arr.length; i++) { // 0~~i做到有序
for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
swap(arr, j, j + 1);
}
}
}
private static void swap(int[] arr, int i, int j) {
if (i == j)
return;
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
2022.02.21
0.master公式
剖析递归x行为的递归行为时间复杂度的估算
注意:每次子函数的数据规模必须相等才能够应用master公式
- T(N): 总问题的数据规模
- a: 每次递归调用的次数
- T(N/b): 每次子函数的数据规模
- O(N^d): 额外的操作的复杂度
1.归并排序
时间复杂度O(N logN), 额外空间复杂度 O(N) ,稳定的排序
- 准备一个辅助数组,然后把原数组的数据放入辅助数组中,最后赋值回去
- 左半边归并,右半边归并,整体再归并
- master公式-> T(N) = 2*T(N/2) + O(N)
public static void process(int[] arr, int left, int right) {
if (left == right) {
return;
}
int mid = left + ((right - left) >> 1);
process(arr, left, mid);
process(arr, mid + 1, right);
merge(arr,left,mid,right);
}
private static void merge(int[] arr, int left, int mid, int right) {
int[] helper = new int[right - left + 1]; //辅助数组
int i = 0; //辅助数组index
int p1 = left; //left指针
int p2 = mid + 1; //right指针
while (p1 <= mid && p2 <= right) {
helper[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= mid) {
helper[i++] = arr[p1++];
}
while (p2 <= right) {
helper[i++] = arr[p2++];
}
for (i = 0; i < helper.length; i++) {
arr[left + i] = helper[i];
}
}
2.快速排序
时间复杂度O(Nlog N), 额外空间复杂度 O(Nlog N) 不稳定的排序
- 挖坑填数+分治法;
- 首先对无序的记录序列进行“一次划分”,通过一趟排序将待排序列分成两部分,使其中一部分记录的关键字均比另一部分小,再分别对这两部分排序,以达到整个序列有序。
//arr[left...right]排好序
public static void quickSort(int[] arr, int left, int right) {
if (arr == null || arr.length < 2) {
return;
}
if (left < right) {
// 随机选出一个数做划分值
swap(arr, left + (int) (Math.random() * (right - left + 1)), right);
int[] p = partition(arr, left, right);
quickSort(arr, left, p[0] - 1);
quickSort(arr, p[1] + 1, right);
}
}
/**
* 处理arr[left..right]的方法
* 默认以arr[right]做划分arr[right] > p ,< p,==p,
* 返回等于区域(左右边界),所以返回一个长度为2的数组p[]
*/
public static int[] partition(int[] arr, int left, int right) {
int less = left - 1;
int more = right;
while (left < more) {
if (arr[left] < arr[right]) { //当前数 < 划分值
swap(arr, ++less, left++);
} else if (arr[left] > arr[right]) { //当前数 > 划分值
swap(arr, --more, left);
} else {
left++;
}
}
swap(arr, more, right);
return new int[]{less + 1, more};
}
public static void swap(int[] arr, int i, int j) {
if (i == j) {
return;
}
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
2022.02.22
0.堆排序
大根堆: 逻辑上为特殊的二叉树,根节点为最大的树
优先级队列其实就是小根堆
-
算法过程:
- 首先将待排序的数组构造成一个大根堆,此时,整个数组的最大值就是堆结构的顶端
- 将顶端的数与末尾的数交换,此时,末尾的数为最大值,剩余待排序数组个数为n-1
- 将剩余的n-1个数再构造成大根堆,再将顶端数与n-1位置的数交换,如此反复执行,便能得到有序数组
//堆排序
public static void heapSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
for (int i = 0; i < arr.length - 1; i++) {
heapInsert(arr, i);
}
int heapSize = arr.length;
swap(arr, 0, --heapSize);
while (heapSize > 0) {
heapIfy(arr, 0, heapSize);
swap(arr, 0, --heapSize);
}
}
// 构造大根堆(通过新插入的数上升)
public static void heapInsert(int[] arr, int index) {
while (arr[index] > arr[(index - 1) / 2]) {
swap(arr, index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
//将剩余的数构造成大根堆(通过顶端的数下降)
public static void heapIfy(int[] arr, int index, int heapSize) {
int left = index * 2 + 1; //左孩子下标
while (left < heapSize) { //下方还有左孩子
//两个孩子中,把较大的孩子的下标给largest
int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
//父和较大孩子值较大的给largest
largest = arr[index] < arr[largest] ? largest : index;
if (largest == index) {
break;
}
swap(arr, index, largest);
index = largest;
left = index * 2 + 1;
}
}
1.比较器
自定义一个排序标准
//PriorityQueue:优先级队列(实质上是小根堆)
PriorityQueue<Integer> heap = new PriorityQueue<>();
heap.add(3);
heap.add(1);
heap.add(4);
heap.add(6);
heap.add(2);
while (!heap.isEmpty()){
System.out.println(heap.poll());
}
//比较器
public static class MyComp implements Comparator<Integer>
{
@Override
public int compare(Integer o1,Integer o2)
{
return o2-o1;
}
}
public static class IdAscendingComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
//return 负数;//表示o1应该放在前边
//return 正数;//表示o2应该放在前边
//return 0;//认为两个东西一样大
//if(o1.id < o2.id){
// return 负数;//表示o1应该放在前边
//}else if(o1.id > o2.id){
// return 正数;//表示o2应该放在前边
//}else {
// return 0;//表示一样大
//}//这几行代码和下边实现的功能是一样的
return o1.id - o2.id;
}
}
// 在堆结构或者红黑树当中如果需要使用排序的话,也可以使用比较器:
//在优先队列,也就是堆结构中,也可以使用比较器
PriorityQueue<Student> queue = new PriorityQueue<>(new IdComparactor());
queue.add(stu1);
queue.add(stu2);
queue.add(stu3);
while(!queue.isEmpty()){
Student student = queue.poll();
System.out.println("name:"+student.name+" , id:"+student.id+" ,age:"+student.age);
}
System.out.println("------------------------------");
//同样地,在红黑树中也可以使用比较器
TreeSet<Student> tree = new TreeSet<Student>(new IdComparactor());
tree.add(stu1);
tree.add(stu2);
tree.add(stu3);
while (!tree.isEmpty()){
Student student = tree.pollFirst();
System.out.println("name:"+student.name+" , id:"+student.id+" ,age:"+student.age);
}
2022.02.23
0.基数排序
基数排序是稳定排序算法,时间复杂度O(N),空间复杂度O(N).
public static void radixSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
radixSort(arr, 0, arr.length - 1, maxBits(arr));
}
/**
* 正经排序
* @param arr 数组
* @param left 左边界
* @param right 右边界
* @param digits 最大值位数
*/
public static void radixSort(int[] arr, int left, int right, int digits) {
final int radix = 10; //表示10进制
int i = 0, j = 0;
int[] helper = new int[right - left + 1]; //辅助数组
// 有多少位进多少次
for (int d = 1; d <= digits; d++) {
int[] count = new int[radix]; //相当于 桶
// 相当于进桶
for (i = left; i <= right; i++) {
j = getDigits(arr[i], d); //得到arr[i]的第d位的数
count[j]++; //count数频+1
}
for (i = 1; i < radix; i++) {
count[i] += count[i - 1]; //求位数累加
}
//相当于出桶
for (i = right; i >= left; i--) {
j = getDigits(arr[i], d);
helper[count[j] - 1] = arr[i];
count[j]--;
}
//copy helper to arr
for (i = left, j = 0; i <= right; i++, j++) {
arr[i] = helper[j];
}
}
}
// 找出最大值并计算出位数
public static int maxBits(int[] arr) {
int max = Integer.MIN_VALUE;
for (int i = 0; i < arr.length; i++) {
max = Math.max(max, arr[i]);
}
int res = 0;
while (max != 0) {
res++;
max /= 10;
}
return res;
}
public static int getDigits(int x, int d) {
return ((x / ((int) Math.pow(10, d - 1))) % 10);
}
1.总结
-
1)不基于比较的排序,对样本数据有严格要求(比如数据量小等),不易改写- 计数排序:要求数据量小
- 基数排序:要求数据的进制已知
-
2)基于比较的排序,只要规定好两个样本怎么比大小就可以直接复用
-
3)基于比较的排序,时间复杂度的极限是O(NlogN)
-
4)时间复杂度O(N*logN)、 额外空间复杂度低于O(N)、 且稳定的基于比较的排序是不存在的。
-
5)为了绝对的速度选快排、为了省空间选堆排、为了稳定性选归并
-
6)排序算法的稳定性:数组中相同的数排序后相对位置不变。
a.归并排序的额外空间复杂度可以变成0(1);“归并排序内部缓存法”,但是将变得不再稳定。
b.“原地归并排序"是垃圾贴,会让时间复杂度变成O(N^2)
c. 快速排序稳定性改进,“01 stable sort",但是会对样本数据要求更多。
2.哈希表和有序表
3.链表
笔试:可以不在意空间复杂度,一切为了时间复杂度
面试:时间复杂度最重要,但要节省空间
::快慢指针::
1)输入链表头节点,奇数长度返回中点,偶数长度返回上中点
2)输入链表头节点,奇数长度返回中点,偶数长度返回下中点
3)输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个
4)输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个.
1)输入链表头节点,奇数长度返回中点,偶数长度返回上中点
static class Node {
int value;
Node next;
}
public static Node midOrUpMidNode(Node head) {
if (head == null || head.next == null || head.next.next == null) {
return head;
}
Node slow = head.next;
Node fast = head.next.next;
while (fast.next != null && fast.next.next != null) { //// find mid node
slow = slow.next; // slow -> mid
fast = fast.next.next; //fast -> end
}
return slow;
}
2)输入链表头节点,奇数长度返回中点,偶数长度返回下中点
public static Node midOrDownMidNode(Node head) {
if (head == null || head.next == null || head.next.next == null) {
return head;
}
Node slow = head.next;
Node fast = head.next;
while (fast.next != null && fast.next.next != null) { //// find mid node
slow = slow.next; // slow -> mid
fast = fast.next.next; //fast -> end
}
return slow;
}
2022.02.25
0.二叉树
0.结构定义
1.递归法
static class Node<T> {
T value;
Node left;
Node right;
}
// 1.递归遍历法
public static void process(Node head) {
if (head == null) {
return;
}
System.out.println(head.value); //先序遍历
process(head.left);
System.out.println(head.value); //中序遍历
process(head.right);
System.out.println(head.value); //后序遍历
}
2.非递归
先序:压栈方法: (1)弹出就打印 (2)如有右孩子就压入右 (3)如有左就压左
private static void preProcess(Node head) {
System.out.println("pre-order:");
if (head != null) {
Stack<Node> stack = new Stack<>();
stack.add(head);
while (!stack.isEmpty()) {
stack.pop();
System.out.println(" " + head.value);
if (head.right != null) {
stack.push(head.right);
}
if (head.left != null) {
stack.push(head.left);
}
}
}
}
后序:先序的逆序打印就是后序
private static void posProcess(Node head) {
System.out.println("pos-order: ");
if (head != null) {
Stack<Node> stack1 = new Stack<>();
Stack<Node> stack2 = new Stack<>();
stack1.push(head);
while (!stack1.isEmpty()) {
head = stack1.pop();
stack2.push(head);
if (head.left != null) {
stack1.push(head.left);
}
if (head.right != null) {
stack1.push(head.right);
}
}
}
}
中序: 1、整条左边界依次压入栈 2、1)无法继续,就弹出并打印,进入弹出结点的右子树,继续1)操作
public static void inProcess(Node head) {
System.out.println("pos-order: ");
Stack<Node> stack = new Stack<>();
if (head != null) {
stack.add(head);
head = head.left;
} else {
head = stack.pop();
System.out.println(" " + head.value);
head = head.right;
}
}
3.广度优先遍历
用队列,Java里是双向链表linkedlist
//3.广度优先遍历
public static void levelProcess(Node head) {
if (head == null) {
return;
}
LinkedList<Node> queue = new LinkedList<>();
queue.add(head);
while (!queue.isEmpty()) {
Node cur = queue.poll();
System.out.println(" " + head.value);
if (cur.left != null) {
queue.add(head.left);
}
if (cur.right != null) {
queue.add(head.right);
}
}
}
2022.03.01
0.搜索二叉树
左.value< 主.value <右.value
/***判断是否为搜索二叉树**/
// 1.递归
public static boolean checkBST(Node head) {
int preValue = Integer.MIN_VALUE;
if (head == null) {
return true;
}
boolean isLeftBst = checkBST(head.left);
if (!isLeftBst) {
return false;
}
if (head.value <= preValue) {
return false;
} else {
preValue = head.value;
}
return checkBST(head.right);
}
// 2.非递归
public static boolean inProcess(Node head) {
if (head == null) {
return true;
}
int preValue = Integer.MIN_VALUE;
Stack<Node> stack = new Stack<>();
if (!stack.isEmpty() || head != null) {
stack.push(head);
head = head.left;
} else {
head = stack.pop();
if (head.value <= preValue) {
return false;
} else {
preValue = head.value;
}
head = head.right;
}
return true;
}
//3.树形DP,动态规划
public static ReturnType process(Node head) {
if (head == null) {
return null;
}
ReturnType leftData = process(head.left);
ReturnType rightData = process(head.right);
int min = head.value;
int max = head.value;
if (leftData != null) {
min = Math.min(min, leftData.min);
max = Math.max(max, leftData.max);
}
if (rightData != null) {
min = Math.min(min, rightData.min);
max = Math.max(max, rightData.max);
}
boolean isBST = true;
if (leftData != null && (!leftData.isBST || leftData.max >= head.value)) {
isBST = false;
}
if (rightData != null && (!rightData.isBST || rightData.min <= head.value)) {
isBST = false;
}
return new ReturnType(isBST,min,max);
}
static class ReturnType {
public boolean isBST;
public int min;
public int max;
public ReturnType(boolean isBST, int min, int max) {
this.isBST = isBST;
this.min = min;
this.max = max;
}
}
1.完全二叉树
// 判断完全二叉树
public static boolean checkCST(Node head) {
if (head == null) {
return true;
}
LinkedList<Node> queue = new LinkedList<>();
//标记是否遇到过左右子不双全节点
boolean leaf = false;
Node left = null;
Node right = null;
if (!queue.isEmpty()) {
head = queue.poll();
left = head.left;
right = head.right;
if (
//如果遇到不双全节点后,当前节点不是叶节点
(leaf && !(left != null && right != null)) || (left == null && right == null)
) {
return false;
}
}
if (left != null) {
queue.add(left);
}
if (right != null) {
queue.add(right);
}
if (left == null && right == null) {
leaf = true;
}
return true;
}
2.平衡二叉树
// 判断平衡二叉树
public static boolean isBalance(Node head) {
return process(head).isBalanced;
}
private static ReturnType process(Node head) {
if (head == null) {
return new ReturnType(true, 0);
}
ReturnType leftData = process(head.left);
ReturnType rightData = process(head.right);
int height = Math.max(leftData.height, rightData.height);
boolean isBalanced = leftData.isBalanced
&& rightData.isBalanced
&& Math.abs(leftData.height - rightData.height) < 2;
return new ReturnType(isBalanced, height);
}
static class ReturnType {
public boolean isBalanced;
public int height;
public ReturnType(boolean isBalanced, int height) {
this.isBalanced = isBalanced;
this.height = height;
}
}
2022.03.02
0.二叉树的序列化和反序列化
//以head为头的二叉树,中序,序列化
public static String serializeByPre(Node head) {
if (head == null) {
return "#_";
}
String res = head.value + "_";
res += serializeByPre(head.left);
res += serializeByPre(head.right);
return res;
}
//反序列化
public static Node reconByPreString(String preString) {
String[] values = preString.split("_");
Queue<String> queue = new LinkedList<>();
for (int i = 0; i < values.length; i++) {
queue.add(values[i]);
}
return reconPreOrder(queue);
}
public static Node reconPreOrder(Queue<String> queue) {
String value = queue.poll();
if (value == "#") {
return null;
}
Node head = new Node(Integer.parseInt(value));
head.left = reconPreOrder(queue);
head.right = reconPreOrder(queue);
return head;
}
2022.03.03
0.图
1).由点的集合和边的集合构成
2)虽然存在有向图和无向图的概念,但实际上都可以用有向图来表达
3)边上可能带有权值
点:
public class Node {
public int value; //节点的数值
public int in; //入度(有多少个节点指向我)
public int out; //出度(我指向多少个节点)
public ArrayList<Node> nexts; //从我出发能到达的下一级节点,邻居节点
public ArrayList<Edge> edges; //从我出发发散出的边的集合
public Node(int value) {
this.value = value;
in = 0;
out = 0;
nexts = new ArrayList<>();
edges = new ArrayList<>();
}
}
边:
public class Edge {
public int weight;//这个边的权重是多少
public Node from;//这个边从哪里出发
public Node to;//这个边到达哪里的
public Edge(int weight, Node from, Node to) {
this.weight = weight;
this.from = from;
this.to = to;
}
}
图:
public class Graph {
public HashMap<Integer, Node> nodes; //点集
public HashSet edges; //边集
public Graph() {
nodes = new HashMap<>();
edges = new HashSet();
}
//图生成器
public Graph createGraph(Integer[][] matrix) {
Graph graph = new Graph();//初始化自定义的图
for (int i = 0; i < matrix.length; i++) {
Integer weight = matrix[i][0];//边的权重
Integer from = matrix[i][1];//from节点的序列
Integer to = matrix[i][2];//to节点的序列
if (!graph.nodes.containsKey(from)) {//先检查from节点存在否,不存在就建
graph.nodes.put(from, new Node(from));
}
if (!graph.nodes.containsKey(to)) {//再检查to节点存在否,不存在就建立
graph.nodes.put(to, new Node(to));
}
Node fromNode = graph.nodes.get(from);//拿出from点
Node toNode = graph.nodes.get(to);//拿出to点
Edge newEdge = new Edge(weight, fromNode, toNode);//建立新的边
fromNode.nexts.add(toNode);//from的邻接点增加了一个to节点
fromNode.out++;//from的出度加1
toNode.in++;//to节点的入度加1
fromNode.edges.add(newEdge);//from节点的边集增加
graph.edges.add(newEdge);//加到整个图的边集里
}
return graph;
}
}
1.广度优先遍历
1,利用队列实现 2,从源节点开始依次按照宽度进队列,然后弹出 3,每弹出一个点,把该节点所有没有进过队列的邻接点放入队 列 4,直到队列变空
public static void bfs(Node node) {//广(宽)度优先遍历
if (node == null) {
return;
}
Queue<Node> queue = new LinkedList<>();
HashSet<Node> map = new HashSet<>();
queue.add(node);
map.add(node);
while (!queue.isEmpty()) {
Node cur = queue.poll();//将当前节点拿出来
System.out.println(cur.value);//打印当前的节点
for (Node next : cur.nexts) {//遍历当前节点的所有邻接点
if (!map.contains(next)) {//如果有某个邻接点已经在set里边了,就不再需要了
map.add(next);
queue.add(next);
}
}
}
}
2.深度优先遍历
1,利用栈实现 2,从源节点开始把节点按照深 度放入栈,然后弹出 3,每弹出一个点,把该节点下一个没有 进过栈的邻接点放入栈 4,直到栈变空
public void dfs(Node node) {
if (node == null) {
return;
}
Stack<Node> stack = new Stack<>();
HashSet<Node> set = new HashSet<>();
stack.add(node);
set.add(node);
System.out.println(node.value);
while (!stack.isEmpty()) {
Node cur = stack.pop();
for (Node next : cur.nexts) {
if (!set.contains(next)) {//如果set中没有下一个的节点就继续
stack.push(cur);//先把当前的节点重新放进去栈中
stack.push(next);//再把下一个节点放进去栈中
set.add(next);//set集合中添加下一个节点
System.out.println(next.value);//打印下一个节点的值
break;//不再继续进行当前的这个循环了,重新回到while循环中,继续下一个节点的遍历先
}
}
}
}
3.拓扑排序
适用范围:要求有向图,且有入度为0的节点,且没有环
实现的逻辑就是:先找出入度为0的节点,然后把他们打印,并且打印完成之后就马上删除掉,然后继续搜索图,找出新的入度为0的节点,继续前边的操作,直到图被完全遍历。
// directed graph and no loop
public static List<Node> sortedTopology(Graph graph) {
HashMap<Node, Integer> inMap = new HashMap<>();//记录所有入点
Queue<Node> zeroInQueue = new LinkedList<>();//记录所有入度为0的节点
for (Node node : graph.nodes.values()) {//values就是当前所有点的意思
inMap.put(node, node.in);//将入点全部登记
if (node.in == 0) {
zeroInQueue.add(node);//将所有入度为0的节点登记
}
}
List<Node> result = new ArrayList<>();
while (!zeroInQueue.isEmpty()) {
Node cur = zeroInQueue.poll();
result.add(cur);
for (Node next : cur.nexts) {
inMap.put(next, inMap.get(next) - 1);//找出新的入度为0的点
if (inMap.get(next) == 0) {
zeroInQueue.add(next);
}
}
}
return result;
}
2022.03.04
0.kruskal算法(K算法) 适用范围:要求无向图
1)总是从权值最小的边开始考虑,依次考察权值依次变大的边 2)当前的边要么进入最小生成树的集合,要么丢弃 3)如果当前的边进入最小生成树的集合中不会形成环,就要当前边 4)如果当前的边进入最小生成树的集合中会形成环,就不要当前边 5)考察完所有边之后,最小生成树的集合也得到了
此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。
把图中的所有边按代价从小到大排序; 把图中的n个顶点看成独立的n棵树组成的森林; 按权值从小到大选择边,所选的边连接的两个顶点ui,viui,vi,应属于两颗不同的树,则成为最小生成树的一条边,并将这两颗树合并作为一颗树。 重复(3),直到所有顶点都在一颗树内或者有n-1条边为止。
//并查集结构
// Union-Find Set
public static class UnionFind {
private HashMap<Node, Node> fatherMap;
private HashMap<Node, Integer> rankMap;
public UnionFind() {
fatherMap = new HashMap<Node, Node>();
rankMap = new HashMap<Node, Integer>();
}
private Node findFather(Node n) {
Node father = fatherMap.get(n);
if (father != n) {
father = findFather(father);
}
fatherMap.put(n, father);
return father;
}
public void makeSets(Collection<Node> nodes) {
fatherMap.clear();
rankMap.clear();
for (Node node : nodes) {
fatherMap.put(node, node);
rankMap.put(node, 1);
}
}
public boolean isSameSet(Node a, Node b) {
return findFather(a) == findFather(b);
}
public void union(Node a, Node b) {
if (a == null || b == null) {
return;
}
Node aFather = findFather(a);
Node bFather = findFather(b);
if (aFather != bFather) {
int aFrank = rankMap.get(aFather);
int bFrank = rankMap.get(bFather);
if (aFrank <= bFrank) {
fatherMap.put(aFather, bFather);
rankMap.put(bFather, aFrank + bFrank);
} else {
fatherMap.put(bFather, aFather);
rankMap.put(aFather, aFrank + bFrank);
}
}
}
}
//比较器
public static class EdgeComparator implements Comparator<Edge> {
@Override
public int compare(Edge o1, Edge o2) {
return o1.weight - o2.weight;
}
}
//kruskal算法
public static Set<Edge> kruskalMST(Graph graph) {
UnionFind unionFind = new UnionFind();
unionFind.makeSets(graph.nodes.values());
PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());
for (Object edge : graph.edges) {
priorityQueue.add((Edge) edge);
}
Set<Edge> result = new HashSet<>();
while (!priorityQueue.isEmpty()) {
Edge edge = priorityQueue.poll();
if (!unionFind.isSameSet(edge.from, edge.to)) {
result.add(edge);
unionFind.union(edge.from, edge.to);
}
}
return result;
}
1.Prim算法(P算法)
此算法可以称为“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。
图的所有顶点集合为VV;初始令集合u=,v=V−uu=,v=V−u; 在两个集合u,vu,v能够组成的边中,选择一条代价最小的边(u0,v0)(u0,v0),加入到最小生成树中,并把v0v0并入到集合u中。 重复上述步骤,直到最小生成树有n-1条边或者n个顶点为止。 由于不断向集合u中加点,所以最小代价边必须同步更新;需要建立一个辅助数组closedge,用来维护集合v中每个顶点与集合u中最小代价边信息。
public static class EdgeComparator implements Comparator<Edge> {
@Override
public int compare(Edge o1, Edge o2) {
return o1.weight - o2.weight;
}
}
public static Set<Edge> primMST(Graph graph) {
PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(
new EdgeComparator());
HashSet<Node> set = new HashSet<>();
Set<Edge> result = new HashSet<>();
for (Node node : graph.nodes.values()) {
if (!set.contains(node)) {
set.add(node);
for (Edge edge : node.edges) {
priorityQueue.add(edge);
}
while (!priorityQueue.isEmpty()) {
Edge edge = priorityQueue.poll();//从优先级队列中弹出一个最小的边
Node toNode = edge.to;
if (!set.contains(toNode)) {
set.add(toNode);
result.add(edge);
for (Edge nextEdge : toNode.edges) {
priorityQueue.add(nextEdge);
}
}
}
}
}
return result;
}
2.迪杰斯特拉算法
求解单元点的最短路径问题:给定带权有向图G和源点v,求v到G中其他顶点的最短路径
限制条件:图G中不存在负权值的边
迪杰斯特拉算法总共就干了两件事:
【1】不断运行广度优先算法找可见点,计算可见点到源点的距离长度
【2】从当前已知的路径中选择长度最短的将其顶点加入S作为确定找到的最短路径的顶点。
public static HashMap<Node, Integer> dijkstra1(Node from) {
HashMap<Node, Integer> distanceMap = new HashMap<>();
distanceMap.put(from, 0);
//已经选择过的结点
HashSet<Node> selectedNodes = new HashSet<>();
Node minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);
while (minNode != null) {
int distance = distanceMap.get(minNode);
for (Edge edge : minNode.edges) {
Node toNode = edge.to;
if (!distanceMap.containsKey(toNode)) {
distanceMap.put(toNode, distance + edge.weight);
}
distanceMap.put(edge.to, Math.min(distanceMap.get(toNode), distance + edge.weight));
}
selectedNodes.add(minNode);
minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);
}
return distanceMap;
}
public static Node getMinDistanceAndUnselectedNode(HashMap<Node, Integer> distanceMap,
HashSet<Node> touchedNodes) {
Node minNode = null;
int minDistance = Integer.MAX_VALUE;
for (Map.Entry<Node, Integer> entry : distanceMap.entrySet()) {
Node node = entry.getKey();
int distance = entry.getValue();
if (!touchedNodes.contains(node) && distance < minDistance) {
minNode = node;
minDistance = distance;
}
}
return minNode;
}
public static class NodeRecord {
public Node node;
public int distance;
public NodeRecord(Node node, int distance) {
this.node = node;
this.distance = distance;
}
}
public static class NodeHeap {
private Node[] nodes;
private HashMap<Node, Integer> heapIndexMap;
private HashMap<Node, Integer> distanceMap;
private int size;
public NodeHeap(int size) {
nodes = new Node[size];
heapIndexMap = new HashMap<>();
distanceMap = new HashMap<>();
this.size = 0;
}
public boolean isEmpty() {
return size == 0;
}
public void addOrUpdateOrIgnore(Node node, int distance) {
if (inHeap(node)) {
distanceMap.put(node, Math.min(distanceMap.get(node), distance));
insertHeapify(node, heapIndexMap.get(node));
}
if (!isEntered(node)) {
nodes[size] = node;
heapIndexMap.put(node, size);
distanceMap.put(node, distance);
insertHeapify(node, size++);
}
}
public NodeRecord pop() {
NodeRecord nodeRecord = new NodeRecord(nodes[0], distanceMap.get(nodes[0]));
swap(0, size - 1);
heapIndexMap.put(nodes[size - 1], -1);
distanceMap.remove(nodes[size - 1]);
nodes[size - 1] = null;
heapify(0, --size);
return nodeRecord;
}
private void insertHeapify(Node node, int index) {
while (distanceMap.get(nodes[index]) < distanceMap.get(nodes[(index - 1) / 2])) {
swap(index, (index - 1) / 2);
index = (index - 1) / 2;
}
}
private void heapify(int index, int size) {
int left = index * 2 + 1;
while (left < size) {
int smallest = left + 1 < size && distanceMap.get(nodes[left + 1]) < distanceMap.get(nodes[left])
? left + 1 : left;
smallest = distanceMap.get(nodes[smallest]) < distanceMap.get(nodes[index]) ? smallest : index;
if (smallest == index) {
break;
}
swap(smallest, index);
index = smallest;
left = index * 2 + 1;
}
}
private boolean isEntered(Node node) {
return heapIndexMap.containsKey(node);
}
private boolean inHeap(Node node) {
return isEntered(node) && heapIndexMap.get(node) != -1;
}
private void swap(int index1, int index2) {
heapIndexMap.put(nodes[index1], index2);
heapIndexMap.put(nodes[index2], index1);
Node tmp = nodes[index1];
nodes[index1] = nodes[index2];
nodes[index2] = tmp;
}
}
public static HashMap<Node, Integer> dijkstra2(Node head, int size) {
NodeHeap nodeHeap = new NodeHeap(size);
nodeHeap.addOrUpdateOrIgnore(head, 0);
HashMap<Node, Integer> result = new HashMap<>();
while (!nodeHeap.isEmpty()) {
NodeRecord record = nodeHeap.pop();
Node cur = record.node;
int distance = record.distance;
for (Edge edge : cur.edges) {
nodeHeap.addOrUpdateOrIgnore(edge.to, edge.weight + distance);
}
result.put(cur, distance);
}
return result;
}
3.并查集
- 有若干个样本a、b、C、d类型假设是V 2)在并查集中一开始认为每个样本都在单独的集合里 3)用户可以在任何时候调用如下两个方法: boolean isSameSet(Vx, V y):查询样本x和样本y是否属于一个集合 void union(V x, V y):把x和y各自所在集合的所有样本合并成- -个集合
- isSameSet和union方法的代价越低越好
步骤:
建立map,k为节点,v为节点的指向节点
1.首先第一步将所有各自的数目整成一个集合(每个节点单独形成一个集合) 2.在一个集合中每个节点都是自己集合中的代表节点,当集合合并的时候,如果决定将2挂到1的底下,此时2所在集合中,只有1节点指向自己,所以1为这个集合的代表节点。 3.由于每一个集合中都有一个集合的代表节点,所以可以通过代表节点来进行是否在同一个集合的判断,也可以将另外一个集合的代表节点连接到本集合中,此时整个集合也只有一个代表节点,实现集合的整合。(首先进行元素数目的判断,将少元素的集合挂到多元素的底下) 优化:在向上查找的过程中,将路径上的所有元素直接连接到代表节点上。
public class UnionFind {
public static class Element<V> {//加了个封装
public V value;
public Element(V value) {
this.value = value;
}
}
public static class UnionFindSet<V> {
public HashMap<V, Element<V>> elementMap;//v为值,Element为元素
public HashMap<Element<V>, Element<V>> fatherMap;//父节点
public HashMap<Element<V>, Integer> sizeMap;//代表节点的集合有多少个节点
public UnionFindSet(List<V> list) {
elementMap = new HashMap<>();
fatherMap = new HashMap<>();
sizeMap = new HashMap<>();
for (V value : list) {
//初始化
Element<V> element = new Element<V>(value);
elementMap.put(value, element);
fatherMap.put(element, element);//每个节点是自己的父
sizeMap.put(element, 1);//每个节点是自己代表节点
}
}
private Element<V> findHead(Element<V> element) {
Stack<Element<V>> path = new Stack<>();//沿途的路
while (element != fatherMap.get(element)) {
path.push(element);//一直走到代表节点
element = fatherMap.get(element);
}
while (!path.isEmpty()) {
//扁平化,所有的节点都是直接指向代表节点的
fatherMap.put(path.pop(), element);
}
return element;
}
public boolean isSameSet(V a, V b) {
//看a有没有注册过,一个值都有对应的element
if (elementMap.containsKey(a) && elementMap.containsKey(b)) {
return findHead(elementMap.get(a)) == findHead(elementMap.get(b));
}
// 如果有一个点没有登记过则不进行查找
return false;
}
public void union(V a, V b) {
if (elementMap.containsKey(a) && elementMap.containsKey(b)) {
//先找代表节点【头结点】
Element<V> aF = findHead(elementMap.get(a));//找a对应的元素的代表节点
Element<V> bF = findHead(elementMap.get(b));//代表节点里面有这个结合的大小
if (aF != bF) {
int aSetsize = sizeMap. get(aHead);
int bSetSize = sizeMap . get(bHead);
// bf 小,将小的节点挂在 大的节点上,直接副高parents中的记录
if (aSetSize >= bSetSize) {
parents . put(bF, aF);
sizeMap. put(aF, aSetSize + bSetSize); .
sizeMap . remove(bF);
} else {
parents . put( aF, bF);
sizeMap . put(bHead, aSetSize + bSetSize);
sizeMap . remove(bF);
}
}
}
}
}
2022.03.05
0.前缀树
- 前缀树又名字典树,单词查找树,Trie树,多路树形结构,和hash效率有一拼,是一种用于快速检索的多叉树结构。多用于词频搜索或者模糊查询。
- 查询时只与样本长度有关,而与样本量无关。
- 1)单个字符串中,字符从前到后的加到-棵多叉树上 2)字符放在路上,节点上有专属的数据项(常 见的是pass和end值) 3)所有样本都这样添加,如果没有路就新建,如有路就复用 4)沿途节点的pass值增加1,每个字符串结束时来到的节点end值增加1 可以完成前缀相关的查询
pass 为通过次数,end 为以当前元素结尾的次数
生成这棵树的代价为 O(N)
//节点结构
public static class TrieNode {
public int path;
public int end;
public TrieNode[] nexts;
public TrieNode() {
path = 0; //有多少个结点到达过
end = 0; //有多少个字符串以这个结点结尾
nexts = new TrieNode[26]; //通向子节点的路,如果题目所给的范围不确定就用map
}
}
public static class Trie {
private TrieNode root;
public Trie() {
//准备一个头结点
root = new TrieNode();
}
//将一个单词插入
public void insert(String word) {
if (word == null) {
return;
}
char[] chs = word.toCharArray();
TrieNode node = root;
int index = 0;
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a';
if (node.nexts[index] == null) {
node.nexts[index] = new TrieNode();
}
node = node.nexts[index];
node.path++;
}
node.end++;
}
//在结构中删除这个单词
public void delete(String word) {
if (search(word) != 0) {
char[] chs = word.toCharArray();
TrieNode node = root;
int index = 0;
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a';
if (--node.nexts[index].path == 0) {
//如果某个结点-1之后==0,则说明此节点之后的结点也是-1之后==0,因此直接=null即可。
node.nexts[index] = null;
return;
}
node = node.nexts[index];
}
node.end--;
}
}
//查找某个单词插入了几次
public int search(String word) {
if (word == null) {
return 0;
}
char[] chs = word.toCharArray();
TrieNode node = root;
int index = 0;
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a';
if (node.nexts[index] == null) {
return 0;
}
node = node.nexts[index];
}
return node.end;
}
//查某个字符串前缀数量是多少
public int prefixNumber(String pre) {
if (pre == null) {
return 0;
}
char[] chs = pre.toCharArray();
TrieNode node = root;
int index = 0;
for (int i = 0; i < chs.length; i++) {
index = chs[i] - 'a';
if (node.nexts[index] == null) {
return 0;
}
node = node.nexts[index];
}
return node.path;
}
}
2022.03.06
0.贪心算法
在某一个标准下,邮箱考虑满足标准的样本,最后考虑最不满足标准的样本。最终得到一个答案的一个算法,叫做贪心算法。也就是说,不从整体上加以考虑,所做出的是在某种意义上的局部最优解。
解题思路:
实现一个不依靠贪心策略的解法X,可用最暴力的尝试.
脑补出糖心策略A,B,C...
用解法X与对数器去验证每一个贪心策略,用实验的方式去得知正确性
不要纠结于算法的
2022.03.24
1.贪心策略在实现时,经常使用到的技巧
- 根据某标准建立一个比较器来排序
- 根据某标准建立一个比较器来组成堆
2022.03.26
0.暴力递归
暴力递归就是尝试
- 把问题转化为规模缩小了的同类问题的子问题
- 有明确的不需要的继续进行递归的条件(base case)
- 有当得到了子问题的结果后的决策过程
- 不记录第一个子问题的解
一定要记住去尝试,这是动态规划的基础
题目
2022.02.20
1.寻找出现双中的单数
// 题目:一组数只有一个数出现一次,其他出现两次,找出这个出现一次的数
public static void main(String[] args) {
int[] arr = {3, 6, 2, 3, 2, 6, 5};
System.out.println(process1(arr));
}
public static int process1(int[] arr) {
int eor = 0;
for (int i : arr) {
eor ^= i;
}
return eor;
}
2.找所有双出现中的两个单数
// 题目:一组数只有两个数出现一次,其他出现两次,找出这两个数:
//因为两个值不同,所以两个值定存在二进制某一位定不同,用这两个值的异或结果二进制中的1,从而将数字分成两组,该位为1和不为1
public static void main(String[] args) {
int[] arr = {-1, 5, 4, -1, 3, 5, 4, 9};
process2(arr);
}
public static void process2(int[] arr) {
int eor = 0;
for (int i : arr) {
eor ^= i;
}
// 取出med中二进制为1的位值(必存在,因为不同值)
int rightOne = eor & (~eor + 1);
int eor2 = 0;
for (int i : arr) {
if ((i & rightOne) == 0) {
eor2 ^= i;
}
}
System.out.println("单数次出现的两个数是:" + eor2 + " 和 " + (eor2 ^ eor));
}
2022.02.21
1.求给定数组的最大值(递归)
应用master公式计算时间复杂度:T(N) = 2*T(N/2) + O(1)
public static int getMax(int[] arr, int left, int right) {
if (left == right) {
return arr[left];
}
int mid = left + ((right - left) >> 1); // >> 比 / 快
int leftMax = getMax(arr, left, mid);
int rightMax = getMax(arr, mid + 1, right);
return Math.max(leftMax, rightMax);
}
2.小和数
题目:在一个数组中,每一个元素左边比当前元素值小的元素值累加起来,叫做这个数组的小和
例: [1,3,4,2,5] 1左边比1小的数,没有; 3左边比3小的数,1; 4左边比4小的数,1、3; 2左边比2小的数,1; 5左边比5小的数,1、3、4、2; 所以小和为1+1+3+1+1+3+4+2=16
public static int smallSum(int[] arr) {
if (arr == null || arr.length < 2) {
return 0;
}
return mergeSort(arr, 0, arr.length - 1);
}
public static int mergeSort(int[] arr, int left, int right) {
if (left == right) {
return 0;
}
int mid = left + ((right - left) >> 1);
return mergeSort(arr, left, mid) + mergeSort(arr, mid + 1, right) + merge(arr, left, mid, right);
}
public static int merge(int[] arr, int left, int mid, int right) {
int[] helper = new int[right - left + 1];
int i = 0;
int p1 = left;
int p2 = mid + 1;
int res = 0;
while (p1 <= mid && p2 <= right) {
res += arr[p1] < arr[p2] ? (right - p2 + 1) * arr[p1] : 0;
helper[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= mid) {
helper[i++] = arr[p1++];
}
while (p2 <= right) {
helper[i++] = arr[p2++];
}
for (i = 0; i < helper.length; i++) {
arr[left + i] = helper[i];
}
return res;
}
3.题目:给定一个数组arr,
题目:给定一个数组arr,和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度O(N)。
public static void main(String[] args) {
int[] arr = {3, 6, 4, 2, 6, 1, 3, 9, 2};
System.out.println("排序前: " + Arrays.toString(arr));
int x = -1;
int num = 5;
for (int i = 0; i < arr.length; i++){
if (arr[i] < num){
swap(arr, i, ++x);
}
}
System.out.println("排序后: " + Arrays.toString(arr));
}
public static void swap(int[] arr, int i, int j) {
if (i == j) {
return;
}
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
4.荷兰国旗问题
题目:给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
public class SortColorTest {
public static void main(String[] args) {
int[] arr = {2, 2, 0, 0, 1, 0, 2, 0, 2};
System.out.println("排序前: " + Arrays.toString(arr));
sortColor(arr, 0, arr.length - 1);
System.out.println("排序前: " + Arrays.toString(arr));
}
public static void sortColor(int[] arr, int left, int right) {
if (arr == null || arr.length < 2) {
return;
}
int less = left - 1;
int more = right;
int index = 0;
while (index < more) {
if (arr[index] < 1) {
swap(arr, index, ++less);
index++;
} else if (arr[index] > 1) {
swap(arr, index, --more);
} else {
index++;
}
}
}
public static void swap(int[] arr, int i, int j) {
if (i == j) {
return;
}
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
}
2022.02.22
1.堆排序题
题目:已知一个几乎有序的数组。几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离一定不超过k, 并且k相对于数组长度来说是比较小的。
请选择一个合适的排序策略,对这个数组进行排序
建立小根堆,
将数组中前k+1个数放入小根堆,因为 0 位置上的数字,只可能在 0---k+1 的位置上,由此得到最小的数,将小根堆堆顶弹出,再将后面的数字依次放入,即可获得有序数列
每个数需要log k 的时间来调整,所以时间复杂度 N*Log k
public static void sortedArr(int[] arr, int k){
//默认小根堆
PriorityQueue<Integer> heap = new PriorityQueue<>();
int index = 0;
for(;index <= Math.min(arr.length,k);index++){
heap.add(arr[index]);
}c
int i = ;
for(;index <= arr.length);i++,index++){
heap.add(arr[index]);
arr[i] = heap.poll();
}
while(!heap.isEmpty()){
arr[i++] = heap.poll();
}
}
2022.02.23
1.判断单项链表回文
1)哈希表方法特别简单(笔试用)
2)改原链表的方法就需要注意边界了( 面试用)
利用栈,先遍历一遍压栈,然后在遍历一遍,同时与栈顶元素对比
用更少的空间?
快慢指针求出中点,和上中点
将后半部分压栈,再次出栈与前半部分对比
常数空间?
先快慢指针定位中点和末尾,将中点的next指针指向null
将后半部分指针逆序,然后头尾依次对比,当有一个的指针指向为null,即为回文结构
中间有一个不一样则为非回文
注意返回结果前,需要把后半部分逆序回来.
// 1.栈实现
public static boolean process(Node head) {
if (head == null) {
return false;
}
Stack<Node> stack = new Stack<>();
Node tail = head;
while (tail != null) {
stack.push(tail);
tail = tail.next;
}
tail = head;
while (tail!= null) {
if (stack.pop().value != tail.value) {
return false;
}
tail = tail.next;
}
return true;
}
//栈+快慢指针
public static boolean process2(Node head) {
if (head == null) {
return false;
}
Node slow = head;
Node fast = head;
Stack<Node> stack = new Stack<>();
while (fast.next != null && fast.next.next != null) {
stack.push(slow);
slow = slow.next;
fast = fast.next.next;
}
if (fast.next != null) {
stack.push(slow);
}
slow = slow.next;
while (!stack.isEmpty()) {
if (stack.pop().value != slow.value) {
return false;
}
slow = slow.next;
}
return true;
}
//3.快慢指针+链表反向
public static boolean process3(Node header) {
if (header == null) {
return false;
}
Node slow = header;
Node fast = header;
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
slow = slow.next;
Node preNode = null;
Node postNode = null;
// 后半段反转
while (slow != null) {
preNode = slow.next;
slow.next = postNode;
postNode = slow;
slow = preNode;
}
Node tailLeft = header;
Node tailRight = postNode;
boolean flag = true;
// 两边向中间判断
while (tailRight != null) {
if (tailLeft.value != tailRight.value) {
flag = false;
break;
}
tailLeft = tailLeft.next;
tailRight = tailRight.next;
}
Node tailNode = null;
// 后半段链表恢复
while (postNode != null) {
preNode = postNode.next;
postNode.next = tailNode;
tailNode = postNode;
postNode = preNode;
}
return flag;
}
2.将单向链表划分
题目:单向链表按某值划分成左边小、中间相等、右边大的形式
1)把链表放入数组里,在数组上做partition (笔试用)
2)分成小、中、大三部分,再把各个部分之间串起来(面试用)
public static Node process(Node head, int pivot) {
if (head == null) {
return null;
}
Node sH = null;// small head
Node sT = null;// small tail
Node eH = null;// equal head
Node eT = null;
Node bH = null;
Node bT = null;// big tail
Node next = null; //save next node
// every node distributed to three lists
while (head != null) {
next = head.next;
head.next = null;
if (head.value < pivot) {
if (sH == null) {
sH = head;
sT = head;
} else {
sT.next = head;
sT = head;
}
} else if (head.value == pivot) {
if (eH == null) {
eH = head;
eT = head;
} else {
eT.next = head;
eT = head;
}
} else {
if (bH == null) {
bH = head;
bT = head;
} else {
bT.next = head;
bT = head;
}
}
head = next;
}
if (sT != null) {
sT.next = eH;
eT = eT == null ? sT : eT; //谁去连大于区域的头,谁就变成eT
}
// 上面的if, 不管跑了没有,et
// all reconnect
if (eT != null) {
eT.next = bH;
}
return sH != null ? sH : bH;
}
3.拷贝有特殊指针的链表
题目:1种特殊的单链表节点类描述如下
class Node {
Int value.
Node next:
Node rand;
Node(int val)
}
rand指针是单链表节点结构中新增的指针,rand可 能指向链表中的任意一个节点,也可能指向null. 给定一个由node节点类型组成的无环单链表的头节点head.请实现一个函数完成这个链表的复制 并返回复制的新链表的头节点。
[要求] 时间复杂度ON).额外空间复杂度O(1)
法1:常规版,hashMap
//1.常规版,hashMap
public static Node process(Node head) {
if (head == null) {
return null;
}
HashMap<Node, Node> map = new HashMap<>();
Node curNode = head;
while (curNode != null) {
map.put(curNode, new Node(curNode.value));
curNode = curNode.next;
}
curNode = head;
while (curNode != null) {
// map.get(cur) 新
map.get(curNode).next = map.get(curNode.next);
map.get(curNode).rand = map.get(curNode.rand);
curNode = curNode.next;
}
return map.get(head);
}
法2:不用hash 表 ,实现拷贝
第一次遍历,建立新的节点插入到老节点的后方
第二次遍历设置rand指针,
如果1的rand指向 ---> 3
则 3的拷贝节点就在 3后方
将 1的拷贝节点的rand 指针指向3 的后一个节点
最后再将拷贝的节点串联起来返回其头节点
public static Node process2(Node head) {
if (head == null) {
return null;
}
Node cur = head;
Node next = null;
// copy node and link to every node
//1->2
//1-> 1’->2
while (cur != null) {
next = cur.next;
cur.next = new Node(cur.value);
cur.next.next = next;
cur = next;
}
cur = head;
Node curCopy = null;
// set copy node rand
//1->1’->2->2'
while (cur != null) {
next = cur.next.next;
curCopy = cur.next;
curCopy.rand = cur.rand != null ? cur.next.rand : null;
cur = next;
}
// head head.next
Node res = head.next;
cur = head;
// split
while (cur != null) {
next = cur.next.next;
curCopy = cur.next;
cur.next = next;
curCopy.next = next != null ? next.next : null;
cur = next;
}
return res;
}
2022.02.24
1.两个单链表相交问题
题目:两个可能有环或者五环的单链表,头节点head1和head2,请实现:两个单链表若相交,返回相交的第一个节点,否则干活null。
// main()直接调用函数
public static Node getIntersectNode(Node head1, Node head2) {
if (head1 == null || head2 == null) {
return null;
}
Node loop1 = getLoopNode(head1);
Node loop2 = getLoopNode(head2);
if (loop1 == null && loop2 == null) { //若两个链表都无环
return noLoop(head1, head2);
}
if (loop1 != null && loop2 != null) { //若两个链表都有环
return bothLoop(head1, loop1, head2, loop2);
}
return null;
}
// 1.传入单链表头节点,若有环则返回欢头节点,否则为null
public static Node hasCircle(Node head) {
if (head == null || head.next == null || head.next.next == null) {
return null;
}
Node slow = head.next;
Node fast = head.next.next;
while (slow != fast) {
if (fast.next == null || fast.next.next == null) {
return null;
}
slow = slow.next;
fast = fast.next.next;
}
fast = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return fast;
}
// 2.若两个单链表无环
public static Node noLoop(Node head1, Node head2) {
if (head1 == null || head2 == null) {
return null;
}
Node cur1 = head1;
Node cur2 = head2;
int n = 0;
while (cur1.next != null) {
n++;
cur1 = cur1.next;
}
while (cur2.next != null) {
n--;
cur2 = cur2.next;
}
if (cur1 != cur2) { //相当于无相交
return null;
}
cur1 = n > 0 ? head1 : head2; //谁长,cur1接谁
cur2 = cur1 == head1 ? head2 : head1;
n = Math.abs(n);
while (n != 0) {
n--;
assert cur1 != null;
cur1 = cur1.next;
}
while (cur1 != cur2) {
assert cur1 != null;
cur1 = cur1.next;
assert cur2 != null;
cur2 = cur2.next;
}
return cur1;
}
//两个有环单链表,返回第一个相交节点,否则null
public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
Node cur1 = null;
Node cur2 = null;
if (loop1 == loop2) {
cur1 = head1;
cur2 = head2;
int n = 0;
while (cur1 != loop1) {
n++;
cur1 = cur1.next;
}
while (cur2 != loop2) {
n--;
cur2 = cur2.next;
}
cur1 = n > 0 ? head1 : head2; //谁长,cur1接谁
cur2 = cur1 == head1 ? head2 : head1;
n = Math.abs(n);
while (n != 0) {
n--;
assert cur1 != null;
cur1 = cur1.next;
}
while (cur1 != cur2) {
assert cur1 != null;
cur1 = cur1.next;
assert cur2 != null;
cur2 = cur2.next;
}
return cur1;
} else {
cur1 = loop1.next;
while (cur1 != loop1) {
if (cur1 == loop2) {
return loop1;
}
cur1 = cur1.next;
}
return null;
}
2022.02.25
1.题目:找到树中哪一层最宽有几个节点
// hash表法
public static int getMaxLevelTree(Node head) {
if (head == null) {
return 0;
}
LinkedList<Node> queue = new LinkedList<>();
queue.add(head);
HashMap<Node, Integer> levelMap = new HashMap<>();
levelMap.put(head, 1);
int curLevel = 1; //当前层
int curLevelNodes = 0; //当前层节点数
int max = Integer.MIN_VALUE;
while (!queue.isEmpty()) {
Node cur = queue.poll();
int curNodeLevel = levelMap.get(cur);
if (curNodeLevel == curLevel) {
curLevelNodes++;
} else {
max = Math.max(max, curLevelNodes);
curLevel++;
curLevelNodes = 1;
}
System.out.println(" " + head.value);
if (cur.left != null) {
levelMap.put(cur.left, curNodeLevel + 1);
queue.add(head.left);
}
if (cur.right != null) {
levelMap.put(cur.right, curNodeLevel + 1);
queue.add(head.right);
}
}
return max;
}
2022.03.02
1.题目:给定二叉树的两个节点Node1,Node2,求出最低公共祖先
//前提:o1,o2必须是以head为头的节点
public static Node lowestAncestor(Node head, Node o1, Node o2) {
if (head == null || head == o1 || head == o2) {
return head;
}
Node left = lowestAncestor(head.left, o1, o2);
Node right = lowestAncestor(head.right, o1, o2);
if (left != null && right != null) {
return head;
}
return left != null ? left : right;
}
2.求二叉树对应节点的后继节点
// 求后继节点
public static Node getSuccessorNode(Node node) {
if (node == null) {
return node;
}
if (node.right != null) {
return getMostRight(node.right);
} else { //无右子树
Node parent = node.parent;
while (parent != null && parent.left != node) {
node = parent;
parent = node.parent;
}
return parent;
}
}
//求该节点的最左节点
public static Node getMostRight(Node node) {
if (node == null) {
return node;
}
while (node.left != null) {
node = node.left;
}
return node;
}
3.折纸问题
public class AllFoldsTest {
public static void main(String[] args) {
printAllFolds(4);
}
public static void printAllFolds(int n) {
process(1, n, true);
}
// i是节点的层数,n是总层数,down true 凹,down false 凸
public static void process(int i, int n, boolean down) {
if (i > n) {
return;
}
process(i + 1, n, true);
System.out.print(down ? "凹 " : "凸 ");
process(i + 1, n, false);
}
}
2022.03.04
0.岛屿问题
一个矩阵中只有0和1两种值,每个位置都可以和自己的上、下、左、右
- 四个位置相连,如果有一片1连在一起,这个部分叫做一个岛,求一个矩阵中有多少个岛?
- 举例: 0 0 1 0 1 0 1 1 1 0 1 0 1 0 0 1 0 0 0 0 0 0 0 0 这个矩阵中有三个岛。
public static int countIslands(Int[][] m){
if(m == null || m[0] == null){
return 0;
}
int N = m.length;
int M = m[0].length;
int res = 0;
for(int i = 0;i<N;i++){
for(int j = 0;j<M;j++{
if(m[i][j] == 1){
res++;
infect(m,i,j,N,M);
}
}
}
return res;
}
public static void infect(int[][]m,int i,int j,int N,int M){
if(i<0 || i>=N||j<0||j>=M || m[i][j] != 1){
return ;
}
m[i][j] =2;
infect(m,i+1,j,N,M);
infect(m,i,j+1,N,M);
infect(m,i-1,j,N,M);
infect(m,i,j-1,N,M);
}
2022.03.06
1.会议问题
题目:给出每一个项目的开始时间和结束时间(数组),让你来安排日程,要求会议室的宣讲次数最多,干活这个最多场次的数量。
public class BestArrange {
public static void main(String[] args) {
}
public static class Program {
public int start;
public int end;
public Program(int start, int end) {
this.start = start;
this.end = end;
}
}
public static class ProGramComparator implements Comparator<Program> {
@Override
public int compare(Program o1, Program o2) {
return o1.end - o2.end;
}
}
public static int bestArrange(Program[] programs, int timePoint) {
Arrays.sort(programs, new ProGramComparator());
int result = 0;
for (int i = 0; i < programs.length; i++) {
if (timePoint <= programs[i].start) {
result++;
timePoint = programs[i].end;
}
}
}
}
2022.3.24
贪心算法---考虑是否能够用霍夫曼编码
1、按最低字典序拼接字符串
- 题目:给定一个字符串类型的数组strs,找到一种拼接方式,使得把所有字符串拼起来之后形成的字符串具有最低的字典序。
字典序:对于两个字符串
- 长度不一样,将短的个补0(0是最小ASCII码值),补成长度一样;
- 先按首字符排序,如果首字符相同,再按第二个字符排序,以此类推。如aa,ab,ba,bb,bc就是一个字典序,从小到大。
【分析】贪心:你定一个指标,按这个指标来,对每个样本分出个优先,优先级大的先处理,优先级小的后处理。
- 本题的贪心策略就是你选择的比较策略
- str1.str2 <= str2.str1,则 str1 放前面,否则 str2 放前面【根据两个字符串拼接的结果的大小来决定排序】,不能直接根据str1和str2的大小比较决定位置排放,比如:b和ba,最小的字典序应该是bab而不是bba。
/**
* title:贪心算法--->按最低字典序拼接字符串
* 给定一个字符串类型的数组strs,找到一种拼接方式,使得把所有字符串拼起来之后形成的字符串具有最低的字典序。
*/
public class StrSort {
public static void main(String[] args) {
String[] strs = {"s","hikj","o","022"};
System.out.println(new StrSort().getLowest(strs));
}
//自定义比较器
public class MyComparator implements Comparator<String> {
@Override
public int compare(String o1, String o2) {
return (o1 + o2).compareTo(o2 + o1); //那个小放前面
}
}
public String getLowest(String[] strs) {
if (strs == null || strs.length == 0) {
return "";
}
Arrays.sort(strs, new MyComparator());
String res = "";
for (String str : strs) {
res += str;
}
return res;
}
}
2.切分金条总代价最小
题目:一块金条切成两半,是需要花费和长度数值一样的铜板的。比如:长度为20的金条,不管切成长度多大的两半,都要花费20个铜板。一群人想整分整块金条,怎么分最省铜板?
例如:给定数组{10, 20, 30},代表一共三个人,整块金条长度为 10+20+30=60. 金条要分成10, 20, 30三个部分。 如果, 先把长度60的金条分成10和50,花费60,再把长度50的金条分成20和30,花费50,一共花费110铜板。
但是如果先把长度60的金条分成30和30,花费60,再把长度30金条分成10和20,花费30 一共花费90铜板。
输入一个数组,返回分割的最小代价。
【分析】:贪心:每次合并代价最小的,设总代价为 cost = 0
- 1)把数组的元素放入优先队列(小根堆)中;
- 2)每次弹出最小的两个数【使其代价最小,因为贪心算法就是局部最优】,然后相加的结果为 c,总代价加上 c,并且将 c 放入堆中;
- 3)重复1)、2)步骤,直到堆中只剩有一个数结束。
- 【注意】: 优先队列是小根堆,你认为谁该在前面,就通过比较器把它的优先级设小【并不是实际数值小就在前面,也可能实际数值大在前面,看你比较器怎么弄了,返回负数:表示o1小于o2】 标准的霍夫曼编码问题:先选两个最小的合并,然后再往上合并(如下图所示)。合并是从下往上,切割的是从上往下:先将60切成30、30,再将其中一个30切成10、20,最后就将60切成:10、20、30
补充:堆结构的扩展与应用【经常用于贪心】:
- 堆:用于在一群数据中拿出最好的那个(根据自定义的比较器不同实现不同的堆,比较器就是贪心的标准),默认建的是小根堆(优先级小的放前面)。
/**
* title:贪心算法----哈夫曼编码--->切分金条总代价最小
* 一块金条切成两半,是需要花费和长度数值一样的铜板的。
* 比如:长度为20的金条,不管切成长度多大的两半,都要花费20个铜板。一群人想整分整块金条,怎么分最省铜板?
*/
public class GoldOperate {
public static void main(String[] args) {
int[] arr = {10, 20, 30};
System.out.println(new GoldOperate().getLowestCost(arr));
}
// 自定义比较器
public static class MyComparator implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2; //负数,说明小的在前
}
}
public Integer getLowestCost(int[] arr) {
//优先级队列是小根堆
PriorityQueue<Integer> pq = new PriorityQueue<>(new MyComparator());
for (int i : arr) {
pq.add(i);
}
int result = 0;
int costOne = 0; //一次的花费
while (pq.size() > 1) {
costOne = pq.poll() + pq.poll();
result += costOne;
pq.add(costOne);
}
return result;
}
}
3.最多做 K 个项目的最大利润
题目:costs[]:花费 ,costs[i] 表示 i 号项目的花费 profits[]:利润, profits[i] 表示 i 号项目在扣除花费之后还能挣到的钱(利润)。一次只能做一个项目,最多做 k 个项目,m 表示你初始的资金。(说明:你每做完一个项目,马上获得的收益,可以支持你去做下一个项目)求你最后获得的最大钱数。
【分析】贪心:每次总是做能够做的项目中利润最大的。
- 准备一个小根堆和大根堆,小根堆放着全部的项目,按谁花费(成本)最低就在头部。
- 1、若小根堆不为空,项目也没做完 K 个,则每次先从小根堆解锁能够做的项目,放入大根堆(大根堆按照解锁的项目中谁的利润最大放在头部);
- 2、大根堆不为空,从大根堆弹出堆顶项目来做(即利润最大的项目,每次只弹出堆顶一个项目来做);
- 3、把 m 加上利润,初始资金增加,再重复1)、2)步骤。
\
/**
* title:贪心酸饭----- 最多做 K 个项目的最大利润
* ----------------------------------------------------------------------------
* 题目:costs[]:花费 ,costs[i] 表示 i 号项目的花费 profits[]:利润,
* profits[i] 表示 i 号项目在扣除花费之后还能挣到的钱(利润)。一次只能做一个项目,
* 最多做 k 个项目,m 表示你初始的资金。
* (说明:你每做完一个项目,马上获得的收益,可以支持你去做下一个项目)求你最后获得的最大钱数。
*/
public class ProjectProfit {
//节点
public class Node {
private int profit;
private int cost;
public Node(int profit, int cost) {
this.profit = profit;
this.cost = cost;
}
}
/**
* 发现最多的利润
* @param k 可以执行的项目
* @param allCosts 预算
* @param profits 利润
* @param costs 成本
* @return 结余
*/
public int findMaxProfit(int k, int allCosts, int[] profits, int[] costs) {
Node[] nodes = new Node[profits.length];
for (int i = 0; i < nodes.length; i++) {
nodes[i] = new Node(profits[i], costs[i]);
}
// 优先级队列是谁小谁放在前面,比较器决定谁小
PriorityQueue<Node> minPQ = new PriorityQueue<>(new MinCostComponent());
PriorityQueue<Node> maxPQ = new PriorityQueue<>(new MaxProfitComponent());
for (int i = 0; i < nodes.length; i++) {
minPQ.add(nodes[i]);// 将所有的项目插入成本堆中
}
//开始解锁新项目
for (int j = 0; j < k; j++) {
// 解锁项目的前提条件:成本堆中还有项目未被解锁并且该项目的成本小于当前的总资金
while (!minPQ.isEmpty() && minPQ.peek().cost <= allCosts) {
maxPQ.add(minPQ.poll());
}
if (maxPQ.isEmpty()) {
// 如果maxPQ为空,则说明没有当前资金能够解锁的新项目了,之前解锁的项目也做完了,即无项目可做
return allCosts;
}
allCosts = maxPQ.poll().profit; // 做利润最大的项目
}
return allCosts;
}
//成本最小堆
public class MinCostComponent implements Comparator<Node> {
@Override
public int compare(Node o1, Node o2) {
return o1.cost - o2.cost;//小的在前
}
}
// 利润最大堆
public class MaxProfitComponent implements Comparator<Node> {
@Override
public int compare(Node o1, Node o2) {
return o2.profit - o1.profit; //大的在前
}
}
}
4.安排最多的宣讲场次
题目:一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。 给你每一个项目开始的时间和结束的时间(给你一个数组,里面是一个个具体项目),你来安排宣讲的日程,要求会议室进行的宣讲的场次最多。返回这个最多的宣讲场次。
- 贪心策略1:不能按照哪个项目开始的早先安排哪个,因为可能开始早的占用时间非常长,显然不合理;
- 贪心策略2:项目持续的时间短优先安排也不合理,因为可能存在时间短的项目时间点正好在其他两个时间长项目中间,这样因为这一个项目就会浪费掉其他两个项目,显然也是不合理的;
- 贪心策略3:按照哪个项目先结束来排。先做结束最早的项目,然后淘汰因为这个做这个项目而不能做的项目(时间冲突),依次这样去做。
/**
* title:贪心算法---------安排最多的宣讲场次
* 一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。
* 给你每一个项目开始的时间和结束的时间(给你一个数组,里面是一个个具体项目),
* 你来安排宣讲的日程,要求会议室进行的宣讲的场次最多。返回这个最多的宣讲场次。
*/
public class Speech {
public static class Program {
public int start;
public int end;
public Program(int start, int end) {
this.start = start;
this.end = end;
}
}
/**
* 获取最佳的宣讲安排
* @param programs 会议
* @param cur 当前时间
* @return
*/
public int getBestArrange(Program[] programs, int cur) {
Arrays.sort(programs, new MyComparator());
int res = 0;
for (int i = 0; i < programs.length; i++) {
// 只有当前时间早于第i个项目的开始时间时,才可以安排
if (cur < programs[i].start) {
res++;
cur = programs[i].end; //当前时间改为结束时间
}
}
return res;
}
// 按照项目的结束时间早来排序,即实现小根堆
public static class MyComparator implements Comparator<Program> {
@Override
public int compare(Program o1, Program o2) {
return o1.end - o2.end;
}
}
}
2022.03.26
1.汉诺塔问题
打印n层汉诺塔从最左边移到最右边的过程
/**
* title:暴力递归---> 汉诺塔
*/
public class HanoiTest {
public static void hanoi(int n) {
if (n > 0) {
func(n, "左", "中", "右");
}
}
private static void func(int i, String start, String end, String other) {
if (i == 1) {
System.out.println("move 1 from " + start + " to " + end);
} else {
func(i - 1, start, other, end);
System.out.println("move " + i + " from " + start + " to " + end);
func(i - 1, other,end,start);
}
}
public static void main(String[] args) {
hanoi(5);
}
}
2.打印一个字符串的全部子序列,包括空字符串
/**
* Editor: hengBao
* Wechat:zh17530588817
* date: 2022/3/26/19:05
* title:打印一个字符串的全部子序列,包括空字符串
*/
public class PrintChildStr {
public static void printChildStr(String str) {
process(str.toCharArray(), 0, new ArrayList<Character>());
}
//当前来到i位置,要和不要,走两条路
//之前的选择,所形成的结果,是str
private static void process(char[] chars, int i, List<Character> res) {
if (i == chars.length) {
printList(res);
return;
}
List<Character> resKeep = copyList(res);
assert resKeep != null;
resKeep.add(chars[i]);
//要当前字符
process(chars, i + 1, resKeep);
//不要当前字符
List<Character> resNoInclude = copyList(res);
process(chars, i + 1, resNoInclude);
}
public static void printList(List<Character> res) {
//自己补充
}
public static List<Character> copyList(List<Character> list) {
//自己补充
return null;
}
}
2022.03.26
1.拿牌问题
/**
* title:暴力递归---> 拿牌问题
*/
public class CardsInLine {
public static void main(String[] args) {
int[] arr = { 1, 9, 5, 1 };
System.out.println(win(arr));
System.out.println(win2(arr));
}
//todo:方法一---> 递归
public static int win(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
return Math.max(first(arr, 0, arr.length - 1), after(arr, 0, arr.length - 1));
}
//先手拿牌
public static int first(int[] arr, int left, int right) {
if (left == right) {
return arr[left]; // 只剩下一张牌
}
return Math.max(arr[left] + after(arr, left + 1, right), arr[right] + after(arr, left, right - 1));
}
//后手拿牌
public static int after(int[] arr, int left, int right) {
if (left == right) {
return 0; // 无牌可拿
}
return Math.min(first(arr, left + 1, right), first(arr, left, right - 1));
}
//todo:方法2---> 动态规划
public static int win2(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
int[][] f = new int[arr.length][arr.length];
int[][] s = new int[arr.length][arr.length];
for (int j = 0; j < arr.length; j++) {
f[j][j] = arr[j];
for (int i = j - 1; i >= 0; i--) {
f[i][j] = Math.max(arr[i] + s[i + 1][j], arr[j] + s[i][j - 1]);
s[i][j] = Math.min(f[i + 1][j], f[i][j - 1]);
}
}
return Math.max(f[0][arr.length - 1], s[0][arr.length - 1]);
}
}
2.字母和数字对应问题
/**
* title:暴力递归-->字母和数字对应问题
* 规定1和A对应,2和B对应,3和C对应。。
* 那么一个数字字符串比如“111”,就可以转化为“AAA”,“KA”,和“AK”
* 给定一个只有数字字符组成的字符串str,返回有多少种转化结果
*/
public class CharToNum {
public static void main(String[] args) {
String str = "12312";
System.out.println(process(str.toCharArray(), 0));
}
public static int process(char[] str, int i) {
if (i == str.length) {
return 1;
}
if (i == '0') {
return 0;
}
if (str[i] == '1') {
int res = process(str, i + 1);
if (i + 1 < str.length) {
res += process(str, i + 2);
}
return res;
}
if (str[i] == '2') {
int res = process(str, i + 1);
if (i + 1 < str.length && str[i + 1] >= '0' && str[i + 1] <= '6') {
res += process(str, i + 1);
}
return res;
}
return process(str, i + 1);
}
}
3.背包问题
/**
* title:背包问题
* 给定两个长度都为N的数组weight和values,weight[i]和values[i]分别表示i号物品的重量和价值。
* 给定一个正数bag,表示一个载重bag的袋子,你装的物品不能超过这个重量,返回你能装下的最多的价值是多少
*/
public class BagProcess {
public static void main(String[] args) {
}
// i..的货物自由选择,形成的最大价值返回
// 重量不要超过bag
// 之前做过的决定所形成的重量,alreadyWeight
public static int process(int[] weight, int[] values, int i, int alreadyWeight, int bag) {
if (alreadyWeight > bag) {
return 0;
}
if (i == weight.length) {
return 0;
}
return Math.max(
process(
// 不要i号货物
weight, values, i + 1, alreadyWeight, bag),
//要i号货物
values[i] + process(weight, values, i + 1, alreadyWeight + weight[i], bag)
);
}