0
点赞
收藏
分享

微信扫一扫

【LeetCode剑指Offer篇快刷指南】第一部分(03 - 22)

剑指Offer

1.数组中的重复数字

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f722sP3W-1651037689648)(picture/1.png)]

解题思路

使用一个哈希表用于记录某个数字出现的次数,**注意题目潜台词说明该数组必然出现重复的元素,因此 if 条件不可能不成立,最后的返回值任意返回即可 **

代码实现

public class s1 {

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

    public int findRepeatNumber(int[] nums) {
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            if (map.containsKey(nums[i])) {
                map.put(nums[i], map.get(nums[i]) + 1);
            } else {
                map.put(nums[i], 1);
            }

            if (map.get(nums[i]) >= 2) {
                return nums[i];
            }
        }
        return nums[n - 1];
    }
}

2.二维数组中的查找

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZmNdnRg5-1651037689649)(picture/2.png)]

解题思路

首先理清题目,请注意每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。由这个规律,我们可以得出该二维数组左下的元素大于它上方的元素,小于它右方的元素,右上的元素与之相反,可以使用二分法

  1. 获得矩阵的长和宽,排除特殊情况
  2. 以左下角为起点,如果其小于目标元素,就往右移动寻找更大的元素,如果其大于目标元素就往上走找更小的元素,返回查询结果即可

代码实现

public class s2 {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        if (matrix.length == 0 || matrix[0].length == 0) {
            return false;
        }
        int m = matrix.length;
        int n = matrix[0].length;

        //从左下角开始
        for (int i = m - 1, j = 0; i >= 0 && j < n; ) {
            //目标元素小于当前元素的值,往上走
            if (matrix[i][j] > target) {
                i--;
            } else if (matrix[i][j] < target) {
                //目标元素大于当前元素的值,往右走
                j++;
            } else {
                return true;
            }
        }
        return false;
    }
}

3.替换空格

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Eykuncbv-1651037689649)(picture/3.png)]

解题思路

因为 Java 中字符串不可变,因此需要 new 一个方便操作的字符串。然后将字符串转换为字符数组并进行遍历,如果碰到 空格 ,就向 res 中加入 “%20” , 否则就将该字符加入 res 中

代码实现

public class s3 {
    public String replaceSpace(String s) {
        StringBuilder res = new StringBuilder();
        for(Character c : s.toCharArray()){
            if(c == ' '){
                res.append("%20");
            }else {
                res.append(c);
            }
        }
        return res.toString();
    }
}

4.从头到尾打印链表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LsyACTC7-1651037689650)(picture/4.png)]

解题思路

把链表中的数据存放在一个 ArrayList 数组中,反转这个数组,然后遍历这个 ArrayList 将其中的值放入 res 中即可

代码实现

public class s4 {

    ArrayList<Integer> temp = new ArrayList<>();

    int[] res;

    public int[] reversePrint(ListNode head) {
        if (head == null){
            return res = new int[0];
        }
        ListNode index = head;
        while (index != null){
            temp.add(index.val);
            index = index.next;
        }
        Collections.reverse(temp);
        res = new int[temp.size()];
        for(int i=0;i<temp.size();i++){
            res[i] = temp.get(i);
        }
        return res;
    }
}

5.重建二叉树

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C5I0VNrU-1651037689650)(picture/5.png)]

解题思路

前序遍历的顺序:根左右

中序遍历的顺序:左根右

按照这两个性质就可以写出重建二叉树的方法

代码实现

public class s5 {

    HashMap<Integer, Integer> valToIndex = new HashMap<>();

    public TreeNode buildTree(int[] preorder, int[] inorder) {
        for (int i = 0; i < inorder.length; i++) {
            valToIndex.put(inorder[i], i);
        }
        return build(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1);
    }

    public TreeNode build(int[] preorder, int pStr, int pEnd, int[] inorder, int iStr, int iEnd) {
        if (pStr > pEnd) {
            return null;
        }

        int rootVal = preorder[pStr];
        int index = valToIndex.get(rootVal);
        int leftSize = index - iStr;

        TreeNode root = new TreeNode(rootVal);
        root.left = build(preorder, pStr + 1, pStr + leftSize, inorder, iStr, index - 1);
        root.right = build(preorder, pStr + leftSize + 1, pEnd, inorder, index + 1, iEnd);
        return root;
    }
}

6.用两个栈实现队列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZdMuosKp-1651037749487)(picture/6.png)]

解题思路

  • 入栈就正常把元素压入 inStack
  • 出栈首先判断 outStack 是否不为空,如果不空直接返回 outStack 里面的元素,如果是空的就把 inStack 里面的元素全部压入 outStack 然后返回栈顶元素即可

代码实现

public class s6 {

