哈希表与统计——594、350、554、609、454(2简3中)

594. 最长和谐子序列(简单)

和谐数组是指一个数组里元素的最大值和最小值之间的差别 正好是 1 。

现在,给你一个整数数组 nums ,请你在所有可能的子序列中找到最长的和谐子序列的长度。

数组的子序列是一个由数组派生出来的序列,它可以通过删除一些元素或不删除元素、且不改变其余元素的顺序而得到。

解法一、哈希统计

如果不存在键,放入1。如果存在键,更新数量。如果存在num+1或者-1的键,更新最大值。

class Solution {
    public int findLHS(int[] nums) {
        HashMap<Integer,Integer> hm = new HashMap<>();
        int max = 0;
        for(int num : nums){
            if(!hm.containsKey(num)){
                hm.put(num,1);
            }else{
                hm.put(num,hm.get(num) + 1);
            }
            if(hm.containsKey(num+1)){
                max = Math.max(max,hm.get(num) + hm.get(num+1));
            }
            if(hm.containsKey(num-1)){
                max = Math.max(max,hm.get(num) + hm.get(num-1));
            }
        }
        return max;
    }
}

 

解法二、排序+滑动窗口

滑窗和双指针做法到底什么差别orzzzz

class Solution {
    public int findLHS(int[] nums) {
        Arrays.sort(nums);
        int n = nums.length, ans = 0;
        for (int i = 0, j = 0; j < n; j++) {
            while (i < j && nums[j] - nums[i] > 1) i++;
            if (nums[j] - nums[i] == 1) ans = Math.max(ans, j - i + 1);
        }
        return ans;
    }
}


350. 两个数组的交集 II(简单)

给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。 

解法一、排序

class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        List<Integer> res = new LinkedList<>();
        Arrays.sort(nums1);
        Arrays.sort(nums2);
        int i = 0,j = 0;
        int n1 = nums1.length,n2 = nums2.length;
        while(i < n1 && j < n2){
            while(i < n1 && j < n2&& nums1[i] < nums2[j])i++;
            while(i < n1 && j < n2 && nums1[i] > nums2[j])j++;
            while(i < n1 && j < n2 && nums1[i] == nums2[j]){
                res.add(nums1[i]);
                i++;
                j++;
            }
        }
        int[] r = new int[res.size()];
        for(int k = 0;k < res.size();k++){
            r[k] = res.get(k);
        }
        return r;
    }
}

 

 解法二、哈希

不过其实感觉如果是哈希的话,直接用数组(桶计数)就挺好的,遍历一个++,遍历另一个--

class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        if (nums1.length > nums2.length) {
            return intersect(nums2, nums1);
        }
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        for (int num : nums1) {
            int count = map.getOrDefault(num, 0) + 1;
            map.put(num, count);
        }
        int[] intersection = new int[nums1.length];
        int index = 0;
        for (int num : nums2) {
            int count = map.getOrDefault(num, 0);
            if (count > 0) {
                intersection[index++] = num;
                count--;
                if (count > 0) {
                    map.put(num, count);
                } else {
                    map.remove(num);
                }
            }
        }
        return Arrays.copyOfRange(intersection, 0, index);
    }
}

作者:力扣官方题解
链接:https://leetcode.cn/problems/intersection-of-two-arrays-ii/solutions/327356/liang-ge-shu-zu-de-jiao-ji-ii-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

554. 砖墙(中等)

你的面前有一堵矩形的、由 n 行砖块组成的砖墙。这些砖块高度相同(也就是一个单位高)但是宽度不同。每一行砖块的宽度之和相等。

你现在要画一条 自顶向下 的、穿过 最少 砖块的垂线。如果你画的线只是从砖块的边缘经过,就不算穿过这块砖。你不能沿着墙的两个垂直边缘之一画线,这样显然是没有穿过一块砖的。

给你一个二维数组 wall ,该数组包含这堵墙的相关信息。其中,wall[i] 是一个代表从左至右每块砖的宽度的数组。你需要找出怎样画才能使这条线 穿过的砖块数量最少 ,并且返回 穿过的砖块数量 。

解法一、哈希表

本质上还是统计题。需要转化思维,算穿过的砖最少,也就是算穿过缝隙最多。例如,对于用例

wall = [[1,2,2,1],[3,1,2],[1,3,2],[2,4],[3,1,2],[1,3,1,1]]

相当于统计1/3/5/6 、 3/4/6 、 1/4/6 、 2/6 、 3/4/6 、 1/4/5/6 中除了6以外,哪个数字出现最多,这个数字也就是代码里面的max。要注意对于不在边缘划线的条件,需要通过哈希表移除最大值的方式达到,而不能在最后的for循环里设置if。

此外有个细节:多写一个循环遍历哈希表值里最大值,比在第一个for里算要快。

class Solution {
    public int leastBricks(List<List<Integer>> wall) {
        int num = wall.size();
        int max = 0,sum = 0;
        HashMap<Integer,Integer> hm = new HashMap<>();
        for(List<Integer> bricks : wall){
            sum = 0;
            for(int brick : bricks){
                sum+=brick;
                if(!hm.containsKey(sum)){
                    hm.put(sum,1);
                }else{
                    hm.put(sum,hm.get(sum) + 1);
                }
            }
        }
        hm.remove(sum);
        for(int t : hm.values()){
            max = Math.max(max,t);
        }
        return num - max;
    }
}

 

 
609. 在系统中查找重复文件(中等)

