Java程序员面试需要注意啥?面试常见手撕模板题以及笔试模板总结

一. 目录

  1. 排序
  2. 二分
  3. 二叉树非递归遍历
  4. 01背包
  5. 最长递增子序列
  6. 最长公共子序列
  7. 最长公共子串
  8. 大数加法
  9. 大数乘法
  10. 大数阶乘
  11. 全排列
  12. 子集
  13. N皇后
  14. 并查集
  15. 树状数组
  16. 线段树
  17. 字典树
  18. 单调栈
  19. 单调队列
  20. KMP
  21. Manacher算法
  22. 拓扑排序
  23. 最小生成树
  24. 最短路
  25. 欧拉回路
  26. GCD和LCM
  27. 素数筛法
  28. 唯一分解定理
  29. 乘法快速幂
  30. 矩阵快速幂

二. 面试常见手撕模板题以及笔试模板总结

0. Java快速输入

先给一个干货,可能有些题用Java会超时(很少),下面是Petr刷题时的模板,一般用了这个就不会出现C++能过Java不能过的情况了。

import java.io.*;
import java.util.*;
public class Main {
    static class FR {
        BufferedReader br;
        StringTokenizer tk;
        FR(InputStream stream) {
            br = new BufferedReader(new InputStreamReader(stream), 32768);
            tk = null;
        }
        String next() {
            while (tk == null || !tk.hasMoreElements()) {
                try {
                    tk = new StringTokenizer(br.readLine());
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return tk.nextToken();
        }
        int nextint() {
            return Integer.parseint(next());
        }
    }
    static void solve(InputStream stream, PrintWriter out) {
        FR in = new FR(stream);
        //  start code.....
    }
    public static void main(String[] args) {
        OutputStream os = System.out;
        InputStream is = System.in;
        PrintWriter out = new PrintWriter(os);
        solve(is, out);
        out.close();
        // 不关闭就没有输出
    }
}

1. 排序

先给出一个swap函数,代表交换数组两个位置的值,很多排序用到这个函数:

static void swap(int[] arr, int a, int b){
    int t = arr[a];
    arr[a] = arr[b];
    arr[b] = t;
}

面试主要考察比较排序(O(N^2)、O(NlogN))排序(非比较排序可以看下面的详细总结)。

①. 冒泡

static void bubbleSort(int[] arr){
    for (int end = arr.length - 1; end > 0; end--){
        Boolean isSort = true;
        for (int i = 0; i < end; i++){
            if(arr[i] > arr[i+1]) {
                swap(arr, i, i + 1);
                isSort = false;
            }
        }
        if(isSort) break;
    }
}

②. 选择

static void selectSort(int[] arr){
    for (int i = 0; i < arr.length; i++){
        int minIdx = i;
        for (int j = i + 1; j < arr.length; j++) minIdx = arr[j] < arr[minIdx] ? j : minIdx;
        swap(arr, minIdx, i);
    }
}

③. 插入

// 几个边界: i=1开始(不是必须)、j >= 0, arr[j+1] = key注意一下
static void insertSort(int[] arr) {
    for (int i = 1; i < arr.length; i++) {
        int key = arr[i], j;
        for (j = i - 1; j >= 0 && arr[j] > key; j--) arr[j + 1] = arr[j];
        arr[j + 1] = key;
    }
}

第二种写法:

// 边界 j > 0
static void insertSort2(int[] arr) {
    for (int i = 1; i < arr.length; i++) {
        for (int j = i; j > 0 && arr[j - 1] > arr[j]; j--) swap(arr, j, j - 1);
    }
}

二分插入排序:

// 注意 R = i-1,注意找第一个>=key的,注意arr[i]先用key保存
static void binaryInsertSort(int[] arr) {
    for (int i = 1; i < arr.length; i++) {
        int L = 0, R = i - 1;
        // 找第一个大于的 二分边界搞不清的看下面的二分链接
        int key = arr[i];
        while (L <= R) {
            int m = L + (R - L) / 2;
            if (arr[m] > arr[i]) {
                R = m - 1;
            } else {
                L = m + 1;
            }
        }
        for (int j = i - 1; j >= L; j--) arr[j + 1] = arr[j];
        arr[L] = key;
    }
}

④. 希尔排序

采取的是增量序列每次减半的策略。

static void shellSort(int[] arr) {
    for (int g = arr.length; g > 0; g /= 2) { // 增量序列 gap
        for (int end = g; end < arr.length; end++) { // 每一个组的结束元素, 从数组第gap个元素开始
            // 每组做插入排序
            int key = arr[end], i;
            for (i = end - g; i >= 0 && key < arr[i]; i -= g) arr[i + g] = arr[i];
            arr[i + g] = key;
        }
    }
}

⑤. 快排

给出的是三路快排

static void quickSort(int[] arr){
    if(arr == null || arr.length == 0) return;
    quickRec(arr, 0, arr.length - 1);
}

static void quickRec(int[] arr, int L, int R) {
    if (L >= R) return;
    swap(arr, L, L + (int) (Math.random() * (R - L + 1)));
    int[] p = partition(arr, L, R);
    quickRec(arr, L, p[0] - 1);
    quickRec(arr, p[1] + 1, R);
}

// 用arr[L]作为划分点
static int[] partition(int[] arr, int L, int R) {
    int key = arr[L];
    int less = L, more = R + 1;
    int cur = L + 1;
    while (cur < more) {
        if (arr[cur] < key) {
            swap(arr, ++less, cur++);
        } else if (arr[cur] > key) {
            swap(arr, --more, cur);
        } else {
            cur++;
        }
    }
    swap(arr, L, less);
    // 返回相等的两个下标, less位置是我最后交换过来的划分值,more位置是>的,所以返回more-1
    return new int[]{less, more - 1};
}

⑥. 归并排序

static void mergeSort(int[] arr){
    if(arr == null || arr.length == 0) return;
    mergeRec(arr, 0, arr.length - 1);
}

//注意是mergeSort(arr, L, m); 不是mergeSort(arr, L, m-1)
static void mergeRec(int[] arr, int L, int R) {
    if (L >= R) return;
    int m = L + (R - L) / 2;
    mergeRec(arr, L, m);
    mergeRec(arr, m + 1, R);
    merge(arr, L, m, R);
}

static void merge(int[] arr, int L, int mid, int R) {
    int[] h = new int[R - L + 1];
    int p1 = L, p2 = mid + 1;
    int k = 0;
    while (p1 <= mid && p2 <= R)
        h[k++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];  // 注意保证稳定性
    while (p1 <= mid) h[k++] = arr[p1++];
    while (p2 <= R) h[k++] = arr[p2++];
    for (int i = 0; i < k; i++) arr[L + i] = h[i];
}

非递归归并排序:

static void mergeSortBU(int[] arr) {
    for (int sz = 1; sz <= arr.length; sz += sz) { // 区间的个数,1..2..4..8
        for (int i = 0; sz + i < arr.length; i += sz + sz) {  // 对[i...i+sz-1]和[i+sz...i+2*sz-1]内归并
            merge(arr, i, i + sz - 1, Math.min(arr.length - 1, i + 2 * sz - 1)); // min防止越界
        }
    }
}

⑦. 堆排

// if(arr == null || arr.length <= 1) return; 是必须的
static void heapSort(int[] arr) {
    if (arr == null || arr.length <= 1) return;
    for (int i = 0; i < arr.length; i++) siftUp(arr, i);//上浮方式建堆
    int size = arr.length - 1;
    swap(arr, 0, size);
    while (size > 0) {
        siftDown(arr, 0, size);
        swap(arr, 0, --size);
    }
}

// 上浮
static void siftUp(int[] arr, int i) {
    while (arr[i] > arr[(i - 1) / 2]) {
        swap(arr, i, (i - 1) / 2);
        i = (i - 1) / 2;
    }
}

// 下沉
static void siftDown(int[] arr, int i, int heapSize) {
    int L = 2 * i + 1;
    while (L < heapSize) {
        int maxIndex = L + 1 < heapSize && arr[L + 1] > arr[L] ? L + 1 : L;
        maxIndex = arr[i] > arr[maxIndex] ? i : maxIndex;
        if (maxIndex == i) break;
        swap(arr, i, maxIndex);
        i = maxIndex;
        L = 2 * i + 1;
    }
}

第二种方式,使用heapfiy的优化,只需要使用siftDown过程即可。

// 注意这里是size+1,因为这个不是交换了最后一个,所以要考虑arr[size],下面不要考虑arr[size]
//   if (arr == null || arr.length <= 1) return; 是必须的
static void heapSort2(int[] arr) {
    if (arr == null || arr.length <= 1) return;
    int size = arr.length - 1;
    for (int i = (size - 1) / 2; i >= 0; i--)
        siftDown(arr, i, size + 1);
    swap(arr, 0, size);
    while (size > 0) {
        siftDown(arr, 0, size);
        swap(arr, 0, --size);
    }
}

其中siftDown过程也可以使用递归的写法:

static void siftDown(int[] arr, int i, int heapSize) { //从arr[i] 开始往下调整
    int L = 2 * i + 1;
    int R = 2 * i + 2;
    int maxIdx = i;
    if (L < heapSize && arr[L] > arr[maxIdx]) maxIdx = L;
    if (R < heapSize && arr[R] > arr[maxIdx]) maxIdx = R;
    if (maxIdx != i) {
        swap(arr, i, maxIdx);
        siftDown(arr, maxIdx, heapSize);
    }
}

2. 二分

二分最主要的就是边界问题:

