leetcodehot/86-90

406-根据身高重建队列

在这里插入图片描述
…刚开始是乱序的—》》调整为有序
题目描述:整数对 (h, k) 表示,其中 h 是这个人的身高,k 是排在这个人前面且身高大于或等于 h 的人数。

渔(套路):一般这种数对,还涉及排序的,根据第一个元素正向排序,根据第二个元素反向排序,或者根据第一个元素反向排序,根据第二个元素正向排序,往往能够简化解题过程。

在本题目中,我首先对数对进行排序,按照数对的元素 1 降序排序,按照数对的元素 2 升序排序。原因是,按照元素 1
进行降序排序,对于每个元素,在其之前的元素的个数,就是大于等于他的元素的数量,而按照第二个元素正向排序,我们希望 k
大的尽量在后面,减少插入操作的次数。 作者:LeahChao
链接:https://leetcode-cn.com/problems/queue-reconstruction-by-height/solution/xian-pai-xu-zai-cha-dui-dong-hua-yan-shi-suan-fa-g/

class Solution {
    public int[][] reconstructQueue(int[][] people) {
        //先对数组进行排序,按照第一个元素进行降序,按照第二个元素升序
        //这样的原因是:“按照第二个元素正向排序,我们希望 k 大的尽量在后面,减少插入操作的次数。
        //”不止是为了减少插入次数,也是为了保证正确性。 举个例子,在身高一样,k不一样的时候,譬如[5,2]和[5,3], 
        //对于最后排完的数组,[5,2]必然在[5,3]的前面。所以如果遍历的时候[5,3]在前面,等它先插入完,
        //这个时候它前面会有3个大于等于它的数组对,遍历到[5,2]的时候,它必然又会插入[5,3]前面
        //(因为它会插入链表索引为2的地方),这个时候[5,3]前面就会有4个大于等于它的数组对了,这样就会出错。
        Arrays.sort(people,new Comparator<int[]>(){
            public int compare(int[] person1,int[] person2){
                if(person1[0] != person2[0]){
                    return person2[0] - person1[0];
                }else{
                    return person1[1]-person2[1];
                }
            }
        });
        //新建list,保存结果,都是插入操作,因此用链表
        List<int[]> list = new LinkedList<>();
        for(int i=0; i<people.length;i++){
            if(list.size()>people[i][1]){
                //结果集中元素个数大于第i个人前面应有的人数时,将第i个人插入到结果集的 people[i][1]位置
                //由于是按照第一个元素降序排列的,因此插入到对应位置,前面就一定会有people[i][1]个人大于或者等于他
                list.add(people[i][1],people[i]);
            }else{
                //结果集中元素个数小于等于第i个人前面应有的人数时,将第i个人追加到结果集的后面
                list.add(list.size(),people[i]);
            }
        }
        //list转为数组
        return list.toArray(new int[list.size()][]);
    }
}

416. 分割等和子集

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

示例 1:

输入:nums = [1,5,11,5] 输出:true 解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:

输入:nums = [1,2,3,5] 输出:false 解释:数组不能分割成两个元素和相等的子集。

class Solution {
    //问题可以转化为找数组内若干元素和等于总和的一半
    /**
        dp[i][j] 表示从数组[0,i]中选取若干个整数(可以是0个)组成一些数组,这些数字的和等于target
        状态方程:j>=nums[i] ,则可以选取也可以不选取:
                                    1.选取 dp[i][j] = dp[i-1][j-nums[i]];
                                    2.不选取 dp[i][j] = dp[i-1][j];
                j<nums[i],就肯定不能选取,则这个时候 dp[i][j] = dp[i-1][j]
        边界:i==0时:只有dp[i][nums[i]]=true
            对于dp[i][0] ,只有i在取值范围内都是true
     */
    public boolean canPartition(int[] nums) {
        int n = nums.length;
        //n小于2肯定不行
        if(n<2) return false;
        int sum =0,maxNum = 0;
        //求数组的和,并找出最大值
        for(int num:nums){
            sum += num;
            maxNum = Math.max(maxNum,num);
        }
        int target = sum/2;
        //sum是奇数不行(两个相同的数加起来一定是偶数)
        //maxNum大于sum/2返回false
        if(sum%2 !=0 || maxNum > target) return false;

        boolean[][] dp = new boolean[n][target+1];
        //边界
        for(int i=0;i<n;i++){
            dp[i][0] = true;
        }
        dp[0][nums[0]] = true;
        for(int i=1;i<n;i++){
            int num = nums[i];
            for(int j=1;j<=target;j++){
                if(j>=num){
                    dp[i][j] = dp[i-1][j]|dp[i-1][j-num];
                }else{
                    dp[i][j] = dp[i-1][j];
                }
            }
        }
        return dp[n-1][target];
    }
}

437. 路径总和 III

给定一个二叉树,它的每个结点都存放着一个整数值。

找出路径和等于给定数值的路径总数。

