394 字符串解码、406 根据身高重建队列、416 分割等和子集(等于)0-1背包、437 路径总和3 递归、448 找到所有数组中消失的数字、461 汉明距离 位运算异或、543 二叉树的直径

  1. 394 字符串解码 栈
  2. 406 根据身高重建队列 自定义排序 按k插入
  3. 416 分割等和子集 0-1背包
  4. 437 路径总和3 递归
  5. 438 找到字符串中所有字母异位词 滑动窗口hashmap
  6. 448 找到所有数组中消失的数字 下标当作数
  7. 461 汉明距离 位运算 异或
  8. 494 目标和 (填+/-)
  9. 538 把二叉搜索树转换为累加树(中序遍历反着 )
  10. 543 二叉树的直径
  11. 560 和为k的子数组
  12. 572 另一个树的子树
  13. 581 最短无须连续子数组
  14. 617合并二叉树
  15. 647回文子串
  16. 771宝石与石头

394 字符串解码

给定一个经过编码的字符串,返回它解码后的字符串。
编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。

示例 1:
输入:s = “3[a]2[bc]”
输出:“aaabcbc”
示例 2:
输入:s = “3[a2[c]]”
输出:“accaccacc”
示例 3:
输入:s = “2[abc]3[cd]ef”
输出:“abcabccdcdcdef”
示例 4:
输入:s = “abc3[cd]xyz”
输出:“abccdcdcdxyz”

solution:
本题难点在于括号内嵌套括号,需要从内向外生成与拼接字符串,这与栈的先入后出特性对应。

算法流程:

构建辅助栈 stack, 遍历字符串 s 中每个字符 c;
当 c 为数字时,将数字字符转化为数字 multi,用于后续倍数计算;
当 c 为字母时,在 res 尾部添加 c;
当 c 为 [ 时,将当前 multi 和 res 入栈,并分别置空置 00:
记录此 [ 前的临时结果 res 至栈,用于发现对应 ] 后的拼接操作;
记录此 [ 前的倍数 multi 至栈,用于发现对应 ] 后,获取 multi × […] 字符串。
进入到新 [ 后,res 和 multi 重新记录。
当 c 为 ] 时,stack 出栈,拼接字符串 res = last_res + cur_multi * res,其中:
last_res是上个 [ 到当前 [ 的字符串,例如 “3[a2[c]]” 中的 a;
cur_multi是当前 [ 到 ] 内字符串的重复倍数,例如 “3[a2[c]]” 中的 2。
返回字符串 res。
这个动图很好

class Solution {
    public String decodeString(String s) {
        StringBuilder res = new StringBuilder();
        int multi = 0;
        LinkedList<Integer> stack_multi = new LinkedList<>();
        LinkedList<String> stack_res = new LinkedList<>();
        for(Character c : s.toCharArray()) {
            if(c == '[') {
                stack_multi.addLast(multi);
                stack_res.addLast(res.toString());
                multi = 0;
                res = new StringBuilder();
            }
            else if(c == ']') {
                StringBuilder tmp = new StringBuilder();
                int cur_multi = stack_multi.removeLast();
                for(int i = 0; i < cur_multi; i++) tmp.append(res);
                res = new StringBuilder(stack_res.removeLast() + tmp);
            }
            else if(c >= '0' && c <= '9') multi = multi * 10 + Integer.parseInt(c + "");
            else res.append(c);
        }
        return res.toString();
    }
}


406 根据身高重建队列

假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 编写一个算法来重建这个队列。

注意:
总人数少于1100人。

示例

输入:
[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]

输出:
[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]

solution 好妙的方法!!!!

class Solution {
    public int[][] reconstructQueue(int[][] people) {
        if (0 == people.length || 0 == people[0].length)
            return new int[0][0];
         //按照身高降序 K升序排序 
        Arrays.sort(people, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[0] == o2[0] ? o1[1] - o2[1] : o2[0] - o1[0];
            }
        });
        List<int[]> list = new ArrayList<>();
        //K值定义为 排在h前面且身高大于或等于h的人数 
        //因为从身高降序开始插入,此时所有人身高都大于等于h
        //因此K值即为需要插入的位置
        for (int[] i : people) {
            list.add(i[1], i);
        }
        return list.toArray(new int[list.size()][]);
    }
}

416 分割等和子集

给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

注意:

每个数组中的元素不会超过 100
数组的大小不会超过 200
示例 1:

输入: [1, 5, 11, 5]

输出: true

  • 本题与 0-1 背包问题有一个很大的不同,即
    0-1 背包问题选取的物品的容积总量不能超过规定的总量;本题选取的数字之和需要恰恰好等于规定的和的一半。这一点区别,决定了在初始化的时候,所有的值应该初始化为 false。 (《背包九讲》的作者在介绍 0-1 背包问题的时候,有强调过这点区别,我在这里也只是再重复一下。)

作为“0-1 背包问题”,它的特点是:“每个数只能用一次”。思路是:物品一个一个选,容量也逐个放大考虑。我们实际生活中也是这样做的,尝试一个一个把候选物品放入“背包”。

具体做法是:画一个 len 行,target + 1 列的表格。这里 len 是物品的个数,target 是背包的容量。len 行表示一个一个物品考虑,target + 1多出来的那 1 列,表示背包容量从 0 开始,很多时候,我们需要考虑这个容量为 0 的数值。

状态定义:dp[i][j]表示从数组的 [0, i] 这个子区间内挑选一些正整数,每个数只能用一次,使得这些数的和恰好等于 j。
状态转移方程:很多时候,状态转移方程思考的角度是“分类讨论”,对于“0-1 背包问题”而言就是“当前考虑到的数字选与不选”。
1、不选择 nums[i],如果在 [0, i - 1] 这个子区间内已经有一部分元素,使得它们的和为 j ,那么 dp[i][j] = true;

2、选择 nums[i],如果在 [0, i - 1] 这个子区间内就得找到一部分元素,使得它们的和为 j - nums[i]。

状态转移方程是:
dp[i][j] = dp[i - 1][j] or dp[i - 1][j - nums[i]]

一般写出状态转移方程以后,就需要考虑边界条件(一般而言也是初始化条件)。

  1. j - nums[i] 作为数组的下标,一定得保证大于等于 0 ,因此 nums[i] <= j;
  2. 注意到一种非常特殊的情况:j 恰好等于 nums[i],即单独 nums[j] 这个数恰好等于此时“背包的容积” j,这也是符合题意的。

二维数组 dp [ ][ ]

public class Solution {

    public boolean canPartition(int[] nums) {
        int len = nums.length;
        if (len == 0) {
            return false;
        }

        int sum = 0;
        for (int num : nums) {
            sum += num;
        }

        // 特判:如果是奇数,就不符合要求
        if ((sum & 1) == 1) {
            return false;
        }

        int target = sum / 2;

        // 创建二维状态数组,行:物品索引,列:容量(包括 0)
        boolean[][] dp = new boolean[len][target + 1];

        // 先填表格第 0 行,第 1 个数只能让容积为它自己的背包恰好装满
        if (nums[0] <= target) {
            dp[0][nums[0]] = true;
        }

        // 再填表格后面几行
        for (int i = 1; i < len; i++) {
            for (int j = 0; j <= target; j++) {
                // 直接从上一行先把结果抄下来,然后再修正
                dp[i][j] = dp[i - 1][j];

                if (nums[i] == j) {
                    dp[i][j] = true;
                    continue;
                }
                if (nums[i] < j) {
                    dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];
                }
            }
        }
        return dp[len - 1][target];
    }
}

一维数组 dp【】

“0-1 背包问题”常规优化:“装填数组”从二维降到一维,减少空间复杂度。

在“填表格”的时候,当前行只参考了上一行的值,因此状态数组可以只设置 2 行,使用“滚动数组”的技巧“填表格”即可;

实际上连“滚动数组”都不必,在“填表格”的时候,当前行总是参考了它上面一行 “头顶上” 那个位置和“左上角”某个位置的值。因此,我们可以只开一个一维数组,从后向前依次填表即可。