  • 第一个=key的,不存在返回-1;
  • 第一个>=key的;
  • 第一个>key的;
  • 最后一个=key的;
  • 最后一个<=key的;
  • 最后一个<key的;

最基本的二分查找:

static int b(int[] arr, int key){
    int L = 0, R = arr.length - 1;
    while(L <= R){
        int mid = L + (R - L) / 2;
        if(arr[mid] == key) return mid;
        if(arr[mid] > key)
            R = mid - 1;
        else
            L = mid + 1;
    }
    return -1;
}

口诀: 左边的先R(if后的),右边的先L。

查找第一个=key的,不存在返回-1:

// 左边的三个, 注意是L < arr.length
static int firstEqual(int[] arr, int key){
    int L = 0, R = arr.length - 1;
    while(L <= R){
        int mid = L + (R - L) / 2;
        if(arr[mid] >= key)
            R = mid - 1;
        else
            L = mid + 1;
    }
    if(L < arr.length && arr[L] == key) return L;
    return -1;
}

第一个>=key的:

static int firstLargeEqual(int[] arr, int key){
    int L = 0, R = arr.length - 1;
    while(L <= R){
        int mid = L + (R - L) / 2;
        if(arr[mid] >= key)
            R = mid - 1;
        else
            L = mid + 1;
    }
    return L;
}

第一个>key的:

static int firstLarge(int[] arr, int key){
    int L = 0, R = arr.length - 1;
    while(L <= R){
        int mid = L + (R - L) / 2;
        if(arr[mid] > key) // 因为是第一个> 的,所以>
            R = mid - 1;
        else
            L = mid + 1;
    }
    return L;
}

最后一个=key的:

// 右边的三个 注意是 R>=0
static int lastEqual(int[] arr, int key){
    int L = 0, R = arr.length - 1;
    while(L <= R){
        int mid = L + (R - L) / 2;
        if(arr[mid] <= key)
            L = mid + 1;
        else
            R = mid - 1;
    }
    if(R >= 0 && arr[R] == key)
        return R;
    return -1;
}

最后一个<=key的:

static int lastEqualSmall(int[] arr, int key){
    int L = 0, R = arr.length - 1;
    while(L <= R){
        int mid = L + (R - L) / 2;
        if(arr[mid] <= key)
            L = mid + 1;
        else
            R = mid - 1;
    }
    return R;
}

最后一个<key的:

static int lastSmall(int[] arr, int key){
    int L = 0, R = arr.length - 1;
    while(L <= R){
        int mid = L + (R - L) / 2;
        if(arr[mid] < key)
            L = mid + 1;
        else
            R = mid - 1;
    }
    return R;
}

3. 二叉树非递归遍历

这里给出一种非递归前序遍历、非递归中序、以及两种非递归后序遍历。

public class M3_BinaryTree {