路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。

class Solution {
    /**
       前缀和+dfs
       在同一个路径之下(可以理解成二叉树从root节点出发,到叶子节点的某一条路径),如果两个数的前缀总和是相同的,那么这些节点之间的元素总和为零。进一步扩展相同的想法,如果前缀总和currSum,在节点A和节点B处相差target,则位于节点A和节点B之间的元素之和是target。

       也就是不断遍历回溯查找从某点出发前缀和等于target的过程
     */
    public int pathSum(TreeNode root, int targetSum) {
        //key是前缀和,value 是大小为key的前缀和出现的次数
        Map<Integer,Integer> prefixSumCount = new HashMap<>();
        //前缀和为0的一条路径
        prefixSumCount.put(0,1);
        //前缀和的递归遍历
        return recursionPathSum(root, prefixSumCount, targetSum, 0);
    }
    //前缀和的递归回溯思路
    //从当前节点反推到根节点(反推比较好理解,正向其实也只有一条),有且仅有一条路径,因为这是一棵树
    //如果此前有和为currSum-target,而当前的和又为currSum,两者的差就肯定为target了
    //所以前缀和对于当前路径来说是唯一的,当前记录的前缀和,在回溯结束,回到本层时去除,保证其不影响其他分支的结果
    private int recursionPathSum(TreeNode node, Map<Integer, Integer> prefixSumCount, int target, int currSum) {
        // 1.递归终止条件
        if(node == null) return 0;
        //2.本层要做的事情
        int res = 0;
        //当前路径上的和
        currSum += node.val;

        //看看root到当前节点这条路上是否存在节点前缀和加target为currSum的路径
        //当前节点->root节点反推,有且仅有一条路径,如果此前有和为currSum-target,而当前的和又为currSum,两者的差就肯定为target了
        // currSum-target相当于找路径的起点,起点的sum+target=currSum,当前点到起点的距离就是target
        res += prefixSumCount.getOrDefault(currSum - target, 0);
        // 更新路径上当前节点前缀和的个数
        prefixSumCount.put(currSum, prefixSumCount.getOrDefault(currSum, 0) + 1);
        //---核心代码

        // 3.进入下一层
        res += recursionPathSum(node.left, prefixSumCount, target, currSum);
        res += recursionPathSum(node.right, prefixSumCount, target, currSum);

        // 4.回到本层,恢复状态,去除当前节点的前缀和数量
        prefixSumCount.put(currSum, prefixSumCount.get(currSum) - 1);
        return res;
    }
}

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

给定一个字符串 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” 的字母异位词。

class Solution {
    //滑动窗口
    public List<Integer> findAnagrams(String s, String p) {
        int[] fre = new int[26];
        //表示窗口内相差的字符的数量
        int dif=0;
        //统计匹配串中字符出现的频数
        for(char c:p.toCharArray()){
            fre[c-'a']++;
            dif++;
        }
        //left用于标记正在匹配的滑动窗口的最左端
        //right用来标记s中,当前正在匹配的字符
        int left=0,right=0;
        int len = s.length();
        char[] ch = s.toCharArray();
        List<Integer> list = new ArrayList<>();
        while(right<len){
            char rightChar = ch[right];

            //当前字符是p中的字符
            if(fre[rightChar-'a']>0){
                fre[rightChar-'a']--;
                //差距减少
                dif--;
                //香右移动继续匹配
                right++;

                //差距为0是,说明当前窗口内为所求
                if(dif==0) list.add(left);
            }else{
                   //rightChar 是p以外的字符,如'c'此时 left 和right都应该后移,同时恢复fre数组,
                    //因为left在最左边,因此通过fre[array[left]-'a']++;就可以恢复
                    while(fre[rightChar-'a']<=0&&left<right){
                        fre[ch[left]-'a']++;
                        left++;
                        dif++;
                    }
                    //移动到left==right时
                    if(left==right){
                        //如果此时字符在p中
                        if(fre[ch[right]-'a']>0){
                            continue;
                        }else{
                            left++;
                            right++;
                        }
                    }
                    
            }
        }
         return list; 
    }
}

448. 找到所有数组中消失的数字

给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。

示例 1:
输入:nums = [4,3,2,7,8,2,3,1] 输出:[5,6]
示例 2:

输入:nums = [1,1] 输出:[2]

class Solution {
    public List<Integer> findDisappearedNumbers(int[] nums) {
        List<Integer> ans = new ArrayList<>();
        int n=nums.length;
        if(nums==null || n==0) return ans;
        for(int num:nums){
            //num-1是因为下标是从0~n-1,取余防止越界
            int x = (num-1)%n;
            //这样以缺失元素为下标的位置上的数就会小于n
            nums[x] +=n;
        }
        for(int i=0;i<n;i++){
            if(nums[i]<=n){
                //返回小于等于n位置上的下标
                ans.add(i+1);
            }
        }
        return ans;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值