算法学习day24

一、砖墙

题意:

有一面砖墙,每一行的长度都相等,但是每一行中每一块砖的宽度不相等,导致上下不是对齐的,现在要求从上方某个位置画一条自上而下的垂线,要求穿过的砖数是最少的。(要求穿过的砖数最少相当于是穿过的部分都在砖块的边缘)

思路:

使用哈希表将每一行中砖块边缘的部分记录下来,键值对为:边缘部分:出现的次数。然后最后遍历哈希表将次数最大的数字获取出来,返回 size()-max;

代码:
class Solution {
    public int leastBricks(List<List<Integer>> wall) {
        Map<Integer,Integer> map=new HashMap<>();
        for(List<Integer> row:wall){
            int sum=0;
            for(int i=0;i<row.size()-1;i++){
                sum+=row.get(i);
                map.put(sum,map.getOrDefault(sum,0)+1);
            }
        }
        int ans=0;
        for(Map.Entry<Integer,Integer> entry:map.entrySet()){
            int value=entry.getValue();
            ans=Math.max(ans,value);
        }
        return wall.size()-ans;
    }
}

二、在系统中查找重复文件

题意:

给你一个目录信息列表 paths ,包括目录路径,以及该目录中的所有文件及其内容,请你按路径返回文件系统中的所有重复文件。括号里面表示文件内容。一个目录下面的根目录只显示一次。比如说root/a/1.txt 和root/a/2.txt

输入:paths = ["root/a 1.txt(abcd) 2.txt(efgh)","root/c 3.txt(abcd)","root/c/d 4.txt(efgh)","root 4.txt(efgh)"]
输出:[["root/a/2.txt","root/c/d/4.txt","root/4.txt"],["root/a/1.txt","root/c/3.txt"]]
思路:仍然使用哈希表 键值对:文件内容:文件路径

把文件内容和相对应的文件路径存储到键值对中,判断某一个文件内容所对应的文件路径的数量是否是>1的,如果是就说明有重复的,反而说明没有重复的。

当截取文件内容的时候,使用String.substring(split[0].indexOf("("),split[0].substring(")")+1);

获得文件路径的时候,使用字符串拼接。

代码:
class Solution {
    public List<List<String>> findDuplicate(String[] paths) {
        //使用哈希表存储括号里面的内容 键值对分别为:括号里的内容 位置
     
        //首先先将文件内容和文件路径放到map集合中
        Map<String,List<String>> map=new HashMap<>();
        for(int i=0;i<paths.length;i++){
            String[] split=paths[i].split("\\ ");
            String root=split[0];
            int size=split.length;
            for(int j=1;j<size;j++){
                String context=split[j].substring(split[j].indexOf("(")+1,split[j].indexOf(")"));
                String path=root+"/"+split[j].substring(0,split[j].indexOf("("));
                if(!map.containsKey(context)){
                    List<String> list=new ArrayList<String>();
                    list.add(path);
                    map.put(context,list);
                }else{
                    List<String> list=map.get(context);
                    list.add(path);
                    map.put(context,list);
                }
            }
        }

        //导出结果
        List<List<String>> res=new ArrayList<>();
        Set<String> keys=map.keySet();
        for(String key:keys){
            List<String> list=map.get(key);
            if(list.size()>1){
                res.add(list);
            }
        }
        return res;
    }
}

前缀和系列(可以和map配合 提高效率)

前缀和:可以简单的理解为数列的前n项和。Sn=a1+a2+a3+a4+...+ab=n;Sn-Sn-1=an;所以

Sn=Sn-1+an;

二维前缀和:S[i][j]=S[i-1][j]+S[i][j-1]-S[i-1][j-1]+a[i][j]。以(i,j)为右下角的子矩阵中所有元素的和

三、和为k的子数组

给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 

子数组是数组中元素的连续非空序列。

暴力法:

固定左边的起点,不断移动右边的起点,看是否sum==k。如果相等,count++;

class Solution {
    int count=0;
    public int subarraySum(int[] nums, int k) {
        for(int left=0;left<nums.length;left++){
            int sum=0;
            for(int right=left;right<nums.length;right++){
                sum+=nums[right];
                if(sum==k)count++;
            }
        }
        return count;
    }
}
前缀和+哈希表:
思路:

使用前缀和,统计从0到下标为n,这段区域的和。

当统计到index为i的时候,就看0->index这段范围内的和-k是否在map中出现。

如果出现的话,就说明差集可以得到k;范围也就是0->index减去和为k的的这个范围

如果不出现的话,说明那个差还没有出现过,就把前缀和放到map集合中。

代码:
class Solution {
    public int subarraySum(int[] nums, int k) {
        int count = 0;
        Map<Integer,Integer> preSumMap=new HashMap<>();
        int preSum=0;
        preSumMap.put(preSum,1);
        for(int num:nums){
            preSum+=num;
            if(preSumMap.containsKey(preSum-k)){
                count+=preSumMap.get(preSum-k);
            }
            preSumMap.put(preSum,preSumMap.getOrDefault(preSum,0)+1);
        }
        return count;
    }
}

四、统计优美子数组(前缀和+哈希表)

给你一个整数数组 nums 和一个整数 k。如果某个连续子数组中恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」。返回优美子数组的个数

思路:

和上道题一样,我们使用前缀和+哈希表的思路来解决。

在遍历nums数组中每一个元素的时候,如果该数字是奇数,那么preSum++;如果是偶数,preSum保持不变;

判断完奇偶数之后,要判断当前遍历到的位置,有没有可能出现k个奇数。如果当前preSum>=k,说明有可能,此时需要判断map.containsKey(preSum-k)是否存在?如果存在的话,中间这一段就一定是优美子数组。