    static PrintStream out = System.out; //打印

    static class Node{
        int val;
        Node left;
        Node right;

        public Node(int val) {
            this.val = val;
        }
    }

    static Node buildTree(int[] arr, int i){
        if(i >= arr.length || arr[i] == -1) return null;
        Node root = new Node(arr[i]);
        root.left = buildTree(arr, i * 2 + 1);
        root.right = buildTree(arr, i * 2 + 2);
        return root;
    }

    static Node buildTree(Scanner in){
        Node root = null;
        int val = in.nextInt();
        if(val != -1){
            root = new Node(val);
            root.left = buildTree(in);
            root.right = buildTree(in);
        }
        return root;
    }

    //前序
    static void preOrder(Node root){
        if(root == null) return;
        Stack<Node> stack = new Stack<>();
        Node p = root;
//        stack.push(root); // wrong
        while(p != null || !stack.isEmpty()){
            while(p != null){
                out.print(p.val + " ");
                stack.push(p); // 注意先推入
                p = p.left;
            }
            p = stack.pop();
            p = p.right;
        }
        out.println();
    }

    //中序
    static void inOrder(Node root){
        if(root == null) return;
        Stack<Node> stack = new Stack<>();
        Node p = root;
        while(p != null || !stack.isEmpty()){
            while(p != null){
                stack.push(p);
                p = p.left;
            }
            p = stack.pop();
            out.print(p.val + " ");
            p = p.right;
        }
        out.println();
    }


