双非本科准备秋招(7)——力扣哈希、二分与容器学习

学了三天容器的知识了,追踪了不少次源码,整体上有所感悟,今天再背背容器的相关面试题啥的就结束了,明天开始学JVM。

力扣

1、1. 两数之和

暴力的方法直接就能想出来,两层for嘛,第一层遍历每个数,第二层再遍历一次,每次都看看和等不等于target。

如何用哈希的知识优化呢,哈希表查询时间O(1),所以我们可以减少一层for循环,遍历数组的时候,查表,看看表里有没有值等于target-nums[i]的,这就是我们需要的值。

class Solution {
    public int[] twoSum(int[] nums, int target) {
        HashMap map = new HashMap<>();
        for(int i = 0; i < nums.length; i++){
            int k = target - nums[i];
            if(map.containsKey(k)){
                return new int[]{map.get(k), i};
            }
            map.put(nums[i], i);
        }
        return null;
    }
}

2、3. 无重复字符的最长子串

方法一:

我想的是,每次遍历这个字符串,每次遍历的这个字符都当作第一个节点,然后建立一个哈希表,一直添加元素直到有重复的,同时维护一个ans,记录最大值。

class Solution {
    public int lengthOfLongestSubstring(String s) {
        // 每次找都用一个新的hash表,用来存放每个元素,这样就能知道重复没。
        //维护一个ans,每次取最大值。
        int ans = -1;
        for(int i = 0; i < s.length()-ans+1; i++){
            HashMap map = new HashMap<>();
            for(int j = i; j < s.length(); j++){
                if(map.containsKey(s.charAt(j))){
                    break;
                }
                map.put(s.charAt(j), j);
            }
            ans = Math.max(ans, map.size());
        }
        return ans;
    }
}

方法二:优化

看了看别人的代码,可以从两方面优化一下。

思路如下:

用begin和end记录下标,比如abcabcbb,

begin一开始在a,end也在a,如果哈希表中没有这个元素:那么就加入到哈希表中,哈希表中存放的是元素和下标['a',0],如果有了:那么就把begin更新到这个元素的下一个位置,也就是从哈希表中查找到这个元素的value,value就是下标,加一后就是begin的位置。需要注意,如果是abba的情况,那么最后begin会更新到第一个a的后面,所以需要判断一下更新的begin是不是比当前的begin小。

public int lengthOfLongestSubstring(String s) {
        int ans = 0, begin = 0;
        HashMap map = new HashMap<>();
        for(int end = 0; end < s.length(); end++){
            char ch = s.charAt(end);
            if(map.containsKey(ch)){
                begin = Math.max(begin, map.get(ch) + 1);
                map.put(ch, end);
            }
            else{
                map.put(ch, end);
            }
            ans = Math.max(ans, end-begin+1);
        }
        return ans;
    }
  • s 由英文字母、数字、符号和空格组成

我们再优化一下,因为s的范围很小,ASCII码表示足矣,ASCII码只有128位,所以我们完全可以用数组来替换HashMap。s的每个元素对应的ASCII码就是数组的下标,数组存放的就是该字符在元素中出现的位置。

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int ans = 0, begin = 0;
        int[] map = new int[130];
        for(int i = 0; i < 130; i++) map[i] = -1;
        for(int end = 0; end < s.length(); end++){
            char ch = s.charAt(end);
            if(map[ch] != -1){//出现过
                begin = Math.max(begin, map[ch] + 1);
                map[ch] = end;
            }
            else{
                map[ch] = end;
            }
            ans = Math.max(ans, end-begin+1);
        }
        return ans;
    }
}

3、2300. 咒语和药水的成功对数

好久没做过二分的题了,今天做一道二分的题目练练手,这个题用二分的话思路还是挺简单的。

先给药水(potions)排个序,我们遍历每个咒语(spells),将每个咒语的值在药水(potions)里二分查找,找的是最左侧的元素,因为可能有重复值。

其实就是把二分法搜索最左侧的元素代码改动一下,判断条件是spell*arr[m]>=success就缩小,spell是当前咒语,arr[m]是当前的药水,如果强度足够了(超过success),那么就继续缩小右边界j。

最后返回药水长度-i的值,就是使用当前咒语搭配药水,强度足够的药水的个数。

注意success的类型是long,spell和arr注意也转成long,要不然通过不了。

