小黄做的算法题

算法题中的一些tip:
特殊字符:遇到需要根据特殊字符切分或者输出,如‘|',需要用两个左斜杠’\\'进行转义
数值溢出:有些题目要求输出整数,如果返回值类型用int,那么很可能数值溢出,考虑用long
遇到树问题:一般是分成左右子树(分治),然后做递归处理
不用加减符号做运算:那就是用位运算来操作,无进位对应异或^,有进位对应与&。
不用循环、判断语句、乘法符号做乘法:一般使用递归+短路原理
动态边界:对于遍历矩阵、数值这些问题,一般需要把边界设置成动态
从后往前搞:对于字符串之类的,无论是()还是[],可以先检查右)右],再往前找,就很nice。对于挪移位置,可以先往后一步挪移到位,这样就不用没挪一次都要移动大部分。
栈:遇到逆序的,先进后出的,就可以想到栈
二分查找:遇到有序数组,就要想到二分,只有有序二分才有意义
二进制:一般涉及到位运算。
		位运算的奇技淫巧:两数相加无进位,相当于两数做异或^,有进位对应与&。
		一个数n与1做&,可以判断n最后一位是否为1
位运算的规律:
		重复的数异或会抵消掉,相当于这个数没参与异或,不会影响结果。如: 101^010^101 = 010,无论重复的数在那个位置,都会抵消。
HashSet存储(数组、坐标):遇到HashSet需要存储非单一元素,如坐标时,可以使用位运算或拼接字符串的方式,将多个元素转成一个元素。例如:a = 5,b = 2,就可以构造c = a<<3|b, (解释:a向左移动三位,在和b做或运算,得到的c是独一无二的,Hash值不会重复。)  拼接字符串就简单了,调用String.valueOf(),将数转成字符串再拼接,中间加个隔离符 (如“,”)。
倒数第K个:可以用双指针,第一个指针先走k,后面第一个和第二个指针一起走,当第一个指针走完的时候,第二个指针刚好到倒数第K个。
单调栈:
前缀和:
滑动窗口:

思想:

做一道题,首先想:用什么数据结构,然后再想,用什么算法。
一般一道题有三种情况:
1、用到特殊的数据结构
2、用到特殊的算法
3、用到特殊的额数据结构和算法。
这也就印证了那句,程序是由数据结构和算法组成的。
虽然java里没有指针,但是写算法里要融入指针的思想。

如何写算法:

  • 由简单到复杂
    验证一步走一步
    多打印中间结果(数据科学家的习惯)
    -先局部后整体
    没有思路时先部分(先实现某一个小功能)
    -先粗糙后精细
    变量更名
    语句合并
    边界处理

估计时间复杂度

看执行次数最多的语句

排序算法的比较

冒泡:
基本不用,太慢了
选择:
基本不用,不稳
插入:
样本小且基本有序的时候效率比较高(思想:打扑克牌的时候,新摸的牌插入到手牌中)
希尔:
插入排序的改进版,更块,但是不稳定。思想就是按照一定间隔划分数组,从每个数组取数组成一个新数组,对新数组做插入排序后放回原数组,所有新数组都排序一遍后,缩小间隔,直到间隔为1,择排序完成。主要的有点在,大间隔的时候移动快,小间隔的时候移动少。

1\树的深度

## 题目描述:
输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
## 题解
给出一颗二叉树,求树的最大深度,也就是从根节点到所有叶子节点中的最大值

方法一:

分治法简介:求一个规模为n的问题,先求左边规模大约为n/2的问题,再求右边规模大约为n/2的问题,然后合并左边,右边的解,从而求得最终解。具体可参考归并排序。
步骤:

 1. 求 pro(left, rigth) -> int
 2. 先求pro(left, (left+right)/2) -> lval
 3. 再求pro((left+right)/2 + 1, right) -> rval
 4. merge(lval, rval) -> result

这里以本题为具体例子:
函数是求二叉树的最大深度,我们不必管函数具体是怎么实现的。
所以最终结果为 max( 头结点左子树的最大深度, 头结点右子树的最大深度)+1

 1. 求TreeDepth(TreeNode* pRoot)->int
 2. 先求 TreeDepth(pRoot->left) ->lval
 3. 再求TreeDepth(pRoot->right) ->rval
 4. return max(lval, rval) + 1 

## 代码(递归思想)

public class Solution {
    public int TreeDepth(TreeNode root) {
        if(root == null)return 0;
        int leftDepth = TreeDepth(root.left);
        int rightDepth = TreeDepth(root.right);
        int result = 1 + ((leftDepth > rightDepth)?leftDepth:rightDepth);
        return result;
    }
}

2\不用加减乘除做加法(位运算)

## 题目描述
写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
## 题解:
位运算
## 方法:
知识补充:按位与&,按位或|, 按位异或^
小黄做的算法题_第1张图片 补码
计算机中存整数n是用补码存的。
如果n为正数,则原码=反码=补码
如果n为负数,则补码=反码+1
本题是考察对位运算的运用,使用位运算来实现两数的加法。
设两数字的二进制形式 a,b ,其求和 s = a + b ,a(i) 代表 a 的二进制第 i 位,则分为以下四种情况:
小黄做的算法题_第2张图片观察发现,无进位和运算就是按位异或结果,进位就是与运算结果但是需要左移一位,因为进位影响下一位的运算。所以s = a + b,其实就是无进位和+进位的结果。
算法步骤:

  1. 计算a和b的无进位和,和进位
  2. 如果进位不为0,则说明a+b的结果等于无进位和+进位,此时,把无进位和作为a,进位作为b,继续计算
  3. 如果进位等于0, 说明此时a+b的结果就等于无进位和,返回无进位和即可。
    小黄做的算法题_第3张图片Q:你可能有疑问,如果是一个数为负数或者两个数都为负数怎么办?
    A:上述补码的介绍,补码就是解决减法的问题,计算机把减法看做加法来运算。
    所以代码如下:
public class Solution {
    public int Add(int num1,int num2) {
        int result = 0;
        int carry = 0;
        do{
            result = num1 ^ num2;       //不带进位的加法
            carry = (num1 & num2) << 1; //进位
            num1 = result;  
            num2 = carry;   
        }while(carry != 0); // 进位不为0则继续执行加法处理进位
        return result;
    }
}

3\二叉树的镜像(递归)

## 题目描述
操作给定的二叉树,将其变换为源二叉树的镜像。
小黄做的算法题_第4张图片

## 题解
简单题,用递归完事。找出终止条件,迭代体
## 代码

public class Solution {
    public void Mirror(TreeNode root) {
        if(root==null){
            return;
        }
        TreeNode temp = root.left;
        root.left = root.right;
        root.right = temp;
        Mirror(root.left);
        Mirror(root.right);
    }
}

4\顺时针打印矩阵(动态矩阵边界)

## 题目描述
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
小黄做的算法题_第5张图片
## 题解
简单来说,就是不断地收缩矩阵的边界
定义四个变量代表范围,up、down、left、right

  • 向右走存入整行的值,当存入后,该行再也不会被遍历,代表上边界的 up 加一,同时判断是否和代表下边界的 down 交错
  • 向下走存入整列的值,当存入后,该列再也不会被遍历,代表右边界的 right 减一,同时判断是否和代表左边界的 left 交错
  • 向左走存入整行的值,当存入后,该行再也不会被遍历,代表下边界的 down 减一,同时判断是否和代表上边界的 up 交错
  • 向上走存入整列的值,当存入后,该列再也不会被遍历,代表左边界的 left 加一,同时判断是否和代表右边界的 right 交错
    ## 代码
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> printMatrix(int [][] matrix) {
        ArrayList<Integer> list = new ArrayList<>();
        if(matrix == null || matrix.length == 0 || matrix[0].length == 0){
            return list;
        }
        int up = 0;
        int down = matrix.length-1;
        int left = 0;
        int right = matrix[0].length-1;
        while(true){
            // 最上面一行
            for(int col=left;col<=right;col++){
                list.add(matrix[up][col]);
            }
            // 向下逼近
            up++;
            // 判断是否越界
            if(up > down){
                break;
            }
            // 最右边一行
            for(int row=up;row<=down;row++){
                list.add(matrix[row][right]);
            }
            // 向左逼近
            right--;
            // 判断是否越界
            if(left > right){
                break;
            }
            // 最下面一行
            for(int col=right;col>=left;col--){
                list.add(matrix[down][col]);
            }
            // 向上逼近
            down--;
            // 判断是否越界
            if(up > down){
                break;
            }
            // 最左边一行
            for(int row=down;row>=up;row--){
                list.add(matrix[row][left]);
            }
            // 向右逼近
            left++;
            // 判断是否越界
            if(left > right){
                break;
            }
        }
        return list;
    }
}

5\求1+2+3+…+n(“短路求值原理”来做为递归的结束条件)

题目描述
求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
方法
递归需要用到if,所以如何避开if来做判断?
if (n== 1) return 1;
也就是说如果n==1,需要终止递归,所以我们想到了逻辑与&&连接符。
A&&B,表示如果A成立则执行B,否则如果A不成立,不用执行B
因此我们可以这样。在n>1的时候,执行递归函数。

public class Solution {
    public int Sum_Solution(int n) {
        int sum = n;
        boolean stop = (sum!=0) && ((sum += Sum_Solution(n-1))!=0);
        return sum;
    }
}

6\构建乘积数组

题目描述

给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]A[1]…*A[i-1]A[i+1]…*A[n-1]。不能使用除法。(注意:规定B[0] = A[1] * A[2] * … * A[n-1],B[n-1] = A[0] * A[1] * … * A[n-2];)
对于A长度为1的情况,B无意义,故而无法构建,因此该情况不会存在。
我用了最简单的方法时间复杂度O(N2),空间复杂度O(1)

import java.util.ArrayList;
public class Solution {
    public int[] multiply(int[] A) {
        int[] B = new int[A.length];
        for(int i=0;i<A.length;i++){
            B[i] = 1;
            for(int j=0;j<A.length;j++){
                if(i!=j){
                    B[i] *= A[j];
                }
            }
        }
        return B;
    }
}

牛客官方用了时间复杂度O(N)的方法,空间复杂度O(N)

根据题目描述,如果可以使用除法,就很简单。但是要求不能使用。

假设:
left[i] = A[0]*...*A[i-1]
right[i] = A[i+1]*...*A[n-1]
所以:
B[i] = left[i] * right[i]

这样就避免使用了除法。但是如果对每个B[i], 0<=i<n,都这么求,显然时间复杂度太高。

我们把整个结果画到下面图:
小黄做的算法题_第6张图片

可知:
left[i+1] = A[0]*...A[i-1]*A[i]
right[i+1] = A{i+2]*...*A[n-1]

于是,
left[i+1] = left[i] * A[i]
right[i] = right[i+1] * A[i+1]

所以,我们可以先把所有的left[i]求出,right[i]求出。

代码如下:

class Solution {
public:
    vector<int> multiply(const vector<int>& A) {
        vector<int> B(A.size(), 1);
        for (int i=1; i<A.size(); ++i) {
            B[i] = B[i-1] * A[i-1]; // left[i]用B[i]代替
        }
        int tmp = 1;
        for (int j=A.size()-2; j>=0; --j) {
            tmp *= A[j+1]; // right[i]用tmp代替
            B[j] *= tmp;
        }
        return B;
    }
};

7\替换空格

题目描述
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

思路

  1. 最简单和最自然的想法是两个for循环,第一个for监视是否有空格,第二个for把空格后面的字符往后移动。这样的时间复杂度是O(N2),空间复杂度O(1)写出来是拿不到offer的。
  2. 维护一个数组,遇到空格append(%20).空间复杂度O(N),时间复杂度O(N)
public class Solution {
    public String replaceSpace(StringBuffer str) {
    	StringBuffer sb = new StringBuffer();
        for(int i=0;i<str.length();i++){
            if(str.charAt(i) == ' '){
                sb.append("%20");
            }else{
                sb.append(str.charAt(i));
            }
        }
        return sb.toString();
    }
}
  1. 在当前字符串上进行替换。先计算替换后的字符串需要多大的空间,并对原字符串空间进行扩容;从后往前替换字符串的话,每个字符串只需要移动一次;如果从前往后,每个字符串需要多次移动,效率较低。 时间复杂度O(N),空间复杂度O(1).