    //后序第一种: 双栈: 可以实现 中-> 右-> 左, 然后再用一个栈逆转即可
    static void postOrder(Node root){
        if(root == null) return;
        Node p = root;
        Stack<Node> s1 = new Stack<>();
        Stack<Node> s2 = new Stack<>();
        s1.push(root);
        while(!s1.isEmpty()){
            Node cur = s1.pop();
            s2.push(cur);
            if(cur.left != null ) s1.push(cur.left);
            if(cur.right != null ) s1.push(cur.right);
        }
        while(!s2.isEmpty()) out.print(s2.pop().val + " ");
        out.println();
    }


    // 后序第二种pre
    static void postOrder2(Node root){
        Stack<Node> s = new Stack<>();
        s.push(root);
        Node pre = null;
        while(!s.isEmpty()){
            Node cur = s.peek();
            if((cur.left == null && cur.right == null) ||
                    (pre != null && (pre == cur.left || pre == cur.right))){
                out.print(cur.val + " ");
                s.pop();
                pre = cur;
            }else {
                if(cur.right != null) s.push(cur.right);
                if(cur.left != null) s.push(cur.left);
            }
        }
        out.println();
    }

    public static void main(String[] args){
        //int[] arr = {1,2,3,4,5,6,7,8,-1,9,-1,10,-1,11,-1, -1,-1,-1,-1,-1,-1,-1,-1}; // 和下面一样
        int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, -1, 9, -1, 10, -1, 11, -1};
        Node root = buildTree(arr, 0);

        preOrder(root);
        inOrder(root);
        postOrder(root);
        postOrder2(root);

//      Scanner in = new Scanner(new BufferedInputStream(System.in));
//      树结构和上面相同,输入: 1 2 4 8 -1 -1 -1 5 9 -1 -1 -1 3 6 10 -1 -1 -1 7 11 -1 -1 -1
//      Node root2 = buildTree(in);
    }
}

4. 01背包

这个在笔试题中可能会出现,有时候只是出题场景不同。

import java.io.*;
import java.util.*;

/**
 * 01背包
 * 题目: http://acm.hdu.edu.cn/showproblem.php?pid=2602
 */
public class M4_Knapsack {

    static int n, C;
    static int[] w, v;
    static int[][] dp;

    //记忆化
    static int rec(int p, int curW) {
        if (p == n)
            return 0;
        if (dp[p][curW] != -1) return dp[p][curW];
        if (curW + w[p] > C)
            return dp[p][curW] = rec(p + 1, curW);
        else
            return dp[p][curW] = Math.max(rec(p + 1, curW + w[p]) + v[p],
                    rec(p + 1, curW));
    }

