【LeetCode 学习计划】

本文深入探讨了数据结构中的栈、队列和链表,包括如何用两个栈实现队列、如何处理复杂链表以及字符串的替换与查找算法。此外,还涵盖了动态规划问题,如斐波那契数列、青蛙跳台阶、股票最大利润等,以及搜索与回溯算法的应用,如在二叉树中寻找路径和寻找特定值的节点。
摘要由CSDN通过智能技术生成

学习计划广场

剑指Offer

剑指Offer

第 1 天 栈与队列 (简单)

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

解题思路:

难点:花 5 分钟才看懂题目的输入。。。

在这里插入图片描述

  • 首先,我们要知道队列的特性是先进先出,只能在队头做删除操作;而栈的特性是先进后出只能在一端(称为栈顶 (top) )对数据项进行插入和删除。
  • 我们要做到的是,队列队头(即栈底)做删除操作,队列队尾(即栈顶)做插入操作。
  • 因为 Stack 类中有 push()方法,用栈实现尾部插入整数很简单,但要模拟在队列头部删除整数的功能,在同一个栈是做不到的,所以这里需要两个栈来模拟队列。

以下是 Stack 类的一些方法:

方法方法描述
boolean empty()测试堆栈是否为空。
Object peek( )查看堆栈顶部的对象,但不从堆栈中移除它。
Object pop( )移除堆栈顶部的对象,并作为此函数的值返回该对象。
Object push(Object element)把项压入堆栈顶部。

辅助栈 stack2)存储待删除的元素):将 stack1 的元素一个个弹出插入到 stack2,此时 stack1 底部的元素就到了 stack2 的栈顶(倒序),stack2 调用pop()方法即可实现删除头部整数。

CQueue(初始化):两个栈的初始化。

appendTail(在队列尾部插入整数即 stack1 入栈):直接向 stack1 插入。

deleteHead(在队列头部删除整数即 stack2 出栈):

  • 如果 stack1、stack2 都为空,则直接返回 -1,否则 stack1、stack2 必有一个不为空。
  • 如果只有 stack2 为空,将 stack1 的元素一个个弹出插入到 stack2
  • 到了第三步,经过一二步骤的判断,此时 stack2 里不为空,我们直接再弹出 stack2 栈顶元素。

代码

class CQueue {

    // 题目说了 两个栈 stack1 存储元素,方便尾部插入,stack2 存储队列的倒序,方便删除
    private Stack<Integer> stack1, stack2;

    // 初始化
    public CQueue() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }

    // stack1 存储元素
    public void appendTail(int value) {
        stack1.push(value);
    }

    // 移除元素
    public int deleteHead() {
        // 两个栈为空
        if(stack1.empty() && stack2.empty()){
            return -1;
        }
        // stack1 不为空
        if(stack2.empty()){
            while(!stack1.empty()){
                stack2.push(stack1.pop());
            }
        }
        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 30. 包含min函数的栈

解题思路:

这道题的重点是求出栈里元素的最小值,因为 Stack 类并没有提供函数,每次采用对 Stack 集合遍历方式,时间复杂度就不太理想了。

而题目要求调用 minpushpop 的时间复杂度都是 O(1)。

一般情况下,栈的 pushpop 的时间复杂度就是 O(1)了,所以我们只需要以 O(1) 的时间复杂度查找栈中的最小值。

  • 刚开始思路:打算用一个变量存放当前最小值,每次 push 更新,但是当我的 pop 掉的值刚好是当前最小值的时候,变量就没有参照价值了,后面想到用辅助栈 stack2 保存最小值。
  • 辅助栈 stack2 来存储每次 push 进新元素时对应的最小值,如果最小值小于等于(题目没说 push 进去的元素不能重复) stack2 栈顶对应的元素(表示 stack 栈中的最小值),那么就 push 进 stack2 。出栈时如果pop 掉的值刚好是当前最小值的时候,也要pop掉辅助栈 stack2 的栈顶元素。

注意点:

在本题 pop方法中,来判断 stack1 的栈顶元素是否与 stack1 的栈顶元素是否相等。如果用 == 将会无法通过。(看了榜一的题解才了解到o(╥﹏╥)o)

因为对于某些值,s1.push(x) 会转化为 s1.push(Integer.valueOf(x)),然后会利用 cache 的值,导致实例复用。 Integer 的 equals 重写过,比较的是内部 value 的值, == 如果在 [-128,127] 会被 cache 缓存,超过这个范围则比较的是对象是否相同。

代码

class MinStack {

    // stack1 存储栈, stack2 栈顶存储栈的最小值
    private Stack<Integer> stack1, stack2;

    /** initialize your data structure here. */
    public MinStack() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }

    public void push(int x) {
        // stack1 已经空了 或者 stack2 原最小值大于等于 x
        if(stack2.empty() || stack2.peek() >= x){
            stack2.push(x);
        }
        stack1.push(x);
    }

    public void pop() {
        if(stack1.pop().equals(stack2.peek())){
            stack2.pop();
        }
    }

    public int top() {
        return stack1.peek();
    }

    public int min() {
        return stack2.peek();
    }
}

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.min();
 */
