面试必刷的算法题 - 剑指offer题集Java实现

本文详细介绍了《剑指offer》中各类面试算法题的Java实现,涵盖数据结构、算法与数据操作、代码质量和鲁棒性等方面,通过实例解析和优化策略,帮助读者理解和掌握面试中常见的算法问题。
摘要由CSDN通过智能技术生成

本文的题目均来自LeetCode的剑指offer题库
本文的分类参考自书籍《剑指offer》
代码均采用Java实现,且大多都是最优解

码这篇文章的目的是方便自己复习看,所以很多代码是经过优化的,并且几乎没有题解,只是提了提思路。如果第一次刷的不建议只看,建议看看思路然后自己去官方站做,如果看不懂可以看很多大佬的题解

点击问题标题可直达LeetCode中文站刷题!!!

文章目录

基础知识

数据结构

面试题03.数组中重复的数字

在这里插入图片描述
空间优先:原地排序法
遍历数组,将遍历的数试探放入值正确位置,如果正确位置已经有过正确数了,且不是当前位置,说明发现了重复数了,退出
如果正确位置不是正确值,将正确值交换到正确位置
(93%,100%)

class Solution {
   
    public int findRepeatNumber(int[] nums) {
   
        int i = 0;
        while (i < nums.length){
   
            if (nums[i] == i){
   
                i++;
                continue;
            }
            int x = nums[i];
            if (nums[x] == x){
   
                return x;
            }
            int temp = nums[i];
            nums[i] = nums[x];
            nums[x] = temp;
        }
        return -1;
    }
}

时间优先:字典
用两个字节的boolean 的数组,占用空间小

class Solution {
   
    public int findRepeatNumber(int[] nums) {
   
        boolean compare[] = new boolean[nums.length];
        for (int i = 0; i < nums.length; i++) {
   
            if (compare[nums[i]]) return nums[i];
            compare[nums[i]] = true;
        }
        return -1;
    }
}

面试题04.二维数组中的查找

在这里插入图片描述
从左下或者右上为起点的查找:查找状态树是BST
(双100%)

class Solution {
   
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
   
        
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) return false;

        int m = matrix.length;
        int n = matrix[0].length;

        int row = 0;
        int col = n - 1;

        while (row < m && col >= 0) {
   
            if (target == matrix[row][col]) return true;
            else if (target > matrix[row][col]) row++;
            else col--;
        }
        return false;
    }
}

面试题05.替换空格

在这里插入图片描述
使用线程不安全的底层实现为动态数组的StringBuilder实现字符串重组
(双100%)

class Solution {
   
    public String replaceSpace(String s) {
   
        StringBuilder bd = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
   
            if (s.charAt(i) == ' ') bd.append("%20");
            else bd.append(s.charAt(i));
        }
        return bd.toString();
    }
}

面试题06.从尾到头打印链表

在这里插入图片描述
翻转链表后顺序打印

  • 翻转链表,统计链表长度
  • 初始化结果集数组,遍历链表添加值
    (双100%)
class Solution {
   
    public int[] reversePrint(ListNode head) {
   
        ListNode pre = null;
        ListNode cur = head;
        int count = 0;//反转时顺便统计链表长度,以便初始化数组
        while (cur != null) {
   
            ListNode tmp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = tmp;
            count++;
        }
        int[] res = new int[count];//初始化结果集
        cur = pre;//将反转后的头指针赋值给cur
        int index = 0;//数组下标指针
        while (cur != null) {
   
            res[index++] = cur.val;
            cur = cur.next;
        }
        return res;
    }
}

面试题07.重建二叉树

在这里插入图片描述
模拟生成二叉树:

  • 前序遍历的节点对应根节点
  • 前序遍历的值在中序遍历中将待遍历值分为两部分,小于根节点值的在左子树,大于根节点值的在右子树
class Solution {
   
    public TreeNode buildTree(int[] preorder, int[] inorder) {
   
        return helper(preorder, 0, preorder.length, inorder, 0, inorder.length);
    }