    // 普通
    static int dp(){
        int[][] dp = new int[n+1][C+1];
        for(int i = n - 1; i >= 0; i--){
            for(int j = 0; j <= C; j++){
                dp[i][j] = j + w[i] > C ? dp[i+1][j] :
                        Math.max(dp[i+1][j], dp[i+1][j+w[i]]+v[i]);
            }
        }
        return dp[0][0];
    }

    // 二维滚动
    static int dp2(){
        int[][] dp = new int[2][C+1];
        for(int i = n - 1; i >= 0; i--){
            for(int j = 0; j <= C; j++){
                dp[i&1][j] = j + w[i] > C ? dp[(i+1)&1][j] :
                        Math.max(dp[(i+1)&1][j], dp[(i+1)&1][j+w[i]]+v[i]);
            }
        }
        return dp[0][0];
    }

    // 一维dp
    static int dp3(){
        int[] dp = new int[C + 1];
        for (int i = n - 1; i >= 0; i--) {
            for (int j = 0; j <= C; j++) { // 注意顺序一定要这样
                dp[j] = j + w[i] > C ? dp[j] : Math.max(dp[j], dp[j + w[i]] + v[i]);
            }
        }
        return dp[0];
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(new BufferedInputStream(System.in));
        PrintWriter out = new PrintWriter(System.out);
        int T = in.nextInt();
        for (int t = 0; t < T; t++) {
            n = in.nextInt();
            C = in.nextInt();
            w = new int[n];
            v = new int[n];
            for (int i = 0; i < n; i++) v[i] = in.nextInt();
            for (int i = 0; i < n; i++) w[i] = in.nextInt();
            dp = new int[n][C + 1];
            for (int i = 0; i < n; i++) Arrays.fill(dp[i], -1);
//            out.println(rec(0, 0));
//            out.println(dp());
//            out.println(dp2());
            out.println(dp3());
            out.flush();
        }
        out.close();
    }
}

5. 最长递增子序列

这个也是笔试中可能出现变种的题目的。

import java.io.PrintWriter;
import java.util.Arrays;

/**
 * 最长公共子序列
 * 题目: https://leetcode-cn.com/problems/longest-increasing-subsequence/
 */
public class M5_LIS {

    // O(N^2)
    public int lengthOfLIS(int[] nums){
        if(nums == null || nums.length == 0) return 0;
        int[] dp = new int[nums.length];
        int res = 1;
        for(int i = 0; i < nums.length; i++){
            dp[i] = 1;
            for(int j = 0; j < i; j++){
                if(nums[j] < nums[i])
                    dp[i] = Math.max(dp[i], dp[j] + 1);
            }
            res = Math.max(res, dp[i]);
        }
        return res;
    }

    // O(N^2)
    static int[] getDp(int[] nums){
        if(nums == null || nums.length == 0) return new int[]{};
        int[] dp = new int[nums.length];
        for(int i = 0; i < nums.length; i++){
            dp[i] = 1;
            for(int j = 0; j < i; j++){
                if(nums[j] < nums[i])
                    dp[i] = Math.max(dp[i], dp[j] + 1);
            }
        }
        return dp;
    }

    static int[] getLIS(int[] arr, int[] dp){
        int maxLen = 0, end = 0;
        for(int i = 0; i < dp.length; i++) if(dp[i] > maxLen){
            maxLen = dp[i];
            end = i;
        }
        int[] lis = new int[maxLen];
        lis[--maxLen] = arr[end];
        for(int i = end - 1; i >= 0; i--){
            if(dp[i] == dp[end] - 1 && arr[i] < arr[end]){
                lis[--maxLen] = arr[i];
                end = i;
            }
        }
        return lis;
    }

