解题小技巧(哈希,双指针,前缀和,二分搜索,单调栈,单调队列,滑动窗口)

前言

人还是太容易懒散,工作稍微一饱和,小说短视频一看,几个好友一浪,什么学习计划和目标都抛诸脑后了。今天总算是水到400题了,趁着热度还在,赶紧总结一下几个解题技巧吧。
在这里插入图片描述

1 哈希

Hash算法在一些题中,可以将暴力法时间复杂度为平方级O(n^2)降到线性级O(n),因为Hash算法的基本操作put,get,contains等都是O(1)的。Java中可以使用HashMap或者HashSet来达到目的。

经典的简单题两数之和的解法,只用最多一次循环就得到答案
class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer,Integer> map=new HashMap<>(nums.length*2);
        for(int i=0;i<nums.length;i++){ 
            if(map.containsKey(target-nums[i])){
                return new int[]{map.get(target-nums[i]),i};
            }
            map.put(nums[i],i);
        }
        return new int[]{};
    }
}
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。

示例:
输入: ["eat", "tea", "tan", "ate", "nat", "bat"]
输出:
[
  ["ate","eat","tea"],
  ["nat","tan"],
  ["bat"]
]
说明:

所有输入均为小写字母。
不考虑答案输出的顺序。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/group-anagrams
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这个题用HashSet来判断重复
class Solution {
     public List<String> findRepeatedDnaSequences(String s) {
        Set<String> set=new HashSet<>();
        Set<String> res=new HashSet<>();
        int left=0;
        int right=10;
        while(right<=s.length()){
            String sub=s.substring(left,right);
            if(set.contains(sub)){
                res.add(sub);
            }
            set.add(sub);
            left++;
            right++;
        }

        return new ArrayList<>(res);
    }
}
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

示例 1:
输入: s = "anagram", t = "nagaram"
输出: true
示例 2:
输入: s = "rat", t = "car"
输出: false
说明:
你可以假设字符串只包含小写字母。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/valid-anagram
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这个题中,因为只有小写字母,所有直接用一个26长度的数组来做映射,其实也是一个简版Hash算法
class Solution {
    public boolean isAnagram(String s, String t) {
        if(s.length()!=t.length()){
            return false;
        }
        int[] map=new int[26];
        for(int i=0;i<s.length();i++){
            map[s.charAt(i)-'a']++;
        }
        boolean flag=true;
        for(int i=0;i<t.length();i++){
            int v = map[t.charAt(i)-'a']++;
            v--;
            if(v<0){
                return false;
            }
            map[t.charAt(i)-'a']=v;
        }
        return flag;
    }
}

2双指针

双指针是使用两个指针同时来处理,有时候一个指针从头开始,一个指针从尾部开始,从中间靠拢,有时候一个快一个慢,又叫快慢指针,二分搜索,滑动窗口也需要用到双指针,只是侧重点不同。

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
说明:本题中,我们将空字符串定义为有效的回文串。

示例 1:
输入: "A man, a plan, a canal: Panama"
输出: true
示例 2:
输入: "race a car"
输出: false

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/valid-palindrome
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这个解法是一个指针从头开始,一个指针从尾部开始,不断靠拢,如果能熬到循环结束,说明是回文
class Solution {
    public boolean isPalindrome(String s) {
        if(s.length()==0||s.length()==1){
            return true;
        }
        int l=0;
        int r=s.length()-1;
        while(l<r){
            if(!(isChar(s.charAt(l))||isNumber(s.charAt(l)))){
                l++;
                continue;
            }
            if(!(isChar(s.charAt(r))||isNumber(s.charAt(r)))){
                r--;
                continue;
            }
            int cha = Math.abs(s.charAt(l)-s.charAt(r));
            if(cha==0 || (cha==('a'-'A') && isChar(s.charAt(l))&&isChar(s.charAt(r)) )){
                 l++;r--;
                 continue;
            }
            return false;
        }
        return true;
    }
    public boolean isChar(char c){
        return (c>='a'&&c<='z') ||(c>='A'&&c<='Z'); 
    }
     public boolean isNumber(char c){
        return c>='0'&&c<='9'; 
    }
}
实现一种算法,找出单向链表中倒数第 k 个节点。返回该节点的值。

