剑指offer专题——day01

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

(2)287. 寻找重复数

(3)剑指 Offer 04. 二维数组中的查找

(4)剑指 Offer 05. 替换空格

(5)剑指 Offer 06. 从尾到头打印链表

(6)剑指 Offer 07. 重建二叉树

(7)19. 二叉树的下一个节点

(8)剑指 Offer 09. 用两个栈实现队列

(9)剑指 Offer 10- I. 斐波那契数列

(10)剑指 Offer 10- II. 青蛙跳台阶问题

(11)剑指 Offer 11. 旋转数组的最小数字

(12)剑指 Offer 12. 矩阵中的路径

----------------------------------------------------------------------

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

Difficulty: 简单

找出数组中重复的数字。

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

示例 1:

输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3 

限制:

2 <= n <= 100000

Solution:一个萝卜一个坑

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

class Solution {
    public int findRepeatNumber(int[] nums) {
        int len = nums.length;
        int i=0;
        while(i < len){
            if(nums[i] == i){  //在本位置上
                i++;
            }
            else{
                if(nums[nums[i]] == nums[i]) return nums[i];  //遇到重复的数字了
                else{
                    int temp = nums[nums[i]];
                    nums[nums[i]] = nums[i];
                    nums[i] = temp;
                }
            }
        }
        return -1;
    }
}

287. 寻找重复数

Difficulty: 中等

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 _n _之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

示例 1:

输入: [1,3,4,2,2]
输出: 2

示例 2:

输入: [3,1,3,4,2]
输出: 3

说明:

  1. 不能更改原数组(假设数组是只读的)。
  2. 只能使用额外的 O(1) 的空间。
  3. 时间复杂度小于 O(n2) 。
  4. 数组中只有一个重复的数字,但它可能不止重复出现一次。
Solution:方法1:二分查找;方法2:快慢指针

方法1:二分查找:实际是对1,2,3,4,…,n进行二分
时间复杂度:O(nlogn)
空间复杂度:O(1)

//二分法:实际是对1,2,3,4,......,n进行二分
class Solution {
    public int findDuplicate(int[] nums) {
        int len = nums.length, left = 0, right;
        right = len;  //注意是 1 到 n
        while(left < right){
            int mid = (right-left)/2 + left;
            int count = 0; //统计所有比nums[mid]小于等于的元素个数
            for(int i=0; i<len; i++){
                if(nums[i] <= mid) count++;
            }
                //应该有mid个比mid小,实际有count个
            if(count > mid){
                right = mid;
            }
            else{
                left = mid+1;
            }
        }
        return left;
    }
}

方法2:快慢指针:类似于找链表中环的入口
时间复杂度:小于O(n^2)
空间复杂度:O(1)

//快慢指针的思想
class Solution {
    public int findDuplicate(int[] nums) {
        int len = nums.length, slow = 0, fast = 0;
        while(true){
            slow = nums[slow];
            fast = nums[nums[fast]];
            if(slow == fast){       //重逢了
                slow = 0;
                while(nums[slow] != nums[fast]){  //寻找环的入口
                    slow = nums[slow];
                    fast = nums[fast];
                }
                return nums[slow];
            }
        }
    }
}

剑指 Offer 04. 二维数组中的查找

Difficulty: 简单

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

示例:

现有矩阵 matrix 如下:

[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]

给定 target = 5,返回 true

给定 target = 20,返回 false

限制:

0 <= n <= 1000

0 <= m <= 1000

**注意:**本题与主站 240 题相同:

Solution:从左上角开始找,每个可以去掉一行,或者一列
//从左上角开始找,每个可以去掉一行,或者一列
class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {

        int m = matrix.length;
        if(m <= 0) return false;
        int n = matrix[0].length;
        if(n <= 0) return false;

        int i = 0, j = n-1; //左上角
        while(i<m && j>=0){
            if(matrix[i][j] == target) return true;
            else if(matrix[i][j] < target) i++;
            else j--;
        }
        return false;

    }
}

剑指 Offer 05. 替换空格

Difficulty: 简单

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

示例 1:

输入:s = "We are happy."
输出:"We%20are%20happy."

限制:

0 <= s 的长度 <= 10000