这一点第 1 次接触的时候,可能会觉得很奇怪,理解的办法是,就拿题目中的示例,画一个表格,自己模拟一遍程序是如何“填表”的行为,就很清楚为什么状态数组压缩到 1 行的时候,需要“从后前向”填表。

“从后向前” 写的过程中,一旦 nums[i] <= j 不满足,可以马上退出当前循环,因为后面的 j 的值肯定越来越小,没有必要继续做判断,直接进入外层循环的下一层。相当于也是一个剪枝,这一点是“从前向后”填表所不具备的。

class Solution {
    public boolean canPartition(int[] nums) {
        if(nums.length==0) return false;
        int sum=0;
        for(int i=0;i<nums.length;i++){
            sum+=nums[i];
        }


        if(sum%2==1) return false;

        int n=nums.length;
        int c=sum/2;
        boolean[] dp=new boolean[c+1];
        dp[0]=true;

        for(int i=0;i<=c;i++){
            if(nums[0]==c) dp[i]=true;
        }

        for(int i=1;i<n;i++){
            for(int j=c;j>=nums[i];j--){
                dp[j]=dp[j] || dp[j-nums[i]];
            }
        }
        return dp[c];
    }
}

437 路径总和3

给定一个二叉树,它的每个结点都存放着一个整数值。
找出路径和等于给定数值的路径总数。
路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。
示例:
root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8
10
/
5 -3
/ \
3 2 11
/ \
3 -2 1
返回 3。和等于 8 的路径有:

  1. 5 -> 3
  2. 5 -> 2 -> 1
  3. -3 -> 11

递归嵌套递归 逻辑比较复杂

以往的路径 包括node
394 字符串解码、406 根据身高重建队列、416 分割等和子集(等于)0-1背包、437 路径总和3 递归、448 找到所有数组中消失的数字、461 汉明距离 位运算异或、543 二叉树的直径_第1张图片
此时有可能不包括node所以要递归中加递归
都考虑了

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    // 在根为root的二叉树中,寻找和为sum 的路径,返回这样的路径个数
    public int pathSum(TreeNode root, int sum) {
        if(root==null) return 0;
        int res=helper(root,sum);//先求包含根节点的路径
        res+=pathSum(root.left,sum);//再求不包含根节点的路径
        res+=pathSum(root.right,sum);
        return res;
    }
    //在以node为根节点的二叉树中,寻找包含node 的路径,和为sum,返回路径
    public int helper(TreeNode root,int sum){
        if(root==null) return 0;
        int res=0;
        if(root.val==sum)   res+=1;
        res+=helper(root.left,sum-root.val);
        res+=helper(root.right,sum-root.val);
        return res;
    }
}

438 找到字符串中所有字母异位词

lee438 找到字符串中所有字母异位词

给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。

字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。

说明:
字母异位词指字母相同,但排列不同的字符串。
不考虑答案输出的顺序。
示例 1:
输入:
s: “cbaebabacd” p: “abc”
输出:
[0, 6]

解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的字母异位词。
起始索引等于 6 的子串是 “bac”, 它是 “abc” 的字母异位词。
示例 2:
输入:
s: “abab” p: “ab”
输出:
[0, 1, 2]
解释:
起始索引等于 0 的子串是 “ab”, 它是 “ab” 的字母异位词。
起始索引等于 1 的子串是 “ba”, 它是 “ab” 的字母异位词。
起始索引等于 2 的子串是 “ab”, 它是 “ab” 的字母异位词。

solution 直接修改之前的

