力扣HOT100题目

主要整理了力扣HOT100相关题目解析。后续有新的实现思路会不断完善。具体原理全部都放到了代码注释中,可整体直接copy到本地运行、理解。

1.  哈希

1.1 两数之和问题

class Solution {
    public int[] twoSum(int[] nums, int target) {
        //固定第一个数字为i进行循环,第二个数字那就是sum-i
        int len = nums.length;
        //将第二个数字存入map集合中,key:sum-i的那个数字;
        //value:如果该key存在于nums数组中,那么就可以将该数字的下标作为该key的value;
        Map<Integer,Integer> ht = new HashMap<Integer,Integer>();
        for(int i =0;i<len;i++){
            int remaining = target-nums[i];
            /**
            判断remaining是否存在于map中,存在的话直接返回即可;
            此时还没有将i及其值放入到map中,所以后续遍历要先不断放入后面的数字;
            一直到想要的数字并且map也存在这个数字的时候才会返回值;
            此时的i代表了第二个数字的索引,因此位于第二位;
            在map中找到想要的那个数字其实是第一位。
             */
            if(ht.containsKey(remaining)){
                return new int[]{ht.get(remaining),i};
            }
            //将i在数组中对应的值作为key,索引i作为value,放入到map集合中
            ht.put(nums[i],i);
        }
        return new int[0];
    }
}

1.2 字母异位词分组

class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        //hashmap存储:key是排序后的唯一字符串,value是可以变换为key的字符串列表
        Map<String, List<String>> map = new HashMap<String, List<String>>();
        for (String str : strs) {
            /**
            遍历排序所有的strs,然后将其排序
            排序的字符串作为key(可保证唯一性)
            而每次获取的array作为value即可*/
            char[] array = str.toCharArray();
            Arrays.sort(array);
            String key = new String(array);
            //创建列表,将map中具有相同key的字符串放到一个列表中,
            //不存在的话创建新的空列表(这表示一种新的字母异位词分组形式)
            List<String> list = map.getOrDefault(key, new ArrayList<String>());
            //将这个str添加到对应的那个list中
            list.add(str);
            //map中此时将排序后唯一的字符串视为key;
            //可以还原成key的字符串存储到了list中,将这个list作为hashmap的值
            map.put(key, list);
        }
        //通过.values()方法获取所有值的集合
        return new ArrayList<>(map.values());
    }
}

1.3 最长连续序列

class Solution {
    public int longestConsecutive(int[] nums) {
        //转换成hashset存储nums中的所有数据
        //hashset过滤掉重复元素,可直接符合数字连续的要求
        HashSet<Integer> set = new HashSet<Integer>();
        for (int num : nums) {
            set.add(num);
        }
        //先定义最小长度是0(有空数组)
        int sublen = 0;
        //对于hashset中的每一个num进行判断
        for (int num : set) {
            /**判断hashset中是否存在num-1这个数字,
            true,表明现在这个num不是最小的起始点,直接跳过对该num的操作;
            等于fasle表明他可能是最小的,需要下一步判断
            注意这里无法判断全局最小,如果要全局最小,
            需要在这里嵌套一个for循环遍历整个set,复杂度变高*/
            if (set.contains(num - 1)) {
                continue;
            }
            //先定义此时这个for循环中,临时子序列的长度以及临时数字的大小
            int forlen = 1;
            int fornum = num;
            /**
            判断hashset中是否包含比此时的临时数字要大1的数字,
            有的话表明连上了,继续while到下一个数字;
            直到连不上数字时,此时就存储了以num为起始点,
            连续子序列的最大值fornum,以及最长的长度值forlen*/
            while (set.contains(fornum + 1)) {
                fornum += 1;
                forlen += 1;
            }
            //此前获取到的最长长度   和   该循环中的num得到的最长长度值,取max即可
            sublen = Math.max(sublen, forlen);
        }
        return sublen;
    }
}

2. 双指针(快慢指针)解决问题

2.1 移动target元素