代码:
class Solution {
    public int numberOfSubarrays(int[] nums, int k) {
        // 键值对 出现奇数的次数:次数
        Map<Integer,Integer> map = new HashMap<>();
        map.put(0, 1);
        // 前缀和改为 前n个里面奇数的个数
        int preSum = 0;
        int count = 0;
        for (int num : nums) {
            preSum=num%2==0?preSum:preSum+1;
            if (map.containsKey(preSum - k)) {
                count += map.get(preSum - k);
                System.out.println(count);
            }
            map.put(preSum, map.getOrDefault(preSum, 0) + 1);

        }
        return count;
    }
}

五、和可被K整除的子数组(哈希表+前缀和)

给定一个整数数组 nums 和一个整数 k ,返回其中元素之和可被 k 整除的非空 子数组 的数目。

子数组 是数组中 连续 的部分。

思路:

在本道题中,使用preSum[]来表示前缀和数组。为了在数组中能够找到被K整除的子数组。即在遍历的过程中看map哈希表中是否存在余数。为什么这么说呢?preSum[i]+preSum[i,j]=preSum[j](假设这里已经遍历到了j处),要使得preSum[i,j]可以整除K 。preSum[i]的余数一定和(preSum%k+k)%k的余数相等。那我们这里就从map中判断是否存在就可以了。

代码:
class Solution {
    public int subarraysDivByK(int[] nums, int k) {
        /**
         * 1.仍然使用前缀和+哈希表的策略 键值对为:元素之和:出现的次数
         */
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0, 1);
        int count = 0;
        int preSum = 0;
        for (int num : nums) {
            preSum += num;
            int key=(preSum%k+k)%k;//防止余数变为负数
            if(map.containsKey(key)){
                count+=map.get(key);
            }
            //前一段可能增加 
            map.put(key,map.getOrDefault(key,0)+1);
        }
        return count;
    }
}

六、连续的子数组和(哈希表+前缀和)

给你一个整数数组 nums 和一个整数 k ,如果 nums 有一个 好的子数组 返回 true ,否则返回 false

一个 好的子数组 是:

  • 长度 至少为 2 ,且
  • 子数组元素总和为 k 的倍数。
  • 输入:nums = [23,2,4,6,7], k = 6
  • 输出:true
  • 解释:[2,4] 是一个大小为 2 的子数组,并且和为 6 。
思路:

为了能够找到长度>=2,和为k的倍数的连续子数组,仍然使用哈希表进行存储。但是键值对为:余数:最小的下标。我们这里只需要判断是否存在这样的子数组,不需要判断出现的次数。因此只保存最小的下标。

首先先判断子数组元素总和是否为k的倍数。和上一题一样,通过map.containsKey(key),key是能使范围里面的和为k的倍数的余数。如果存在,再去判断下标差

如何判断下标差,因为value里面存的是余数的最小下标,直接使用i+1-get(key)>=2即可 

代码:
/**
map集合中存储一个余数和它对应的最小下标位置
 */
class Solution {
    public boolean checkSubarraySum(int[] nums, int k) {
        Map<Integer,Integer> map=new HashMap<>();
        int preSum=0;
        map.put(0,0);
        for(int i=0;i<nums.length;i++){
            preSum+=nums[i];
            int key=(preSum%k+k)%k;
            if(map.containsKey(key)){
                //判断索引位置 目前结束位置是一个i
                int index=map.get(key);
                if(i-index+1>=2)return true;
            }else{
                map.put(key,i+1);
            }
        }
        return false;
    }
}
注意:

这里的map里的下标值应该是对应下标+1,因为0的位置存放了0

七、连续数组(前缀和+哈希表)

给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。

输入: nums = [0,1,0]
输出: 2
说明: [0, 1] (或 [1, 0]) 是具有相同数量0和1的最长连续子数组。
思路:

题目中要求统计有相同数量的0和1的最长连续子数组,可以将0变成-1,然后求和为0的最长连续子数组。这样就和之前的题类似了。

代码:
/**
哈希表和前缀和 键值对为:和:下标
 */ 
class Solution {
    public int findMaxLength(int[] nums) {
        Map<Integer,Integer> map=new HashMap<>();
        int max=0;
        int preSum=0;
        map.put(0,0);
        for(int i=0;i<nums.length;i++){
            if(nums[i]==0)nums[i]=-1;
            preSum+=nums[i];
            if(map.containsKey(preSum)){
                int part=map.get(preSum);
                max=Math.max(max,i-part+1);
            }else{
                map.put(preSum,i+1);
            }
        }
        return max;
    }
}

总结:

1.求最多数组的个数,map中的value存放次数

2.求最长子数组,map中的value存放最小下标

八、验证回文串II

给你一个字符串 s最多 可以从中删除一个字符。

请你判断 s 是否能成为回文字符串:如果能,返回 true ;否则,返回 false 。

思路:

使用快慢指针从两边向里面进行判断,如果相等就left++,right--;

如果不相等的话,可以通过删除左边的元素即left++/或者删除右边的元素即right--

来判断删除后的字符串是不是回文串。

代码:
class Solution {
    public boolean validPalindrome(String s) {
        int left=0;
        int right=s.length()-1;
        while(left<right){
            if(s.charAt(left)!=s.charAt(right)){
                return validPalindromeHelper(s,left+1,right)||validPalindromeHelper(s,left,right-1);
            }else{
                left++;
                right--;
            }
        }
        return true;
    }
    public boolean validPalindromeHelper(String s,int left,int right){
        while(left<right){
            if(s.charAt(left)==s.charAt(right)){
                left++;
                right--;
            }else{
                return false;
            }
        }
        return true;
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值