public class Solution {
    public String replaceSpace(StringBuffer str) {
        int spacenum = 0;
        for(int i = 0; i < str.length(); i++){
            if(str.charAt(i) == ' '){
                spacenum++;
            }
        }
        int oldLength = str.length();
        int oldIndex = oldLength - 1;
        int newLength = oldLength + spacenum*2;
        str.setLength(newLength);
        int newIndex = newLength - 1;
        for(; oldIndex >= 0 && oldLength < newLength; oldIndex--){
            if(str.charAt(oldIndex) == ' '){
                str.setCharAt(newIndex--, '0');
                str.setCharAt(newIndex--, '2');
                str.setCharAt(newIndex--, '%');
            }else{
                str.setCharAt(newIndex--, str.charAt(oldIndex)); //attention:这里 -- 写在后面,先执行表达式后减减
            }
        }
        return str.toString();
    }
}

8\从尾到头打印链表

题目描述
输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

思路

  1. listNode 是链表,只能从头遍历到尾,但是输出却要求从尾到头,这是典型的"先进后出",我们可以想到栈!
    ArrayList 中有个方法是 add(index,value),可以指定 index 位置插入 value 值
    所以我们在遍历 listNode 的同时将每个遇到的值插入到 list 的 0 位置,最后输出 listNode 即可得到逆序链表

时间复杂度:O(n^2) 由于add(index,value),每执行一次都要后移所有数据
空间复杂度:O(n)

import java.util.*;
public class Solution {
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        ArrayList<Integer> list = new ArrayList<>();
        ListNode tmp = listNode;
        while(tmp!=null){
            list.add(0,tmp.val);
            tmp = tmp.next;
        }
        return list;
    }
}

使用Stack对象

public ArrayList printListFromTailToHead(ListNode listNode) {
    ArrayList list = new ArrayList();
    Stack stack = new Stack();
    while (listNode != null) {
        stack.push(listNode.val);
        listNode = listNode.next;
    }
    while (!stack.empty()) {
        list.add(stack.pop());
    }
    return list;
}

时间复杂度:O(n)
空间复杂度:O(n)

  1. 既然非递归都实现了,那么我们也可以利用递归,借助系统的"栈"帮忙打印(递归本质上就是一个栈结构)

时间复杂度:O(n)
空间复杂度:O(n)

但是极有可能栈溢出
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

import java.util.ArrayList;
public class Solution {
    ArrayList<Integer> list = new ArrayList<>();
    public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
        while(listNode!=null){
            printListFromTailToHead(listNode.next);
            list.add(listNode.val);
        }
        return list;
    }
}

9\重建二叉树

题目描述
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

1. 分析

根据中序遍历和前序遍历可以确定二叉树,具体过程为:

  1. 根据前序序列第一个结点确定根结点
  2. 根据根结点在中序序列中的位置分割出左右两个子序列
  3. 对左子树和右子树分别递归使用同样的方法继续分解

例如:
前序序列{1,2,4,7,3,5,6,8} = pre
中序序列{4,7,2,1,5,3,8,6} = in

  1. 根据当前前序序列的第一个结点确定根结点,为 1
  2. 找到 1 在中序遍历序列中的位置,为 in[3]
  3. 切割左右子树,则 in[3] 前面的为左子树, in[3] 后面的为右子树
  4. 则切割后的左子树前序序列为:{2,4,7},切割后的左子树中序序列为:{4,7,2};切割后的右子树前序序列为:{3,5,6,8},切割后的右子树中序序列为:{5,3,8,6}
  5. 对子树分别使用同样的方法分解
/**
 * Definition for binary tree
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
import java.util.Arrays;
public class Solution {
    public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
        //终止条件
        if(pre.length == 0||in.length == 0){
            return null;
        }
        
        TreeNode root = new TreeNode(pre[0]);  //赋值只是这条语句起了作用
        //在中序中查找前序的根  迭代体
        for(int i=0; i<in.length; i++){
            if(in[i] == pre[0]){
                //左子树,注意copyOfRange函数,左闭右开
                root.left = reConstructBinaryTree(Arrays.copyOfRange(pre, 1, i+1), Arrays.copyOfRange(in, 0, i));
                root.right = reConstructBinaryTree(Arrays.copyOfRange(pre, i+1, pre.length), Arrays.copyOfRange(in, i+1, in.length));
            }
        }
        return root;
    }
}

10\用两个桟实现队列

题目描述
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

1. 分析

队列的特性是:“先入先出”,栈的特性是:“先入后出”

当我们向模拟的队列插入数 a,b,c 时,假设插入的是 stack1,此时的栈情况为:

栈 stack1:{a,b,c}
栈 stack2:{} 

当需要弹出一个数,根据队列的"先进先出"原则,a 先进入,则 a 应该先弹出。但是此时 a 在 stack1 的最下面,将 stack1 中全部元素逐个弹出压入 stack2,现在可以正确的从 stack2 中弹出 a,此时的栈情况为:

栈 stack1:{}
栈 stack2:{c,b} 

继续弹出一个数,b 比 c 先进入"队列",b 弹出,注意此时 b 在 stack2 的栈顶,可直接弹出,此时的栈情况为:

栈 stack1:{}
栈 stack2:{c} 

此时向模拟队列插入一个数 d,还是插入 stack1,此时的栈情况为:

栈 stack1:{d}
栈 stack2:{c} 

弹出一个数,c 比 d 先进入,c 弹出,注意此时 c 在 stack2 的栈顶,可直接弹出,此时的栈情况为:

栈 stack1:{d}
栈 stack2:{c} 

根据上述栗子可得出结论:

当插入时,直接插入 stack1
当弹出时,当 stack2 不为空,弹出 stack2 栈顶元素,如果 stack2 为空,将 stack1 中的全部数逐个出栈入栈 stack2,再弹出 stack2 栈顶元素 
import java.util.Stack;

public class Solution {
    Stack<Integer> stack1 = new Stack<Integer>();
    Stack<Integer> stack2 = new Stack<Integer>();
    
    public void push(int node) {
        stack1.add(node);
        
    }
    
    public int pop() {
        if(stack2.size()!=0){
            return stack2.pop();
            
        }
        while(stack1.size()!=0){
            int a = stack1.pop();
            stack2.add(a);
        }
        return stack2.pop();
    }
}

11\选择数组的最小数字

题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

暴力解法

import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        int account=array[0];
        for(int i=1;i<array.length;i++){
            if(array[i]<account){
                account = array[i];
            }
        }
        return account;
    }
}

O(logn)的解法
二分查找的思想。

import java.util.ArrayList;
public class Solution {
    public int minNumberInRotateArray(int [] array) {
        int p1 = 0;
        int p3 = array.length-1;
        int p2 = p3;
        while((p3-p1)!=1){
            p2 = (p1+p3)/2;
            if(array[p1]<=array[p2]){
                p1=p2;
            }else if(array[p2]<=array[p3]){
                p3 = p2;
            }
        }
        p2=p3;
        
        return array[p2];
    }
}

12\二进制中1的个数

题目描述
输入一个整数,输出该数32位二进制表示中1的个数。其中负数用补码表示。

示例1

输入
10
返回值
2

分析:
首先明确正整数和负整数在计算机中的存储方式。
知识补充:
1)int类型的话: 十进制5,二进制是00000000 00000000 00000000 00000101,十进制-5,二进制是11111111 11111111 11111111 11111011(反码、补码的知识)。
2)正整数向右移位 >>1,高位补零;负整数向右移位 >>1,高位补1;
解题思路
用1与n做与运算。结果若为1,说明n的最低为是1;然后计数器count++,同时n>>1。
然后循环计算。
考虑到负整数向右移位 >>1,高位补1,所以循环的终止条件应该是遍历一遍,int类型32位,所以循环次数是32.
不能用while(n>0)的条件来判断是负数会陷入死循环。

solution

public class Solution {
    public static int NumberOf1(int n) {
        int count = 0;
        
        for (int i=0;i<32;i++){
            if((n&1)==1){
                count++;
            }
            n = n>>1;
        }
        return count;
    }
}

拓展

这道题的解题思想也可以用来解决下列问题:

  1. 用一条语句判断一个整数是不是2的整数次方。(2的整数次方的整数只能有一个1。可能要考虑下符号位?)
  2. 输入两个整数m和n,计算需要改变m的二进制表示中的多少位才能得到n?(m和n取异或,再统计异或结果中1的个数。)
  3. 把一个整数减去1之后再和原来的的整数做位与运算,得到的结果相当于是把整数的二进制表示中的最右边的一个1变成0.

13\数值的整数次方

题目描述
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

保证base和exponent不同时为0

示例1

输入
2,3

返回值
8.00000

solution
考虑指数是否为负数,如果是负数就要把运算结果做倒数。如果涉及到倒数,就要考虑基数是否为零。

public class Solution {
    public double Power(double base, int exponent) {
        if(base == 0.0){
            return 0.0;
        }
        double result = 1.0d;
        int e = exponent>0?exponent:-exponent;
        for(int i=1;i<=e;i++){
            result *= base;
        }
        return exponent>0?result:1/result;
  }
}

14\删除链表中重复的节点

题目描述
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

示例

输入
{1,2,3,3,4,4,5}

返回值
{1,2,5}

分析
多次遍历,第一次遍历把重复的结点值存入 set 容器,第二次遍历,当结点值存储在 set 容器中,就删除该结点

/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
import java.util.*;
public class Solution {
    public ListNode deleteDuplication(ListNode pHead){

        //如果是null直接返回null
        if(pHead==null){
            return null;
        }
        // 先找出相同结点,存入 set
        HashSet<Integer> set = new HashSet<>();
        ListNode pre = pHead;
        ListNode cur = pHead.next;
        while(cur!=null){
            if(pre.val==cur.val){
            set.add(cur.val);
            }
            pre = cur;
            cur = cur.next;
        }
        
        // 再根据相同节点删除
        // 先删头部{1,1,1,1,1,1,1}的情况  这里加上 pHead!=null放在
        //判断前面是因为如果pHead变成null了后面的pHead.val会报空指针异常
        while(pHead !=null && set.contains(pHead.val)){
            pHead = pHead.next;
        }
        //执行完上面的语句之后可能会出现null{},所以要再判断一下
        if(pHead==null){
            return null;
        }
        // 再删中间结点
        pre = pHead;
        cur = pHead.next;
        while(cur!=null){
            if(set.contains(cur.val)){
                pre.next = cur.next;
                cur = cur.next;
            }else{
                pre = cur;
                cur = cur.next;
            }
            
        }
        return pHead;
    }
}

15\Java 连续子数组的最

链接

16、反转链表

新建两个链表节点,分别是pre post。
head节点代表当前节点。
post记录head.next使得原有链表不要断开。
pre赋值给head.next使得链表反转。

public class Solution {
    public ListNode ReverseList(ListNode head) {
         // 判断链表为空或长度为1的情况
        if(head == null || head.next == null){
            return head;
        }
        ListNode pre = null; // 当前节点的前一个节点
        ListNode next = null; // 当前节点的下一个节点
        while( head != null){
            next = head.next; // 记录当前节点的下一个节点位置;
            head.next = pre; // 让当前节点指向前一个节点位置,完成反转
            pre = head; // pre 往右走
            head = next;// 当前节点往右继续走
        }
        return pre;
    }
}

17\ 背包问题

背包问题是什么?

import java.util.Arrays;
import java.util.Scanner;

public class PackageProblem {

    /**
     * 链接:https://www.nowcoder.com/questionTerminal/dee7a45562324d9a8e2bc7f0e4465bfc
     * [编程题]风电场风机发电调度问题
     * 

* 某风电场每台风机的发电量和距离升压站的距离各不相同, *

* 如风机1:发电量30,距离20;风机2:发电量35,距离25;风机3:发电量25,距离18……, *

* 要求在输电总距离限定(如小于100)的前提下,选择风机向升压站输电,使得输送的电量最大。 * * 输入 * 30 20 35 40 * 20 18 25 30 * 50 * * 输出 * 38 */ public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int[] dis = Arrays.stream(scanner.nextLine().split(" ")).mapToInt(Integer::valueOf).toArray(); int[] power = Arrays.stream(scanner.nextLine().split(" ")).mapToInt(Integer::valueOf).toArray(); int maxDis = scanner.nextInt(); System.out.println(theMax(power,dis,maxDis)); } public static int theMax(int[] power, int[] dis, int maxDis){ int[][] table = new int[dis.length][maxDis+1]; for (int i = 0; i < dis.length; i++) { int iPower = power[i]; int iDis = dis[i]; for (int j = 0; j <= maxDis; j++) { if (j>=iDis){ if (i==0){ table[i][j] = iPower; }else { table[i][j] = Integer.max(table[i-1][j],table[i-1][j-iDis]+iPower); } }else { if (i>0){ table[i][j]=table[i-1][j]; } } } } return table[table.length-1][maxDis]; } }