class Solution {
    public int[] successfulPairs(int[] spells, int[] potions, long success) {
        Arrays.sort(potions);
        int[] ans = new int[spells.length];
        int cnt = 0;
        for(int i = 0; i < spells.length; i++){
            ans[cnt++] = Search(spells[i], potions, success);
        }
        return ans;
    }
    public int Search(int spell, int[] arr, long success){
        int i = 0, j = arr.length-1;
        while(i <= j){
            int m = (i + j) >>> 1;
            if(((long)spell*(long)arr[m]) >= success){//如果大了或者等于,一直缩小,找到最小的
               j = m-1;
            }
            else{
                i = m+1;
            }
        }
        //此时i的值就是最左侧的元素下标了.
        return arr.length-i;
    }
}

容器

TreeSet与TreeMap

TreeSet能排序是因为它可以接收一个比较器对象。

双非本科准备秋招(7)——力扣哈希、二分与容器学习_第1张图片

进入源码,我们发现TreeSet底层原理还是TreeMap,所以TreeSet源码超级短,就是一直调用TreeMap的方法而已。

追踪一下add方法,调用了put方法。

第一个值会走这个逻辑,compare(key, key),注释说了检查一下类型,有可能比较器是null

双非本科准备秋招(7)——力扣哈希、二分与容器学习_第2张图片
再添加一个值就要执行下面这个核心逻辑,cpr就是我们比较器对象,do-while循环查找插入的位置,cmp是当前的key和t的key比较的结果,用这个结果决定添加的位置。

Comparator cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }

关键在于:

return t.setValue(value);

这句话说明当cmp比较的结果为0,那么就不添加,而是替换值,并且返回之前的值。所以treeset里判断是不是重复元素取决于我们重写的compare!

双非本科准备秋招(7)——力扣哈希、二分与容器学习_第3张图片

举个例子,如果把比较规则写成字符串长度相减,那么就只添加一个值,因为添加的长度都是3,compare返回的结果都是0

双非本科准备秋招(7)——力扣哈希、二分与容器学习_第4张图片

Collections工具类

常用方法:

public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add("sss");
        list.add("aa");
        list.add("bbbb");
        list.add("cccc");
        list.add("dddddd");

//        reverse(List) 反转
        Collections.reverse(list);
        System.out.println("reverse " + list);

//        shuffle(List)打乱
        Collections.shuffle(list);
        System.out.println("shuffle " + list);

//        sort(List)自然排序
        Collections.sort(list);
        System.out.println("sort " + list);

//        sort(List, Comparator)
//        Collections.sort(list, new Comparator() {
//            @Override
//            public int compare(Object o1, Object o2) {
//                return ((String)o1).length() - ((String)o2).length();
//            }
//        });
        Collections.sort(list, (o1, o2) -> ((String)o1).length() - ((String)o2).length());
        System.out.println("sort2: " + list);

//        swap下标交换
        Collections.swap(list, 0, 2);
        System.out.println("swap: " + list);

//        max自然顺序的最大元素,也可以传入比较器;min同理,省略不写
        Comparable max = Collections.max(list);
        System.out.println("max自然顺序的最大元素 " + max);
        Object max1 = Collections.max(list, (o1, o2) -> ((String) o1).length() - ((String) o2).length());
        System.out.println("max长度最大元素 " + max1);

//      frequency返回指定元素出现的次数
        Collections.frequency(list, "aa");
        System.out.println("frequency返回指定元素出现的次数 " + max1);

//        copy(List desc, List src) 从src拷贝到desc中(浅拷贝),desc长度必须大于等于src才行
//        需要注意:desc = list并不是拷贝,而是地址指向同一个对象而已。
        ArrayList desc = new ArrayList();
        desc = list;
        Collections.copy(desc, list);
        System.out.println("copy: " + desc);

//        replaceAll(List, OldVal, NewVal)旧值替换
        Collections.replaceAll(list, "aa", "aaaaaaa");
        System.out.println("replaceAll: " + list);

    }

控制台输出:

reverse [dddddd, cccc, bbbb, aa, sss]
shuffle [aa, bbbb, cccc, sss, dddddd]
sort [aa, bbbb, cccc, dddddd, sss]
sort2: [aa, sss, bbbb, cccc, dddddd]
swap: [bbbb, sss, aa, cccc, dddddd]
max自然顺序的最大元素 sss
max长度最大元素 dddddd
frequency返回指定元素出现的次数 dddddd
copy: [bbbb, sss, aa, cccc, dddddd]
replaceAll: [bbbb, sss, aaaaaaa, cccc, dddddd]

你可能感兴趣的:(leetcode,哈希算法,学习,求职招聘,java,intellij-idea,idea)