class Solution {
    public void moveZeroes(int[] nums) {
        //核心:slowIndex用于存储不为val的索引,fastIndex往后找不为val的位置
        int val = 0;
        int len = nums.length;
        //慢指针,每一个slowIndex的位置都代表了将要存储的不为val的数据的索引
        int slowIndex = 0;
        //快指针遍历整个nums数组
        for (int fastIndex = 0; fastIndex < len; fastIndex++) {
            //快指针用于寻找nums中不等于val的数字索引位置
            //当找到不为val的位置时,将此时slowIndex处于的位置赋值为找到的值
            if (nums[fastIndex] != val) {
                nums[slowIndex] = nums[fastIndex];
                //找到后,相当于slowIndex这个位置已经存在新的数据了,继续定位下一个位置即可。
                slowIndex++;
            }
        }
        /**for循环后,代表了已经重新将所有不为val的数据放到了最前面,
        并且此时slowIndex指向了前面数据的下一个位置,将数组后面填充val即可*/
        while (slowIndex < len) {
            nums[slowIndex] = val;
            slowIndex++;
        }
    }
}

2.2 三数之和

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<List<Integer>>();
        int len = nums.length;
        Arrays.sort(nums);
        for (int first = 0; first < len; first++) {
            //当最小的fisrt都大于0的时候,就没必要进行了
            if (nums[first] > 0) {
                break;
            }
            //如果first和前面的值重复了,需要跳过
            if (first > 0 && nums[first] == nums[first - 1]) {
                continue;
            }
            /**
            维护第二个数字和第三个数字
            类似于双指针的形式,来指定范围 */
            int second = first + 1, third = len - 1;
            while (second < third) {
                int sum = nums[first] + nums[second] + nums[third];
                if (sum == 0) {
                    //满足要求时,需要将f,s,t存入到列表中
                    List<Integer> list = new ArrayList<Integer>();
                    list.add(nums[first]);
                    list.add(nums[second]);
                    list.add(nums[third]);
                    //将该列表存入到最终结果集合中
                    result.add(list);
                    second++;
                    third--;
                    //second重复时跳过  后面  重复的second继续往后
                    while (second < third && nums[second] == nums[second - 1]) {
                        second++;
                    }
                    //third重复同样要跳过  前面  重复的third继续往前
                    while (second < third && nums[third] == nums[third + 1]) {
                        third--;
                    }
                } else if (sum > 0) {
                    //sum过大,需要缩小最大值third
                    third--;
                } else {
                    //sum过小,需要缩小最小值second
                    second++;
                }
            }
        }
        return result;
    }
}

2.3 盛水最多的容器

class Solution {
    public int maxArea(int[] height) {
        //使用左右双指针的方法,逐步缩小左右区间范围,在这个过程中不断更新maxarea
        int left = 0;
        int right = height.length - 1;
        int maxarea = 0;
        while (left < right) {
            //取最矮的作为容器高度
            int minheight = Math.min(height[left], height[right]);
            //计算此时区间面积
            int currArea = minheight * (right - left);
            //比较此时的区间面积,已获取到的最大面积,取二者最大值
            maxarea = Math.max(maxarea, currArea);
            //需要更新影响面积的索引(最矮的那个位置的索引需要移动)
            if (height[left] < height[right]) {
                left++;
            } else {
                right--;
            }
        }
        return maxarea;
    }
}

2.4 接雨水问题

3. 滑动窗口

3.1 无重复字符的最长子串

class Solution {
    public int lengthOfLongestSubstring(String s) {
        /**
        左右指针表示窗口边界,不同于[0,len]开始缩小
        这个窗口大小是从0开始扩张,然后收缩
        有不重复的话右指针扩展边界,重复的话左指针缩小窗口边界*/
        Map<Character, Integer> window = new HashMap<>();
        int left = 0, right = 0, len = s.length();
        int result = 0;
        //需要使用右指针逐渐向右侧移动遍历整个字符串
        while (right < len) {
            //记录当前右指针对应的字符的值
            char right_bound_char = s.charAt(right);
            right++;
            /**
            将该值记录到map中,key是该字符,value:
            如果能get到那么获取在map中该字符真实的出现次数;
            否则默认是第一次出现,记录为0+1.*/
            window.put(right_bound_char, window.getOrDefault(right_bound_char, 0) + 1);
            /**
            当这个窗口中最新的字符存在重复时
            那么需要缩小窗口直至最新get到的字符的value不大于1
            也就是一直缩小到每个字符最多一次时的窗口才能符合要求*/
            while (window.get(right_bound_char) > 1) {
                //获取左边界的字符
                char left_bound_char = s.charAt(left);
                left++;
                //将左边界字符的value值进行更新,为get-1
                window.put(left_bound_char, window.get(left_bound_char) - 1);
            }
            //result记录的是不断更新的最新长度;
            //right - left表示的是当前循环所能达到的最长长度
            result = Math.max(result, right - left);
        }
        return result;
    }
}