    private TreeNode helper(int[] preorder, int p_start, int p_end, int[] inorder, int i_start, int i_end) {
   
        //p前后指针重复说明p遍历完了
        if (p_start == p_end) return null;
        //初始化root节点
        int root_val = preorder[p_start];
        TreeNode root = new TreeNode(root_val);
        //root节点再中序遍历中的位置
        int i_root_index = 0;
        //查找位置
        for (int i = i_start; i < i_end; i++) {
   
            if (root.val == inorder[i]) {
   
                i_root_index = i;
                break;
            }
        }
        int leftNum = i_root_index - i_start;
        root.left = helper(preorder, p_start + 1, p_start + leftNum + 1, inorder, i_start, i_root_index);
        root.right = helper(preorder, p_start + leftNum + 1, p_end, inorder, i_root_index + 1, i_end);
        return root;
    }
}

面试题09.用两个栈实现队列

在这里插入图片描述
定义输入栈和输出栈

  • 如果添加操作,直接放入输入栈
  • 如果移除操作,判断输出栈有无值,如果没有就将输入栈所有值加入输出栈再输出
class CQueue {
   

    Stack<Integer> in;
    Stack<Integer> out;

    public CQueue() {
   
        in = new Stack<>();
        out = new Stack<>();
    }

    public void appendTail(int value) {
   
        in.add(value);
    }

    public int deleteHead() {
   
        if (out.isEmpty()) {
   
            while (!in.isEmpty()) {
   
                out.add(in.pop());
            }
        }
        return out.isEmpty() ? -1 : out.pop();
    }
}

算法与数据操作

面试题10-I.斐波那契数列

在这里插入图片描述
自顶向下:带缓存的递归
(双100%)

class Solution {
   
     private int cache[];

    public int fib(int n) {
   
        cache = new int[n + 1];
        return Myfib(n);
    }
    
    private int Myfib(int n) {
   
        if (n < 2) return n;
        if (cache[n] == 0) {
   
            cache[n] = Myfib(n - 1) + Myfib(n - 2);
        }
        return cache[n]%1000000007;
    }
}

自底向上:动态规划
(双100%)

class Solution {
   
    public int fib(int n) {
   
        if (n < 2) return n;
        int dp0 = 0;
        int dp1 = 1;
        int dp2 = 1;
        for (int i = 2; i <= n; i++) {
   
            dp2 = (dp1 + dp0)%1000000007;
            dp0 = dp1;
            dp1 = dp2;
        }
        return dp2;
    }
}

面试题11.旋转数组的最小数字

在这里插入图片描述
二分查找:

  • numbers[mid] > numbers[right]:如45612,最小值在右边,left = mid +1
  • numbers[mid] < numbers[right]:如45123,mid位置可能是最小值,right = mid
  • numbers[mid] = numbers[right]:如,12222,值都为2,所以左移一个继续找

分支条件有点不通用,需要记一下

class Solution {
   
    public int minArray(int[] numbers) {
   
        int left = 0;
        int right = numbers.length - 1;
        while (left < right) {
   
            int mid = (left + right) >> 1;
            if (numbers[mid] > numbers[right]) {
   
                left = mid + 1;
            } else if (numbers[mid] < numbers[right]) {
   
                right = mid;
            } else {
   
                right--;
            }
        }
        return numbers[left];
    }
}

面试题12.矩阵中的路径

在这里插入图片描述
回溯算法+dfs
遍历数组,找到等于查找字符串第一个字符的且没有访问过的位置,dfs查找

class Solution {
   
    public boolean exist(char[][] board, String word) {
   
        if (board == null || board.length == 0) return false;
        int m = board.length;
        int n = board[0].length;
        boolean[][] visited = new boolean[m][n];
        for (int i = 0; i < m; i++) {
   
            for (int j = 0; j < n; j++) {
   
                if (!visited[i][j] && dfs(i, j, visited, board, word, 0)) {
   
                    return true;
                }
            }
        }
        return false;
    }