    Deque<Integer> inStack;
    Deque<Integer> outStack;

    public s6() {
        inStack = new ArrayDeque<Integer>();
        outStack = new ArrayDeque<Integer>();
    }

    public void appendTail(int value) {
        inStack.push(value);
    }

    public int deleteHead() {
        if(!outStack.isEmpty()){
            return outStack.pop();
        }else{
            while(!inStack.isEmpty()){
                outStack.push(inStack.pop());
            }
            return outStack.isEmpty() ? -1 : outStack.pop();
        }
    }
}

7.斐波那契数列

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IjCOmsp8-1651037689651)(picture/7.png)]

解题思路

动态规划

base case:

dp[0] = 0;
dp[1] = 1;

状态转移方程

dp[i] = dp[i-1] + dp[i-2];

注意最后取模就行

代码实现

public class s7 {
    public int fib(int n) {
        if(n == 0){
            return 0;
        }
        int[] dp = new int[n+1];
        //base case
        dp[0] = 0;
        dp[1] = 1;
        for(int i=2;i<=n;i++){
            dp[i] = dp[i-1] + dp[i-2];
            dp[i] %= 1000000007;
        }
        return dp[n];
    }
}

8.青蛙跳台阶问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hg5OxDWj-1651037689651)(picture/8.png)]

解题思路

动态规划

base case:

dp[0] = 1;
dp[1] = 1;

状态转移方程

 dp[i] = dp[i - 1] + dp[i - 2];

最后注意取模即可

代码实现

public class s8 {
    public int numWays(int n) {
        if (n == 0) {
            return 1;
        }

        int[] dp = new int[n + 1];
        dp[0] = 1;
        dp[1] = 1;

        for (int i = 2; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
            dp[i] %= 1000000007;
        }
        return dp[n];
    }
}

9.旋转数组的最小数字

在这里插入图片描述

解题思路

平常的使用 sort 排序然后找第一个的方法太 low 了,这里不谈。

使用二分法进行最小数的查找

  1. 声明 i,j 双指针指向 nums 数组左右两端
  2. 开始循环,令 m = (i + j) / 2 为每次二分的中点,具体分为三种情况
    • nums [m] > nums[j] 时:说明 m 一定在旋转数组的左边,即旋转点 x 一定在 [m + 1, j]闭区间内,执行 i = m + 1
    • nums[m] < nums[j] 时:说明 m 一定在旋转数组的右边,即旋转点 x 一定在[i,m]闭区间内,执行 j = m
    • nums[m] == nums[j] ,执行 j = j - 1
  3. 当 i == j 跳出循环,返回此时 nums[i] 的值即可

代码实现

public class s9 {
    public int minArray(int[] numbers) {
        int i = 0, j = numbers.length - 1;
        while (i < j) {
            int m = (i + j) / 2;
            if (numbers[m] > numbers[j]) {
                i = m + 1;
            } else if (numbers[m] < numbers[j]) {
                j = m;
            } else {
                j--;
            }
        }
        return numbers[i];
    }
}

10.矩阵中的路径

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V9UnVHyG-1651037689651)(picture/9.png)]

解题思路

采用深度优先搜索和剪枝的方式解决

明确终止条件

  • 行或列索引越界
  • 当前矩阵字符与目标字符不同

递归工作

  1. 标记当前访问过的元素,采用特殊符号标记的形式避免其他问题发生
  2. 搜索下一单元格,上下左右均可
  3. 在搜索完成后将特殊标记清除,恢复矩阵保证下次搜索的正常进行

代码实现

public boolean exist(char[][] board, String word) {
        char[] words = word.toCharArray();
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                if (dfs(board, words, i, j, 0)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * @param board 对象矩阵
     * @param word  目标字符
     * @param i     行索引
     * @param j     列索引
     * @param k     目标字符索引
     */
    public boolean dfs(char[][] board, char[] word, int i, int j, int k) {
        if (i >= board.length || i < 0 || j >= board[0].length ||
                j < 0 || board[i][j] != word[k]) {
            return false;
        }
        if (k == word.length - 1) {
            return true;
        }
        board[i][j] = '\0';
        boolean res = dfs(board, word, i + 1, j, k + 1) ||
                dfs(board, word, i - 1, j, k + 1) ||
                dfs(board, word, i, j + 1, k + 1) ||
                dfs(board, word, i, j - 1, k + 1);
        board[i][j] = word[k];
        return res;
    }

11.机器人的运动范围

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6r9ippaS-1651037689651)(picture/11.png)]

解题思路

动态规划

base case:

dp[0][0] = true;

状态转移:

dp[i][j] |= dp[i - 1][j];
dp[i][j] |= dp[i][j - 1];

代码实现

public class s11 {