第 2 天 链表 (简单)

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

解题思路:

  1. 遍历,获取链表长度
  2. 创建数组(数组空间为链表长度),向数组反向填充链表的 val

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] reversePrint(ListNode head) {
        int len = 0;
        ListNode node1 = head, node2 = head;
        while(node1 != null) {
            len ++;
            node1 = node1.next;
        }
        int[] result = new int[len];
        for(int i = len - 1; i >= 0; i --) {
            result[i] = node2.val;
            node2 = node2.next;
        }
        return result;
    }
}

剑指 Offer 24. 反转链表

解题思路:

这里讲解的反转链表更全面: https://blog.csdn.net/qq_36903042/article/details/105576141

这里用的是 迭代法
在遍历链表时,我们需要三个指针,第一个指针 cur 指向当前头结点 head,第二个指针 node 指向 cur(当前结点),第三个指针为存储后一个节点的临时节点 temp

  1. 先保存下一节点。

  2. 然后让当前结点的指向前一节点。

  3. 最后移动当前结点到下一结点。(head 头节点一开始初始化指向 null)。

  4. 重复该循环。

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode node = null, cur = head, temp = null;
        while(cur != null) {
            temp = cur.next;
            cur.next = node;
            node = cur;
            cur = temp;
        }
        return node;
    }
}

剑指 Offer 35. 复杂链表的复制

解题思路:

?> 后续再补,没搞懂

第 3 天 字符串 (简单)

剑指 Offer 05. 替换空格

解题思路:

  1. 使用 StringBuilder 拼接 ,遍历字符串碰到空格则替换 %20,否则直接 append

代码

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

剑指 Offer 58 - II. 左旋转字符串

解题思路:

java api 中调用 substring 截取字符串前后两段

代码

class Solution {
    public String reverseLeftWords(String s, int n) {
        return s.substring(n) + s.substring(0, n);
    }
}
第 4 天 查找算法 (简单)

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

解题思路:

  1. 第一种解法比较容易想到,先把输入的数组排序,遍历排序后的数组,直到遍历该下标元素与前一个元素重复即可。
  2. 第二种解法可以用哈希表。遍历整个数组,判断该数组元素是否已经在哈希表中,如果没有就加入到哈希表,如果哈希表已经存在该元素,即可返回该重复结果。
  3. 第三种解法是达到了另类的字典目的,可遍历数组并通过交换操作,使元素的 索引 一一对应。来自**《剑指 Offer——名企面试官精讲典型编程题》第二版** 39页 💯
    1. 在这里插入图片描述

    2. 在这里插入图片描述

代码
第一种解法

class Solution {
    public int findRepeatNumber(int[] nums) {
        Arrays.sort(nums);
        int i = 0;
        for(i = 1; i < nums.length; ++ i) {
            if(nums[i] == nums[i - 1]) {
                break;
            }
        }
        return nums[i];
    }
}

第二种解法

class Solution {
    public int findRepeatNumber(int[] nums) {
        Set<Integer> set = new HashSet<>();
        for(int i = 0; i < nums.length; ++ i) {
            if(set.contains(nums[i])){
                return nums[i];
            }
            set.add(nums[i]);
        }
        return -1;
    }
}

第三种解法💯

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];
                }
                int temp = nums[i];
                nums[i] = nums[temp];
                nums[temp] = temp;
            }
        }
        return -1;
    }
}

剑指 Offer 53 - I. 在排序数组中查找数字 I

解题思路:

二分查找算法,递归找出最左边、最右边的 target

代码

class Solution {
    public int search(int[] nums, int target) {
        if(nums != null || nums.length > 0) {
            int left = getFirstVal(nums, 0, nums.length - 1, target);
            int right = getLastVal(nums, 0, nums.length - 1, target);
            if(left > -1 && right > -1) {
                return right - left + 1;
            }
        }
       return 0;
    }
    public int getFirstVal(int[] nums, int start, int end, int target) {
        if(start > end) {
            return -1;
        }
        int mid = (start + end) / 2;
        if(nums[mid] == target) {
            if(mid > 0 && nums[mid - 1] != target || mid == 0){
                return mid;
            }else {
                end = mid - 1;
            }
            
        }else if(nums[mid] > target) {
            end = mid - 1;
        }else {
            start = mid + 1;
        }

        return getFirstVal(nums, start, end, target);
    }