    private boolean dfs(int i, int j, boolean[][] visited, char[][] board, String word, int index) {
   
        if (index == word.length()) {
   
            return true;
        }
        //退出条件
        if (i == -1 || j == -1 || i == board.length || j == board[0].length
                || visited[i][j] || board[i][j] != word.charAt(index)) {
   
            return false;
        }
        visited[i][j] = true;
        //尝试访问四个方向
        if (dfs(i + 1, j, visited, board, word, index + 1)) return true;
        if (dfs(i - 1, j, visited, board, word, index + 1)) return true;
        if (dfs(i, j + 1, visited, board, word, index + 1)) return true;
        if (dfs(i, j - 1, visited, board, word, index + 1)) return true;
        //回溯
        visited[i][j] = false;
        return false;
    }
}

面试题13.机器人的运动范围

在这里插入图片描述
回溯算法:
从0,0位置开始向i增长和j增长的方向深度优先遍历

class Solution {
   
    public int movingCount(int m, int n, int k) {
   
        boolean visited[][] = new boolean[m][n];
        return dfs(0, 0, m, n, k, visited);
    }

    private int dfs(int i, int j, int m, int n, int k, boolean[][] visited) {
   

        if (i == m || j == n || visited[i][j] || getDigist(i) + getDigist(j) > k) {
   
            return 0;
        }
        visited[i][j] = true;
        //从0,0开始,定这两个方向dfs
        return 1 + dfs(i + 1, j, m, n, k, visited) + dfs(i, j + 1, m, n, k, visited);
    }
    //求数位和
    private int getDigist(int num) {
   
        int res = 0;
        while (num != 0) {
   
            res += num % 10;
            num /= 10;
        }
        return res;
    }
}

面试题14-I.剪绳子

在这里插入图片描述
动态规划:

  • 初始化dp1和dp2(处理i<3的情况)
  • n长度的绳子的最大乘积是有三种可能
    • i<3时就是1
    • 将绳子分两段的乘积(如果长度<=3,则不分的长度比分了长)
    • 绳子分两段,其中一段继续分(dp[i-j]缓存了最大值的)
class Solution {
   
    public int cuttingRope(int n) {
   
        int[] dp = new int[n + 1];
        dp[1] = 1;
        dp[2] = 1;
        for (int i = 3; i <= n; i++) {
   
            for (int j = 1; j < i; j++) {
   
                dp[i] = Math.max(dp[i], Math.max(dp[i - j] * j, j * (i - j)));
            }
        }

        return dp[n];
    }
}

贪心算法:
经推理论证可知:

  • 尽可能多的3带来的乘积最大
  • n%3==0时,最大积为所有3相乘
  • n%3==1时,13<22,所以将一个3同1换成2和2
  • n%3==2时,最大积为所有3相乘*2
    (双100%)
class Solution {
   
    public int cuttingRope(int n) {
   
        if (n <= 3) return n - 1;
        int x = n / 3;
        int y = n % 3;
        if (y == 0) return (int) Math.pow(3, x);
        if (y == 1) return (int) Math.pow(3, x - 1) * 2 * 2;
        return (int) Math.pow(3, x) * 2;
    }
}

面试题15.二进制中1的个数

在这里插入图片描述
位运算:
关于最后一位1的两个运算技巧:

  • 去掉最后一个1:n&(n-1)
  • 获取最后一个1:n&(-n)

当然,通过一个标志位从最后一个开始移位比较也可以

所以这里就有多种解法,以n&(n-1)为例

public class Solution {
   
    // you need to treat n as an unsigned value
    public int hammingWeight(int n) {
   
        int count = 0;
        while (n != 0) {
   
            n &= (n - 1);
            count++;
        }
        return count;
    }
}

高质量的代码

代码完整性

面试题16.数值的整数次方

在这里插入图片描述
分治思想:求pow(x,n)可以转化为求一半的次方

class Solution {
   
    public double myPow(double x, int n) {
   
        if (n < 0) {
   
            n = -n;
            x = 1 / x;
        }
        return pow(x, n);
    }