    // O(N * logN)
    public int lengthOfLIS2(int[] nums) {
        if (nums == null || nums.length == 0)
            return 0;
        int[] dp = new int[nums.length];
        int[] ends = new int[nums.length + 1];
        dp[0] = 1;
        ends[1] = nums[0];
        int right = 1;  // [1~right]为有效区 ends数组是有序的(升序), right是右边界
        int L, mid, R;
        for (int i = 1; i < nums.length; i++) {
            L = 1;
            R = right;
            // 找到第一个>=arr[i]的,返回结果是 L
            while (L <= R) {
                mid = L + (R - L) / 2;
                if (ends[mid] >= nums[i])
                    R = mid - 1;
                else
                    L = mid + 1;
            }
            // 说明以arr[i]以arr[i]结尾的最长递增子序列=ends区有效长度+1
            if (L > right) { //没有找到arr[i]是最长的 (因为下标从1开始,所以判断是>right),
                dp[i] = right + 1;
                ends[right + 1] = nums[i]; // 扩大ends数组
                right += 1;  //扩大有效区
            } else {  // 找到了arr[l] > arr[i], 更新end[l] = arr[i] ,表示l长度的最长子序列结尾可以更新为arr[i]
                dp[i] = right; // dp[i]还是没有加长
                ends[L] = nums[i];
            }
        }
        return right;
    }


    public static void main(String[] args){
        PrintWriter out = new PrintWriter(System.out);
        int[] arr = {10,9,2,5,3,7,101,18};
        out.println(Arrays.toString(getLIS(arr, getDp(arr))));

        out.close();
    }
}

6. 最长公共子序列

也是笔试中可能出现的经典DP题目。

import java.io.BufferedInputStream;
import java.util.Scanner;

/**
 * 最长公共子串
 * 题目: http://www.51nod.com/Challenge/Problem.html#!#problemId=1006
 */
public class M6_LCS {

    /**
     * dp[i][j]代表的是 str[0..i]与str[0...j]的最长公共子序列
     */
    static int[][] getDp(char[] s1, char[] s2) {
        int n1 = s1.length, n2 = s2.length;
        int[][] dp = new int[n1][n2];
        dp[0][0] = s1[0] == s2[0] ? 1 : 0;
        for (int i = 1; i < n1; i++)  // 一旦dp[i][0]被设置成1,则dp[i~N-1][0]都为1
            dp[i][0] = Math.max(dp[i - 1][0], s1[i] == s2[0] ? 1 : 0);
        for (int j = 1; j < n2; j++)
            dp[0][j] = Math.max(dp[0][j - 1], s2[j] == s1[0] ? 1 : 0);

        for (int i = 1; i < n1; i++) {
            for (int j = 1; j < n2; j++) {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                if (s1[i] == s2[j]) {
                    dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - 1] + 1);
                }
            }
        }
        return dp;
    }

    static String getLCS(char[] s1, char[] s2, int[][] dp) {
        if(s1 == null || s1.length == 0 || s2 == null || s2.length == 0)
            return "";
        int i = s1.length - 1;
        int j = s2.length - 1;
        char[] res = new char[dp[i][j]]; //生成答案的数组
        int index = dp[i][j] - 1;
        while (index >= 0) {
            if (i > 0 && dp[i][j] == dp[i - 1][j]) {
                i--;
            } else if (j > 0 && dp[i][j] == dp[i][j - 1]) {
                j--;
            } else { // dp[i][j] = dp[i-1][j-1]+1
                res[index--] = s1[i];
                i--;
                j--;
            }
        }
        return String.valueOf(res);
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(new BufferedInputStream(System.in));
        char[] s1 = in.next().toCharArray();
        char[] s2 = in.next().toCharArray();

        int[][] dp = getDp(s1, s2);
//        System.out.println(dp[s1.length-1][s2.length-1]); //length of lcs
        System.out.println(getLCS(s1, s2, dp));
    }
}

7. 最长公共子串

同理,也是可能出现的。

/**
 * 最长公共子串问题
 */
public class M7_LSS {

    public int findLongest(String A, int n, String B, int m) {
        char[] s1 = A.toCharArray();
        char[] s2 = B.toCharArray();
        int[][] dp = new int[s1.length][s2.length];
        for (int i = 0; i < s1.length; i++) //注意和最长公共子序列有点不同
            dp[i][0] = s1[i] == s2[0] ? 1 : 0;
        for (int j = 0; j < s2.length; j++)
            dp[0][j] = s1[0] == s2[j] ? 1 : 0;
        int res = 0;
        for (int i = 1; i < s1.length; i++) {
            for (int j = 1; j < s2.length; j++) {
                if (s1[i] == s2[j]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                    res = Math.max(res, dp[i][j]);
                }
            }
        }
        return res;  //dp数组中的最大值,就是最大公共字串的长度
    }