总结一下,背包问题的解法就是构建一个表。
小黄做的算法题_第7张图片两个循环:

for (int i = 0; i < dis.length; i++) {
            for (int j = 0; j <= maxDis; j++) {

大循环更新行,小循环更新列。每次for填一个值。这个值就是table[i][j].

table[i][j]的更新公式。
j j>=w(i) V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}

18\ 找到 K 个最接近的元素

给定一个排序好的数组,两个整数 k 和 x,从数组中找到最靠近 x(两数之差最小)的 k 个数。
返回的结果必须要是按升序排好的。
如果有两个数与 x 的差值一样,优先选择数值较小的那个数。

输入描述:

第一行为排序好的数组arr
第二行为查找的个数k
第三行为基准值x

输出描述:

按升序排好的的数组

示例1
输入

1,2,3,4,5
4
3

输出

1,2,3,4

说明

k 的值为正数,且总是小于给定排序数组的长度
数组不为空,且长度不超过 104
数组里的每个元素与 x 的绝对值不超过 104

import java.util.Arrays;
import java.util.Scanner;
import java.util.function.IntPredicate;

public class Main {
	public static void main(String[] args){
		Scanner input = new Scanner(System.in);
		String s = input.nextLine();
		String[] str = s.split(",");
		int[] array = new int[str.length];
		for(int i=0;i<str.length;i++){
			array[i]= Integer.parseInt(str[i]);
		}
		
		int k = input.nextInt();
		int x = input.nextInt();
		
		int min=10000;
		int position = 0;
		
		for(int i=0;i<=array.length-k;i++){
			int sum=0;
			for(int j=0;j<k;j++){
				sum += Math.abs(array[i+j]-x);
			}
			//新的sum只有比上一次的min小才会更新position,保证了 条件:如果有两个数与 x 的差值一样,优先选择数值较小的那个数。
			if(sum < min){
				min = sum;
				position=i;
			}
		}
		int[] array2 = Arrays.copyOfRange(array, position,position+k);
		for(int i=0;i<array2.length;i++){
			System.out.print(array2[i]);
			if(i!=array2.length-1){
				System.out.print(",");
			}
		}
	}
}

总结,这里的核心思想是使用滑动窗口。
窗口大小就是查找的个数k。计算窗口内的值和基准值x的差值和,找到最小的那个窗口。把里面的数取出来就是我们想要的了。

19、数组中重复的数据

给定一个整数数组 a,其中1 ≤ a[i] ≤ n (n为数组长度), 其中有些元素出现两次而其他元素出现一次。

找到所有出现两次的元素。

你可以不用到任何额外空间并在O(n)时间复杂度内解决这个问题吗?
示例:

输入:
[4,3,2,7,8,2,3,1]

输出:
[2,3]

这道题首先的限制是不使用任何额外空间,如果可以使用的话,直接用一个set将他们存起来便可以了

那么在不使用额外空间的情况下,我们只能在数组上直接操作了,有以下方法可以对数组进行操作:

1.元素归位法

2.取余法

3.取负法

在这道题中我们使用的是元素归位法

就是将n个元素交换到它应该在的位置。例如,元素5就放到位置4(下标从0开始)。

当然这里可能有人要发问,如果数组中的数字比数组的长度还大呢,刚好原题给出了限制

其中1 ≤ a[i] ≤ n (n为数组长度)

那么我们便是判断nums[i]与nums[nums[i]-1]是否相等,如若不相等的话我们就将nums[nums[i]-1]放到nums[i]的位置上

即while(i

即for(int j = 0; j if(nums[j]!=j+1){将这个重复的数字保留起来}}

代码如下
小黄做的算法题_第8张图片

20、360笔试-抽奖概率

A和B两个人在抽奖。现在有一个抽奖箱,里面有n张中奖票,m张不中奖票。A和B轮流从中抽一张奖票出来。如果有人抽到中奖票就结束,抽到中奖票的人胜利。抽过的奖票会被丢弃。额外的,B每次抽后,会再次抽取一张票并丢弃掉(这张票中奖不算B胜利)。现在,A先抽,请问A的胜率,保留4位小数后输出。如果两人到最后也没有抽到中奖票算作B胜利

输入
输入两个数字n,m,代表中奖票和不中奖票的数量 (0≤n,m≤1000)

输出
输出A的胜率,保留4位小数。

样例输入
2 3
样例输出
0.6000

提示
样例输入2
1 3
样例输出2
0.5000
样例二解释:
如果A第一轮抽到中奖票,A胜利,概率0.25。
如果A第二轮抽到中奖票,情况为A第一轮没有抽到中奖票,B也没有抽到中奖票,并且B丢弃掉的奖票也不是中奖票。概率为3/4 * 2/3 * 1/2=0.25
综上,中奖率为0.5。

分析:动态规划、递归

假设当前剩余中奖票数n,不中奖票数m
每一轮A开始抽时,概率为(上一轮没抽中概率)*n/(n+m)
即问题分解为:
1.计算上一轮没抽中概 率(计算本轮没抽中概率,传入下一轮递归函数中)
2.计算本轮当前中奖概率
其他可知条件:
1.当前n为0时,结束递归;
2.当前m<3时,结束递归;(每轮抽奖消耗三张票,m<3时B必赢)
3.B丢弃的票有两种情况,每次递归要调用两次函数
# 总的概率
totalRate = 0
# 参数分别是:中奖票数,不中奖票数,本轮概率
def luckDraw(n,m,rate):
    # 累计本次抽中的概率
    global totalRate
    totalRate += rate*n/(n+m)
    if n == 0 or m < 3:
        return 0
    # 两个都没抽中概率
    failRate = (m/(n+m))*((m-1)/(n+m-1))
    # 丢掉m的概率
    loseM = (m-2)/(m+n-2)
    # 丢掉n的概率
    loseN = n/(m+n-2)

    luckDraw(n-1,m-2,failRate*loseN*rate)
    luckDraw(n,m-3,failRate*loseM*rate)

# 第一轮,第三个参数为1100%
luckDraw(5,3,1)
print("%1.4f" % totalRate)

21、获取两个字符串的最长相同子串

暴力解
把小的字符串 所有可能组合放到一个列表
然后挨个判断是否包含于长的字符串中

/*
获取两个字符串中最大相同子串。枚举出长度较短的字符串的所有子串。将子串放在字符串数组中。
通过循环判断长度较长的字符串中是否包含字符串数组中的元素,并返回包含且最长的子字符串。
*/
 
class StringTest4 
{
	public static void main(String[] args) 
	{
		string_sub("asddghjukihjughjkssfghyj","adhasjkssdghjuoop");
		System.out.println("Hello World!");
	}
 
	public static void string_sub(String str1,String str2)
	{
		if(str1.length()>str2.length())
			sop(deal(str2,str1));
		else
			sop(deal(str1,str2));
		
	}
 
	public static String deal(String str1,String str2)
	{
		String[] arr=new String[100000000];
		boolean flag=false;
		String max="";
		int k=0;
		for (int j=0;j<str1.length();j++)
		{
			for(int i=j+1;i<=str1.length();i++)
				arr[k++]=str1.substring(j,i);
		}
		
		for(int i=0;i<k;i++)
		{
			if(str2.contains(arr[i]))
			{
				flag=true;
				if(max.length()<arr[i].length())
				{
					max=arr[i];
				}
			}
		}
		return flag?max:"不存在公共子串";
	}
 
	public static void sop(Object obj)
	{
		System.out.println(obj);
	}
}

22、字节2021第五次笔试第一题

一共四题,我两个小时只做了第一题,而且还不能全部ac。
题目:给出一个字符串AC.4562
让你解密,以第一个字符为基础,如果是字母(一定是大写的),则数字和字母都向后移动n位。n是首个字母在26个字母中的位置,如A是1,B是2;如果是数字,则移动相应数字位置。如果超过边界如Z向后移动一位,变成A,9向后移动一位变成0.如果是非大写字母和数字,则只需移动一位。非字母非数字不需要移动。

下面是我写的,比较冗余,优化空间还比较大。
题目核心,就是字符的ASCII码。
关键点

a 的ASCII码 97;
A 的ASCII码 65;
0 的ASCII码 48;
int转char 用强转  int a = 97;   char b = (char)a;
对于超边界问题,用求%
	int local = (dif_char_temp + dif_num)%26+65;
package byteDance;

// 本题为考试多行输入输出规范示例,无需提交,不计分。



import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String str = sc.nextLine();
        String result = "";
        //    int a = 97;
        //   char b = (char)a;
        //   System.out.println(b);

        char c = str.charAt(0);
        int dif_char = c - 'A' + 1;
        int dif_num = c - '0';
        if(c>=65&c<=91){  //如果是大写字母和数字

            for(int i = 0; i < str.length(); i++){
                char temp = str.charAt(i);
                int dif_char_temp = temp - 'A';
                int dif_num_temp = temp - '0';

                //int a = (dif_char_temp + dif_char)%26;
                //char newOne = (char)a;
                //System.out.println(a);
                if(temp>=65&temp<=91){
                    int local = (dif_char_temp + dif_char)%26+65;
                    char theNewChar = (char)local;
                    result += theNewChar;

                }else{
                    if(temp>=48&temp<=58){
                        int local = (dif_num_temp + dif_char)%10+48;
                        char theNewChar = (char)local;
                        result += theNewChar;
                    }else{
                        result += temp;
                    }
                }

            }
        }else{
            if(c>=48&c<58){//如果是数字
                for(int i = 0; i < str.length(); i++){
                    char temp = str.charAt(i);
                    int dif_char_temp = temp - 'A';
                    int dif_num_temp = temp - '0';
                    if(temp>=65&temp<=91){
                        int local = (dif_char_temp + dif_num)%26+65;
                        char theNewChar = (char)local;
                        result += theNewChar;
                    }else{
                        if(temp>=48&temp<=58){
                            int local = (dif_num_temp + dif_num)%10+48;
                            char theNewChar = (char)local;
                            result += theNewChar;
                        }else{
                            result += temp;
                        }
                    }
                }
            }else{//两者都不是
                for(int i = 0; i < str.length(); i++){
                    char temp = str.charAt(i);
                    int dif_char_temp = temp - 'A';
                    int dif_num_temp = temp - '0';

                    if(temp>=65&temp<=91){
                        int local = (dif_char_temp + 1)%26+65;
                        char theNewChar = (char)local;
                        result += theNewChar;
                    }else{
                        if(temp>=48&temp<=58){
                            int local = (dif_num_temp + 1)%10+48;
                            char theNewChar = (char)local;
                            result += theNewChar;
                        }else{
                            result += temp;
                        }
                    }
                }

            }

        }
        System.out.println(result);
    }
}

23、实现Trie树/字典树/前缀树

package Leecode.Trie;
//实现字典树
public class Trie {
    private Trie[] children;
    private boolean isEnd;

    public Trie() {
        children = new Trie[26]; //二十六个字母  直接用索引代替字符 操作真骚
        isEnd = false;
    }

    public void insert(String word) {
        Trie node = this;
        for (int i = 0; i < word.length(); i++) {
            char ch = word.charAt(i);
            int index = ch - 'a';
            if (node.children[index] == null) {
                node.children[index] = new Trie();
            }
            node = node.children[index];  //迭代 指向旧节点的指针指向新节点
        }
        node.isEnd = true;
    }

    public boolean search(String word) {
        Trie node = searchPrefix(word);
        return node != null && node.isEnd;
    }

    public boolean startsWith(String prefix) {
        return searchPrefix(prefix) != null;
    }

    private Trie searchPrefix(String prefix) {
        Trie node = this;
        for (int i = 0; i < prefix.length(); i++) {
            char ch = prefix.charAt(i);
            int index = ch - 'a';
            if (node.children[index] == null) {
                return null;
            }
            node = node.children[index];//迭代 指向旧节点的指针指向新节点
        }
        return node;
    }
}

24、LCP 03. 机器人大冒险Leecode

小黄做的算法题_第9张图片解法:核心是用hashset判断是否冲突。
关键:有时间要求,所以不能把路径从零走到终点,要计算循环次数,由第一次循环推断出后面的循环走的路径,这样才不会超时间。
小黄做的算法题_第10张图片