Solution:难点在于,在原空间基础上,不使用额外的空间
class Solution {
    public String replaceSpace(StringBuilder s) {
        int len = s.length();
        for(int i=0; i<len; i++){
            if(s.charAt(i) == ' ') s.append("  "); //添加两个空格
        }
        int cur = len-1, temp = s.length()-1; 
        while(cur >= 0){
            if(s.charAt(cur) == ' '){
                s.setCharAt(temp--, '0');
                s.setCharAt(temp--, '2');
                s.setCharAt(temp--, '%');
            }
            else{
                s.setCharAt(temp--, s.charAt(cur));
            }
            cur--;
        }
        return s.toString();
    }
}

剑指 Offer 06. 从尾到头打印链表

Difficulty: 简单

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

示例 1:

输入:head = [1,3,2]
输出:[2,3,1]

限制:

0 <= 链表长度 <= 10000

Solution:先翻转,再遍历
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] reversePrint(ListNode head) {
        
        if(head == null) return new int[0];

        //第一步:翻转链表
        int count = 1;
        ListNode temp = new ListNode(-1), node;
        temp.next = head;
        node = head;
        while(node != null && node.next != null){
            ListNode next = node.next;
            node.next = next.next;
            next.next = temp.next;
            temp.next = next;
            count++;
        }

        //第二步:遍历
        int[] res = new int[count];
        node = temp.next;
        for(int i=0; i<count; i++){
            res[i] = node.val;
            node = node.next;
        }
        return res;
        
    }
}

剑指 Offer 07. 重建二叉树

Difficulty: 中等

输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

例如,给出

前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]

返回如下的二叉树:

    3
   / \
  9  20
    /  \
   15   7

限制:

0 <= 节点个数 <= 5000