    public int getLastVal(int[] nums, int start, int end, int target) {
        if(start > end) {
            return -1;
        }
        int mid = (start + end) / 2;
        if(nums[mid] == target) {
            if(mid < nums.length - 1 && nums[mid + 1] != target || mid == nums.length - 1){
                return mid;
            }else {
                start = mid + 1;
            }
            
        }else if(nums[mid] > target) {
            end = mid - 1;
        }else {
            start = mid + 1;
        }
        return getLastVal(nums, start, end, target);
    }
}

剑指 Offer 53 - II. 0~n-1中缺失的数字

解题思路:

二分查找。

0~n-1 这些数字在数组中都是排序的,因此数组中开始的一些数字与它们的下标相同。

不在数组中的那个数字记为 leftleft 左边的所有数字与它们的下标相同。

代码

class Solution {
    public int missingNumber(int[] nums) {
        if(nums == null || nums.length < 0) {
            return -1;
        }
        int left = 0, right = nums.length - 1;
        while(left <= right) {
            int mid = (left + right) >> 1;
            if(nums[mid] == mid) {
                left = mid + 1;
            }else{
                right = mid - 1;
            }
        }
        return left;
    }
}
第 5 天 查找算法 (中等)

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

解题思路:

[0, matrix[0].length - 1]右上角开始比较。

  • 如果当前位置元素比 target 小,则这一行可以排除,往下数一行比较
  • 如果当前位置元素比 target 大,则这一列可以排除,往左数一列比较
  • 如果相等,返回 true
  • 如果越界了还没找到,说明不存在,返回 false

代码

class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        if(matrix == null || matrix.length == 0 ||matrix[0].length == 0) {
            return false;
        }
        int n = matrix.length, m = matrix[0].length;
        int tmpN = 0, tmpM = m - 1;
        while(tmpN < n && tmpM >= 0) {
            if(matrix[tmpN][tmpM] == target) {
                return true;
            }else if(matrix[tmpN][tmpM] > target) {
                tmpM --;
            }else {
                tmpN ++;
            }
        }
        return false;
    }
}

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

解题思路:

题目有标注输入的数组是递增排序的数组,此时它的最小值就是第一个元素。

进行旋转,把一个数组最开始的若干个元素搬到数组的末尾。>= 最小值 <=考虑数组元素重复的情况。

二分查找。

  • 如果中间元素大于右边元素,证明 所求最小值就在右半部分
  • 如果中间元素小于右边元素,证明 所求最小值就在左半部分(递增数组,最小值左边的元素都 >= 右边元素)。

代码

class Solution {
    public int minArray(int[] numbers) {
        if(numbers == null || numbers.length < 0) {
            return -1;
        }
        int start = 0, end = numbers.length - 1;
        while(start < end) {
            int mid = (end + start) / 2;
            if(numbers[mid] < numbers[end]){
                end = mid;
            }else if(numbers[mid] > numbers[end]){
                start = mid + 1;
            }else {
                end --;
            }
        }
        return numbers[start];
    }
}

剑指 Offer 50. 第一个只出现一次的字符

解题思路:

第一个只出现一次的字符:下面我用的是 HashMap,不保证顺序,想要保证顺序可以使用 LinkedHashMap去实现。

  1. 遍历字符串,用哈希表判断数量
    1. 遍历到的元素,若在哈希表存在,哈希表对应的值变为false(不满足数量为 1);若在哈希表中不存在该键,加入,哈希表对应的值设为 true
  2. 第二次遍历字符串,若字符在哈希表中的键为 true,返回该字符。遍历结束,没有则返回' '

代码

class Solution {
    public char firstUniqChar(String s) {
        Map<Character, Boolean> map = new HashMap<>();
        char[] ch = s.toCharArray();
        for(int i = 0; i < ch.length; ++ i) {
            if(map.containsKey(ch[i])){
                map.put(ch[i], false);
            }else {
                map.put(ch[i], true);
            }
        }
        
        for(int i = 0; i < ch.length; ++ i) {
            if(map.get(ch[i])) {
                return ch[i];
            }
        }

        return ' ';
    }
}
第 6 天 搜索与回溯算法 (简单)

剑指 Offer 32 - I. 从上到下打印二叉树