选left移动时判断:

 while (right-left >= p.length()) {
                if(right-left == p.length() && valid == need.size()){
                    list.add(left);
                }
class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        HashMap<Character, Integer> need = new HashMap<>();
        HashMap<Character, Integer> window = new HashMap<>();
        List<Integer> list = new ArrayList<>();
        // 存需要的字母,可能会有重复
        for (int i = 0; i < p.length(); i++) {
            char curChar = p.charAt(i);
            need.put(curChar, need.getOrDefault(curChar, 0) + 1);
        }

        int left = 0, right = 0;
        int valid = 0;
        int start = 0, minLen = Integer.MAX_VALUE;

        while (right < s.length()) {
            char c = s.charAt(right);
            right++;
            if (need.containsKey(c)) {
                window.put(c, window.getOrDefault(c, 0) + 1);
                if ((int)window.get(c) ==(int) need.get(c)) {
                    valid++;
                }
            }
            // 判断左侧窗口
            while (right-left >= p.length()) {
                if(right-left == p.length() && valid == need.size()){
                    list.add(left);
                }
                char d = s.charAt(left);
                left++;
                if (need.containsKey(d)) {
                    if ((int)window.get(d) ==(int) need.get(d)) {
                        valid--;
                    }
                    window.put(d, window.get(d) - 1);
                }
            }
        }
        return list;
    }
}

448 找到所有数组中消失的数字 下标记数

给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。

找到所有在 [1, n] 范围之间没有出现在数组中的数字。

您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。

示例:

输入:
[4,3,2,7,8,2,3,1]

输出:
[5,6]

solution
找出 1 - n 中没有出现的数字。不能使用额外的空间,两次循环时间复杂度为 2O(n),即为 O(n)。
*
* 解题思路:使用数组的下标来标记数字的出现于否,通过一遍遍历即可标记出全部已经出现的数组
*
* [4,3,2,7,8,2,3,1] 初始数据
*
* [4,3,2,-7,8,2,3,1] 第一个数据 4 出现,将数组的第四个也就是下标 3 的数据修改为负数。-7 计算时,通过绝对值处理一下即可不影响数据的计算
* [4,3,-2,-7,8,2,3,1]
* [4,-3,-2,-7,8,2,3,1]
* [4,-3,-2,-7,8,2,-3,1]
* [4,-3,-2,-7,8,2,-3,-1]
* [4,-3,-2,-7,8,2,-3,-1]
* [4,-3,-2,-7,8,2,-3,-1]
* [-4,-3,-2,-7,8,2,-3,-1]
*
* 计算结束,数组的第五个,第六个依然为整数,证明 5,6 没有出现


    public List<Integer> findDisappearedNumbers(int[] nums) {
        List<Integer> results = new ArrayList<>();
        for (int i = 0; i < nums.length; i++) {
            if (nums[Math.abs(nums[i]) - 1] > 0) {
                nums[Math.abs(nums[i]) - 1] = - nums[Math.abs(nums[i]) - 1];
            }
        }
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] > 0) {
                results.add(i + 1);
            }
        }
        return results;
    }

461 汉明距离

两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。

给出两个整数 x 和 y,计算它们之间的汉明距离。

注意:
0 ≤ x, y < 231.

示例:

输入: x = 1, y = 4

输出: 2

解释:
1 (0 0 0 1)
4 (0 1 0 0)
↑ ↑

上面的箭头指出了对应二进制位不同的位置。

solution1 先异或再加所有的1喂

class Solution {
    public int hammingDistance(int x, int y) {
        int z = x ^ y;
	    int sum = 0;
	    while (z!=0){
		    sum += z & 1;
		    z = z>>1;
	    }
	    return sum;
    }
}

solution2

public int hammingDistance(int x, int y) {
        return Integer.bitCount(x^y);
    }

494 目标和(填+/-得和)

给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。

返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

示例:

输入:nums: [1, 1, 1, 1, 1], S: 3
输出:5
解释:

-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3

一共有5种方法让最终目标和为3。

提示:
数组非空,且长度不会超过 20 。
初始的数组的和不会超过 1000 。
保证返回的最终结果能被 32 位整数存下。

labuladong
solution1 回溯