public class Solution {
    public boolean robot(String command, int[][] obstacles, int x, int y) {


        //用set存储第一轮内走过的坐标
        HashSet<String> hashSet = new HashSet<>();
        int countU = 0;
        int countR = 0;
        hashSet.add(String.valueOf(0) +","+ String.valueOf(0));
        for (int i = 0; i < command.length(); i++) {
            char c = command.charAt(i);
            if (c=='U'){
                countU++;
            }else {
                countR++;
            }
            hashSet.add(String.valueOf(countU) +","+ String.valueOf(countR));
        }
        int loop_times = Math.min(y/countU,x/countR);//取最小那个是因为在最小轮数内不能满足,大轮数也一定不满足,计算走到终点需要多少轮
        
        //先判断终点在不在set里面,在里面不一定成拱,不在里面一定不成功
        if (!hashSet.contains(String.valueOf(y-loop_times*countU) +","+ String.valueOf(x-loop_times*countR))){
            return false;
        }

        for (int[] obstacle :
                obstacles) {
            if (obstacle.length != 2) {//有可能输入的障碍物是空的
                continue;
            }
            if (obstacle[0]>x||obstacle[1]>y){//障碍物超过终点,直接继续
                continue;
            }
            int ob_loop_times = Math.min(obstacle[0]/countR,obstacle[1]/countU);
            if (hashSet.contains(String.valueOf(obstacle[1]-ob_loop_times*countU) +","+ String.valueOf(obstacle[0]-ob_loop_times*countR))){
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int[][] obstacles= {{4,2}};
//        int[][] obstacles= {{7,7},{0,5},{2,7},{8,6},{8,7},{6,5},{4,4},{0,3},{3,6},};
        boolean urr = solution.robot("URR", obstacles, 3, 2);
        System.out.println(urr);
    }
}

25、旋转词

判断str1是否为str2的旋转词。
旋转词:”123456“ 和”456123“互为旋转词
思想:将str1拼接成两倍及‘123456123456’,判段‘456123’是否为str1的字串即可。
成功将该问题转换为字串问题,查到字串可以用str.indexOf()这个API,底层使用的是改进的KMP算法。
最经典的算法用KMP算法.

26、判断二叉树small是不是二叉树big的子树

思想:把两个二叉树都前序列化成字符串,然后又可以转换为KMP算法判断字串了。

27、 链表中倒数最后k个结点

题目: 输入一个链表,输出一个链表,该输出链表包含原链表中从倒数第k个结点至尾节点的全部节点。
如果该链表长度小于k,请返回一个长度为 0 的链表。
思路:1、计算链表的长度
2、倒数第k个就是 正常顺序 链表长度-k(从零算起)
3、找到正常顺序的那个节点 返回就好了

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param pHead ListNode类 
     * @param k int整型 
     * @return ListNode类
     */
    public ListNode FindKthToTail (ListNode pHead, int k) {
        // write code here
        ListNode countNode = pHead;
        int count = 0;
        while(countNode!=null){
            count++;
            countNode = countNode.next;
        }
        
        if(count

评论区的大神做法:,双指针求解 这题要求链表的倒数第k个节点,最简单的方式就是使用两个指针,第一个指针先移动k步,然后第二个指针再从头开始,这个时候这两个指针同时移动,当第一个指针到链表的末尾的时候,返回第二个指针即可。注意,如果第一个指针还没走k步的时候链表就为空了,我们直接返回null即可。

    public ListNode FindKthToTail (ListNode pHead, int k) {
        // write code here
        ListNode first = pHead;
        ListNode second = pHead;
        while(k-->0){
            if(first==null)
                return null;
            first = first.next;
        }
        
       while(first!=null){
           first = first.next;
           second = second.next;
       }
       return second;
    }

28 栈的压入、弹出序列

描述
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
题解:理解题目很重要,一开始很困惑觉得压入顺序是1,2,3,4,5,那弹出顺序肯定是5,4,3,2,1,后来发面原来压入和弹出可以是交叉进行的,就是先压1,2,然后弹出2,再压入3,4,5,那么弹出顺序就是2,5,4,3,1了。
方法:模拟法

直接模拟即可。因为弹出之前的值都会先入栈,所以这里用个栈来辅助。

初始化:用指针i指向pushV的第一个位置, 指针j指向popV的第一个位置
如果pushV[i] != popV[j], 那么应该将pushV[i]放入栈中, ++i
否则,pushV[i]==popV[j], 说明这个元素是放入栈中立马弹出,所以,++i, ++j,然后应该检查popV[j]
与栈顶元素是否相等,如果相等,++j, 并且弹出栈顶元素
4,重复2,3, 如果i==pushV.size(), 说明入栈序列访问完,此时检查栈是否为空,如果为空,说明匹配,斗则不匹配。

如下图:
小黄做的算法题_第11张图片
代码:

public class IsPopOrder {
    public boolean isPopOrder(int[] pushA,int[] popA){
        Stack stack = new Stack<>();
        int i=0,j=0;
        while (i

29、二叉搜索树与双向链表

题目:
注意:
1.要求不能创建任何新的结点,只能调整树中结点指针的指向。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继
2.返回链表中的第一个节点的指针
3.函数返回的TreeNode,有左右指针,其实可以看成一个双向链表的数据结构
4.你不用输出或者处理,示例中输出里面的英文,比如"From left to right are:"这样的,程序会根据你的返回值自动打印输出

示例:
输入: {10,6,14,4,8,12,16}
输出:From left to right are:4,6,8,10,12,14,16;From right to left are:16,14,12,10,8,6,4;
解析:
输入就是一棵二叉树,如上图,输出的时候会将这个双向链表从左到右输出,以及
从右到左输出,确保答案的正确

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。如下图所示
小黄做的算法题_第12张图片思路:用一个列表存储中序遍历的树,再遍历一遍更改列表树节点的指针。
代码:

public class JZ26 {
    ArrayList list = new ArrayList<>();
    public TreeNode Convert(TreeNode node) {
        if (node==null){
            return null;
        }
        traversal(node);
        if (list.size()==1){
            return list.get(0);
        }
        for (int i = 0; i < list.size(); i++) {
            if (i==0){
                list.get(i).right = list.get(i+1);
            }else if(i==list.size()-1){
                list.get(i).left = list.get(i-1);
            }else {
                list.get(i).right = list.get(i+1);
                list.get(i).left = list.get(i-1);
            }
        }
        return list.get(0);
    }
    public void traversal(TreeNode root){
        if (root.left!=null){
            traversal(root.left);
        }
        list.add(root);
        if (root.right!=null){
            traversal(root.right);
        }
    }

    public static void main(String[] args) {
        TreeNode tree = CreatBinaryTree.getTree();
        TreeNode node = new JZ26().Convert(tree);
    }
}

30.JZ67 剪绳子

题目:
描述
给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1,m<=n),每段绳子的长度记为k[1],…,k[m]。请问k[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
输入描述:
输入一个数n,意义见题面。(2 <= n <= 60)
返回值描述:
输出答案。
示例1
输入:
8
返回值:
18

思路:

剪绳子后面的数学原理
1、等分的时候乘积最大
2、按等分列出乘积结果的表达式
3、对表达式求导,发现分3段时乘积最大。
4、n平均生成三段的时候不一定整除,需要处理一下。
代码:

    public int cutRope(int target) {
        if(target<=0) return 0;
        if(target==1 || target == 2) return 1;
        if(target==3) return 2;
        int m = target % 3;
        switch(m){
            case 0 :
                return (int) Math.pow(3, target / 3);
            case 1 :
                return (int) Math.pow(3, target / 3 - 1) * 4;
            case 2 :
                return (int) Math.pow(3, target / 3) * 2;
        }
        return 0;
    }

31.JZ32 把数组排成最小的数

描述
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
示例1
输入:
[3,32,321]

返回值:
“321323”

思路:
自定义一种排序规则,排序完按顺序拼接即可。
自定义一个比较大小的函数,比较两个字符串s1, s2大小的时候,先将它们拼接起来,比较s1+s2,和s2+s1那个大,如果s1+s2大,那说明s2应该放前面,所以按这个规则,s2就应该排在s1前面。

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
public class Solution {
    public  String PrintMinNumber(int[] numbers) {
        int n;
        String s = "";
        ArrayList list = new ArrayList();
        n = numbers.length;
        for (int i = 0; i < n; i++) {
            list.add(numbers[i]);

        }
        Collections.sort(list, new Comparator() {

            public int compare(Integer str1, Integer str2) {
                String s1 = str1 + "" + str2;
                String s2 = str2 + "" + str1;
                return s1.compareTo(s2);
            }
        });

        for (int j : list) {
            s += j;
        }
        return s;

    }
}

32.数组中的逆序对

描述
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

对于50%50%50%的数据,size≤104size\leq 10^4size≤104
对于100%100%100%的数据,size≤105size\leq 10^5size≤105

输入描述:
题目保证输入的数组中没有的相同的数字
示例1
输入:

[1,2,3,4,5,6,7,0]

返回值:

7

思路:
1:暴力解
按住一个数,计算后面的数小于当前数的个数。
两个for,时间复杂度o(n2),空间复杂度o(1)
代码不贴了,太简单

2:利用归并排序的思想:
当进行合并操作的时候,如果nums[p1]>nums[p2]的话,那么这个时候就组成逆序对。然后就是nums[p1]~nums[mid]都大于nums[p2],所以此时的逆序对数就是mid-p1+1。

时间复杂度为:O(NlogN)。因为是归并排序,所以时间复杂度为O(NlogN)。空间复杂度为:O(N)。因为借助了辅助数组temp

public class InversePairs {
    static long p=0;
    public static int inversePairs(int [] array) {
        sort(array,0,array.length-1);
        return (int)(p%1000000007);
    }

    private static void sort(int[] array, int left, int right) {
        if (leftarray[j]){
                tmp[k++] = array[j++];
                p += (mid-i+1);
            }else {
                tmp[k++] = array[i++];
            }
        }
        while (i<=mid){
            tmp[k++] = array[i++];
        }
        while (j<=right){
            tmp[k++] = array[j++];
        }
        int t = left;
        while (t<=right){
            array[t] = tmp[t++];
        }
    }

    public static void main(String[] args) {
        int[] a = {627126,415347,850134,371085,279048,705820,453064,944751,92317,58592,167988,284065,992573,78043,190215,104546,607528,391775,701214,849731,231053,603058,975374,199773,479544,143961,206797,325662,90035,69615,429916,717161,484962,796403,604598,280362,502223,57662,741466,594540,632606,909454,394957,625180,503849,585172,729726,627729,976947,947293,477461,724352,66703,452835,440478,62599,596797,163627,388261,203184,233243,334529,436697,234557,647284,41295,514920,665859,615310,256386,776752,247916,682192,171709,389448,186041,273234,635527,813771,766533,582820,807584,490886,649523,260419,447716,228474,373568,611343,616735,576752,844586,467616,529801,595496,631253,571097,110416,297112,186407,883154,73864,950675,81698,245574,340124,267739,35160,975651,597862,801693,74823,921798,292579,240698,182218,256647,469172,72138,867991,602259,165243,228929,69875,695044,824425,701128,782493,451193,998241,485252,334347,588457,435928,416045,350383,292404,200137,385543,268055,314351,187237,859230,236150,996168,99928,934720,252816,569100,523210,120807,171359,688453,866088,757586,383498,206866,458715,682343,658059,973308,167596,508759,78117,603524,441156,428501,412280,157645,814044,196687,471997,1281,55917,224499,997450,155845,159219,250266,241297,682429,887425,412656,887235,269865,686594,787085,476731,661661,469428,134791,634969,637024,643550,229439,756900,601058,657940,169180,758704,471984,365867,230701,473266,421784,455200,470716,93981,130771,237334,335278,329552,641111,264286,733139,910976,950881,520224,904060,612542,989653,38851,763864,143029,198753,993303,899930,799811,651243,585462,558515,639579,951330,305568,112845,889466,277120,99913,499800,924243,853599,835078,770148,11062,615717,503287,922039,82950,23512,826099,695492,529517,381302,975708,672546,96407,485363,88828,896218,652958,674291,971086,292538,141973,276654,921735,547791,70127,21649,47591,994370,391600,399022,764518,402663,14739,267806,841054,97689,807670,183505,309533,337187,564807,801594,9733,661214,803309,614914,73784,45626};
        int i = inversePairs(a);
        System.out.println(i);
    }
}

33、数组中只出现一次的两个数字

描述
一个整型数组里除了两个数字只出现一次,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

示例1
输入:

[1,4,1,6]

返回值:

[4,6]

说明:

返回的结果中较小的数排在前面

思路:
1、直接用set来解决。但是很显然没啥美感
2、利用位运算异或的性质。

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
先考虑另一个问题,一个整型数组里除了一个数字之外,其他的数字都出现了两次,那把所有的数据异或起来,异或的结果就是那一个数字。(因为出现过两次的数字异或起来就抵消了)

对于这个题来说,如果把数字全异或起来,最后异或出来的结果是不同的那两个数的异或,然后随便从异或结果里面找一位1,因为某一位上异或结果是1的话,说明要找两个数,这一位上一个是1,一个是0。那么就可以把原来数组中,这一位是1的分成一组,这一位是0的分成一组。这样就有了两组每一组中会包含一个不同的数和一部分出现两次的数。 然后组内异或就可以了。

import java.util.*;
public class Solution {
    public int[] FindNumsAppearOnce (int[] array) {
        int[] ans = new int[2];
        int ans1 = 0,ans2 = 0;
        int XORsum = 0;
        for(int i = 0 ; i < array.length ; i++){
            XORsum ^= array[i];
        }
        int t = 1;//找出异或和中哪一位是1
        while((XORsum&t)==0){
            t<<=1;
        }
        for(int i = 0 ; i < array.length ; i++){
            if((t&array[i])==0){
                ans1 ^= array[i];
            }
            else{
                ans2 ^= array[i];
            }
        }
        if(ans1

34、和为S的连续正数序列

描述
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
返回值描述:
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序
示例1
输入:
9
返回值:
[[2,3,4],[4,5]]

思路:滑动窗口

算法步骤: 

1、初始化,i=1,j=(int)Math.ceil(Math.sqrt(2*sum+0.25)-0.5), 窗口大小为j-i+1,已知i=1,可以很轻松算出sum和j的关系,因为是从i=1算的,所以为最大可能的窗口大小。
2、如果窗口中值的和小于目标值sum, 表示需要扩大窗口,j++,i++
3、否则,如果窗口值和大于目标值sum,表示需要缩小窗口,j--;
4、否则,等于目标值,存结果,窗口往右移,j++,i++,继续进行步骤2,3,4 .
import java.util.ArrayList;
public class Solution {
    public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
                int right = (int)Math.ceil(Math.sqrt(2*sum+0.25)-0.5);
        int left = 1;
        ArrayList<ArrayList<Integer>> result = new ArrayList<>();
        while(right>left){
            if(subSum(left,right)>sum){
                right--;
            }else if(subSum(left,right)<sum){
                left++;
                right++;
            }else{
                ArrayList<Integer> tmp = new ArrayList<>();
                for(int i = left;i<=right;i++){
                    tmp.add(i);
                }
                result.add(tmp);
                left++;
                right++;
            }
        }
        return result;
    }
    public int subSum(int left, int right){
        return (left+right)*(right-left+1)/2;
    }
}

35、射击游戏

描述
小易正在玩一款新出的射击游戏,这个射击游戏在一个二维平面进行,小易在坐标原点(0,0),平面上有n只怪物,每个怪物有所在的坐标(x[i], y[i])。小易进行一次射击会把x轴和y轴上(包含坐标原点)的怪物一次性消灭。
小易是这个游戏的VIP玩家,他拥有两项特权操作:
1、让平面内的所有怪物同时向任意同一方向移动任意同一距离
2、让平面内的所有怪物同时对于小易(0,0)旋转任意同一角度
小易要进行一次射击。小易在进行射击前,可以使用这两项特权操作任意次。
小易想知道在他射击的时候最多可以同时消灭多少只怪物,请你帮帮小易。

思路:先选择3点,前两个点构造一条直线,后面一个点构造一条垂直于前一条直线的直线,然后枚举剩下的点是否在这两条线上。
涉及到的数学知识:
判断一个点是否在某直线上:
直线上有两个点(x1,y1) (x2,y2),待判断的点(x3,y3),
如果(x3-x1)/(y3-y1)=(x2-x1)/(y2-y1),则共线

判断是否在垂线上:
运用的公理:两垂直线的斜率之积为-1
直线上有两个点(x1,y1) (x2,y2),垂线上点(x4,y4)待判断的点(x3,y3)。
(x2-x1)/(y2-y1)*(x3-x4)/(y3-y4)=-1,则该点在垂线上。

package jianZhiOffer;

/**
 * @Describe:先选择3个不共线的点,前两个点构造一条直线,后面一个点构造一条垂直于前一条直线的直线,然后枚举剩下的点是否在这两条线上,更新最大值就行了
 * @Author: Sendren
 * @Date: 2021/7/21 14:00
 */
import java.util.Scanner;
public class ShotGame{
    public static void main(String[] args){
        Scanner sc=new Scanner(System.in);
        while(sc.hasNext()){
            int n=sc.nextInt();
            int[] x=new int[n];
            int[] y=new int[n];
            for(int i=0;i<n;i++){
                x[i]=sc.nextInt();
            }
            for(int i=0;i<n;i++){
                y[i]=sc.nextInt();
            }
            int res=solve(n,x,y);
            System.out.println(res);
        }
    }
    public static int solve(int n,int[] x,int[] y){
        int res = 1;
        if (n <= 2)return n;
        for (int i=0; i<n; i++) {  //first point
            for (int j=0; j<n; j++) {  //second point
                if (i != j){
                    int dx1 = x[j] - x[i];
                    int dy1 = y[j] - y[i];
                    for (int k=0; k<n; k++) {  //third point
                        int count = 0;
                        if (k != i && k != j){
                            for (int r=0; r<n; r++) {
                                int dx2 = x[r] - x[i];
                                int dy2 = y[r] - y[i];
                                if (dy1*dx2 == dx1*dy2) {//判断当前点是否和选定的前两个点在同一条直线上
                                    count++;
                                }else{
                                    dx2 = x[r] - x[k];
                                    dy2 = y[r] - y[k];
                                    if (dy1*dy2 == -dx1*dx2) {//判断当前点是否在选定的第三个点和前两个点形成垂直的线上
                                        count++;
                                    }
                                }
                            }
                        }
                        res = Math.max(res, count);
                    }
                }
            }
        }
        return res;
    }

}

36、和为S的两个数字

描述
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,返回两个数的乘积最小的,如果无法找出这样的数字,返回一个空数组即可。
返回值描述:
对应每个测试案例,输出两个数,小的先输出。
示例1
输入:
[1,2,4,7,11,15],15

返回值:
[4,11]

思路:
因为数组是有序的,所以可以用双指针,指向数组的首尾,具体步骤如下:
1.初始化:指针i指向数组首, 指针j指向数组尾部
2. 如果arr[i] + arr[j] == sum , 说明是可能解
3. 否则如果arr[i] + arr[j] > sum, 说明和太大,所以–j
4. 否则如果arr[i] + arr[j] < sum, 说明和太小,所以++i

    public ArrayList<Integer> findNumbersWithSum(int [] array,int sum) {
        ArrayList<Integer> list = new ArrayList<>();
        int[] result = new int[2];
        result[0] = 999;
        result[1] = 999;
        int l = 0;
        int r = array.length-1;
        while(l<r){
            if((array[l]+array[r])==sum){
                if((array[l]*array[r])<(result[0]*result[1])){
                    result[0] = array[l];
                    result[1] = array[r];
                }
                l++;
                r--;
            }else if((array[l]+array[r])<sum){
                l++;
            }else{
                r--;
            }
        }
        list.add(result[0]);
        list.add(result[1]);
        return list;
    }

37、孩子们的游戏(圆圈中最后剩下的数) 描述

每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)

如果没有小朋友,请返回-1
示例1
输入:
5,3

返回值:
3

思路:
1、用循环链表。
2、模拟,一个指针,走到队尾后重制为零,再继续走。

public int LastRemaining_Solution(int n, int m) {
        if(n==0){
            return -1;
        }
        LinkedList<Integer> linkedList = new LinkedList<>();
        for (int i = 0; i < n; i++) {
            linkedList.addLast(i);
        }
        int i = (m-1)%n ;
        while (linkedList.size()>1){
            linkedList.remove(i);
            int count = 0;
            while (count<(m-1)){
                i = i+1;
                if (i==linkedList.size()){
                    i = 0;//reset
                }
                if (i>linkedList.size()){
                    i = 1;
                }
                count++;
            }
        }
        return linkedList.peek();
    }

38、JZ55 链表中环的入口结点

描述
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null。

输入描述:
输入分为2段,第一段是入环前的链表部分,第二段是链表环的部分,后台将这2个会组装成一个有环或者无环单链表
返回值描述:
返回链表的环的入口结点即可。而我们后台程序会打印这个节点

思路:
方法一:哈希法

遍历单链表的每个结点
如果当前结点地址没有出现在set中,则存入set中
否则,出现在set中,则当前结点就是环的入口结点
整个单链表遍历完,若没出现在set中,则不存在环
/*
 public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}
*/
import java.util.HashSet;
public class Solution {

    public ListNode EntryNodeOfLoop(ListNode pHead) {
        HashSet<ListNode> set = new HashSet<>();
        while(pHead!=null){
            if(set.contains(pHead)){
                return pHead;
            }
            set.add(pHead);
            pHead = pHead.next;
        }
        return null;
    }
}

方法二:双指针法

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
这题我们可以采用双指针解法,一快一慢指针。快指针每次跑两个element,慢指针每次跑一个。如果存在一个圈,总有一天,快指针是能追上慢指针的。
如下图所示,我们先找到快慢指针相遇的点,p。我们再假设,环的入口在点q,从头节点到点q距离为A,q p两点间距离为B,p q两点间距离为C。
因为快指针是慢指针的两倍速,且他们在p点相遇,则我们可以得到等式 2(A+B) = A+B+C+B. (感谢评论区大佬们的改正,此处应为:如果环前面的链表很长,而环短,那么快指针进入环以后可能转了好几圈(假设为n圈)才和慢指针相遇。但无论如何,慢指针在进入环的第一圈的时候就会和快的相遇。等式应更正为 2(A+B)= A+ nB + (n-1)C)
由3的等式,我们可得,C = A。
这时,因为我们的slow指针已经在p,我们可以新建一个另外的指针,slow2,让他从头节点开始走,每次只走下一个,原slow指针继续保持原来的走法,和slow2同样,每次只走下一个。
我们期待着slow2和原slow指针的相遇,因为我们知道A=C,所以当他们相遇的点,一定是q了。
我们返回slow2或者slow任意一个节点即可,因为此刻他们指向的是同一个节点,即环的起始点,q。
小黄做的算法题_第13张图片
public class Solution {

public ListNode EntryNodeOfLoop(ListNode pHead)
{
    if(pHead == null || pHead.next == null){
        return null;
    }

    ListNode fast = pHead;
    ListNode slow = pHead;

    while(fast != null && fast.next != null){
        fast = fast.next.next;
        slow = slow.next;
        if(fast == slow){
            ListNode slow2 = pHead;
            while(slow2 != slow){
                slow2 = slow2.next;
                slow = slow.next;
            }
            return slow2;
        }
    }
    return null;

}

}

39、JZ57 二叉树的下一个结点

给定一个二叉树其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的next指针。下图为一棵有9个节点的二叉树。树中从父节点指向子节点的指针用实线表示,从子节点指向父节点的用虚线表示。
小黄做的算法题_第14张图片 示例:
输入:{8,6,10,5,7,9,11},8
返回:9
解析:这个组装传入的子树根节点,其实就是整颗树,中序遍历{5,6,7,8,9,10,11},根节点8的下一个节点就是9,应该返回{9,10,11},后台只打印子树的下一个节点,所以只会打印9,如下图,其实都有指向左右孩子的指针,还有指向父节点的指针,下图没有画出来
小黄做的算法题_第15张图片

输入:
{8,6,10,5,7,9,11},8

返回值:
9

思路
1、迭代回父节点root,然后中序遍历二叉树,然后找到目标点。

/*
public class TreeLinkNode {
    int val;
    TreeLinkNode left = null;
    TreeLinkNode right = null;
    TreeLinkNode next = null;

    TreeLinkNode(int val) {
        this.val = val;
    }
}
*/
import java.util.*;
public class Solution {
    public TreeLinkNode GetNext(TreeLinkNode pNode) {
        TreeLinkNode origin = pNode;
        ArrayList<TreeLinkNode> list = new ArrayList<>();
        while(pNode.next!=null){
            pNode = pNode.next;
        }
        midTraversal(list,pNode);
        for(int i=0;i<list.size()-1;i++){
            if(list.get(i)==origin){
                return list.get(i+1);
            }
        }
        return null;
    }
    public void midTraversal(ArrayList list,TreeLinkNode node){
        if(node.left!=null){
            midTraversal(list,node.left);
        }
        list.add(node);
        if(node.right!=null){
            midTraversal(list,node.right);
        }
    }
}

40、JZ60 把二叉树打印成多行

描述
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
例如:
给定的二叉树是{1,2,3,#,#,4,5}
小黄做的算法题_第16张图片
该二叉树多行打印层序遍历的结果是
[
[1],
[2,3],
[4,5]
]

思路:维护一个队列,该队列每次存储一层的节点,依次弹出后再把下一层的节点塞进去。

import java.util.*;
public class Solution {
ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {

        Queue<TreeNode> queue = new LinkedList<>();
        ArrayList<ArrayList<Integer>> result = new ArrayList<>();
            if(pRoot==null){
            return result;
        }
        queue.add(pRoot);
        while (!queue.isEmpty()){
            int size = queue.size();
            ArrayList<Integer> tmp = new  ArrayList<>();
            for (int i = 0; i < size; i++) {
                TreeNode poll = queue.poll();
                tmp.add(poll.val);
                if (poll.left!=null){
                    queue.add(poll.left);
                }
                if (poll.right!=null){
                    queue.add(poll.right);
                }
            }
            result.add(tmp);
        }
        return result;
    }
}

41、JZ63 数据流中的中位数

描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
小黄做的算法题_第17张图片
思路:
1、暴力解
维护一个数组,,每插入一个新的数,就对数组做排序

import java.util.*;
public class Solution {
    LinkedList<Integer> list = new LinkedList<>();
    public void Insert(Integer num) {
        list.add(num);
        Collections.sort(list);
    }

    public Double GetMedian() {
        int size = list.size();
        int mid = size/2;
        return list.size()%2!=0?list.get(mid)*1.0:(list.get(mid)+list.get(mid-1))/2.0;
    }

}

2、和暴力解类似,但是排序算法用插入排序,这样排序的时间复杂度就下降了。

3、大顶堆小顶堆
思想是利用优先队列,优先队列分为大顶堆和小顶堆,默认维护的是小顶堆的优先队列

    private int cnt = 0;
    private PriorityQueue<Integer> low = new PriorityQueue<>();
    // 默认维护小顶堆
    private PriorityQueue<Integer> high = new PriorityQueue<>(new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2.compareTo(o1);
        }
    });

    public void Insert(Integer num) {
        // 数量++
        cnt++;
        // 如果为奇数的话
        if ((cnt & 1) == 1) {
            // 由于奇数,需要存放在大顶堆上
            // 但是呢,现在你不知道num与小顶堆的情况
            // 小顶堆存放的是后半段大的数
            // 如果当前值比小顶堆上的那个数更大
            if (!low.isEmpty() && num > low.peek()) {
                // 存进去
                low.offer(num);
                // 然后在将那个最小的吐出来
                num = low.poll();
            } // 最小的就放到大顶堆,因为它存放前半段
            high.offer(num);
        } else {
            // 偶数的话,此时需要存放的是小的数
            // 注意无论是大顶堆还是小顶堆,吐出数的前提是得有数
            if (!high.isEmpty() && num < high.peek()) {
                high.offer(num);
                num = high.poll();
            } // 大数被吐出,小顶堆插入
            low.offer(num);
        }

    }

    public Double GetMedian() {// 表明是偶数
        double res = 0;
        // 奇数
        if ((cnt & 1) == 1) {
            res = high.peek();
        } else {
            res = (high.peek() + low.peek()) / 2.0;
        }
        return res;
    }

42、回溯算法解矩阵中的路径

小黄做的算法题_第18张图片
小黄做的算法题_第19张图片
思路:用回溯算法
肯定是要用到递归的
递归体:就是走哪条路径
跳出条件:根据题目的要求
具体思路看着篇文章

package jianZhiOffer;

import java.util.ArrayList;
import java.util.HashSet;

/**
 * @Describe:
 * @Author: Sendren
 * @Date: 2021/7/26 14:51
 */

public class HasPath {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param matrix char字符型二维数组
     * @param word string字符串
     * @return bool布尔型
     */
    public boolean hasPath (char[][] matrix, String word) {
        char[] words = word.toCharArray();
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[0].length; j++) {
                //从[i,j]这个坐标开始查找
                if(dfs(matrix,words,i,j,0)){
                    return true;
                }
            }
        }
        return false;
    }

    private boolean dfs(char[][] matrix, char[] words, int i, int j, int index) {
        //边界的判断,如果越界直接返回false。index表示的是查找到字符串word的第几个字符,
        //如果这个字符不等于matrix[i][j],说明验证这个坐标路径是走不通的,直接返回false
        if (i>= matrix.length||i<0||j>= matrix[0].length||j<0||matrix[i][j]!=words[index]){
            return false;
        }
        //如果word的每个字符都查找完了,直接返回true
        if (index == words.length-1){
            return true;
        }
        //把当前坐标的值保存下来,为了在最后复原
        char tmp = matrix[i][j];
        //然后修改当前坐标的值
        matrix[i][j] = '#';
        //走递归,沿着单前坐标的上下左右4个方向查找
        boolean res = dfs(matrix, words, i + 1, j, index + 1)
                || dfs(matrix, words, i - 1, j, index + 1)
                || dfs(matrix, words, i, j + 1, index + 1)
                || dfs(matrix, words, i , j - 1, index + 1);
        //走到这 如果res是false,说明这条路都走不通,那么把matrix[i][j]还原一下
        matrix[i][j] = tmp;
        return res;
    }

}


43、JZ17 树的子结构

判断一棵树是否是另一颗树的子树。

思路:分两步走
第一步:递归遍历大树,找相同根节点
第二步:以相同根节点为起点,递归对比左右子树是否相同
具体思路看这篇

    //找相同根节点
    public boolean hasSubTree(TreeNode root1,TreeNode root2){
        if (root2==null){
            return false;
        }
        if (root1==null&&root2!=null){
            return false;
        }
        boolean flag = false;
        if (root1.val== root2.val){
            flag = isSubTree(root1,root2);
        }
        if (!flag){
            flag = hasSubTree(root1.left, root2);
            if (!flag){
                flag = hasSubTree(root1.right, root2);
            }
        }
        return flag;
    }

    private boolean isSubTree(TreeNode root1, TreeNode root2) {
        if (root2==null){
            return true;
        }
        if (root1==null&&root2!=null){
            return false;
        }
        if (root1.val==root2.val){
            return isSubTree(root1.left,root2.left)
                    && isSubTree(root1.right,root2.right);
        }else {
            return false;
        }
    }

44、复杂链表的复制

描述
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)。 下图是一个含有5个结点的复杂链表。图中实线箭头表示next指针,虚线箭头表示random指针。为简单起见,指向null的指针没有画出。
小黄做的算法题_第20张图片
示例:
输入:{1,2,3,4,5,3,5,#,2,#}
输出:{1,2,3,4,5,3,5,#,2,#}
解析:我们将链表分为两段,前半部分{1,2,3,4,5}为ListNode,后半部分{3,5,#,2,#}是随机指针域表示。
以上示例前半部分可以表示链表为的ListNode:1->2->3->4->5
后半部分,3,5,#,2,#分别的表示为
1的位置指向3,2的位置指向5,3的位置指向null,4的位置指向2,5的位置指向null
如下图:
小黄做的算法题_第21张图片思路:
使用map的数据结构,key为原链表节点,value为新的链表节点,这样就可以进行映射了。

package jianZhiOffer;

import java.util.HashMap;

/**
 * @Describe:深拷贝复杂链表,randomListNode
 * @Author: Sendren
 * @Date: 2021/7/30 15:42
 */
public class RandomListNodeClone {
    public class RandomListNode {
        int label;
        RandomListNode next = null;
        RandomListNode random = null;

        RandomListNode(int label) {
            this.label = label;
        }
    }
    public RandomListNode Clone(RandomListNode pHead){
        if (pHead == null){
            return null;
        }
        //target作为将要返回的头,记住要new的
        RandomListNode target = new RandomListNode(pHead.label);
        //cur 获取链表头
        RandomListNode cur = pHead;
        //p 获取新链表头
        RandomListNode p = target;

        HashMap<RandomListNode, RandomListNode> map = new HashMap<>();

        //由pHead将所有值存入map,每一个结点都要new的
        while (pHead!=null){
            map.put(pHead,new RandomListNode(pHead.label));
            pHead = pHead.next;
        }

        //target作为新链表的头,由cur,p移动来复制链表
        while (cur!=null){
            p.next = map.get(cur.next);
            p.random = map.get(cur.random);

            cur = cur.next;
            p = p.next;

        }
        return target;
    }
}

45、二叉搜索树的后序遍历序列

题目
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
思路
只需要不断地确定出左子树区间和右子树区间,并且判断:左子树区间的所有结点值 < 根结点值 < 右子树区间所有结点值,这个条件是否满足即可。
代码:

class BSTPreOrder {
    public boolean VerifySquenceOfBST(int[] sequence) {
        if (sequence == null || sequence.length == 0) {
            return false;
        }
        return isBST(sequence, 0, sequence.length - 1);
    }

    private boolean isBST(int[] seq, int start, int end) {
        if (start >= end) {
            return true;
        }
        int val = seq[end];
        int split = start;
        for (; split < end && seq[split] < val; split++) ;//找到切分点
        for (int i = split; i < end; i++) {//只判断右边的
            if (seq[i] < val) {
                return false;
            }
        }
        return isBST(seq, start, split - 1) && 
               isBST(seq, split, end - 1);
    }
}

46、JZ20 包含min函数的栈

实现一个栈
描述
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数,并且调用 min函数、push函数 及 pop函数 的时间复杂度都是 O(1)
push(value):将value压入栈中
pop():弹出栈顶元素
top():获取栈顶元素
min():获取栈中最小元素

示例:
输入: [“PSH-1”,“PSH2”,“MIN”,“TOP”,“POP”,“PSH1”,“TOP”,“MIN”]
输出: -1,2,1,-1
解析:
"PSH-1"表示将-1压入栈中,栈中元素为-1
"PSH2"表示将2压入栈中,栈中元素为2,-1
“MIN”表示获取此时栈中最小元素= =>返回-1
"TOP"表示获取栈顶元素= =>返回2
"POP"表示弹出栈顶元素,弹出2,栈中元素为-1
"PSH-1"表示将1压入栈中,栈中元素为1,-1
"TOP"表示获取栈顶元素= =>返回1
“MIN”表示获取此时栈中最小元素==>返回-1

思路
用双栈,一个维护普通的栈数,一个维护当前栈的最小值

代码

import java.util.Stack;

public class Solution {
    Stack<Integer> stack = new Stack<Integer>();
    Stack<Integer> stackMin = new Stack<Integer>();
    public void push(int node) {
        stack.push(node);
        if(stackMin.isEmpty()){
            stackMin.push(node);
        }else{
            if(stackMin.peek()>=node){
                stackMin.push(node);
            }
        }
    }
    
    public void pop() {
        int tmp = stack.pop();
        if(tmp==stackMin.peek()){
            stackMin.pop();
        }
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int min() {
        return stackMin.peek();
    }
}

47、约瑟夫环

题目
有n个人围成一圈,顺序排号。从第一个人开始报数(从1到3报数),凡报到3的人退出圈子,问最后留下的是原来第几号的那位。
思路
建一个链表类,新建成链环
代码

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        System.out.println(YueSeFuCircle(n));
    }
    
    public static int YueSeFuCircle(int n)
        {
            if (n == 1) return 1;
            Node head = new Node(1);
            Node cur = head;
            for (int i = 2; i <= n; i++)
            {
                cur.next = new Node(i);
                cur = cur.next;
            }
            cur.next = head;
            Node pre = null;
            cur = head;
            int count = 1;
            while(cur != pre)
            {
                if(count%3 != 0)
                {
                    pre = cur;
                    cur = cur.next;
                    count++;
                }
                else
                {
                    cur = cur.next;
                    pre.next = cur;
                    count = 1;
                }
            }
            return cur.val;
        }
}

class Node{
    public int val;	//数值 data
    public Node next;	// 结点 node

    public Node(int x){	//可以定义一个有参构造方法,也可以定义一个无参构造方法
        val = x;
    }
}

48、判断两个日期是否为同一星期类型

描述
小陆每天要写一份工作日报,日报标题含有日期。几年后,他翻开以前的日报,想知道两份日报的日期是否同为星期几,请编程帮助他判断。
输入描述
第一行一个正整数T(1<=T<=100)。表示有T个测试样例。
接下来T行,每一行有6个正整数y1,m1,d1,y2,m2,d2,(以空格相间)。其中y1-m1-d1分别为第一个日期的年月日,y2-m2-d2分别为第二个日期的年月日。(满足1970<=y1,y2<=9999, 1<=m1,m2<=12, 1<=d1,d2<=31,且保证两个日期是合法的)。
输出描述
输出T行,对应T个答案。对于每一行,如果两个日期在同一周,输出“True”;否则输出“False”(输出内容不含双引号)
输入例子
2
1970 1 2 2020 2 7
2020 1 1 2020 1 2
输出例子
True
False
思路
用java类LocalDate的parse方法解析,得到LocalDate类,再获得对应的dayofweek。
不过用牛客的测试 老是通不过 垃圾啊

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.util.Scanner;

public class test {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] ints = new int[n * 6];
        for (int i = 0; i < n*6; i++) {
            ints[i] = sc.nextInt();
        }
        for (int i = 0; i < n; i++) {
            int firstYear = ints[i*6];
            int firstMonth = ints[i*6+1];
            String firstMonthString = String.valueOf(firstMonth);
            int firstDay = ints[i*6+2];
            String firstDayString = String.valueOf(firstDay);
            if (firstMonth<10){
                firstMonthString = "0"+firstMonth;
            }
            if (firstDay<10){
                firstDayString = "0"+firstDay;
            }
            String first = firstYear + "-" + firstMonthString + "-" + firstDayString;

            int secondYear = ints[i*6+3];
            int secondMonth = ints[i*6+4];
            String secondMonthString = String.valueOf(secondMonth);
            int secondDay = ints[i*6+5];
            String secondDayString = String.valueOf(secondDay);
            if (secondMonth<10){
                secondMonthString = "0"+secondMonth;
            }
            if (firstDay<10){
                secondDayString = "0"+secondDay;
            }
            String second = secondYear + "-" + secondMonthString + "-" + secondDayString;

            LocalDate firstDate = LocalDate.parse(first);
            LocalDate secondDate = LocalDate.parse(second);
            DayOfWeek firstDateDayOfWeek = firstDate.getDayOfWeek();
            DayOfWeek secondDateDayOfWeek = secondDate.getDayOfWeek();
            System.out.println(firstDateDayOfWeek == secondDateDayOfWeek ? "true" : "false");
        }
    }
}


49、递归 青蛙跳阶梯

段誉身具凌波微波,动无常则,若危若安,一次能走一级台阶或者两级台阶,他要爬一段30级的山路,问有多少种走法?分析如何计算,然后编程解答。
进阶问题:当他轻功熟练度提升,一次最多可以走三级,那就结果有什么变化?后来走火入魔了,不能走一级,只能走二或三级,又有什么变化?
输入描述:
输入一个数n(1<=n<=30),代表段誉要爬一段n级的山路。
输出描述:
输出三个整数a,b,c(以空格相间)。其中a为段誉一次能走一级或两级台阶的走法;b为段誉一次能走一级、二级或三级台阶的走法;c为段誉一次能走二级或三级台阶的走法。
输入例子1:
3
输出例子1:
3 4 1
思路
递归就完事了

import java.util.Scanner;
public class Main{
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        System.out.println(timesOne(n)+" "+timesTwo(n)+" "+timesThree(n));

    }

    private static int timesOne(int n) {
        if (n==0){
            return 1;
        }
        if (n==1){
            return 1;
        }
        return timesOne(n-2)+timesOne(n-1);
    }
    private static int timesTwo(int n) {
        if (n==0){
            return 1;
        }
        if (n==1){
            return 1;
        }
        if (n==2){
            return 2;
        }
        return timesTwo(n-3)+timesTwo(n-2)+timesTwo(n-1);
    }
    private static int timesThree(int n) {
        if (n==0){
            return 1;
        }
        if (n==1){
            return 0;
        }
        if (n==2){
            return 1;
        }
        return timesThree(n-2)+timesThree(n-3);
    }
}

50、 二叉树中和为某一直的路径

描述
输入一颗二叉树的根节点和一个整数,按字典序打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
示例1
输入:

{10,5,12,4,7},22

返回值:

[[10,5,7],[10,12]]

思路:使用回溯算法
1、采用深度优先搜索的方式,枚举每一条从根节点到叶子节点的路径。当我们遍历到叶子节点,且此时路径和恰为目标和时,我们就找到了一条满足条件的路径。
2、我们也可以采用广度优先搜索的方式,遍历这棵树。当我们遍历到叶子节点,且此时路径和恰为目标和时,我们就找到了一条满足条件的路径。为了节省空间,我们使用哈希表记录树中的每一个节点的父节点。每次找到一个满足条件的节点,我们就从该节点出发不断向父节点迭代,即可还原出从根节点到当前节点的路径。

举例说明:
采用深度优先搜索的方式:
二叉树:{10,5,12,4,7}, 目标值:22
根据二叉树可以枚举出所有从根节点到叶子节点的路径【10,5,4】、【10,5,7】、【10,12】
将不同的路径节点值相加,并判断符合结果等于目标值的路径:【10,5,7】、【10,12】

import java.util.ArrayList;
import java.util.LinkedList;
/**
public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;
    }
}
*/
public class Solution {
    //初始化集合
    ArrayList res = new ArrayList<>();
    LinkedListpath = new LinkedList<>(); 
    public ArrayList FindPath(TreeNode root,int target) {
        dfs(root, target);
        return res;
    }
    
    public void dfs(TreeNode root,int tar){
        
        if(root == null){
            return;
        }
        //将root节点放入路径集合
        path.add(root.val);
        //更新目标值,每放入一个节点,目标值应该相应减去对应节点的值,直到目标值为0
        tar -= root.val;
        //如果目标值减到了0 && 左节点为空 && 右节点为空 证明树已遍历完,此路径为目标路径
        if(tar==0 && root.left == null && root.right == null){
            res.add(new ArrayList(path));
        }
        // 递归左右子树
        dfs(root.left, tar);
        dfs(root.right, tar);
        //删除当前节点,在回溯过程中,此节点不在新路径上
        path.removeLast();
    }
}

51、机器人的运动范围

描述
地上有一个rows行和cols列的方格。坐标从 [0,0] 到 [rows-1,cols-1]。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于threshold的格子。 例如,当threshold为18时,机器人能够进入方格[35,37],因为3+5+3+7 = 18。但是,它不能进入方格[35,38],因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?

范围:
1 <= rows, cols<= 100
0 <= threshold <= 20

题解:回溯算法

DFS(深度优先搜索)

这道题说的是一个机器人从左上角开始,他可以沿着上下左右四个方向走,并且走到的每个格子坐标的数字和不大于k,问可以走多少个格子。我们先来画个图看一下
小黄做的算法题_第22张图片这里统计的是能走多少个格子,所以统计肯定是不能有重复的,题中说了,机器人是可以沿着上下左右四个方向走的。但你想一下,任何一个格子你从任何一个方向进来(比如从上面进来),那么他只能往其他3个方向走,因为如果在往回走就重复了。但实际上我们只要沿着两个方向走就可以了,一个是右边,一个是下边,也就是上面图中红色的箭头。我们来看下代码

    public int movingCount(int threshold, int rows, int cols) {
        //临时变量visited记录格子是否被访问过
        boolean[][] visited = new boolean[rows][cols];
        return dfs(0, 0, rows, cols, threshold, visited);
    }

    public int dfs(int i, int j, int rows, int cols, int threshold, boolean[][] visited) {
        //i >= rows || j >= cols是边界条件的判断,threshold < sum(i, j)判断当前格子坐标是否
        // 满足条件,visited[i][j]判断这个格子是否被访问过
        if (i >= rows || j >= cols || threshold < sum(i, j) || visited[i][j])
            return 0;
        //标注这个格子被访问过
        visited[i][j] = true;
        //沿着当前格子的右边和下边继续访问
        return 1 + dfs(i + 1, j, rows, cols, threshold, visited) +
                dfs(i, j + 1, rows, cols, threshold, visited);
    }

    //计算两个坐标数字的和
    private int sum(int i, int j) {
        int sum = 0;
        //计算坐标i所有数字的和
        while (i != 0) {
            sum += i % 10;
            i /= 10;
        }
        //计算坐标j所有数字的和
        while (j != 0) {
            sum += j % 10;
            j /= 10;
        }
        return sum;
    }

时间复杂度:O(mn),最坏情况下,每个格子都走一遍
空间复杂度:O(m
n),需要二维数组visited记录每个格子是否被访问过

BFS(广度优先搜索)

DFS是沿着一个方向一直往下走,有一种不撞南墙不回头的感觉,直到不满足条件才会回头。而BFS就显得有点博爱了,他不是一条道走下去,他会把离他最近的都访问一遍,访问完之后才开始访问第二近的……,一直这样下去,所以最好的一种数据结构就是使用队列,因为队列是先进先出,离他最近的访问完之后加入到队列中,最先入队的也是最先出队的,代码和上面有很多相似的地方,基本上没什么难度,来看下,代码中有详细的注释

import java.util.LinkedList;
import java.util.Queue;

public class Solution {
    public int movingCount(int threshold, int rows, int cols) {
        //临时变量visited记录格子是否被访问过
        boolean[][] visited = new boolean[rows][cols];
        int res = 0;
        //创建一个队列,保存的是访问到的格子坐标,是个二维数组
        Queue<int[]> queue = new LinkedList<>();
        //从左上角坐标[0,0]点开始访问,add方法表示把坐标
        // 点加入到队列的队尾
        queue.add(new int[]{0, 0});
        while (queue.size() > 0) {
            //这里的poll()函数表示的是移除队列头部元素,因为队列
            // 是先进先出,从尾部添加,从头部移除
            int[] x = queue.poll();
            int i = x[0], j = x[1];
            //i >= rows || j >= cols是边界条件的判断,threshold < sum(i, j)判断当前格子坐标是否
            // 满足条件,visited[i][j]判断这个格子是否被访问过
            if (i >= rows || j >= cols || threshold < sum(i, j) || visited[i][j])
                continue;
            //标注这个格子被访问过
            visited[i][j] = true;
            res++;
            //把当前格子右边格子的坐标加入到队列中
            queue.add(new int[]{i + 1, j});
            //把当前格子下边格子的坐标加入到队列中
            queue.add(new int[]{i, j + 1});
        }
        return res;
    }

    //计算两个坐标数字的和
    private int sum(int i, int j) {
        int sum = 0;
        //计算坐标i所有数字的和
        while (i != 0) {
            sum += i % 10;
            i /= 10;
        }
        //计算坐标j所有数字的和
        while (j != 0) {
            sum += j % 10;
            j /= 10;
        }
        return sum;
    }
}

时间复杂度:O(mn),最坏情况下,每个格子都要记录
空间复杂度:O(m
n),需要二维数组visited记录每个格子是否被访问过

做这道题之前首先要明白DFS和BFS是什么意思,才能使用这两种方式。我们来画个图看一下
小黄做的算法题_第23张图片

假如从A点开始访问,DFS就是沿着一条道走下去,然后再走其他的道……。BFS就是图中先访问圈内的部分,然后再把圈放大继续访问……。

52、 Z 字形变换

小黄做的算法题_第24张图片小黄做的算法题_第25张图片
小黄做的算法题_第26张图片
自动挡的flag

    public static String convert(String s, int numRows) {
        if (numRows<2){
            return s;
        }
        String[] strings = new String[numRows];
        for (int i = 0; i < strings.length; i++) {
            strings[i]="";
        }
        int index = 0;
        int flag = -1;
        for (int i = 0; i < s.length(); i++) {
            strings[index] += s.charAt(i);
            if (index==0||index==numRows-1){
                flag *= -1;
            }
            index += flag;
        }
        String result = "";
        for (String x :
                strings) {
            result += x;
        }
        return result;
    }

53 盛最多水的容器

小黄做的算法题_第27张图片
思路 :双指针加滑动窗口
基本的表达式: area = min(height[i], height[j]) * (j - i) 使用两个指针,值小的指针向内移动,这样就减小了搜索空间 因为面积取决于指针的距离与值小的值乘积,如果值大的值向内移动,距离一定减小,而求面积的另外一个乘数一定小于等于值小的值,因此面积一定减小,而我们要求最大的面积,因此值大的指针不动,而值小的指针向内移动遍历
代码:

import java.util.*;
class Solution {
    public int maxArea(int[] height) {
        int i = 0;
        int j = height.length-1;
        int res = 0;
        while(i<j){
            int x = Math.min(height[i],height[j]);
            res = Math.max(res, x*(j-i));
            if(height[i]>height[j]){
                j--;
            }else{
                i++;
            }
        }
        return res;
    }
}

54 整数转罗马数字

小黄做的算法题_第28张图片小黄做的算法题_第29张图片思路:用数组存储罗马数字,取余去除对应的罗马数字进行字符串拼接

class Solution {
    public String intToRoman(int num) {
        String[] one = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"};
        String[] ten = {"X","XX","XXX","XL","L","LX","LXX","LXXX","XC"};
        String[] hundred = {"C","CC","CCC","CD","D","DC","DCC","DCCC","CM"};
        String[] thousand = {"M","MM","MMM"};
        String[][] map = {one,ten,hundred,thousand};
        int bit = 0;//控制个位还是百位。。。
        ArrayList<String> resArray = new ArrayList<>();
        while(num>0){
            int index = num%10;
            if(index==0){
            }else{
                resArray.add(map[bit][index-1]);
            }
            bit++;
            num = num/10;
        }
        String res = "";
        for(int i=resArray.size()-1;i>=0;i--){
            res += resArray.get(i);
        }
        return res;
    }
}

小黄做的算法题_第30张图片

55 三数之和

题目:
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

小黄做的算法题_第31张图片
小黄做的算法题_第32张图片
小黄做的算法题_第33张图片
小黄做的算法题_第34张图片
小黄做的算法题_第35张图片

小黄做的算法题_第36张图片

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        int n = nums.length;
        Arrays.sort(nums);
        List<List<Integer>> ans = new ArrayList<>();
        // 枚举 a
        for (int first = 0; first < n; first++) {
            //需要和上次枚举的数不相同
            if (first!=0&&nums[first]==nums[first-1]){
                continue;
            }
            // c对应的指针初始指向数组最右端
            int third = n-1;
            int target = -nums[first];
            //枚举b
            for (int second = first+1; second < n; second++) {
                // 需要和上一次枚举的数不相同
                if (second>first+1&&nums[second]==nums[second-1]){
                    continue;
                }
                // 需要保证 b 的指针在 c 的指针的左侧
                while (second<third&&nums[second]+nums[third]>target){
                    --third;
                }
                // 如果指针重合,随着 b 后续的增加
                // 就不会有满足 a+b+c=0 并且 b
                if (second==third){
                    break;
                }
                if (nums[second]+nums[third]==target){
                    List<Integer> list = new ArrayList<>();
                    list.add(nums[first]);
                    list.add(nums[second]);
                    list.add(nums[third]);
                    ans.add(list);
                }
            }
        }
        return ans;

    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        List<List<Integer>> threeSum = solution.threeSum(new int[]{-1, 0, 1, 2, -1, -4});
        System.out.println(threeSum);
    }
}