    private double pow(double x, int n) {
   
        if (n == 0) return 1;
        double half = pow(x, n / 2);
        return (n & 1) == 1 ? half * half * x : half * half;
    }
}

面试题17.打印从1到最大的n位数

在这里插入图片描述
计算出上限,一直循环添加就可以了

class Solution {
   
    public int[] printNumbers(int n) {
   
        int count = (int) (Math.pow(10, n) - 1);
        int res[] = new int[count];
        for (int i = 0; i < count; i++) {
   
            res[i] = i + 1;
        }
        return res;
    }
}

面试题18.删除链表的节点

在这里插入图片描述

class Solution {
   
    public ListNode deleteNode(ListNode head, int val) {
   
        if (head.val == val) return head.next;
        ListNode cur = head;
        while (cur.next != null) {
   
            if (cur.next.val != val)cur = cur.next;
            else {
   
                cur.next = cur.next.next;
                break;
            }
        }
        return head;
    }
}

面试题19.正则表达式匹配

在这里插入图片描述
在这里插入图片描述动态规划

  • 如果 p.charAt(j) == s.charAt(i) : dp[i][j] = dp[i-1][j-1]
  • 如果 p.charAt(j) == ‘.’ : dp[i][j] = dp[i-1][j-1]
  • 如果 p.charAt(j) == ‘*’:
    • 如果 p.charAt(j-1) != s.charAt(i) : dp[i][j] = dp[i][j-2] //in this case, a* only counts as empty
    • 如果 p.charAt(i-1) == s.charAt(i) or p.charAt(i-1) == ‘.’:
      • dp[i][j] = dp[i-1][j] //in this case, a* counts as multiple a
      • or dp[i][j] = dp[i][j-1] // in this case, a* counts as single a
      • or dp[i][j] = dp[i][j-2] // in this case, a* counts as empty
class Solution {
   
    public boolean isMatch(String s, String p) {
   
        if (s == null || p == null) return false;

        int m = s.length();
        int n = p.length();

        boolean[][] dp = new boolean[m + 1][n + 1];
        dp[0][0] = true;

        //"" 和p的匹配关系初始化,a*a*a*a*a*这种能够匹配空串,其他的是都是false。
        //  奇数位不管什么字符都是false,偶数位为* 时则: dp[0][i] = dp[0][i - 2]
        for (int i = 2; i <= n; i += 2) {
   
            if (p.charAt(i - 1) == '*') {
   
                dp[0][i] = dp[0][i - 2];
            }
        }

        for (int i = 1; i <= m; i++) {
   
            for (int j = 1; j <= n; j++) {
   
                char sc = s.charAt(i - 1);
                char pc = p.charAt(j - 1);
                if (sc == pc || pc == '.') {
   
                    dp[i][j] = dp[i - 1][j - 1];
                } else if (pc == '*') {
   
                    if (dp[i][j - 2]) {
   
                        dp[i][j] = true;
                    } else if (sc == p.charAt(j - 2) || p.charAt(j - 2) == '.') {
   
                        dp[i][j] = dp[i - 1][j];
                    }
                }
            }
        }
        return dp[m][n];
    }
}

面试题20.表示数值的字符串

在这里插入图片描述
设置三个标志:numSeen 是否有数字,dotSeen 是否有’.’,eSeen 是否有e

  • 如果是数字,numSeen = true
  • 如果是.,且之前没出现过.e,dotSeen = true
  • 如果是e或者E,且之前没出现过e,eSeen = true,numSeen 设置为false,为了避免123e这种e后面不带数字的情况
  • 如果是+或者-,判断是否在第一个位置或者前一个位置是不是e或E,如果不满足返回false
  • 其他情况都不满足,返回false
  • 最后返回numSeen ,因为要判断e后面有没有数字,没数字也是不合法的
class Solution {
   
    public boolean isNumber(String s) {
   
        if (s == null || s.length() == 0) return false;
        //去掉首位空格
        s = s.trim();
        boolean numFlag = fal
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值