给你一个目录信息列表 paths ,包括目录路径,以及该目录中的所有文件及其内容,请你按路径返回文件系统中的所有重复文件。答案可按 任意顺序 返回。

一组重复的文件至少包括 两个 具有完全相同内容的文件。

输入 列表中的单个目录信息字符串的格式如下:

  • "root/d1/d2/.../dm f1.txt(f1_content) f2.txt(f2_content) ... fn.txt(fn_content)"

这意味着,在目录 root/d1/d2/.../dm 下,有 n 个文件 ( f1.txtf2.txt ... fn.txt ) 的内容分别是 ( f1_contentf2_content ... fn_content ) 。注意:n >= 1 且 m >= 0 。如果 m = 0 ,则表示该目录是根目录。

输出 是由 重复文件路径组 构成的列表。其中每个组由所有具有相同内容文件的文件路径组成。文件路径是具有下列格式的字符串:

  • "directory_path/file_name.txt"

 解法一、哈希

(备注写在前面)官方做法里的键值对是String--List<String>

遍历给的字符串组,对于每一个固定的字符串,用空格分割。此时的temp[0]是地址,之后的都是文件名和内容。把内容context作为键存入哈希,值则是对应的list中下标,方便每个文件找到自己在res中的位置。

这里有一个问题:如果重复时再存的话,第一次重复需要存两个,其余只需要存一个。所以不妨逆向思维,先把所有的都存进去,再删去不重复的(即size == 1)。

但是,尤其对于size == 1情况下的删除细节,也出过很多问题。最初时尝试了for的int i循环,但由于一边遍历一边删,它的size很快就变小了,也就是会访问过界。第二次时尝试了for-each循环,结果报错java.util.ConcurrentModificationException

        for(List<String> list : res){
            if(list.size() == 1)res.remove(list);
        }

 经检查,发现是遍历器和remove的删除问题,事故原因溯至轮子(源码)的某个值更新出现差错。于是再进行优化。解决方法有两种,两种都依旧是for的int i循环①每次删除操作后,i--②从后往前遍历。

class Solution {
    public List<List<String>> findDuplicate(String[] paths) {
        List<List<String>> res = new LinkedList<>();
        HashMap<String,Integer> hm = new HashMap<>();
        for(String s : paths){
            String[] temp = s.split(" ");
            for(int i = 1;i < temp.length;i++){
                String context = temp[i].substring(temp[i].indexOf('(')+1);
                StringBuilder sb = new StringBuilder();
                sb.append(temp[0]).append('/').append(temp[i].substring(0,temp[i].indexOf('(')));
                if(hm.containsKey(context)){//如果已经存在
                    res.get(hm.get(context)).add(sb.toString());//取出对应
                }else{//放进去
                    hm.put(context,res.size());
                    List<String> tempList = new LinkedList<>();
                    tempList.add(sb.toString());
                    res.add(tempList);
                }
            }
        }
         for(int i = res.size()-1;i >= 0;i--){
            if(res.get(i).size() == 1)res.remove(i);
        }
        return res;
    }
}

454. 四数相加 II(中等)

给你四个整数数组 nums1nums2nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

  • 0 <= i, j, k, l < n
  • nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

解法一、哈希

两两分组判断,时间复杂度O(n^2)

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        HashMap<Integer,Integer> hm = new HashMap<>();
        int res = 0;
        for(int num1 : nums1){
            for(int num2 : nums2){
                int sumAB = num1 + num2;
                if(!hm.containsKey(sumAB)){
                    hm.put(sumAB,1);
                }else{
                    hm.put(sumAB,hm.get(sumAB)+1);
                }
            }
        }
        for(int num3 : nums3){
            for(int num4 : nums4){
                int sumCD = -num3-num4;
                if(hm.containsKey(sumCD)){
                    res+= hm.get(sumCD);
                }
            }
        }
        return res;
    }
}

 

解法二、桶排序

时间复杂度差太多了。。第一个for循环之前确认范围,第一个for循环统计数量,第二个for循环查找符合条件的数量并加入res。原理没变,数据结构优化了

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        int[] n1 = getMaxMin(nums1);
        int[] n2 = getMaxMin(nums2);
        int[] n3 = getMaxMin(nums3);
        int[] n4 = getMaxMin(nums4);
        int maxSum = Math.max(n1[0] + n2[0], -n3[1] - n4[1]);
        int minSum = Math.min(n1[1] + n2[1], -n3[0] - n4[0]);
        int[] map = new int[maxSum - minSum + 1];
        for (int i : nums1) {
            for (int j : nums2) {
                map[i + j - minSum] ++ ;
            }
        }
        int count = 0;
        for (int i : nums3) {
            for (int j : nums4) {
                count += map[- i - j - minSum];
            }
        }
        return count;
    }
    public int[] getMaxMin (int[] nums) {
        int[] num = Arrays.copyOf(nums, nums.length);
        Arrays.sort(num);
        int min = num[0];
        int max = num[nums.length - 1];
        return new int[] {max, min};
    }
}

 


碎碎念

  • 594重要的思路转换——能够删除任何数,就是能够选择任何想要的数。滑动窗口很有意思,虽然没搞懂和双指针根本上的差别。应该只是基于双指针实现?这附近埋个思考。
  • 554的思路转换很有意思。技术力感觉还好,配得上中等题。
  • 609写起来太冗余复杂了,但是也很有趣。确实感觉到细节处理能力上升了,而且一开始打好代码框架确实很重要
  • 454看了下题解才学会二二分组来着。
  • 其实还有个18,但是太难了,今天没睡午觉有点困,不想做。。
  • 19
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值