56 最接近的三数之和

小黄做的算法题_第37张图片
思路:类似55题 先排序后双指针。

class Solution {
    public int threeSumClosest(int[] nums, int target) {
        Arrays.sort(nums);
        int n = nums.length;
        int result=nums[0]+nums[1]+nums[2];
        int dif = Math.abs(target-nums[0]-nums[1]-nums[2]);
        for (int first = 0; first < n; first++) {
            int third = n-1;
            for (int second = first+1; second < n; second++) {
                if (second==third){
                    break;
                }
                int cur_sum = nums[first]+nums[second]+nums[third];
                int cur_dif = target-cur_sum;
                if (dif>Math.abs(cur_dif)){
                    dif = Math.abs(cur_dif);
                    result = cur_sum;
                }
                if (cur_dif==0){
                    return cur_sum;
                }else if (cur_dif>0){//cur_sum is small , so second need move to right
                    continue;
                }else {
                    third--;
                    second--;
                }
            }
        }

        return result;
    }
}

57、两两交换链表中的节点

小黄做的算法题_第38张图片提示:

链表中节点的数目在范围 [0, 100] 内
0 <= Node.val <= 100

思路:
用栈,先放两个进去,再倒出来

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
public static ListNode swapPairs(ListNode head) {
        Stack<ListNode> stack = new Stack<>();
        ListNode headCur = new ListNode(0);
        ListNode result = headCur;
        ListNode cur = head;
        while (cur!=null){
            for (int i = 0; cur!=null&&i < 2; i++) {
                stack.push(cur);
                cur = cur.next;
            }
            while (!stack.isEmpty()){
                headCur.next = stack.pop();
                headCur = headCur.next;
            }
        }
        headCur.next=null;
        return result.next;
    }
}