class Solution {
    int res = 0;
    public int findTargetSumWays(int[] nums, int s) {
        if(nums == null || nums.length == 0)return 0;
        helper(nums,0,s);
        return res;
    }
    public void helper(int[] nums,int i, int target){
        if(i == nums.length){
            if(target == 0){
                res++;
            }
            return;
        }
        // 选 -
        target += nums[i];
        helper(nums,i+1,target);
        target -=nums[i];
        // 选 +
        target -=nums[i];
        helper(nums,i+1,target);
        target += nums[i];
    }
}

以上回溯算法可以解决这个问题,时间复杂度为 O(2^N),N 为 nums 的大小。这个复杂度怎么算的?回忆前文 学习数据结构和算法的框架思维,发现这个回溯算法就是个二叉树的遍历问题

void backtrack(int[] nums, int i, int rest) {
if (i == nums.length) {
return;
}
backtrack(nums, i + 1, rest - nums[i]);
backtrack(nums, i + 1, rest + nums[i]);
}
树的高度就是 nums 的长度嘛,所以说时间复杂度就是这棵二叉树的节点数,为 O(2^N),其实是非常低效的。

solution2
首先,如果我们把 nums 划分成两个子集 A 和 B,分别代表分配 + 的数和分配 - 的数,那么他们和 target 存在如下关系:

sum(A) - sum(B) = target
sum(A) = target + sum(B)
sum(A) + sum(A) = target + sum(B) + sum(A)
2 * sum(A) = target + sum(nums)
综上,可以推出 sum(A) = (target + sum(nums)) / 2,也就是把原问题转化成:nums 中存在几个子集 A,使得 A 中元素的和为 (target + sum(nums)) / 2?

有一个背包,容量为 sum,现在给你 N 个物品,第 i 个物品的重量为 nums[i - 1](注意 1 <= i <= N),每个物品只有一个,请问你有几种不同的方法能够恰好装满这个背包?

现在,这就是一个正宗的动态规划问题了,下面按照我们一直强调的动态规划套路走流程:

dp[i][j] = x 表示,若只在前 i 个物品中选择,若当前背包的容量为 j,则最多有 x 种方法可以恰好装满背包。

根据这个定义,显然 dp[0][…] = 0,因为没有物品的话,根本没办法装背包;dp[…][0] = 1,因为如果背包的最大载重为 0,「什么都不装」就是唯一的一种装法。

我们所求的答案就是 dp[N][sum],即使用所有 N 个物品,有几种方法可以装满容量为 sum 的背包。

第三步,根据「选择」,思考状态转移的逻辑。

回想刚才的 dp 数组含义,可以根据「选择」对 dp[i][j] 得到以下状态转移:

如果不把 nums[i] 算入子集,或者说你不把这第 i 个物品装入背包,那么恰好装满背包的方法数就取决于上一个状态 dp[i-1][j],继承之前的结果。

如果把 nums[i] 算入子集,或者说你把这第 i 个物品装入了背包,那么只要看前 i - 1 个物品有几种方法可以装满 j - nums[i-1] 的重量就行了,所以取决于状态 dp[i-1][j-nums[i-1]]。

PS:注意我们说的 i 是从 1 开始算的,而数组 nums 的索引时从 0 开始算的,所以 nums[i-1] 代表的是第 i 个物品的重量,j - nums[i-1] 就是背包装入物品 i 之后还剩下的容量。

由于 dp[i][j] 为装满背包的总方法数,所以应该以上两种选择的结果求和,得到状态转移方程:

dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i-1]];

/* 计算 nums 中有几个子集的和为 sum */
int subsets(int[] nums, int sum) {
    int n = nums.length;
    int[][] dp = new int[n + 1][sum + 1];
    // base case
    for (int i = 0; i <= n; i++) {
        dp[i][0] = 1;
    }
    
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= sum; j++) {
            if (j >= nums[i-1]) {
                // 两种选择的结果之和
                dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i-1]];
            } else {
                // 背包的空间不足,只能选择不装物品 i
                dp[i][j] = dp[i-1][j];
            }
        }
    }
    return dp[n][sum];
}


然后,发现这个 dp[i][j] 只和前一行 dp[i-1][…] 有关,那么肯定可以优化成一维 dp:

