【LeetCode】剑指Offer

剑指Offer

文章目录

剑指 Offer 03. 数组中重复的数字

找出数组中重复的数字。在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

// 如果用set或者map的话
// Time:O(n)
// Space:O(n)

// 值:	0 2 1 3 3
// 下标: 0 1 2 3 4
// 对于每个数字nums[i]如果这个数字 nums[i] 不等于其 下标 i的话,则将这个数字放到 nums[i]这个下标上,如果交换的时候发现两者相同则直接返回。
class Solution {
   
    public int findRepeatNumber(int[] nums) {
   
        for (int i = 0; i < nums.length; i++) {
   
            while (nums[i] != i) {
   
                if (nums[i] == nums[nums[i]]) {
   
                    return nums[i];
                }
                swap(nums, i, nums[i]);
            }
        }
        return -1;
    }

    private void swap(int[] nums, int i, int j) {
   
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}
// Time:O(n)
// Space:O(1)
剑指 Offer 04. 二维数组中的查找

在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

class Solution {
   
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
   
        if (matrix == null || matrix.length == 0) return false;
        int m = matrix.length, n = matrix[0].length;
        int row = 0, col = n - 1;
        while (row < m && col >= 0) {
   
            if (matrix[row][col] == target) {
   
                return true;
            } else if (matrix[row][col] < target) {
   
                row++;
            } else {
   
                col--;
            }
        }
        return false;
    }
}
// Time:O(m+n)
// Space:O(1)
剑指 Offer 05. 替换空格

难度简单211

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

class Solution {
   
    public String replaceSpace(String s) {
   
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
   
            char c = s.charAt(i);
            if (c == ' ') {
   
                sb.append("%20");
            } else {
   
                sb.append(c);
            }
        }
        return sb.toString();
    }
}
// Time:O(n)
// Space:O(n)
剑指 Offer 06. 从尾到头打印链表

难度简单234

输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。

// 1. 反转链表,然后正常放入值
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
public int[] reversePrint(ListNode head) {
   
        ListNode dummy = new ListNode(0);
        ListNode p = head;
        int len = 0;
        while (p != null) {
   
            len++;
            ListNode temp = p.next;
            p.next = dummy.next;
            dummy.next = p;
            p = temp;
        }
        int[] res = new int[len];
        int i = 0;
        while (dummy.next != null) {
   
            res[i++] = dummy.next.val;
            dummy = dummy.next;
        }
        return res;
    }
}

// 2. 第一个值放到最后一个位置,第二个值放到倒数第二个位置,以此类推
class Solution {
   
    public int[] reversePrint(ListNode head) {
   
        int len = 0;
        ListNode p = head;
        while (p != null) {
   
            len++;
            p = p.next;
        }
        int[] res = new int[len];
        p = head;
        for (int i = len - 1; i >= 0; i--) {
   
            res[i] = p.val;
            p = p.next;
        }
        return res;
    }
}
// Time:O(n)
// Space:O(n)
剑指 Offer 07. 重建二叉树

难度中等663

输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。

假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
   
    public TreeNode buildTree(int[] preorder, int[] inorder) {
   
        return helper(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1);
    }

    private TreeNode helper (int[] preorder, int pre_left, int pre_right, int[] inorder, int in_left, int in_right) {
   
        if (pre_left > pre_right || in_left > in_right) return null;
        TreeNode root = new TreeNode(preorder[pre_left]);
        int ind = 0;
        for (int i = in_left; i <= in_right; i++) {
   
            if (inorder[i] == preorder[pre_left]) {
   
                ind = i;
                break;
            }
        }
        int left_size = ind - 1 - in_left + 1, right_size = in_right - (ind + 1) + 1;
        root.left = helper(preorder, pre_left + 1, pre_left + left_size, inorder, in_left, in_left + left_size - 1);
        root.right = helper(preorder, pre_right - right_size + 1, pre_right, inorder, in_right - right_size + 1, in_right);
        return root;
    }
}
// Time: O(n+nlgn)->O(nlgn)
// Space:O(lgn)
剑指 Offer 09. 用两个栈实现队列

用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTaildeleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

class CQueue {
   
    private Deque<Integer> stack1;
    private Deque<Integer> stack2;


    public CQueue() {
   
        stack1 = new LinkedList<>();
        stack2 = new LinkedList<>();
    }
    
    public void appendTail(int value) {
   
        stack1.push(value);
    }
    
    public int deleteHead() {
   
        if (stack2.isEmpty()) {
   
            while (!stack1.isEmpty()) {
   
                stack2.push(stack1.pop());
            }
        }
        if (stack2.isEmpty()) {
   
            return -1;
        } else {
   
            return stack2.pop();
        }
    }
}

/**
 * Your CQueue object will be instantiated and called as such:
 * CQueue obj = new CQueue();
 * obj.appendTail(value);
 * int param_2 = obj.deleteHead();
 */