递归的代码

找终止条件:本题终止条件很明显,当递归到链表为空或者链表只剩一个元素的时候,没得交换了,自然就终止了。
找返回值:返回给上一层递归的值应该是已经交换完成后的子链表。
单次的过程:因为递归是重复做一样的事情,所以从宏观上考虑,只用考虑某一步是怎么完成的。我们假设待交换的俩节点分别为head和next,next的应该接受上一级返回的子链表(参考第2步)。就相当于是一个含三个节点的链表交换前两个节点,就很简单了,想不明白的画画图就ok。
class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }
        ListNode next = head.next;
        head.next = swapPairs(next.next);
        next.next = head;
        return next;
    }
}

58、电话号码的字母组合

小黄做的算法题_第39张图片思路:dfs回溯
new一个LinkedList<>();保存当前结果
把所有结果放进new ArrayList<>()里

class Solution {
    String[] stringMap = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
    public List<String> letterCombinations(String digits) {
        List<String> list = new ArrayList<>();
        if (digits.length()==0){
            return list;
        }
        LinkedList<Character> linkedList = new LinkedList<>();
        dfs(list,digits,linkedList,0);
        return list;
    }

    private void dfs(List<String> list,String digits,LinkedList<Character> linkedList,int index) {
        if (index>=digits.length()){//the situation of end
            StringBuilder sb = new StringBuilder();
            Iterator<Character> iterator = linkedList.iterator();
            while (iterator.hasNext()){
                sb.append(iterator.next());
            }
            list.add(sb.toString());
            return;
        }
        int i = digits.charAt(index) - 48;
        String s = stringMap[i];
        for (int j = 0; j < s.length(); j++) {
            linkedList.add(s.charAt(j));
            dfs(list,digits,linkedList,index+1);
            linkedList.removeLast();
        }
    }
}