    static int[][] getDp(char[] s1, char[] s2) {
        int[][] dp = new int[s1.length][s2.length];
        for (int i = 0; i < s1.length; i++) //注意和最长公共子序列有点不同
            dp[i][0] = s1[i] == s2[0] ? 1 : 0;
        for (int j = 0; j < s2.length; j++)
            dp[0][j] = s1[0] == s2[j] ? 1 : 0;
        int res = 0;
        for (int i = 1; i < s1.length; i++) {
            for (int j = 1; j < s2.length; j++) {
                if (s1[i] == s2[j]) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                    res = Math.max(res, dp[i][j]);
                }
            }
        }
        System.out.println(res);  //4
        return dp;  //dp数组中的最大值,就是最大公共字串的长度
    }

    /**
     * 根据dp表得到答案
     */
    static String getLongestSubstring(String s1, String s2, int[][] dp) {
        if (s1 == null || s2 == null || s1.length() == 0 || s2.length() == 0)
            return "";
        int max = 0, end = 0;
        for (int i = 0; i < dp.length; i++) {
            for (int j = 0; j < dp[0].length; j++) {
                if (dp[i][j] > max) {
                    max = dp[i][j];
                    end = i;
                }
            }
        }
        return s1.substring(end - max + 1, end + 1);
    }

    // 空间优化
    public int findLongest(String A, String B) {
        char[] s1 = A.toCharArray();
        char[] s2 = B.toCharArray();
        int row = 0, col = s2.length - 1; //从右上角开始
        int max = 0, end = 0;     //记录最大长度和结束位置
        while (row < s1.length) {
            int i = row, j = col;
            int ul = 0;
            while (i < s1.length && j < s2.length) {
                if (s1[i] == s2[j])
                    ul++;
                else
                    ul = 0;
                if (ul > max) {
                    max = ul;
                    end = i;
                }
                i++;
                j++;
            }
            if (col > 0) // 还没到最左边 --> 往左移动
                col--;
            else
                row++;  //到了最左  --> 往下移动
        }
        return max;
        //return sa.substring(end-max+1, end+1); // [end-max+1, end] 返回公共子串
    }
}

8. 大数加法

这个在拼多多的笔试中就出现过。。。

public class M8_BigAdd {

    //大数加法
    static String add(String str1, String str2){
        char[] s1 = str1.toCharArray();
        char[] s2 = str2.toCharArray();
        int n1 = s1.length, n2 = s2.length;
        int maxL = Math.max(n1, n2);
        int[] a = new int[maxL + 1];//注意a,b的数组大小都必须是maxL+1
        int[] b = new int[maxL + 1];
        for(int i = 0; i < n1; i++) a[i] = s1[n1 - i - 1] - '0';
        for(int i = 0; i < n2; i++) b[i] = s2[n2 - i - 1] - '0';
        for(int i = 0; i < maxL; i++){
            if(a[i] + b[i] >= 10){
                int tmp = a[i] + b[i];//注意一定要先抽取出来
                a[i] = tmp%10;
                a[i+1] += tmp/10;
            }else
                a[i] += b[i];
        }
        StringBuilder sb = new StringBuilder();
        if(a[maxL] != 0) sb.append((char)(a[maxL] + '0'));
        for(int i = maxL-1; i >= 0; i--) sb.append((char)(a[i] + '0'));
        return sb.toString();
    }
}

9. 大数乘法

也不难。

public class M9_BigMul {