解题思路:

就层序遍历。

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public int[] levelOrder(TreeNode root) {
        if(root == null) {
            return new int[0];
        }
        int[] result;
         Queue<TreeNode> queue = new LinkedList<>();
         List<Integer> list = new ArrayList<>();
         queue.add(root);
         while(!queue.isEmpty()) {
             TreeNode temp = queue.poll();
             list.add(temp.val);
             if(temp.left != null) {
                 queue.add(temp.left);
             }
             if(temp.right != null) {
                 queue.add(temp.right);
             }
         }
         result = new int[list.size()];
         int i = 0;
         for(Integer index : list) {
             result[i ++] = index;
         }
         return result;
    }
}

剑指 Offer 32 - II. 从上到下打印二叉树 II

解题思路:

按层遍历。每次遍历,记录这一层的个数,临时列表 temp 去存储当前层打印结果,直到该层个数为 0。

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        if(root == null) {
            return new ArrayList<List<Integer>>();
        }
        Queue<TreeNode> queue = new LinkedList<>();
        List<List<Integer>> list = new ArrayList<>();
        queue.add(root);
        while(!queue.isEmpty()) {
            List<Integer> temp = new ArrayList<>();
            for(int i = queue.size(); i > 0; i --) {
                TreeNode node = queue.poll();
                temp.add(node.val);
                if(node.left != null) {
                    queue.add(node.left);
                }
                if(node.right != null) {
                    queue.add(node.right);
                }
            }
            list.add(temp);
        }
        return list;

    }
}

剑指 Offer 32 - III. 从上到下打印二叉树 III

解题思路:

跟上一题类似,都是每一层单独作为一行打印。

不同的是,以根节点(第一层)开始,奇数层从左到右打印,偶数行从右到左打印。

所以我们可以在之前基础上借由 boolean flag 布尔变量值 去判断此时的层的奇偶数:每次遍历完一行,状态改变 :flag = !flag;

  • 只有当 flag 为 true,证明此层是偶数行,使用 Collections.reverse(temp); 反转临时列表元素。

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        if(root == null) {
            return new ArrayList<>();
        }
        Queue<TreeNode> queue = new LinkedList<>();
        List<List<Integer>> list = new ArrayList<>();
        queue.add(root);
        boolean flag = false;
        while(!queue.isEmpty()) {
            List<Integer> temp = new ArrayList<>();
            for(int i = queue.size(); i > 0; i --) {
                TreeNode node = queue.poll();
                temp.add(node.val);
                if(node.left != null) {
                    queue.add(node.left);
                }
                if(node.right != null) {
                    queue.add(node.right);
                }
            }
            if(flag){
                Collections.reverse(temp);
            }
            flag = !flag;
            list.add(temp);
        }
        return list;
    }
}
第 7 天 搜索与回溯算法 (简单)

剑指 Offer 26. 树的子结构

解题思路:

DFS。

  • isSubTree():函数判断A存在一部分和B相等
    • B 为空,比较结束,A 存在一部分和 B 相等,true。终止条件
    • B 不为空,A 已经为空或者 A结点的值与 B 结点的值不相等,两者不匹配,false。终止条件
    • 都不是,就继续递归判断,判断 A 和 B 的左子节点是否相等(判断 A 和 B 的右子节点是否相等)
  • 递归遍历A树的每一个节点作为根结点和B树进行比较
    • 不符合就比较左侧的,判断 A 的左子节点和 B 是否相等。
    • 不符合就比较右侧的,判断 A 的右子节点和 B 是否相等。
    • 只要当前结点,左侧,右侧最终匹配成功,都可以判断 B 是 A 的子结构。

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        if(A == null) {
            return false;
        }
        if(B == null) {
            return false;
        }
        boolean result = isSubTree(A, B);
        boolean res = isSubStructure(A.left, B) || isSubStructure(A.right, B);
        return result || res;
    }

    public boolean isSubTree(TreeNode A, TreeNode B) {
        if(B == null) {
            return true;
        }
        if(A == null || A.val != B.val) {
            return false;
        }
        return isSubTree(A.left, B.left) && isSubTree(A.right, B.right);
    }
}

剑指 Offer 27. 二叉树的镜像

解题思路:

每个结点的左右结点交换位置。

递归,借助临时节点 temp 来交换左右结点。

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        if(root == null) {
            return null;
        }
        TreeNode temp = root.left;
        root.left = mirrorTree(root.right);
        root.right = mirrorTree(temp);
        return root;
    }
}

剑指 Offer 28. 对称的二叉树