⭐⭐剑指 Offer 10- I. 斐波那契数列

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:

F(0) = 0,   F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.

斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

class Solution {
   
    public int fib(int n) {
   
        if (n == 0 || n == 1) return n;
        int a = 0, b = 1;
        for (int i = 2; i <= n; i++) {
   
            int c = (a + b) % 1000000007;
            a = b;
            b = c;
        }
        return b;
    }
}

矩阵快速幂

class Solution {
   
    static final int MOD = 1000000007;

    public int fib(int n) {
   
        if (n < 2) {
   
            return n;
        }
        int[][] q = {
   {
   1, 1}, {
   1, 0}};
        int[][] res = pow(q, n - 1);
        return res[0][0];
    }

    public int[][] pow(int[][] a, int n) {
   
        int[][] ret = {
   {
   1, 0}, {
   0, 1}};
        while (n > 0) {
   
            if ((n & 1) == 1) {
   
                ret = multiply(ret, a);
            }
            n >>= 1;	// n /= 2
            a = multiply(a, a);
        }
        return ret;
    }

    public int[][] multiply(int[][] a, int[][] b) {
   
        int[][] c = new int[2][2];
        for (int i = 0; i < 2; i++) {
   
            for (int j = 0; j < 2; j++) {
   
                c[i][j] = (int) (((long) a[i][0] * b[0][j] + (long) a[i][1] * b[1][j]) % MOD);
            }
        }
        return c;
    }
}
剑指 Offer 10- II. 青蛙跳台阶问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

class Solution {
   
    public int numWays(int n) {
   
        if (n == 0 || n == 1) return 1;
        int[] dp = new int[n + 1];
        int a = 1, b = 1, c = 2;
        for (int i = 2; i <= n; i++) {
   
            c = (a + b) % 1000000007;
            a = b;
            b = c;
        }
        return c;
    }
}
⭐⭐剑指 Offer 11. 旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。

给你一个可能存在 重复 元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如,数组 [3,4,5,1,2][1,2,3,4,5] 的一次旋转,该数组的最小值为1。

// 注意不能用mid与left进行比较进行区间缩小
class Solution {
   
    public int minArray(int[] numbers) {
   
        int len = numbers.length;
        int left = 0, right = len - 1;
        while (left < right) {
   
            int mid = left + (right - left) / 2;
            if (numbers[mid] > numbers[right]) {
   
                // [mid + 1, right]
                left = mid + 1;
            } else if (numbers[mid] < numbers[right]) {
   
                // [left, mid]
                right = mid;
            } else {
   
                // 若想等,只能将right缩小,无法确定具体区间
	// 示例一 [1, 0, 1, 1, 1][1,0,1,1,1] :旋转点 x = 1x=1 ,因此 m=2,m=2 在 右排序数组 中。
	// 示例二 [1, 1, 1, 0, 1][1,1,1,0,1] :旋转点 x = 3x=3 ,因此 m=2,m=2 在 左排序数组 中。
                right--;
            }
        }
        return numbers[left];
    }
}
// Time:O(lgn)
// Space:O(1)
剑指 Offer 12. 矩阵中的路径

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

class Solution {
   
    public boolean exist(char[][] board, String word) {
   
        if (board == null || board.length == 0 || board[0].length == 0) return false;
        int m = board.length, 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 (dfs(visited, board, i, j, word, 0)) {
   
                    return true;
                }
            }
        }
        return false;
    }

    private boolean dfs(boolean[][] visited, char[][] board, int i, int j, String word, int index) {
   
        if ((i < 0 || i >= board.length) || (j < 0 || j >= board[0].length) || visited[i][j] || board[i][j] != word.charAt(index))
            return false;
        if (index == word.length() - 1) {
   
            return true;
        }
        visited[i][j] = true;
        boolean find = dfs(visited, board, i + 1, j, word, index + 1)
                || dfs(visited, board, i - 1, j, word, index + 1)
                || dfs(visited, board, i, j + 1, word, index + 1)
                || dfs(visited, board, i, j - 1, word, index + 1);
        visited[i][j] = false;
        return find;
    }
}
⭐⭐剑指 Offer 13. 机器人的运动范围

难度中等436

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

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

    private int dfs(boolean[][] vis, int i, int j, int k) {
   
        if ((i < 0 || i >= vis.length) || (j < 0 || j >= vis[0].length) || vis[i][j] || (i / 10 + i % 10 + j / 10 + j % 10) > k) return 0;
        vis[i][j] = true;
        return 1 + dfs(vis, i + 1, j, k) + dfs(vis, i, j + 1, k);
    }
}
⭐⭐剑指 Offer 14- I. 剪绳子

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m-1] 。请问 k[0]*k[1]*...*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

动态规划

class Solution {
   