59、括号生成

小黄做的算法题_第40张图片
思路:所有的括号组合都是由一对()派生出来的。
如n=2,就是在()的里面加个()变成(()),或者()里)的右面加()变成()()。
n=3时,在n=2的基础上在(())和 ()()的左括号前后加括号。
所以用递归最合适了。
最后用set去重。

class Solution {
    public List<String> generateParenthesis(int n) {
        ArrayList<String> list = new ArrayList<>();
        String[] parenthesis = getParenthesis(n);
        HashSet<String> set = new HashSet<>();
        for (int i = 0; i < parenthesis.length; i++) {
            set.add(parenthesis[i]);
        }
        Iterator<String> iterator = set.iterator();
        while (iterator.hasNext()){
            list.add(iterator.next());
        }
        return list;
    }

    private String[] getParenthesis(int n) {
        if (n==1){//终止条件
            return new String[]{"()"};
        }
        //迭代体
        String[] parenthesis = getParenthesis( n - 1);
        ArrayList<String> builderArrayList = new ArrayList<>();
        for (int i = 0; i < parenthesis.length; i++) {
            String p = parenthesis[i];

            for (int j = 0; j < p.length(); j++) {
                if (p.charAt(j)==')'){
                    StringBuilder sb1 = new StringBuilder(p);
                    StringBuilder sb2 = new StringBuilder(p);
                    sb1.insert(j, "()");//在)前加括号
                    sb2.insert(j + 1, "()");//在)后加括号
                    builderArrayList.add(sb1.toString());
                    builderArrayList.add(sb2.toString());
                }
            }
        }
        String[] strings = new String[builderArrayList.size()];
        for (int i = 0; i < builderArrayList.size(); i++) {
            strings[i] = builderArrayList.get(i);
        }
        return strings;
    }
}