/* 计算 nums 中有几个子集的和为 sum */
int subsets(int[] nums, int sum) {
    int n = nums.length;
    int[] dp = new int[sum + 1];
    // base case
    dp[0] = 1;
    
    for (int i = 1; i <= n; i++) {
        // j 要从后往前遍历
        for (int j = sum; j >= 0; j--) {
            // 状态转移方程
            if (j >= nums[i-1]) {
                dp[j] = dp[j] + dp[j-nums[i-1]];
            } else {
                dp[j] = dp[j];
            }
        }
    }
    return dp[sum];
}

538 把二叉搜索树转换为累加树

给定一个二叉搜索树(Binary Search Tree),把它转换成为累加树(Greater Tree),使得每个节点的值是原来的节点值加上所有大于它的节点值之和。
例如:
输入: 原始二叉搜索树:
5
/
2 13

输出: 转换为累加树:
18
/
20 13

solution1 递归

BST的中序遍历就是从小到大,那么反过来就是从大到小,然后累加就好了.有个全局变量和sum

* public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    int num = 0;
    public TreeNode convertBST(TreeNode root) {
        helper(root);
        return root;
    }
    public void helper(TreeNode root){
        if(root == null) return ;
        helper(root.right);
        root.val = root.val + num;
        num = root.val;
        helper(root.left);
        
    }
}

solution2 迭代

class Solution {
    int num = 0;
    public TreeNode convertBST(TreeNode root) {
        if(root == null) return root;
        TreeNode newRoot = root;
        Stack<TreeNode> stack = new Stack<>();
        while(root != null || !stack.isEmpty()){
            while(root != null){
                stack.push(root);
                root = root.right;  
            }
        
            root = stack.pop();
            root.val +=num;
            num = root.val;
            root = root.left;
        
        }
        return newRoot;
    }
}

543 二叉树的直径

遍历每一个节点,以每一个节点为中心点计算最长路径(左子树边长+右子树边长),更新全局变量max。

class Solution {
    int max = 0;
    public int diameterOfBinaryTree(TreeNode root) {
        if (root == null) {
            return 0;
        }
        dfs(root);
        return max;
    }
    
    private int dfs(TreeNode root) {
        if (root.left == null && root.right == null) {
            return 0;
        }
        int leftSize = root.left == null? 0: dfs(root.left) + 1;
        int rightSize = root.right == null? 0: dfs(root.right) + 1;
        max = Math.max(max, leftSize + rightSize);
        return Math.max(leftSize, rightSize);
    }  
}

类似于124 二叉树最大路径和

560 和为k的子数组

给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。

示例 1 :

输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。

class Solution {
    public int subarraySum(int[] nums, int k) {
         /**
        扫描一遍数组, 使用map记录出现同样的和的次数, 对每个i计算累计和sum并判断map内是否有sum-k
        **/
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        int sum = 0, ret = 0;

        for(int i = 0; i < nums.length; ++i) {
            sum += nums[i];
            if(map.containsKey(sum-k))
                ret += map.get(sum-k);
            map.put(sum, map.getOrDefault(sum, 0)+1);
        }
        
        return ret;
    }
}

572 另一个树的子树