    public int cuttingRope(int n) {
   
        // dp[i] 表示长度为i至少剪一刀的最大乘积
        int[] dp = new int[n + 1];
        dp[2] = 1;
        for (int i = 3; i <= n; i++) {
   
            for (int j = 1; j <= i - 1; j++) {
   
                dp[i] = Math.max(dp[i], Math.max(j * (i - j), j * dp[i - j]));
            }
        }
        return dp[n];
    }
}

贪心

算法流程:
n ≤ 3 n \leq 3 n3 时,按照规则应不切分,但由于题目要求必须剪成 m>1 段,因此必须剪出一段长度为 1 的绳子,即返回 n - 1 。
n > 3 n n>3n n>3n 时,求 n 除以 3 的 整数部分 a 和 余数部分bb (即 n = 3a+b ),并分为以下三种情况:
当 b = 0 时,直接返回 3 a 3^a 3a
当 b = 1 时,要将一个 1 + 31+3 转换为 2+22+2,因此返回 3 a − 1 × 4 3^{a-1} \times 4 3a1×4
当 b = 2 时,返回 3 a × 2 3^a \times 2 3a×2

class Solution {
   
    public int cuttingRope(int n) {
   
        if (n <= 3) return n - 1;
        int res = 1;
        while (n > 4) {
   
            res = res * 3;
            n -= 3;
        }
        res *= n;
        return res;
    }
}
剑指 Offer 14- II. 剪绳子 II

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m - 1] 。请问 k[0]*k[1]*...*k[m - 1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

// 1. dp
class Solution {
   
    public int cuttingRope(int n) {
   
        // dp[i] 表示总长度为 i 能够得到的最大乘积
        // 最后一段可以长度 j 为 1 , 2, ..., i - 2
        // 对于前面长度为 i - j的话可以剪,也可以不剪
        // 那么结果就是 max(dp[i], max(j * dp[i - j], j * (i - j)))
        // 也就是 1 * dp[i - 1], 2 * d[i - 2], ... , (i - 2) * dp[2]
        int[] dp = new int[n + 1];
        dp[2] = 1;
        for (int i = 3; i <= n; i++) {
   
            for (int j = 1; j <= i - 2; j++) {
   
                dp[i] = Math.max(dp[i], Math.max(j * dp[i - j], j * (i - j)));
            }
        }
        return dp[n];
    }
}
class Solution {
   
    public int cuttingRope(int n) {
   
        if (n <= 3) return n - 1;
        long res = 1;
        while(n > 4){
   
            res = res * 3 % 1000000007;
            n -= 3;
        }
        return (int)(res * n % 1000000007);
    }
}
剑指 Offer 15. 二进制中1的个数

难度简单215

编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为 汉明重量).)。

class Solution {
   
    // 1. 检查第i位时,将n与2^i次方进行与运算,如果与完之后等于2^i,说明该位置是1,如果与完之后等于0,说明该位置是0
    // 1 0 0 1 0
    // 0 0 0 1 0
    public int hammingWeight(int n) {
   
        int cnt = 0;
        for (int i = 0; i < 32; i++) {
   
            if ((n & (1 << i)) != 0) cnt++;
        }
        return cnt;
    }

    // 2. 每次检查最低位是不是1,是则计数,每次判断完最低位之后都要右移一位
    public int hammingWeight_1(int n) {
   
        int cnt = 0;
        for (int i = 0; i < 32; i++) {
   
            if ((n & 1) == 1) cnt++;
            n = n >> 1;
        }
        return cnt;
    }

    // 3. 通过n & (n-1)来将n最低位的1置为0,直到n等于0为止
    public int hammingWeight_2(int n) {
   
        int cnt = 0;
        while (n != 0) {
   
            n = n & (n - 1);
            cnt++;
        }
        return cnt;
    }
}
⭐⭐剑指 Offer 16. 数值的整数次方

难度中等254

实现 pow(x, n) ,即计算 x 的 n 次幂函数(即,x^n)。不得使用库函数,同时不需要考虑大数问题。

快速幂 + 递归

class Solution {
   
    public double myPow(double x, int n) {
   
        if(n == 0) return 1;
        if(n == 1) return x;
        if(n == -1) return 1 / x;
        // n / 2 + n / 2 + n % 2
        double half = myPow(x, n / 2);
        double mod = myPow(x, n % 2);
        return half * half * mod;
    }
}
// Time:O(lgn)
// Space:O(lgn)

快速幂 + 迭代

class Solution {
   
    public double myPow(double x, int n) {
   
        if (n == 0) return 1.0;
        long b = n;	// 负数最小值的绝对值超出int范围,所以用更大容量防止溢出
        if (b < 0) {
   
            x = 1 / x;
            b = -b;
        }
        double res = 1.0;
        while (b > 0) {
   
            if ((b & 1
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值