60 两数相除

小黄做的算法题_第41张图片
思路:
/**
* 解题思路:这题是除法,所以先普及下除法术语
* 商,公式是:(被除数-余数)÷除数=商,记作:被除数÷除数=商…余数,是一种数学术语。
* 在一个除法算式里,被除数、余数、除数和商的关系为:(被除数-余数)÷除数=商,记作:被除数÷除数=商…余数,
* 进而推导得出:商×除数+余数=被除数。
*
* 要求商,我们首先想到的是减法,能被减多少次,那么商就为多少,但是明显减法的效率太低
*
* 那么我们可以用位移法,因为计算机在做位移时效率特别高,向左移1相当于乘以2,向右位移1相当于除以2
*
* 我们可以把一个dividend(被除数)先除以2n,n最初为31,不断减小n去试探,当某个n满足dividend/2n>=divisor时,
*
* 表示我们找到了一个足够大的数,这个数divisor是不大于dividend的,所以我们就可以减去2^n个divisor,以此类推
*
* 我们可以以100/3为例
*
* 2n是1,2,4,8…231这种数,当n为31时,这个数特别大,100/2^n是一个很小的数,肯定是小于3的,所以循环下来,
*
* 当n=5时,100/32=3, 刚好是大于等于3的,这时我们将100-32
3=4,也就是减去了32个3,接下来我们再处理4,同样手法可以再减去一个3
*
* 所以一共是减去了33个3,所以商就是33
*
* 这其中得处理一些特殊的数,比如divisor是不能为0的,Integer.MIN_VALUE和Integer.MAX_VALUE
*
*/

代码

public int divide(int dividend, int divisor) {
        if (dividend == 0) {
            return 0;
        }
        if (dividend == Integer.MIN_VALUE && divisor == -1) {
            return Integer.MAX_VALUE;
        }
        boolean negative;
        negative = (dividend ^ divisor) <0;//用异或来计算是否符号相异
        long t = Math.abs((long) dividend);
        long d= Math.abs((long) divisor);
        int result = 0;
        for (int i=31; i>=0;i--) {
            if ((t>>i)>=d) {//找出足够大的数2^n*divisor
                result+=1<<i;//将结果加上2^n
                t-=d<<i;//将被除数减去2^n*divisor
            }
        }
        return negative ? -result : result;//符号相异取反
    }

61、搜索旋转排序数组

小黄做的算法题_第42张图片
思路 ://如果中间的数小于最右边的数,则右半段是有序的,
// 若中间数大于最右边数,则左半段是有序的,
// 我们只要在有序的半段里用首尾两个数组来判断目标值是否在这一区域内,
// 这样就可以确定保留哪半边了

public class Solution {
    public int search(int[] nums, int target) {
        int len = nums.length;
        int left = 0, right = len-1;
        while(left <= right){
            int mid = (left + right) / 2;
            if(nums[mid] == target)
                return mid;
            else if(nums[mid] < nums[right]){//说明左边有序
                if(nums[mid] < target && target <= nums[right])//判断target是否在左边
                    left = mid+1;
                else
                    right = mid-1;
            }
            else{
                if(nums[left] <= target && target < nums[mid])
                    right = mid-1;
                else
                    left = mid+1;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int search = solution.search(new int[]{4, 5, 6, 7, 0, 1, 2}, 0);
        System.out.println(search);
    }
}

62、全排列

小黄做的算法题_第43张图片
思路 :就是用dfs就行了,关键点是要判重,用List的boolean contains(Object o);这个api。
代码:

public class Solution {
    private List<List<Integer>> list = new ArrayList();
    public List<List<Integer>> permute(int[] nums) {
        LinkedList<Integer> linkedList = new LinkedList<>();
        dfs(nums,linkedList);
        return list;
    }

    private void dfs(int[] nums, LinkedList<Integer> linkedList) {
        if (linkedList.size()==nums.length){
            list.add(new ArrayList<Integer>(linkedList));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if (linkedList.contains(nums[i])){
                continue;
            }
            linkedList.addLast(nums[i]);
            dfs(nums,linkedList);
            linkedList.pollLast();
        }
    }
    
    public static void main(String[] args) {
        Solution solution = new Solution();
        List<List<Integer>> permute = solution.permute(new int[]{1, 2, 3});
        for (List<Integer> x:
             permute) {
            for (Integer y :
                    x) {
                System.out.print(y);
            }
            System.out.println(" ");
        }
    }
}

你可能感兴趣的:(JAVA,算法)