    public int movingCount(int m, int n, int k) {
        if (k == 0) {
            return 1;
        }
        //dp[i][j] : (i,j)是否能够到达的布尔值
        boolean[][] dp = new boolean[m][n];
        int ans = 1;
        dp[0][0] = true;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if ((i == 0 && j == 0) || get(i) + get(j) > k) {
                    continue;
                }

                if (i - 1 >= 0) {
                    dp[i][j] |= dp[i - 1][j];
                }
                if (j - 1 >= 0) {
                    dp[i][j] |= dp[i][j - 1];
                }
                ans += dp[i][j] ? 1 : 0;
            }
        }
        return ans;
    }

    public int get(int x) {
        int res = 0;
        while (x != 0) {
            res += x % 10;
            x /= 10;
        }
        return res;
    }
}

12.剪绳子

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z64K4evY-1651037689652)(picture/12.png)]

解题思路

动态规划

base case : 当绳长为2,最大乘积为被分为两段的乘积,为 2

dp[2] = 1

状态转移方程

dp[i] // 不剪绳子
j * (i - j) // 剪一段长度为 j ,另一段长度为 i - j
j * dp[i - j]  // 剪一段长度为 j ,另一段也进行裁剪,即 dp[i-j]

代码实现

public class s12 {
    public int cuttingRope(int n) {
        //dp[i] : 记录从 0 到 i 绳子被剪成 m 段乘积的最大值
        int[] dp = new int[n + 1];
        //base case
        dp[2] = 1;
        for (int i = 3; i <= n; i++) {
            for (int j = 2; j < i; j++) {
                dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j]));
            }
        }
        return dp[n];
    }
}

13.剪绳子-2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D33lQUQs-1651037689652)(picture/13.png)]

解题思路

循环取余+贪心

定义 mul 作为乘积,每次乘 3 后,n 要减3,以此实现有多少3乘多少3

需要讨论的是最后一段的取值,若最后一段余下2,3,4直接返回,因为特例最后一段为 1 时需要拆为 2 * 2

代码实现

public class s13 {
    public int cuttingRope(int n) {
        if (n <= 3) {
            return n - 1;
        }

        //最大int 2147483647,为防止在某一次 mul乘3已经溢出,类型需要设为long
        long mul = 1;

        //3,3,3,3,3,4
        //3,3,3,3,3,3
        //3,3,3,3,3,2
        //3,3,3,3,3,1  X 此种情况算在和前面 3+1 和为 4
        while (n > 4) {
            //每次乘积后取余防止大数
            mul = mul * 3 % 1000000007;

            n -= 3;
        }
        return (int) (mul * n % 1000000007);
    }
}

14.二进制中1的个数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OmKRo8Ws-1651037689652)(picture/14.png)]

解题思路

对 int 的每一位进行检查,并统计 1 的个数

代码实现

public class s14 {
    public int hammingWeight(int n) {
        int ret = 0;
        for (int i = 0; i < 32; i++) {
            if ((n & (1 << i)) != 0) {
                ret++;
            }
        }
        return ret;
    }
}

15.数值的整数次方

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sb8nu6Tt-1651037689652)(picture/15.png)]

解题思路(搬运)

快速幂法

这里借用Krahets大佬的讲解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vBUvVd3p-1651037689653)(picture/15_1.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nLovYYC5-1651037689653)(picture/15_2.png)]

代码实现

public class s15 {
    public double myPow(double x, int n) {
        if (x == 0) {
            return 0;
        }
        //防止int正负转换越界
        long b = n;
        double res = 1.0;
        //负数转为正数计算
        if (b < 0) {
            x = 1 / x;
            b = -b;
        }
        while (b > 0) {
            // b % 2 == 1
            if ((b & 1) == 1) {
                res *= x;
            }
            // x = x ^ 2
            x *= x;
            // b //= 2(向下取整)
            b >>= 1;
        }
        return res;
    }
}

16.打印从1到最大的n位数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9OpJp1wm-1651037689653)(picture/16.png)]

解题思路

通过观察发现最终生成的数字个数为 10 ^ n - 1,因此生成一个对应大小的数组,遍历输入对应的值即可

代码实现

public class s16 {

    int[] res;

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

18.删除链表的节点

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7jL1GlG4-1651037689653)(picture/17.png)]

解题思路

采用双指针结合虚拟头结点(dummy)的方式,首先让一个快指针指向 dummy.next ,让慢指针指向 dummy ,避免删除的是第一个节点导致空指针异常的错误,之后通过 while 循环寻找值为 val 的节点,找到了就让慢指针指向快指针的下一个节点。前进的方式是快慢指针每次同步前进一步,保证随时可以获取快指针指向节点的前一个节点。

代码实现

public class s17 {