3.2 找到字符串的字母异位词

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        Map<Character, Integer> window = new HashMap<>();
        Map<Character, Integer> need = new HashMap<>();
        //将子串p的所有字符以及各字符的数量存入到need中
        for (char c : p.toCharArray()) {
            need.put(c, need.getOrDefault(c, 0) + 1);
        }
        int left = 0, right = 0;
        int valid = 0;
        //列表存储符合要求的子串起始索引
        List<Integer> resultList = new ArrayList<>();
        while (right < s.length()) {
            char right_bound_char = s.charAt(right);
            right++;
            //当need中存在这个字符的时候,需要将其添加到window集合中
            if (need.containsKey(right_bound_char)) {
                window.put(right_bound_char, window.getOrDefault(right_bound_char, 0) + 1);
                //当该字符在window和need两个map中的数量一样时,
                //表明满足了这个字符以及数量限制的要求
                if (window.get(right_bound_char).equals(need.get(right_bound_char))) {
                    valid++;
                }
            }
            //确保窗口长度不超过目标字符串p的长度,在窗口内字符频率达标的时候及时记录结果
            while (right - left >= p.length()) {
                //当通过上面得到的valid正好也是need长度的时候,表示完全符合条件,
                //将其索引存入到list中
                if (valid == need.size()) {
                    resultList.add(left);
                }
                //通过left缩小左边界
                char left_bound_char = s.charAt(left);
                left++;
                //need中包含这个字符时
                if (need.containsKey(left_bound_char)) {
                    //need需要这个字符,但是窗口左边界又要右移的时候,
                    //如果之前恰好满足了数量的条件,此时移动了,必然使valid的数量-1
                    if (window.get(left_bound_char).equals(need.get(left_bound_char))) {
                        valid--;
                    }
                    //更新这个字符在window中的数量,value-1
                    window.put(left_bound_char, window.get(left_bound_char) - 1);
                }
            }
        }
        return resultList;
    }
}

3.3 最小覆盖子串问题

class Solution {
    public String minWindow(String s, String t) {
        //核心:窗口增加和减少的时机判断
        //window需要先扩大,直至满足子串的要求
        //随后针对大范围的window进行缩小,使其满足最终的要求
        Map<Character, Integer> window = new HashMap<>();
        Map<Character, Integer> need = new HashMap<>();
        for (char c : t.toCharArray()) {
            need.put(c, need.getOrDefault(c, 0) + 1);
        }
        int left = 0, right = 0;
        //当valid==need.size()的时候表示全部拿到了所需的字符
        int valid = 0;
        //start记录开始位置,len表示子串长度,那么(s,s+l)就是最终子串
        //注意len设置,需要在循环中对其进行更新,便于最终返回时的判断
        int start = 0, len = Integer.MAX_VALUE;
        while (right < s.length()) {
            //得到每个字符
            char right_bound_char = s.charAt(right);
            right++;
            //判断need中是否存在这个字符,需要的话put这个字符,并使valid+1
            if (need.containsKey(right_bound_char)) {
                window.put(right_bound_char, window.getOrDefault(right_bound_char, 0) + 1);
                if (window.get(right_bound_char).equals(need.get(right_bound_char))) {
                    valid++;
                }
            }
            //当满足valid==need.size()时候,就需要精细化字符范围,缩小窗口长度
            while (valid == need.size()) {
                //更新最小覆盖子串的位置:从start开始到start+len的范围就是最小子串
                //要保证这次的窗口长度需要小于原来的长度,继续缩小
                if (right - left < len) {
                    start = left;
                    len = right - left;
                }
                //从左侧开始循环,判断需要移除的字符
                char left_bound_char = s.charAt(left);
                left++;
                //当need中包含这个字符,需要移除这个字符
                if (need.containsKey(left_bound_char)) {
                    //判断:如果此时window中该字符数量和need需要的该字符数量一致的时候,
                    //如果移除了该字符必然会导致valid-1;
                    if (window.get(left_bound_char).equals(need.get(left_bound_char))) {
                        valid--;
                    }
                    window.put(left_bound_char, window.get(left_bound_char) - 1);
                }
            }
        }
        //整体循环结束后,返回最小覆盖子串
        //len之前设置为maxvalue,如果没有在循环中更新那就表明不存在这样的子串返回""",
        //否则返回范围(s,s+l)
        return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
    }
}

4. 子串

**总结**

如果觉得对你有帮助的话,可以收藏起来方便后续浏览;或者您有什么建议的话,可以在评论区交流,后续我也会不断维护。谢谢!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值