目录
给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。
'.' 匹配任意单个字符'*' 匹配零个或多个前面的那一个元素解题思路:
- 匹配的意思,随着s长度的增加,p长度的增加,p是否依旧能够和s匹配起来,这就很容易想到动态规划。
- 从双方都为空开始考虑,直到最终全部走完
- 构建一个用于动态规划的二维数组f,分别代表不同长度下是否匹配的结果,i 代表 s 的长度,j 代表 p 的长度,真实的位数得是 i - 1 , j - 1
- 起始 f [0] [0] 为 true,代表两个空字符串能匹配起来
- i 从 0 开始,即当s为空的时候,p能否和空匹配,p如果是普通字符,则和s不匹配,只有遇到
*的时候才需要考虑特殊情况,例如'a*',可以代表'*'把'a'带走了,即也为空- j 从 1 开始,因为p为空的时候,s的长度只要是大于0的,就不能匹配起来,这种默认就为 false
- 循环开始,逐步考虑,当 s 的长度只有 i 的时候,随着 j 长度的增加,p 是否依旧能和 s 匹配起来
- 这里有特殊情况就是遇到
'*'的时候,所以分为两种情况进行讨论:
- 遇到
'*',
看'*'之前跟的字符是什么,是否和 第 i-1 位上的字符匹配(这里当前状态还是 i、j,j 是 '*',所以结果得根据'*'之前的走,看 j - 1)
匹配,则表示'*'使得之前的字符重复零次、一次或者多次
'a*' = '',等于 j 去掉两位,i 保持不变,看 j - 2'a*' = 'a',匹配,看 i - 1 ,j - 2'a*' = 'aaaaa......',看 i - 1,j 保持不变,因为'*'可以表示多个,例如aaaaaaaaa和a*- 不匹配,则表示
'*' 使得之前的字符重复零次,'a*' = '',即 p 去掉了两位,结果自然看 j - 2 和 当前长度依旧 为 i 的 s 是否匹配- 没有遇到
'*',看第i-1位和第j-1位是否匹配
- 这里如果遇到
'.',代表万能符,直接认为是匹配匹配:表示当前位置是匹配的,最终结果还是要看双方都没到这一步之前的时候。例如 aa 和 ba ,双方都到达了第二个a,当前位置匹配,则结果看第一个位置即 a 和 b,即看 i - 1 和 j - 1不匹配:直接为 false
- class Solution {
- public boolean isMatch(String s, String p) {
- int m = s.length();
- int n = p.length();
-
- boolean[][] f = new boolean[m+1][n+1];
- f[0][0] = true;
- for (int i=0;i<=m;i++){
- for (int j=1;j<=n;j++){
- if (p.charAt(j-1) == '*'){
- if (match(s,p,i,j-1)){
- f[i][j] = f[i][j-2] || f[i-1][j-2] || f[i-1][j] ;
- }else{
- f[i][j] = f[i][j-2];
- }
- }else{
- if (match(s,p,i,j)){
- f[i][j] = f[i-1][j-1];
- }
- }
- }
- }
- return f[m][n];
- }
-
- public boolean match(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);
- }
- }
难度中等1272收藏分享切换为英文接收动态反馈
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
解题思路:
- 前k个高的元素,很容易想到优先队列,始终维护长度为k的队列,按照次数进行排序。
- 遍历一遍数组,将对应的值以及出现的次数,用map保存下来。
- 创建优先队列,重写它的比较方法,这里用的是一个数组,数组的第一位是具体值,第二位是出现的次数,比较的话,就是按照次数进行排序,小的始终在顶部。
- 遍历之前存储的map集合,构建数组插入到队列中去,因为始终维护一个长度为k大小的队列,所以要判断当前队列长度是否超过k,如果超过,则和队列顶端最小的比,比较它们出现的次数,如果大于,则进行替换,然后依次进行下去,最终得到一个长度为k的,出现次数最高的这样的一个队列,最终将其转换为数组输出。
- class Solution {
- // 前k高,即你要维护一个k长度的优先队列
- // 用HashMap记录每个元素出现的个数
- public int[] topKFrequent(int[] nums, int k) {
- HashMap
map = new HashMap(); - for (int n : nums){
- map.put(n,map.getOrDefault(n,0)+1);
- }
-
- // int[] [值,次数]
- PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>(){
- public int compare(int[] n ,int[] m){
- return n[1] - m[1];
- }
- });
-
- // 遍历map,往queue里面插值
- for(Map.Entry
entry:map.entrySet()){ - int[] p = new int[]{entry.getKey(),entry.getValue()};
- // 如果不满k个,直接先往里面塞
- if (queue.size() < k){
- queue.add(p);
- }else{
- // 塞满了以后,拿出最小的一个判断,如果当前值比最小的要大,则进行替换,否则不动
- if (queue.peek()[1] < p[1]){
- queue.poll();
- queue.add(p);
- }
- }
- }
-
- int[] res = new int[k];
- for (int i=0;i
- res[i] = queue.poll()[0];
- }
- return res;
- }
- }
105. 从前序与中序遍历序列构造二叉树
难度中等1673收藏分享切换为英文接收动态反馈
给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
例如:
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]
解题思路:
- 前序遍历,根节点在前。中序遍历,根节点在中间。即从前序遍历中拿到根节点,根据根节点在中序遍历的位置,可以把中序遍历结果分为左右两颗子树,进而知道左子树长度,右子树长度,根据左子树长度,回到前序遍历中,找到左子树,也可以将其分为左右两颗子树,然后递归下去。
- 拿例子举例就是,前序遍历第一个元素肯定为根节点,拿到 根节点 3 去中序遍历中找到 3 的下角标位置为 1,即 从 0 到 1 之前为左子树,1 之后直到最后为右子树,根据计算可以知道左子树大小为 1,拿到大小去前序遍历中,从 3 往后数 1 则为左子树,左子树后面跟的就是右子树。即 9 为 左子树 ,20 15 7 为右子树。这里有指针对应记录树的开始位置和结束位置。而又因为 20 15 7 为前序遍历,所以 20 为根节点,拿到 20 去中序遍历中查找其位置,因为之前找根节点 3 的时候,已经划分最开始的左右子树,这里有指针记录当前右子树的开始位置在15,即15 到 20之前为左子树,20之后直到最后为右子树,由此构建出一个完整的树。
- class Solution {
- public TreeNode buildTree(int[] preorder, int[] inorder) {
- return dps(preorder,0,preorder.length,inorder,0,inorder.length);
- }
-
- public TreeNode dps(int[] preorder, int p_start,int p_end,int[] inorder,int i_start,int i_end){
- if (p_start == p_end){
- return null;
- }
-
- TreeNode root = new TreeNode(preorder[p_start]);
- // 找到根节点,划分左右两棵子树
- int root_index = 0;
- for (int i=0;i
- if (inorder[i] == preorder[p_start]){
- root_index = i;
- break;
- }
- }
- // 这里就知道左子树的长度
- int leftNum = root_index - i_start;
-
- // 划分左子树,根节点后面跟左子树的长度即为左子树
- root.left = dps(preorder,p_start+1,p_start+leftNum+1,inorder,i_start,root_index);
- root.right = dps(preorder,p_start+leftNum+1,p_end,inorder,root_index+1,i_end);
- return root;
- }
- }
438. 找到字符串中所有字母异位词
难度中等955收藏分享切换为英文接收动态反馈
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
解题思路:
- 这道题很容易想到,暴力解决,枚举s中每个和p长度相等的子字符串,判断其是否是异位词。这里判断两个字符串是否为异位词采用的就是先转换为字符数组,进行排序,最终比较是否相同。
- 同样这道题还能用滑动窗口来做,也容易理解。维护一个26长度的数组,位置对应a-z,数值对应数量,这样一个字符数组就代表一个字符串。滑动窗口大小为p的长度,将窗口内的子字符串也转换为数组,比较和p转换的数组是否相同,滑动的话,就将左边的剔除,将右边的加入,对应的就是数组位置上数字的加减
- class Solution {
- public List
findAnagrams(String s, String p) { - int s_length = s.length();
- int p_length = p.length();
- char[] new_p_c = p.toCharArray();
- Arrays.sort(new_p_c);
- List
res = new ArrayList(); - for (int i=0;i<=s_length-p_length;i++){
- char[] new_c = s.substring(i,i+p_length).toCharArray();
- Arrays.sort(new_c);
- if (Arrays.equals(new_p_c,new_c)){
- res.add(i);
- }
- }
- return res;
- }
- }
- class Solution {
- public List
findAnagrams(String s, String p) { - int s_length = s.length();
- int p_length = p.length();
- List
res = new ArrayList(); - int[] num_1 = new int[26]; // s 子字符串转换的数组
- int[] num_2 = new int[26]; // p 字符串转换的数组
- if (p_length > s_length){
- return res;
- }
- for (int i=0;i
- num_1[s.charAt(i) - 'a']++;
- num_2[p.charAt(i) - 'a']++;
- }
-
- // 如果一开始两个就相等,则加入
- if (Arrays.equals(num_1,num_2)){
- res.add(0);
- }
-
- for (int i=1;i<=s_length-p_length;i++){
- // 减去左边
- num_1[s.charAt(i-1) - 'a']--;
- // 加入右边
- num_1[s.charAt(i + p_length - 1) - 'a']++;
-
- if (Arrays.equals(num_1,num_2)){
- res.add(i);
- }
- }
- return res;
- }
- }
437. 路径总和 III
难度中等1400收藏分享切换为英文接收动态反馈
给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
解题思路:
- 采用前缀和的形式,记录经过每个节点的时候的前缀和,key为前缀和,value为出现的次数。如果能在map中找到 (前缀和 - target),就说明存在从该节点到之前的节点,中间正好差了个 target的这种情况。
- 把当前节点加入,计算当前前缀和
- 查看之前是否存在 前缀和 = 当前前缀和 - target ,如果存在,就表示有这样一条,就可以加入进去
- 把当前前缀和也记录一下,如果和之前前缀和重复了,则+1,表示有多条
- 分别递归左子树和右子树
- 为了互相不影响,这条路径的前缀和不会影响到别的路径的前缀和。整体递归结束后,应当还原,将该前缀和删除。因为可能存在多条,所以这里不会删除,只是进行 -1 操作
map.put(0, 1) :因为任何节点本身也可以形成一个路径。如果某个节点的值就为target,那么它本身就是一个解。currSum - target = 0 , 当前节点算一个解,所以一开始就要申明。
- class Solution {
- // 前缀和的方式
- public int pathSum(TreeNode root, int targetSum) {
- HashMap
map = new HashMap(); - map.put(0L,1);
- return dps(root,map,targetSum,0L);
- }
-
- public int dps(TreeNode root, HashMap
map ,int targetSum,long currSum) { - if (root == null){
- return 0;
- }
- long res = 0;
-
- currSum += root.val;
-
- res += map.getOrDefault(currSum-targetSum,0);
- map.put(currSum,map.getOrDefault(currSum,0)+1);
-
-
- res += dps(root.left,map,targetSum,currSum);
- res += dps(root.right,map,targetSum,currSum);
-
- map.put(currSum,map.getOrDefault(currSum,0)-1);
- return (int)res;
- }
-
- }
64. 最小路径和
难度中等1300收藏分享切换为英文接收动态反馈
给定一个包含非负整数的 m x n 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
解题思路:
- 动态规划的问题,路径只能往右或者往下,所以说只要一个累加,选择小的那一方就可以。将上边界和左边界先初始化好,中间的值只需逐步选择小的值,每个步径的值都是累加的结果。
- class Solution {
- public int minPathSum(int[][] grid) {
- int n = grid.length;
- int m = grid[0].length;
- int[][] res = new int[n][m];
- res[0][0] = grid[0][0];
- for (int i=1;i
- res[i][0] = res[i-1][0] + grid[i][0];
- }
- for (int j=1;j
- res[0][j] = res[0][j-1] + grid[0][j];
- }
- for (int i=1;i
- for (int j=1;j
- res[i][j] = Math.min(res[i-1][j],res[i][j-1]) + grid[i][j];
- }
- }
- return res[n-1][m-1];
- }
- }
62. 不同路径
难度中等1475收藏分享切换为英文接收动态反馈
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
解题思路:
- 动态规划的问题,初始化两条边界都为1,因为就一条路。每次都只能向下向右,所以每次走到下一步,都有两种选择,选择数即是上方和左方的累加。
- class Solution {
- public int uniquePaths(int m, int n) {
- int[][] res = new int[m][n];
- res[0][0] = 1;
- for (int i=1;i
- res[i][0] = 1;
- }
- for (int j=1;j
- res[0][j] = 1;
- }
- for (int i=1;i
- for (int j=1;j
- res[i][j] = res[i-1][j] + res[i][j-1];
- }
- }
- return res[m-1][n-1];
- }
- }
56. 合并区间
难度中等1571收藏分享切换为英文接收动态反馈
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
解题思路:
- 首先根据数组的第一位进行排序,保证顺序,将第一个数组作为起始数组,也就是比较组,第二个数组开始和第一个比,如果第二个数组的起始已经比比较组的结束要大,则可以作为新的数组添加进去,相反,则需要进行合并,因为是排过序的,所以起始肯定是比较组,结束则是比较两个组的结束谁更加大,最终结果就选谁。
- class Solution {
- public int[][] merge(int[][] intervals) {
- if (intervals.length == 1){
- return intervals;
- }
- Arrays.sort(intervals, (v1, v2) -> v1[0] - v2[0]);
- List<int[]> res = new ArrayList();
- res.add(intervals[0]);
- for (int i=1;i
- if (res.get(res.size()-1)[1] < intervals[i][0]){
- res.add(intervals[i]);
- }else{
- res.get(res.size()-1)[1] = Math.max(res.get(res.size()-1)[1], intervals[i][1]);
- }
- }
- return res.toArray(new int[res.size()][]);
- }
- }
55. 跳跃游戏
难度中等1929收藏分享切换为英文接收动态反馈
给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
解题思路:
- 看的就是你能去到的最远的地方,同时把最远的地方记录下来。当前会记录一个能到达的最远地方,如果遍历到当前位置超过能到达的最远地方则表示到不了,如果可以到达,则更新从当前位置出发能到达的最远地方,比较更新最大值,一直遍历下去直到倒数第二位,看最终的结果,能够到达的地方是不是远远超过最后一位。
- class Solution {
- public boolean canJump(int[] nums) {
- if (nums[0] == 0 && nums.length > 1) return false;
- int res = 0;
- for (int i=0;i
1;i++){ - if (i <= res){
- res = Math.max(res,nums[i]+i);
- }
- }
- return res >= nums.length-1;
- }
- }
75. 颜色分类
难度中等1347收藏分享切换为英文接收动态反馈
给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
必须在不使用库的sort函数的情况下解决这个问题。
解题思路:
- 三种颜色分别用0 1 2表示,每个各占一块区域,0占左边,1占中间,2占右边,所以遇见0就往左边塞,遇到2就往右边塞,遇到1可以不动。
- 三个指针,其中一个指针负责遍历。每次遇到0,当前位置和左指针交换,交换完毕后,左指针和遍历指针都要一起移动。如果遇到的是1则,不动,如果遇到的是2,尾部指针向前移动,然后和尾部指针交换,,当前指针不动,因为不知道交换过来的是什么,所以还需要再判断一次当前位置的数值需要往哪里移动。
- class Solution {
- public void sortColors(int[] nums) {
- int left = 0;
- int mid = 0;
- int right = nums.length;
- while (mid < right){
- if (nums[mid] == 0){
- swap(nums,left,mid);
- mid++;
- left++;
- }else if (nums[mid] == 1){
- mid++;
- }else if (nums[mid] == 2){
- right--;
- swap(nums,mid,right);
- }
- }
- }
-
- public void swap(int[] nums,int i,int j){
- int x = nums[i];
- nums[i] = nums[j];
- nums[j] = x;
- }
- }
78. 子集
难度中等1722收藏分享切换为英文接收动态反馈
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
解题思路:
- 典型的一道回溯算法题,这边每经过一个值,都需要输入到最终的结果集当中。区别在于只往后,之前用过的就不再用了。
- class Solution {
- public List
> subsets(int[] nums) {
- List
> res = new ArrayList();
- dps(nums,res,new ArrayList(),0);
- return res;
- }
-
- public void dps(int[] nums,List
> res,List list,int depth)
{ - res.add(new ArrayList<>(list));
- for (int i=depth;i
- list.add(nums[i]);
- dps(nums,res,list,i+1);
- list.remove(list.size()-1);
- }
- }
- }
79. 单词搜索
难度中等1380收藏分享切换为英文接收动态反馈
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
解题思路:
- 走格子,走过的格子不能再走,找到格子中的单词和 word 匹配,首先在格子中找到 word 头个单词字母,然后从其开始出发往后找寻剩下的。
- 这里注意几个条件,首先遇到超过格子边界的就不用再找下去了,其次因为不能重复使用,所以需要有个数组记录使用情况,遇到用过的也直接返回,说明这条路不通。其次就是都没用过,就得比较是不是接下去得字母,例如 ABC,从A开始,接下来就找B,找到B接下来找C。最终返回条件就是如果能成功找到最后一位则返回 true。因为是格子,所以可以从格子的上下左右走,只要一条路符合条件,就可以接着往下走,这里如果每条路都走不通,说明这个格子错误,所以要暂时回退,回退的时候得把这个格子使用记录消除。
- class Solution {
- public boolean exist(char[][] board, String word) {
- int len = word.length();
- int m = board.length;
- int n = board[0].length;
- boolean[][] used = new boolean[m][n];
- for (int i=0;i
- for (int j=0;j
- if (board[i][j] == word.charAt(0)){
- if (dfs(len,m,n,i,j,0,used,board,word)){
- return true;
- }
- }
- }
- }
- return false;
- }
-
- public boolean dfs(int len,int m,int n,int i,int j,int index,boolean[][] used,char[][] board,String word){
- if (i < 0 || j < 0 || i > m-1 || j > n-1 || used[i][j] || board[i][j] != word.charAt(index) ){
- return false;
- }
- if (index == len-1){
- return word.charAt(index) == board[i][j];
- }
- used[i][j] = true;
- if (dfs(len,m,n,i+1,j,index+1,used,board,word) || dfs(len,m,n,i-1,j,index+1,used,board,word) || dfs(len,m,n,i,j+1,index+1,used,board,word) || dfs(len,m,n,i,j-1,index+1,used,board,word)){
- return true;
- }
- used[i][j] = false;
- return false;
- }
- }
406. 根据身高重建队列
难度中等1346收藏分享切换为英文接收动态反馈
假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。
请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。
解题思路:
- 这道题有个技巧。先对原数组进行排序,因为本身就是看身高排序,所以对身高进行排序,如果遇到相同得,第二位 0 肯定在 1 得前面,所以最终,第一位是身高降序排列,排序后再对第二位进行升序排列。原数组就变成了 [[7,0],[7,1],[6,1],[5,0],[5,2],[4,4]]。这时候开始插入,因为本身就是按照身高排序得,高的先进去排序,后面矮的无论怎么移动,都不会影响到高的,所以就只需要经过细微调整,而调整得方式就看第二位,排在它前面得有几个,每次插入当前得时候会进行判断。
-
例如:
输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
- 插入 [7,0] 变成 [7,0]
- 插入 [7,1] 变成 [7,0],[7,1]
- 插入 [6,1] 变成 [7,0],[6,1],[7,1],因为6得前面只有一位比它高,所以理所当然第二位
- 插入 [5,0] 变成 [5,0],[7,0],[6,1],[7,1],因为5得前面没有比它高,所以理所当然第一位
- 插入 [5,2] 变成 [5,0],[7,0],[5,2],[6,1],[7,1],因为5得前面有两个比它高的,所以理所当然第三位
- 插入 [4,4] 变成 [5,0],[7,0],[5,2],[6,1],[4,4],[7,1],因为4得前面有四个比它高的,所以理所当然第五位
- class Solution {
- public int[][] reconstructQueue(int[][] people) {
- Arrays.sort(people,new Comparator<int[]>(){
- @Override
- public int compare(int[] person1, int[] person2){
- if (person1[0] != person2[0]){
- // 如果两个数不相等,第一个数则从高到低排序
- return person2[0] - person1[0];
- }else{
- // 如果两个数相等,第二个数则从低到高排序
- return person1[1] - person2[1];
- }
- }
- });
- List<int[]> res = new ArrayList();
- for (int i=0;i
- // 如果队列中得数量超过了,应该排在第几位,则插入到对应得第几位中去
- if (res.size() > people[i][1]){
- res.add(people[i][1],people[i]);
- }else{
- // 暂且前面得人如果没超过,则直接插入到末尾
- res.add(res.size(),people[i]);
- }
- }
- return res.toArray(new int[res.size()][]);
- }
- }
96. 不同的二叉搜索树
难度中等1865收藏分享切换为英文接收动态反馈
给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
解题思路:
满足以下性质:
令h(0)=1,h(1)=1,满足递推式 h(n)= h(0)*h(n-1)+h(1)*h(n-2) + … + h(n-1)h(0) (n>=2)。
以题目来说,假设总长度为n,当前访问到第 j 个根节点,那么它的左子树可以是 j - 1 个,右子树是 n - j 个。这里 j 的取值不同,可能的答案就不同,所以可以进行累加。
- class Solution {
- public int numTrees(int n) {
- int[] dp = new int[n+1];
- dp[0] = 1;
- dp[1] = 1;
- for (int i=2;i<=n;i++){
- for (int j=1;j<=i;j++){
- dp[i] += dp[j-1]*dp[i-j];
- }
- }
- return dp[n];
- }
- }
300. 最长递增子序列
难度中等2655收藏分享切换为英文接收动态反馈
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
解题思路:
- 维护了dp数组用来动态规划:
- 初始状态,单个数字本身就可以作为一个子序列,所以长度都可以先设置为1
- 依次遍历一遍整个数组,当遍历到该位置时,这之前的数字都要和其作比较,这样才能直到是否是递增序列,同时记录下到当前位置时,最大的子序列数量
例如:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
初始值:dp:[1,1,1,1,1,1,1,1]
从 10 开始:因为是第一位,所以 dp 保持不变 ,dp:[1,1,1,1,1,1,1,1]
从 9 开始:9 和它之前的相比,就一个10,但因为是降序,所以本身不构成递增序列,dp 依旧保持不变,dp:[1,1,1,1,1,1,1,1]
从 2 开始:2 和它之前的相比,一个 10 ,一个 9,依旧降序,dp 保持不变,dp:[1,1,1,1,1,1,1,1]
从 5 开始:10 ,9 都是降序,2 是 升序,则 2,5 构成 递增序列,原本 2 的位置是 1,所以 5 的位置在 2 位置的基础上 + 1,dp:[1,1,1,2,1,1,1,1]
从 3 开始:10 ,9 ,5 都是降序,2 是 升序,则 2 ,3 构成递增序列,原本 2 的位置是1,所以 3 的位置在 2 位置基础上 + 1,dp:[1,1,1,2,2,1,1,1]
从 7 开始:10 , 9 都是降序,2,5,3 都是升序,所以要有比较,取最大者。2,7 构成递增,7 就是 在 2的位置上 + 1。5,7 构成递增,7 在 5 的位置上 + 1。3,7构成递增,7 在 3 的基础上 + 1,取最大值则为 3 ,dp:[1,1,1,2,2,3,1,1]
从 101 开始 :之前的都可以构成递增数列,所以可以是10,101,这里是 10 的基础上 + 1,后面依旧,取最大,结果 dp:[1,1,1,2,2,3,4,1]
从 18 开始:10,9,2,5,3,7 都可以构成,在此基础上 + 1,最终结果 dp:[1,1,1,2,2,3,4,4]
最终只要从该数组中取最大值,则是能构成的最大递增子序列
- class Solution {
- public int lengthOfLIS(int[] nums) {
- int res = 0;
- int[] dp = new int[nums.length];
- Arrays.fill(dp,1);
- // 每一位遍历一遍
- for (int i=0;i
- // 遍历每一位的时候,之前的数都要和其比较
- for (int j=0;j
- if (nums[j] < nums[i]){
- dp[i] = Math.max(dp[i],dp[j]+1);
- }
- }
- res = Math.max(res,dp[i]);
- }
- return res;
- }
- }
152. 乘积最大子数组
难度中等1731收藏分享切换为英文接收动态反馈
给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
测试用例的答案是一个 32-位 整数。
子数组 是数组的连续子序列。
解题思路:
- 暴力破解:例举出所有的连续子序列,计算出它们值,维护一个最大值
- 动态规划:依次乘起来,把之前的乘起来的数和当前数作比较,如果大于则表示还是乘起来有效,如果反而小了,说明之前的无效,可以从当前重新算起。因为会存在负数,所以要同时维护最小值,再遇到负数的时候就乘以最小值
- class Solution {
- public int maxProduct(int[] nums) {
- if (nums.length == 1){
- return nums[0];
- }
- int max = Integer.MIN_VALUE;
-
- for (int i=0;i
- int curr = nums[i];
- if (curr >= 0){
- max = Math.max(max,curr);
- }
-
- for (int j=i+1;j
- curr *= nums[j];
- if (curr >= 0){
- max = Math.max(max,curr);
- }
- }
- }
- return max;
- }
- }
- class Solution {
- public int maxProduct(int[] nums) {
- int res = Integer.MIN_VALUE;
- int max = 1;
- int min = 1;
-
- for (int i=0;i
- if (nums[i] < 0){
- int temp = max;
- max = min;
- min = temp;
- }
- max = Math.max(nums[i],nums[i]*max);
- min = Math.min(nums[i],nums[i]*min);
- res = Math.max(res,max);
- }
-
- return res;
- }
- }
148. 排序链表
难度中等1718收藏分享切换为英文接收动态反馈
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
解题思路:
- 用优先队列,遍历一遍节点,加入到队列中,排序过后,再依个取出,重新组成链表(不符合题意)
- 用归并排序(递归),快慢指针定位中间节点,拆分出左右两部分,然后递归拆分,直到拆到最小单位,然后对应的左右开始合并,小的在前,大的在后。等于是合并两个有序链表。
- class Solution {
- public ListNode sortList(ListNode head) {
- PriorityQueue
queue = new PriorityQueue(new Comparator(){ - public int compare(ListNode head1,ListNode head2){
- return head1.val - head2.val;
- }
- });
- while (head != null){
- ListNode next = head.next;
- head.next = null;
- queue.add(head);
- head = next;
- }
- int size = queue.size();
- ListNode res = new ListNode(0);
- ListNode h = res;
- for (int i=0;i
- h.next = queue.poll();
- h = h.next;
- }
- return res.next;
- }
- }
- class Solution {
- // 归并排序
- public ListNode sortList(ListNode head) {
- if (head == null || head.next == null){
- return head;
- }
- // 快慢指针找到中间节点
- ListNode fast = head.next;
- ListNode slow = head;
- while (fast != null && fast.next != null){
- fast = fast.next.next;
- slow = slow.next;
- }
- ListNode temp = slow.next;
- slow.next = null;
- // head是左边,slow是右边
- // 递归着分解
- ListNode left = sortList(head);
- ListNode right = sortList(temp);
-
- // 定义一个新的,用于合并
- ListNode res = new ListNode(0);
- ListNode h = res;
- while (left != null && right != null){
- if (left.val < right.val){
- h.next = left;
- left = left.next;
- }else{
- h.next = right;
- right = right.next;
- }
- h = h.next;
- }
-
- h.next = left != null ? left : right;
-
- return res.next;
- }
- }
279. 完全平方数
难度中等1440收藏分享切换为英文接收动态反馈
给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。
完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 和 11 不是。
解题思路:
- 动态规划
dp[ i ] = i :比如当前是3,可能的组成就是 + 1 + 1 + 1,这样结果就是 3,即都是由1组成的。
总体思想就是,a^2 + b^2 = n,遍历 j 的同时,通过 n - j * j 找到 b^2,看 b^2 最小是由多少个数量构成的,最终就是 b^2 的方案 + 当前的 j^2,即 dp [ i - j * j ] + 1。
举例: 5
i = 1,dp [1] = 1; 当 j = 1,dp[1-1*1] = 0,最终为1;
i = 2,dp [2] = 2; 当 j = 1,dp[2-1*1] = 1,最终为2;
i = 3,dp [3] = 3; 当 j = 1,dp[3-1*1] = 2,最终为3;
i = 4,dp [4] = 1; 当 j = 1,dp[4-1*1] = 3,最终为4; 当 j =2,dp[4-2*2] = 0,最终为1,取最小为1
i = 5,dp [5] = 2; 当 j = 1,dp[5-1*1] = 1,最终为2; 当 j =2,dp[5-2*2] = 1,最终为2,取最小为2
- class Solution {
- public int numSquares(int n) {
- int[] dp = new int[n+1];
- for (int i=1;i<=n;i++){
- dp[i] = i;
- for (int j=1;i-j*j>=0;j++){
- dp[i] = Math.min(dp[i],dp[i-j*j]+1);
- }
- }
- return dp[n];
- }
- }
208. 实现 Trie (前缀树)
难度中等1244收藏分享切换为英文接收动态反馈
Trie(发音类似 "try")或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。
请你实现 Trie 类:
Trie() 初始化前缀树对象。void insert(String word) 向前缀树中插入字符串 word 。boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。
解题思路:
- 前缀树,顾名思义把一个个字符弄乘树的形式,当查找的时候可以沿着树遍历查找,插入方法这里用一个长度26的数组代替,在对应的位置再次挂载一个长度26的数组,这样挂载下去,就可以表示一个完整的字符串。查找方法这里是遍历字符串中字符算出字符应该所在位置,该位置如果没有挂载任何东西,则表示断路,如果字符串还有后续则不匹配,只有字符串都匹配到了才行。
- 这里会出现一个问题,就算匹配到相应结果,但是不确定是不是就是想要的字符串,可能该位置还是会挂载其他,举个例子说明:app、apple,先有了app,后续的le继续挂载在p后面。这时候当查找ap的时候,显然ap的p后面还挂载了东西,结果就是理应搜不到的,但是查找app的时候,虽然p后面还挂载le,但是app确实之前完整的字符串输入,为了解决这个问题,需要额外的一个标志位判断,当输入一个字符串例如app的时候,在末尾p处打上记号,表示这是一个字符串的结尾,当查看app的时候,遍历到p处停止,且p是带有标志位的,则表示一个完整的字符串,返回查找到。如果查前缀的话,只要查到就满足,无需管是不是完整。
- class Trie {
-
- class TrieNode {
- boolean val;
- TrieNode[] children = new TrieNode[26];
- }
-
- private TrieNode root;
-
- public Trie() {
- root = new TrieNode();
- }
-
- public void insert(String word) {
- TrieNode p = root;
- for (char c : word.toCharArray()){
- int i = c - 'a';
- if (p.children[i] == null){
- p.children[i] = new TrieNode();
- }
- p = p.children[i];
- }
- p.val = true;
- }
-
- public boolean search(String word) {
- TrieNode p = root;
- for (char c : word.toCharArray()){
- int i = c - 'a';
- if (p.children[i] == null)return false;
- p = p.children[i];
- }
- return p.val;
- }
-
- public boolean startsWith(String prefix) {
- TrieNode p = root;
- for (char c : prefix.toCharArray()){
- int i = c - 'a';
- if (p.children[i] == null)return false;
- p = p.children[i];
- }
- return true;
- }
- }
-
- /**
- * Your Trie object will be instantiated and called as such:
- * Trie obj = new Trie();
- * obj.insert(word);
- * boolean param_2 = obj.search(word);
- * boolean param_3 = obj.startsWith(prefix);
- */
53. 最大子数组和
难度简单5152收藏分享切换为英文接收动态反馈
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
解题思路:
- 动态规划:平移的过程中,不断更新最大值。不断更新最大子序列的左端,如果累加的结果和当前的数比起来,当前的数更大,说明之前的数起了反效果,所以要不断更新左端。其次每次都得更新最大值,来保证最后能获取到最大值。
- 分治:将数组一分为二,一直分下去,直到最小单位再合并,合并的时候要弄清除概念。即最终所求是区间的最大值,这个最大值可能出现在左边,也可能出现在右边,也有可能出现在中间,所以每个都得维护这几个变量。
lSum:以左侧为开端,最大子数组和
rSum:以右侧为开端,最大子数组和
mSum:整个区间的最大值
iSum:整个区间的和
合并后的lSum。有2中可能:左序列+右序列的左侧 ,左序列的左侧
合并后的rSum。有2中可能:右序列+左序列的右侧 ,右序列的右侧
合并后的mSum。有3种可能:
合并后的最大连续子列和序列完全在L中,即 L.mSum
合并后的最大连续子列和序列完全在R中,即 R.mSum
合并后的最大连续子列和序列横跨L和R,则该序列一定是从L中的某一位置开始延续到mid,然后从mid+1(R的左边界)开始延续到R中的某一位置。即L.rSum + R.lSum
- class Solution {
- public int maxSubArray(int[] nums) {
- int pre = 0;
- int max = nums[0];
- for (int x:nums){
- pre = Math.max(pre+x,x);
- max = Math.max(max,pre);
- }
- return max;
- }
- }
- class Solution {
- public class Status{
- int lSum; // 以左侧为开端,最大和
- int rSum; // 以右侧为开端,最大和
- int mSum; // 区间最大和
- int iSum; // 区间整体和
-
- public Status(int lSum,int rSum,int mSum,int iSum){
- this.lSum = lSum;
- this.rSum = rSum;
- this.mSum = mSum;
- this.iSum = iSum;
- }
- }
-
- public int maxSubArray(int[] nums) {
- return getInfo(nums,0,nums.length-1).mSum;
- }
-
- public Status getInfo(int[] a,int left,int right){
- if (left == right){
- return new Status(a[left],a[left],a[left],a[left]);
- }
- int m = (left + right) >> 1; // 获取中间值,右移等于除二
- Status l = getInfo(a,left,m);
- Status r = getInfo(a,m+1,right);
- return com(l,r);
- }
-
- public Status com(Status left,Status right){
- int iSum = left.iSum + right.iSum;
- int lSum = Math.max(left.lSum,left.iSum + right.lSum);
- int rSum = Math.max(right.rSum,right.iSum + left.rSum);
- //可能是左区间的最大值,也可能是右区间的最大值,也有可能是中间合并即左边的右端点开始的最大值加右边以左端开始的最大值
- int mSum = Math.max(left.mSum,Math.max(right.mSum,left.rSum+right.lSum));
- return new Status(lSum,rSum,mSum,iSum);
- }
- }
1. 两数之和
难度简单14940收藏分享切换为英文接收动态反馈
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
解题思路:
- 需要用到额外的空间,因为是查找两个数和为target,所以当你拿到一个数,你就知道另外一个对应的数是多少,利用这个思想,遍历的同时去查找看是否存在target-curr,如果存在则表示找到这一对值,如果不存在则表示还没有遇到,则可以插入进去。key作为另一半,value则是当前位置用于最终返回。
- class Solution {
- public int[] twoSum(int[] nums, int target) {
- int[] res = new int[2];
- HashMap
map = new HashMap(); - for (int i=0;i
- if (map.containsKey(nums[i])){
- return new int[]{map.get(nums[i]),i};
- }else{
- map.put(target-nums[i],i);
- }
- }
- return res;
- }
- }
20. 有效的括号
难度简单3417收藏分享切换为英文接收动态反馈
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
解题思路:
- 用一个栈来接受左括号,当遇到右括号的时候,需要判断栈顶元素是不是对应的括号,如果不是则不是一个有效的括号,如果对应,则将栈顶元素弹出,表示匹配。这里注意,当栈为空的时候,直接放入右括号则是错误情况,例如:‘)’,无论后续如何,都是对应不上的。
- class Solution {
- public boolean isValid(String s) {
- Stack
stack = new Stack(); - char[] c = s.toCharArray();
- for (char i : c){
- if (i == '(' || i == '{' || i == '['){
- stack.push(i);
- }else{
- if (!stack.isEmpty()){
- if (i == ')' && stack.peek() != '('){
- return false;
- }else if (i == '}' && stack.peek() != '{'){
- return false;
- }else if (i == ']' && stack.peek() != '['){
- return false;
- }
- stack.pop();
- }else{
- return false;
- }
- }
- }
- return stack.isEmpty();
- }
- }
70. 爬楼梯
难度简单2550收藏分享切换为英文接收动态反馈
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
解题思路:
- 经典爬楼,每次可以一步或者两步,所以用动态规划求解。每增加一层,都可以是前一层跳过来,或者是前两层调过来。很容易得到 dp[i] = dp [i-1] + dp[i-2]
- class Solution {
- public int climbStairs(int n) {
- if (n <= 1){
- return n;
- }
- int[] dp = new int[n+1];
- dp[0] = 0;
- dp[1] = 1;
- dp[2] = 2;
- for (int i=3;i<=n;i++){
- dp[i] = dp[i-1] + dp[i-2];
- }
- return dp[n];
- }
- }
338. 比特位计数
难度简单1048收藏分享切换为英文接收动态反馈
给你一个整数 n ,对于 0 <= i <= n 中的每个 i ,计算其二进制表示中 1 的个数 ,返回一个长度为 n + 1 的数组 ans 作为答案。
解题思路:
- 这里分为两种情况偶数和奇数,偶数的1的个数总和它的倍数的1的个数相同,例如:2 4 8 16 它们的1的个数都为1,所以只要知道2的1的个数,就能知道后面2 4 8 等的1的个数。奇数的1的个数总是比它小1的偶数的1的个数再多1,例如:1 就比0的1个个数多1,3就比2的1的个数多1,5就比4的1的个数多1。
- class Solution {
- public int[] countBits(int n) {
- int[] res = new int[n+1];
- res[0] = 0;
- for (int i=1;i<=n;i++){
- // 奇数,比偶数+1
- if (i%2==1){
- res[i] = res[i-1] + 1;
- }else{
- // 偶数,和 /2 结果相同 2 4 8 16 32 64 1的个数都相同
- res[i] = res[i/2];
- }
- }
- return res;
- }
- }
21. 合并两个有序链表
难度简单2554收藏分享切换为英文接收动态反馈
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
解题思路:
- 合并两个有序链表和合并两个有序数组一样,只不过换成了链表,所以自己得构建一个链表,然后依次去比较两个链表的头节点,谁比较小就取谁,最终一方将被取完,这时候循环比较停止,将剩下的一方直接连接到构建的链表末尾,最终返回结果。
- /**
- * Definition for singly-linked list.
- * public class ListNode {
- * int val;
- * ListNode next;
- * ListNode() {}
- * ListNode(int val) { this.val = val; }
- * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
- * }
- */
- class Solution {
- public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
- ListNode head = new ListNode(0);
- ListNode res = head;
- while (list1 != null && list2 != null){
- if (list1.val < list2.val){
- res.next = list1;
- res = res.next;
- list1 = list1.next;
- }else{
- res.next = list2;
- res = res.next;
- list2 = list2.next;
- }
- }
- if (list1 != null) res.next = list1;
- if (list2 != null) res.next = list2;
- return head.next;
- }
- }
94. 二叉树的中序遍历
难度简单1514收藏分享切换为英文接收动态反馈
给定一个二叉树的根节点 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
inorderTraversal(TreeNode root) { - List
res = new ArrayList(); - dfs(root,res);
- return res;
- }
-
- public void dfs(TreeNode root,List
res) { - if (root == null)return;
- dfs(root.left,res);
- res.add(root.val);
- dfs(root.right,res);
- }
- }
- /**
- * 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
inorderTraversal(TreeNode root) { - List
res = new ArrayList(); - if (root == null)return res;
- Deque
queue = new LinkedList(); - while (root != null || !queue.isEmpty()){
- while (root != null){
- queue.push(root);
- root = root.left;
- }
- root = queue.pop();
- res.add(root.val);
- root = root.right;
- }
- return res;
- }
- }
283. 移动零
难度简单1671收藏分享切换为英文接收动态反馈
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
解题思路:
- 总体就是遇到非零,就从头开始填入,填入后,原本的位置就可以置为0,这里注意一点,如果本身填入的位置就是自己,则表示不动,无需置为0。
- class Solution {
- public void moveZeroes(int[] nums) {
- int index = 0;
- for (int i=0;i
- if (nums[i] != 0){
- nums[index] = nums[i];
- if (index != i){
- nums[i] = 0;
- }
- index++;
- }
- }
- }
- }
101. 对称二叉树
难度简单2034收藏分享切换为英文接收动态反馈
给你一个二叉树的根节点 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 boolean isSymmetric(TreeNode root) {
- return isSym(root.left,root.right);
- }
-
- public boolean isSym (TreeNode left,TreeNode right){
- if (left == null && right == null){
- return true;
- }
- if (left == null && right != null){
- return false;
- }
- if (left != null && right == null){
- return false;
- }
- if (left.val == right.val){
- return isSym(left.left,right.right) && isSym(left.right,right.left);
- }
- return false;
- }
- }
104. 二叉树的最大深度
难度简单1313收藏分享切换为英文接收动态反馈
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
解题思路:
- 这里得写法和层序遍历一样。按层放入队列,再取出,再放入,每次取都算是一次记录,进行累加,最终得到答案。
- /**
- * 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 int maxDepth(TreeNode root) {
- Deque
queue = new LinkedList(); - if (root == null){
- return 0;
- }
- queue.add(root);
- int max = 0;
- while (!queue.isEmpty()){
- int size = queue.size();
- max++;
- for (int i=0;i
- TreeNode node = queue.poll();
- if (node.left != null){
- queue.add(node.left);
- }
- if (node.right != null){
- queue.add(node.right);
- }
- }
- }
- return max;
- }
- }
136. 只出现一次的数字
难度简单2511收藏分享切换为英文接收动态反馈
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
解题思路:
- 都是成对,只有一个落单。先从小到大排序,将相同的挨在一起,两两判断,不同的话就是前面那个为答案,如果最终都没有找到,说明最后一位就是答案。
- 该题,还可以用异或来解决,只要是相同的异或就变成0了,0和任何数异或还是原来的数,所以最后留下的则是答案。
- class Solution {
- public int singleNumber(int[] nums) {
- Arrays.sort(nums);
- for (int i=1;i
- if (nums[i] != nums[i-1]){
- return nums[i-1];
- }
- i++;
- }
- return nums[nums.length-1];
- }
- }
- class Solution {
- public int singleNumber(int[] nums) {
- int res = nums[0];
- for (int i=1;i
- res ^= nums[i];
- }
- return res;
- }
- }
141. 环形链表
难度简单1557收藏分享切换为英文接收动态反馈
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
解题思路:
- 快慢指针,交错,一个移动一步,一个移动两步,如果有环总会相遇,要判断的很简单,相遇的时候停下来,同时要保证快指针不为null,不然next方法会报错,最后停止的时候进行判断,究竟是相遇停止还是没有环导致fast为空才停止。
- /**
- * Definition for singly-linked list.
- * class ListNode {
- * int val;
- * ListNode next;
- * ListNode(int x) {
- * val = x;
- * next = null;
- * }
- * }
- */
- public class Solution {
- public boolean hasCycle(ListNode head) {
- if (head == null) return false;
- ListNode fast = head.next;
- ListNode slow = head;
- while (fast != slow && fast != null && fast.next != null){
- fast = fast.next.next;
- slow = slow.next;
- }
- return fast == slow;
- }
- }
169. 多数元素
难度简单1510收藏分享切换为英文接收动态反馈
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
解题思路:
- 最多的元素占了超过1/2,所以排序后,无论怎么排,中间的位置肯定就是最多的元素。
例如:1 1 2 2 3 3 3 3 3
例如:1 1 1 1 2 2 2
例如:1 2 2 2 2 2 3 3 3
- class Solution {
- public int majorityElement(int[] nums) {
- Arrays.sort(nums);
- return nums[nums.length/2];
- }
- }
121. 买卖股票的最佳时机
难度简单2465收藏分享切换为英文接收动态反馈
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
解题思路:
- 动态规划问题:每天都可以分为持有和不持有两个状态。因为只允许买卖一次,所以当前想要持有,就表明昨天是持有的今天保持,或者说之前都没有持有,今天开始买进。而不持有就表示之前一直不持有,或者之前一直是持有的,今天刚刚卖出。
- 暴力破解方法:遍历的同时记录最小的数,要获利最大,肯定是后面减之前出现最小数的时候,所以要更新最小数值,同时更新下最大值。
- class Solution {
- public int maxProfit(int[] prices) {
- int len = prices.length;
- int[][] dp = new int[len][2];
- dp[0][0] = 0; // 第一天不持有
- dp[0][1] = -prices[0]; // 第一天持有
- for (int i=1;i
- dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]+prices[i]); // 现在不持有,可能是一直不持有维持上一个,可能是原先持有,现在卖掉了
- dp[i][1] = Math.max(dp[i-1][1],-prices[i]); // 现在持有,可能是一直持有,可能是上一阶段不持有,当前刚刚持有(所以是之前一直是0,现在持有就要扣掉现在的钱)
- }
- return dp[len-1][0];
- }
- }
- class Solution {
- public int maxProfit(int[] prices) {
- int max = Integer.MIN_VALUE;
- int p = Integer.MAX_VALUE;
- for (int n : prices){
- p = Math.min(n,p); // 维护一个最小的值,后面的数就可以减最小的数得到一个最大值
- max = Math.max(max,n-p);
- }
- return max;
- }
- }
448. 找到所有数组中消失的数字
难度简单1040收藏分享切换为英文接收动态反馈
给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。
解题思路:
- 比较直白的一种方式,先排序,后遍历,然后x从1开始记录,如果遇到当前的数,则把后续相同的数都跳过,同时x+1,查看2是否出现,没有出现则加入到列表中,同时x+1,当前index不变,因为当前位置还没匹配上。最终遍历结束记录下x目前是多少,理应x达到n,如果没有达到,就说明还缺了后面这几位,则需要补全。
- 根据题目所知,在1-n,所以干脆构建一个数组,每个下角标对应的就是1-n,遍历原数组,找到对应下角标的位置,在该位置上+1,证明有过值,最终遍历下数组,只要还是0的,则表示该下角标的数没有出现过。
- class Solution {
- public List
findDisappearedNumbers(int[] nums) { - List
res = new ArrayList(); - Arrays.sort(nums);
- int x = 1;
- int index = 0;
- while (index < nums.length){
- if (x != nums[index]){
- res.add(x);
- x++;
- }else{
- while (index < nums.length && x == nums[index]){
- index++;
- }
- x++;
- }
- }
- for (int i=x;i<=nums.length;i++){
- res.add(i);
- }
- return res;
- }
- }
- class Solution {
- public List
findDisappearedNumbers(int[] nums) { - List
res = new ArrayList(); - int[] p = new int[nums.length+1];
- for (int n:nums){
- p[n]++;
- }
- for (int i=1;i
- if (p[i] == 0){
- res.add(i);
- }
- }
- return res;
- }
- }
160. 相交链表
难度简单1788收藏分享切换为英文接收动态反馈
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
图示两个链表在节点 c1 开始相交:

题目数据 保证 整个链式结构中不存在环。
解题思路:
- 如果有相交,则表示后半部分相同。
- A = a + c
- B = b + c
- 唯一就在于a,b不同,所以要想相同,a + b = b + a
- 扩展一下,a + c + b = b + c + a,即 A + b = B + a,相遇的点就是相交的点
- 也就是说走完A再走B,走完B再走A
- /**
- * 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 l1 = headA;
- ListNode l2 = headB;
- boolean isFirstA = true;
- boolean isFirstB = true;
- while (l1 != l2){
- if (l1 == null){
- l1 = headB;
- }else{
- l1 = l1.next;
- }
- if (l2 == null){
- l2 = headA;
- }else{
- l2 = l2.next;
- }
- }
- return l1;
- }
- }
206. 反转链表
难度简单2654收藏分享切换为英文接收动态反馈
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
解题思路:
- 经典题,反转的意思就是头变成尾,尾变成头
- 所以在这里得自己先整一个虚拟尾出来,第一个节点的next指向它,注意在指向前,应该把原本链表后面的元素都先保存起来,然后进行迭代,现在的尾部就是第一个元素了,下一个要接上去的链表就是之前事先保存起来的。
- /**
- * Definition for singly-linked list.
- * public class ListNode {
- * int val;
- * ListNode next;
- * ListNode() {}
- * ListNode(int val) { this.val = val; }
- * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
- * }
- */
- class Solution {
- public ListNode reverseList(ListNode head) {
- ListNode pre = null;
- while (head != null){
- ListNode next = head.next;
- head.next = pre;
- pre = head;
- head = next;
- }
- return pre;
- }
- }
226. 翻转二叉树
难度简单1367收藏分享切换为英文接收动态反馈
给你一棵二叉树的根节点 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 TreeNode invertTree(TreeNode root) {
- if (root == null){
- return null;
- }
- TreeNode node = root.left;
- root.left = root.right;
- root.right = node;
- invertTree(root.left);
- invertTree(root.right);
- return root;
- }
- }
543. 二叉树的直径
难度简单1108收藏分享切换为英文接收动态反馈
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
1
/ \
2 3
/ \
4 5
解题思路:
- 迭代计算左边和右边,遇到null返回,回退后比较左右,取最大值+1,同时更新下最大路径,因为最大值可能不穿过根节点,再一侧就可能出现,所以随时更新下最大值。
- 举例:最左侧最后一个节点4,由于左右节点皆为null,所以left和right都为0,所以该节点最后的值为1,他的父节点2,看它的右节点5,同理5也是1,这时候对于2这个节点来说,左右都是1,加上本身就是2,在往上1节点,先看右侧,右侧就一个3,3的节点深度为1,所有1的右侧深度为1,1的左侧深度为2,1节点最终为3。
- /**
- * 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 {
- private int res = 0;
- public int diameterOfBinaryTree(TreeNode root) {
- com(root);
- return res;
- }
-
- public int com(TreeNode root){
- if (root == null){
- return 0;
- }
- int left = com(root.left);
- int right = com(root.right);
- res = Math.max(res,left+right);
- return Math.max(left,right) + 1;
- }
- }
617. 合并二叉树
难度简单1032收藏分享切换为英文接收动态反馈
给你两棵二叉树: root1 和 root2 。
想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
返回合并后的二叉树。
注意: 合并过程必须从两个树的根节点开始。
解题思路:
- 当前节点则是两个节点val的和,左节点和左节点,右节点和右节点,如果一方为空,则直接以另外一方为准。
- /**
- * 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 TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
- if (root1 == null) return root2;
- if (root2 == null) return root1;
-
- TreeNode root = new TreeNode(root1.val + root2.val);
- root.left = mergeTrees(root1.left,root2.left);
- root.right = mergeTrees(root1.right,root2.right);
- return root;
- }
- }
234. 回文链表
难度简单1456收藏分享切换为英文接收动态反馈
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
解题思路:
- 快慢指针,当快指针走到末尾的时候,慢指针正好走到中间,这里得区分奇偶,当奇数的时候,慢指针就会停留在正中间,所以需要往后移动一位,这样以head为头部,slow为尾部,这两节是头尾相反链表,所以只要将slow进行翻转,然后挨个和head匹配是不是相等就可以了。
- /**
- * Definition for singly-linked list.
- * public class ListNode {
- * int val;
- * ListNode next;
- * ListNode() {}
- * ListNode(int val) { this.val = val; }
- * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
- * }
- */
- class Solution {
- public boolean isPalindrome(ListNode head) {
- ListNode fast = head;
- ListNode slow = head;
- while (fast != null && fast.next != null){
- fast = fast.next.next;
- slow = slow.next;
- }
- /* 奇数 */
- if (fast != null){
- slow = slow.next;
- }
-
- // 反转链表
- slow = revs(slow);
- fast = head;
- while (slow != null){
- if (slow.val != fast.val){
- return false;
- }
- slow = slow.next;
- fast = fast.next;
- }
- return true;
- }
-
- public ListNode revs(ListNode head){
- ListNode pre = null;
- while (head != null){
- ListNode next = head.next;
- head.next = pre;
- pre = head;
- head = next;
- }
- return pre;
- }
- }
461. 汉明距离
难度简单618收藏分享切换为英文接收动态反馈
两个整数之间的 汉明距离 指的是这两个数字对应二进制位不同的位置的数目。
给你两个整数 x 和 y,计算并返回它们之间的汉明距离。
解题思路:
- 当两个数异或在一起的时候,只有不同的时候才能保留下来,也就是题目所说的汉明距离,要想知道这其中包含多少个1,就需要逐步从末尾算出有多少个1, 这时候可以利用平移,将1慢慢的都平移到末尾,然后只需要判断末尾是否是1。
- class Solution {
- public int hammingDistance(int x, int y) {
- int res = 0;
- int s = x^y;
- while (s != 0){
- res += s&1;
- s = s>>1;
- }
- return res;
- }
- }
139. 单词拆分
难度中等1736收藏分享切换为英文接收动态反馈
给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。
注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
解题思路:
- DFS求解,从字符串第一位开始截取,查看是否存在在wordDict中,如果有就可以这样迭代下去,剩余的字符串逐个开始匹配,最终如果全部匹配完则表示完全匹配。这里要注意一个点,要记录访问过的节点,如后续再遇到这个位置,可以直接返回,因为之前也从该位置走过一遍,无需重复。
- 动态规划,即如果0-j 的部分已经匹配上了,就看 j-i的位置能不能匹配上。所以dp[j] 代表的就是之前匹配上了的状态,如果之前都没匹配上,后续根本不用看。
- class Solution {
- public boolean wordBreak(String s, List
wordDict) { - return dfs(s,wordDict,0,new HashSet
()); - }
-
- public boolean dfs(String s,List
wordDict,int begin,HashSet set) { - if (begin == s.length()){
- return true;
- }
-
- // 这是依次截取字符串判断,list中是否包含字符串
- for (int i=begin+1;i<=s.length();i++){
- // 遇到过经历过的开始,直接返回
- if (set.contains(i))continue;
- // 新截取的字符串
- if (wordDict.contains(s.substring(begin,i))){
- if(dfs(s,wordDict,i,set)){
- return true;
- }
- // 记录下i的位置,表示这之后都已经 经历过一遍了,不用重复经历
- set.add(i);
- }
- }
- return false;
- }
- }
- class Solution {
- public boolean wordBreak(String s, List
wordDict) { - int len = s.length();
- Set
wordDictSet = new HashSet(wordDict); - boolean[] dp = new boolean[len+1];
- dp[0] = true;
- for (int i=1;i<=len;i++){
- for (int j=0;j
- if (dp[j] && wordDict.contains(s.substring(j,i))){
- dp[i] = true;
- }
- }
- }
- return dp[len];
- }
- }
142. 环形链表 II
难度中等1690收藏分享切换为英文接收动态反馈
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
解题思路:
- 假设有环,环节点之前的路程称为 a ,环的路径为 b
- 快慢指针,
- f = 2 * s (快指针是慢指针的两倍)
- f = s + n * b (当两个指针相遇的时候,快指针肯定比慢指针多饶了 n 圈)
- s = n * b (联立上述关系式)
- f = 2 * n * b (联立上述关系式)
- 假设 k = a + n * b 也就是说,要停留在环的入口需要满足这个关系
- 而慢指针 s 正好满足 n * b,也就是说慢指针只需要再走个 a ,就可以到达环的入口,a是不确定的,所以如何去寻找这个a呢?通过上述定义可以发现 a 就是环节点之前的路程,所以我们可以起一个指针,它的位置在链表的头部,让它和慢指针同时走 a 步,这时候两指针就会再环入口相遇。
- /**
- * Definition for singly-linked list.
- * class ListNode {
- * int val;
- * ListNode next;
- * ListNode(int x) {
- * val = x;
- * next = null;
- * }
- * }
- */
- public class Solution {
- public ListNode detectCycle(ListNode head) {
- ListNode fast = head;
- ListNode slow = head;
- while (true){
- if (fast == null || fast.next == null || slow == null) return null;
- fast = fast.next.next;
- slow = slow.next;
- if (fast == slow){
- break;
- }
- }
- fast = head;
- while (fast != slow){
- fast = fast.next;
- slow = slow.next;
- }
- return fast;
- }
- }
287. 寻找重复数
难度中等1835收藏分享切换为英文接收动态反馈
给定一个包含 n + 1 个整数的数组 nums ,其数字都在 [1, n] 范围内(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,返回 这个重复的数 。
你设计的解决方案必须 不修改 数组 nums 且只用常量级 O(1) 的额外空间。
解题思路:
- 按照题目说范围在1-n中,所以可以构建一个n+1的数组,将出现的数都在数组中打上标记,因为重复的数只有一个,所以每次打标记之前可以查询,如果在这之前有过标记,则说明该数重复。
- class Solution {
- public int findDuplicate(int[] nums) {
- int[] p = new int[nums.length+1];
- for (int n:nums){
- if (p[n] != 0){
- return n;
- }else{
- p[n]++;
- }
- }
- return nums[0];
- }
- }
198. 打家劫舍
难度中等2228收藏分享切换为英文接收动态反馈
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
解题思路:
- 动态规划:因为不能偷相邻的,所以每天都有两种选择,偷还是不偷
- 如果选择今天偷,说明昨天就不能偷,也就是前天的金额加今天的金额
- 如果选择今天不偷,则和昨天的金额一样
- class Solution {
- public int rob(int[] nums) {
- int[] dp = new int[nums.length+1];
- dp[0] = 0;
- dp[1] = nums[0];
- for (int i=2;i<=nums.length;i++){
- dp[i] = Math.max(dp[i-1],dp[i-2]+nums[i-1]);
- }
- return dp[nums.length];
- }
- }
221. 最大正方形
难度中等1219收藏分享切换为英文接收动态反馈
在一个由 '0' 和 '1' 组成的二维矩阵内,找到只包含 '1' 的最大正方形,并返回其面积。
解题思路:
- 动态规划:正方形,至少四个方块都得有值,所以就以右下角得方块为准,它得上方,左方,左上方都得有值,而每个含有1得方块都可以自己作为一个正方形,所以本身就有值为1,所以每个值得确定都得看另外三个值,取最小得那一方,因为只要有一方是0的话,就无法组成大三角.
- 以四个方块都为1为例
- 1 1
- 1 x
- 这里的x首先自身是1,其次构成了一个大的正方形所以1+1
- 以四个方块有0有1为例
- 1 0
- 1 x
- 这里的x,只能自身构成正方形,所以只能0+1
- class Solution {
- public int maximalSquare(char[][] matrix) {
- int max = 0;
- if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
- return max;
- }
- int n = matrix.length;
- int m = matrix[0].length;
- int[][] dp = new int[n][m];
- for (int i=0;i
- for (int j=0;j
- if (matrix[i][j] == '1'){
- if (i == 0 || j == 0){
- dp[i][j] = 1;
- }else{
- dp[i][j] = Math.min(Math.min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+1;
- }
- max = Math.max(max, dp[i][j]);
- }
- }
- }
- return max*max;
- }
-
- }
494. 目标和
难度中等1324收藏分享切换为英文接收动态反馈
给你一个整数数组 nums 和一个整数 target 。
向数组中的每个整数前添加 '+' 或 '-' ,然后串联起所有整数,可以构造一个 表达式 :
- 例如,
nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1" 。
返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。
解题思路:
- 回溯:每种都有两个选择,所以可以直接暴力向下搜索
- class Solution {
- int res = 0;
- public int findTargetSumWays(int[] nums, int target) {
- int[] p = new int[]{+1,-1};
- dfs(nums,target,p,0);
- return res;
- }
-
- public void dfs(int[] nums,int target,int[] p,int depth){
- if (depth == nums.length){
- if (target == 0){
- res++;
- }
- return;
- }
-
- for (int i=0;i<2;i++){
- dfs(nums,target-nums[depth]*p[i],p,depth+1);
- }
- }
- }
416. 分割等和子集
难度中等1422收藏分享切换为英文接收动态反馈
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
解题思路:
- 背包问题:找到几个目标值,装入背包,正好能把背包装完,可以用动态规划来解决
- 两个参量,第一个为目标值,第二个为背包容量
- 随着目标值得不同,目标容量得不同,对应得结果也不同
- 当前值的状态有这几种情况:
- 不选当前值,所以和上一时刻的状态保持不变,当然容量也是一样
- 如果选择当前值,则要看直接有没有选择过另外一半,也就是说如果当前值是4,目标值是7,我要看之前的值能不能填满3,所以要看上一个状态下的另一部分。
- class Solution {
- public boolean canPartition(int[] nums) {
- int sum = 0;
- for (int n:nums){
- sum += n;
- }
- // 要想一分为2,首先和必须是偶数
- if (sum % 2 == 1){
- return false;
- }
-
- // 找到目标值target
- int target = sum/2;
-
- // 第一列是每个数,第二个数容量
- boolean[][] dp = new boolean[nums.length][target+1];
-
- // 定义下初始值
- if (nums[0] <= target){
- dp[0][nums[0]] = true;
- }
-
- for (int i=1;i
- for (int j=0;j<=target;j++){
- // 可以是不装当前值,所以和上一个得状态一样,容量没有变化
- dp[i][j] = dp[i-1][j];
-
- // 如果当前值就是我要找得值,就说明可以直接放入,那直接为true
- if (nums[i] == j) {
- dp[i][j] = true;
- continue;
- }
-
- // 如果比当前值要小,即如果选当前值,那要找到余下得另外一半,另一半的状态肯定是上一时刻的
- if (nums[i] < j) {
- dp[i][j] = dp[i][j] || dp[i-1][j-nums[i]];
- }
-
- // 如果比目标值要大,那就可以直接跳过不考虑
- }
- }
-
- return dp[nums.length-1][target];
- }
- }
337. 打家劫舍 III
难度中等1382收藏分享切换为英文接收动态反馈
小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为 root 。
除了 root 之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果 两个直接相连的房子在同一天晚上被打劫 ,房屋将自动报警。
给定二叉树的 root 。返回 在不触动警报的情况下 ,小偷能够盗取的最高金额 。
解题思路:
- 每个节点都有两种状态选和不选,因为是树节点,所以需要两个map分别存取,每个节点选和不选的状态
- 所以递归左右子树的时候,每个节点的状态都要写入map中
- 如果选择该节点,则下面的左右子树不能选,所以从不选取当前节点的左右子树中获取之前打家劫舍过的值。
- 如果不选择该节点,则下面的左右子树就可选可不选,在可选可不选中选择更大的
- /**
- * 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 {
- HashMap
l = new HashMap(); // 选取当前节点 - HashMap
r = new HashMap(); // 不选取当前节点 - public int rob(TreeNode root) {
- dfs(root);
- return Math.max(l.getOrDefault(root,0),r.getOrDefault(root,0));
- }
-
- public void dfs(TreeNode node){
- if (node == null)return;
-
- dfs(node.left);
- dfs(node.right);
-
- l.put(node,node.val + r.getOrDefault(node.left,0) + r.getOrDefault(node.right,0)); // 如果选择当前节点,就不能选取下面的左右
- r.put(node,Math.max(l.getOrDefault(node.left,0),r.getOrDefault(node.left,0)) + Math.max(l.getOrDefault(node.right,0),r.getOrDefault(node.right,0))); // 如果不选当前节点,它的左右节点可以选可以不选,所以可以选更大的那一个
- }
- }
22. 括号生成
难度中等2784收藏分享切换为英文接收动态反馈
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
解题思路:
- 括号对,即左括号有n个,右括号有n个
- 随机放左括号,放右括号,当都放完了,则是一种结果
- 这里注意,左括号的数理应是比右括号数要少,先有左才有右,如果右多,则表示右多余的右括号,这种情况就是错误的。
- 剩下的就是递归左括号和右括号,注意剩下的括号数量
- class Solution {
- public List
generateParenthesis(int n) { - List
res = new ArrayList(); - dfs(res,"",n,n);
- return res;
- }
-
- public void dfs(List
res,String s,int left,int right) { - if (left == 0 && right == 0){
- res.add(s);
- return;
- }
-
- if (left > right)return; // 必须要左括号小于右括号,如果右括号少,说明之前有多个右括号,则无法闭合
-
- if (left > 0){
- dfs(res,s+"(",left-1,right);
- }
-
- if (right > 0){
- dfs(res,s+")",left,right-1);
- }
- }
- }
31. 下一个排列
难度中等1843收藏分享切换为英文接收动态反馈
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
- 例如,
arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。
- 例如,
arr = [1,2,3] 的下一个排列是 [1,3,2] 。 - 类似地,
arr = [2,3,1] 的下一个排列是 [3,1,2] 。 - 而
arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。
给你一个整数数组 nums ,找出 nums 的下一个排列。
必须 原地 修改,只允许使用额外常数空间。
解题思路:
- 举例:12385764
- 下一个排列: 12386457
- 查找规律,从最后第一位开始找,第一个升序的位置 5 7,即5开始往后可以作调整
- 从下个排列中其实可以看出,6变成了第一位,也就是从后往前找的第一个比5大的,然后交换位置
- 交换后变成 6754
- 这时候离最终的答案,差一个倒序754变成457
- 这里还得分情况讨论:
- 如果说是升序的情况,但升序的时候已经是最后两个数了 ,比如 4 5,下一位就是 5 4,所以只需要交换这个两个数,然后直接返回就行
- 如果说是升序的情况,但后面的数没有一个比开头的数大的,比如 5 7 4 3,下一位是 7 3 4 5,因为后续没有大的数了,所以也是升序这两个数直接进行交换,交换过后变成 7 5 4 3,只需要将后面的降序排列,就是最终答案
- 如果说是升序的情况,且能找到后面比开头大的数,则是正常情况
- 如果说以上情况都没有发生,则表明当前数已经是最后一个排列了,它的下一位就是最开始即从小到大,而最后一位就是从大到小,只需倒叙即可
- class Solution {
- // 找到下一个排列
- // 举例
- // 12385764
- // 下一个排列 12386457
- // 查找规律,从最后第一位开始找,第一个升序的位置 5 7,即5开始往后可以作调整
- // 从下个排列中其实可以看出,6变成了第一位,也就是从后往前找的第一个比5大的,然后交换位置
- // 交换后变成6754
- // 这时候离最终的答案,差一个倒序754变成457
- public void nextPermutation(int[] nums) {
-
- for (int i=nums.length-1;i>0;i--){
-
- // 从后往前找到第一个升序的位置
- if (nums[i-1] < nums[i]){
- // 如果已经是组后一位了,就直接交换,然后返回
- if (i == nums.length -1){
- swap(nums,i-1,i);
- return;
- }
-
- // 从后往前找到第一个大于开端的数
- for (int j=nums.length-1;j>i;j--){
- if (nums[i-1] < nums[j]){
- // 交换两个值
- swap(nums,i-1,j);
- // 从降序变成升序
- reverse(nums,i,nums.length-1);
- return;
- }
- }
-
- // 如果从后往前并没有找到比它还大的数,则直接交换升序的两个值,然后把后面的变成升序
- swap(nums,i-1,i);
- reverse(nums,i,nums.length-1);
- return;
- }
- }
- // 如果什么都没有找到,说明已经在最后一位,只需要倒转就行
- reverse(nums,0,nums.length-1);
- }
-
- public void swap(int[] nums,int i,int j){
- int temp = nums[i];
- nums[i] = nums[j];
- nums[j] = temp;
- }
-
- public void reverse(int[] nums,int i,int j){
- while (i
- swap(nums,i,j);
- i++;
- j--;
- }
- }
- }
560. 和为 K 的子数组
难度中等1600收藏分享切换为英文接收动态反馈
给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的连续子数组的个数 。
解题思路:
- 看见连续子数组,就想到前缀和
- 遍历整个数组,当前缀和sum等于目标k的时候,算一个。同时去查看之前的前缀和,有没有差值是sum-k的,如果有就说明之前已经存在,则当前的数值得叠加上之前的,map中保留该次前缀和,次数+1,如果有重复就叠加上去。
- 举个例子:
- -1,1,0
- k = 0
- 遍历到当前 -1,sum = -1,-1不等于k,同时查看之前有无前缀和为-1得节点,发现没有,则将当前 -1 ,存入map,value 为 1.
- 遍历到当前 1,sum = 0, 0等于k,本次就为1个,res++,同时去判断之前有无前缀和 0 的节点,发现没有,则将当前 0 ,存入map,value 为 0.
- 遍历到当前 0,sum = 0,0等于k,本次就为1个,res++,同时去判断之前有无前缀和 0的节点,发现有,则res 的加上之前的结果,然后存入map,value + 1。这里作个说明,本身sum如果为k,肯定算一个,其次查找两个前缀和之差能否等于k,也就是说例子中,-1到1的前缀和为0,-1到0的前缀和也为0,所以说1往后到0之间的前缀和也为0,所以最终答案为3,即-1到1,-1到0,0本身
- class Solution {
- public int subarraySum(int[] nums, int k) {
- // 前缀和
- HashMap
map = new HashMap(); - int sum = 0;
- int res = 0;
- for (int n:nums){
- sum += n;
- // 前缀和加起来,如果
- if (sum == k){
- res++;
- }
- if (map.containsKey(sum-k)){
- res += map.get(sum-k);
- }
- map.put(sum,map.getOrDefault(sum,0)+1);
- }
- return res;
- }
- }
739. 每日温度
难度中等1239收藏分享切换为英文接收动态反馈
给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。
解题思路:
- 暴力解法:从每个温度后开始遍历,第一个大于该温度的位置,和当前位置的差值就是需要的天数。
- 栈:遍历数组,每当一个数组即将放入栈时,先和栈顶元素比较,如果比栈顶元素要小。直接如栈。如果比栈顶元素要大,则说明最近的一个温度要高的天来了,所以取出栈顶元素,它们下角标的差值就是需要等待的天数。比较的时候,只要是比栈顶元素大的,就挨个弹出。直到遇到比自己要大的为止,或者说是栈为空的时候。总结:当前的元素肯定是要入栈的,因为要得到之后最近一天比它高的温度的天数,就一定要和后面作比较,每次操作区别就在于之前的元素要不要出栈,只要是小的就出栈,值就是下角标的差值。
- class Solution {
- public int[] dailyTemperatures(int[] temperatures) {
- int len = temperatures.length;
- int[] res = new int[len];
- for (int i=0;i
- for (int j=i+1;j
- if (temperatures[i] < temperatures[j]){
- res[i] = j-i;
- break;
- }
- }
- }
- return res;
- }
- }
- class Solution {
- public int[] dailyTemperatures(int[] temperatures) {
- int len = temperatures.length;
- int[] res = new int[len];
- Stack<int[]> stack = new Stack();
- for (int i=0;i
- while (!stack.isEmpty() && temperatures[i] > stack.peek()[0]){
- int[] r = stack.pop();
- res[r[1]] = i - r[1];
- }
- stack.push(new int[]{temperatures[i],i});
- }
- return res;
- }
- }
39. 组合总和
难度中等2083收藏分享切换为英文接收动态反馈
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。
解题思路:
- 回溯:每个元素都可以用多次。但要避免重复,得加一个起始条件,只能往前不能往后
- 用一个Deque 存储当前元素,把当前元素加入,回退的时候得删除
- 举例: [2 ,3,7],target = 7
- 如果不加起始条件,可以是[2 2 3]、[2 3 2]、[3 2 2],要至少一个元素不同才能视为一个不同的组合,所以第一个 2 2 3,只往后可以。2 3 2 和 3 2 2 不行。当回退到元素3时,后面跟的元素只能从 3 开始往后。
- class Solution {
- public List
> combinationSum(int[] candidates, int target) {
- List
> res = new ArrayList();
- Deque
queue = new LinkedList(); - dfs(candidates,target,res,queue,0);
- return res;
- }
-
- public void dfs(int[] candidates, int target,List
> res,Deque queue,int begin)
{ - if (target < 0){
- return;
- }
- if (target == 0){
- res.add(new ArrayList(queue));
- return;
- }
- for (int i=begin;i
- queue.addLast(candidates[i]);
- dfs(candidates,target-candidates[i],res,queue,i);
- queue.removeLast();
- }
- }
- }
5. 最长回文子串
难度中等5514收藏分享切换为英文接收动态反馈
给你一个字符串 s,找到 s 中最长的回文子串。
解题思路:
- 动态规划:dp[i][j]表示 [ i , j ] 这个区间是否能构成回文子串
- 找到最长的回文子串,只需要找到开端和长度,就能得到最终答案
- 首先得知道,每个单个字符都是回文,所以 i = j 时,dp[i][j] = true
- 其他情况:
- 回文,检查两端,即 i 和 j ,如果 i ,j 不相等,直接可以判断不是
- 如果相等,则看它们缩小范围的时候是不是,这里注意一点,有特殊情况,举例说明 abb,当 i = 1,j = 2,即子字符串 bb,理应是回文,但是当我们判断两端i和j相等,所以要看缩小范围的时候,i = 2 ,j =1,因为遍历的时候 i 是小于等于 j 的,所以当 j = 1的时候,i 顶多到 1 ,所以 i = 2,j = 1,默认为 false,这就不符合实际了,所以要加一个判断,当两个差值小于3的时候,直接判断 true。
- class Solution {
- public String longestPalindrome(String s) {
- int len = s.length();
- if (len < 2){
- return s;
- }
- int maxLen = 1;
- int begin = 0;
- // [i,j] 这个区间的字符串是否是回文
- boolean[][] dp = new boolean[len][len];
- dp[0][0] = true;
- for (int j=1;j
- for (int i=0;i<=j;i++){
- if (i == j){
- dp[i][j] = true;
- }else{
- if (s.charAt(i) == s.charAt(j)){
- if (j - i == 1){
- dp[i][j] = true;
- }else{
- dp[i][j] = dp[i+1][j-1];
- }
- }else{
- dp[i][j] = false; // 两边都不相等,肯定不是
- }
- }
- if (dp[i][j]){
- if (j - i + 1 > maxLen){
- begin = i;
- maxLen = j - i + 1;
- }
- }
- }
- }
- return s.substring(begin, begin + maxLen);
- }
- }
3. 无重复字符的最长子串
难度中等7924收藏分享切换为英文接收动态反馈
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
解题思路:
- 滑动窗口,始终保持无重复子串,即如果遇到重复的,则对应着移动
- 举个例子:abca
- 当移动到第二个a的时候,为了保证窗口内没有重复,所以窗口左端得移动到该重复字符得后一位,即bca,这样就能无重复,如何判断之前有没有遇到过重复,就要用一个map保留信息,当发现直线遇到过,立马移动左端点
- 这里有特殊情况
- 举个例子:abba
- 当先移动到第二个b的时候,左端点自动移动到第一个b的后面,但是当后续又遇到a的时候,犹豫a也是重复的,这时候理应移动到第一个a的后面,但是滑动窗口是单向的,不能向后滑动,所以得和目前的左端点进行比较,只能往后不能往前。每次移动过后都要进行最大值的判断,同时保存下当前值的信息
- class Solution {
- public int lengthOfLongestSubstring(String s) {
- if (s.length() == 0){
- return 0;
- }
- HashMap
map = new HashMap(); - int left = 0;
- int maxLen = 1;
- for (int i=0;i
- // 如果有key存在,则把左指针移动到该key
- if (map.containsKey(s.charAt(i))){
- left = Math.max(map.get(s.charAt(i))+1,left);
- }
- maxLen = Math.max(maxLen,i-left+1);
- map.put(s.charAt(i),i);
- }
- return maxLen;
- }
- }
11. 盛最多水的容器
难度中等3686收藏分享切换为英文接收动态反馈
给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。
找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
说明:你不能倾斜容器。
解题思路:
- 测试最大容器,也就是要知道容器的两边应该固定在什么位置
- 两端肯定需要向里收缩,关键是一起收缩还是收缩哪一边
- 如果收缩长的那边,因为短的那一边没动,容器大小以短的那边为准,再加上它们直线距离缩短了,所以容量肯定是缩小了。如果收缩短的那边,因为长的那边没动,虽然它们直线距离缩短了,但是短的那边的板子是有可能变长的,这样最终的容器大小就有可能变大。综上理应每次都移动短的那一边。
- 所以选用两个指针分别在一左一右,长的那边固定不动,短的那边收紧一格,每次都判断当前容器大小,选出最大的,指针相遇即为结束
- class Solution {
- public int maxArea(int[] height) {
- int left = 0;
- int right = height.length-1;
- int max = 0;
- while (left < right){
- if (height[left] < height[right]){
- max = Math.max(max,(right-left)*height[left]);
- left++;
- }else{
- max = Math.max(max,(right-left)*height[right]);
- right--;
- }
- }
- return max;
- }
- }
200. 岛屿数量
难度中等1826收藏分享切换为英文接收动态反馈
给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
解题思路:
- 岛屿问题:
- 只要是一块岛屿,都是‘1’相连,也就是说判断岛屿就靠‘1’判断,那如何判断有几块呢?只需要该块走过的岛屿全部变成‘0’,这时候就不会影响到别的岛屿的判断
- 从岛屿的任何一点出发,只要是岛屿,总有‘1’相连,只需要把相连的‘1’都置为‘0’
- 举例:
- ["1","1","0","0","0"]
- ["1","1","0","0","0"]
- ["0","0","1","0","0"]
- ["0","0","0","1","1"]
- 遍历整个二维数组,从头部出发,每个格子都可以上下左右再出发,为了走过的不在继续走,把走过的‘1’,置为‘0’,同时如果遇到‘0’或者超出边界直接返回
- ["0","0","0","0","0"]
- ["0","0","0","0","0"]
- ["0","0","1","0","0"]
- ["0","0","0","1","1"]
- 犹豫第一个岛屿的‘1’,都置为了‘0’,所以第二次遍历到第[2,2]这个点的'1'
- ["0","0","0","0","0"]
- ["0","0","0","0","0"]
- ["0","0","0","0","0"]
- ["0","0","0","1","1"]
- 第三次遍历到[3,3]这个点
- ["0","0","0","0","0"]
- ["0","0","0","0","0"]
- ["0","0","0","0","0"]
- ["0","0","0","0","0"]
- 遍历结束,计算每次遍历到‘1’的次数就行
- class Solution {
- public int numIslands(char[][] grid) {
- int n = grid.length;
- int m = grid[0].length;
- int res = 0;
- for (int i=0;i
- for (int j=0;j
- if (grid[i][j] == '1'){
- dfs(grid,n,m,i,j);
- res++;
- }
- }
- }
- return res;
- }
-
- public void dfs(char[][] grid,int n,int m,int i,int j){
- if (i < 0 || j < 0 || i >= n || j >= m || grid[i][j] == '0'){
- return;
- }
-
- if (grid[i][j] == '1'){
- grid[i][j] = '0';
- }
-
- dfs(grid,n,m,i+1,j);
- dfs(grid,n,m,i-1,j);
- dfs(grid,n,m,i,j+1);
- dfs(grid,n,m,i,j-1);
- }
- }
207. 课程表
难度中等1358收藏分享切换为英文接收动态反馈
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
- 例如,先修课程对
[0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。
解题思路:
- 完成一个课程之前需要完成前置课程,所以得找到最开始得课程,即不用完成别的课程的课程,可能会有好几个这样的课程。
- 遍历课程数组,用一个数组去接受,每个有前置条件的该课程数量+1,代表着想要完成该课程需要先上完多少个前置课程,对应的你也得知道前置课程是什么,所以还要用个map来接受。
- 当数组生成之后,你再遍历一遍就知道哪些课程直接就可以完成,即那些值为0的课程,即不需要完成前置课程的课程,将它们都放入到队列中去
- 从队列中获取这些课程,获取代表着完成,当完成该课程时,去查看哪些课程依赖着该课程,所有依赖的课程,它们的数组中前置课程数量可以-1,如果减完后为0,表示该课程就可以完成了,将课程加入队列中,队列持续运转直到队列为空且没有新的课程加入
- 最终遍历一遍原来的数组,如果还有不是0的课程,说明存在互相依赖的关系,始终无法抵消,结果就为false。如果最终结果都为0,则没有环,可以全部完成为true
- class Solution {
- public boolean canFinish(int numCourses, int[][] prerequisites) {
- int[] p = new int[numCourses];
- HashMap
> map = new HashMap(); - // 每个课程要完成需要别的课程的次数
- for (int[] n : prerequisites){
- p[n[0]]++;
- List
list = map.getOrDefault(n[1],new ArrayList()); - list.add(n[0]);
- map.put(n[1],list);
- }
- Deque
queue = new LinkedList(); - for (int i=0;i
- // 如果不被需要,说明可以先一步完成,完成后可删除对应需要它作为先置条件的课程
- if (p[i] == 0){
- queue.add(i);
- }
- }
-
- while (!queue.isEmpty()){
- int i = queue.poll();
- List
list = map.get(i); - if (list != null){
- for (int l : list){
- p[l]--;
- if (p[l] == 0){
- queue.add(l);
- }
- }
- }
-
- }
-
- for (int i=0;i
- if (p[i] != 0){
- return false;
- }
- }
- return true;
- }
- }
17. 电话号码的字母组合
难度中等2034收藏分享切换为英文接收动态反馈
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
解题思路:
- 将整个数字键盘列举出来,如果是23,就是去char二维数组把对应的字符数组取到
- 像是全排列一样,只不过每次排列的选择都是新的一位,可以用StringBuffer拼接,当拼接到最后一位的时候可以把当前结果加入到结果集中,然后回溯。
- 举例:23
- 先获取2,找到char二维数组中对应{'a','b','c'}
- 从头开始遍历 {'a','b','c'},获取 'a',StringBuffer添加'a',继续下一位是3,找到对应的{'d','e','f'},从头遍历,获取'd',加入StringBuffer,继续,发现已经到末尾了,则把‘ad’放入结果集中,回溯,之前StringBuffer中添加了'd',现在删除,并遍历到第二位'e',加入StringBuffer中,继续发现又到了末尾,‘ae’加入,然后如此回溯 'af' 加入,最后一位遍历结束则往前回溯,之前遍历到'a',现在是'b',然后往下继续是'd',然后如此继续
- class Solution {
- private char[][] c = {{' '},{' '},{'a','b','c'},{'d','e','f'},{'g','h','i'},{'j','k','l'},{'m','n','o'},{'p','q','r','s'},{'t','u','v'},{'w','x','y','z'}};
- public List
letterCombinations(String digits) { - List
res = new ArrayList(); - if (digits.length() == 0){
- return res;
- }
- dfs(digits,0,res,new StringBuffer());
- return res;
- }
-
- public void dfs(String digits,int index,List
res,StringBuffer s) { - if (index == digits.length()){
- res.add(s.toString());
- return;
- }
- char[] x = c[digits.charAt(index) - '0'];
- for (int i=0;i
- s.append(x[i]+"");
- dfs(digits,index+1,res,s);
- s.deleteCharAt(s.length()-1);
- }
- }
- }
15. 三数之和
难度中等5056收藏分享切换为英文接收动态反馈
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
解题思路:
- 三数之和可以说是二数之和的加强版,因为最终的结果要是不能重复的,所以说先要排个序,有重复的方便跳过,两数之和就是找到两个和为目标值的数,三数就是另一个数的相反数充当目标值,充当目标数的数可以是任意一个,所以可以遍历一遍原数组,每个数都可以充当一遍,犹豫是不能重复,所以当轮到后面的数的时候就不能往前去查找
- 查找两个数的时候,因为是排过序的,所以可以用双指针,左右两边往里面收缩,如果左右两边相加sum等于目标值,则可以作为一种结果,同时左右边界都可以往里面收缩。如果sum比目标值要大,则表示过大,得缩小右边界,因为可能存在重复,所以要用个while把所有重复得都跳过。如果当前过小,则左边界要右移,加大sum值
- twoSum的结果集中只有两个,所以每个结果集要把当前数也加入进去,最后加入到三数的结果集中,同样的,之后遍历的时候和本次一样的数
- class Solution {
- public List
> threeSum(int[] nums) {
- // 排序避免重复数
- Arrays.sort(nums);
- List
> res = new ArrayList();
- for (int i=0;i
- List
> list = twoSum(nums,i+1,-nums[i]);
- if (list.size() > 0){
- // 每个list都补上当前数,然后再加入到大的结果集中
- for (List
list1 : list){ - list1.add(nums[i]);
- res.add(list1);
- }
- }
- // 跳过重复的数
- while (i < nums.length-1 && nums[i+1] == nums[i]){
- i++;
- }
- }
- return res;
- }
-
- public List
> twoSum(int[] nums,int left,int target){
- int right = nums.length-1;
- List
> res = new ArrayList();
- while (left < right){
- int leftNum = nums[left];
- int rightNum = nums[right];
- int sum = leftNum + rightNum;
- if (sum == target){
- // 找到了两个值
- List
list = new ArrayList(); - list.add(leftNum);
- list.add(rightNum);
- res.add(list);
-
- // 左边如果有重复的则跳过
- while (left < right && nums[left] == leftNum){
- left++;
- }
-
- // 右边如果有重复的则跳过
- while (left < right && nums[right] == rightNum){
- right--;
- }
- }else if (sum > target){
- // 表示大了,右边得缩小范围
- while (left < right && nums[right] == rightNum){
- right--;
- }
- }else if (sum < target){
- // 表示小了,左边得扩大范围
- while (left < right && nums[left] == leftNum){
- left++;
- }
- }
- }
- return res;
- }
- }
19. 删除链表的倒数第 N 个结点
难度中等2151收藏分享切换为英文接收动态反馈
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
解题思路:
- 这里有个技巧,想要删除倒数第N个节点,就可以用双指针,让双指针间隔N个节点,这样当右指针到末尾的时候,左指针正好在删除节点的左侧
- 这里有个问题,如果我要删除的倒数第N个正好是第一个,这样我的左指针一直指向的都是第一个节点。而通常解法是说同时移动,左指针的下一位才是要删除的元素,这样就删除不了了。所以解决办法就是新定义一个头部,从头部开始往后,这样左指针一开始的位置就是在定义的头部,而如果要删除第一个元素,即左指针的后一位就可以删除了,最终结果返回头部后面的链表
- 举例:[1],删除1
- 定义了一个头部 0, 0 -> 1
- 左指针在0,右指针移动到1
- 由于右指针已经在末尾,所以并不存在同时移动
- 因为始终认为左指针的下一位是要删除的节点,所以自然而然删除了第一位
- /**
- * Definition for singly-linked list.
- * public class ListNode {
- * int val;
- * ListNode next;
- * ListNode() {}
- * ListNode(int val) { this.val = val; }
- * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
- * }
- */
- class Solution {
- public ListNode removeNthFromEnd(ListNode head, int n) {
- // 删除倒数第N个节点,就可以利用两指针
- // 让右指针先动倒数N个节点
- // 然后左指针再和右指针同时动
- // 当右指针移动到最后的时候,左指针正好移动到要删除节点的前端
- ListNode pre = new ListNode(0);
- pre.next = head;
- ListNode left = pre;
- ListNode right = pre;
- while (n-- > 0){
- right = right.next;
- }
- while (right.next != null){
- left = left.next;
- right = right.next;
- }
- left.next = left.next.next;
- return pre.next;
- }
- }
34. 在排序数组中查找元素的第一个和最后一个位置
难度中等1824收藏分享切换为英文接收动态反馈
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。
解题思路:
- 既然是在有序数组里面找到目标值的左右两个端点
- 可以用两次二分查找,分别找到左端点和右端点
- 第一次查找,找到第一个>=target值的,也就是左边界。所以当二分查找的时候,所有比target要小的数肯定不是,那left 每次更新就从mid+1 开始,而右边界始终保持大于等于的状态,最终当左右相遇的时候,right也就是左边界
- 第二次查找,找到最后一个<=target值的,也就是右边界。所以当二分查找的时候判断当前num[mid]的值是否比target要大,如果要大,那就肯定不是右边界,就收缩右边界 right = mid - 1,相反 left = mid,保持左边界始终是收紧状态,这样当left 和 right 相遇的时候,right可以保证是右边界
- 二分while的结束条件是left > right,所以left可能会大于right,有可能会产生越界,因此取right肯定没错
- class Solution {
- public int[] searchRange(int[] nums, int target) {
- if (nums.length == 0){
- return new int[]{-1,-1};
- }
- if (nums.length == 1){
- if (nums[0] == target){
- return new int[]{0,0};
- }
- }
- // 两次循环,一次找到左端点,一次找到右端点
-
- int left = 0;
- int right = nums.length-1;
-
- while (left < right){
- int mid = left + (right-left)/2;
- if (nums[mid] < target){
- left = mid + 1;
- }else{
- right = mid;
- }
- }
-
-
- if (nums[right] != target)return new int[]{-1,-1};
- int L = right;
- left = 0;
- right = nums.length-1;
-
- while (left < right){
- int mid = left + (right-left+1)/2;
- if (nums[mid] <= target){
- left = mid;
- }else{
- right = mid - 1;
- }
- }
-
-
- return new int[]{L,right};
- }
- }
647. 回文子串
难度中等940收藏分享切换为英文接收动态反馈
给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
解题思路:
- 两层遍历,探索不同位置时,从0到该位置不同情况字符串能否构成回文
- 回文,首先每个字符都是一个回文,所以当 i == j 的时候算一次
- 其次得两端 i,j 位置的字符相等,再看里面的,而里面的情况取决于去掉这两端后子字符串是否能构成回文,这个在之前遍历的时候已经得知了。这里注意一个点,当 i 和 j靠的很近的时候,也就是 baa ,这种情况当 i 在 2,j 在 1的时候,dp[i-1][j+1]=dp[1][2],j是不能超过 i 的,所以默认为 false,但是这种情况 aa 是回文,所以要单独拿出来判定
- class Solution {
- public int countSubstrings(String s) {
- // 回文子串的数目
- int s_len = s.length();
- int res = 0;
- boolean[][] dp = new boolean[s_len][s_len];
- dp[0][0] = true;
- for (int i=0;i
- for (int j=0;j<=i;j++){
- if (i == j){
- dp[i][j] = true;
- }else{
- if (s.charAt(i) == s.charAt(j)){
- if (i - j == 1){
- dp[i][j] = true;
- }else {
- dp[i][j] = dp[i-1][j+1];
- }
- }
- }
- if (dp[i][j])res++;
- }
- }
- return res;
- }
- }
48. 旋转图像
难度中等1376收藏分享切换为英文接收动态反馈
给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
解题思路:
-
上下对称:matrix[i][j] -> matrix[n-i-1][j],(列不变)
左右对称:matrix[i][j] -> matrix[i][n-j-1],(行不变)
主对角线对称:matrix[i][j] -> matrix[j][i],(行列互换)
副对角线对称:matrix[i][j] -> matrix[n-j-1][n-i-1] (行列均变,且互换)
-
顺时针旋转 90 度:上下对称 + 主对角线对称 或者 主对角线对称 + 左右对称
-
顺时针旋转 180 度:上下对称 + 左右对称 (两次 90 度)
-
顺时针旋转 270 度:左右对称 + 主对角线对称 (180 度 + 90 度)
- class Solution {
- public void rotate(int[][] matrix) {
- int n = matrix.length;
- // 水平翻转
- // matrix[i][j] = matrix[n-i-1][j]
- for (int i=0;i
2;i++){ - for (int j=0;j
- int temp = matrix[n-i-1][j];
- matrix[n-i-1][j] = matrix[i][j];
- matrix[i][j] = temp;
- }
- }
-
-
- // 主对角线翻转
- // matrix[i][j] = matrix[j][i]
- for (int i=0;i
- for (int j=0;j
- int temp = matrix[j][i];
- matrix[j][i] = matrix[i][j];
- matrix[i][j] = temp;
- }
- }
-
- }
- }
322. 零钱兑换
难度中等2071收藏分享切换为英文接收动态反馈
给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。
计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。
你可以认为每种硬币的数量是无限的。
解题思路:
- 动态规划,随着金额的增加,每次都可以从硬币组里面选硬币来填充它,但前提是当前钱币的金额得小于总金额,这样才有意义。如果当前选择了该硬币,就可以查看总金额减去该硬币,剩余金额组成需要最少多少个硬币,而这个金额在之前就已经得出,每次计算当前金额所需最少硬币,肯定是从硬币组里面选择
- 举个例子:1 2 5 硬币组 金额 6
- 当金额为1的时候,只有硬币组中1满足,所以dp[1] = 1
- 当金额为2的时候,硬币组里选择硬币1的时候,剩余金额为1,查看金额1的时候需要最少几个硬币,发现是1,所以总共加自己为2。当选择硬币2的时候,剩余金额为0,加上自己为1,两者取小的就是1,dp[2] = 1
- 当金额为3的时候,选择硬币1,剩余金额为2,能组成金额为2的最少硬币个数dp[2] = 1,所以加上自己为2。选择硬币2,剩余金额为1,组成金额为1的最少硬币个数dp[1] =1,所以加上自己也为2,最终dp[3] = 2
- 当金额为4的时候,选择硬币1 + dp[3] = 3,选择硬币 2 + dp[2] = 2,最终dp[4] = 2
- 当金额为5的时候,选择硬币1 + dp[4] = 3, 选择硬币2 + dp[3] = 3,选择硬币5 + dp[0] = 1,最终dp[5] = 1
- 当金额为6的时候,选择硬币1 + dp[5] = 2,选择硬币2 + dp[4] = 4,选择硬币5 + dp[1] = 2,所以最终dp[6] = 2
- 这里对于没有硬币的情况判定,假设硬币组中存在1,要组成amount,需要amount个硬币,所以这里设置最大值amount+1,如果最后还是这个值,表示没有硬币能够组成
- class Solution {
- public int coinChange(int[] coins, int amount) {
- // 背包问题,每个都可以重复装直到装满
- int[] dp = new int[amount+1];
- Arrays.fill(dp,amount+1);
- dp[0] = 0;
- for (int i=1;i<=amount;i++){
- for (int j=0;j
- // 当前的钱币要小于等于总金额才有意义
- if (coins[j] <= i) {
- dp[i] = Math.min(dp[i], dp[i-coins[j]] + 1);
- }
- }
- }
- return dp[amount] > amount ? -1 : dp[amount];
- }
- }
236. 二叉树的最近公共祖先
难度中等1872收藏分享切换为英文接收动态反馈
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
解题思路:
- 找到公共父节点,分两种情况
- 如果正好在左边找到一个,在右边找到一个,那它们的父节点就是上一级
- 如果只在一边找到一个,另一边没找到,说明只存在一边,那就返回有值得一边
- 如果都没找到,就直接返回null就可以
- 没找到得那一侧会向上递交null,找到的也就是有值得那一侧会像上递交值。没值和没值返回没值,有值和没值返回有值,当两个有值得分支相遇,说明它们到达了共同的父节点,也就是我们要找的
- /**
- * Definition for a binary tree node.
- * public class TreeNode {
- * int val;
- * TreeNode left;
- * TreeNode right;
- * TreeNode(int x) { val = x; }
- * }
- */
- class Solution {
- public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
- if (root == null || root == p || root == q){
- return root;
- }
-
- TreeNode left = lowestCommonAncestor(root.left,p,q);
- TreeNode right = lowestCommonAncestor(root.right,p,q);
-
- // 都没找到
- if (left == null && right == null){
- return null;
- }
- // 其余情况一个为null,一个不为null,说明找到一个
- if (left == null){
- return right;
- }
-
- if (right == null){
- return left;
- }
-
- // 还有情况就是,在左右各找到一个,说明它们的父节点就是root
- return root;
- }
-
- }
155. 最小栈
难度中等1369收藏分享切换为英文接收动态反馈
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack 类:
MinStack() 初始化堆栈对象。void push(int val) 将元素val推入堆栈。void pop() 删除堆栈顶部的元素。int top() 获取堆栈顶部的元素。int getMin() 获取堆栈中的最小元素。
解题思路:
- 设计两个栈
- 一个用来普通放入、删除、获取顶部元素
- 一个用来记录当前最小元素的栈
- 最小元素栈每次放入的时候和栈的顶部元素比较大小,哪个小就放入哪个
- 弹出的时候,两个栈同时弹出
- 举例:依次放入 -1 ,0 ,1
- 最小元素栈应该为 -1,-1,-1
- 举例:依次放入 -1,-2,1,-3
- 最小元素栈应该为 -1,-2,-2,-3
- class MinStack {
-
- private Stack
stack; - private Stack
stack1; -
- public MinStack() {
- stack = new Stack();
- stack1 = new Stack();
- }
-
- public void push(int val) {
- if (stack.isEmpty()){
- stack.push(val);
- stack1.push(val);
- }else{
- stack.push(val);
- if (stack1.peek() > val){
- stack1.push(val);
- }else{
- stack1.push(stack1.peek());
- }
- }
- }
-
- public void pop() {
- if (!stack.isEmpty()){
- stack.pop();
- stack1.pop();
- }
- }
-
- public int top() {
- return stack.peek();
- }
-
- public int getMin() {
- return stack1.peek();
- }
- }
-
- /**
- * Your MinStack object will be instantiated and called as such:
- * MinStack obj = new MinStack();
- * obj.push(val);
- * obj.pop();
- * int param_3 = obj.top();
- * int param_4 = obj.getMin();
- */
238. 除自身以外数组的乘积
难度中等1216收藏分享切换为英文接收动态反馈
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。
题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
请不要使用除法,且在 O(n) 时间复杂度内完成此题。
解题思路:
- 除了当前以外的元素乘起来,即元素的左边*元素的右边
- 可以用两次遍历
- 第一次遍历,计算从左边挨个乘起来的值,每个元素的值是该元素左边的累乘
- 第二遍遍历,计算从右边挨个乘起来的值,每个元素的值是该元素右边的累乘
- 第一遍是赋值,第二遍乘积,第二遍是两者结合(右边+左边)
- 举个例子:1 2 3 4
- 第一遍遍历,正向遍历,当前是之前元素累乘,res 为 1,1,1*2,1*2*3
- 第二遍遍历,反向遍历,当前是之后元素累乘,res 为 1*4*3*2,1*4*3,1*2*4,1*2*3
- class Solution {
- public int[] productExceptSelf(int[] nums) {
- // 先把该元素左边乘起来,再把右边乘起来
- int[] res = new int[nums.length];
- res[0] = 1;
- int p = 1;
- for (int i=1;i
- p *= nums[i-1];
- res[i] = p;
- }
-
- p = 1;
- for (int j=nums.length-1;j>0;j--){
- p *= nums[j];
- res[j-1] *= p;
- }
- return res;
- }
- }
309. 最佳买卖股票时机含冷冻期
难度中等1280收藏分享切换为英文接收动态反馈
给定一个整数数组prices,其中第 prices[i] 表示第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
- 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
解题思路:
- 这是买卖股票的变形,多个一个冷冻期且可以多次买卖
- 每天都有两个选择,持有不持有
- 不持有:可以是保持昨天的不持有状态;也可以是昨天持有状态下,今天卖掉了
- 持有:可以是保持昨天的持有状态;因为有冷东期,所以是保持前天的不持有状态,然后今天持有了
- 都是取最大收益
- class Solution {
- public int maxProfit(int[] prices) {
- if (prices.length == 1)return 0;
- int[][] dp = new int[prices.length][2];
- dp[0][0] = 0;
- dp[0][1] = -prices[0];
- dp[1][0] = Math.max(dp[0][0],dp[0][1] + prices[1]);
- dp[1][1] = Math.max(dp[0][1],-prices[1]);
- for(int i=2;i
- dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1] + prices[i]);
- dp[i][1] = Math.max(dp[i-1][1],dp[i-2][0] - prices[i]);
- }
- return dp[prices.length-1][0];
- }
- }
621. 任务调度器
难度中等969收藏分享切换为英文接收动态反馈
给你一个用字符数组 tasks 表示的 CPU 需要执行的任务列表。其中每个字母表示一种不同种类的任务。任务可以以任意顺序执行,并且每个任务都可以在 1 个单位时间内执行完。在任何一个单位时间,CPU 可以完成一个任务,或者处于待命状态。
然而,两个 相同种类 的任务之间必须有长度为整数 n 的冷却时间,因此至少有连续 n 个单位时间内 CPU 在执行不同的任务,或者在待命状态。
你需要计算完成所有任务所需要的 最短时间 。
解题思路:
- 每个相同任务之间,需要间隔n个时间休息
- 所以出现次数最多的任务,肯定需要(max-1)*(n+1)+1个时间
- 举个例子:A出现次数最多,出现了3次,n为2,所以两个A直接隔了2
- A x x A x x A ,其中x代表其他,也就是不看其他任务,单完成A需要, (3-1)*(2+1)+1,最后一个任务可直接完成
- 其他任务出现次数小于A,所以其他任务可以随意的插入到两个A的间隔中去,比如A B C A B x A,对总的等待时间没有影响
- 如果说出现次数等于A,比如 A B C A B x A B,最终 (3-1)*(2-1)+1+1,所以还要计算等于出现最大次数的种类的个数
- 如果说种类很多超过了间隔,A B C D E A B C D E,那本身就不需要额外的等待时间,挨个执行任务就行,数组大小就是需要用的时间
- 所以想要完成这些任务,至少需要大于等于数组大小,如果发现次数比数组大小要小,就说明种类多的情况没考虑进去,最终结果就应该是数组大小,反之如果大于,就表明是正常情况,取计算结果就行
- class Solution {
- public int leastInterval(char[] tasks, int n) {
- int[] dp = new int[26];
- for (int i=0;i
- dp[tasks[i]-'A']++;
- }
-
- // 获取出现次数最多的任务
- int max = 0;
- for (int num : dp){
- max = Math.max(max,num);
- }
-
- // 前max-1个任务,每个任务都需要n+1的时间,最后一个任务无需等待直接完成
- int time = (max-1)*(n+1);
-
- // 计算有多少个和最大值并列的任务,最后一个任务需要多久能完成
- for (int num : dp){
- if (num == max){
- time++;
- }
- }
-
- return Math.max(tasks.length,time);
- }
- }
102. 二叉树的层序遍历
难度中等1408收藏分享切换为英文接收动态反馈
给你二叉树的根节点 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
> levelOrder(TreeNode root) {
- List
> res = new ArrayList();
- if (root == null)return res;
- Queue
queue = new LinkedList(); - queue.add(root);
- while (!queue.isEmpty()){
- int size = queue.size();
- List
list = new ArrayList(); - for (int i=0;i
- TreeNode node = queue.poll();
- list.add(node.val);
- if (node.left != null){
- queue.add(node.left);
- }
- if (node.right != null){
- queue.add(node.right);
- }
- }
- res.add(list);
- }
- return res;
- }
- }
114. 二叉树展开为链表
难度中等1257收藏分享切换为英文接收动态反馈
给你二叉树的根结点 root ,请你将它展开为一个单链表:
- 展开后的单链表应该同样使用
TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。 - 展开后的单链表应该与二叉树 先序遍历 顺序相同。
解题思路:
- 按照前序遍历,把每个节点放入list中去
- 遍历,相邻两个节点互为父子节点,前一个节点左边为空,右边则是下一个节点
- /**
- * 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 void flatten(TreeNode root) {
- List
list = new ArrayList(); - dfs(root,list);
- for (int i=1;i
- TreeNode pre = list.get(i-1);
- TreeNode next = list.get(i);
- pre.right = next;
- pre.left = null;
- }
- }
-
- public void dfs(TreeNode root,List
list) { - if (root == null){
- return;
- }
- list.add(root);
- dfs(root.left,list);
- dfs(root.right,list);
- }
- }
394. 字符串解码
难度中等1243收藏分享切换为英文接收动态反馈
给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。
解题思路:
- 这个和判断有效括号一样,遇到闭合 ' ] ',就去栈中找另外一半 ' [ ',它们中间的字符串就是需要重复的,' [ ' 开括号之前的就是需要重复的数字,由于数字大小不一定,所以要循环判断是不是数字,将数字都弹出,然后合并成最终循环的次数,再根据次数循环着把字符串挨个推入栈中,最后当遍历完整个字符串后,栈中就是我们最后的结果,只不过顺序是反的
- class Solution {
- public String decodeString(String s) {
- Stack
stack = new Stack(); - char[] c = s.toCharArray();
- for (int i=0;i
- // 判断是不是']'闭合符号
- if (c[i] == ']'){
- // 如果是,则先取出'['开符号之间的字母组合
- String new_s = "";
- while (stack.peek() != '['){
- new_s = stack.pop().toString() + new_s;
- }
- stack.pop(); // 把'['取出
-
- // 取出数字
- String nums = "";
- while (true){
- if (!stack.isEmpty() && stack.peek()-'0'>=0 && stack.peek()-'0' <=9){
- nums = stack.pop() + nums;
- }else{
- break;
- }
- }
-
- // 重复字母nums次,也是一个字符一个字符往栈里面加
- char[] x = new_s.toCharArray();
- for (int j=0;j
- for (int k=0;k
- stack.push(x[k]);
- }
- }
- }else{
- // 不是闭合都可以往栈里面加
- stack.push(c[i]);
- }
- }
-
- String res = "";
- // 合并栈中元素
- while (!stack.isEmpty()){
- res = stack.pop().toString() + res;
- }
- return res;
- }
- }
538. 把二叉搜索树转换为累加树
难度中等764收藏分享切换为英文接收动态反馈
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
提醒一下,二叉搜索树满足下列约束条件:
- 节点的左子树仅包含键 小于 节点键的节点。
- 节点的右子树仅包含键 大于 节点键的节点。
- 左右子树也必须是二叉搜索树。
注意:本题和 1038: 力扣 相同
解题思路:
- 把中序遍历反过来遍历,先遍历右边再遍历左边,就满足了累加的顺序
- 只需要再维护一个sum,把遍历过的值都累加起来,每次赋给遍历到的节点即可