解题思路:

从根节点向下逐步递归,判断各左右节点是否对称,从而判断整棵树树是否为对称二叉树。

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isSymmetric(TreeNode root) {
        // 树为空,肯定对称
        if(root == null) {
            return true;
        }
        // 判断左右结点是否对称
        boolean res = ismirror(root.left, root.right);
        return res;
    }

    public boolean ismirror(TreeNode Tleft, TreeNode Tright) {
        // 两树为空,肯定对称
        if(Tleft == null && Tright == null) {
            return true;
        }
        // 其中一棵树为空,或者不对称:两树当前结点值不相等
        if(Tleft == null || Tright == null || Tleft.val != Tright.val) {
            return false;
        }
        // 两树结点均不为空,递归
        boolean res = ismirror(Tleft.left, Tright.right) && ismirror(Tleft.right, Tright.left);
        return res;
    }
}
第 8 天 动态规划 (简单)

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

解题思路:

从 斐波那契数列性质 f(n) = f(n - 1) + f(n - 2) 入手。

动态规划。

代码

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

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

解题思路:

逻辑和斐波那契数列是一致的。

代码

class Solution {
    public int numWays(int n) {
        if(n <= 1) {
            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]) % 1000000007;
        }
        return dp[n];
    }
}

剑指 Offer 63. 股票的最大利润

解题思路:

d[i] 指的前 i 天所能得到的最大利润。

双 for 暴力解法。

在这里插入图片描述

代码

class Solution {
    public int maxProfit(int[] prices) {
        int[] dp = new int[prices.length + 1];
        dp[0] = 0;
        for(int i = 0; i < prices.length; ++ i) {
            int cost = 0;
            for(int j = i + 1; j < prices.length; ++ j) {
                cost = Math.max(prices[j] - prices[i], cost);
            } 
            dp[i + 1] = Math.max(dp[i], cost);
        }
        return dp[prices.length];
    }
}
第 9 天 动态规划 (中等)

剑指 Offer 47. 礼物的最大价值

解题思路:

单元格只可能是从上边或左边到达。上一步加上这个单元格的价值。

状态转移方程:dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]) + grid[i - 1][j - 1];

代码

class Solution {
    public int maxValue(int[][] grid) {
        int m = grid.length, n = grid[0].length;
        int[][] dp = new int[m + 1][n + 1];
        for(int i = 1; i <= m; ++ i) {
            for(int j = 1; j <= n; ++ j) {
                dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]) + grid[i - 1][j - 1];
            }
        }
        return dp[m][n];
    }
}

剑指 Offer 42. 连续子数组的最大和

解题思路:

dp[i] 指的 以 num[i] 为结尾的连续子数组最大和,保存子问题的解。

若 nums[i] + dp[i - 1] 反而变小,还不如从 nums[i] 开始计算。

代码

class Solution {
    public int maxSubArray(int[] nums) {
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        int maxTemp = dp[0];
        for(int i = 1; i < nums.length; ++ i) {
            dp[i] = Math.max(nums[i], dp[i - 1] + nums[i]);
            maxTemp = Math.max(maxTemp, dp[i]);
        }
        return maxTemp;
    }
}
第 10 天 动态规划 (中等)

剑指 Offer 46. 把数字翻译成字符串

解题思路:

dp[i] 代表以 下标 i 为结尾前的数字的翻译方案数量。

  • 如果连续值不能被整体翻译(不属于[10, 25]),那么它们的翻译方案只有一种。
  • 如果连续值能被整体翻译,那么它们的翻译方案有两种。

应该。。

像之前的青蛙跳台阶,在[10, 25]范围的话一次可以爬一级或爬两级,其他的只能爬一级。

代码

class Solution {
    public int translateNum(int num) {
        // 数字转字符串
        String str = String.valueOf(num);
        int len = str.length();
        int[] dp = new int[len + 1];
        dp[0] = 1;
        dp[1] = 1;
        for(int i = 2; i <= str.length(); ++ i) {
            String temp = str.substring(i - 2, i);
            // 分情况 
            // 1. 连续两位数字在 [10, 25]内可以组合翻译,也可以单独翻译 -> 就是2种翻译方法
            // 2. 不在其中,单独翻译  -> 就是1种翻译方法
            if(temp.compareTo("10") >= 0 && temp.compareTo("25") <= 0){
                dp[i] = dp[i - 1] + dp[i - 2];
            }else {
                dp[i] = dp[i - 1];
            }
        }
        return dp[dp.length - 1];

    }
}

剑指 Offer 48. 最长不含重复字符的子字符串

