树
二叉树的中序遍历力扣题目链接给定一个二叉树的根节点 root,返回它的中序遍历。
递归,写个新方法来递归
/** * 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 { public List<Integer> inorderTraversal(TreeNode root) { List<Integer ...
哈希表
认识哈希一般来说,哈希表都是用来快速判断一个元素是否出现在集合里。假设需要查询一个名字是否在这所学校里:
枚举的时间复杂度是 O(n)
哈希表的时间复杂度是 O(1)
其中蕴含的原理就是将学生的姓名直接映射为哈希表上的索引,随后通过查询索引下标快速知道这位学生是否在这所学校当中。常用的三种哈希法的数据结构:
哈希数组
set
map
当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。
有效的字母异位词力扣题目链接给定两个字符串 s 和 t,编写一个函数来判断 t 是否是 s 的字母异位词。
先将 s 中的每个字符映射到一个数组中,用于标志每个字符出现的次数
遍历 t 中所有的字符,将数组中每个字符对应位置的数值减去 1
若流程结束,数组中不全为 0,则返回 false
class Solution { public boolean isAnagram(String s, String t) { int[] record = new int[26]; for (int i = 0; ...
栈与队列
认识栈与队列栈是一种遵循先入后出逻辑的线性数据结构。在栈中,堆叠元素的顶部称为栈顶,底部称为栈底。将元素添加到栈顶的操作叫做入栈,删除栈顶元素的操作叫做出栈。
/* 初始化栈 */Stack<Integer> stack = new Stack<>();/* 元素入栈 */stack.push(1);stack.push(3);stack.push(2);stack.push(5);stack.push(4);/* 访问栈顶元素 */int peek = stack.peek();/* 元素出栈 */int pop = stack.pop();/* 获取栈的长度 */int size = stack.size();/* 判断是否为空 */boolean isEmpty = stack.isEmpty();
队列是一种遵循先入后出规则的线性数据结构。队列头部称为队首,尾部称为队尾,将元素加入队尾的操作叫做入队,删除队首元素的操作叫做出队。
/* 初始化队列 */Queue<Integer> queue = new LinkedList<>( ...
链表
认识链表链表是一种线性表数据结构。它使用一组任意的存储单元(可以是连续的,也可以是不连续的),来存储一组具有相同类型的数据。以单链表为例,链表的存储方式如下图所示。如上图所示,链表通过将一组任意的存储单元串联在一起。其中,每个数据元素占用若干存储单元的组合称为一个链节点。为了将所有的节点串起来,每个链节点不仅要存放一个数据元素的值,还要存放一个指出这个数据元素在逻辑关系上的直接后继元素所在链节点的地址,该地址被称为后继指针next。链表的几种类型:
单链表:每个节点有一个指针,指向自己的直接后继,但首尾不相连
双向链表:每个节点有两个指针,分别指向直接后继与直接前驱
循环链表:最后一个链节点指向头结点,形成一个环
双向循环链表:顾名思义,集合了双向链表与循环链表的特征
//链节点的定义public class ListNode{ int val; ListNode next; public ListNode(int val, ListNode next){ this.val = val; this.next = ne ...
分治法
Pow(x,n)力扣题目链接实现 pow(x, n),即计算 x 的整数 n 次幂函数。
分治思想,将 x 的 n 次幂分解成两个 x 的二分之 n 次幂
class Solution { public double myPow(double x, int n) { // 基本情况:如果指数 n 为 0,任何数的 0 次幂都为 1 if (n == 0) return 1; // 基本情况:如果指数 n 为 1,x 的 1 次幂就是 x 本身 if (n == 1) return x; // 基本情况:如果指数 n 为 -1,x 的 -1 次幂就是 1/x if (n == -1) return 1 / x; // 递归计算 x 的 n/2 次幂 double a = myPow(x, n / 2); // 递归计算 x 的 n%2 次幂,实际上是为了处理幂的奇偶性 ...
动态规划
认识动态规划动态规划的核心思想是将复杂问题分解成小的、易于解决的子问题,并找到这些子问题之间的递推关系。每个子问题只解决一次,并将其解保存起来,如果再遇到相同的子问题,只是简单地查表取出已计算的结果。动态规划的过程通常分为四个阶段:
定义子问题
实现需要反复执行解决的子问题部分
识别并求解出基本子问题
通过递推关系,自底向上地将子问题的解组合起来,形成原问题的解
爬楼梯力扣题目链接假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
dp 数组的含义是爬到 第 i 级台阶有 dp[i] 种爬法
class Solution { public int climbStairs(int n) { if (n == 0) { return 0; } else if (n == 1) { return 1; } else if (n == 2) { ...
数组
认识数组数组是最基础的一种数据结构,无论是什么样的算法题,大部分都会涉及到对数组的操作。如何有效的利用数组,并且在数组上运用各种算法进行题目求解,是我们学习的目标。常见的基于数组的问题有排序、二分查找、双指针、滑动窗口以及模拟。
三数之和力扣题目链接给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0。请你返回所有和为 0 且不重复的三元组。
核心 1 在于如何去除重复:排序后跳过相同的数字。
核心 2 在于如何去除不必要的计算:nums[i] 大于 0,就结束跳出。
class Solution { public List<List<Integer>> threeSum(int[] nums) { List<List<Integer>> List = new Ar ...
回溯法
怎样理解回溯回溯算法本质上也是一种通过穷举来解决问题的方法,它的核心思想是从一个初始状态出发,暴力搜素所有可能的解决方案,当遇到正确的解则将其记录,直到找到解或者尝试了所有可能的选择都无法找到解为止。回溯算法通常采用深度优先搜索来遍历解空间,之所以称之为回溯算法,是因为该算法在搜素解空间时会采用尝试与回退的策略。当算法在搜索过程中遇到某个状态无法继续前进或无法得到满足条件的解时,它会撤销上一步的选择,退回到之前的状态,并尝试其他可能的选择。此外,复杂的回溯问题通常包含一个或多个约束条件,约束条件常常用来剪枝。理解了以上基本概念,接下来尝试将回溯算法的尝试、回退、剪枝的主体框架提炼出来,提升代码的通用性。在以下框架代码中,state 表示问题的当前状态,choices 表示当前状态下可以做出的选择。
/* 回溯算法框架 */void backtrack(State state, List<Choice> choices, List<State> res) { // 判断是否为解 if (isSolution(state)) { ...
贪心算法
怎样理解贪心贪心的本质是选择每一个阶段的局部最优,从而达到全局最优的目标。也就是在每个问题的决策阶段,都选择当前看起来最优的选择(贪心)。究竟什么时候用到贪心?
能通过局部最优推出整体最优,就可以尝试贪心策略;若不可行,可能需要动态规划
贪心算法的一般解题步骤:
将大问题分解为若干子问题
总结出合适的贪心策略
求解每一个子问题的最优解
将局部最优解堆叠成全局最优解
贪心算法和动态规划的区别:
动态规划会根据之前阶段的所有决策来考虑当前决策,并使用过去子问题的解来构建当前子问题的解
贪心算法不会考虑过去的决策,而是一路向前地进行贪心选择,不断缩小问题范围,直至问题被解决
零钱兑换问题给定 n 种硬币,第 i 种硬币的面值为 coins[i-1],目标金额为 amt,每种硬币可以重复选取,问能够凑出目标金额的最少硬币数量。若无法凑出目标金额则返回 -1。
每次选择不大于且最接近它的硬币,不断循环该步骤,直到凑出目标金额
public int coinChangeGreedy(int[] coins, int amt){ //coins数组有序 int ...