目录
class07
package class07;
public class Code01_RecursiveTraversalBT {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int v) {
value = v;
}
}
public static void f(Node head) {
if (head == null) {
return;
}
/**此处打印是先序遍历*/
// System.out.println(head.value);
f(head.left);
/**此处打印是中序遍历*/
// System.out.println(head.value);
f(head.right);
/**此处打印是后序遍历*/
// System.out.println(head.value);
}
public static void pre(Node head) {
if (head == null) {
return;
}
System.out.println(head.value);
pre(head.left);
pre(head.right);
}
public static void in(Node head) {
if (head == null) {
return;
}
in(head.left);
System.out.println(head.value);
in(head.right);
}
public static void pos(Node head) {
if (head == null) {
return;
}
pos(head.left);
pos(head.right);
System.out.println(head.value);
}
public static void main(String[] args) {
Node head = new Node(1);
head.left = new Node(2);
head.right = new Node(3);
head.left.left = new Node(4);
head.left.right = new Node(5);
head.right.left = new Node(6);
head.right.right = new Node(7);
pre(head);
System.out.println("========");
in(head);
System.out.println("========");
pos(head);
System.out.println("========");
}
}
package class07;
import java.util.Stack;
public class Code02_UnRecursiveTraversalBT {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int v) {
value = v;
}
}
/**
* 先序遍历
*
* @param head
*/
public static void pre(Node head) {
System.out.print("pre-order: ");
if (head != null) {
Stack<Node> stack = new Stack<Node>();
stack.add(head);
while (!stack.isEmpty()) {
head = stack.pop();
System.out.print(head.value + " ");
if (head.right != null) {
stack.push(head.right);
}
if (head.left != null) {
stack.push(head.left);
}
}
}
System.out.println();
}
/**
* 中序遍历
*
* @param head
*/
public static void in(Node head) {
System.out.print("in-order: ");
if (head != null) {
Stack<Node> stack = new Stack<Node>();
while (!stack.isEmpty() || head != null) {
if (head != null) {
//节点存在->将节点入栈-跳左子树
stack.push(head);
head = head.left;
} else {
//左节点不存在->弹出栈顶并打印-跳栈顶元素的右节点
head = stack.pop();
System.out.print(head.value + " ");
head = head.right;
}
}
}
System.out.println();
}
/**
* 后续遍历
* 使用2个栈实现
* 通过s2这个栈存储中间结果
*
* @param head
*/
public static void pos1(Node head) {
System.out.print("pos-order: ");
if (head != null) {
Stack<Node> s1 = new Stack<Node>();
Stack<Node> s2 = new Stack<Node>();
s1.push(head);
while (!s1.isEmpty()) {
head = s1.pop();
s2.push(head);
if (head.left != null) {
s1.push(head.left);
}
if (head.right != null) {
s1.push(head.right);
}
}
while (!s2.isEmpty()) {
System.out.print(s2.pop().value + " ");
}
}
System.out.println();
}
/**
* 后续遍历 炫技版
* 使用1个栈实现
*/
public static void pos2(Node h) {
System.out.print("pos-order: ");
if (h != null) {
Stack<Node> stack = new Stack<Node>();
stack.push(h);
Node c = null;
while (!stack.isEmpty()) {
c = stack.peek();
/**
* h记录上一次打印的点
* 如果当前节点c的左树被处理过,h是当前节点的左孩子
* 如果当前节点c的右树被处理过,h是当前节点的右孩子,并且左树在之前已经被处理过了
*/
// 上次打印的不是当前节点的左孩子和右孩子,说明当前节点之前没被处理过,处理左树
// 判断左树有没有处理过:当上一次打印的点是当前节点的右孩子时,即h==c.right,左树肯定处理过了,因为是左右中的后续遍历
if (c.left != null && h != c.left && h != c.right) {
stack.push(c.left);
}
// 左树处理完了,h不是当前节点的右孩子,说明右树没被处理过,处理右树
else if (c.right != null && h != c.right) {
stack.push(c.right);
}
// 左右树都处理完了,处理自己即打印
else {
System.out.print(stack.pop().value + " ");
// h记录上一次打印的点
h = c;
}
}
}
System.out.println();
}
public static void main(String[] args) {
Node head = new Node(1);
head.left = new Node(2);
head.right = new Node(3);
head.left.left = new Node(4);
head.left.right = new Node(5);
head.right.left = new Node(6);
head.right.right = new Node(7);
pre(head);
System.out.println("========");
in(head);
System.out.println("========");
pos1(head);
System.out.println("========");
pos2(head);
System.out.println("========");
}
}
package class07;
import java.util.LinkedList;
import java.util.Queue;
public class Code03_LevelTraversalBT {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int v) {
value = v;
}
}
public static void level(Node head) {
if (head == null) {
return;
}
Queue<Node> queue = new LinkedList<>();
queue.add(head);
while (!queue.isEmpty()) {
Node cur = queue.poll();
System.out.println(cur.value);
if (cur.left != null) {
queue.add(cur.left);
}
if (cur.right != null) {
queue.add(cur.right);
}
}
}
public static void main(String[] args) {
Node head = new Node(1);
head.left = new Node(2);
head.right = new Node(3);
head.left.left = new Node(4);
head.left.right = new Node(5);
head.right.left = new Node(6);
head.right.right = new Node(7);
level(head);
System.out.println("========");
}
}
package class07;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
public class Code04_SerializeAndReconstructTree {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
/**
* 先序遍历的序列化
*/
public static Queue<String> preSerial(Node head) {
Queue<String> ans = new LinkedList<>();
pres(head, ans);
return ans;
}
public static void pres(Node head, Queue<String> ans) {
if (head == null) {
ans.add(null);// 不存在则放入null
} else {
// 中-左-右
ans.add(String.valueOf(head.value));//存在则放入节点的值
pres(head.left, ans);
pres(head.right, ans);
}
}
/**
* 先序遍历的反序列化
*/
public static Node buildByPreQueue(Queue<String> prelist) {
if (prelist == null || prelist.size() == 0) {
return null;
}
return preb(prelist);
}
public static Node preb(Queue<String> prelist) {
String value = prelist.poll();
if (value == null) {
return null;
}
Node head = new Node(Integer.valueOf(value));
head.left = preb(prelist);//prelist已经弹出一个了,当前prelist头部是head的左节点的序列化值
head.right = preb(prelist);
return head;
}
/**
* 后续遍历(左右中)的反序列化
*/
public static Node buildByPostQueue(Queue<String> postList){
if (postList==null||postList.size()==0){
return null;
}
// 左右中-》栈-》栈中顺序 中右左
Stack<String> stack = new Stack<>();
while (!postList.isEmpty()){
stack.push(postList.poll());
}
return posb(stack);
}
public static Node posb(Stack<String> postStack){
String value = postStack.pop();
if (value==null){
return null;
}
Node head = new Node(Integer.parseInt(value));// 中
head.right = posb(postStack);// 右
head.left = posb(postStack);// 左
return head;
}
/**
* 按层序列化
*/
public static Queue<String> levelSerial(Node head) {
Queue<String> syncResult = new LinkedList<>();//保存序列化结果
if (head == null) {
syncResult.add(null);//节点为空,序列化保存为null
// 节点不存在,不入辅助队列queue
} else {
syncResult.add(String.valueOf(head.value));//节点不为空,序列化结果为节点值
Queue<Node> queue = new LinkedList<Node>();
queue.add(head);//节点存在则入队列[不存在则不入队列]
while (!queue.isEmpty()) {
head = queue.poll();
// 左/右节点存在,同时入 序列化结果队列 和 辅助队列
// 左/右节点不存在,只入 序列化结果队列,不入 辅助队列
if (head.left != null) {
syncResult.add(String.valueOf(head.left.value));
queue.add(head.left);
} else {
syncResult.add(null);
}
if (head.right != null) {
syncResult.add(String.valueOf(head.right.value));
queue.add(head.right);
} else {
syncResult.add(null);
}
}
}
return syncResult;
}
/**
* 按层反序列化
*/
public static Node buildByLevelQueue(Queue<String> levelList) {
if (levelList == null || levelList.size() == 0) {
return null;
}
Node head = generateNode(levelList.poll());
Queue<Node> queue = new LinkedList<Node>();
if (head != null) {
queue.add(head);
}
Node node = null;
while (!queue.isEmpty()) {
node = queue.poll();
node.left = generateNode(levelList.poll());
node.right = generateNode(levelList.poll());
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
return head;
}
public static Node generateNode(String val) {
if (val == null) {
return null;
}
return new Node(Integer.valueOf(val));
}
// for test
public static Node generateRandomBST(int maxLevel, int maxValue) {
return generate(1, maxLevel, maxValue);
}
// for test
public static Node generate(int level, int maxLevel, int maxValue) {
if (level > maxLevel || Math.random() < 0.5) {
return null;
}
Node head = new Node((int) (Math.random() * maxValue));
head.left = generate(level + 1, maxLevel, maxValue);
head.right = generate(level + 1, maxLevel, maxValue);
return head;
}
// for test
public static boolean isSameValueStructure(Node head1, Node head2) {
if (head1 == null && head2 != null) {
return false;
}
if (head1 != null && head2 == null) {
return false;
}
if (head1 == null && head2 == null) {
return true;
}
if (head1.value != head2.value) {
return false;
}
return isSameValueStructure(head1.left, head2.left) && isSameValueStructure(head1.right, head2.right);
}
public static void main(String[] args) {
int maxLevel = 5;
int maxValue = 100;
int testTimes = 1000000;
for (int i = 0; i < testTimes; i++) {
Node head = generateRandomBST(maxLevel, maxValue);
Queue<String> pre = preSerial(head);
Queue<String> level = levelSerial(head);
Node preBuild = buildByPreQueue(pre);
Node levelBuild = buildByLevelQueue(level);
if (!isSameValueStructure(preBuild, levelBuild)) {
System.out.println("Oops!");
}
}
System.out.println("finish!");
}
}
package class07;
public class Code05_PrintBinaryTree {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static void printTree(Node head) {
System.out.println("Binary Tree:");
printInOrder(head, 0, "H", 17);
System.out.println();
}
/**
*
* @param head
* @param height 当前节点在第几层
* @param to 值两边的符号,如v36v代表36从直观的结果在某个节点的上面,在二叉树上即某个节点的右节点
* @param len 数值部分所占的长度,固定17
*/
public static void printInOrder(Node head, int height, String to, int len) {
if (head == null) {
return;
}
//处理右节点
printInOrder(head.right, height + 1, "v", len);
//处理自己,即如何打印当前节点
String val = to + head.value + to;// 给当前值前后加符号
int lenM = val.length();//值的长度[在中间]
int lenL = (len - lenM) / 2;//值首位的前面需要补多少空格
int lenR = len - lenM - lenL;//值末位的后面需要补多少空格
val = getSpace(lenL) + val + getSpace(lenR);// 空格+值+空格,总长度17
// 在整个长度17的值前面需要根据节点层数再补空格
// 头节点左边不需要补,因为头节点长度17,所以第一层节点左边补1*17,又第一层节点+第二层节点长度=17*2,所以第二层节点左边补2*17...
System.out.println(getSpace(height * len) + val);
// 处理左节点
printInOrder(head.left, height + 1, "^", len);
}
public static String getSpace(int num) {
String space = " ";
StringBuffer buf = new StringBuffer("");
for (int i = 0; i < num; i++) {
buf.append(space);
}
return buf.toString();
}
public static void main(String[] args) {
Node head = new Node(1);
head.left = new Node(-222222222);
head.right = new Node(3);
head.left.left = new Node(Integer.MIN_VALUE);
head.right.left = new Node(55555555);
head.right.right = new Node(66);
head.left.left.right = new Node(777);
printTree(head);
head = new Node(1);
head.left = new Node(2);
head.right = new Node(3);
head.left.left = new Node(4);
head.right.left = new Node(5);
head.right.right = new Node(6);
head.left.left.right = new Node(7);
printTree(head);
head = new Node(1);
head.left = new Node(1);
head.right = new Node(1);
head.left.left = new Node(1);
head.right.left = new Node(1);
head.right.right = new Node(1);
head.left.left.right = new Node(1);
printTree(head);
}
}
package class07;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Queue;
public class Code06_TreeMaxWidth {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
/**
* 求数的最大宽度
* 即求树的节点数最多的层有多少节点
* 队列 + hashmap 实现
*/
public static int maxWidthUseMap(Node head) {
if (head == null) {
return 0;
}
Queue<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 = 0;// 节点最多的层的节点数
while (!queue.isEmpty()) {
Node cur = queue.poll();
int curNodeLevel = levelMap.get(cur);
if (cur.left != null) {
levelMap.put(cur.left, curNodeLevel + 1);
queue.add(cur.left);
}
if (cur.right != null) {
levelMap.put(cur.right, curNodeLevel + 1);
queue.add(cur.right);
}
// 没有跳层,当前层节点数++
if (curNodeLevel == curLevel) {
curLevelNodes++;
} else {
// 来到新层时结算上一层、由于是首次来到新层,层数+1,新层节点数=1
max = Math.max(max, curLevelNodes);
curLevel++;
curLevelNodes = 1;
}
}
// 结算最后一层
max = Math.max(max, curLevelNodes);
return max;
}
public static int maxWidthNoMap(Node head) {
if (head == null) {
return 0;
}
Queue<Node> queue = new LinkedList<>();
queue.add(head);
Node curEnd = head;
Node nextEnd = null;
int max = 0;
int curLevelNodes = 0;
while (!queue.isEmpty()) {
Node cur = queue.poll();
if (cur.left != null) {
queue.add(cur.left);
nextEnd = cur.left;
}
if (cur.right != null) {
queue.add(cur.right);
nextEnd = cur.right;
}
curLevelNodes++;
if (cur == curEnd) {
max = Math.max(max, curLevelNodes);
curLevelNodes = 0;
curEnd = nextEnd;
}
}
return max;
}
// for test
public static Node generateRandomBST(int maxLevel, int maxValue) {
return generate(1, maxLevel, maxValue);
}
// for test
public static Node generate(int level, int maxLevel, int maxValue) {
if (level > maxLevel || Math.random() < 0.5) {
return null;
}
Node head = new Node((int) (Math.random() * maxValue));
head.left = generate(level + 1, maxLevel, maxValue);
head.right = generate(level + 1, maxLevel, maxValue);
return head;
}
public static void main(String[] args) {
int maxLevel = 10;
int maxValue = 100;
int testTimes = 1000000;
for (int i = 0; i < testTimes; i++) {
Node head = generateRandomBST(maxLevel, maxValue);
if (maxWidthUseMap(head) != maxWidthNoMap(head)) {
System.out.println("Oops!");
}
}
System.out.println("finish!");
}
}
package class07;
public class Code07_SuccessorNode {
public static class Node {
public int value;
public Node left;
public Node right;
public Node parent;// 指向父节点的指针!
public Node(int data) {
this.value = data;
}
}
public static Node getSuccessorNode(Node node) {
if (node == null) {
return node;
}
if (node.right != null) {
return getLeftMost(node.right);
} else { // 无右子树
Node parent = node.parent;
while (parent != null && parent.left != node) { // 当前节点是其父亲节点右孩子
node = parent;
parent = node.parent;
}
// 当给定的node是整棵树最右节点时,它一直向上找都找不到自己是谁的左孩子,直到上面while循环头节点的parent==null跳出循环返回空
return parent;
}
}
public static Node getLeftMost(Node node) {
if (node == null) {
return node;
}
while (node.left != null) {
node = node.left;
}
return node;
}
public static void main(String[] args) {
Node head = new Node(6);
head.parent = null;
head.left = new Node(3);
head.left.parent = head;
head.left.left = new Node(1);
head.left.left.parent = head.left;
head.left.left.right = new Node(2);
head.left.left.right.parent = head.left.left;
head.left.right = new Node(4);
head.left.right.parent = head.left;
head.left.right.right = new Node(5);
head.left.right.right.parent = head.left.right;
head.right = new Node(9);
head.right.parent = head;
head.right.left = new Node(8);
head.right.left.parent = head.right;
head.right.left.left = new Node(7);
head.right.left.left.parent = head.right.left;
head.right.right = new Node(10);
head.right.right.parent = head.right;
Node test = head.left.left;
System.out.println(test.value + " next: " + getSuccessorNode(test).value);
test = head.left.left.right;
System.out.println(test.value + " next: " + getSuccessorNode(test).value);
test = head.left;
System.out.println(test.value + " next: " + getSuccessorNode(test).value);
test = head.left.right;
System.out.println(test.value + " next: " + getSuccessorNode(test).value);
test = head.left.right.right;
System.out.println(test.value + " next: " + getSuccessorNode(test).value);
test = head;
System.out.println(test.value + " next: " + getSuccessorNode(test).value);
test = head.right.left.left;
System.out.println(test.value + " next: " + getSuccessorNode(test).value);
test = head.right.left;
System.out.println(test.value + " next: " + getSuccessorNode(test).value);
test = head.right;
System.out.println(test.value + " next: " + getSuccessorNode(test).value);
test = head.right.right; // 10's next is null
System.out.println(test.value + " next: " + getSuccessorNode(test));
}
}
package class07;
public class Code08_PaperFolding {
public static void printAllFolds(int N) {
printProcess(1, N, true);
}
/**
* 递归过程,来到了某一个节点
* @param i 节点的层数
* @param N 对折次数,也是折痕映射成树的层数
* @param down down == true 凹 down == false 凸
*/
public static void printProcess(int i, int N, boolean down) {
if (i > N) {
return;
}
/**
* 中序遍历处理顺序:左中右
*/
// 处理左节点,左节点都是凹的
printProcess(i + 1, N, true);
// 处理自己
System.out.println(down ? "凹------------ " : "凸———————————— ");
// 处理右节点,右节点都是凸的
printProcess(i + 1, N, false);
}
public static void main(String[] args) {
int N = 3;
printAllFolds(N);
}
}
class08
package class08;
public class Code01_IsBalanced {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static boolean isBalanced1(Node head) {
boolean[] ans = new boolean[1];
ans[0] = true;
process1(head, ans);
return ans[0];
}
public static int process1(Node head, boolean[] ans) {
if (!ans[0] || head == null) {
return -1;
}
int leftHeight = process1(head.left, ans);
int rightHeight = process1(head.right, ans);
if (Math.abs(leftHeight - rightHeight) > 1) {
ans[0] = false;
}
return Math.max(leftHeight, rightHeight) + 1;
}
public static boolean isBalanced2(Node head) {
return process2(head).isBalaced;
}
// 需要左右节点返回的信息集合
public static class Info {
public boolean isBalaced;
public int height;
public Info(boolean b, int h) {
isBalaced = b;
height = h;
}
}
// 给定节点,返回该节点携带的信息
public static Info process2(Node head) {
if (head == null) {
return new Info(true, 0);
}
Info leftInfo = process2(head.left);
Info rightInfo = process2(head.right);
int height = Math.max(leftInfo.height, rightInfo.height) + 1;
boolean isBalanced = true;
/**
* 平衡:左树平衡+右树平衡+左右树高度差<=1
*/
if (!leftInfo.isBalaced || !rightInfo.isBalaced || Math.abs(leftInfo.height - rightInfo.height) > 1) {
isBalanced = false;
}
return new Info(isBalanced, height);
}
// for test
public static Node generateRandomBST(int maxLevel, int maxValue) {
return generate(1, maxLevel, maxValue);
}
// for test
public static Node generate(int level, int maxLevel, int maxValue) {
if (level > maxLevel || Math.random() < 0.5) {
return null;
}
Node head = new Node((int) (Math.random() * maxValue));
head.left = generate(level + 1, maxLevel, maxValue);
head.right = generate(level + 1, maxLevel, maxValue);
return head;
}
public static void main(String[] args) {
int maxLevel = 5;
int maxValue = 100;
int testTimes = 1000000;
for (int i = 0; i < testTimes; i++) {
Node head = generateRandomBST(maxLevel, maxValue);
if (isBalanced1(head) != isBalanced2(head)) {
System.out.println("Oops!");
}
}
System.out.println("finish!");
}
}
package class08;
public class Code02_IsFull {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static boolean isFull1(Node head) {
if (head == null) {
return true;
}
int height = h(head);
int nodes = n(head);
return (1 << height) - 1 == nodes;
}
public static int h(Node head) {
if (head == null) {
return 0;
}
return Math.max(h(head.left), h(head.right)) + 1;
}
public static int n(Node head) {
if (head == null) {
return 0;
}
return n(head.left) + n(head.right) + 1;
}
public static boolean isFull2(Node head) {
if (head == null) {
return true;
}
Info all = process(head);
return (1 << all.height) - 1 == all.nodes;
}
public static class Info {
public int height;
public int nodes;
public Info(int h, int n) {
height = h;
nodes = n;
}
}
public static Info process(Node head) {
if (head == null) {
return new Info(0, 0);
}
Info leftInfo = process(head.left);
Info rightInfo = process(head.right);
int height = Math.max(leftInfo.height, rightInfo.height) + 1;
int nodes = leftInfo.nodes + rightInfo.nodes + 1;
return new Info(height, nodes);
}
// for test
public static Node generateRandomBST(int maxLevel, int maxValue) {
return generate(1, maxLevel, maxValue);
}
// for test
public static Node generate(int level, int maxLevel, int maxValue) {
if (level > maxLevel || Math.random() < 0.5) {
return null;
}
Node head = new Node((int) (Math.random() * maxValue));
head.left = generate(level + 1, maxLevel, maxValue);
head.right = generate(level + 1, maxLevel, maxValue);
return head;
}
public static void main(String[] args) {
int maxLevel = 5;
int maxValue = 100;
int testTimes = 1000000;
for (int i = 0; i < testTimes; i++) {
Node head = generateRandomBST(maxLevel, maxValue);
if (isFull1(head) != isFull2(head)) {
System.out.println("Oops!");
}
}
System.out.println("finish!");
}
}
package class08;
import java.util.ArrayList;
public class Code03_IsBST {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static boolean isBST1(Node head) {
if (head == null) {
return true;
}
ArrayList<Node> arr = new ArrayList<>();
in(head, arr);
for (int i = 1; i < arr.size(); i++) {
if (arr.get(i).value <= arr.get(i - 1).value) {
return false;
}
}
return true;
}
public static void in(Node head, ArrayList<Node> arr) {
if (head == null) {
return;
}
in(head.left, arr);
arr.add(head);
in(head.right, arr);
}
public static boolean isBST2(Node head) {
if (head == null) {
return true;
}
return process(head).isBST;
}
public static class Info {
boolean isBST;
public int min;
public int max;
public Info(boolean is, int mi, int ma) {
isBST = is;
min = mi;
max = ma;
}
}
public static Info process(Node head) {
if (head == null) {
return null;
}
Info leftInfo = process(head.left);
Info rightInfo = process(head.right);
int min = head.value;
int max = head.value;
if (leftInfo != null) {
min = Math.min(min, leftInfo.min);
max = Math.max(max, leftInfo.max);
}
if (rightInfo != null) {
min = Math.min(min, rightInfo.min);
max = Math.max(max, rightInfo.max);
}
boolean isBST = false;
if (
(leftInfo == null ? true : (leftInfo.isBST && leftInfo.max < head.value))
&&
(rightInfo == null ? true : (rightInfo.isBST && rightInfo.min > head.value))
) {
isBST = true;
}
return new Info(isBST, min, max);
}
// for test
public static Node generateRandomBST(int maxLevel, int maxValue) {
return generate(1, maxLevel, maxValue);
}
// for test
public static Node generate(int level, int maxLevel, int maxValue) {
if (level > maxLevel || Math.random() < 0.5) {
return null;
}
Node head = new Node((int) (Math.random() * maxValue));
head.left = generate(level + 1, maxLevel, maxValue);
head.right = generate(level + 1, maxLevel, maxValue);
return head;
}
public static void main(String[] args) {
int maxLevel = 4;
int maxValue = 100;
int testTimes = 1000000;
for (int i = 0; i < testTimes; i++) {
Node head = generateRandomBST(maxLevel, maxValue);
if (isBST1(head) != isBST2(head)) {
System.out.println("Oops!");
}
}
System.out.println("finish!");
}
}
package class08;
import java.util.ArrayList;
/**
* 给定一棵二叉树的头节点head,
* 返回这颗二叉树中最大的二叉搜索子树的大小
*/
public class Code04_MaxSubBSTSize {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static int getBSTSize(Node head) {
if (head == null) {
return 0;
}
ArrayList<Node> arr = new ArrayList<>();
in(head, arr);
for (int i = 1; i < arr.size(); i++) {
if (arr.get(i).value <= arr.get(i - 1).value) {
return 0;
}
}
return arr.size();
}
public static void in(Node head, ArrayList<Node> arr) {
if (head == null) {
return;
}
in(head.left, arr);
arr.add(head);
in(head.right, arr);
}
public static int maxSubBSTSize1(Node head) {
if (head == null) {
return 0;
}
int h = getBSTSize(head);
if (h != 0) {
return h;
}
return Math.max(maxSubBSTSize1(head.left), maxSubBSTSize1(head.right));
}
public static int maxSubBSTSize2(Node head) {
if (head == null) {
return 0;
}
return process(head).maxSubBSTSize;
}
public static class Info {
public boolean isBST;// 是不是搜索二叉树
public int maxSubBSTSize;// 最大搜索子树的大小
public int min;// 树上的最大值
public int max;// 树上的最小值
public Info(boolean is, int size, int mi, int ma) {
isBST = is;
maxSubBSTSize = size;
min = mi;
max = ma;
}
}
public static Info process(Node head) {
if (head == null) {
return null;
}
Info leftInfo = process(head.left);
Info rightInfo = process(head.right);
// 组织自己的4个数据
int min = head.value;
int max = head.value;
int maxSubBSTSize = 0;
boolean isBST = false;
if (leftInfo != null) {
min = Math.min(min, leftInfo.min);
max = Math.max(max, leftInfo.max);
maxSubBSTSize = Math.max(maxSubBSTSize, leftInfo.maxSubBSTSize);
}
if (rightInfo != null) {
min = Math.min(min, rightInfo.min);
max = Math.max(max, rightInfo.max);
maxSubBSTSize = Math.max(maxSubBSTSize, rightInfo.maxSubBSTSize);
}
if (
(leftInfo == null ? true : (leftInfo.isBST && leftInfo.max < head.value))
&&
(rightInfo == null ? true : (rightInfo.isBST && rightInfo.min > head.value))
) {
isBST = true;
maxSubBSTSize = (leftInfo == null ? 0 : leftInfo.maxSubBSTSize)
+ (rightInfo == null ? 0 : rightInfo.maxSubBSTSize) + 1;
}
return new Info(isBST, maxSubBSTSize, min, max);
}
// for test
public static Node generateRandomBST(int maxLevel, int maxValue) {
return generate(1, maxLevel, maxValue);
}
// for test
public static Node generate(int level, int maxLevel, int maxValue) {
if (level > maxLevel || Math.random() < 0.5) {
return null;
}
Node head = new Node((int) (Math.random() * maxValue));
head.left = generate(level + 1, maxLevel, maxValue);
head.right = generate(level + 1, maxLevel, maxValue);
return head;
}
public static void main(String[] args) {
int maxLevel = 4;
int maxValue = 100;
int testTimes = 1000000;
for (int i = 0; i < testTimes; i++) {
Node head = generateRandomBST(maxLevel, maxValue);
if (maxSubBSTSize1(head) != maxSubBSTSize2(head)) {
System.out.println("Oops!");
}
}
System.out.println("finish!");
}
}
package class08;
import java.util.ArrayList;
/**
* 给定一棵二叉树的头节点head,
* 返回这颗二叉树中最大的二叉搜索子树的头节点
*/
public class Code05_MaxSubBSTHead {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static int getBSTSize(Node head) {
if (head == null) {
return 0;
}
ArrayList<Node> arr = new ArrayList<>();
in(head, arr);
for (int i = 1; i < arr.size(); i++) {
if (arr.get(i).value <= arr.get(i - 1).value) {
return 0;
}
}
return arr.size();
}
public static void in(Node head, ArrayList<Node> arr) {
if (head == null) {
return;
}
in(head.left, arr);
arr.add(head);
in(head.right, arr);
}
public static Node maxSubBSTHead1(Node head) {
if (head == null) {
return null;
}
if (getBSTSize(head) != 0) {
return head;
}
Node leftAns = maxSubBSTHead1(head.left);
Node rightAns = maxSubBSTHead1(head.right);
return getBSTSize(leftAns) >= getBSTSize(rightAns) ? leftAns : rightAns;
}
public static Node maxSubBSTHead2(Node head) {
if (head == null) {
return null;
}
return process(head).maxSubBSTHead;
}
/**
* Info中不用包含左树整体是否时搜索二叉树的条件 boolean isAllBST
* 因为可以通过maxSubBSTHead是否是X节点的左节点/右节点 来判断 X的左树/右树是否是搜索二叉树
* 如果X左孩子的maxSubBSTHead就是X左孩子自己,那么以X左孩子为头节点的树整体就是搜索二叉树
*/
public static class Info {
public Node maxSubBSTHead;// 树中最大搜索二叉树的头节点
public int maxSubBSTSize;// 树中最大搜索二叉树的大小
public int min;// 树中最小值
public int max;// 树中最大值
public Info(Node h, int size, int mi, int ma) {
maxSubBSTHead = h;
maxSubBSTSize = size;
min = mi;
max = ma;
}
}
public static Info process(Node head) {
if (head == null) {
return null;
}
Info leftInfo = process(head.left);
Info rightInfo = process(head.right);
int min = head.value;
int max = head.value;
Node maxSubBSTHead = null;
int maxSubBSTSize = 0;
if (leftInfo != null) {
min = Math.min(min, leftInfo.min);
max = Math.max(max, leftInfo.max);
maxSubBSTHead = leftInfo.maxSubBSTHead;
maxSubBSTSize = leftInfo.maxSubBSTSize;
}
if (rightInfo != null) {
min = Math.min(min, rightInfo.min);
max = Math.max(max, rightInfo.max);
if (rightInfo.maxSubBSTSize > maxSubBSTSize) {
maxSubBSTHead = rightInfo.maxSubBSTHead;
maxSubBSTSize = rightInfo.maxSubBSTSize;
}
}
/**
* leftInfo.maxSubBSTHead == head.left
* X左孩子的maxSubBSTHead就是X左孩子自己,那么以X左孩子为头节点的树整体就是搜索二叉树
*/
if ((leftInfo == null ? true : (leftInfo.maxSubBSTHead == head.left && leftInfo.max < head.value))
&& (rightInfo == null ? true : (rightInfo.maxSubBSTHead == head.right && rightInfo.min > head.value))) {
maxSubBSTHead = head;
maxSubBSTSize = (leftInfo == null ? 0 : leftInfo.maxSubBSTSize)
+ (rightInfo == null ? 0 : rightInfo.maxSubBSTSize) + 1;
}
return new Info(maxSubBSTHead, maxSubBSTSize, min, max);
}
// for test
public static Node generateRandomBST(int maxLevel, int maxValue) {
return generate(1, maxLevel, maxValue);
}
// for test
public static Node generate(int level, int maxLevel, int maxValue) {
if (level > maxLevel || Math.random() < 0.5) {
return null;
}
Node head = new Node((int) (Math.random() * maxValue));
head.left = generate(level + 1, maxLevel, maxValue);
head.right = generate(level + 1, maxLevel, maxValue);
return head;
}
public static void main(String[] args) {
int maxLevel = 4;
int maxValue = 100;
int testTimes = 1000000;
for (int i = 0; i < testTimes; i++) {
Node head = generateRandomBST(maxLevel, maxValue);
if (maxSubBSTHead1(head) != maxSubBSTHead2(head)) {
System.out.println("Oops!");
}
}
System.out.println("finish!");
}
}
package class08;
import java.util.LinkedList;
public class Code06_IsCBT {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static boolean isCBT1(Node head) {
if (head == null) {
return true;
}
LinkedList<Node> queue = new LinkedList<>();
// 是否遇到过左右两个孩子不双全的节点
boolean leaf = false;
Node l = null;
Node r = null;
queue.add(head);
while (!queue.isEmpty()) {
head = queue.poll();
l = head.left;
r = head.right;
if (leaf) {
// 遇到过左右不双全的节点后,后面的节点必须是叶节点
if (l != null || r != null) {
return false;
}
} else {
// 没遇到过左右不双全的节点
// 存在右节点不存在左节点->必不是完全二叉树
if (l == null && r != null) {
return false;
}
// 左右节点有一个不存在,则修改标志位,后续的节点改为判断是否是叶节点
if (l == null || r == null) {
leaf = true;
}
}
if (l != null) {
queue.add(l);
}
if (r != null) {
queue.add(r);
}
}
return true;
}
public static boolean isCBT2(Node head) {
if (head == null) {
return true;
}
return process(head).isCBT;
}
public static class Info {
public boolean isFull;
public boolean isCBT;
public int height;
public Info(boolean full, boolean cbt, int h) {
isFull = full;
isCBT = cbt;
height = h;
}
}
public static Info process(Node head) {
if (head == null) {
return new Info(true, true, 0);
}
Info leftInfo = process(head.left);
Info rightInfo = process(head.right);
int height = Math.max(leftInfo.height, rightInfo.height) + 1;
boolean isFull = leftInfo.isFull && rightInfo.isFull && leftInfo.height == rightInfo.height;
boolean isCBT = false;
if (isFull) {
isCBT = true;
} else {
if (leftInfo.isCBT && rightInfo.isCBT) {
if (leftInfo.isCBT && rightInfo.isFull && leftInfo.height == rightInfo.height + 1) {
isCBT = true;
}
if (leftInfo.isFull && rightInfo.isFull && leftInfo.height == rightInfo.height + 1) {
isCBT = true;
}
if (leftInfo.isFull && rightInfo.isCBT && leftInfo.height == rightInfo.height) {
isCBT = true;
}
}
}
return new Info(isFull, isCBT, height);
}
// for test
public static Node generateRandomBST(int maxLevel, int maxValue) {
return generate(1, maxLevel, maxValue);
}
// for test
public static Node generate(int level, int maxLevel, int maxValue) {
if (level > maxLevel || Math.random() < 0.5) {
return null;
}
Node head = new Node((int) (Math.random() * maxValue));
head.left = generate(level + 1, maxLevel, maxValue);
head.right = generate(level + 1, maxLevel, maxValue);
return head;
}
public static void main(String[] args) {
int maxLevel = 5;
int maxValue = 100;
int testTimes = 1000000;
for (int i = 0; i < testTimes; i++) {
Node head = generateRandomBST(maxLevel, maxValue);
if (isCBT1(head) != isCBT2(head)) {
System.out.println("Oops!");
}
}
System.out.println("finish!");
}
}
package class08;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
public class Code07_lowestAncestor {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static Node lowestAncestor1(Node head, Node o1, Node o2) {
if (head == null) {
return null;
}
// <节点,节点的父节点>
HashMap<Node, Node> parentMap = new HashMap<>();
parentMap.put(head, null);
fillParentMap(head, parentMap);
HashSet<Node> o1Set = new HashSet<>();
Node cur = o1;
o1Set.add(cur);
// cur一直向上跳,将沿途的父节点添加到set
while (parentMap.get(cur) != null) {
cur = parentMap.get(cur);
o1Set.add(cur);
}
// o2也一直向上跳,每次跳的时候判断o1是否经过过这个点
cur = o2;
while (!o1Set.contains(cur)) {
cur = parentMap.get(cur);
}
return cur;
}
public static void fillParentMap(Node head, HashMap<Node, Node> parentMap) {
if (head.left != null) {
parentMap.put(head.left, head);
fillParentMap(head.left, parentMap);
}
if (head.right != null) {
parentMap.put(head.right, head);
fillParentMap(head.right, parentMap);
}
}
public static Node lowestAncestor2(Node head, Node o1, Node o2) {
return process(head, o1, o2).ans;
}
public static class Info {
public Node ans;// o1o2的最初交汇点是谁,如果不是在此树上交汇的,则是null
public boolean findO1;// 此树上找到o1
public boolean findO2;// 此树上找到o2
public Info(Node a, boolean f1, boolean f2) {
ans = a;
findO1 = f1;
findO2 = f2;
}
}
public static Info process(Node X, Node o1, Node o2) {
if (X == null) {
return new Info(null, false, false);
}
Info leftInfo = process(X.left, o1, o2);
Info rightInfo = process(X.right, o1, o2);
// X本身是o1/左树发现o1/右树发现o1
boolean findO1 = X == o1 || leftInfo.findO1 || rightInfo.findO1;
boolean findO2 = X == o2 || leftInfo.findO2 || rightInfo.findO2;
// o1o2的最初交汇点在哪?
Node ans = null;
// 若左树已发现交汇点,以左树交汇点为准
if (leftInfo.ans != null) {
ans = leftInfo.ans;
}
// 若右树已发现交汇点,以右树交汇点为准
if (rightInfo.ans != null) {
ans = rightInfo.ans;
}
// 左右树都没发现交汇点,自己为头的这棵树上又找到了o1和o2->自己就是最初交汇点
if (ans == null) {
if (findO1 && findO2) {
ans = X;
}
}
return new Info(ans, findO1, findO2);
}
// for test
public static Node generateRandomBST(int maxLevel, int maxValue) {
return generate(1, maxLevel, maxValue);
}
// for test
public static Node generate(int level, int maxLevel, int maxValue) {
if (level > maxLevel || Math.random() < 0.5) {
return null;
}
Node head = new Node((int) (Math.random() * maxValue));
head.left = generate(level + 1, maxLevel, maxValue);
head.right = generate(level + 1, maxLevel, maxValue);
return head;
}
// for test
public static Node pickRandomOne(Node head) {
if (head == null) {
return null;
}
ArrayList<Node> arr = new ArrayList<>();
fillPrelist(head, arr);
int randomIndex = (int) (Math.random() * arr.size());
return arr.get(randomIndex);
}
// for test
public static void fillPrelist(Node head, ArrayList<Node> arr) {
if (head == null) {
return;
}
arr.add(head);
fillPrelist(head.left, arr);
fillPrelist(head.right, arr);
}
public static void main(String[] args) {
int maxLevel = 4;
int maxValue = 100;
int testTimes = 1000000;
for (int i = 0; i < testTimes; i++) {
Node head = generateRandomBST(maxLevel, maxValue);
Node o1 = pickRandomOne(head);
Node o2 = pickRandomOne(head);
if (lowestAncestor1(head, o1, o2) != lowestAncestor2(head, o1, o2)) {
System.out.println("Oops!");
}
}
System.out.println("finish!");
}
}
package class08;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
public class Code08_MaxDistance {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static int maxDistance1(Node head) {
if (head == null) {
return 0;
}
ArrayList<Node> arr = getPrelist(head);
HashMap<Node, Node> parentMap = getParentMap(head);
int max = 0;
for (int i = 0; i < arr.size(); i++) {
for (int j = i; j < arr.size(); j++) {
max = Math.max(max, distance(parentMap, arr.get(i), arr.get(j)));
}
}
return max;
}
public static ArrayList<Node> getPrelist(Node head) {
ArrayList<Node> arr = new ArrayList<>();
fillPrelist(head, arr);
return arr;
}
public static void fillPrelist(Node head, ArrayList<Node> arr) {
if (head == null) {
return;
}
arr.add(head);
fillPrelist(head.left, arr);
fillPrelist(head.right, arr);
}
public static HashMap<Node, Node> getParentMap(Node head) {
HashMap<Node, Node> map = new HashMap<>();
map.put(head, null);
fillParentMap(head, map);
return map;
}
public static void fillParentMap(Node head, HashMap<Node, Node> parentMap) {
if (head.left != null) {
parentMap.put(head.left, head);
fillParentMap(head.left, parentMap);
}
if (head.right != null) {
parentMap.put(head.right, head);
fillParentMap(head.right, parentMap);
}
}
public static int distance(HashMap<Node, Node> parentMap, Node o1, Node o2) {
HashSet<Node> o1Set = new HashSet<>();
Node cur = o1;
o1Set.add(cur);
while (parentMap.get(cur) != null) {
cur = parentMap.get(cur);
o1Set.add(cur);
}
cur = o2;
while (!o1Set.contains(cur)) {
cur = parentMap.get(cur);
}
Node lowestAncestor = cur;
cur = o1;
int distance1 = 1;
while (cur != lowestAncestor) {
cur = parentMap.get(cur);
distance1++;
}
cur = o2;
int distance2 = 1;
while (cur != lowestAncestor) {
cur = parentMap.get(cur);
distance2++;
}
return distance1 + distance2 - 1;
}
public static int maxDistance2(Node head) {
return process(head).maxDistance;
}
public static class Info {
public int maxDistance;
public int height;
public Info(int dis, int h) {
maxDistance = dis;
height = h;
}
}
public static Info process(Node head) {
if (head == null) {
return new Info(0, 0);
}
Info leftInfo = process(head.left);
Info rightInfo = process(head.right);
int height = Math.max(leftInfo.height, rightInfo.height) + 1;
int maxDistance = Math.max(Math.max(leftInfo.maxDistance, rightInfo.maxDistance),
leftInfo.height + rightInfo.height + 1);
return new Info(maxDistance, height);
}
// for test
public static Node generateRandomBST(int maxLevel, int maxValue) {
return generate(1, maxLevel, maxValue);
}
// for test
public static Node generate(int level, int maxLevel, int maxValue) {
if (level > maxLevel || Math.random() < 0.5) {
return null;
}
Node head = new Node((int) (Math.random() * maxValue));
head.left = generate(level + 1, maxLevel, maxValue);
head.right = generate(level + 1, maxLevel, maxValue);
return head;
}
public static void main(String[] args) {
int maxLevel = 4;
int maxValue = 100;
int testTimes = 1000000;
for (int i = 0; i < testTimes; i++) {
Node head = generateRandomBST(maxLevel, maxValue);
if (maxDistance1(head) != maxDistance2(head)) {
System.out.println("Oops!");
}
}
System.out.println("finish!");
}
}
package class08;
import java.util.ArrayList;
import java.util.List;
public class Code09_MaxHappy {
public static class Employee {
public int happy;
public List<Employee> nexts;
public Employee(int h) {
happy = h;
nexts = new ArrayList<>();
}
}
public static int maxHappy1(Employee boss) {
if (boss == null) {
return 0;
}
return process1(boss, false);
}
public static int process1(Employee cur, boolean up) {
if (up) {
int ans = 0;
for (Employee next : cur.nexts) {
ans += process1(next, false);
}
return ans;
} else {
int p1 = cur.happy;
int p2 = 0;
for (Employee next : cur.nexts) {
p1 += process1(next, true);
p2 += process1(next, false);
}
return Math.max(p1, p2);
}
}
public static int maxHappy2(Employee boss) {
if (boss == null) {
return 0;
}
Info all = process2(boss);
return Math.max(all.yes, all.no);
}
public static class Info {
public int yes;//头节点在来的情况下整棵树的最大快乐值
public int no;//头节点在不来的情况下整棵树的最大快乐值
public Info(int y, int n) {
yes = y;
no = n;
}
}
public static Info process2(Employee x) {
if (x.nexts.isEmpty()) {
return new Info(x.happy, 0);
}
int yes = x.happy;
int no = 0;
for (Employee next : x.nexts) {
Info nextInfo = process2(next);
yes += nextInfo.no;
no += Math.max(nextInfo.yes, nextInfo.no);
}
return new Info(yes, no);
}
// for test
public static Employee genarateBoss(int maxLevel, int maxNexts, int maxHappy) {
if (Math.random() < 0.02) {
return null;
}
Employee boss = new Employee((int) (Math.random() * (maxHappy + 1)));
genarateNexts(boss, 1, maxLevel, maxNexts, maxHappy);
return boss;
}
// for test
public static void genarateNexts(Employee e, int level, int maxLevel, int maxNexts, int maxHappy) {
if (level > maxLevel) {
return;
}
int nextsSize = (int) (Math.random() * (maxNexts + 1));
for (int i = 0; i < nextsSize; i++) {
Employee next = new Employee((int) (Math.random() * (maxHappy + 1)));
e.nexts.add(next);
genarateNexts(next, level + 1, maxLevel, maxNexts, maxHappy);
}
}
public static void main(String[] args) {
int maxLevel = 4;
int maxNexts = 7;
int maxHappy = 100;
int testTimes = 100000;
for (int i = 0; i < testTimes; i++) {
Employee boss = genarateBoss(maxLevel, maxNexts, maxHappy);
if (maxHappy1(boss) != maxHappy2(boss)) {
System.out.println("Oops!");
}
}
System.out.println("finish!");
}
}
class09
package class09;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
/**
* 给定一个由字符串组成的数组strs,
* 必须把所有的字符串拼接起来,
* 返回所有可能的拼接结果中,字典序最小的结果
*/
public class Code01_LowestLexicography {
/**
* 暴力解法
*/
public static String lowestString1(String[] strs) {
if (strs == null || strs.length == 0) {
return "";
}
ArrayList<String> all = new ArrayList<>();
HashSet<Integer> use = new HashSet<>();
process(strs, use, "", all);
// 遍历比较所有排列组合结果
String lowest = all.get(0);
for (int i = 1; i < all.size(); i++) {
if (all.get(i).compareTo(lowest) < 0) {
lowest = all.get(i);
}
}
return lowest;
}
/**
* 优化解法
*/
public static String lowestString2(String[] strs) {
if (strs == null || strs.length == 0) {
return "";
}
Arrays.sort(strs, new MyComparator());
String res = "";
for (int i = 0; i < strs.length; i++) {
res += strs[i];
}
return res;
}
/**
* 排列组合:
* 将str数组中的元素进行全排列,全排列结果放到all中
* 比如str=[a,b,c],全排列结果为:abc,acb,bac,bca,cab,cba
* @param strs 原始字符串组成的数组
* @param use 已经使用过的字符串在str中的下标
* @param path 单次排列组合结果
* @param all 所有排列组合的结果集
*/
public static void process(String[] strs, HashSet<Integer> use, String path, ArrayList<String> all) {
if (use.size() == strs.length) {
all.add(path);
} else {
for (int i = 0; i < strs.length; i++) {
if (!use.contains(i)) {
use.add(i);
process(strs, use, path + strs[i], all);
use.remove(i);
}
}
}
}
public static class MyComparator implements Comparator<String> {
@Override
public int compare(String a, String b) {
return (a + b).compareTo(b + a);
}
}
// for test
public static String generateRandomString(int strLen) {
char[] ans = new char[(int) (Math.random() * strLen) + 1];
for (int i = 0; i < ans.length; i++) {
int value = (int) (Math.random() * 5);
ans[i] = (char) (97 + value);
}
return String.valueOf(ans);
}
// for test
public static String[] generateRandomStringArray(int arrLen, int strLen) {
String[] ans = new String[(int) (Math.random() * arrLen) + 1];
for (int i = 0; i < ans.length; i++) {
ans[i] = generateRandomString(strLen);
}
return ans;
}
// for test
public static String[] copyStringArray(String[] arr) {
String[] ans = new String[arr.length];
for (int i = 0; i < ans.length; i++) {
ans[i] = String.valueOf(arr[i]);
}
return ans;
}
public static void main(String[] args) {
int arrLen = 6;
int strLen = 5;
int testTimes = 100000;
for (int i = 0; i < testTimes; i++) {
String[] arr1 = generateRandomStringArray(arrLen, strLen);
String[] arr2 = copyStringArray(arr1);
if (!lowestString1(arr1).equals(lowestString2(arr2))) {
System.out.println("Oops!");
}
}
System.out.println("finish!");
}
}
package class09;
import java.util.HashSet;
public class Code02_Light {
public static int minLight1(String road) {
if (road == null || road.length() == 0) {
return 0;
}
return process(road.toCharArray(), 0, new HashSet<>());
}
/**
* 暴力解法
* str[index]位置自由选择放灯/不放灯
* str[0...index-1]范围内已经做完决定了,放灯的位置记录在lights中
* 要求选出所有能照亮.的方案,并返回lights最小的方案中放了几盏灯
* @param str
* @param index
* @param lights
* @return
*/
public static int process(char[] str, int index, HashSet<Integer> lights) {
if (index == str.length) {// 已经到最后一个位置了,遍历判断str中所有.是否都被照亮
for (int i = 0; i < str.length; i++) {
if (str[i] == '.') {
if (!lights.contains(i - 1) && !lights.contains(i) && !lights.contains(i + 1)) {
return Integer.MAX_VALUE;
}
}
}
return lights.size();
} else {// 后面还有位置待确定放不放灯,分2种情况分别进行
// 情况一:index位置不放灯时,整个方案放灯数量
int nolight = process(str, index + 1, lights);
int yeslight = Integer.MAX_VALUE;
if (str[index] == '.') {
// 情况二:index位置放灯时,整个方案放灯数量
lights.add(index);
yeslight = process(str, index + 1, lights);
lights.remove(index);// 恢复现场
}
// 返回index位置放不放灯两种情况下灯数少的
return Math.min(nolight, yeslight);
}
}
/**
* 贪心解法
*
* @param road
* @return
*/
public static int minLight2(String road) {
char[] str = road.toCharArray();
int index = 0;
int light = 0;
while (index < str.length) {
if (str[index] == 'X') {
index++;
} else {
light++;
if (index + 1 == str.length) {
break;
} else {
if (str[index + 1] == 'X') {
index = index + 2;
} else {
index = index + 3;
}
}
}
}
return light;
}
// for test
public static String randomString(int len) {
char[] res = new char[(int) (Math.random() * len) + 1];
for (int i = 0; i < res.length; i++) {
res[i] = Math.random() < 0.5 ? 'X' : '.';
}
return String.valueOf(res);
}
public static void main(String[] args) {
int len = 20;
int testTime = 100000;
for (int i = 0; i < testTime; i++) {
String test = randomString(len);
int ans1 = minLight1(test);
int ans2 = minLight2(test);
if (ans1 != ans2) {
System.out.println("oops!");
}
}
System.out.println("finish!");
}
}
package class09;
import java.util.PriorityQueue;
public class Code03_LessMoneySplitGold {
/**
* 暴力实现
*/
public static int lessMoney1(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
return process(arr, 0);
}
public static int process(int[] arr, int pre) {
if (arr.length == 1) {
return pre;
}
int ans = Integer.MAX_VALUE;
for (int i = 0; i < arr.length; i++) {
for (int j = i + 1; j < arr.length; j++) {
ans = Math.min(ans, process(copyAndMergeTwo(arr, i, j), pre + arr[i] + arr[j]));
}
}
return ans;
}
public static int[] copyAndMergeTwo(int[] arr, int i, int j) {
// 新数组将arr中i和j位置的数相加合并,变成一个位置,因此总长度少1
int[] ans = new int[arr.length - 1];
int ansi = 0;
// 依次将arr中非i和j位置的数据设置到ans数组中
for (int arri = 0; arri < arr.length; arri++) {
if (arri != i && arri != j) {
ans[ansi++] = arr[arri];
}
}
// ans数组最后一位设为arr中i和j位置的和
ans[ansi] = arr[i] + arr[j];
return ans;
}
/**
* 贪心实现
* 为什么这么做没讲,不必纠结,记住就好
* @param arr
* @return
*/
public static int lessMoney2(int[] arr) {
// 遍历arr放至小根堆
PriorityQueue<Integer> pQ = new PriorityQueue<>();
for (int i = 0; i < arr.length; i++) {
pQ.add(arr[i]);
}
int sum = 0;
int cur = 0;
while (pQ.size() > 1) {
// 弹出大根堆数组的头2个元素相加(每次弹出都会重新调整堆结构),将当前结果累计到总结果中,再将当前结果放回大根堆(也会重新调整堆结构)
cur = pQ.poll() + pQ.poll();
sum += cur;
pQ.add(cur);
}
return sum;
}
// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) (Math.random() * (maxValue + 1));
}
return arr;
}
public static void main(String[] args) {
int testTime = 100000;
int maxSize = 6;
int maxValue = 1000;
for (int i = 0; i < testTime; i++) {
int[] arr = generateRandomArray(maxSize, maxValue);
if (lessMoney1(arr) != lessMoney2(arr)) {
System.out.println("Oops!");
}
}
System.out.println("finish!");
}
}
package class09;
import java.util.Arrays;
import java.util.Comparator;
/**
* 一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。
* 给你每一个项目开始的时间和结束的时间
* 你来安排宣讲的日程,要求会议室进行的宣讲的场次最多。
* 返回最多的宣讲场次。
*/
public class Code04_BestArrange {
public static class Program {
public int start;
public int end;
public Program(int start, int end) {
this.start = start;
this.end = end;
}
}
public static int bestArrange1(Program[] programs) {
if (programs == null || programs.length == 0) {
return 0;
}
return process(programs, 0, 0);
}
/**
* 暴力枚举解法
*
* @param programs 待安排的会议
* @param done 目前已经安排的会议数
* @param timeLine 当前时间点
* @return 最大安排数
*/
public static int process(Program[] programs, int done, int timeLine) {
if (programs.length == 0) {
return done;
}
int max = done;
// 遍历所有会议,判断所有会议都分别进行安排的情况
for (int i = 0; i < programs.length; i++) {
// i会议未超过当前时间点,安排i会议
if (programs[i].start >= timeLine) {
// i位置会议已安排,剔除,done+1
Program[] next = copyButExcept(programs, i);
// i安排完了,递归判断除了i以外的会议分别进行安排的情况
// 递归处理待安排会议,i位置会议的结束时间即下面会议安排判断时的时间点
max = Math.max(max, process(next, done + 1, programs[i].end));
}
}
return max;
}
/**
* 贪心解法
*
* @param programs
* @return
*/
public static int bestArrange2(Program[] programs) {
Arrays.sort(programs, new ProgramComparator());
int timeLine = 0;
int result = 0;
// programs已经按照结束时间排好序,直接顺序遍历安排会议
for (int i = 0; i < programs.length; i++) {
if (timeLine <= programs[i].start) {
result++;
timeLine = programs[i].end;
}
}
return result;
}
// 贪心策略比较器,谁的结束时间早谁在前
public static class ProgramComparator implements Comparator<Program> {
@Override
public int compare(Program o1, Program o2) {
return o1.end - o2.end;
}
}
// 剔除会议数组中下标i的会议
public static Program[] copyButExcept(Program[] programs, int i) {
Program[] ans = new Program[programs.length - 1];
int index = 0;
for (int k = 0; k < programs.length; k++) {
if (k != i) {
ans[index++] = programs[k];
}
}
return ans;
}
// for test
public static Program[] generatePrograms(int programSize, int timeMax) {
Program[] ans = new Program[(int) (Math.random() * (programSize + 1))];
for (int i = 0; i < ans.length; i++) {
int r1 = (int) (Math.random() * (timeMax + 1));
int r2 = (int) (Math.random() * (timeMax + 1));
if (r1 == r2) {
ans[i] = new Program(r1, r1 + 1);
} else {
ans[i] = new Program(Math.min(r1, r2), Math.max(r1, r2));
}
}
return ans;
}
public static void main(String[] args) {
int programSize = 12;
int timeMax = 20;
int timeTimes = 1000000;
for (int i = 0; i < timeTimes; i++) {
Program[] programs = generatePrograms(programSize, timeMax);
if (bestArrange1(programs) != bestArrange2(programs)) {
System.out.println("Oops!");
}
}
System.out.println("finish!");
}
}
package class09;
import java.util.Comparator;
import java.util.PriorityQueue;
public class Code05_IPO {
/**
* @param K 表示你只能串行的最多做k个项目
* @param W 表示你初始的资金
* @param Profits 表示i号项目在扣除花费之后还能挣到的钱(利润)
* @param Capital 表示i号项目的花费
* @return 你最后获得的最大钱数。
*/
public static int findMaximizedCapital(int K, int W, int[] Profits, int[] Capital) {
// 按项目花费从小到大组织小根堆,花费小的在头部
PriorityQueue<Program> minCostQ = new PriorityQueue<>(new MinCostComparator());
// 按项目利润从大到小组织大根堆,利润大的在头部
PriorityQueue<Program> maxProfitQ = new PriorityQueue<>(new MaxProfitComparator());
// 组织花费小根堆
for (int i = 0; i < Profits.length; i++) {
minCostQ.add(new Program(Profits[i], Capital[i]));
}
for (int i = 0; i < K; i++) {
// 组织利润大根堆:遍历花费小根堆,取出所有花费能被当前资金hold的项目,放到利润大根堆
while (!minCostQ.isEmpty() && minCostQ.peek().cost <= W) {
maxProfitQ.add(minCostQ.poll());
}
// 利润大根堆为空,说明当前资金hold不了任何一个项目,无项目可作,直接返回当前资金
if (maxProfitQ.isEmpty()) {
return W;
}
// 贪心策略:取利润最大的项目做,获得的利润加到当前资金中,继续启动后续项目
W += maxProfitQ.poll().priority;
}
return W;
}
public static class Program {
public int priority;
public int cost;
public Program(int priority, int cost) {
this.priority = priority;
this.cost = cost;
}
}
/**
* 小根堆比较策略:花费少的在前
*/
public static class MinCostComparator implements Comparator<Program> {
@Override
public int compare(Program o1, Program o2) {
return o1.cost - o2.cost;
}
}
/**
* 大根堆比较策略:利润大的在前
*/
public static class MaxProfitComparator implements Comparator<Program> {
@Override
public int compare(Program o1, Program o2) {
return o2.priority - o1.priority;
}
}
}
class10
package class10;
import java.util.HashMap;
import java.util.List;
import java.util.Stack;
/**
* 并查集:
*
* 有若干个样本a、b、c、d…类型假设是V
* 在并查集中一开始认为每个样本都在单独的集合里
* 用户可以在任何时候调用如下两个方法:
* boolean isSameSet(V x, V y) : 查询样本x和样本y是否属于一个集合
* void union(V x, V y) : 把x和y各自所在集合的所有样本合并成一个集合
* isSameSet和union方法的代价越低越好
* ----------------------------------------------------------------------
* 1)每个节点都有一条往上指的指针
* 2)节点a往上找到的头节点,叫做a所在集合的代表节点
* 3)查询x和y是否属于同一个集合,就是看看找到的代表节点是不是一个
* 4)把x和y各自所在集合的所有点合并成一个集合,只需要小集合的代表点挂在大集合的代表点的下方即可
*/
public class Code01_UnionFind {
// 只是将value包一层成为节点
public static class Node<V> {
V value;
public Node(V value) {
this.value = value;
}
}
public static class UnionSet<V> {
public HashMap<V, Node<V>> nodes = new HashMap<>();// 记录值和包装后的节点对应的关系
public HashMap<Node<V>, Node<V>> parents = new HashMap<>();// <节点,父节点>
public HashMap<Node<V>, Integer> sizeMap = new HashMap<>();// <代表点,代表点为头节点的树的结点数>,代表点即父节点是自己的节点
// 构造时即完成map初始化
public UnionSet(List<V> values) {
for (V cur : values) {
Node<V> node = new Node<>(cur);
nodes.put(cur, node);
parents.put(node, node);
sizeMap.put(node, 1);// 开始时每个点都指向自己,每个点都是代表点
}
}
/**
* 查询:ab是否在同一个集合
*/
public boolean isSameSet(V a, V b) {
if (!nodes.containsKey(a) || !nodes.containsKey(b)) {
return false;
}
// a和b向上找,直到找到某个点的父节点是它自己,即各自的代表点,如果各自代表点是同一个,则ab在同一个集合中
return findFather(nodes.get(a)) == findFather(nodes.get(b));
}
/**
* 合并:将ab各自所在的集合进行合并
*/
public void union(V a, V b) {
if (!nodes.containsKey(a) || !nodes.containsKey(b)) {
return;
}
// 召唤各自的帮主出来比帮派人数,谁的人多谁就吞并另一方,输了就认另一方为父,并解散帮派
Node<V> aHead = findFather(nodes.get(a));
Node<V> bHead = findFather(nodes.get(b));
if (aHead != bHead) {
Node<V> bigHead = sizeMap.get(aHead) >= sizeMap.get(bHead) ? aHead : bHead;
Node<V> smallHead = bigHead == aHead ? bHead : aHead;
parents.put(smallHead, bigHead);// 认贼作父
sizeMap.put(bigHead, sizeMap.get(bigHead) + sizeMap.get(smallHead));// 合并人数
sizeMap.remove(smallHead);// 解散帮派
}
}
/**
* 查询节点的代表节点
*/
public Node<V> findFather(Node<V> cur) {
/**
* 原始findFather逻辑,每次找代表点都要经历一次遍历过程O(N),复杂度过高
*/
/*
while(cur != parents.get(cur)){
cur = parents.get(cur);
}
return cur;
*/
/**
* 优化后的findFather逻辑,在首次找到某个点的代表点时【消耗O(N)】,即将这个点的父节点指向代表点,后面再调用findFather找这个点的代表点时直接从parents中取就可以了【消耗O(1)】
*/
Stack<Node<V>> path = new Stack<>();// 记录cur向上找代表点这条路径上的所有点
while (cur != parents.get(cur)) {
path.push(cur);
cur = parents.get(cur);// cur向上跳
}
// cur已经来到不能再往上的位置,即代表点
// 将沿途所有节点的父全部指向当前代表点
while (!path.isEmpty()) {
parents.put(path.pop(), cur);
}
return cur;
}
}
}
package class10;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
/**
* 宽度优先遍历
*/
public class Code02_BFS {
// 从node出发,进行宽度优先遍历
public static void bfs(Node node) {
if (node == null) {
return;
}
Queue<Node> queue = new LinkedList<>();
HashSet<Node> set = new HashSet<>();// 保证每个节点只进一次队列,避免死环
queue.add(node);
set.add(node);
while (!queue.isEmpty()) {
Node cur = queue.poll();
System.out.println(cur.value);
for (Node next : cur.nexts) {
if (!set.contains(next)) {
set.add(next);
queue.add(next);
}
}
}
}
}
package class10;
import java.util.HashSet;
import java.util.Stack;
/**
* 深度优先遍历
*/
public class Code02_DFS {
public static 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)) {
stack.push(cur);
stack.push(next);
set.add(next);
System.out.println(next.value);
break;
}
}
}
}
}
package class10;
import java.util.*;
/**
* 拓扑排序
*/
public class Code03_TopologySort {
// directed graph and no loop
public static List<Node> sortedTopology(Graph graph) {
// 维护节点与其剩余入度的关系
HashMap<Node, Integer> inMap = new HashMap<>();
// 剩余入度为0的点,才能进这个队列
Queue<Node> zeroInQueue = new LinkedList<>();
for (Node node : graph.nodes.values()) {
inMap.put(node, node.in);
if (node.in == 0) {
zeroInQueue.add(node);
}
}
// 拓扑排序的结果,即将入度为0的点依次加入result
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);
if (inMap.get(next) == 0) {
zeroInQueue.add(next);
}
}
}
return result;
}
}
package class10;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.Stack;
// 仅适用于无向图
public class Code04_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 (Edge edge : graph.edges) { // M 条边
priorityQueue.add(edge); // O(logM)
}
Set<Edge> result = new HashSet<>();
while (!priorityQueue.isEmpty()) { // M 条边
// 按权重小->大的顺序处理边
Edge edge = priorityQueue.poll(); // O(logM)
if (!unionFind.isSameSet(edge.from, edge.to)) { // O(1)
result.add(edge);
unionFind.union(edge.from, edge.to);
}
}
return result;
}
// 并查集
public static class UnionFind {
// key 某一个节点, value key节点往上的节点
private HashMap<Node, Node> fatherMap;
// key 某一个集合的代表节点, value key所在集合的节点个数
private HashMap<Node, Integer> sizeMap;
public UnionFind() {
fatherMap = new HashMap<Node, Node>();
sizeMap = new HashMap<Node, Integer>();
}
public void makeSets(Collection<Node> nodes) {
fatherMap.clear();
sizeMap.clear();
for (Node node : nodes) {
fatherMap.put(node, node);
sizeMap.put(node, 1);
}
}
private Node findFather(Node n) {
Stack<Node> path = new Stack<>();
while(n != fatherMap.get(n)) {
path.add(n);
n = fatherMap.get(n);
}
while(!path.isEmpty()) {
// 沿途节点的父节点都变成代表节点
fatherMap.put(path.pop(), n);
}
return n;
}
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 aDai = findFather(a);
Node bDai = findFather(b);
if (aDai != bDai) {
int aSetSize = sizeMap.get(aDai);
int bSetSize = sizeMap.get(bDai);
if (aSetSize <= bSetSize) {
fatherMap.put(aDai, bDai);
sizeMap.put(bDai, aSetSize + bSetSize);
sizeMap.remove(aDai);
} else {
fatherMap.put(bDai, aDai);
sizeMap.put(aDai, aSetSize + bSetSize);
sizeMap.remove(bDai);
}
}
}
}
/**
* 权重小的边在前
*/
public static class EdgeComparator implements Comparator<Edge> {
@Override
public int compare(Edge o1, Edge o2) {
return o1.weight - o2.weight;
}
}
public static class MySets{
public HashMap<Node, List<Node>> setMap;
public MySets(List<Node> nodes) {
for(Node cur : nodes) {
List<Node> set = new ArrayList<Node>();
set.add(cur);
setMap.put(cur, set);
}
}
public boolean isSameSet(Node from, Node to) {
List<Node> fromSet = setMap.get(from);
List<Node> toSet = setMap.get(to);
return fromSet == toSet;
}
public void union(Node from, Node to) {
List<Node> fromSet = setMap.get(from);
List<Node> toSet = setMap.get(to);
for(Node toNode : toSet) {
fromSet.add(toNode);
setMap.put(toNode, fromSet);
}
}
}
}
package class10;
import java.util.Comparator;
import java.util.HashSet;
import java.util.PriorityQueue;
import java.util.Set;
// undirected graph only
public class Code05_Prim {
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> handledEdges = new HashSet<>();// 存放已经处理过的边
Set<Edge> result = new HashSet<>(); // 依次挑选的的边在result里
for (Node node : graph.nodes.values()) { // 随便挑了一个点
// node 是开始点
if (!set.contains(node)) {
set.add(node);
for (Edge edge : node.edges) { // 由一个点,解锁所有相连的边
if (!handledEdges.contains(edge)){
priorityQueue.add(edge);
handledEdges.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) {// 继续解锁新的点连通的边
if (!handledEdges.contains(nextEdge)) {
priorityQueue.add(nextEdge);
handledEdges.add(nextEdge);
}
}
}
}
}
break;// 目的是为了防止数据森林,只对一个联通区域进行处理
}
return result;
}
// 请保证graph是连通图
// graph[i][j]表示点i到点j的距离,如果是系统最大值代表无路
// 返回值是最小连通图的路径之和
public static int prim(int[][] graph) {
int size = graph.length;
int[] distances = new int[size];
boolean[] visit = new boolean[size];
visit[0] = true;
for (int i = 0; i < size; i++) {
distances[i] = graph[0][i];
}
int sum = 0;
for (int i = 1; i < size; i++) {
int minPath = Integer.MAX_VALUE;
int minIndex = -1;
for (int j = 0; j < size; j++) {
if (!visit[j] && distances[j] < minPath) {
minPath = distances[j];
minIndex = j;
}
}
if (minIndex == -1) {
return sum;
}
visit[minIndex] = true;
sum += minPath;
for (int j = 0; j < size; j++) {
if (!visit[j] && distances[j] > graph[minIndex][j]) {
distances[j] = graph[minIndex][j];
}
}
}
return sum;
}
public static void main(String[] args) {
System.out.println("hello world!");
}
}
package class10;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
// 边的权重都是正数,没有负数
public class Code06_Dijkstra {
/**
* 未改进的dijkstra算法
* 每次取当前最小距离的点时都要遍历selectedNodes所有记录,耗时
*
* @param from
* @return
*/
public static HashMap<Node, Integer> dijkstra1(Node from) {
// 从from出发到所有点的最小距离
// key : 从from出发到达key
// value : 从from出发到达key的最小距离
// 如果在表中,没有T的记录,含义是从from出发到T这个点的距离为正无穷
HashMap<Node, Integer> distanceMap = new HashMap<>();
distanceMap.put(from, 0);
// 已经求过距离的节点,存在selectedNodes中,以后再也不碰
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);
} else {
distanceMap.put(edge.to, Math.min(distanceMap.get(toNode), distance + edge.weight));
}
}
selectedNodes.add(minNode);// 锁住点
minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);
}
return distanceMap;
}
/**
* 改进后的dijkstra算法
* 每次从小根堆中取当前最小距离的点,但是有时距离需要更新,系统提供的小根堆无法满足
* 手写定制化小根堆,支持修改堆中元素,且修改后自动调整堆结构
* @param from
* @param size
* @return
*/
public static HashMap<Node, Integer> dijkstra2(Node from, int size) {
NodeHeap nodeHeap = new NodeHeap(size);
nodeHeap.addOrUpdateOrIgnore(from, 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;
}
public static Node getMinDistanceAndUnselectedNode(HashMap<Node, Integer> distanceMap, HashSet<Node> selectedNodes) {
Node minNode = null;
int minDistance = Integer.MAX_VALUE;
for (Entry<Node, Integer> entry : distanceMap.entrySet()) {
Node node = entry.getKey();
int distance = entry.getValue();
if (!selectedNodes.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; // 实际的堆结构
// key 某一个node, value 上面数组中的位置,若为-1则表示曾经在堆上,现在不在了
private HashMap<Node, Integer> heapIndexMap;
// key 某一个节点, value 从源节点出发到该节点的目前最小距离
private HashMap<Node, Integer> distanceMap;
private int size; // 堆上有多少个点
public NodeHeap(int size) {
nodes = new Node[size];
heapIndexMap = new HashMap<>();
distanceMap = new HashMap<>();
size = 0;
}
public boolean isEmpty() {
return size == 0;
}
// 有一个点叫node,现在发现了一个从源节点出发到达node的距离为distance
// 判断要不要更新,如果需要的话,就更新
public void addOrUpdateOrIgnore(Node node, int distance) {
if (inHeap(node)) {// 节点在堆上,更新distance为新旧distance中较小的,然后与父节点比向上冒
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);// 记录原来在0位置的点,-1表示曾经在堆上,以后不在了
distanceMap.remove(nodes[size - 1]);
// free C++同学还要把原本堆顶节点析构,对java同学不必
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) {
// 返回左右孩子中距离最小的孩子在堆数组中的下标(left+1是右孩子下标)
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;
}
}
}
package class10;
/**
* 图中的边
*/
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;
}
}
package class10;
import java.util.HashMap;
import java.util.HashSet;
/**
* 图
*/
public class Graph {
public HashMap<Integer, Node> nodes;// <点的id,点> 点集
public HashSet<Edge> edges;// 边集
public Graph() {
nodes = new HashMap<>();
edges = new HashSet<>();
}
}
package class10;
public class GraphGenerator {
// matrix 所有的边
// N*3 的矩阵
// [weight, from节点上面的值,to节点上面的值]
public static Graph createGraph(Integer[][] matrix) {
Graph graph = new Graph();
for (int i = 0; i < matrix.length; i++) { // matrix[0][0], matrix[0][1] matrix[0][2]
Integer weight = matrix[i][0];
Integer from = matrix[i][1];
Integer to = matrix[i][2];
/**
* 处理图的点集
*/
if (!graph.nodes.containsKey(from)) {
graph.nodes.put(from, new Node(from));
}
if (!graph.nodes.containsKey(to)) {
graph.nodes.put(to, new Node(to));
}
/**
* 处理前后节点相关属性,及边
*/
Node fromNode = graph.nodes.get(from);
Node toNode = graph.nodes.get(to);
Edge newEdge = new Edge(weight, fromNode, toNode);
fromNode.nexts.add(toNode);
fromNode.out++;
toNode.in++;
fromNode.edges.add(newEdge);
/**
* 处理图的边集
*/
graph.edges.add(newEdge);
}
return graph;
}
}
package class10;
import java.util.ArrayList;
/**
* 图中的点
*/
public class Node {
public int value;//点的id
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<>();
}
}
class11
package class11;
public class Code01_Hanoi {
public static void hanoi1(int n) {
leftToRight(n);
}
public static void leftToRight(int n) {
if (n == 1) {
System.out.println("Move 1 from left to right");
return;
}
leftToMid(n - 1);
System.out.println("Move " + n + " from left to right");
midToRight(n - 1);
}
public static void leftToMid(int n) {
if (n == 1) {
System.out.println("Move 1 from left to mid");
return;
}
leftToRight(n - 1);
System.out.println("Move " + n + " from left to mid");
rightToMid(n - 1);
}
public static void rightToMid(int n) {
if (n == 1) {
System.out.println("Move 1 from right to mid");
return;
}
rightToLeft(n - 1);
System.out.println("Move " + n + " from right to mid");
leftToMid(n - 1);
}
public static void midToRight(int n) {
if (n == 1) {
System.out.println("Move 1 from mid to right");
return;
}
midToLeft(n - 1);
System.out.println("Move " + n + " from mid to right");
leftToRight(n - 1);
}
public static void midToLeft(int n) {
if (n == 1) {
System.out.println("Move 1 from mid to left");
return;
}
midToRight(n - 1);
System.out.println("Move " + n + " from mid to left");
rightToLeft(n - 1);
}
public static void rightToLeft(int n) {
if (n == 1) {
System.out.println("Move 1 from right to left");
return;
}
rightToMid(n - 1);
System.out.println("Move " + n + " from right to left");
midToLeft(n - 1);
}
public static void hanoi2(int n) {
if (n > 0) {
func(n, "left", "right", "mid");
}
}
// 1~i 圆盘 目标是from -> to, other是另外一个
public static void func(int N, String from, String to, String other) {
if (N == 1) { // base
System.out.println("Move 1 from " + from + " to " + to);
} else {
func(N - 1, from, other, to);
System.out.println("Move " + N + " from " + from + " to " + to);
func(N - 1, other, to, from);
}
}
public static void main(String[] args) {
int n = 3;
hanoi1(n);
System.out.println("============");
hanoi2(n);
System.out.println("============");
}
}
package class11;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
public class Code02_PrintAllSubsquences {
public static List<String> subs(String s) {
char[] str = s.toCharArray();
String path = "";
List<String> ans = new ArrayList<>();
process1(str, 0, ans, path);
return ans;
}
public static void process1(char[] str, int index, List<String> ans, String path) {
if (index == str.length) {
ans.add(path);
return;
}
String no = path;
process1(str, index + 1, ans, no);
String yes = path + String.valueOf(str[index]);
process1(str, index + 1, ans, yes);
}
public static List<String> subsNoRepeat(String s) {
char[] str = s.toCharArray();
String path = "";
HashSet<String> set = new HashSet<>();
process2(str, 0, set, path);
List<String> ans = new ArrayList<>();
for (String cur : set) {
ans.add(cur);
}
return ans;
}
public static void process2(char[] str, int index, HashSet<String> set, String path) {
if (index == str.length) {
set.add(path);
return;
}
String no = path;
process2(str, index + 1, set, no);
String yes = path + String.valueOf(str[index]);
process2(str, index + 1, set, yes);
}
public static void main(String[] args) {
String test = "aacc";
List<String> ans1 = subs(test);
List<String> ans2 = subsNoRepeat(test);
for (String str : ans1) {
System.out.println(str);
}
System.out.println("=================");
for (String str : ans2) {
System.out.println(str);
}
System.out.println("=================");
}
}
package class11;
import java.util.ArrayList;
import java.util.List;
public class Code03_PrintAllPermutations {
public static ArrayList<String> permutation(String str) {
ArrayList<String> res = new ArrayList<>();
if (str == null || str.length() == 0) {
return res;
}
char[] chs = str.toCharArray();
process(chs, 0, res);
return res;
}
/**
* str中i位置之前的都已经做好决定放什么字符了,不再动了
* str中的i位置后面的字符都有机会来到i位置
*/
public static void process(char[] str, int i, ArrayList<String> res) {
// i来到终止位置时,此时str中所有字符按顺序连接起来组成的字符串就是全排列的一种情况
if (i == str.length) {
res.add(String.valueOf(str));
return;
}
// i没到终止位置,i往后的所有位置的字符都可以来到i位置,依次将i后所有位置的字符与i位置的字符交换
for (int j = i; j < str.length; j++) {
swap(str, i, j);// 将i位置的字符与j位置的字符交换
process(str, i + 1, res);// i+1位置上后面的字符人人都有机会
swap(str, i, j);// 恢复现场,继续for循环,将j+1位置的字符与i位置交换
}
}
public static ArrayList<String> permutationNoRepeat(String str) {
ArrayList<String> res = new ArrayList<>();
if (str == null || str.length() == 0) {
return res;
}
char[] chs = str.toCharArray();
process2(chs, 0, res);
return res;
}
/**
* 分支限界,提前杀死无用分支
*/
public static void process2(char[] str, int i, ArrayList<String> res) {
if (i == str.length) {
res.add(String.valueOf(str));
}
// 在当前将i后面位置的字符依次换到i位置的过程中,记录出现过的字符,如a出现过则visit[0]=true; c出现过则visit[2]=true
// 例如[aba]中0位置有三个分支,00交换/01交换/02交换
// 处理00交换时,a字符visit[0]=false没出现过;
// 处理01交换时,b字符visit[1]=false没出现过;
// 处理02交换时,a字符visit[0]=true出现过,那这个分支直接杀死
boolean[] visit = new boolean[26]; // visit[0 1 .. 25]
for (int j = i; j < str.length; j++) {
// 下一个字符没出现过,才会将它与i位置交换
if (!visit[str[j] - 'a']) {
visit[str[j] - 'a'] = true;// 记录此字符出现过
swap(str, i, j);
process2(str, i + 1, res);
swap(str, i, j);
}
}
}
public static void swap(char[] chs, int i, int j) {
char tmp = chs[i];
chs[i] = chs[j];
chs[j] = tmp;
}
public static void main(String[] args) {
String s = "aac";
List<String> ans1 = permutation(s);
for (String str : ans1) {
System.out.println(str);
}
System.out.println("=======");
List<String> ans2 = permutationNoRepeat(s);
for (String str : ans2) {
System.out.println(str);
}
}
}
package class11;
import java.util.Stack;
public class Code04_ReverseStackUsingRecursive {
/**
* 依次取出栈底的数,进行压栈保存,直到从底取到顶了,这时原始栈顶部的数据在程序栈的顶部,再将程序栈从顶至下push到栈中,即实现原始栈顶的元素现在到栈底了
*/
public static void reverse(Stack<Integer> stack) {
if (stack.isEmpty()) {
return;
}
int i = f(stack);
reverse(stack);
stack.push(i);
}
/**
* 功能:去掉栈底的数并返回这个数
*/
public static int f(Stack<Integer> stack) {
int result = stack.pop();
if (stack.isEmpty()) {
return result;
} else {
int last = f(stack);
stack.push(result);
return last;
}
}
public static void main(String[] args) {
Stack<Integer> test = new Stack<Integer>();
test.push(1);
test.push(2);
test.push(3);
test.push(4);
test.push(5);
reverse(test);
while (!test.isEmpty()) {
System.out.println(test.pop());
}
}
}
package class11;
/**
* 规定1和A对应、2和B对应、3和C对应...
* 那么一个数字字符串比如"111”就可以转化为:
* "AAA"、"KA"和"AK"
* 给定一个只有数字字符组成的字符串str,返回有多少种转化结果
*/
public class Code06_ConvertToLetterString {
public static int number(String str) {
if (str == null || str.length() == 0) {
return 0;
}
return process(str.toCharArray(), 0);
}
/**
* str中i之前的位置,如何转化已经做过决定了, 不用再关心,可以认为i位置之前啥都没有
*
* @param str
* @param i
* @return str中i及i位置之后的字符子串 有多少种转化的结果
*/
public static int process(char[] str, int i) {
// i到终止位置,说明这条分支整个走下来了,即将此分支从上到下的转化结果拼接后,形成1种有效的转化结果
if (i == str.length) { // base case
return 1;
}
// i还没有到终止位置
// i位置是0,i自己不能转化,(i和i+1)组成的'0x'也不能转化 -> i及i位置之后的字符子串无可转化结果
if (str[i] == '0') {
return 0;
}
// str[i]字符不是‘0’
if (str[i] == '1') {
int res = process(str, i + 1); // i自己作为单独的部分,求str中i+1及i+1位置之后的字符子串,有多少种转化的结果
if (i + 1 < str.length) {
res += process(str, i + 2); // (i和i+1)作为单独的部分,求str中i+2及i+2位置之后的字符子串,有多少种转化的结果
}
return res;
}
if (str[i] == '2') {
int res = process(str, i + 1); // i自己作为单独的部分,后续有多少种方法
// (i和i+1)作为单独的部分并且没有超过26,后续有多少种方法
if (i + 1 < str.length && (str[i + 1] >= '0' && str[i + 1] <= '6')) {
res += process(str, i + 2); // (i和i+1)作为单独的部分,后续有多少种方法
}
return res;
}
// str[i] == '3' ~ '9',i只能作为单独部分,(i和i+1)不可能作为单独部分
return process(str, i + 1);
}
/**
* 动态规划
*/
public static int myDP(String s) {
if (s == null || s.length() == 0) {
return 0;
}
char[] str = s.toCharArray();
int N = str.length;
int[] dp = new int[N + 1];
dp[N] = 1;
for (int index = N - 1; index >= 0; index--) {
if (str[index] == '0') {
dp[index] = 0;
} else if (str[index] == '1') {
dp[index] = dp[index + 1];
if (index + 1 < N) {
dp[index] += dp[index + 2];
}
} else if (str[index] == '2') {
dp[index] = dp[index + 1];
if (index + 1 < N && (str[index + 1] >= '0' && str[index + 1] <= '6')) {
dp[index] += dp[index + 2];
}
} else {
dp[index] = dp[index + 1];
}
}
return dp[0];
}
public static void main(String[] args) {
System.out.println(number("11111"));
System.out.println(myDP("11111"));
}
}
package class11;
/**
* 给定两个长度都为N的数组weights和values,
* weights[i]和values[i]分别代表 i号物品的重量和价值。
* 给定一个正数bag,表示一个载重bag的袋子,
* 你装的物品不能超过这个重量。
* 返回你能装下最多的价值是多少?
*/
public class Code07_Knapsack {
public static int getMaxValue(int[] w, int[] v, int bag) {
return process(w, v, 0, bag);
}
public static int getMaxValue2(int[] w, int[] v, int bag) {
return process2(w, v, 0, 0, bag);
}
/**
* 方式一:以剩余空间rest为参数
*
* @param index index之前的货物选与不选已定,不再操心
* @param rest bag的剩余空间
* @return 对index及其后面的货物自由选择,返回能够获得的最大价值
*/
public static int process(int[] w, int[] v, int index, int rest) {
if (rest < 0) { // base case 1
return -1;
}
if (index == w.length) { // base case 2
return 0;
}
// 有货也有空间
// 不选index位置的货物,求挑选index+1及其后位置的货物能够获得的最大价值
int p1 = process(w, v, index + 1, rest);
// 选择index位置的货物,bag剩余重量需减去index货物重量,求挑选index+1及其后位置的货物能够获得的最大价值
int p2next = process(w, v, index + 1, rest - w[index]);
int p2 = -1;
if (p2next != -1) {
p2 = v[index] + p2next;
}
return Math.max(p1, p2);
}
/**
* 方式二:以已用多少空间alreadyW为参数
*
* @param index index之前的货物选与不选已定,不再操心
* @param alreadyW bag已经用掉多少空间
* @param bag 背包总空间,固定不变
* @return 对index及其后面的货物自由选择,返回能够获得的最大价值
*/
public static int process2(int[] w, int[] v, int index, int alreadyW, int bag) {
if (alreadyW > bag) {
return -1;
}
// 重量没超
if (index == w.length) {
return 0;
}
int p1 = process2(w, v, index + 1, alreadyW, bag);
int p2next = process2(w, v, index + 1, alreadyW + w[index], bag);
int p2 = -1;
if (p2next != -1) {
p2 = v[index] + p2next;
}
return Math.max(p1, p2);
}
public static int dpWay(int[] w, int[] v, int bag) {
int N = w.length;
int[][] dp = new int[N + 1][bag + 1];
for (int index = N - 1; index >= 0; index--) {
for (int rest = 1; rest <= bag; rest++) {
dp[index][rest] = dp[index + 1][rest];
if (rest >= w[index]) {
dp[index][rest] = Math.max(dp[index][rest], v[index] + dp[index + 1][rest - w[index]]);
}
}
}
return dp[0][bag];
}
public static void main(String[] args) {
int[] weights = { 3, 2, 4, 7 };
int[] values = { 5, 6, 3, 19 };
int bag = 11;
System.out.println(getMaxValue(weights, values, bag));
System.out.println(dpWay(weights, values, bag));
}
}
package class11;
/**
* 给定一个整型数组arr,代表数值不同的纸牌排成一条线,玩家A和玩家B依次拿走每张纸牌,
* 规定玩家A先拿,玩家B后拿,但是每个玩家每次只能拿走最左或最右的纸牌,
* 玩家A和玩家B都绝顶聪明。请返回最后获胜者的分数。
*/
public class Code08_CardsInLine {
public static int win1(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
return Math.max(// 最大的分数即获胜者分数
f(arr, 0, arr.length - 1), // 先手在[0~L-1]获得的分数
s(arr, 0, arr.length - 1) // 后手在[0~L-1]获得的分数
);
}
/**
* 先手,自己有当下这一步的选择权
* 从arr的L-R范围上,如果先手拿牌,获得的最大分数
*/
public static int f(int[] arr, int L, int R) {
if (L == R) {
return arr[L];
}
return Math.max(// 自己先手选择,当然选最大的
arr[L] + s(arr, L + 1, R), // 自己选L,然后自己变后手,从[L+1,R]上获得的分数
arr[R] + s(arr, L, R - 1)// 自己选R,然后自己变后手,从[L,R-1]上获得的分数
);
}
/**
* 后手,自己无当下这一步的选择权,即当下这一步的选择是先手方留给你的,先手方肯定扔给你最差的
* 从arr的L-R范围上,如果后手拿牌,获得的最小分数(因为自己是后手,没有选择权,获得的分数由对方决定,而对方肯定给自己留最差的选择)
*/
public static int s(int[] arr, int L, int R) {
if (L == R) {// 仅剩L一张牌,又因为是后手拿牌,所以L肯定被对手拿走了,自己拿个锤子
return 0;
}
return Math.min(// 先手绝顶聪明,肯定知道自己拿完后后手方的两种选择中哪个最差,留给对方
f(arr, L + 1, R), // 对方拿完后,自己后手拿,若对方拿L,那自己变先手从[L+1.R]上获取主动权去拿最好的
f(arr, L, R - 1)// 对方拿完后,自己后手拿,若对方拿R,那自己变先手从[L.R-1]上获取主动权去拿最好的
);
}
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]);
}
public static void main(String[] args) {
int[] arr = { 1, 9, 1 };
System.out.println(win1(arr));
System.out.println(win2(arr));
}
}
package class11;
/**
* N皇后问题是指在N*N的棋盘上要摆N个皇后,
* 要求任何两个皇后不同行、不同列, 也不在同一条斜线上
* 给定一个整数n,返回n皇后的摆法有多少种。 n=1,返回1
* n=2或3,2皇后和3皇后问题无论怎么摆都不行,返回0
* n=8,返回92
*/
public class Code09_NQueens {
/**
* 给定一个整数n,返回n皇后的摆法有多少种
*/
public static int num1(int n) {
if (n < 1) {
return 0;
}
// record[0] ? record[1] ? record[2]
int[] record = new int[n]; // record[i] -> i行的皇后,放在了第几列
return process1(0, record, n);
}
/**
* 优化版:
* 位运算优化常数
* 请不要超过32皇后问题
*/
public static int num2(int n) {
if (n < 1 || n > 32) {
return 0;
}
// (1 << n) - 1的含义:假如n=4,1<<4-1=10000-1=01111,最终即生成一个二进后n位都是1,前面补0的数
int limit = n == 32 ? -1 : (1 << n) - 1;
return process2(limit, 0, 0, 0);
}
/**
* 潜台词:record[0..i-1]的皇后,任何两个皇后一定都不共行、不共列,不共斜线
* @param rowIndex 目前来到了第i行
* @param record record[0..i-1]表示之前的行,放了皇后的列位置
* @param n n代表整体一共有多少行
* @return 从第i行开始到第n行结束,在这些行上分别放皇后,满足条件的摆法有多少种
*/
public static int process1(int rowIndex, int[] record, int n) {
if (rowIndex == n) { // 终止行是n-1行,此时rowIndex到达n行,说明已越过最后一行,即此分支的路整个走通了
return 1;
}
int res = 0;
for (int colIndex = 0; colIndex < n; colIndex++) { // 当前行在rowIndex行,尝试rowIndex行所有的列 -> colIndex
// 当前rowIndex行的皇后,放在colIndex列,必须满足和之前(0..rowIndex-1)的皇后,不共行、不共列、不共斜线
if (isValid(record, rowIndex, colIndex)) {
record[rowIndex] = colIndex;
res += process1(rowIndex + 1, record, n);
}
}
return res;
}
/**
* 判断在rowIndex行的第colIndex列放皇后是否可以
* 需要根据record[0..rowIndex-1]行上已经放的皇后进行判断,不能同行、同列、对角线
* record[0..rowIndex-1]你需要看,record[rowIndex...]不需要看
*/
public static boolean isValid(int[] record, int rowIndex, int colIndex) {
for (int k = 0; k < rowIndex; k++) { // 之前的某个k行的皇后
if (
// 2点同列
colIndex == record[k]
||
// 2点在同一斜线上(即满足|列差值|=|行差值|)
Math.abs(record[k] - colIndex) == Math.abs(rowIndex - k)
) {
return false;
}
}
return true;
}
public static int process2(
int limit,// 固定,划定问题规模,后n位都是1
int colLim,// colLim 列的限制,1的位置不能放皇后,0的位置可以
int leftDiaLim,// leftDiaLim 左斜线的限制,1的位置不能放皇后,0的位置可以
int rightDiaLim// rightDiaLim 右斜线的限制,1的位置不能放皇后,0的位置可以
) {
if (colLim == limit) { // base case:列限制colLim的二进制形式的所有位上都是1,表示所有位置都放满皇后了,此分支有效
return 1;
}
/**
* pos:pos的二进制位上是1的位置才可以放皇后
*
* colLim | leftDiaLim | rightDiaLim -> 总限制,二进制位上是1的不能放皇后,是0的可以放皇后,但是二进制前面补了很多0造成干扰
* ~(colLim | leftDiaLim | rightDiaLim) -> 总限制取反,二进制位上是0的不能放皇后,是1的可以放皇后,但是二进制前面补了很多0变成了1仍造成干扰
* limit & (~(colLim | leftDiaLim | rightDiaLim)) -> 按位与,都是1的才取1,过滤掉了二进制前面补足数的干扰,剩下的所有1就是放皇后的位置
*/
int pos = limit & (~(colLim | leftDiaLim | rightDiaLim));
int mostRightOne = 0;
int res = 0;
// 将pos二进制位上是1的位置分别放皇后
while (pos != 0) {
mostRightOne = pos & (~pos + 1);// 只保留post二进制最右侧的1,其余为全变0
pos = pos - mostRightOne;// 将post最右侧的1变0,下次再进while循环时提取的mostRightOne只保留post倒数第二位的1
res += process2(limit,
colLim | mostRightOne,// 皇后放在mostRightOne二进制为1的位置,后续列不能放此位置了,此位置+当前对列的限制->对下一行列的限制
(leftDiaLim | mostRightOne) << 1,// 当前左斜线限制+皇后放的位置,整体左移,即整体向左下角滑动一步,即下一行在这些位置上都不能放皇后
// >>>无符号右移,高位补0
(rightDiaLim | mostRightOne) >>> 1);// 当前右斜线限制+皇后放的位置,整体右移,即整体向右下角滑动一步,即下一行在这些位置上都不能放皇后
}
return res;
}
public static void main(String[] args) {
int n = 14;
long start = System.currentTimeMillis();
System.out.println(num2(n));
long end = System.currentTimeMillis();
System.out.println("cost time: " + (end - start) + "ms");
start = System.currentTimeMillis();
System.out.println(num1(n));
end = System.currentTimeMillis();
System.out.println("cost time: " + (end - start) + "ms");
}
}
class12
package class12;
public class Code01_RobotWalk {
/**
* 解法1:暴力递归
*
* @param N 总共N个位置
* @param cur 从cur位置出发
* @param rest 还剩rest步
* @param P 最终要到达的位置
* @return 返回最终能达到P的方法数
*/
public static int ways1(int N, int cur, int rest, int P) {
// 参数无效直接返回0
if (N < 2 || rest < 1 || cur < 1 || cur > N || P < 1 || P > N) {
return 0;
}
// 总共N个位置,从M点出发,还剩K步,返回最终能达到P的方法数
return walk(N, cur, rest, P);
}
/**
* 解法2:记忆化搜索
*/
public static int waysCache(int N, int cur, int rest, int P) {
if (N < 2 || rest < 1 || cur < 1 || cur > N || P < 1 || P > N) {
return 0;
}
// 存储从cur位置出发,走rest步,到达P位置的走法。
// 当前位置必在[0,N]之间,共N+1种可能
// 剩余步数会逐步递减,必不会超过初始步数,必在[0,rest]之间,共rest+1种可能
int[][] dp = new int[N + 1][rest + 1];
// 全部初始化为-1,-1代表该位置之前没有计算结果,需要计算,若为其他值,代表之前算过了,直接返回该位置的值即可
for (int i = 0; i <= N + 1; i++) {
for (int j = 0; j <= rest + 1; j++) {
dp[i][j] = -1;
}
}
return walkCache(N, cur, rest, P, dp);
}
public static int walkCache(int N, int cur, int rest, int P,int[][] dp) {
if (dp[cur][rest] != -1) {// 缓存中有结果,直接返回,减少重复计算
return dp[cur][rest];
}
if (rest == 0) {
dp[cur][rest] = cur == P ? 1 : 0;// 将结果缓存起来
return dp[cur][rest];
}
if (cur == 1) {
dp[cur][rest] = walkCache(N, 2, rest - 1, P, dp);// 将结果缓存起来
return dp[cur][rest];
}
if (cur == N) {
dp[cur][rest] = walkCache(N, N - 1, rest - 1, P, dp);// 将结果缓存起来
return dp[cur][rest];
}
dp[cur][rest] = walkCache(N, cur + 1, rest - 1, P, dp) + walkCache(N, cur - 1, rest - 1, P, dp);// 将结果缓存起来
return dp[cur][rest];
}
// N : 位置为1 ~ N,固定参数
// cur : 当前在cur位置,可变参数
// rest : 还剩res步没有走,可变参数
// P : 最终目标位置是P,固定参数
// 该函数的含义:只能在1~N这些位置上移动,当前在cur位置,走完rest步之后,停在P位置的方法数作为返回值返回
public static int walk(int N, int cur, int rest, int P) {
// 如果没有剩余步数了,当前的cur位置就是最后的位置
// 如果最后的位置停在P上,那么之前做的移动是有效的
// 如果最后的位置没在P上,那么之前做的移动是无效的
if (rest == 0) {
return cur == P ? 1 : 0;
}
// 如果还有rest步要走,而当前的cur位置在1位置上,那么当前这步只能从1走向2
// 后续的过程就是,来到2位置上,还剩rest-1步要走
if (cur == 1) {
return walk(N, 2, rest - 1, P);
}
// 如果还有rest步要走,而当前的cur位置在N位置上,那么当前这步只能从N走向N-1
// 后续的过程就是,来到N-1位置上,还剩rest-1步要走
if (cur == N) {
return walk(N, N - 1, rest - 1, P);
}
// 如果还有rest步要走,而当前的cur位置在中间位置上,那么当前这步可以走向左,也可以走向右
// 走向左之后,后续的过程就是,来到cur-1位置上,还剩rest-1步要走
// 走向右之后,后续的过程就是,来到cur+1位置上,还剩rest-1步要走
// 走向左、走向右是截然不同的方法,所以总方法数要都算上
return walk(N, cur + 1, rest - 1, P) + walk(N, cur - 1, rest - 1, P);
}
public static int ways2(int N, int M, int K, int P) {
// 参数无效直接返回0
if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {
return 0;
}
int[][] dp = new int[K + 1][N + 1];
dp[0][P] = 1;
for (int i = 1; i <= K; i++) {
for (int j = 1; j <= N; j++) {
if (j == 1) {
dp[i][j] = dp[i - 1][2];
} else if (j == N) {
dp[i][j] = dp[i - 1][N - 1];
} else {
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1];
}
}
}
return dp[K][M];
}
public static int ways3(int N, int M, int K, int P) {
// 参数无效直接返回0
if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {
return 0;
}
int[] dp = new int[N + 1];
dp[P] = 1;
for (int i = 1; i <= K; i++) {
int leftUp = dp[1];// 左上角的值
for (int j = 1; j <= N; j++) {
int tmp = dp[j];
if (j == 1) {
dp[j] = dp[j + 1];
} else if (j == N) {
dp[j] = leftUp;
} else {
dp[j] = leftUp + dp[j + 1];
}
leftUp = tmp;
}
}
return dp[M];
}
// ways4是你的方法
public static int ways4(int N, int M, int K, int P) {
if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {
return 0;
}
return process(N, 0, P, M, K);
}
// 一共N个位置,从M点出发,一共只有K步。返回走到位置j,剩余步数为i的方法数
public static int process(int N, int i, int j, int M, int K) {
if (i == K) {
return j == M ? 1 : 0;
}
if (j == 1) {
return process(N, i + 1, j + 1, M, K);
}
if (j == N) {
return process(N, i + 1, j - 1, M, K);
}
return process(N, i + 1, j + 1, M, K) + process(N, i + 1, j - 1, M, K);
}
// ways5是你的方法的dp优化
public static int ways5(int N, int M, int K, int P) {
if (N < 2 || K < 1 || M < 1 || M > N || P < 1 || P > N) {
return 0;
}
int[][] dp = new int[K + 1][N + 1];
dp[K][M] = 1;
for (int i = K - 1; i >= 0; i--) {
for (int j = 1; j <= N; j++) {
if (j == 1) {
dp[i][j] = dp[i + 1][j + 1];
} else if (j == N) {
dp[i][j] = dp[i + 1][j - 1];
} else {
dp[i][j] = dp[i + 1][j + 1] + dp[i + 1][j - 1];
}
}
}
return dp[0][P];
}
public static void main(String[] args) {
System.out.println(ways1(7, 4, 9, 5));
System.out.println(ways2(7, 4, 9, 5));
System.out.println(ways3(7, 4, 9, 5));
System.out.println(ways4(7, 4, 9, 5));
System.out.println(ways5(7, 4, 9, 5));
}
}
package class12;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
/**
* 给定一个字符串str,给定一个字符串类型的数组arr。
* arr里的每一个字符串,代表一张贴纸,你可以把单个字符剪开使用,目的是拼出str来。
* 每张贴纸可以使用多次,返回需要至少多少张贴纸可以完成这个任务。
* 例子:str= "babac",arr = {"ba","c","abcd"}
* 至少需要两张贴纸"ba"和"abcd",因为使用这两张贴纸,把每一个字符单独剪开,含有2个a、2个b、1个c。是可以拼出str的。所以返回2。
*/
public class Code02_StickersToSpellWord {
/**
* 划分方案1:
* 记忆化搜索
* @param stickers 都有哪些贴纸
* @param target 待拼接的字符串
* @return 拼出target至少需要的贴纸数
*/
public static int minStickers1(String[] stickers, String target) {
int n = stickers.length;
// 将每个贴纸都转成int[26],0位置代表贴纸字符串中有多少个a,1位置代表有多少个b,...,25位置代表有多少个z
int[][] stickerMap = new int[n][26];
for (int i = 0; i < n; i++) {
char[] str = stickers[i].toCharArray();
for (char c : str) {
stickerMap[i][c - 'a']++;
}
}
// 记忆化搜索傻缓存 k-字符串 v-拼接k至少需要的贴纸数
HashMap<String, Integer> dp = new HashMap<>();
dp.put("", 0);// 空字符串不需要贴纸
return process1(dp, stickerMap, target);
}
/**
* @param dp 傻缓存
* @param stickerMap 贴纸数组转化而来,固定不变
* @param restStr ★★★★唯一可变参数:剩余待拼接字符串
* @return 拼出target至少需要的贴纸数
*
* ★★★★规定return -1代表贴纸品不出字符串
*/
public static int process1(HashMap<String, Integer> dp, int[][] stickerMap, String restStr) {
if (dp.containsKey(restStr)) {
return dp.get(restStr);
}
/**
* 将可变参数restStr转成线性结构:
* int[26],每位代表restStr中有多少个该位字符
*/
int[] restStrMap = new int[26];
char[] restStrChars = restStr.toCharArray();
for (char c : restStrChars) {
restStrMap[c - 'a']++;
}
int ans = Integer.MAX_VALUE;// 先假设至少需要无穷多个贴纸
int n = stickerMap.length;
for (int i = 0; i < n; i++) {// 循环每个贴纸
/**
* 判断贴纸有效性
* 当前贴纸中的字符 和 待拼接字符串的字符 无交集时,跳过当前贴纸
* eg: restStr="abc",贴纸=“xyz”,经"xyz"处理后str还是“abc”,"abc"没处理完,后面还要被贴纸“xyz”处理->栈溢出
*/
boolean available = false;// 当前贴纸有效性
for (int k = 0; k < 26; k++) {
// 待拼接字符串需要当前字符 && 当前贴纸有当前字符,只要帖只中有一个字符可用,该帖之就有用
if (restStrMap[k] > 0 && stickerMap[i][k] > 0) {
available = true;
break;
}
}
if (!available) {
continue;
}
/**
* 判断贴纸有效性的另一种方法,感觉没太好
* 当前贴纸中 没有 待拼接字符串的第一个字符,贴纸无效
*/
// if (stickerMap[i][restStrChars[0] - 'a'] == 0) {
// continue;
// }
// restStr使用当前贴纸后,还剩余的待拼接字符串(顺序不重要)
StringBuilder restRestStr = new StringBuilder();
for (int j = 0; j < 26; j++) {// 循环各个位置
if (restStrMap[j] > 0) { // restStr包含当前位置的字符,才需要从当前贴纸取该字符
/**
* x=使用当前贴纸后,待拼接字符串中还有多少当前字符没有得到拼接:
* x=restStrMap[j]-stickerMap[i][j]=待拼接字符串需要多少个当前字符-当前贴纸有多少当前字符
* 当前贴纸拥有的当前字符数 有可能多于 待拼接字符串需要的当前字符数,说明使用此贴纸后消去了所有当前字符
* eg: "abaabc"使用“a”贴纸后只去掉了一个a,restRestStr=“aabbc”
* eg: "abaabc"使用“aaaaaa”贴纸后去掉了三个a,restRestStr=“bbc”
*/
for (int k = 0; k < Math.max(0, restStrMap[j] - stickerMap[i][j]); k++) {
restRestStr.append((char) ('a' + j));
}
}
}
/**
* 递归后续字符串,1 + tmp是使用当前贴纸时,拼出restStr需要的贴纸数
* tmp != -1 代表贴纸能拼出restRestStr,ans=1 + tmp
* tmp == -1 代表贴纸能拼不出restRestStr,ans 仍等于无穷大
*/
int tmp = process1(dp, stickerMap, restRestStr.toString());
if (tmp != -1) {
ans = Math.min(ans, 1 + tmp);
}
}
dp.put(restStr, ans == Integer.MAX_VALUE ? -1 : ans);
return dp.get(restStr);
}
public static int minStickers2(String[] stickers, String target) {
int n = stickers.length;
int[][] map = new int[n][26];
for (int i = 0; i < n; i++) {
char[] str = stickers[i].toCharArray();
for (char c : str) {
map[i][c - 'a']++;
}
}
char[] str = target.toCharArray();
int[] tmap = new int[26];
for (char c : str) {
tmap[c - 'a']++;
}
HashMap<String, Integer> dp = new HashMap<>();
int ans = process2(map, 0, tmap, dp);
return ans;
}
public static int process2(int[][] map, int i, int[] tmap, HashMap<String, Integer> dp) {
StringBuilder keyBuilder = new StringBuilder();
keyBuilder.append(i + "_");
for (int asc = 0; asc < 26; asc++) {
if (tmap[asc] != 0) {
keyBuilder.append((char) (asc + 'a') + "_" + tmap[asc] + "_");
}
}
String key = keyBuilder.toString();
if (dp.containsKey(key)) {
return dp.get(key);
}
boolean finish = true;
for (int asc = 0; asc < 26; asc++) {
if (tmap[asc] != 0) {
finish = false;
break;
}
}
if (finish) {
dp.put(key, 0);
return 0;
}
if (i == map.length) {
dp.put(key, -1);
return -1;
}
int maxZhang = 0;
for (int asc = 0; asc < 26; asc++) {
if (map[i][asc] != 0 && tmap[asc] != 0) {
maxZhang = Math.max(maxZhang, (tmap[asc] / map[i][asc]) + (tmap[asc] % map[i][asc] == 0 ? 0 : 1));
}
}
int[] backup = Arrays.copyOf(tmap, tmap.length);
int min = Integer.MAX_VALUE;
int next = process2(map, i + 1, tmap, dp);
tmap = Arrays.copyOf(backup, backup.length);
if (next != -1) {
min = next;
}
for (int zhang = 1; zhang <= maxZhang; zhang++) {
for (int asc = 0; asc < 26; asc++) {
tmap[asc] = Math.max(0, tmap[asc] - (map[i][asc] * zhang));
}
next = process2(map, i + 1, tmap, dp);
tmap = Arrays.copyOf(backup, backup.length);
if (next != -1) {
min = Math.min(min, zhang + next);
}
}
int ans = min == Integer.MAX_VALUE ? -1 : min;
dp.put(key, ans);
return ans;
}
public static void main(String[] args) {
String str = "aabbacddaaaab";
String[] arr = {"abddd","bbbbbbabbbb","cannx", "xyz"};
System.out.println(minStickers1(arr,str));
System.out.println(minStickers2(arr,str));
}
}
package class12;
/**
* 给定两个长度都为N的数组weights和values,
* weights[i]和values[i]分别代表 i号物品的重量和价值。
* 给定一个正数bag,表示一个载重bag的袋子,
* 你装的物品不能超过这个重量。
* 返回你能装下最多的价值是多少?
*/
public class Code03_Knapsack {
public static int getMaxValue(int[] w, int[] v, int bag) {
return process(w, v, 0, bag);
}
/**
* 暴力递归
*
* @param index index之前的货物选与不选已定,不再操心
* @param rest bag的剩余空间
* @return 对index及其后面的货物自由选择,返回能够获得的最大价值
*/
public static int process(int[] w, int[] v, int index, int rest) {
if (rest < 0) { // base case 1
return -1;
}
if (index == w.length) { // base case 2
return 0;
}
// 有货也有空间
// 不选index位置的货物,求挑选index+1及其后位置的货物能够获得的最大价值
int p1 = process(w, v, index + 1, rest);
// 选择index位置的货物,bag剩余重量需减去index货物重量,求挑选index+1及其后位置的货物能够获得的最大价值
int p2next = process(w, v, index + 1, rest - w[index]);
int p2 = -1;
if (p2next != -1) {
p2 = v[index] + p2next;
}
return Math.max(p1, p2);
}
/**
* 动态规划
*/
public static int dpWay(int[] w, int[] v, int bag) {
int N = w.length;
// int[index][rest] dp 初始化时所有元素的值都是0
// index范围[0,N]共N+1个数,准备N+1长度;rest范围[0,bag]共bag+1个数,准备bag+1长度
int[][] dp = new int[N + 1][bag + 1];
/**
* 从dp[N][...]行向上逐行填充行
* 根据暴力递归base case 2可得dp[N][...]这一行全都是0,上步初始化时已经赋0
*/
// 从下到上,直到0行。为什么要从下到上?因为递归中process(index)是依赖process(index+1)的,所以先把index最大的求出来,小的index才能直接用
for (int index = N - 1; index >= 0; index--) {
// 从左到右,直到bag列
for (int rest = 0; rest <= bag; rest++) {
// 求dp[index][rest]的值
int p1 = dp[index + 1][rest];// 当前是index行,因为是从下往上求值,所以index+1行的值都已经求过了
int p2 = -1;
if (rest - w[index] >= 0) {
p2 = v[index] + dp[index + 1][rest - w[index]];
}
dp[index][rest] = Math.max(p1, p2);
}
}
return dp[0][bag];
}
public static void main(String[] args) {
int[] weights = {3, 2, 4, 7};
int[] values = {5, 6, 3, 19};
int bag = 11;
System.out.println(getMaxValue(weights, values, bag));
System.out.println(dpWay(weights, values, bag));
}
}
package class12;
public class Code04_CardsInLine {
public static int win1(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
return Math.max(// 最大的分数即获胜者分数
f(arr, 0, arr.length - 1), // 先手在[0~L-1]获得的分数
s(arr, 0, arr.length - 1) // 后手在[0~L-1]获得的分数
);
}
/**
* 先手,自己有当下这一步的选择权
* 从arr的L-R范围上,如果先手拿牌,获得的最大分数
*/
public static int f(int[] arr, int L, int R) {
if (L == R) {
return arr[L];
}
return Math.max(// 自己先手选择,当然选最大的
arr[L] + s(arr, L + 1, R), // 自己选L,然后自己变后手,从[L+1,R]上获得的分数
arr[R] + s(arr, L, R - 1)// 自己选R,然后自己变后手,从[L,R-1]上获得的分数
);
}
/**
* 后手,自己无当下这一步的选择权,即当下这一步的选择是先手方留给你的,先手方肯定扔给你最差的
* 从arr的L-R范围上,如果后手拿牌,获得的最小分数(因为自己是后手,没有选择权,获得的分数由对方决定,而对方肯定给自己留最差的选择)
*/
public static int s(int[] arr, int L, int R) {
if (L == R) {// 仅剩L一张牌,又因为是后手拿牌,所以L肯定被对手拿走了,自己拿个锤子
return 0;
}
return Math.min(// 先手绝顶聪明,肯定知道自己拿完后后手方的两种选择中哪个最差,留给对方
f(arr, L + 1, R), // 对方拿完后,自己后手拿,若对方拿L,那自己变先手从[L+1.R]上获取主动权去拿最好的
f(arr, L, R - 1)// 对方拿完后,自己后手拿,若对方拿R,那自己变先手从[L.R-1]上获取主动权去拿最好的
);
}
public static int windp(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
int N = arr.length;
int[][] f = new int[N][N];
int[][] s = new int[N][N];
for (int i = 0; i < N; i++) {
f[i][i] = arr[i];
s[i][i] = 0;
}
// 0,0 右下方移动
// 0,1
// 0,2
// 0,N-1
for (int col = 1; col < N; col++) {
// 对角线出发位置(0,col)
int L = 0;
int R = col;
while (L < N && R < N) {
f[L][R] = Math.max(arr[L] + s[L + 1][R], arr[R] + s[L][R - 1]);
s[L][R] = Math.min(f[L + 1][R], f[L][R - 1]);
L++;
R++;
}
}
return Math.max(f[0][N - 1], s[0][N - 1]);
}
/**
* 我自己写的
*/
public static int myDP(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
int N = arr.length;
int[][] f = new int[N][N];
int[][] s = new int[N][N];
for (int i = 0; i < N; i++) {
f[i][i] = arr[i];
s[i][i] = 0;
}
for (int colIndex = 1; colIndex < N; colIndex++) {
for (int row = 0, col = colIndex; row < N && col < N; row++, col++) {
f[row][col] = Math.max(arr[row] + s[row + 1][col], arr[col] + s[row][col - 1]);
s[row][col] = Math.min(f[row + 1][col], f[row][col - 1]);
}
}
return Math.max(f[0][N - 1], s[0][N - 1]);
}
public static void main(String[] args) {
int[] arr = {5, 7, 4, 5, 8, 1, 6, 0, 3, 4, 6, 1, 7};
System.out.println(win1(arr));
System.out.println(windp(arr));
System.out.println(myDP(arr));
}
}
package class12;
/**
* 两个字符串的最长公共子序列问题
*/
public class Code05_LongestCommonSubsequence {
public static int lcse(char[] str1, char[] str2) {
int[][] dp = new int[str1.length][str2.length];
dp[0][0] = str1[0] == str2[0] ? 1 : 0;
for (int i = 1; i < str1.length; i++) {
dp[i][0] = Math.max(dp[i - 1][0], str1[i] == str2[0] ? 1 : 0);
}
for (int j = 1; j < str2.length; j++) {
dp[0][j] = Math.max(dp[0][j - 1], str1[0] == str2[j] ? 1 : 0);
}
for (int i = 1; i < str1.length; i++) {
for (int j = 1; j < str2.length; j++) {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
if (str1[i] == str2[j]) {
dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - 1] + 1);
}
}
}
return dp[str1.length - 1][str2.length - 1];
}
public static void main(String[] args) {
}
}
package class12;
import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
// 题目
// 数组arr代表每一个咖啡机冲一杯咖啡的时间,每个咖啡机只能串行的制造咖啡。
// 现在有n个人需要喝咖啡,只能用咖啡机来制造咖啡。
// 认为每个人喝咖啡的时间非常短,冲好的时间即是喝完的时间。
// 每个人喝完之后咖啡杯可以选择洗或者自然挥发干净,只有一台洗咖啡杯的机器,只能串行的洗咖啡杯。
// 洗杯子的机器洗完一个杯子时间为a,任何一个杯子自然挥发干净的时间为b。
// 四个参数:arr, n, a, b
// 假设时间点从0开始,返回所有人喝完咖啡并洗完咖啡杯的全部过程结束后,至少来到什么时间点。
public class Code06_Coffee {
// 方法一:暴力尝试方法
public static int minTime1(int[] arr, int n, int a, int b) {
int[] times = new int[arr.length];
int[] drink = new int[n];
return forceMake(arr, times, 0, drink, n, a, b);
}
// 方法一,每个人暴力尝试用每一个咖啡机给自己做咖啡
public static int forceMake(int[] arr, int[] times, int kth, int[] drink, int n, int a, int b) {
if (kth == n) {
int[] drinkSorted = Arrays.copyOf(drink, kth);
Arrays.sort(drinkSorted);
return forceWash(drinkSorted, a, b, 0, 0, 0);
}
int time = Integer.MAX_VALUE;
for (int i = 0; i < arr.length; i++) {
int work = arr[i];
int pre = times[i];
drink[kth] = pre + work;
times[i] = pre + work;
time = Math.min(time, forceMake(arr, times, kth + 1, drink, n, a, b));
drink[kth] = 0;
times[i] = pre;
}
return time;
}
// 方法一,暴力尝试洗咖啡杯的方式
public static int forceWash(int[] drinks, int a, int b, int index, int washLine, int time) {
if (index == drinks.length) {
return time;
}
// 选择一:当前index号咖啡杯,选择用洗咖啡机刷干净
int wash = Math.max(drinks[index], washLine) + a;
int ans1 = forceWash(drinks, a, b, index + 1, wash, Math.max(wash, time));
// 选择二:当前index号咖啡杯,选择自然挥发
int dry = drinks[index] + b;
int ans2 = forceWash(drinks, a, b, index + 1, washLine, Math.max(dry, time));
return Math.min(ans1, ans2);
}
// 方法二:稍微好一点的解法
public static class Machine {
public int timePoint;
public int workTime;
public Machine(int t, int w) {
timePoint = t;
workTime = w;
}
}
public static class MachineComparator implements Comparator<Machine> {
@Override
public int compare(Machine o1, Machine o2) {
return (o1.timePoint + o1.workTime) - (o2.timePoint + o2.workTime);
}
}
// 方法二,每个人暴力尝试用每一个咖啡机给自己做咖啡,优化成贪心
public static int minTime2(int[] arr, int n, int a, int b) {
PriorityQueue<Machine> heap = new PriorityQueue<Machine>(new MachineComparator());
for (int i = 0; i < arr.length; i++) {
heap.add(new Machine(0, arr[i]));
}
int[] drinks = new int[n];
for (int i = 0; i < n; i++) {
Machine cur = heap.poll();
cur.timePoint += cur.workTime;
drinks[i] = cur.timePoint;
heap.add(cur);
}
return process(drinks, a, b, 0, 0);
}
// 方法二,洗咖啡杯的方式和原来一样,只是这个暴力版本减少了一个可变参数
public static int process(int[] drinks, int a, int b, int index, int washLine) {
if (index == drinks.length - 1) {
return Math.min(Math.max(washLine, drinks[index]) + a, drinks[index] + b);
}
// wash是我当前的咖啡杯,洗完的时间
int wash = Math.max(washLine, drinks[index]) + a;
int next1 = process(drinks, a, b, index + 1, wash);
int p1 = Math.max(wash, next1);
int dry = drinks[index] + b;
int next2 = process(drinks, a, b, index + 1, washLine);
int p2 = Math.max(dry, next2);
return Math.min(p1, p2);
}
// 方法三:最终版本,把方法二洗咖啡杯的暴力尝试进一步优化成动态规划
public static int minTime3(int[] arr, int n, int a, int b) {
PriorityQueue<Machine> heap = new PriorityQueue<Machine>(new MachineComparator());
for (int i = 0; i < arr.length; i++) {
heap.add(new Machine(0, arr[i]));
}
int[] drinks = new int[n];
for (int i = 0; i < n; i++) {
Machine cur = heap.poll();
cur.timePoint += cur.workTime;
drinks[i] = cur.timePoint;
heap.add(cur);
}
if (a >= b) {
return drinks[n - 1] + b;
}
int[][] dp = new int[n][drinks[n - 1] + n * a];
for (int i = 0; i < dp[0].length; i++) {
dp[n - 1][i] = Math.min(Math.max(i, drinks[n - 1]) + a, drinks[n - 1] + b);
}
for (int row = n - 2; row >= 0; row--) { // row 咖啡杯的编号
int washLine = drinks[row] + (row + 1) * a;
for (int col = 0; col < washLine; col++) {
int wash = Math.max(col, drinks[row]) + a;
dp[row][col] = Math.min(Math.max(wash, dp[row + 1][wash]), Math.max(drinks[row] + b, dp[row + 1][col]));
}
}
return dp[0][0];
}
// for test
public static int[] randomArray(int len, int max) {
int[] arr = new int[len];
for (int i = 0; i < len; i++) {
arr[i] = (int) (Math.random() * max) + 1;
}
return arr;
}
// for test
public static void printArray(int[] arr) {
System.out.print("arr : ");
for (int j = 0; j < arr.length; j++) {
System.out.print(arr[j] + ", ");
}
System.out.println();
}
public static void main(String[] args) {
int len = 5;
int max = 9;
int testTime = 50000;
for (int i = 0; i < testTime; i++) {
int[] arr = randomArray(len, max);
int n = (int) (Math.random() * 5) + 1;
int a = (int) (Math.random() * 5) + 1;
int b = (int) (Math.random() * 10) + 1;
int ans1 = minTime1(arr, n, a, b);
int ans2 = minTime2(arr, n, a, b);
int ans3 = minTime3(arr, n, a, b);
if (ans1 != ans2 || ans2 != ans3) {
printArray(arr);
System.out.println("n : " + n);
System.out.println("a : " + a);
System.out.println("b : " + b);
System.out.println(ans1 + " , " + ans2 + " , " + ans3);
System.out.println("===============");
break;
}
}
}
}
package class12;
/**
* 给定一个数组drinkFinishTimePoints,代表每个人喝完咖啡,可以开始刷杯子/自然挥发的时间点,从小到大有序排列
* 只有一台清洗机,一次只能洗一个杯子,洗一个需要耗费a时间,只能串行洗
* 每个咖啡杯也可以自己挥发干净,时间耗费b,咖啡杯可以并行挥发
* 返回让所有咖啡杯变干净的最早完成时间
* 共提供三个参数:int[] drinkFinishTimePoints、int a、int b
*/
public class Code06_MyCoffee {
public static int minTime(int[] drinkFinishTimePoints, int a, int b) {
return process(drinkFinishTimePoints, a, b, 0, 0);
}
/**
* 暴力递归
*
* @param drinkFinishTimePoints 每个人喝完咖啡,可以开始刷杯子/自然挥发的时间点,从小到大有序排列
* @param a 洗一个杯子需要耗费的时间【固定值】
* @param b 挥发一个杯子需要耗费的时间【固定值】
* @param index drinkFinishTimePoints[0...index-1]喝完的咖啡已经决定是洗还是挥发
* @param washableTimePoint 清洗机下一次可以用的时间点(当前可能正在被占用)
* @return 将drinks[index ...]中的所有杯子变干净的最早时间点
*/
public static int process(int[] drinkFinishTimePoints, int a, int b, int index, int washableTimePoint) {
if (index == drinkFinishTimePoints.length - 1) {// 最后一个杯子
int dry = drinkFinishTimePoints[index] + b;// 挥发完的时间点
// 可以开始刷杯子的时间点i vs 清洗机下一次空闲的时间点j: 如果i>j喝完咖啡就可以立即洗,如果i<j喝完咖啡需要等清洗机空闲才能洗
int wash = Math.max(drinkFinishTimePoints[index] + a, washableTimePoint + a);
return Math.min(dry, wash);
}
// 还有不止一个杯子
//------------------------------------------------------------------
// 采用挥发方案,后续杯子全部处理完的时间点:当前杯子不占用清洗机,washableTimePoint不变
int laterTime = process(drinkFinishTimePoints, a, b, index + 1, washableTimePoint);
// 采用挥发方案,当前杯子挥发完的时间点
int curFinishTime = drinkFinishTimePoints[index] + b;
// 采用挥发方案,当前杯子挥发完,后续杯子也处理完的时间点
int dryEndTimePoint = Math.max(laterTime, curFinishTime);
//-------------------------------------------------------------------
// 采用清洗方案,后续杯子全部处理完的时间点: 当前杯子占用清洗机
laterTime = process(drinkFinishTimePoints, a, b, index + 1, washableTimePoint + a);
// 采用清洗方案,当前杯子清洗完的时间点
curFinishTime = drinkFinishTimePoints[index] + a;
int washEndTimePoint = Math.max(laterTime, curFinishTime);
// 两种方案最先结束的
return Math.min(dryEndTimePoint, washEndTimePoint);
}
/**
* 动态规划
*
* @param drinkFinishTimePoints 每个人喝完咖啡,可以开始刷杯子/自然挥发的时间点,从小到大有序排列
* @param a 洗一个杯子需要耗费的时间【固定值】
* @param b 挥发一个杯子需要耗费的时间【固定值】
* @return 将drinks[index ...]中的所有杯子变干净的最早时间点
*/
public static int minTimeDP(int[] drinkFinishTimePoints, int a, int b) {
int N = drinkFinishTimePoints.length;
if (a >= b) {// 清洗耗费时间比挥发耗费时间还长,全部杯子都挥发,返回最后一个挥发完的时间点
return drinkFinishTimePoints[N - 1] + b;
}
/**
* 动态规划
*
* 组织dp[index][washableTimePoint]
* index 0~N-1
* washableTimePoint 0~washableTimePointEnd 最大值需要根据业务分析,最大值是所有杯子都清洗时最后一个清洗完的时间点
*/
int washableTimePointEnd = 0;
for (int index = 0; index < N; index++) {
washableTimePointEnd = Math.max(washableTimePointEnd + a, drinkFinishTimePoints[index] + a);
}
int[][] dp = new int[N][washableTimePointEnd + 1];
// base case:index=N-1时dp[N-1][...]=?
for (int washableTimePoint = 0; washableTimePoint <= washableTimePointEnd; washableTimePoint++) {
int dry = drinkFinishTimePoints[N - 1] + b;
int wash = Math.max(drinkFinishTimePoints[N - 1] + a, washableTimePoint + a);
dp[N - 1][washableTimePoint] = Math.min(dry, wash);
}
// dp[N-2 ...][0 ... ]
for (int index = N - 2; index >= 0; index--) {
for (int washableTimePoint = 0; washableTimePoint <= washableTimePointEnd; washableTimePoint++) {
// 挥发
int laterTime = dp[index + 1][washableTimePoint];
int curFinishTime = drinkFinishTimePoints[index] + b;
int dp1 = Math.max(laterTime, curFinishTime);
// 清洗
int dp2 = Integer.MAX_VALUE;
if (washableTimePoint + a <= washableTimePointEnd) {// 洗完时间点不越界时,清洗方案才成立
laterTime = dp[index + 1][washableTimePoint + a];// 保证washableTimePoint + a不越界
curFinishTime = drinkFinishTimePoints[index] + a;
dp2 = Math.max(laterTime, curFinishTime);
}
dp[index][washableTimePoint] = Math.min(dp1, dp2);
}
}
return dp[0][0];
}
public static void main(String[] args) {
int[] drinkFinishTimePoints = new int[]{1,3,4,4,4,6,7,7,7,9,11};
int a = 1;
int b = 5;
System.out.println(minTime(drinkFinishTimePoints,a,b));
System.out.println(minTimeDP(drinkFinishTimePoints,a,b));
}
}
package class12;
public class Code07_HorseJump {
// 10*9
// 0~9 y
// 0~8 x
public static int ways(int a, int b, int step) {
return f(0, 0, step, a, b);
}
// 马在(i,j)位置,还有step步要去跳
// 返回最终来到(a,b)的方法数
public static int f(int i, int j, int step, int a, int b) {
if (i < 0 || i > 9 || j < 0 || j > 8) {
return 0;
}
if (step == 0) {
return (i == a && j == b) ? 1 : 0;
}
return f(i - 2, j + 1, step - 1, a, b)
+ f(i - 1, j + 2, step - 1, a, b)
+ f(i + 1, j + 2, step - 1, a, b)
+ f(i + 2, j + 1, step - 1, a, b)
+ f(i + 2, j - 1, step - 1, a, b)
+ f(i + 1, j - 2, step - 1, a, b)
+ f(i - 1, j - 2, step - 1, a, b)
+ f(i - 2, j - 1, step - 1, a, b);
}
public static int waysdp(int a, int b, int s) {
// (i,j,0~ step)
int[][][] dp = new int[10][9][s+1];
dp[a][b][0] = 1;
for(int step = 1 ; step <= s;step++ ) { // 按层来
for(int i = 0 ; i < 10;i++) {
for(int j = 0 ; j < 9; j++) {
dp[i][j][step] = getValue(dp,i - 2, j + 1, step - 1)
+ getValue(dp,i - 1, j + 2, step - 1)
+ getValue(dp,i + 1, j + 2, step - 1)
+ getValue(dp,i + 2, j + 1, step - 1)
+ getValue(dp,i + 2, j - 1, step - 1)
+ getValue(dp,i + 1, j - 2, step - 1)
+ getValue(dp,i - 1, j - 2, step - 1)
+ getValue(dp,i - 2, j - 1, step - 1);
}
}
}
return dp[0][0][s];
}
// 在dp表中,得到dp[i][j][step]的值,但如果(i,j)位置越界的话,返回0;
public static int getValue(int[][][] dp, int i, int j, int step) {
if (i < 0 || i > 9 || j < 0 || j > 8) {
return 0;
}
return dp[i][j][step];
}
public static void main(String[] args) {
int x = 7;
int y = 7;
int step = 10;
System.out.println(ways(x, y, step));
System.out.println(waysdp(x, y, step));
}
}
package class12;
public class Code08_MinPathSum {
public static int minPathSum1(int[][] m) {
if (m == null || m.length == 0 || m[0] == null || m[0].length == 0) {
return 0;
}
int row = m.length;
int col = m[0].length;
int[][] dp = new int[row][col];
dp[0][0] = m[0][0];
for (int i = 1; i < row; i++) {
dp[i][0] = dp[i - 1][0] + m[i][0];
}
for (int j = 1; j < col; j++) {
dp[0][j] = dp[0][j - 1] + m[0][j];
}
for (int i = 1; i < row; i++) {
for (int j = 1; j < col; j++) {
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + m[i][j];
}
}
return dp[row - 1][col - 1];
}
public static int minPathSum2(int[][] m) {
if (m == null || m.length == 0 || m[0] == null || m[0].length == 0) {
return 0;
}
int more = Math.max(m.length, m[0].length); // �����������ϴ���Ǹ�Ϊmore
int less = Math.min(m.length, m[0].length); // ������������С���Ǹ�Ϊless
boolean rowmore = more == m.length; // �����Dz��Ǵ��ڵ�������
int[] arr = new int[less]; // ��������ij��Ƚ�Ϊ�����������е���Сֵ
arr[0] = m[0][0];
for (int i = 1; i < less; i++) {
arr[i] = arr[i - 1] + (rowmore ? m[0][i] : m[i][0]);
}
for (int i = 1; i < more; i++) {
arr[0] = arr[0] + (rowmore ? m[i][0] : m[0][i]);
for (int j = 1; j < less; j++) {
arr[j] = Math.min(arr[j - 1], arr[j])
+ (rowmore ? m[i][j] : m[j][i]);
}
}
return arr[less - 1];
}
// for test
public static int[][] generateRandomMatrix(int rowSize, int colSize) {
if (rowSize < 0 || colSize < 0) {
return null;
}
int[][] result = new int[rowSize][colSize];
for (int i = 0; i != result.length; i++) {
for (int j = 0; j != result[0].length; j++) {
result[i][j] = (int) (Math.random() * 10);
}
}
return result;
}
// for test
public static void printMatrix(int[][] matrix) {
for (int i = 0; i != matrix.length; i++) {
for (int j = 0; j != matrix[0].length; j++) {
System.out.print(matrix[i][j] + " ");
}
System.out.println();
}
}
public static void main(String[] args) {
// int[][] m = generateRandomMatrix(3, 4);
int[][] m = { { 1, 3, 5, 9 }, { 8, 1, 3, 4 }, { 5, 0, 6, 1 },
{ 8, 8, 4, 0 } };
printMatrix(m);
System.out.println(minPathSum1(m));
System.out.println(minPathSum2(m));
}
}
package class12;
/**
* 给定数组arr,arr中所有的值都为正数且不重复 每个值代表一种面值的货币,每种面值的货币可以使用任意张
* 再给定一个整数 aim,代表要找的钱数 求组成 aim 的方法数
* e.g: arr[5,10] aim=20 方法有3种:[5*4,5*2+10,10*2]
* <p>
* {@link #ways1}.暴力递归
* {@link #ways2}.记忆化搜索
* {@link #ways3}.经典动态规划
* {@link #ways4}.优化经典动态规划枚举操作
*/
public class Code09_CoinsWay {
/**
* 1. 暴力递归
* arr中都是正数且无重复值,返回组成aim的方法数
*/
public static int ways1(int[] arr, int aim) {
if (arr == null || arr.length == 0 || aim < 0) {
return 0;
}
return process1(arr, 0, aim);
}
/**
* 使用arr[index ...]中的面额凑出rest这么多钱,有多少种凑法
*
* @param index 可以使用arr的index位置之后的所有面值
* @param rest 需要凑的剩余钱数
*/
public static int process1(int[] arr, int index, int rest) {
if (index == arr.length) {// base case
return rest == 0 ? 1 : 0;// 面额到头了,钱凑齐了则代表一种有效凑法
}
int ways = 0;
// index位置的面额,可选[0~x]次,只要rest还没凑够
for (int chooseTimes = 0; arr[index] * chooseTimes <= rest; chooseTimes++) {
ways += process1(arr, index + 1, rest - arr[index] * chooseTimes);
}
return ways;
}
/**
* 2.暴力递归 改 记忆化搜索
*/
public static int ways2(int[] arr, int aim) {
if (arr == null || arr.length == 0 || aim < 0) {
return 0;
}
// 缓存表 index范围[0~arr.length] aim可能值范围[0~aim]
// 缓存表也可用HashMap替换,key可以是"index_rest"组合,可节省二维数组中有些用不到的空间,可省去二维数组初始化-1的步骤
int[][] dp = new int[arr.length + 1][aim + 1];
for (int i = 0; i < arr.length + 1; i++) {
for (int j = 0; j < aim + 1; j++) {
dp[i][j] = -1;
}
}
return process2(arr, 0, aim, dp);
}
public static int process2(int[] arr, int index, int rest, int[][] dp) {
if (dp[index][rest] != -1) {
return dp[index][rest];
}
if (index == arr.length) {
dp[index][rest] = rest == 0 ? 1 : 0;
return dp[index][rest];// 面额到头了,钱凑齐了则代表一种有效凑法
}
int ways = 0;
// index位置的面额,可选[0~x]次,只要rest还没凑够
for (int chooseTimes = 0; arr[index] * chooseTimes <= rest; chooseTimes++) {
dp[index + 1][rest - arr[index] * chooseTimes] =
process2(arr, index + 1, rest - arr[index] * chooseTimes, dp);
ways += dp[index + 1][rest - arr[index] * chooseTimes];
}
return ways;
}
/**
* 3.暴力递归 改 经典动态规划
*/
public static int ways3(int[] arr, int aim) {
if (arr == null || arr.length == 0 || aim < 0) {
return 0;
}
int N = arr.length;
// 缓存表 index范围[0~arr.length] aim可能值范围[0~aim]
int[][] dp = new int[N + 1][aim + 1];
dp[N][0] = 1;// base case
// 二维表 从下往上 从左往右 填充
for (int index = N - 1; index >= 0; index--) {
for (int rest = 0; rest <= aim; rest++) {
dp[index][rest] = 0;
// index位置的面额,可选[0~x]次,只要rest还没凑够
for (int chooseTimes = 0; arr[index] * chooseTimes <= rest; chooseTimes++) {
dp[index][rest] += dp[index + 1][rest - arr[index] * chooseTimes];
}
}
}
return dp[0][aim];
}
/**
* 4. 优化经典动态规划的枚举操作
*/
public static int ways4(int[] arr, int aim) {
if (arr == null || arr.length == 0 || aim < 0) {
return 0;
}
int N = arr.length;
// 缓存表 index范围[0~arr.length] aim可能值范围[0~aim]
int[][] dp = new int[N + 1][aim + 1];
dp[N][0] = 1;// base case
// 二维表 从下往上 从左往右 填充
for (int index = N - 1; index >= 0; index--) {
for (int rest = 0; rest <= aim; rest++) {
/**
* 通过实际距离分析得到如下规律:
* 当前位置 = 下面的位置 + 向左边跳当前面额即arr[index]的位置
* 可运用该规律替换存在重复计算的枚举过程
*/
if (rest - arr[index] >= 0) {// 向左边跳的位置还不越界
dp[index][rest] = dp[index + 1][rest] + dp[index][rest - arr[index]];
} else {// 向左边跳无可跳,当前位置 = 下面的位置
dp[index][rest] = dp[index + 1][rest];
}
}
}
return dp[0][aim];
}
public static void main(String[] args) {
int[] arr = {5, 2, 3, 1};
int sum = 350;
System.out.println(ways1(arr, sum));
System.out.println(ways2(arr, sum));
System.out.println(ways3(arr, sum));
System.out.println(ways4(arr, sum));
}
}