注意:本题相对原题稍作改动

示例:
输入: 1->2->3->4->5 和 k = 2
输出: 4

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/kth-node-from-end-of-list-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这里fast先移动k个位置,然后slow指针从头开始移动,等fast到最后了,slow就在倒数k的位置了,比起要反转链表是不是减少了额外的操作
class Solution {
    public int kthToLast(ListNode head, int k) {
        int fast,slow=0;
        ListNode pre=head;
        while(k>0){
            k--;
            head=head.next;
        }
        while(null!=head){
            pre=pre.next;
            head=head.next;
        }
        return pre.val;
    }
}

3二分搜索

二分搜索,使用双指针的技巧,比对中间值的大小,然后缩小边界,可以将O(n)的时间复杂度降低到O(logn),极大的加快检索的速度,二叉搜索数也是这个道理。

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
你可以假设数组中无重复元素。

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/search-insert-position
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

二分搜索查询插入位置
class Solution {
    public int searchInsert(int[] nums, int target) {
        int l=0,r=nums.length-1,res=nums.length;
        while(l<=r){
            int m=(l+r)>>1;
            if(nums[m]==target){
                return m;
            }else if(nums[m]>target){
                res=m;
                r=m-1;
            }else if(nums[m]<target){
                l=m+1;
            }
        }
        return res;
    }
}

4前缀和

前缀和是对数组的每个index预处理与之前的所有元素的和,一般可以用来处理连续字串问题。

给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。

示例 1 :
输入:nums = [1,1,1], k = 2
输出: 2 , [1,1][1,1] 为两种不同的情况。
说明 :
数组的长度为 [1, 20,000]。
数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/subarray-sum-equals-k
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这个题解法,用HashMap保持每个位置的前缀和,
class Solution {
    public int subarraySum(int[] nums, int k) {
        Map<Integer,Integer> map = new HashMap<>();
        int res=0;
        int count=0;
        map.put(0,1);
        for(int i=0;i<nums.length;i++){
            count+=nums[i];
            // System.out.println(map);
            if(map.containsKey(count-k)){
                res+=map.get(count-k);
            }
            map.put(count,map.getOrDefault(count,0)+1);
        }
        return res;
    }
}

5单调栈

单调栈是遍历时同时,维持一个单调递增或者单调递减的先进后出栈结果,当不再能够维持这个性质时候,不断出栈,直到又可以维持单调性为止,依赖这个特性,一般可以来处理下一个更大值问题。

给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。
请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。
nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。

示例 1:
输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]
解释:
    对于 num1 中的数字 4 ,你无法在第二个数组中找到下一个更大的数字,因此输出 -1 。
    对于 num1 中的数字 1 ,第二个数组中数字1右边的下一个较大数字是 3 。
    对于 num1 中的数字 2 ,第二个数组中没有下一个更大的数字,因此输出 -1 。
示例 2:
输入: nums1 = [2,4], nums2 = [1,2,3,4].
输出: [3,-1]
解释:
    对于 num1 中的数字 2 ,第二个数组中的下一个较大数字是 3 。
    对于 num1 中的数字 4 ,第二个数组中没有下一个更大的数字,因此输出 -1 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/next-greater-element-i
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
class Solution {
    public int[] nextGreaterElement(int[] nums1, int[] nums2) {
        Map<Integer,Integer> map = new HashMap<>();
        Deque<Integer> stack=new LinkedList<>();
        
        int cur=nums2[0];
        for(int i=0;i<nums2.length;i++){
            while(!stack.isEmpty()&&nums2[i]>stack.peek()){
                map.put(stack.pop(),nums2[i]);
            }
            stack.push(nums2[i]);
        }
        int[] res=new int[nums1.length];
        for(int i=0;i<nums1.length;i++){
            res[i]=null==map.get(nums1[i])?-1:map.get(nums1[i]);
        }
        return res;
    }
}