Solution:递归实现
/**
 * 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) {
        HashMap<Integer, Integer> map = new HashMap<>();  //便于寻找根节点的索引
        int len1 = preorder.length, len2 = inorder.length;
        for(int i=0; i<len2; i++){
            map.put(inorder[i], i);
        }

        return build(preorder, 0, len1-1, inorder, 0, len2-1, map);
    }

    public TreeNode build(int[] preorder, int l1, int r1, int[] inorder, int l2, int r2, HashMap<Integer, Integer> map){

        if(l1 > r1) return null;
        TreeNode root = new TreeNode(preorder[l1]);
        int index = map.get(preorder[l1]);     //找寻索引的位置
        int leftNum = index-l2, rightNum = r2-index;   //左子树多少个元素
        root.left = build(preorder, l1+1, l1+leftNum, inorder, l2, index-1, map); //右子树多少元素
        root.right = build(preorder, r1-rightNum+1, r1, inorder, index+1, r2, map);
        return root;
    }
}

19. 二叉树的下一个节点

给定一棵二叉树的其中一个节点,请找出中序遍历序列的下一个节点。

注意:

如果给定的节点是中序遍历序列的最后一个,则返回空节点;
二叉树一定不为空,且给定的节点一定不是空节点;

样例

假定二叉树是:[2, 1, 3, null, null, null, null], 给出的是值等于2的节点。

则应返回值等于3的节点。

解释:该二叉树的结构如下,2的后继节点是3。
  2
 / \
1   3
Solution:分情况讨论
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode father;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode inorderSuccessor(TreeNode p) {
        if(p==null) return null;  //虽然题目说了不为空,但是习惯判断是否为null
        
        if(p.right != null){      //右子树不为空,那么就是右子树的最左结点
            p = p.right;
            while(p.left != null){
                p = p.left;
            }
            return p;
        }
        
        else if(p.father == null) return null;  //父节点为空,说明是最后一个结点
        
        else if(p.father.left == p){        //如果p是父节点的左节点,那直接返回父节点
            return p.father;
        }
        
        else{     //如果p是父节点的右节点,则寻找p的父节点的右节点不是p, 或者p的父节点为空
            while(p.father!=null && p.father.right==p){
                p = p.father;
            }
            return p.father;
        }
    }
    
}

剑指 Offer 09. 用两个栈实现队列

Difficulty: 简单

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

示例 1:

输入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
输出:[null,null,3,-1]

示例 2:

输入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
输出:[null,-1,null,null,5,2]

提示:

  • 1 <= values <= 10000
  • 最多会对 appendTail、deleteHead 进行 10000 次调用
Solution:一个栈作为入队操作,一个栈作为出栈操作
class CQueue {

    private LinkedList<Integer> stack1 = new LinkedList<>();
    private LinkedList<Integer> stack2 = new LinkedList<>();

    public CQueue() {
    }
    
    public void appendTail(int value) {
        stack1.add(value);
    }
    
    public int deleteHead() {
        if(stack2.isEmpty()){   //这判断是最关键的
            int size = stack1.size();
            if(size <= 0) return -1;
            for(int i=0; i<size; i++){
                stack2.add(stack1.remove(stack1.size()-1));
            }
        }
        return stack2.remove(stack2.size()-1);
    }
}

/**
 * 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. 斐波那契数列

Difficulty: 简单

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

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

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

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

示例 1:

输入:n = 2
输出:1

示例 2:

输入:n = 5
输出:5

提示:

  • 0 <= n <= 100

注意:本题与主站 509 题相同:

Solution:O(n)时间复杂度,O(1)空间复杂度

1秒,可以计算10^8。

class Solution {
    public int fib(int n) {
        if(n == 0) return 0;
        if(n == 1) return 1;
        int x1 = 0, x2 = 1;
        for(int i=2; i<=n; i++){
            int temp = x2;
            x2 = (x1+x2)%1000000007; //1(8个0)7
            x1 = temp;
        }
        return x2;
    }
}

剑指 Offer 10- II. 青蛙跳台阶问题

Difficulty: 简单

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

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

示例 1:

输入:n = 2
输出:2

示例 2:

输入:n = 7
输出:21

示例 3:

输入:n = 0
输出:1

提示:

  • 0 <= n <= 100

注意:本题与主站 70 题相同:

Solution:斐波拉切的应用
//斐波拉切数列
class Solution {
    public int numWays(int n) {
        if(n == 0) return 1;
        if(n == 1) return 1;

        int x1 = 1, x2 = 1;
        for(int i=2; i<=n; i++){
            int temp = x2;
            x2 = (x1+x2)%1000000007;
            x1 = temp;
        }
        return x2;
    }
}

剑指 Offer 11. 旋转数组的最小数字

Difficulty: 简单

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2][1,2,3,4,5] 的一个旋转,该数组的最小值为1。

示例 1:

输入:[3,4,5,1,2]
输出:1

示例 2:

输入:[2,2,2,0,1]
输出:0
Solution:二分法

Language: 全部题目

class Solution {
    public int minArray(int[] numbers) {
        int len = numbers.length, left = 0, right;
        right = len-1;
        while(left < right){

            if(numbers[left] < numbers[right]) return numbers[left];  //直接有序

            int mid = (right - left)/2 + left;
            if(numbers[left] < numbers[mid]){  //左边有序
                left = mid+1;
            }
            else if(numbers[mid] < numbers[right]){  //右边有序
                right = mid;
            }
            else{  //无法判断,说明有重复的元素
                left++;
            }
        }
        return numbers[left];

    }
}

剑指 Offer 12. 矩阵中的路径

Difficulty: 中等

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。

[[“a”,“b”,“c”,“e”],
[“s”,“f”,“c”,“s”],
[“a”,“d”,“e”,“e”]]

但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。

示例 1:

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true

示例 2:

输入:board = [["a","b"],["c","d"]], word = "abcd"
输出:false

提示:

  • 1 <= board.length <= 200
  • 1 <= board[i].length <= 200
Solution:回溯
class Solution {
    public boolean exist(char[][] board, String word) {
        int m = board.length;
        if(m <= 0) return false;
        int n = board[0].length;
        if(n <= 0) return false;

        for(int i=0; i<m; i++){
            for(int j=0; j<n; j++){
                if(board[i][j] == word.charAt(0)){
                    boolean[][] used = new boolean[m][n];
                    if(help(board, word, 0, i, j, used)) return true;
                }
            }
        }
        return false;
    }

    public boolean help(char[][] board, String word, int index, int i, int j,
                            boolean[][] used){
        
        if(index == word.length()){
            return true;
        }

        int m = board.length, n = board[0].length;
        if(i<0 || i>=m || j<0 || j>=n || used[i][j] || board[i][j]!=word.charAt(index))
                 return false;
        used[i][j] = true;

        if(help(board, word, index+1, i, j+1, used) || 
            help(board, word, index+1, i+1, j, used) ||
            help(board, word, index+1, i-1, j, used) || 
            help(board, word, index+1, i, j-1, used))
                return true;

        used[i][j] = false;
        return false;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值