    public ListNode deleteNode(ListNode head, int val) {
        ListNode dummy = new ListNode(-1), p = head, q = dummy;
        dummy.next = head;
        while (p != null) {
            if (p.val == val) {
                q.next = p.next;
                break;
            }
            p = p.next;
            q = q.next;
        }
        return dummy.next;
    }
}

18.正则表达式匹配

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-11U7yzyR-1651037689654)(picture/18.png)]

解题思路

动态规划

base case:

 dp[0][0] = true;

状态转移方程:

dp[i][j] = dp[i][j - 2];
	dp[i][j] = dp[i][j] || dp[i - 1][j];
	dp[i][j] = dp[i - 1][j - 1];

代码实现

public class s18 {
    public boolean isMatch(String s, String p) {
        int m = s.length();
        int n = p.length();

        boolean[][] dp = new boolean[m + 1][n + 1];
        dp[0][0] = true;
        for (int i = 0; i <= m; ++i) {
            for (int j = 1; j <= n; ++j) {
                if (p.charAt(j - 1) == '*') {
                    dp[i][j] = dp[i][j - 2];
                    if (matches(s, p, i, j - 1)) {
                        dp[i][j] = dp[i][j] || dp[i - 1][j];
                    }
                } else {
                    if (matches(s, p, i, j)) {
                        dp[i][j] = dp[i - 1][j - 1];
                    }
                }
            }
        }
        return dp[m][n];
    }

    public boolean matches(String s, String p, int i, int j) {
        if (i == 0) {
            return false;
        }
        if (p.charAt(j - 1) == '.') {
            return true;
        }
        return s.charAt(i - 1) == p.charAt(j - 1);
    }
}

19.表示数值的字符串

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CVHtSbQC-1651037689654)(picture/19.png)]

解题思路

这里参考Krahets大佬的解法

本题使用有限状态自动机。根据字符类型和合法数值的特点,先定义状态,再画出状态转移图,最后编写代码即可。

字符类型:

空格 「 」、数字「0—9 」 、正负号 「 +− 」 、小数点 「 . 」 、幂符号 「 eE 」 。

状态定义:

按照字符串从左到右的顺序,定义以下 9 种状态。

开始的空格
幂符号前的正负号
小数点前的数字
小数点、小数点后的数字
当小数点前为空格时,小数点、小数点后的数字
幂符号
幂符号后的正负号
幂符号后的数字
结尾的空格
结束状态:

合法的结束状态有 2, 3, 7, 8 。

代码实现

这里展示Krahets大佬的代码

class s19 {
    public boolean isNumber(String s) {
        Map[] states = {
                new HashMap<Character,Integer>() {{ put(' ', 0); put('s', 1); put('d', 2); put('.', 4); }}, // 0.
                new HashMap<Character,Integer>() {{ put('d', 2); put('.', 4); }},                           // 1.
                new HashMap<Character,Integer>() {{ put('d', 2); put('.', 3); put('e', 5); put(' ', 8); }}, // 2.
                new HashMap<Character,Integer>() {{ put('d', 3); put('e', 5); put(' ', 8); }},              // 3.
                new HashMap<Character,Integer>() {{ put('d', 3); }},                                        // 4.
                new HashMap<Character,Integer>() {{ put('s', 6); put('d', 7); }},                           // 5.
                new HashMap<Character,Integer>() {{ put('d', 7); }},                                        // 6.
                new HashMap<Character,Integer>() {{ put('d', 7); put(' ', 8); }},                           // 7.
                new HashMap<Character,Integer>() {{ put(' ', 8); }}                                         // 8.
        };
        int p = 0;
        char t;
        for(char c : s.toCharArray()) {
            if(c >= '0' && c <= '9') t = 'd';
            else if(c == '+' || c == '-') t = 's';
            else if(c == 'e' || c == 'E') t = 'e';
            else if(c == '.' || c == ' ') t = c;
            else t = '?';
            if(!states[p].containsKey(t)) return false;
            p = (int)states[p].get(t);
        }
        return p == 2 || p == 3 || p == 7 || p == 8;
    }
}

20.调整数组顺序使奇数位于偶数前面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KMTMaSpb-1651037689654)(picture/20.png)]

解题思路

采用双指针的思想,i,j 分别指向 nums 两端,i 去寻找偶数,j 去寻找奇数,找到之后交换 i,j 指向元素的值

代码实现

public class s20 {

    public int[] exchange(int[] nums) {
        int i = 0, j = nums.length - 1;
        while (i < j) {
            while (i < j && (nums[i] % 2) != 0) {
                i++;
            }
            while (i < j && (nums[j] % 2) == 0) {
                j--;
            }
            swap(nums, i, j);
        }
        return nums;
    }

    public void swap(int[] nums, int a, int b) {
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }
}
举报

相关推荐

0 条评论