解题思路:

滑动窗口的概念。

双指针 + 哈希表。

哈希表:遍历,统计各字符最后一次出现的索引位置,遇到重复,记录左指针(重复字符在哈希表中对应的值),并在哈希表更新该字符现在的下标位置。

左指针 - 当前下标,就是滑动窗口的长度。如果大于 res,就需要进行更新。

代码

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        // 使用哈希表 统计各字符最后一次出现的索引位置
        Map<Character, Integer> map = new HashMap<>();
        int res = 0, index = -1;
        for(int i = 0; i < n; ++ i) {
            char ch = s.charAt(i);
            if(map.containsKey(ch)){
                index = Math.max(index, map.get(ch));
            }
            map.put(ch, i);
            res = Math.max(res, i - index);
        } 
        return res;
    }
}
第 11 天 双指针 (简单)

剑指 Offer 18. 删除链表的节点

解题思路:

cur = head,cur 指向当前节点

cur = cur.next,进行链表遍历,直到找到要删除节点,跳过,指向待删除节点的下一个指向。

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        if(head == null) {
            return null;
        }
        if(head.val == val) {
            return head.next;
        }
        ListNode cur = head;
        ListNode pre = null;
        // 记录要删除结点
        while(cur.next != null){
            if(cur.next.val == val) {
                break;
            }
            cur = cur.next;
        }
        if(cur.next != null) {
            cur.next = cur.next.next;
        }
        return head;
    }
}

剑指 Offer 22. 链表中倒数第k个节点

解题思路:

我自己想到的是 顺序遍历,直到 len - k 个节点返回即可。

看了题目评论(新天地):快慢指针,先让快指针走k步,然后两个指针同步走,当快指针走到头时,慢指针就是链表倒数第k个节点。

刷题还是不够。。

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode cur1 = head, cur2 = head;
        while(cur1 != null && k-- > 0) {
            cur1 = cur1.next;
        }
        while(cur1 != null) {
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return cur2;
    }
}
第 12 天 双指针 (简单)

剑指 Offer 25. 合并两个排序的链表

解题思路:

声明哑结点、头节点,当两个链表都不为空时,让链表 l1、l2 节点的值进行比较,哪个小就让头节点先指向谁,一直遍历到存在任意一个链表为空,跳出循环。

又链表 l1、l2递增 的,若某个链表剩下还有的,直接让头节点的next指向剩下的

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if(l1 == null) {
            return l2;
        }
        if(l2 == null) {
            return l1;
        }
        ListNode dummyHead = new ListNode(0);
        ListNode cur = dummyHead;
        while(l1 != null && l2 != null) {
            if(l1.val >= l2.val) {
                cur.next = l2;
                l2 = l2.next;
            }else {
                cur.next = l1;
                l1 = l1.next;
            }
            cur = cur.next;
        }
        if(l1 != null) {
            cur.next = l1;
        }
        if(l2 != null) {
            cur.next = l2;
        }
        return dummyHead.next;
    }
}

剑指 Offer 52. 两个链表的第一个公共节点

解题思路:

是要求指针相等,而不是第一个相等的数字。

先求出两个链表的长度,长度较长的链表再走相差的长度,让链表处于同一起跑线。

然后遍历两个链表遇到相同则跳出遍历。

代码

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode A = headA, B = headB;
        int lenA = 0, lenB = 0;
        while (A != null) {
            ++ lenA;
            A = A.next;
        }
        while (B != null) {
            ++ lenB;
            B = B.next;
        }
        A = headA;
        B = headB;
        int temp = Math.abs(lenA - lenB);
        if(lenA > lenB) {
            while(temp-- > 0) A = A.next;
        }else {
            while(temp-- > 0) B = B.next;
        }
        while(A != B) {
            A = A.next;
            B = B.next;
        }
        return A;
    }
}
第 13 天 双指针 (简单)

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

解题思路:

双指针。

左指针找偶数,右指针找奇数,两边交换。

直到指针左边都是奇数,指针右边都是偶数

代码

class Solution {
    public int[] exchange(int[] nums) {
        int left = 0, right = nums.length - 1;
        while(left < right) {
            while(left < right && (nums[left] & 1) != 0) {
                left ++;
            }
            while(left < right && (nums[right] & 1) == 0) {
                right --;
            }
            int temp = nums[left];
            nums[left] = nums[right];
            nums[right] = temp;
        }
        return nums;
    }
}

剑指 Offer 57. 和为s的两个数字

解题思路:

双指针。

题目有注明 :递增数组。