/**
 * 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 isSubtree(TreeNode root1, TreeNode root2) {
        boolean result=false;
        if(root1!=null&&root2!=null){  //不是while,因为只执行一次
            if(root1.val==root2.val){
                result=doesHave(root1,root2);
            }
            if(!result){
                result=isSubtree(root1.left,root2);
            }
            if(!result)
                result=isSubtree(root1.right,root2);
        }
        return result;
    }
    public boolean doesHave(TreeNode node1,TreeNode node2){
        if(node2==null && node1 == null)
            return true;
        if(node1==null || node2 == null)
            return false;
        if(node1.val!=node2.val){
            return false;
        }
        return doesHave(node1.left,node2.left)&&doesHave(node1.right,node2.right);
    }
}

581 最短无须连续子数组

high = 0;// 第一个大值不正确的索引
low = 1; // 第一个小值不正确的索引
maxValue;// 记录从左到右的当前最大值
minValue;// 记录从右到左的当前最小值

从左到右遍历找到需要变得最右边的指针high的位置
再从右到左遍历找到需要变得最左边的指针low的位置
最后答案就是 high-low+1;

class Solution {
    public int findUnsortedSubarray(int[] nums) {
        if(nums == null || nums.length == 0|| nums.length == 1) return 0;    
        int high = 0, low = nums.length-1;
        int maxCur = 0, minCur =  nums[nums.length-1];
        for(int i=1;i<nums.length;i++){
            if(nums[i]<maxCur){
                high = i;
            }
            maxCur = Math.max(maxCur,nums[i]);
        }
        for(int i=nums.length-2;i>=0;i--){
            if(nums[i]>minCur){
                low = i;
            }
            minCur = Math.min(minCur,nums[i]);
        }
        return high-low+1<0?0:high-low+1;
    }
}

617合并二叉树 合并到t1

给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。

你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。

示例 1:

输入:
Tree 1 Tree 2
1 2
/ \ / \
3 2 1 3
/ \ \
5 4 7
输出:
合并后的树:
3
/
4 5
/ \ \
5 4 7
注意: 合并必须从两个树的根节点开始。

solution1
都合并到t1上

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
        if(t1 == null )return t2;
        if(t2 == null )return t1;
        
        t1.val += t2.val;
        t1.left = mergeTrees(t1.left,t2.left);
        t1.right = mergeTrees(t1.right,t2.right);
        return t1;       
    }
}

solution2 不修改原二叉树的解法

    public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
        if (t1 == null && t2 == null) {
            return null;
        }
        // 先合并根节点
        TreeNode root = new TreeNode((t1 == null ? 0 : t1.val) + (t2 == null ? 0 : t2.val));
        // 再递归合并左右子树
        root.left = mergeTrees(t1 == null ? null : t1.left, t2 == null ? null : t2.left);
        root.right = mergeTrees(t1 == null ? null : t1.right, t2 == null ? null : t2.right);
        return root;
    }
}

647回文子串 用判断方法,加count计数

给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被计为是不同的子串。

示例 1:
输入: “abc”
输出: 3
解释: 三个回文子串: “a”, “b”, “c”.
示例 2:
输入: “aaa”
输出: 6
说明: 6个回文子串: “a”, “a”, “a”, “aa”, “aa”, “aaa”.

class Solution {
    public int countSubstrings(String s) {
        int res = 0;
        int n = s.length();
        boolean[][] dp = new boolean[n][n];
        for(int i=n-1;i>=0;i--){
            for(int j=i;j<n;j++){
                if(s.charAt(i) == s.charAt(j) && (j-i<=2||dp[i+1][j-1])){
                    dp[i][j] = true;
                    res++;
                }
            }
        }
        return res;
    }
}

771宝石与石头 ,s字符里找j出现的

给定字符串J 代表石头中宝石的类型,和字符串 S代表你拥有的石头。 S 中每个字符代表了一种你拥有的石头的类型,你想知道你拥有的石头中有多少是宝石。

J 中的字母不重复,J 和 S中的所有字符都是字母。字母区分大小写,因此"a"和"A"是不同类型的石头。

示例 1:
输入: J = “aA”, S = “aAAbbbb”
输出: 3
示例 2:
输入: J = “z”, S = “ZZ”
输出: 0
注意:
S 和 J 最多含有50个字母。
J 中的字符不重复。

考虑更高的空间性能,使用byte数组。ASCII码中字母的跨度为65~122,所以定义数组长度为58最节省。

class Solution {
    public int numJewelsInStones(String J, String S) {
        if(J == null || J == "" || S == null || S == "") return 0;
        byte[] arr = new byte[60];
        int count = 0;
        for(char c:J.toCharArray()){
            arr[c-65] = 1;
        }
        for(char c:S.toCharArray()){
            if(arr[c-65] == 1){
                count++;
            }
        }
        return count;
    }
}

你可能感兴趣的:(lee必刷)