0. 代码
栈 Deque<Integer> stack = new LinkedList<>(); stack.push(); stack.pop()
队列 Queue<Integer> queue = new LinkedList<>(); queue.add(3); queue.remove()
双端队列 Deque<Integer> deque = new LinkedList<>(); 队列相关 add,remove,栈相关:addFirst,removeLast
List<Integer> list = new ArrayList<>(); //添加元素到list的指定位置
list.add(0,5);
小根堆(默认) PriorityQueue<Integer> pq = new PriorityQueue<>(); pq.add(1); pq.remove()
大根堆 PriorityQueue<Integer> pq = new PriorityQueue<>((v1,v2)-> v2-v1);
HashSet<Integer> hashSet = new HashSet<>(); //hash表
TreeSet<Integer> treeSet = new TreeSet<>(); //有序表
comparator方法 (a,b)->{return a-b;} //升序
若只有一行可以改为 (a,b)-> a-b //升序
二进制String转十进制
Integer.parseInt("1111",2)
十进制转二进制
Integer.toBinaryString(7)
集合翻转
Collections.reverse(result);
位运算
判断是否是 2 的幂 :A & (A - 1) == 0
最低位的 1 变为 0 :n &= (n - 1)
最低位的 1:A & (-A)
list转数组
list.toArray();
二维
list.toArray(new int[0][]);
Math.ceil(3.5);//天花板
Math.floor(3.5);//地板
1. 队列
电话号码的字母组合
思路:先入队列,再逐个出队列,出队列的元素和新的字符相加再进入队列,直到遍历结束
public static List<String> letterCombinations(String digits) {
Map<Integer,String[]> map = new HashMap<>();
map.put(2,new String[]{"a","b","c"});
map.put(3,new String[]{"d","e","f"});
map.put(4,new String[]{"g","h","i"});
map.put(5,new String[]{"j","k","l"});
map.put(6,new String[]{"m","n","o"});
map.put(7,new String[]{"p","q","r","s"});
map.put(8,new String[]{"t","u","v"});
map.put(9,new String[]{"w","x","y","z"});
Queue<String> queue =new LinkedList<>();
for(int i = 0;i<digits.length();i++){
if(queue.isEmpty()){
String[] tmp = map.get(Integer.valueOf(String.valueOf(digits.charAt(i))));
for(int j = 0;j<tmp.length;j++){
queue.add(tmp[j]);
}
}else {
int m = queue.size();
String[] tmp = map.get(Integer.valueOf(String.valueOf(digits.charAt(i))));
for(int j = 0;j<m;j++){
String a = queue.poll();
for(int k = 0;k<tmp.length;k++){
queue.add(a+tmp[k]);
}
}
}
}
List<String> result = Lists.newArrayList();
while (!queue.isEmpty()){
result.add(queue.poll());
}
return result;
}
层次遍历
public List<List<Integer>> levelOrder(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
List<List<Integer>> result = new ArrayList<>();
if(root == null){
return result;
}
queue.add(root);
while (!queue.isEmpty()){
int num = queue.size();
List<Integer> tmp = new ArrayList<>();
for(int i = 0;i<num;i++){
TreeNode node = queue.remove();
if(null != node.left)
queue.add(node.left);
if(null != node.right)
queue.add(node.right);
tmp.add(node.val);
}
result.add(tmp);
}
return result;
}
2. 栈
括号匹配
public static boolean isValid(String s) {
Stack<String> stack = new Stack<>();
for (int i = 0;i<s.length();i++){
String tmp = String.valueOf(s.charAt(i));
if(tmp.equals("(") || tmp.equals("{") || tmp.equals("[")){
stack.push(tmp);
}else if(tmp.equals(")")){
if(stack.isEmpty() || !stack.pop().equals("(")){
return false;
}
}else if(tmp.equals("}")){
if(stack.isEmpty() || !stack.pop().equals("{")){
return false;
}
}else if(tmp.equals("]")){
if(stack.isEmpty() || !stack.pop().equals("[")){
return false;
}
}
}
if(stack.isEmpty()){
return true;
}
return false;
}
最长匹配括号
思路,栈中存储括号的下标,若为(,则入栈,否则出栈,判断栈是否为空,若为空,说明没有以其匹配的左括号,放入下标(最后一个没有被匹配右括号的下标);否则,和栈顶元素计算最大长度;
public static int maxl(String s){
Stack<Integer> stack = new Stack<>();
stack.push(-1);
int max = 0;
for (int i = 0;i<s.length();i++){
if(s.charAt(i) == '('){
stack.push(i);
}else {
stack.pop();
if(stack.isEmpty()){
stack.push(i);
}else {
max = Math.max(max,i-stack.peek());
}
}
}
return max;
}
linux路径
单调栈
数组左右两遍比他大的(接雨水)
//3 2 1 4 1 5
//i入栈时确定 i左边比他大的(栈顶)
//i出栈 确定 i右边比他大的,要加入的
public static int[][] getMax(int arr[]) {
int n = arr.length;
int[][] result = new int[n][2];
Stack<Integer> stack = new Stack<>();
stack.push(0);
result[0][0] = -1;
result[n-1][1] = -1;
for(int i = 1;i< n;i++){
if(!stack.isEmpty() && arr[stack.peek()]<=arr[i]){//出战
while (!stack.isEmpty() && arr[stack.peek()]<=arr[i]){
result[stack.pop()][1] = arr[i];
}
result[i][0] = stack.isEmpty() ? -1 : arr[stack.peek()] ;
stack.push(i);
} else { //入栈
result[i][0] = arr[stack.peek()];
stack.push(i);
}
}
return result;
}
int arr3[] = new int[]{3,2,1,4,1,5};
int arr4[][] = getMax(arr3);
for (int i = 0; i < length; i++) {
while (!stack.isEmpty() && T[i] > T[stack.peek()]) {
int pre = stack.pop();
result[pre] = i - pre;
}
stack.add(i);
}
每日温度(减栈)
栈中元素为下标和温度值,温度减少才入栈
去除重复字符(增栈,考虑剩余)
去除重复字符,字典序最小
求最大矩形(84,写,增栈)
3. 队列
往往类似于“请找到满足xx的最x的区间(子串、子数组)的xx”这类问题都可以使用该方法进行解决。
用滑动窗口的双端队列解决
滑动窗口的最大值
public static int[] maxValue(int arr[],int k){
Deque<Integer> queue = new LinkedList<Integer>();
queue.addFirst(0);
int result[] = new int[arr.length-k+1];
for(int i = 1;i<arr.length;i++){
if(arr[i] >= arr[queue.getLast()]) {
while (!queue.isEmpty() && arr[i] >= arr[queue.getLast()]) {
queue.removeLast();
}
}
while(!queue.isEmpty() && i-queue.getFirst() >= k){
queue.removeFirst();
}
queue.addLast(i);
if(i>=k-1){
result[i-k+1] = arr[queue.getFirst()];
}
}
return result;
}
int arr[] = new int[]{1,3,2,2,1,5};
int arr2[] = maxValue(arr,3);
3. 链表
链表逆序
栈
public static ListNode swapAll(ListNode head) {
Stack<ListNode> stack = new Stack<>();
ListNode result = new ListNode(1,null);
result.next = head;
ListNode tmp = head;
while (tmp != null){
stack.push(tmp);
tmp = tmp.next;
}
tmp = result;
while (!stack.isEmpty()){
tmp.next = stack.pop();
tmp = tmp.next;
}
tmp.next = null;
return result.next;
}
两两交换链表的节点
申请一个节点指向头节点,一个临时节点做交换
public static ListNode swapPairs(ListNode head) {
ListNode dummyHead = new ListNode(1,null);
dummyHead.next = head;
ListNode temp = dummyHead;
while (temp.next != null && temp.next.next != null) {
ListNode node1 = temp.next;
ListNode node2 = temp.next.next;
temp.next = node2;
node1.next = node2.next;
node2.next = node1;
temp = node1;
}
return dummyHead.next;
}
旋转链表
先形成环,再在指定位置断开
4. 数组
旋转图像(数组旋转90度)
先对角线旋转,再水平旋转
螺旋数组
public List<Integer> spiralOrder(int[][] matrix) {
List<Integer> res = new ArrayList<>();
int count = 0, row = matrix.length, column = matrix[0].length;
int total = row * column;
int up = 0, down = row - 1, left = 0, right = column - 1;
while(count < total){
for(int i = left; i <= right && count < total; i++){
res.add(matrix[up][i]);
count++;
}
up++;
for(int i = up; i <= down && count < total; i++){
res.add(matrix[i][right]);
count++;
}
right--;
for(int i = right; i >= left && count < total; i--){
res.add(matrix[down][i]);
count++;
}
down--;
for(int i = down; i >= up && count < total; i--){
res.add(matrix[i][left]);
count++;
}
left++;
}
return res;
}
合并区间
先升序排列,再合并
public static int[][] merge(int[][] intervals) {
List<int[]> res = new ArrayList<>();
Arrays.sort(intervals,(a,b)->a[0]-b[0]);
int[][] result= new int[][]{};
int left = -1,right = -1;
for(int i = 0;i<intervals.length;i++){
if(left == -1 && right == -1){
left = intervals[i][0];
right = intervals[i][1];
}
if(intervals[i][0]<=right){
right = Math.max(right,intervals[i][1]);
}else {
res.add(new int[]{left,right});
left = intervals[i][0];
right = intervals[i][1];
}
}
res.add(new int[]{left,right});
return res.toArray(new int[0][]);
}
5. 二维数组
矩阵置0
描述,O(1)空间复杂度,若某一行或某一列有0,则这一行及这一列都置为0
思路,用第一行和第一列作为这一行或者这一列是否有0的标志位,用两个变量记录第一行及第一列本身是否有0
6.有序表
有序表(log(n)treeMap,很多数据结构都能实现,红黑树(约束目的就是长的路径不能是短的2倍),avl树(左右高度差不超过1),sizeBalance树(外甥的节点数量不能超过叔叔),前三个都通过左右旋实现,区别是判断平衡性的规则不一样,跳表等)
大楼轮廓
》大楼的轮廓问题:
1:确定每个点的最大高度,即可构成结果;
2: 确定每个点的最大高度treemap2(坐标,高度),需要遍历点的时候求得当前有哪些高度及高度出现的次数,treemap的getLastKey,treemap1(最大高度,高度的次数)
3:将每个大楼变为两个边(上还是下)的对象,形成的序列排序;即可构成treemap1
public static class buildC implements Comparator<Build>{
@Override
public int compare(Build o1, Build o2) {
if(o1.pos == o2.pos){
return o1.up ? -1 : 1;
}else {
return o1.pos - o2.pos;
}
}
}
public static class Build{
public int pos;
public int height;
public boolean up;
public Build(int pos ,int height,boolean up){
this.pos = pos;
this.height = height;
this.up = up;
}
}
public static List<List<Integer>> get(int builds[][]){
List<List<Integer>> results = new ArrayList<List<Integer>>();
Build[] buildBusi = new Build[builds.length*2];
for (int i = 0;i<builds.length;i++) {
buildBusi[2*i] = new Build(builds[i][0],builds[i][2],true);
buildBusi[2*i+1] = new Build(builds[i][1],builds[i][2],false);
}
Arrays.sort(buildBusi,new buildC());
TreeMap<Integer,Integer> levelmap = new TreeMap<>();//高度,出现次数
TreeMap<Integer,Integer> maxmap = new TreeMap<>();//位置,最高高度
for(int i = 0;i<buildBusi.length;i++){
Build tmp = buildBusi[i];
if(tmp.up){//先更新高度次数信息
if(levelmap.containsKey(tmp.height)){
levelmap.put(tmp.height,levelmap.get(tmp.height)+1);
}else {
levelmap.put(tmp.height,1);
}
}else {
levelmap.put(tmp.height,levelmap.get(tmp.height)-1);
if(levelmap.get(tmp.height) == 0){
levelmap.remove(tmp.height);
}
}
if(levelmap.isEmpty()){
maxmap.put(tmp.pos,0);
}else {
maxmap.put(tmp.pos,levelmap.lastKey());
}
}
int preH = 0;
int preindex = 0;
for (int pos: maxmap.keySet()) {
int curH = maxmap.get(pos);
if(curH != preH){
if(preH == 0){
preH = curH;
preindex = pos;
continue;
}
List<Integer > result = new ArrayList<>();
result.add(preindex);
result.add(pos);
result.add(preH);
results.add(result);
preindex = pos;
preH = curH;
}
}
return results;
}
int builds[][] = new int[][]{{1,3,3},{2,4,4},{5,6,1}};
System.out.println(get(builds));
找工作
public static int[] getMaxPay(Job[] jobs,int [] abli){
int[] result = new int[abli.length];
Arrays.sort(jobs,new JobComparator());
TreeMap<Integer,Integer> best = new TreeMap<>();
best.put(jobs[0].limit,jobs[0].pay);
Job pre = jobs[0];
for(int i = 1;i<jobs.length;i++){
if(jobs[i].limit== pre.limit || jobs[i].pay <= pre.pay){
continue;
}
pre = jobs[i];
best.put(pre.limit,pre.pay);
}
for(int i = 0;i<abli.length;i++){
result[i] = best.floorKey(abli[i]) != null ? best.get( best.floorKey(abli[i])) : 0;
}
return result;
}
static class Job{
public int limit;
public int pay;
public Job(int limit,int pay){
this.limit = limit;
this.pay = pay;
}
}
public static class JobComparator implements Comparator<Job>{
public int compare(Job o1,Job o2){
return o1.limit == o2.limit ? o2.pay - o1.pay : o1.limit - o2.limit;
}
}```
Job [] jobs = new Job[]{new Job(1,2),new Job(3,6),new Job(1,5),new Job(2,4)};
int b[] = getMaxPay(jobs,new int[]{1,2,3,5});
System.out.println();
# 4. 回溯
深度优先,递归
## 模板
public static void dfs(){
//终止条件
for(){
//if()剪枝
//操作
dfs();//递归调用
//逆操作,回溯
}
}
## 全排列
要解决重复问题,我们只要设定一个规则,保证在填第
idx
idx 个数的时候重复数字只会被填入一次即可。而在本题解中,我们选择对原数组排序,保证相同的数字都相邻,然后每次填入的数一定是这个数所在重复数集合中「从左往右第一个未被填过的数字」,即如下的判断条件:
```java
if(i>0 && nums[i] == nums[i-1] && used[i-1] == 0){
continue;
}
全排列后的第k个
方法1:既然所有的全排列是从小到大,那么可以对每一位的数字进行定位。例如,假如给定题目为(5,46)。固定第一位数,后面4位的全排列数为24,math.ceil(46/24)=2,即处于第1位数的第二个循环中,即第一位数为2.同理,对于固定第二位数,math.ceil((46-24)/6)=4,即处于第2位数的第四个循环中(此时列表移除了已确定的数字2),即第2位数为5.同理,可依次推理出最后结果为“25341”.总复杂度为O(n).
方法二:全排列+剪枝
n皇后,8皇后
原理同上,只是判断重复时,斜着的和竖着的通过set判断
括号生成
/**
* @param curStr 当前递归得到的结果
* @param left 左括号已经用了几个
* @param right 右括号已经用了几个
* @param n 左括号、右括号一共得用几个
* @param res 结果集
*/
private void dfs(String curStr, int left, int right, int n, List<String> res) {
if (left == n && right == n) {
res.add(curStr);
return;
}
// 剪枝
if (left < right) {
return;
}
if (left < n) {
dfs(curStr + "(", left + 1, right, n, res);
}
if (right < n) {
dfs(curStr + ")", left, right + 1, n, res);
}
}
}
ip生成,电话号码
5. 递归(树的深度优先)
先序中虚得到后续
public static String getPost(String pre,String in){
return getPost(pre,in,0,pre.length() -1,0,pre.length()-1);
}
public static String getPost(String pre,String in,int preL,int preR,int inL,int inR){
if(preL == preR){
return String.valueOf(pre.charAt(preL));
}
if(preL > preR){
return "";
}
char chara = pre.charAt(preL);
int inIndex = in.indexOf(chara);
int leftLength = inIndex - inL;
int rightLength = preR - preL - leftLength;
String left = getPost(pre,in,preL+1,preL+1+leftLength-1,inL,inL+leftLength-1);
String right = getPost(pre,in,preR-rightLength+1, preR,inR-rightLength+1 , inR);
return left+right+chara;
}
public static String getPost(String pre,String in){
return getPost(pre,in,0,pre.length() -1,0,pre.length()-1);
}
public static String getPost(String pre,String in,int preL,int preR,int inL,int inR){
if(preL == preR){
return String.valueOf(pre.charAt(preL));
}
if(preL > preR){
return "";
}
char chara = pre.charAt(preL);
int inIndex = in.indexOf(chara);
int leftLength = inIndex - inL;
int rightLength = preR - preL - leftLength;
String left = getPost(pre,in,preL+1,preL+1+leftLength-1,inL,inL+leftLength-1);
String right = getPost(pre,in,preR-rightLength+1, preR,inR-rightLength+1 , inR);
return left+right+chara;
}
数学公式的结果
包含括号的需要递归调用不包含括号的
//不包含括号的
System.out.println(result("1+2*12+4"));
public static int result(String str){
if(null == str){
throw new RuntimeException("");
}
Stack<String> stack = new Stack<>();
String input = "";
for(int i = 0;i<str.length();i++){
if(str.charAt(i) == '+' || str.charAt(i) == '-' || str.charAt(i) == '*' || str.charAt(i) == '/'){
if(!stack.isEmpty() && (stack.peek().equals("*") || stack.peek().equals("/"))){
String fuhao = stack.pop();
stack.push(String.valueOf(comp(stack.pop(),input,fuhao)));
}else {
stack.push(input);
}
stack.push(String.valueOf(str.charAt(i)));
input = "";
}else {
input += str.charAt(i);
}
}
stack.push(input);
String result = stack.pop();
while (!stack.isEmpty()){
String fuhao = stack.pop();
result = String.valueOf(comp(stack.pop(),result,fuhao));
}
return Integer.valueOf(result);
}
public static int comp(String a,String b,String fuhao){
if(fuhao.equals("*")){
return Integer.valueOf(a)*Integer.valueOf(b);
}else if(fuhao.equals("/")){
return Integer.valueOf(a)/Integer.valueOf(b);
}else if(fuhao.equals("+")){
return Integer.valueOf(a)+Integer.valueOf(b);
}else if(fuhao.equals("-")){
return Integer.valueOf(a)/Integer.valueOf(b);
}
return -1;
}
求完全二叉树的节点个数
先求深度,然后分为,右侧的最左节点是否深度一样 ?(左子树满二叉+1+递归调用右子树):(右慢二叉+1+递归调用左子树)
public static int get(Node node){
if(null == node){
return 0;
}
if(node.left == null && node.right == null){
return 1;
}
int deep = 1;
Node tmp = node;
while (tmp.left != null){
deep++;
tmp = tmp.left;
}
int rightDeep = 1;
tmp = node.right;
while (tmp != null){
rightDeep++;
tmp = tmp.left;
}
if(deep == rightDeep){
return 1+get(node.right)+(int)Math.pow(2,deep-1) -1;
} else {
return 1+get(node.left)+(int)Math.pow(2,deep-2) -1;
}
}
Node node7 = new Node(7,null,null);
Node node6 = new Node(6,null,null);
Node node5 = new Node(5,null,null);
Node node4 = new Node(4,null,null);
Node node3 = new Node(3,node6,node7);
Node node2 = new Node(2,node4,node5);
Node node1 = new Node(1,node2,node3);
System.out.println(get(node1));
根据先序中序得到后序
public static void set(int [] pre,int [] mid,int [] pos,int preL,int preR,int midL,int midR,int posL,int posR){
if(pre == null || mid == null){
return ;
}
if(preL > preR){
return;
}
if(pre[preL] == mid[midL]){
pos[posL] = pre[preL];
}
pos[posR] = pre[preL];
int inPos = midL;//3
for(int i =midL;i<=midR;i++){
if(mid[i] == pre[preL]){
inPos = i;
break;
}
}
// inPos - midL 左边的长度 3
// midR - inPos 右边的长度 2
//midL+midR+1整体长度
set(pre,mid,pos,preL+1,preL+ inPos-midL,midL,inPos-1,posL,posL+inPos-midL-1);
set(pre,mid,pos,preL +inPos - midL +1,preR,inPos+1,midR,posL-midL+inPos,posR-1);
// Node head = pre;
return ;
}
//124536 425163 452631
int pre[] = new int[]{1,2,4,5,3,6};
int mid[] = new int[]{4,2,5,1,6,3};
int pos[] = new int[6];
set(pre,mid,pos,0,pre.length-1,0,pre.length-1,0,pre.length-1);
System.out.println();
二叉树最大路径(根到叶子)和
public static int maxRoute(Node head){
if(head == null){
return 0;
}
if(head.left == null && head.right == null){
return head.value;
}
int left = maxRoute(head.left);
int right = maxRoute(head.right);
return Math.max(left,right) + head.value;
}
数字转字母的方式数
//考虑str的第k位,前面形成的数量是times
public static int calcu(String str,int k,int times){
if(k >= str.length() - 1){
return times;
}
if(str.charAt(k) == '0'){
return 0;
}
if(str.charAt(k) - '1' >= 2){//3等等
return calcu(str,k+1,times);
}
else if(str.charAt(k) - '1' == 1){//为2
if(k+1 <=str.length() && str.charAt(k+1) - '1' <= 5){
return calcu(str,k+1,times) + calcu(str,k+2,times);
}else {
return calcu(str,k+1,times);
}
} else {//为1
return calcu(str,k+1,times) + calcu(str,k+2,times);
}
}
public static int calcu2(String str,int index){
if(index >= str.length() - 1){
return 1;
}
if(str.charAt(index) == '0'){
return 0;
}
int res = calcu2(str,index+1);
if(index + 1 == str.length()){
return res;
}
if((str.charAt(index)-'0')*10 + (str.charAt(index+1)-'0') <=26){
res += calcu2(str,index+2);
}
return res;
}
public static int calcu3(String str){
int deep[] = new int[str.length()+1];
deep[str.length()] = 1;
deep[str.length()-1] = str.charAt(str.length()-1) == '0' ? 0 : 1;
for(int i = str.length() - 2;i>=0;i--){
if(str.charAt(i) == '0'){
deep[i] = 0;
} else {
deep[i] = deep[i+1];
if((str.charAt(i)-'0')*10 + (str.charAt(i+1)-'0') <=26){
deep[i] = deep[i] + deep[i+2];
}
}
}
return deep[0];
}
System.out.println(calcu("127221",0,1));
System.out.println(calcu2("127221",0));
逆序栈
//逆序栈
public static void reverseStack(Stack<Integer> stack){
if(stack.isEmpty()){
return ;
}
int i = f(stack);
reverseStack(stack);
stack.push(i);
}
//删除并返回栈底元素,其他不变
public static int f(Stack<Integer> stack){
int result = stack.pop();
if(stack.isEmpty()){
return result;
}
int last = f(stack);
stack.push(result);
return last;
}
Stack<Integer> stack2 = new Stack<Integer>();
stack2.push(1);
stack2.push(2);
stack2.push(3);
stack2.push(4);
stack2.push(5);
reverseStack(stack2);
取数
/**
* 取数
* @param arr
* @param start
* @param end
* @return
*/
public static int first(int arr[],int start,int end){
if(start >= end){
return arr[start];
}
return Math.max(arr[start] +second(arr,start+1,end), arr[end] +second(arr,start,end-1));
}
public static int second(int arr[],int start,int end){
if(start >= end){
return 0;
}
return Math.min(first(arr,start+1,end),first(arr,start,end-1));
}
int arr[] = new int[]{8,3,2,9,7,};
System.out.println(first(arr,0,arr.length-1));
字符串全排列
public static void printAll(String str){
char[] chs = str.toCharArray();
List<String> results = new ArrayList<>();
process(chs,0,results);
System.out.println();
}
/**
* 123
* 到str中i位置的要处理,i之前的数据固定
* @param str
* @param i
*/
public static void process(char[] str,int i,List<String> results){
if(i == str.length){
results.add(String.valueOf(str));
return;
}
boolean visited[] = new boolean[26];
for(int j = i;j<str.length;j++){
if(!visited[str[j]- 'a']){
visited[str[j] - 'a'] = true;
swap(str,i,j);
process(str,i+1,results);
swap(str,i,j);}
}
}
public static void swap(char[] str,int i,int j){
char tmp = str[i];
str[i] = str[j];
str[j] = tmp;
}
printAll("abca");
字符串的子序列
public static void print(String str,int k,String route){
if(k == str.length()){
System.out.println(route);
} else {
String have = route + str.charAt(k);
print(str,k+1,have);
String nohave = route ;
print(str,k+1,nohave);
}
}
print("123",0,"");
汉诺塔步骤打印
n层汉诺塔问题:大的不能压小的,打印步骤; (2的n次方)-1
public static void hannuota(int n){
move(n,"左","中","右");
}
public static void move(int n,String from ,String other,String to){
if(n == 1){
System.out.println("move 1 from" + from +"to" + to);
}else {
move(n-1,from,to,other);
System.out.println("move" +n+ "from" + from +"to" + to);
move(n-1,other,from,to);
}
}
hannuota(3);
验证二插搜索树
public boolean isValidBST(TreeNode root) {
return isValidBST(root,Long.MIN_VALUE,Long.MAX_VALUE);
}
public boolean isValidBST(TreeNode root,long low,long high){
if(root == null)
return true;
if(root.val <=low || root.val>= high){
return false;
}
return isValidBST(root.left,low,root.val) && isValidBST(root.right,root.val,high);
}
相同的树
有序数组转为二叉搜索
public static TreeNode sortedArrayToBST(int[] nums) {
TreeNode result = sortedArrayToBST(nums,0, nums.length-1);
return result;
}
public static TreeNode sortedArrayToBST(int[] nums,int left,int right){
if(left == right){
return new TreeNode(nums[left]);
}else if(left > right){
return null;
}
int mid = (right+left)/2;
return new TreeNode(nums[mid],sortedArrayToBST(nums,left,mid-1),sortedArrayToBST(nums,mid+1,right));
}
二叉树最大路径和
1:求最大贡献值(节点值+左右子树最大贡献值)
2:求最大路径(节点值+左右子树最大贡献值(如果值为正的话))
public static int maxPathSum(TreeNode root) {
List<Integer> max = new ArrayList<>();
max.add(Integer.MIN_VALUE);
maxPathSum(root,max);
return max.get(0);
}
public static int maxPathSum(TreeNode root,List<Integer> max){
if(root == null){
return 0;
}
if(root.left == null && root.right == null){
max.set(0,Math.max(max.get(0),root.val));
return root.val;
}
int left = maxPathSum(root.left,max);
int right = maxPathSum(root.right,max);
int tmp = root.val;
if(left >0){
tmp += left;
}
if(right>0){
tmp+=right;
}
max.set(0,Math.max(max.get(0),tmp));
return root.val+Math.max(left,right);
}
6. 树
morris遍历:若没有左节点,到右节点;若有左节点,左节点的最右.right == cur(遍历过) ? 最右.right = null然后遍历右节点 :最右.right指向cur然后指向左节点
二叉树中最大搜索二叉树的节点个数
/**
* //返回最大搜索二叉树的节点数量及当前节点值,是否以当前节点为头
*/
public static Info max(Node node){
if(null == node){
return null;
}
Info result = new Info();
result.value = node.value;
result.count = 1;
if(null == node.left && null == node.right){
result.ifThis = true;
return result;
}
Info left = max(node.left);
if(null != left){
if(left.ifThis && left.value < node.value){
result.ifThis = true;
result.count += left.count;
} else {
result.count = left.count > result.count ? left.count : result.count;
}
}else {
result.ifThis = true;
}
Info right = max(node.right);
if(null != right){
if(right.ifThis && right.value > node.value){
if(result.ifThis){
result.count += right.count;
} else {
if(right.value + 1 > result.count){
result.ifThis = true;
result.count = right.count + 1;
}
}
}else {
if(right.count > result.count){
result.ifThis = false;
result.count = right.count;
}
}
}
return result;
}
public static class Info{
public int count; //数量
public int value; //节点值
public boolean ifThis = false; //是否以此节点为头
}
// /** 2
// 3 2
// 1 4 6 8
// 2
Node node2 = new Node(2,null,null);
Node node1 = new Node(1,null,node2);
Node node4 = new Node(4,null,null);
Node node3 = new Node(3,node1,node4);
Node node6 = new Node(6,null,null);
Node node8 = new Node(8,null,null);
Node node7 = new Node(2,node6,node8);
Node node5 = new Node(2,node3,node7);
Info info = max(node5);
搜索二叉树转为有序的双向链表
//返回以node为头的搜索二叉树的头尾节点
public static Result change(Node node){
if(null == node){
return null;
}
Result result = new Result();
ListNode2 mid = new ListNode2(node.value);
Result left = change(node.left);
if(null != left){
result.small = left.small;
left.large.next = mid;
mid.pre = left.large;
}else {
result.small = mid;
}
Result right = change(node.right);
if(null != right){
result.large = right.large;
right.small.pre = mid;
mid.next = right.small;
}else {
result.large = mid;
}
return result;
}
public static class Result{
ListNode2 small;
ListNode2 large;
}
/** 5
3 7
1 4 6 8
2
* @param args
*/
Node node2 = new Node(2,null,null);
Node node1 = new Node(1,null,node2);
Node node4 = new Node(4,null,null);
Node node3 = new Node(3,node1,node4);
Node node6 = new Node(6,null,null);
Node node8 = new Node(8,null,null);
Node node7 = new Node(7,node6,node8);
Node node5 = new Node(5,node3,node7);
Result result = change(node5);
最大快乐值
// 最大快乐值
// 当前go + 下一个not, 当前not + 下一级 max(go, not)
//返回 当前go和not 的最大happy值
public static Info maxHappy(Node2 node){
if(node == null){
return new Info(0,0);
}
if(null == node.nexts || node.nexts.size() == 0){
return new Info(node.happy, node.not);
}
int curH = node.happy;
int curNot = node.not;
for (Node2 tmp:node.nexts){
Info info = maxHappy(tmp);
curH += info.not;
curNot = curNot + Math.max(info.not ,info.go);
}
return new Info(curH,curNot);
}
static class Info{
public int go;
public int not;
public Info(int go,int not){
this.go = go;
this.not = not;
}
}
Node2 node2 = new Node2(100,2);
Node2 node3 = new Node2(1,2);
Node2 node4 = new Node2(10,2);
node2.add(node3);
node2.add(node4);
Node2 node5 = new Node2(9,2);
Node2 node6 = new Node2(5,2);
Node2 node7 = new Node2(2,2);
node3.add(node5);
node3.add(node6);
node3.add(node7);
Info result = maxHappy(node2);
二叉树最大距离
//若考虑头 ,左高 + 右高 +1
//若不考虑头 ,max(左,右)
public static Info maxLength(Node head){
if(null == head){
return new Info(0,0);
}
Info left = maxLength(head.left);
Info right = maxLength(head.right);
int leftHeight = left.height;
int leftValue = left.value;
int rightHeight = right.height ;
int rightV = right.value;
int v1 = leftHeight+rightHeight + 1;//考虑头
int v2 = Math.max(v1,Math.max(leftValue,rightV)); //value
int height = Math.max(leftHeight,rightHeight) + 1; //高度
return new Info(height,v2) ;
}
static class Info{
public int height;
public int value;
Info(int height,int value){
this.height = height;
this.value = value;
}
}
Node node7 =new Node(7,null,null);
Node node6 =new Node(6,null,null);
Node node5 =new Node(5,null,null);
Node node4 =new Node(4,null,null);
Node node3 =new Node(3,node6,node7);
Node node2 =new Node(2,node4,node5);
Node node1 =new Node(1,node2,node3);
// morrisPre(node1);
// morrisIn(node1);
// morrisafter(node1);
Info info = maxLength(node1);```
## 深度用递归
时间复杂度o(n*n!)
```java
public static void dfs(){
//终止条件
//
//操作
dfs();//递归调用
//逆操作,回溯
}
广度用队列
中序遍历
递归
public static void recoverTree(TreeNode root) {
List<TreeNode> res = new ArrayList<>();
recoverTree( root,res);
}
private static void recoverTree(TreeNode root, List<TreeNode> res) {
if(root == null){
return;
}
recoverTree(root.left,res);
res.add(root);
recoverTree(root.right,res);
}
恢复二插搜索树
用中序遍历(递增)找到错误位置的数据的下标,然后交换
public static void recoverTree(TreeNode root) {
List<TreeNode> res = new ArrayList<>();
recoverTree( root,res);
TreeNode a = null,b=null;
for(int i = 1;i<res.size();i++){
if(res.get(i).val<res.get(i-1).val){
a = res.get(i);
if(b == null){
b = res.get(i-1);
}
}
}
}
private static void recoverTree(TreeNode root, List<TreeNode> res) {
if(root == null){
return;
}
recoverTree(root.left,res);
res.add(root);
recoverTree(root.right,res);
}
常数级别的可以用莫里斯遍历
二叉树展开为链表
先中序遍历,再展开
public static void flatten(TreeNode root) {
if(null == root){
return;
}
List<TreeNode> list = new ArrayList<>();
flatten(root,list);
TreeNode result = root;
for(int i = 0;i<list.size();i++){
TreeNode tmp = list.get(i);
result.left = null;
result.right = tmp;
result = result.right;
}
result.right = null;
}
public static void flatten(TreeNode root,List<TreeNode> result){
if(root == null){
return;
}
result.add(root);
flatten(root.left,result);
flatten(root.right,result);
}
前缀树(next节点用map)
子数组最大异或和
System.out.println(max(new int[]{11,1,15,10,13,4}));
public static int max(int arr[]){
int sum = 0;
Node head = new Node();
int max = Integer.MIN_VALUE;
addNum(0,head);
for(int i = 0;i<arr.length;i++){
sum ^= arr[i];
max = Math.max(max,maxXor(head,sum));
addNum(arr[i],head);
}
return max;
}
public static void addNum(int num,Node head){
Node node = head;
for(int bit = 31;bit >=0;bit--){
int path = num>>bit & 1;
node.nexts[path] = node.nexts[path] == null ? new Node() : node.nexts[path];
node = node.nexts[path];
}
}
public static int maxXor(Node head,int num){
int result = 0;
Node node = head;
for(int bit = 31;bit >=0;bit--){
int path = (num>>bit & 1);//理论最好
int best = bit == 31 ? path : path^1;
int real = node.nexts[best] != null ? best : (best ^ 1);//实际
result |= (path^real)<<bit;
node=node.nexts[real];
}
return result;
}
public static class Node{
public Node[] nexts = new Node[2];
}
目录打印成树形结构
构造前缀树,然后深度优先打印
public static void print(Node3 head,String pre){
if(pre != ""){
System.out.println(pre+head.value);
}
pre = pre + "--";
for (String key: head.nexts.keySet()) {
print(head.nexts.get(key),pre);
}
}
public static Node3 print(String [] arrs){
Node3 head = new Node3("head",new TreeMap<>());
for (String str: arrs) {
Node3 cur = head;
String[] nodes = str.split(",");
for (String nodeValue: nodes) {
if( cur.nexts.containsKey(nodeValue)){
cur = cur.nexts.get(nodeValue);
} else {
Node3 newNode = new Node3(nodeValue,new TreeMap<>());
cur.nexts.put(nodeValue,newNode);
cur = newNode;
}
}
}
return head;
}
public class Node3 {
public String value;
public TreeMap<String,Node3> nexts;
public Node3(String value,TreeMap<String,Node3> nexts) {
this.value = value;
this.nexts = nexts;
}
public Node3(){
}
}
Node3 head = print(new String[]{"a,b,c","a,d,e","f"});
print(head,"");
7. 并查集
并查集:多链表,集合中每个元素指向上一个元素,只有一个元素时指向自己(集合快速合并,一个元素的最上面的指针指向另一个集合的顶部元素,判断两个元素是否在同一个集合)优化:沿着元素往上找,将指针指向顶部元素
========================
算法
1. 排序
一般用快排,复杂度常数项低,有空间限制用堆排序,需要稳定用归并
注:快排空间o(logn)因为递归调用
选择排序(逐渐找到最小的,严格o(n2),不稳定)
冒泡(两两比较,找到最大的放最后,严格o(n2),稳定)
插入(逐渐做到0-i有序,最差o(n2),稳定)
归并排序(先排左右两测,再归并,o(n*logn),稳定)
求小和and逆序对
快排(小于放左边,递归,不稳定,写,奇数放左边,偶数右边的题目 )
大小根堆(o(n*logn),不稳定)
堆排序过程:先构建大根堆,然后一个个删除堆顶,放到最后,结束后就排序完成
是完全二叉树,通过下标计算来比较调整树
插入数据
删除头节点
删除最大值后,最后一个数据放到头节点,然后和左右孩子较大的比较,输了交换
给定一个数组构造大根堆(下面两个方法都可以)
几乎有序的数组排序
k=6的话,构造空间大小为7的小根堆
计数排序(年龄)
基数排序(电话)
先排个位,准备多个桶,数字按照个位放入相应桶中,从桶中出来放回队列(桶中数据是队列,先入先出)
2. 逆序对个数
分两步
1:合并两个有序数组(归并排序)
2:改1,合并时,左边数组的指针移动时,计算逆序对的贡献
https://leetcode.cn/problems/merge-sorted-array/solution/he-bing-liang-ge-you-xu-shu-zu-by-leetco-rrb0/
拓扑排序
有向无环图 转成 线性的排序 就叫 拓扑排序。
课程表
用队列维持入度为0的点,然后相关的入度减1
2. 逆序
public static void test(int a[],int start,int end){
while(start < end){
int tmp = a[start];
a[start] = a[end];
a[end] = tmp;
start++;
end--;
}
}
3.1. 旋转数组(数组右移动k位)
思路:3次逆序
3.2 旋转数组的最小值
思路:二分法(二分法带等号)
区间先递增,后到最小值,再递增
public int findMin(int[] nums) {
int low = 0;
int high = nums.length - 1;
while (low <= high) {
int pivot = low + (high - low) / 2;
if (nums[pivot] < nums[high]) {
high = pivot;
} else {
low = pivot + 1;
}
}
return nums[low];
}
3.3 搜索旋转数组(二分)
3. 动态规划(可以拆分为子问题,递归改dp时,注意dp方程的参数改为i,j)
从左往右尝试
0-1背包
当前位置和前一位置有关,构造位置和剩余容量的dp
System.out.println(bag(new int[]{8,3,4,3},new int[]{9,3,4,3},3,10));
System.out.println(bag2(new int[]{8,3,4,3},new int[]{9,3,4,3},10));
public static int bag2(int w[],int v[],int W){
int deep[][] = new int[w.length][W+1];
for(int j = 0;j<=W;j++){
if(j>=w[0]){
deep[0][j] = w[0];
}
}
for (int i =1;i<w.length;i++){
for(int j = 0;j<=W;j++){
int result = deep[i-1][j];
//当前放
if(j>=w[i]){
result = Math.max(deep[i-1][j-w[i]]+v[i],result) ;
}
deep[i][j] = result;
}
}
return deep[w.length-1][W];
}
* 到0-index位置时,剩余W空间
* 返回最大价值
public static int bag(int w[],int v[],int index,int W){
if(index < 0 ){
return 0;
}
//当前不放
int result = bag(w,v,index-1,W);
//当前放
if(W>=w[index]){
result = Math.max(bag(w,v,index-1,W-w[index])+v[index],result) ;
}
return result;
}
两个字符串的最长公共子序列(子序列要考虑左上角的,dp不能是必须以什么结尾的)
定义dp【】【】,代表arr1的0-i和arr2的0-j的最长公共子串;分四种情况,是否以i结尾和以j结尾,取max
System.out.println(max("12345","1666231456"));
public static int max(String str1,String str2){
if(null == str1 || null == str2){
return 0;
}
int deep[][] = new int[str1.length()][str2.length()];
deep[0][0] = str1.charAt(0) == str2.charAt(0) ? 1 : 0;
for(int j =1;j<str2.length();j++){//第一行
if(deep[0][j-1] == 1){
deep[0][j] = 1;
continue;
}
if(str1.charAt(0) == str2.charAt(j)){
deep[0][j] = 1;
}
}
for(int i =1;i<str1.length();i++){//第一列
if(deep[i-1][0] == 1){
deep[i][0] = 1;
continue;
}
if(str2.charAt(0) == str1.charAt(i)){
deep[i][0] = 1;
}
}
for(int i = 1;i<str1.length();i++){
for(int j = 1;j<str2.length();j++){
int tmp = deep[i-1][j-1];
tmp = Math.max(tmp,deep[i][j-1]);
tmp = Math.max(tmp,deep[i-1][j]);
if(str1.charAt(i) == str2.charAt(j)){
tmp = Math.max(tmp,deep[i-1][j-1]+1);
}
deep[i][j] = tmp;
}
}
return deep[str1.length()-1][str2.length()-1];
}
两个字符串的最长公共子串
定义dp【】【】,代表arr1必须以i结尾和arr2必须以j结尾的最长公共子串,每个位置和左上角的位置有关;可以空间压缩
public static int max(String str1,String str2){
if(null == str1 || null == str2){
return 0;
}
int deep[][] = new int[str1.length()][str2.length()];
int max = 0;
for(int j =0;j<str2.length();j++){
if(str1.charAt(0) == str2.charAt(j)){
deep[0][j] = 1;
}
}
for(int i =0;i<str1.length();i++){
if(str2.charAt(0) == str1.charAt(i)){
deep[i][0] = 1;
}
}
for(int i = 1;i<str1.length();i++){
for(int j = 1;j<str2.length();j++){
if(str1.charAt(i) == str2.charAt(j)){
deep[i][j] = deep[i-1][j-1]+1;
max = Math.max(deep[i][j],max);
}
}
}
return max;
}
字符串最少多少次切割成多个回文子串
System.out.println(min("134343414"));
public static int min(String str){
char chars[] = str.toCharArray();
int len = chars.length;
int deep[] = new int[len+1];//i-后面需要最少的
deep[len-1] = 1;
deep[len] = 0;
boolean[][] isHui = isH(chars);
for(int i = len-2;i>=0;i--){
deep[i] = len-i;
for(int j = i;j<len;j++){
if(isHui[i][j]){
deep[i] = Math.min(deep[i],1+deep[j+1]);
}
}
}
return deep[0];
}
public static boolean[][] isH(char chars[]){
boolean[][] results = new boolean[chars.length][chars.length];
for(int i=chars.length-1;i>=0;i--){
for (int j = i;j<chars.length;j++){
if(i == j){
results[i][j] = true;
}else {
if(chars[i] == chars[j]){
if(j-i<3 || results[i+1][j-1]){
results[i][j] = true;
}
}
}
}
}
return results;
}
范围上尝试
(str(i,j)上尝试,可能性要考虑是否包含开头和结尾,重点在于思考可能性是否能解决问题)一般子序列有这种问题
遍历顺序:一个递减,一个递增
最长回文子串
方程:
遍历顺序:一个递减,一个递增
System.out.println(max("1234565432"));
public static int max(String str){
int len = str.length();
boolean deep[][] = new boolean[len][len];//以i开头,j结尾是不是
int max = 1;
for(int i = len-1;i>=0;i--){
for(int j = i;j<len;j++){
if(i==j){
deep[i][j] = true;
}else if(str.charAt(i) == str.charAt(j)){
if(j-i<3 || deep[i+1][j-1]){
deep[i][j] = true;
max = Math.max(max,j-i+1);
}
}
}
}
return max;
}
字符串str的最长回文子序列(可能性要考虑是否包含开头和结尾,重点在于思考可能性是否能解决问题,一般子序列有这种问题)
System.out.println(max("1a2b3c4d3e2f1"));
public static int max(String str){
int len = str.length();
int deep[][] = new int[len][len];//以i,j范围内
for(int i = len-1;i>=0;i--){
for(int j = i;j<len;j++){
if(i==j){
deep[i][j] = 1;
}else {
int tmp = deep[i+1][j-1];
tmp = Math.max(tmp,deep[i+1][j]);
tmp = Math.max(tmp,deep[i][j-1]);
if(str.charAt(i) == str.charAt(j)){
tmp = Math.max(tmp,2+deep[i+1][j-1]);
}
deep[i][j] = tmp;
}
}
}
return deep[0][len-1];
}
蛇走路
//有依赖但没有单调性,所以需要遍历
public static int max(int arr[][]){
int result = 0;
for(int i = 0;i<arr.length;i++){
for(int j = 0;j<arr[0].length;j++){
Info f = max(arr,i,j);
result = Math.max(result,Math.max(f.no, f.yes));
}
}
return result;
}
public static Info max(int arr[][],int i,int j) {
if(j==0){
return new Info(arr[i][j],-arr[i][j]);
}
int preNo = -1;
int preyes = -1;
if(i>0){
Info preUp = max(arr,i-1,j-1);
if(preUp.no >=0){
preNo = preUp.no;
}
if(preUp.yes >=0){
preyes = preUp.yes;
}
}
Info pre = max(arr,i,j-1);
if(pre.no >=0){
preNo = Math.max(pre.no,preNo);
}
if(pre.yes >=0){
preyes = Math.max(pre.yes,preyes);
}
if(i< arr.length-1){
Info down = max(arr,i+1,j-1);
if(down.no >=0){
preNo = Math.max(down.no,preNo);
}
if(down.yes >=0){
preyes = Math.max(down.yes,preyes);
}
}
int no = -1;
int yes = -1;
if(preNo >=0){
no = preNo+arr[i][j];
yes = preNo-arr[i][j];
}
if(preyes>=0){
yes = preyes+arr[i][j];
}
return new Info(no,yes);
}
public static class Info{
public int no;
public int yes;
public Info(int no,int yes){
this.no = no;
this.yes = yes;
}
}
System.out.println(max(new int[][]{{1,-4,10},{3,-2,-1},{2,-1,0},{0,5,-2}}));
数组拆分子数组,需要子数组内的数据异或结果都为0,最多能拆分多少个子数组
维护dp和异或和结果的最新位置的下标,如果两个位置异或和结果一样,那这两个位置之间异或和为0;dp分两种,
以i结尾的数字不在疑惑和为0的结果中,dp[i] = dp[i-1],
以i结尾的小数字疑惑和=0,dp[i]=1+dp[上一个疑惑和为这个值的下标];
两种情况求max
public static int max(int arr[]) {
Map<Integer,Integer> resultMap = new HashMap<>();//异或和结果的最新下标
int result = 0;
int[]deep = new int[arr.length];
deep[0] = arr[0] == 0 ? 1 : 0;
resultMap.put(arr[0],0);
for(int i = 1;i<arr.length;i++){
result = result ^ arr[i];
//当前数字不在结果里面
int result1 = deep[i-1];
if(resultMap.get(result) != null){
//当前数字在结果里面
result1 = Math.max(result1,1+deep[resultMap.get(result)]);
}
resultMap.put(result,i);
deep[i] = result1;
}
return deep[arr.length-1];
}
System.out.println(max(new int[]{3,2,1,0,1,0,2,0,3,2,1,0,4,0}));
字符串1变成字符串2的最小代价
public static int minPrice(String str1,String str2,int i ,int j,int add,int del,int cha){
if(i < 0){
return j < 0 ? 0 : add*(j+1);
}
if(j<0){
return i<0?0:del*(i+1);
}
int min ;
if(str1.charAt(i) == str2.charAt(j)){
min = minPrice(str1,str2,i-1,j-1,add,del,cha);
}else {
min = minPrice(str1,str2,i-1,j-1,add,del,cha) + cha;
}
min = Math.min(min,minPrice(str1,str2,i-1,j,add,del,cha) + del);
min = Math.min(min,minPrice(str1,str2,i,j-1,add,del,cha) + add);
return min;
}
System.out.println(minPrice("bcde","cbed",3,3,1,1,1));
找到字符串没有重复字符的最长子串(从左往右)
假设当前i位置,结果为min(i-1位置推出的最大长度+1,i位置所在字符的上一个位置的长度)
public static int maxNoRep(String str){
if(null == str || str.length() < 1){
return 0;
}
char[] chars = str.toCharArray();
Map<Character,Integer> map = new HashMap<>();
int deep[] =new int[chars.length];
map.put(chars[0],0);
int max = 1;
deep[0] = 1;
for(int i = 1;i<chars.length;i++){
if(map.containsKey(chars[i])){
deep[i] = Math.min(deep[i-1]+1,i-map.get(chars[i]));
}else {
deep[i] = deep[i-1]+1;
}
map.put(chars[i],i);
max = Math.max(max,deep[i]);
}
return max;
}
System.out.println(maxNoRep("1234216987"));
最长有效括号长度
//必须以index结尾的最大长度,遍历一遍得到最大的
public static int max2(String str){
int deep[] = new int[str.length()];
int max = 0;
deep[0] = 0;
for(int i = 1;i<str.length();i++){
if(str.charAt(i)=='('){
deep[i] = 0;
}else {
if(str.charAt(i-1) == '('){
deep[i] = i>=2 ? 2 + deep[i-2] : 2;
max = deep[i] > max ? deep[i] : max;
} else {
int pre = deep[i-1] ;
if(pre >=0 && i-pre-1 >=0 && str.charAt(i-pre-1) == '('){
deep[i] = pre+2;
max = deep[i] > max ? deep[i] : max;
}
}
}
}
return max;
}
//以index结尾的最大长度
public static int max(String str,int index){
if(index <= 0){
return 0;
}
if(str.charAt(index) == '('){
return 0;
}
if(str.charAt(index-1) == '('){
return 2 + max(str,index-2);
} else {
int pre = max(str,index-1) ;
if(pre >=0 && str.charAt(index-pre-1) == '('){
return pre+2;
}
}
return 0;
}
System.out.println(max("((()))()))(",7));
System.out.println(max2("((()))()))("));
n个节点的二叉树的种类
public static int nums2(int n){
int deep[] = new int[n+1];
deep[0] = 1;
deep[1] = 1;
deep[2] = 2;
for(int i=3;i<=n;i++){
for(int k = 0;k<i;k++){
int leftnum = deep[k];
int rightnum = deep[i-k-1];
deep[i] += leftnum*rightnum;
}
}
return deep[n];
}
//
public static int nums(int n){
if(n<0){
return 0;
}
if(n<=1){
return 1;
}
if(n==2){
return 2;
}
int total = 0;
for(int i = 0;i<n;i++){
int leftnum = nums(i);
int rightnum = nums(n-i-1);
total += leftnum*rightnum;
}
return total;
}
System.out.println(nums(3));
System.out.println(nums2(3));
mn格子xy位置随机走k步存活概率
// mn格子xy位置随机走k步
//返回存活的概率
//3/4 3/4 3/4 1 4
public static double live(int M,int N,int k,int x,int y){
if(x<0 || y <0 || x >= M || y >=N ){
return 0.0;
}
if(k == 0){
return 1.0;
}
double a = live(M,N,k-1,x-1,y);
double b = live(M,N,k-1,x+1,y);
double c = live(M,N,k-1,x,y+1);
double d = live(M,N,k-1,x,y-1);
return (a+b+c+d )/4.0;
}
System.out.println(live(3,4,2,1,1));
马跳
//马在9*10格子经过k步从原点跳到xy的方式数
public static int ways2(int x,int y,int k){
int deep[][][] = new int[9][10][k+1]; //0-8 0-9 0-k
deep[0][0][0] = 1;
for(int rest = 1;rest<=k;rest++){
for(int i = 0;i<=8;i++){
for (int j = 0; j <= 9; j++) {
deep[i][j][rest] =
getValue(deep, i - 1, j - 2, rest - 1)
+ getValue(deep, i + 1, j - 2, rest - 1)
+ getValue(deep, i - 2, j - 1, rest - 1)
+ getValue(deep, i + 2, j - 1, rest - 1)
+ getValue(deep, i - 1, j + 2, rest - 1)
+ getValue(deep, i + 1, j + 2, rest - 1)
+ getValue(deep, i - 2, j + 1, rest - 1)
+ getValue(deep, i + 2, j + 1, rest - 1);
}
}
}
return deep[x][y][k];
}
public static int getValue(int deep[][][] ,int x,int y,int k){
if(x<0 || y < 0 || x > 8 || y > 9) {
return 0;
}
return deep[x][y][k];
}
// 马到xy经过k的方法数 9*10的盘子
public static int ways(int x,int y,int k){
if(x<0 || y < 0 || x > 8 || y > 9){
return 0;
}
if(k == 0){
return x == 0 && y == 0 ? 1 : 0;
}
return ways(x-1,y-2,k-1)
+ways(x+1,y-2,k-1)
+ways(x-2,y-1,k-1)
+ways(x+2,y-1,k-1)
+ways(x-1,y+2,k-1)
+ways(x+1,y+2,k-1)
+ways(x-2,y+1,k-1)
+ways(x+2,y+1,k-1);
}
System.out.println(ways(2,1,3));
System.out.println(ways2(2,1,3));
找钱
int money[] = new int[]{2,5,10,20};
System.out.println(num(money,20,0));
System.out.println(num2(money,20,0));
public static int num2(int arr[],int aim,int index){
int[][] deep = new int[arr.length + 1][aim+1];
deep[arr.length][0] = 1;
for(int i = arr.length -1 ;i>=0;i--){
for(int j =0;j<= aim;j++){
int result = 0;
for(int k =0;arr[i]*k<=j;k++){
result += deep[i+1][j - arr[i]*k];
}
deep[i][j] = result;
}
}
return deep[0][aim];
}
//方法数 凑够aim,只能用index及之后的数据
// 返回方法数
public static int num(int arr[],int aim,int index){
if(index == arr.length){
return aim == 0? 1 : 0;
}
if (aim < 0) {
return 0;
}
int result = 0;
for(int i =0;arr[index]*i<=aim;i++){
result += num(arr,aim - arr[index]*i,index+1);
}
return result;
}
斜率优化
正数数组,每个数代表一枚硬币,构成aim的总价值,硬币数最少的方法,不可重复使用
//还需要凑够rest,已经用了used
//尝试要注意从左往右或者范围上等,变量规模要减少,方便改递归,这个就不合适
//返回最小的个数
public static int minC(int arr[],int rest,int used){
if(rest == 0){
return used;
}
int coin = Integer.MAX_VALUE;
for(int i=0;i<arr.length;i++){
if(arr[i] <= rest){
coin = Math.min(minC(arr,rest-arr[i],used+1),coin);
}
}
return coin;
}
//还需要凑够rest,只使用index之后的货币,还需要rest
//返回最小coin的个数, 不能凑成返回-1,能凑成返回0
public static int minC2(int arr[],int index,int rest){
if(rest < 0){
return -1;
}
if(rest == 0){
return 0;
}
if(index == arr.length){
return -1;
}
int usei = minC2(arr,index+1,rest-arr[index]) ;
int notUsei = minC2(arr,index+1,rest);
if(usei == -1 && notUsei == -1){
return -1;
}
if(usei == -1){
return notUsei ;
}else if(notUsei == -1){
return usei + 1;
}else {
return Math.min(usei+1,notUsei);
}
}
public static int minC3(int arr[],int rest) {
int deep[][] = new int[arr.length+1][rest+1];
for(int i = 0;i<=rest;i++){
deep[arr.length][i] = -1;
}
deep[arr.length][0] = 0;
for(int i = arr.length-1;i>=0;i--){//用index后面的钱
for (int j =0;j<=rest;j++){//需要凑够rest
int usei = j-arr[i] <0 ? -1 : deep[i+1][j-arr[i]];
int notUsei = deep[i+1][j];
if(usei == -1 && notUsei == -1){
deep[i][j] = -1;
}
if(usei == -1){
deep[i][j] = notUsei ;
}else if(notUsei == -1){
deep[i][j] = usei + 1;
} else {
deep[i][j] = Math.min(usei+1,notUsei);
}
}
}
return deep[0][rest];
}
int arr[] = new int[]{1,2,5,10};
System.out.println(minC2(arr,0,13));
System.out.println(minC3(arr,13));
机器人走路
public static void main(String[] args) {
int n = 4;
int rest = 5;
int[][] cache = new int[n][rest+1];
for (int i =0;i<n;i++){
for (int j=0;j<=rest;j++){
cache[i][j] = -1;
}
}
System.out.println(walk(4,3,0,5));
}
//机器人在n,从cur走到end,用rest步的方法数
public static int walk(int n,int end ,int cur,int rest){
if(rest == 0 ){
return cur == end ? 1 : 0;
}
if(cur == 0){
return walk(n,end,cur+1,rest-1);
}else if(cur == n - 1){
return walk(n,end,cur-1,rest-1);
}else {
return walk(n,end,cur+1,rest-1) + walk(n,end,cur-1,rest-1);
}
}
public static int walk2(int n,int end ,int cur,int rest,int[][] cache){
if(cache[cur][rest] != -1){
return cache[cur][rest];
}
if(rest == 0){
cache[cur][rest] = cur == end ? 1 : 0;
return cache[cur][rest];
}
if(cur == 0){
cache[cur][rest] = walk2(n,end,cur+1,rest-1,cache);
}else if(cur == n - 1){
cache[cur][rest] = walk2(n,end,cur-1,rest-1,cache);
}else {
cache[cur][rest] = walk2(n,end,cur+1,rest-1,cache) + walk2(n,end,cur-1,rest-1,cache);
}
return cache[cur][rest];
}
//当前在cur,剩余rest的方法数
public static int walk3(int n,int end ,int cur,int rest){
int deep[][] = new int[n+1][rest+1];
deep[end][0] = 1;
for(int i = 1;i<=rest;i++){//剩余步数增加
for(int j = 0 ;j<n;j++) {//当前位置
if (j == 0) {
deep[j][i] = deep[j + 1][i - 1];
} else if (j == n - 1) {
deep[j][i] =deep[j - 1][i - 1];
} else {
deep[j][i] = deep[j + 1][i - 1] + deep[j - 1][i - 1];
}
}
}
return deep[cur][rest];
}
鸡蛋掉落
最大最小问题
dp[i] = min(dp[i], dp[i-num]+1)
dp[i] = max(dp[i], dp[i-num]+1)
最大子序列和
如{5,4,-1,7,8}的最长子序列和是23
dp[i] = Math.max(dp[i-1]+a[i],a[i]);
编辑距离
一个单词变为另一个单词(删除,添加,替换的方式)的最短距离
思路:动态规划,如果
if(word1.charAt(i-1) == word2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1];
} else {
dp[i][j] = 1+Math.min(Math.min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1]);
}
数字字符串编码为字母
第一种情况:最后一位使用一个字符,s【i】!=0,dp[i] = dp[i-1]
第二种情况:最后两位拼成字母,s[i-1]!=0 && 10*s[i-1]+s[i] <26,dp[i] = dp[i-2]
不同二插搜索树的个数
public static int aaa(int n) {
int dp[] = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
for (int j = 1; j <= i; j++) {
dp[i] += dp[j-1] * dp[i - j];
}
}
return dp[n];
}
交错字符串
定义 f(i,j) 表示 s1的前 i 个元素和 s2的前 j 个元素是否能交错组成 s3的前 i+j 个元素
public static boolean isaa(String s1,String s2,String s3){
int m = s1.length();
int n = s2.length();
if(m+n != s3.length()){
return false;
}
boolean dp[][] = new boolean[m+1][n+1];
dp[0][0] = true;
for (int i = 0;i<=m;i++){
for(int j = 0;j<=n;j++){
if(i>0){
if(s3.charAt(i+j-1) == s1.charAt(i-1) && dp[i-1][j]){
dp[i][j] = true;
}
}
if(j>0){
if(s3.charAt(i+j-1) == s2.charAt(j-1) && dp[i][j-1]){
dp[i][j] = true;
}
}
}
}
return dp[m][n];
}
不同的子序列
s和t,s中包含t的个数
dp[i][j]代表s前i个字符中包含t前j个字符的个数
public static int aa(String s,String t){
int m = s.length(),n=t.length();
if(m<n){
return 0;
}
// dp[i][j] s中i位能和t中j位匹配的个数
int dp[][] = new int[m+1][n+1];
for(int i = 0;i<=m;i++){
for(int j = 0;j<=n;j++){
if(j == 0){
dp[i][j] = 1;
continue;
}
if(i == 0){
dp[i][j] = 0;
continue;
}
if(s.charAt(i-1) == t.charAt(j-1)){
dp[i][j] = dp[i-1][j-1]+dp[i-1][j];
}else {
dp[i][j] = dp[i-1][j];
}
}
}
return dp[m][n];
}
三角形最短路径和
dp[i][j]=min(dp[i−1][j−1],dp[i−1][j])+c[i][j]
买卖股票(可以交易多次)
//i天,状态j(0,股票),手里的最大收益
dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]-prices[i]);
dp[i][1] = Math.max(dp[i-1][0]+prices[i],dp[i-1][1]);
零钱兑换,数组总和
对于面额为 coin 的硬币,当 coin≤i≤amount 时,如果存在一种硬币组合的金额之和等于i−coin,则在该硬币组合中增加一个面额为 coin 的硬币,即可得到一种金额之和等于 ii 的硬币组合。因此需要遍历coins,对于其中的每一种面额的硬币,更新数组 }dp 中的每个大于或等于该面额的元素的值。
public int change(int amount, int[] coins) {
int[] dp = new int[amount + 1];
dp[0] = 1;
for (int coin : coins) {
for (int i = coin; i <= amount; i++) {
dp[i] += dp[i - coin];
}
}
return dp[amount];
}
4. 二分法
两个有序数组第k小的数(两个有序数组中位数)
中位数是先求两个第k小的再计算
求第k小,先判断两个数组第k/2的数是不是想等,不想等的话,去掉较小的数组的前部分x个,再从剩下的取k-x小;
//求第k小的数;从k=1或一个数组为空返回
public static double mid(int arr1[],int arr2[],int mleft,int nleft,int k){
int m = arr1.length-mleft;//数组长度
int n = arr2.length-nleft;
int mIndex = mleft+ k/2-1; //数组坐标
int nIndex = nleft+ k-k/2-1;
if(m == 0){
return arr2[k-1+nleft];
}
if(n == 0){
return arr1[k-1+mleft];
}
if(k==1){
return arr1[mleft]<arr2[nleft] ? arr1[mleft] : arr2[nleft];
}
if(arr1[mIndex] == arr2[nIndex]){
return arr1[mIndex];
}
if(arr1[mIndex] < arr2[nIndex]){//mIndex left抛弃
return mid(arr1, arr2, mIndex+1, nleft, k-(mIndex-mleft+1));
}else {
return mid(arr1, arr2, mleft, nIndex+1, k-nIndex+nleft-1);
}
}
int arr1[] = new int[]{1,3,4,9};
int arr2[] = new int[]{1,2,3,4,5,6,7,8,9,10};
System.out.println(mid(arr1, arr2, 0, 0, 7));
System.out.println(mid(arr1, arr2, 0, 0, 8));
找局部最小
先判断0和n-1位置是否局部最小,都不是的话2分法
中位返回模板(猜数字,平方根,搜索旋转数组)
while L <= R:
M = (L + R) // 2
if nums[M] == T:
return M
elif nums[M] < T:
L = M + 1
else:
R = M - 1
空间压缩模板(求峰值)
while L < R:
M = (L + R) // 2
if need in s[L:M]:
R = M
else:
L = M + 1
5. 滑动窗口,双指针
正数组组,累计和为k的连续子数组的最长长度
public static int max(int arr[],int k){//2,1,2,1,1,1,6,1
int max = 0;
int left = 0;
int right = 0;
int total = arr[0];
int tmp = 0;
while(left < arr.length && right < arr.length){
total += tmp;
if(total == k){
max = right-left+1 > max ? right-left+1:max;
tmp = right+1 == arr.length ? 0 : arr[right+1];
right++;
}else if(total < k){
tmp = right+1 == arr.length ? 0 : arr[right+1];
right++;
}else {
tmp = -arr[left];
left++;
}
}
return max;
}
System.out.println(max(new int[]{1,2,1,1,1},3));
绳子覆盖的点数
public static int maxCover(int arr[],int k){
if(k == 0){
return 0;
}
int result = 0;
int left = 0;
int right = 0;
for(int i = 1;i<arr.length;i++){
right = i;
if(arr[right] - arr[left] < k){
continue;
}else if(arr[right] - arr[left] == k){
result = right - left +1 > result ? right - left +1: result;
left++;
}else {
result = right - left > result ? right - left : result;
left++;
}
}
return result;
}
int arr[] = new int[]{1,3,4,5,6,7,10};
System.out.println(maxCover(arr,3));
无重复字符的最长子串
public static int lengthOfLongestSubstring(String s) {
int left = 0;
int max = 0;
Map<Character,Integer> map = new HashMap<>();
for(int i = 0;i<s.length();i++){
if(map.containsKey(s.charAt(i))){
left = Math.max(map.get(s.charAt(i))+1,left);
}
map.put(s.charAt(i),i);
max = Math.max(max,i-left+1);
}
return max;
}
盛最多水的容器
public static int maxArea(int[] height) {
int left = 0,right = height.length-1;
int max = 0;
while (left < right){
int current = Math.min(height[left],height[right])*(right-left);
max = Math.max(max,current);
if(height[left] > height[right]){
right--;
}else {
left++;
}
}
return max;
}
三数之和
public static List<List<Integer>> threeSum(int[] a) {
Arrays.sort(a);
List<List<Integer>> result = new ArrayList<>();
for(int i = 0;i<a.length;i++){
if(a[i] > 0){
break;
}
if(i > 0 && a[i] == a[i-1]) continue;
int l = i+1,r=a.length-1;
while (l<r){
if(a[i] + a[l] + a[r] == 0){
ArrayList tmp = new ArrayList();
tmp.add(a[i]);
tmp.add(a[l]);
tmp.add(a[r]);
result.add(tmp);
l++;r--;
}else if(a[i] + a[l] + a[r] < 0){
l++;
}else {
r--;
}
}
}
return result;
}
最接近的三数之和(同上)
链表倒数第n个节点(写)
x的平方分
要考虑边界情况,用long类型
是否有环(快慢指针)
6. 前缀和(数组连续,求子数组)
累加和存位置(最长相等01子数组,最长偶数元音子数组)
累加和存目标(连续和为 k 倍 的子数组)
class Solution {
public boolean checkSubarraySum(int[] nums, int k) {
int n = nums.length;
int[] sum = new int[n + 1];
for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + nums[i - 1];
Set<Integer> set = new HashSet<>();
for (int i = 2; i <= n; i++) {
set.add(sum[i - 2] % k);
if (set.contains(sum[i] % k)) return true;
}
return false;
}
}
7. 堆
中位数
大根堆和小根堆
两个堆数量不一样,则放入大根堆,再poll大根堆的到小根堆
两个堆数量一样,则放入小根堆,再poll小根堆的到大根堆
public static double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length,n=nums2.length;
Queue<Integer> big = new PriorityQueue<>((a,b)->(b-a));//大,存小的
Queue<Integer> small = new PriorityQueue<>();
for(int i = 0;i<m;i++){
addNum(big,small,nums1[i]);
}
for(int i = 0;i<n;i++){
addNum(big,small,nums2[i]);
}
return small.size() != big.size() ? big.peek() : (small.peek() + big.peek())/2.0;
}
public static void addNum(Queue<Integer> big,Queue<Integer> small,int num){
if(big.size() == small.size()) {
small.add(num);
big.add(small.poll());
} else {
big.add(num);
small.add(big.poll());
}
}
8. 其他
z字形变换
public static String re(String s, int num) {
List<StringBuilder> sbs = new ArrayList<>(num);
for (int i = 0; i < num; i++) {
sbs.add(new StringBuilder());
}
int index = 0;
int cycle = num * 2 - 2;
int yu = 0;
for (int i = 0; i < s.length(); i++) {
yu = i % cycle;
if(yu == 0){
index = 0;
}
if (yu < num) {
sbs.get(index).append(s.charAt(i));
index++;
} else {
sbs.get(num-index+1).append(s.charAt(i));
index++;
}
}
StringBuilder result = new StringBuilder();
for (StringBuilder temp :
sbs) {
result.append(temp);
}
return result.toString();
}
下一个升序排列
arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1]
思路:先从后往前找升序对,index= i;i之后的一定降序;在【i+1,len)之间找到最小的大于num【i】的,然后交换值,然后【i+1,len)之间的升序
public static void nextPermutation(int[] nums) {
for(int i = nums.length-2 ;i>= 0;i--){
if(nums[i]<nums[i+1]){
for(int j = nums.length-1;j>i;j--){
if(nums[j]>nums[i]){
int a = nums[i];
nums[i] = nums[j];
nums[j] = a;
sort1(nums,i+1);
return;
}
}
}
}
Arrays.sort(nums);
}
public static void sort1(int[] nums,int i){
int[] result = new int[nums.length];
int[] sort = new int[nums.length-i];
for(int j = i;j<nums.length;j++){
sort[j-i] = nums[j];
}
Arrays.sort(sort);
for(int j = 0;j<nums.length;j++){
if(j>=i){
nums[j] = sort[j-i];
}
}
}
接雨水
对于每一个坐标i,雨水为两边最大高度的较小者减去高度:min(left_max,right_max)-height [i]
买卖股票
遍历一次,记录当前节点之前的最低价格,和最高利润
9. 贪心
坐船问题
》坐船问题,最多两人一条,船限制limit,需要最少多少个船:从小于limit/2的最大位置两个指针,统计出配对的数量a,左边未配对的数量b,右边未配对的c,结果是a+b/2+c(贪心,L先匹配到最右边的,右边移动的位数step,此时L也往左移动step,这部分的配为最优)
public static int min(int arr[],int limit){
if(null == arr || arr.length < 1 || limit <1){
return -1;
}
Arrays.sort(arr);
if(arr[arr.length-1] <= limit/2){
return (arr.length+1)<<1;
}
if(arr[0]>limit/2){
return arr.length;
}
int lessR = arr.length -1;
while (lessR>limit/2){
lessR--;
}
int L = lessR;
int R =L+1;
int lessUnused = 0;
while (L>=0){
int solved = 0;
while (R< arr.length && arr[L]+arr[R]<=limit){
R++;
solved++;
}
if(solved == 0){
lessUnused++;
L--;
} else {
L = Math.max(-1,L-solved);
}
}
int lessAll = lessR+1;
int lessUsed = lessAll - lessUnused;
int moreUnsolved = arr.length - lessR - 1 - lessUsed;
return lessUsed + (lessUnused+1)/2 + moreUnsolved ;
}
System.out.println(min(new int[]{1,2,2,3,4,5,5,6,7,8},8));
最小字典(排序即可)
字符串数组拼接成最小的字典序:字符串按照字典序排序后拼接(错,b,ba),应按照a+b < b+a ? a放前面 : b放前面
做项目
会议室
/**
* 只有一个会议室
* @param meets
* @return
*/
public static int meeting(ArrayList<Meet> meets){
// Meet[] meets
// Arrays.sort(meets,new MyCompatator());
meets.sort(new MyCompatator());
int able = 0;
int result = 0;
for (int i = 0;i<meets.size() ;i++){
if(meets.get(i).start >= able){
result ++;
able = meets.get(i).end;
}
}
return result;
}
ArrayList<Meet> meets = new ArrayList<>();
Meet meet1 = new Meet(0,1); meets.add(meet1);
Meet meet2 = new Meet(1,2);meets.add(meet2);
Meet meet3 = new Meet(2,5);meets.add(meet3);
Meet meet4 = new Meet(1,4);meets.add(meet4);
Meet meet5 = new Meet(7,9);meets.add(meet5);
System.out.println(meeting(meets));
金条切割
public static int minPrice(int arr[]){
PriorityQueue<Integer> queue = new PriorityQueue();
for (int i = 0;i<arr.length;i++){
queue.add(arr[i]);
}
int price = 0;
while(queue.size() > 1){
int newPrice = queue.poll() + queue.poll();
price += newPrice;
queue.add(newPrice);
}
return price;
}
int[] minPriceA = new int[]{10,30,20};
System.out.println(minPrice(minPriceA));
整数转罗马数字
思路:先枚举,再从大往小减
public static String intToRoman(int num) {
int a[] = new int[]{1000,900,500,400,100,90,50,40,10,9,5,4,1};
String b[] = new String[]{"M","CM","D","CD","C","XC","L","XL","X","IX","V","IV","I"};
StringBuilder sb= new StringBuilder();
int count = 0;
while (num != 0){
while (num - a[count] >= 0){
sb.append(b[count]);
num -= a[count];
}
count++;
}
return sb.toString();
}
罗马转数字
有公共前缀,替换
public static int romanToint(String s) {
s.replaceAll("CM","a");
s.replaceAll("CD","b");
s.replaceAll("XC","c");
s.replaceAll("XL","d");
s.replaceAll("IX","e");
s.replaceAll("IV","f");
StringBuilder stringBuilder = new StringBuilder();
int result = 0;
for(int i = 0;i<s.length();i++){
result += get(String.valueOf(s.charAt(i)));
}
return result;
}
public static int get(String s){
switch (s) {
case "M" : return 1000;
case "a" : return 900;
case "D" : return 500;
case "b" : return 400;
case "C" : return 100;
case "c" : return 90;
case "L" : return 50;
case "d" : return 40;
case "X" : return 10;
case "e" : return 9;
case "V" : return 5;
case "f" : return 4;
case "I" : return 1;
default: return 0;
}
}
10. 数学
整数反转
通过取余能求整数的最后一位
思路:通过对10取余求 最后一位,除法求剩余的数字
public static int reverse(int x) {
if (x > Integer.MAX_VALUE || x < Integer.MIN_VALUE)
return 0;
int result = 0;
while (x != 0) {
int yu = x % 10;
x = x / 10;
result = result * 10 + yu;
}
return result;
}
指数运算(pow)
二分法,递归
如x的77次方,可以按照x的38,19,9,4,2,1的顺序
public static double pow(double x, int n) {
if(n == 0){
return 1;
}
double a = pow(x,n/2);
if(n%2 == 0){
return a*a;
}else {
return a*a*x;
}
}
两数相除
格雷编码
原有的集合a(n),前面各位前面加0的集合a1(n) + a镜像后前面加1的集合a2(n) = 即为 a(n+1)
其中a1(n) == a(n)
11. 打表
只能吃4的幂这么多(1,4,16)。两个牛先后吃,谁吃最后一份谁赢,打表法
//先手
//是不是赢
public static boolean win1(int n){
if(n<5){
return n == 2 || n == 0 ? false : true;
}
int k = 1;
while(n - k >= 0){//先手拿掉这一部分,作为后手是不是能赢
if(!win1(n - k)){
return true;
}
k *= 4;
}
return false;
}
for (int i = 0;i<=100;i++){
System.out.println(i+" "+win1(i));
}
6.8袋子
public static int min(int n){
if(n<0 || n%2!=0){
return -1;
}
if(n % 8 == 0){
return n / 8;
}
int b6 = -1;
int b8 = n / 8;
int rest = n- 8*b8;
while(b8 >= 0 && rest < 24){//只用6袋子处理,>24的先-24,这个数之前必定算过,需要剪枝
if(rest % 6 == 0){
b6 = rest / 6;
break;
}
b8--;
rest = n - 8*b8;
}
return b6 == -1 ? -1 : b6 + b8;
}
for (int i = 0;i<=1000;i++){
System.out.println(i+" "+min(i));
}
12. 预处理(代码中部分查询频繁,用低的代价初始化查询结果,加快查询)
最大边长都是1的正方形
n的4次方复杂度,左边顶点n平方,边长n,最内层判断四个边是否为1
预处理两个数组,该点右边有多少个连续的1,下边有多少个连续的1,上面的方法最内层的判断可通过这两个数组快速查询,时间复杂度变为n的三次方
LR
public static int minColor(String str){
char [] chars = str.toCharArray();
int [] ls = new int[chars.length];//i位置及之前L的数量
int n = chars.length;
int count = 0;
for(int i = 0;i<n;i++){
if(chars[i] == 'L'){
count++;
}
ls[i] = count;
}
int min = Integer.MAX_VALUE;
for(int i = 0;i<n;i++){ //i及左边是L,右边是R
if(i == 0){//全是R,ls 中L的数量
min = Math.min(min,ls[n-1]);
}else if(i == n-1){//全是L ,ls中R数量
min = Math.min(min,n-ls[i]);
}else { //i及左边是L,右边是R (i+1) n-i-1 ,
min = Math.min(min, i+1 -ls[i] + ls[n-1] - ls[i] );
}
}
return min;
}
13. 记录辅助信息
包含负数的数组,累计和<=k的连续子数组的最长长度
维护两个数组,数组1以i开始的累加和最小值,数组2是数组1对应的右坐标;
public static int max(int arr[],int k){
//以i开始往后的最小累加和及下标
int len = arr.length;
int minSum[] = new int[len];
int ends[] = new int[len];
minSum[len-1] = arr[len-1];
ends[len-1] = len-1;
for (int i = arr.length -2;i>=0;i--) {
if(minSum[i+1]>0){
minSum[i] = arr[i];
ends[i] = i;
}else {
minSum[i] = arr[i]+minSum[i+1];
ends[i] = ends[i+1];
}
}
int sum = 0;
int end = 0;
int res = 0;
for(int i =0;i<arr.length;i++){
while ( end < len && sum+minSum[end] <= k){
sum += minSum[end];
end = ends[end]+1;//end遍历后为不满足的下一个起始
}
res = Math.max(res,end-i);
if(end>i){
sum -= arr[i];
}else {
end=i+1;
}
}
return res;
}
包含负数的数组,累加和为k的连续子数组的最长长度
map,记录所有累加和及最早出现的位置
public static int max(int arr[],int k){
Map<Integer,Integer> map = new HashMap<>();
int sum = 0;
int result = 0;
map.put(0,-1);
for(int i =0;i<arr.length;i++){
sum += arr[i];
if(map.containsKey(sum - k)){
result = Math.max(result,i - map.get(sum - k) );
}else {
map.put(sum,i);
}
}
return result;
}
System.out.println(max(new int[]{1,1, 2, 1, -1,1,-1, 2 },3));
System.out.println(max(new int[]{1,1, 2, 1, -1, 2 },5));
接雨水
public static int water(int arr[]) {
int n = arr.length;
int leftMax[] = new int[n];
int rightMax[] = new int[n];
leftMax[0] = 0; rightMax[n-1] = 0;
int max = arr[0];int maxR = arr[n-1];
for(int i = 1;i<n;i++){
leftMax[i] = arr[i] < max ? max : 0;
max = max < arr[i] ? arr[i] : max;
}
for(int i = n-1;i>=0;i--){
rightMax[i] = arr[i] < maxR ? maxR : 0;
maxR = maxR < arr[i] ? arr[i] : maxR;
}
int result = 0;
for(int i = 1;i<n-1;i++){
result += (leftMax[i] !=0 && rightMax[i] !=0) ? Math.min(leftMax[i],rightMax[i]) - arr[i]: 0;
}
return result;
}
System.out.println(water(new int[]{4,1,3,5,4,2,6}));
给一个字符串,需要放多少个能构成完成括号字符串
》完整的括号字符串:栈,或者一个变量count,遇到左括号count++,右括号count–,若过程count<0则错误,若结果count!=0则错误
》给一个字符串,需要放多少个能构成完成括号字符串:同上,再加一个ans,若过程中count<0,则令count=0(加一个左括号),同时ans++;遍历结束若count>0,ans+count(补多少个右括号),返回ans
》括号的深度,同上,count的最大值
public static int howMany(String str) {
if(null == str || str.length() == 0){
return 0;
}
int result = 0;
int lefts = 0;
for (int i = 0;i<str.length();i++){
if(str.charAt(i) == '('){
lefts++;
}else {
lefts--;
}
if(lefts < 0){
result++;
lefts = 0;
}
}
return lefts+result;
}
System.out.println(howMany(")((()))()))((()"));
========================
todo 写
优先级1:
37,38,41,43,44,45,55,60,61,63,65,68,76,79,82,83,92,95,105,106,112,113,123,146,10
优先级2,高:
77,78,90,93
- List item
算法实战
布隆过滤器
大数据的:分片(hash),bit数组,1G=10亿字节
搜索热点词:分片,hash计算数量,大根堆
海量数据的中位数:二分,二进制的最高位,分为两个文件,统计个数
短链系统:高进制来缩短,缓存存放映射关系
在线并发用户数:redis
在线并发用户数,redis,zset
红包算法:
线性切割法,一个区间切N-1刀。越早越多
二倍均值法,【0 ~ 剩余金额 / 剩余人数 * 2】中随机,相对均匀
快排
两个方法,第一个,当low《high 时,获取中间的指针,并递归调用左右两边
第二个,返回中间指针,遍历,当遇到比最右边小的时候,交换pointer和当前元素,遍历完成后交换pointer和最右边元素,返回中间的指针pointer
public static int partition(int[] array, int low, int high) {
// 取最后一个元素作为中心元素
int pivot = array[high];
// 定义指向比中心元素大的指针,首先指向第一个元素
int pointer = low;
// 遍历数组中的所有元素,将比中心元素大的放在右边,比中心元素小的放在左边
for (int i = low; i < high; i++) {
if (array[i] <= pivot) {
// 将比中心元素小的元素和指针指向的元素交换位置
// 如果第一个元素比中心元素小,这里就是自己和自己交换位置,指针和索引都向下一位移动
// 如果元素比中心元素大,索引向下移动,指针指向这个较大的元素,直到找到比中心元素小的元素,并交换位置,指针向下移动
int temp = array[i];
array[i] = array[pointer];
array[pointer] = temp;
pointer++;
}
System.out.println(Arrays.toString(array));
}
// 将中心元素和指针指向的元素交换位置
int temp = array[pointer ];
array[pointer] = array[high];
array[high] = temp;
return pointer;
}
public static void quickSort(int[] array, int low, int high) {
if (low < high) {
// 获取划分子数组的位置
int position = partition(array, low, high);
// 左子数组递归调用
quickSort(array, low, position -1);
// 右子数组递归调用
quickSort(array, position + 1, high);
}
}
归并
分:从中间分开,左右两边循环调用,直至只有一个元素,然后调用第二个方法
和:第二个方法,申请一个数组做合并使用
//合并
public static void merge(int[] arr, int L, int M, int R) {
int[] help = new int[R - L + 1];
int i = 0;
int p1 = L;
int p2 = M + 1;
while (p1 <= M && p2 <= R)
help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
while (p1 <= M)
help[i++] = arr[p1++];
while (p2 <= R)
help[i++] = arr[p2++];
for (i = 0; i < help.length; i++)
arr[L + i] = help[i];
}
//分
public static void mergeSort(int[] arr, int L, int R) {
if (L == R)
return;
int mid = L + ((R - L) >> 1);
mergeSort(arr, L, mid);
mergeSort(arr, mid + 1, R);
merge(arr, L, mid, R);
}
LRU
get和set方法,基于linkedHashMap(双向链表 )set时先删除,再判断容量(超过时删除最后一个元素),再put
get时如果存在,需要充值元素位置到第一个
左神
删掉字符串的重复字符,使字典序最小
public static String min(String str){
if(null == str|| str.length()<1){
return str;
}
char[] map = new char[256];
for (int i = 0;i<str.length();i++) {
map[str.charAt(i)]++;
}
int minIndex = 0;
for (int i = 0;i<str.length();i++) {
minIndex = str.charAt(i) < str.charAt(minIndex) ? i : minIndex;
if(--map[str.charAt(i)] == 0){
break;
}
}
return str.charAt(minIndex) + min(str.replaceAll(String.valueOf(str.charAt(minIndex)),""));
}
System.out.println(min("bbcaakb"));
过称为选择某一个字符,选到某一段词频为0了,在前面的一段选最小的,删掉string的该字符继续这个操作
一个数组,求如果排序后相邻两个数的最大差值,且时间复杂度0(n)
找出长度为n数组中所有未出现的数,每个数字位于1到n之间
由一个点出发,力争让i位置上的数字为i+1,当不满足时不断调整,满足的时候从下一个下标继续开始
for(int i = 0;i<arr.length;i++){
int tmp = arr[i];
if(tmp == i+1){
continue;
}
while(tmp != arr[tmp-1]){
int tmp2 = arr[tmp-1];
arr[tmp-1] = tmp;
tmp = tmp2;
}
}
notShow(new int[]{4,2,1,3,4});
路灯安装
public static int min(String str){
if(null == str || str.length() <1){
return 0;
}
char[] chars = str.toCharArray();
int result = 0;
for(int i =0;i<chars.length;){
if(chars[i] == '.' ){
result++;
if(i+1 < chars.length && chars[i+1] =='.'){//第二个也是。,那第三个不管是。还是x都跳过
i = i+3;
continue;
}
}
i++;
}
return result;
}
System.out.println(min(".x.x.."));
子数组的最大连续累加和
public static int max(int arr[]) {
int result = Integer.MIN_VALUE;
int preSum = 0;
for(int i = 0;i<arr.length;i++){
preSum += arr[i];
result = Math.max(result,preSum);
preSum = preSum > 0 ? preSum : 0;
}
return result;
}
System.out.println(max(new int[]{1,2,3,-4,5,-1,2,1} ));
字符串转数字(对超过min的拦截)
public static int change(String str){
if(null == str || str.length()<1){
throw new RuntimeException("aa");
}
char[] chars;
boolean neg = false;
if(str.charAt(0) == '-' ){
neg = true;
chars = str.substring(1).toCharArray();
}else {
chars = str.toCharArray();
}
if(!isValid(chars)){
throw new RuntimeException("bb");
}
int mina = Integer.MIN_VALUE / 10;
int minb = Integer.MIN_VALUE % 10;
int result = - (chars[0]-'0');
for(int i = 1;i<chars.length;i++){
if(result < mina || (result == mina && ('0'-chars[i] < minb))){
throw new RuntimeException("bb");
}
result = result*10 - (chars[i]-'0');
}
if(!neg && result == Integer.MIN_VALUE){
throw new RuntimeException("bb");
}
return neg ? result : -result;
}
public static boolean isValid(char[] chars){
if(chars[0] <= '0' || chars[0] > '9'){
return false;
}
for(int i = 1;i<chars.length;i++){
if(chars[i]>'9' || chars[i] <'0'){
return false;
}
}
return true;
}
System.out.println(Integer.MIN_VALUE);
System.out.println(change("2147483649"));
洗衣机均分
思想:就是计算每个位置需要经历多少次传递,其中的最大值
1;求每个位置最小的轮数,分为左右两边多,少共四种情况,两边都少时,为L+R,其他为max(L,R)
2;求每个位置最小的轮数中的最大值
public static int min(int arr[]) {
if(arr.length<2){
return arr[0];
}
int length = arr.length;
int total = 0;
for(int i = 0;i<length;i++){
total += arr[i];
}
if(total % length != 0){
return -1;
}
int each = total / length;
int max = Math.abs(arr[0]-each);
max = Math.max(max,Math.abs(arr[length-1]-each));
int curTotal = 0;
for(int i =1;i<length-1;i++){
curTotal += arr[i-1];//左边有i个
int leftNeed = each*i;
int rightTotal = total - curTotal -arr[i];
int rightNeed = each*(length-i-1);
if(curTotal < leftNeed && rightNeed>rightTotal){
max = Math.max(max,leftNeed-curTotal+rightNeed-rightTotal);
}else {
max = Math.max(max,Math.max(Math.abs(leftNeed-curTotal),Math.abs(rightNeed-rightTotal)));
}
}
return max;
}
// int arr[] = new int[]{1, 0, 5};
int arr[] = new int[]{0, 2, 0};
System.out.println(min(arr));
数组最大前缀疑惑和
前缀数标准代码
对数器:
打爆气球(尝试模型)
每个位置的气球最后被打爆
汉诺塔游戏路径
n层汉诺塔最优2的n次方-1步
f(n) = f(n-1) + 1 + f(n-1)
分3步
- 0 - i-1 从from到other
- i到to
- 0 - i-1从other到to
f(n) = f(n-1) + 1 + f(n-1)
判断两个字符串是否互为旋变串
同一级的可以互换位置,新的字符串就是原来的旋变串
范围上尝试
先尝试str1的所有可能的第一刀怎么划分,然后考虑str2的两种情况,旋变或者不旋变
dp
str1包含str2所有字符的最小长度(滑动窗口)
实现LFU(最近最少使用,code难,原理简单,try)
o(1)时间复杂度
通过词频的双向链表(存key,value对象),双向链表之间也是通过双向链表连接,和key的map,map指向链表对象的地址
良好加油站(code难)
建立ill - res的数组
然后四个变量,若前闭后开的变量中没有良好出发点,则没有,若有,如H,则验证剩下的点(沿H逆时针)能到H即可
二叉搜索树的错误节点
错误节点是中序遍历后,第一次降序的前一个节点和最后一次降序的后一个节点
最多矩阵重叠
先处理线段最多重合,线段按照start排序,建立有序表,用于存end字段,每个线段进入时,将有序表中《=start的内容删掉,然后加入自己的end(含义:重合区域必须已start开头的最多有几段),此时有序表的节点个数就是该线段的最多重合
然后,
矩阵按照下边界排序,逐个将矩阵上边界放入容器中,遍历,容器中比下一个矩阵的下边界底的丢出去,再放入自己的上边界,此时容器中的矩阵的每个左右边界,变成了一维的求最多重合问题,遍历完求出最大
数组中两个数据出现了奇数次
最长有效括号
public static int maxLength(String str){
int limit = 0;
int currL = 0;
int maxL = 0;
char[] chara = str.toCharArray();
for(int i =0;i<chara.length;i++){
if(chara[i] =='('){
limit++;
}else {
if(limit == 0){
currL = 0;
}else {
limit--;
currL+=2;
maxL = currL > maxL ? currL : maxL;
}
}
}
return maxL;
}
System.out.println(maxLength(")((((())())"));
将栈a中数据调整有序,栈顶最大
public static void orderStack(Stack<Integer> stack){
if(stack.isEmpty()){
return;
}
Stack<Integer> help = new Stack<>();
while (!stack.isEmpty()){
Integer top = stack.pop();
while (!help.isEmpty() && help.peek() > top){
stack.push(help.pop());
}
help.push(top);
}
while (!help.isEmpty()){
stack.push(help.pop());
}
}
Stack<Integer> stack = new Stack<>();
stack.push(5);
stack.push(1);
stack.push(3);
stack.push(2);
stack.push(4);
orderStack(stack);
System.out.println();
不考虑局部移动
矩阵z字型打印
定义两个点开始都在左上角,然后a往右移动,到尽头后往下移动,b往下移动,到尽头后往右移动,打印a到b之间的斜线,分为a到b打印和b到a打印,交替进行
public static void printZ(int arr[][]){
int ax=0,bx=0,ay=0,by=0;
boolean reverse = true;
int step = 0;
int row = arr.length;
int col = arr[0].length;
while (step<=arr.length+arr[0].length){
print(arr,ax,ay,bx,by,reverse);
if(ax<row-1){
ax++;
}else {
ay++;
}
if(by<col-1){
by++;
}else {
bx++;
}
reverse = !reverse;
step++;
}
}
public static void print(int arr[][],int ax,int ay,int bx,int by,boolean reverse){
if(reverse){
while (ax >= bx){
System.out.println(arr[ax--][ay++]);
}
}else {
while (ax >= bx){
System.out.println(arr[bx++][by--]);
}
}
}
//1 2 3 4
//4 5 6 7
//7 8 9 1
int arr[][] = new int[][]{{1,2,3,4},{4,5,6,7},{7,8,9,1}};
printZ(arr);
二维数组旋转
正方形矩阵旋转90度:有限变量:一层层转变,定义框调整的函数f(),每一层根据4个顶点开始互换,然后点移动继续互换,定义两个左上右下的顶点确定一个正方形
public static void xuan(int arr[][]) {
if(null == arr || arr.length < 2){
return;
}
int lx = 0;;
int rx = arr.length-1;
while (lx<rx){
xuan(arr,lx,rx);
lx++;
rx--;
}
}
//1234
//4567
//7891
//4567
public static void xuan(int arr[][],int lx,int rx){
int ax = lx;
int ay = lx;//++
int bx = lx;//++
int by = rx;
int cx = rx;
int cy = rx;//--
int dx = rx;//--
int dy = lx;
while (ay<rx){
int tmp = arr[ax][ay];
arr[ax][ay] = arr[dx][dy];
arr[dx][dy] = arr[cx][cy];
arr[cx][cy] = arr[bx][by];
arr[bx][by] = tmp;
ay++;
bx++;
cy--;
dx--;
}
}
//1234
//4567
//7891
//2345
int arr[][] = new int[][]{{1,2,3,4},{4,5,6,7},{7,8,9,1},{2,3,4,5}};
xuan(arr);
螺旋打印
public static void print(int [][] arr){
int lx = 0;
int ly = 0;
int rx = arr.length-1;
int ry = arr[0].length-1;
while (lx <= rx && ly <= ry){
printSide(arr,lx,ly,rx,ry);
lx++;ly++;
rx--;ry--;
}
}
public static void printSide(int [][] arr,int lx,int ly,int rx,int ry){
if(lx == rx){//同一行
for(int i=ly;i<=ry;i++){
System.out.println(arr[lx][i]);
}
}else if(ly ==ry){
for(int i=lx;i<=rx;i++){
System.out.println(arr[i][ly]);
}
} else {
for(int i =ly;i< ry;i++){
System.out.println(arr[lx][i]);
}
for(int i =lx;i<rx;i++){
System.out.println(arr[i][ry]);
}
for(int i =ry;i> ly;i--){
System.out.println(arr[rx][i]);
}
for(int i = rx;i>lx;i--){
System.out.println(arr[i][ly]);
}
}
}
//123456
//567821
//678914
//743139
//134241
int arr[][] = new int[][]{{1, 2, 3, 4, 5,6}, {5, 6, 7, 8, 2,1}, {6, 7, 8, 9, 1,4},{7,4,3,1,3,9},{1,3,4,2,4,1}};
print(arr);
技巧
压缩数组
二维数组的最大累加和(n3次方)
给定一个整形矩阵,找出子矩阵的最大累加和:求第一行,第一行到第2行,一直到第n行;第二行到第n行,两层for循环,判断i,j行内都包含在里面的最大累加和;
循环内部压缩数组,用(子数组的最大连续累加和)方法;
数据交换(异或 )
int a = 1;
int b = 4;
a = a^b;
b = a^b;
a = a^b;
向上取整
向上取整:+1再除以2
位运算
位运算:
得到num二进制位:假设32位,则 for(move=31;move》=0;move-- ){ 第i位=num>>move & 1}
相加:a+b= a | b
得到n最右侧的1
int rightone = n & (~n + 1)
>>,>>>区别,对负数有区别
技巧1(+号表示互斥条件的if,else)
两个有符号数较大的,不能比较:互斥条件用加法相加的形式能实现if,else,可以用+号表示互斥条件的if,else
拿到数最右侧的1:
判断一个数是不是2的幂(二进制只有一个1),拿到最右侧的1,和原数比较是否相等;或者x & (x -1) == 0
判断一个数是不是4的幂:先判断只有一个1,再与上01010101,!= 0,说明是4的幂
不用算术运算符,实现加减乘除:
加:异或(无进位相加)a,与运算作左移一位的进位信息b;结果 = a+b ;继续运算直至进位信息为0
相反数 = 取反+1 = ~n + 1
减:add(a,相反数b)=add(a,add(~b,1))
乘:模拟二进制的乘法;
除:a/b,b左移动但不超过a,减去移动后的值,记录移动位数,剪掉剩下的值作为a,继续这个操作;
异或
异或:无进位相加
x ^ x = 0; (题目,一些数,只有一个数出现了奇数次,其他都是偶数次)
x^0 = x;
x ^ y =z,则x = y^z
数组中arr[i到j]的异或和 = arr[0到i-1] ^ arr[0到j]
拿金币问题,数组中所有数异或,结果为0则必败;
大数据技巧(大事化小(hash分块);位图;范围的区间)
多循环几次,每次只处理这个一个小区间的数
对数器
比较器
分数
a/b,求最大公约数c,然后a/c +“/” + b/c
最大公约数
public static long max(long m,long n){
return n == 0 ? m : max(n, m % n);
}