可以使用双指针左右夹击。

  • 若左右指针对应的索引的值的和等于 target,返回。
  • 若左右指针对应的索引的值的和大于 target,右指针的值太大了,right --。
  • 若左右指针对应的索引的值的和小于 target,左指针的值太小了,left --。

代码

class Solution {
    public int[] twoSum(int[] nums, int target) {
        int left = 0, right = nums.length - 1;
        while(left < right) {
            int sum = nums[left] + nums[right];
            if(sum == target) {
                return new int[]{nums[left] , nums[right]};
            }else if(sum > target) {
                right --;
            }else {
                left ++;
            }
        }
        return null;
    }
}

剑指 Offer 58 - I. 翻转单词顺序

解题思路:

双指针。

倒序遍历,左指针、右指针分别代表单词的左右下标。

  • 倒序遍历,直到指针指向的字符不为空(去除两个单词间有多余的空格),此为单词右下标
  • 继续遍历,直到指针指向的字符为空,此为单词左下标
  • 将双指针左右夹击的单词字符串加入到 StringBuilder 中
  • 最后返回结果,记得去除最后的空格。

代码

class Solution {
    public String reverseWords(String s) {
        s = s.trim();
        StringBuilder sb = new StringBuilder(); // 保存结果
        int left = s.length() -1, right = left; // 单词左右
        while(left >= 0) {
            while(left >= 0 && s.charAt(left) == ' ') {
                left --;
            }
            right = left;
            while(left >= 0 && s.charAt(left) != ' ') {
                left --;
            }
            sb.append(s.substring(left + 1, right + 1)).append(" ");
        }
        return sb.toString().trim();
    }
}
第 14 天 搜索与回溯算法 (中等)

剑指 Offer 12. 矩阵中的路径

解题思路:

深度搜索 + 剪枝

  1. 返回 false,此路不通:
  • 越界
  • 当前遍历到的字符不等于 board[i][j] 位置上的字符,匹配不上

不存在以上情况,进行下一步骤。
2. 在上一步的基础上,即匹配成功:

  • 当 number 到达 words 数组的最后一个字符,那么说明能够在矩阵 board 中找到一条路径,此时返回 true;

若没有到达结尾,继续匹配字符串的下一个位置的字符

3.递归地去 board[i][j] 的上下左右四个方向去找,重复1、2步骤去判断剩下的路径

遍历过 board[i][j] 的时候,board[i][j] 设置为 ‘\0’,表明已经访问过

在 深度搜索 的过程当中,如果发现某条路已经不通了,就进行回溯操作,board[i][j]重新设置为 words[number],表明未访问过。

代码

class Solution {
    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, i, j, words, 0)) {
            return true;
          }
        }
      }
      return false;
    }

    public static boolean dfs(char[][] board, int i, int j, char[] words, int number) {
      if(i < 0 || i >= board.length || j < 0 || j >= board[0].length || board[i][j] != words[number]) return false;
      if (number == words.length - 1) return true;
      board[i][j] = '\0';
      boolean res = dfs(board, i + 1, j, words, number + 1) || dfs(board, i - 1, j, words, number + 1) || dfs(board, i, j + 1, words, number + 1) || dfs(board, i, j - 1, words, number + 1);
      board[i][j] = words[number];
      return res;
    }

}

剑指 Offer 13. 机器人的运动范围

代码

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){
      // 判断越界,或行坐标和列坐标的数位之和大于k的格子,或者已经访问过了
      if(i < 0 || i >= m || j < 0 || j >= n || visited[i][j] || (i / 10 + i % 10 + j / 10 + j % 10) > k) return 0;
      visited[i][j] = true;
      return 1 + dfs(i + 1, j, m, n, k, visited) + dfs(i - 1, j, m, n, k, visited) + dfs(i, j + 1, m, n, k, visited) + dfs(i, j - 1, m, n, k, visited);
    }
}
第 15 天 搜索与回溯算法 (中等)

剑指 Offer 34. 二叉树中和为某一值的路径

解题思路:

路径是要从根节点到叶子节点的,不用剪枝。

代码