    // 大数乘法
    static String mul(String str1, String str2){
        char[] s1 = str1.toCharArray();
        char[] s2 = str2.toCharArray();
        int n1 = s1.length, n2 = s2.length;
        int[] a = new int[n1];
        int[] b = new int[n2];
        int[] c = new int[n1 + n2];
        for(int i = 0; i < n1; i++) a[i] = s1[n1 - i - 1] - '0';
        for(int i = 0; i < n2; i++) b[i] = s2[n2 - i - 1] - '0';
        for(int i = 0; i < n1; i++){
            for(int j = 0; j < n2; j++){
                c[i+j] += a[i] * b[j];
            }
        }
        for(int i = 0; i < n1 + n2 - 1; i++){
            if(c[i] >= 10){
                c[i+1] += c[i]/10;
                c[i] %= 10;
            }
        }
        int i;
        for(i = n1 + n2 - 1; i >= 0; i--) if(c[i] != 0) break;
        StringBuilder sb = new StringBuilder();
        for(; i >= 0; i--) sb.append( (char)(c[i] + '0'));
        return sb.toString();
    }
}

10. 大数阶乘

这个稍微特殊一点。我这里简单讲一下,举个例子结合代码就懂了。

比如算50的阶乘:

  • 我们要先从1开始乘:1*2=2,将2存到a[0]中;
  • 接下来是用a[0]3;23=6,将6储存在a[0]中;
  • 接下来是用a[0]4;64=24,是两位数,那么24%10==4存到a[0]中,24/10==2存到a[1]中;
  • 接下来是用a[0]5;a[1]5+num(如果前一位相乘结果位数是两位数,那么num就等于十位上的那个数字;如果是一位数,num==0);24*5=120,是三位数,那么120%10==0存到a[0]中,120/10%10==2存到a[1]中,120/100==1存到a[2]中;
  • 接下来是用a[0]6、a[1]6+num、a[2]6+num、1206=720,那么720%10==0存到a[0]中,720/10%10==2存到a[1]中,720/100==7存到a[2]中。。。

代码:

/**
 * 题目链接:
 * http://nyoj.top/problem/28
 */
public class M10_BigPow {

    //大数计算阶乘位数,可以自己在网上找一下博客
    //lg(N!)=[lg(N*(N-1)*(N-2)*......*3*2*1)]+1 = [lgN+lg(N-1)+lg(N-2)+......+lg3+lg2+lg1]+1;
    static int factorialDigit(int n) {
        double sum = 0;
        for (int i = 1; i <= n; i++)
            sum += Math.log10(i);
        return (int) sum + 1;
    }

    static String bigFactorial(int n) {
        int[] res = new int[100001];
        int digit = 1;
        res[0] = 1;
        for (int i = 2; i <= n; i++) {
            int carry = 0;
            for (int j = 0; j < digit; j++) {
                int temp = res[j] * i + carry; //每一位的运算结果
                res[j] = temp % 10;   //将最低位保留在原位置
                carry = temp / 10;   //计算进位, 等下这个进位会累加到j+1
            }
            while (carry != 0) {
                res[digit] = carry % 10;
                carry /= 10;
                digit++;
            }
        }
        StringBuilder sb = new StringBuilder();
        for (int i = digit - 1; i >= 0; i--) sb.append( (char)(res[i] + '0'));
        return sb.toString();
    }

    public static void main(String[] args){
        System.out.println(bigFactorial(5));
    }
}

写在最后

限于篇幅,剩余20个例子,就不在文中一一展现出来,本文共30个面试常见手撕模板题,现已全部整理完毕,需要的朋友

点击下方传送门即可免费领取

传送门

以下是整理好的部分PDF文件截图

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容

  • 目录 1. 栈和队列1.用两个队列实现栈2.用两个栈实现队列3.实现一个栈,可以用常数级时间找出栈中的最小值4.判...
    MigrationUK阅读 3,033评论 4 20
  • 动态规划 111. 爬楼梯思路类似斐波那契数列注意考虑第 0 阶的特殊情况 272. 爬楼梯 II思路类似上题,只...
    6默默Welsh阅读 2,429评论 0 1
  • 首先总结以下Java和C、C++中的一般控制台输入方式,方便以后的编程题: java键盘输入 java读文件(会自...
    androidjp阅读 2,293评论 0 16
  • 1、用C语言实现一个revert函数,它的功能是将输入的字符串在原串上倒序后返回。 2、用C语言实现函数void ...
    希崽家的小哲阅读 6,270评论 0 12
  • findall函数返回的总是正则表达式在字符串中所有匹配结果的列表,此处主要讨论列表中“结果”的展现方式,即fin...
    mugtmag阅读 1,708评论 0 0