6单调队列

单调队列则类似,维持一个单调递增或者单调递减的先进后出栈结果,当不再能够维持这个性质时候,不断出队,直到又可以维持单调性为止,依赖这个特性,可以处理一定范围内的最值问题。

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。

示例 1:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7
示例 2:
输入:nums = [1], k = 1
输出:[1]
示例 3:
输入:nums = [1,-1], k = 1
输出:[1,-1]
示例 4:
输入:nums = [9,11], k = 2
输出:[11]
示例 5:
输入:nums = [4,-2], k = 2
输出:[4]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sliding-window-maximum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

窗口之内的最大值,可以用单调队列实现
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        Deque<Integer> queue=new LinkedList<>();
        int[] res=new int[nums.length-k+1];
        int left=0,right=0,resIndex=0;
        while(right<nums.length){
            int cur=nums[right];
            right++;
            while(!queue.isEmpty()&&queue.peekLast()<cur){
                queue.pollLast();
            }
            queue.offer(cur);
            if(right-left==k){
                // System.out.println(queue);
                res[left]=queue.peek();
                int leftCur=nums[left];
                if(queue.peek()==leftCur){
                    queue.poll();
                }
                left++;
            }
        }
        return res;
    }
}

7滑动窗口

滑动窗口算法,使用两个指针一前一后,当窗口未达到条件是,需要扩大窗口,当窗口达到条件,这时候需要一边缩小窗口,一边处理结果,但又不能达到条件时,需要跳出来接着扩大窗口。一般可以用来处理一些

给你一个字符串 s 和一个整数 k ,请你找出 s 中的最长子串, 要求该子串中的每一字符出现次数都不少于 k 。返回这一子串的长度。

示例 1:
输入:s = "aaabb", k = 3
输出:3
解释:最长子串为 "aaa" ,其中 'a' 重复了 3 次。
示例 2:
输入:s = "ababbc", k = 2
输出:5
解释:最长子串为 "ababb" ,其中 'a' 重复了 2 次, 'b' 重复了 3 次。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-substring-with-at-least-k-repeating-characters
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

class Solution {
    public int longestSubstring(String s, int k) {
        int maxlength=0;   
        for(int i=1;i<=26;i++){
            int[] windowMap=new int[26];
            int total=0,less=0;
            int left=0,right=0;
            if(i>s.length()){
                continue;
            }
            while(right<s.length()){
                char cur=s.charAt(right);
                right++;            
                windowMap[cur-'a']++;
                if(windowMap[cur-'a']==1){
                    total++;
                    less++;
                }
                if(windowMap[cur-'a']==k){
                    less--;
                }
                while(total>i){
                    int leftCur=s.charAt(left);
                    windowMap[leftCur-'a']--;
                    if(windowMap[leftCur-'a']==k-1){
                        less++;
                    }
                    if(windowMap[leftCur-'a']==0){
                        less--;
                        total--;
                    }
                    left++;
                }

                if(less==0){
                    maxlength=Math.max(maxlength,right-left);
                }
            }
        }
        return maxlength;
    }
}
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:
输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
示例 4:
输入: s = ""
输出: 0

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。


class Solution {
    public int lengthOfLongestSubstring(String s) {
        Map<Character,Integer> map = new HashMap<>(1024);
        int left=0,right=0;
        int maxLength=0;
        while(right<s.length()){
            char cur=s.charAt(right);
            right++;
            if(map.containsKey(cur)){
                left=Math.max(left,map.get(cur));
            }
            maxLength=Math.max(maxLength,right-left);
            map.put(cur,right);
        }
        return maxLength;
    }
}

结语

这些初级的小技巧,就可以大大降低问题时间复杂度,不禁让人惊喜,更何况那些高级的算法,为社会的发展做出了巨大贡献,这就是算法吸引人之处吧。此文涉及的题解时间跨度较大,描述不是太详细,因为有些我也记不起来了,而且写一篇文章相当耗费精力。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值