/**

import java.util.ArrayList;
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    LinkedList<List<Integer>> result = new LinkedList<>();
    LinkedList<Integer> temp = new LinkedList<>();
    public List<List<Integer>> pathSum(TreeNode root, int target) {
      dfs(root, target);
      return result;
    }
    private void dfs(TreeNode root, int target){
      if(root == null) return;
      temp.add(root.val);
      target -= root.val;
      if(target == 0 && root.left == null && root.right == null) result.add(new LinkedList(temp));
      dfs(root.left, target);
      dfs(root.right, target);
      temp.removeLast();
    }
}

剑指 Offer 36. 二叉搜索树与双向链表

解题思路:

中序遍历的同时,构建双链表。

有思路,链表实现还是差点。10分钟做不出来,看了题解。。希望再刷能做出来

代码

/*
// Definition for a Node.
class Node {
    public int val;
    public Node left;
    public Node right;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val,Node _left,Node _right) {
        val = _val;
        left = _left;
        right = _right;
    }
};
*/
class Solution {
    Node head, pre; // head 头节点, pre 当前结点
    public Node treeToDoublyList(Node root) {
      if(root == null) return null;
      dfs(root);
      // 头尾相连
      head.left = pre;
      pre.right = head;
      return head;
    }
    private void dfs(Node cur) {
      if(cur == null) return;
      // 递归 “左-中-右” 二叉搜索树的中序遍历结果即为递增序列
      dfs(cur.left);
      if(pre != null) {
        pre.right = cur;
      }else {
        head = cur;
      } 
      cur.left = pre;
      pre = cur;
      dfs(cur.right);
    }
    
}

剑指 Offer 54. 二叉搜索树的第k大节点

解题思路:

二叉搜索树的基本性质

​ ① 若任意结点的左子树不空,则左子树上所有结点的值均不大于它的根结点的值;

​ ② 若任意结点的右子树不空,则右子树上所有结点的值均不小于它的根结点的值;

​ ③ 任意结点的左、右子树也分别为二叉搜索树。

二叉搜索树 中序遍历 为 “左、根、右” 顺序, 得到的是递增序列

反之,我们求遍历顺序 为“左、根、右”,为递减序列,第 k 个即为题目所求。

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    static int message;
    int count = 0;
    public int kthLargest(TreeNode root, int k) {
      dfs(root, k);
      return message;
    }
    private void dfs(TreeNode root, int k) {
      if(root == null) return;
      dfs(root.right, k);
      if(k == ++count) {
        message = root.val;
        return;
      }
      dfs(root.left, k);
    }
}
第 26 天 字符串 (中等)

剑指 Offer 20. 表示数值的字符串

?> 后面补上

剑指 Offer 67. 把字符串转换成整数

解题思路:

  • 开始去除无用的开头空格字符。

  • 若函数不能进行有效的转换时,请返回 0:

    • 第一个非空格字符不是一个有效整数字符,也不是正或者负号

    • 字符串为空或字符串仅包含空白字符

  • 接着符号位,有可能 ‘’+’’ , ‘’-’’ , 或者无符号 ;这里用 boolean 变量去保存符号位(只有出现"-",flag = false,表明该数位负数,其余符号位情况为正数),返回前判断正负。

  • 对符号位后面出现的数字进行遍历拼接,转化为数字,遍历到字符不为数字时,停止;

  • 最后,根据 boolean 变量保存的该数字正负情况,确定返回结果 sum 的正负。

拼接公式:sum = sum * 10 + (str.charAt(index) - ‘0’);

对于数字越界处理,在下一位数字进位(*10)之前(Integer.MAX_VALUE:2147483648):

如果 sum 的值已经大于 Integer.MAX_VALUE / 10,那么下一位数字加入, 执行 sum = sum * 10 + (str.charAt(index) - '0'); 还没加上下一位数字的值就已经溢出;

如果 sum的值 等于 214748364 (Integer.MAX_VALUE / 10),那么下一位数字加入, 执行 sum = sum * 10 + (str.charAt(index) - '0'); 只要 str.charAt(index) - '0' 的值不超过 7,最后的结果就不会溢出。

代码

class Solution {
    public int strToInt(String str) {
        if(str == null) {
            return 0;
        }
        int sum = 0, index = 0, len = str.length();
        boolean flag = true;
        while(index < len && str.charAt(index) == ' ') {
            index ++;
        }
        if(index == len) return 0;
        if(str.charAt(index) == '+' || str.charAt(index) == '-') {
            flag = str.charAt(index) == '-' ? false : true;
            index ++;
        }
        while (index < len) {
            if(str.charAt(index) < '0' || str.charAt(index) > '9') break;
            if(sum > Integer.MAX_VALUE / 10 || (sum == Integer.MAX_VALUE / 10 && str.charAt(index) > '7')){
                return flag ? Integer.MAX_VALUE : Integer.MIN_VALUE;
            }
            sum = sum * 10 + (str.charAt(index) - '0');
            index ++;
        }
        return flag?sum:-sum;
    }
}

剑指Offer 专向突击版

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sail Jamie

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值