力扣hot100道算法解题技巧+算法心得(一)

一、哈希

解题思路

将相同的字母或者事物映射到同一个槽位进行计数,或者使用空间存放已经遍历过的相同的字母或者事物,例如HashMap等。

1、字母异位词分组

1)关键解题步骤
无非就是将获取的字符串进行排序,排序后的字符串都是一样的,然后以此为键,值是对应存放的集合。

2)代码

  public List<List<String>> groupAnagrams(String[] strs) {
        HashMap<String, List<String>> hm = new HashMap<>();
        for (String str : strs) {
            char[] chars = str.toCharArray();
            Arrays.sort(chars);
            String key = new String(chars);
            List<String> list = hm.getOrDefault(key, new ArrayList<>());
            list.add(str);
            hm.put(key, list);
        }
        return new ArrayList<>(hm.values());
    }

2、最长连续序列

1)解题关键思路
就是首先利用Set进行去重同时方便后续判断是否存在某值,与此同时判断当前遍历的元素是否存在小于它的连续值,如果不存在,则当前值为最长连续子序列的起始值,如果存在,说明之前已经计算过最大值了无需再计算。

2)代码

 public int longestConsecutive(int[] nums) {
        int res = 0;    // 记录最长连续序列的长度
        Set<Integer> numSet = new HashSet<>();  // 记录所有的数值
        for(int num: nums){
            numSet.add(num);    // 将数组中的值加入哈希表中
        }
        int seqLen;     // 连续序列的长度
        for(int num: numSet){
             // 如果集合中不包含当前整数减一的值,说明当前整数可能是一个连续序列的起点,如果包含
             // 说明就是已经存在过了
            if(!numSet.contains(num - 1)){
                seqLen = 1;
                while(numSet.contains(++num))seqLen++;  // 不断查找连续序列,直到num的下一个数不存在于数组中
                res = Math.max(res, seqLen);    // 更新最长连续序列长度
            }
        }
        return res;
    }

二、双指针

1、盛最多水的容器

1)解题思路
寻找两个元素构成的最大面积就是结果,那如何寻找结果呢?
使用双指针思路,定义起始左右指针。
若向内 移动短板 ,应该移动哪个短板才可能使得面积变大呢?

  1. 如果h[l] >= h[r] 那么必须 r 向左移动才有可能增大面积,因为l最大板块向右移动得到的面积一定会变小,因为面积计算的高度是h[r]不变或者面积的高度变小,宽度减少,最终面积一定会变小。
  2. 第二种情况就和第一种相反。

2)代码

 public int maxArea(int[] height) {
    int max = 0;
        int l = 0;
        int r = height.length - 1;
        while (r > l) {
            max = Math.max(Math.min(height[l], height[r]) * (r - l),max);
            if (height[l] <= height[r]) {
                l++;
            } else {
                r--;
            }
        }
        return max;
    }

2、三数之和

1)解题关键
理解双指针,首先进行排序!!!,这是后续使用双指针的关键解题核心,后面就是>0 r 左移,< 0 l 右移。

2)代码

 public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(nums);
        int length = nums.length - 1;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] > 0) continue;
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int l = i + 1;
            int r = length;
            while (r > l) {
                int sum = nums[i] + nums[r] + nums[l];
                if (sum > 0) {
                    r--;
                } else if (sum < 0) {
                    l++;
                } else {
                    res.add(Arrays.asList(nums[i], nums[r], nums[l]));
                    // 进行去重
                    while (r > l && nums[l] == nums[l + 1]) {
                        l++;
                    }
                    while (r > l && nums[r] == nums[r - 1]) {
                        r--;
                    }
                    l++;
                    r--;
                }
            }
        }
        return res;
    }

3、接雨水

1)关键解题思路
解法有:1 双指针 。2 单调栈,递增栈。本题使用单调栈,具体单调栈方法看其他内容单调栈相关内容,此节不做解释。

2)代码

 public int trap(int[] height) {
        int length = height.length;
        int res = 0;
        Stack<Integer> stack = new Stack<>();
        stack.push(0);
        for (int i = 1; i < length; i++) {
            while (!stack.isEmpty() && height[i] > height[stack.peek()]) {
                int mid = height[stack.pop()];
                if (!stack.isEmpty()) {
                    int right = height[i];
                    int left = height[stack.peek()];
                    int h = (Math.min(right, left) - mid) * (i - stack.peek() - 1);
                    res += h;
                }
            }
            stack.push(i);
        }
        return res;
    }

三、滑动窗口

1、无重复字符的最长子串

1)解题思路
运用滑动窗口,将无重复的一直遍历盖住,并用HashMap存储对应的字母所在的索引位置,如果重复,直接获取该重复字母的位置+1,从而移除了该字母的位置。
2)代码

 public int lengthOfLongestSubstring(String s) {
        int len = s.length();
        int res = 0;
        // 代表的是不重复字母的起始索引位置
        int l = 0;
        // 键:字母,值:字母最后出现的一次索引
        HashMap<Character, Integer> hm = new HashMap<>();
        for (int i = 0; i < len; i++) {
            char c = s.charAt(i);
            if (hm.containsKey(c)) {
                // hm.get(c) + 1:因为子串中包含c,故需要将c排除出去
                // 先找到c下标,然后+1就将c排除出去了
                l = Math.max(l, hm.get(c) + 1);
            }
            hm.put(c, i);
            res = Math.max(res,i-l+1);
        }
        return res;
    }

2、找到字符串中所有字母的异位词

1)解题思路
关键点只需要返回起始索引。而且只有一个字符串,那么就是直接使用滑动窗口,在s窗口中cnt<0时候说明字符在s中多了,就需要减少。

2)代码

  public List<Integer> findAnagrams(String s, String p) {
        int sLen = s.length();
        int pLen = p.length();
        int[] cnt = new int[26];
        // 字母异位词的起始索引
        int l = 0;
        List<Integer> result = new ArrayList<>();
        // 进行哈希映射
        for (int i = 0; i < pLen; i++) {
            cnt[p.charAt(i) - 'a']++;
        }
        for (int i = 0; i < sLen; i++) {
            char c = s.charAt(i);
            cnt[c - 'a']--;
            // 说明s窗口里面这个字母多了,需要减少,从左往右减少
            while (cnt[c - 'a'] < 0) {
                cnt[s.charAt(l++)-'a']++;
            }
            if (i - l + 1 == pLen) {
                result.add(l);
            }
        }
        return result;
    }

四、子串

1、和为K的子数组

1)关键解题思路
在这里插入图片描述
利用HashMap存放对应的前缀和索引,然后如果存在 sum - k的前缀和索引存在,那么区间[j,i]就是前缀和子数组。

2)代码

   public int subarraySum(int[] nums, int k) {
         int resCount = 0;
        // 键代表前缀和,值代表出现和的次数
        HashMap<Integer, Integer> hm = new HashMap<>();
        hm.put(0,1);
        int preNum = 0;
        for (int num : nums) {
            // 获取到preNum值
            preNum += num;

            if (hm.containsKey(preNum - k)) {
                resCount += hm.get(preNum - k);
            }
            // hm.getOrDefault(preNum, 0) + 1:用来获取前缀和为preNum的个数。
            hm.put(preNum, hm.getOrDefault(preNum, 0) + 1);
        }
        return resCount;  
    }

2、滑动窗口最大值

1)关键思路
利用单调队列,而不是单调栈,因为如果使用栈,栈里面只能维护一个最大元素不好操作,单调队列是递降的,始终维护队列头顶元素是最大值,队头元素后面始终是单调递减的,注意这里add() 和 push() 的区别add是队列的,push是栈的方法。

2)代码

class Solution {
  public int[] maxSlidingWindow(int[] nums, int k) {
        int[] maxRes = new int[nums.length-k+1];
        MyDeque MyDeque = new MyDeque();
        for (int i = 0; i < k; i++) {
            MyDeque.push(nums[i]); 
        }
        int start = 0;
           maxRes[start] = MyDeque.getMax();
        for (int i = k; i < nums.length; i++) {
            MyDeque.pop(nums[i-k]);
            MyDeque.push(nums[i]);
            maxRes[++start] = MyDeque.getMax();
        }
        return maxRes;
    }
}
class MyDeque{
    Deque<Integer> deque = new LinkedList<>();

    void pop(int val) {
        if (!deque.isEmpty()&& val == deque.peek()) {
            deque.pop();
        }
    }

    void push(int val){
        while (!deque.isEmpty() &&deque.getLast() < val) {
         deque.removeLast();
        }
        deque.add(val);
    }
    int getMax(){
      return   deque.peek();
    }
}

3、最小覆盖子串

1)关键解题思路
利用滑动窗口,以及cnt哈希表映射始终维护着一个窗口,但是与此不同的是,这个窗口只需要最小包含有 t 字符串即可,不需要严格要求滑动窗口只能有t子串,所以什么时候达到 s 的子串全包含 t 呢?需要设置一个 less 作为标记,当 less == 0时候说明 s 遍历得到的子串刚好全部包含了 t,此时进行排除筛选寻找该段符合条件的最小长度的子串,使用 left 进行遍历排除,达到最小值后替换 ansLeft ,ansRight 得到最小符合子串的索引下标,然后继续搜索下段子串,直到遍历结束。

2)代码

  public String minWindow(String S, String t) {
        char[] s = S.toCharArray();
        int m = s.length;
        // 最小包含的长度
        int min = m;
        // 最小包含字母的起始索引
        int ansLeft = -1;
        int ansRight = 0;
        int[] cnt = new int[128];
        // 表示是否全部包含了t的标志变量
        int less = 0;
        for (char c : t.toCharArray()) {
            if (cnt[c ] == 0) {
                less++; // 有 less 种字母的出现次数 < t 中的字母出现次数
            }
            cnt[c]++;
        }
        // 用来寻找最小子串长度的
        int left = 0;
        for (int i = 0; i < m; i++) {
            char c = s[i];
            cnt[c]--;
            if (cnt[c] == 0) {
                less--;
            }
            // 当less == 0 表示s的子串已经全部包含了t
            while (less == 0) {
                // 开始排除
                cnt[s[left]]++;
                // 说明排除到包含t的字符了
                if (cnt[s[left]] > 0) {
                    int len = i - left + 1;
                    if (min >= len) {
                        min = len;
                        ansLeft = left;
                        ansRight = i;
                    }
                    less++;
                }
                left++;
            }
        }
        return ansLeft == -1?"":S.substring(ansLeft, ansRight + 1);
    }

五、普通数组

1、合并区间

1)关键解题思路
关键就是用什么来存放遍历后得到的不重复的区间,以便用来存放数组,所以最关键点就是 LinkedList<int[]> list = new LinkedList<>(); 这个用来存放int[ ]值

2)代码

public int[][] merge(int[][] intervals) {
        // 对左区间进行排序
        Arrays.sort(intervals,(o1,o2) ->o1[0] - o2[0]
        );
       // 此时想到返回的是一个int[][],需要进行存储
        // 凭经验用list存 int[0]:存放左边界, int[1]:存放右边界
        LinkedList<int[]> list = new LinkedList<>();
        list.add(new int[]{intervals[0][0], intervals[0][1]});
        for (int i = 1; i < intervals.length; i++) {
            // 区间重叠
               int[] preList = list.getLast();
            if ( preList[1] >= intervals[i][0]) {
                list.removeLast();
                int end = Math.max(preList[1],intervals[i][1]);
                list.add(new int[]{preList[0], end});
            }
            else{
                list.add(new int[]{intervals[i][0], intervals[i][1]});

            }
        }
        return list.toArray(new int[list.size()][]);
    }

2、 轮转数组

1)关键思路
所轮转的关键思路:无非就是结尾的元素跑到前头,前头的元素跑到后头,故而第一步先反转整个数组,然后再对前k个元素进行反转,后n-k个元素反转即可得到结果。

2)代码

 public void rotate(int[] nums, int k) {
        int n = nums.length;
        k %= n;
        // 反转整个数组
        reverse(nums, 0, n - 1);
        reverse(nums, 0, k - 1);
        reverse(nums, k, n - 1);
    }

    private void reverse(int[] nums, int start, int end) {
        while (start < end) {
            int temp = nums[start];
            nums[start] = nums[end];
            nums[end] = temp;
            start++;
            end--;
        }
    }

3、除自身以外数组的乘积

1)关键思路
为了求除了自身的乘积之和,可以进行分组先进行求左边数组之乘积然后再进行右边的乘积,最终求得结果。

2)代码

        public int[] productExceptSelf(int[] nums) {
        int length = nums.length;
        int[] answer = new int[length];
        // 拆分左右乘法
        // answer[i]:表示i左边的累乘积之和
        answer[0] = 1;
       // 求左边乘积 利用answer存放之前的乘积
        for (int i = 1; i < nums.length; i++) {
            answer[i] = nums[i - 1] * answer[i - 1];
        }
        int R = 1;
        for (int i = length-1; i >= 0; i--) {
            answer[i] *= R;
            R *= nums[i];
        }
        return answer;
    }

4、缺失的第一个正数

1)关键思路
主要关键点就是:将数组按 [1,2,3,4,6] 这样分,如果出现这种 [1,2,3,7,5] 一看就知道7位置不对,所以缺少的是4,总结来说就是i索引,存储的数据是i+1。
2)代码

 // 找出缺失的第一个正整数
    public int firstMissingPositive(int[] nums) {
        int n = nums.length;
        // 关键点就是对应数组的位置如果正确就是最新正整数,
        // 比如数组索引1,应该存放的是数字2如果不存在那么就是最小的
        // 其中 如果调整好后,nums[i] = i+1
        for (int i = 0; i < n; i++) {
            // 开始调整
            while (nums[i] > 0 && nums[i] <= n && nums[i] != nums[nums[i] - 1]) {
                // 为什么不能先 int temp = nums[i]先?
                // 因为后续使用的nums[nums[i]-1] = temp需要用到了num[i]
                int temp = nums[nums[i] - 1];
                nums[nums[i] - 1] = nums[i];
                nums[i] = temp;
            }

        }
        // 经过调整后
        for (int i = 0; i < n; i++) {
            if (nums[i] != i + 1) {
                return i + 1;
            }
        }
        return n + 1;
    }

六、矩阵

1、矩阵置零

1)关键思路

无非就是找出该0的行,列,然后将符合行和列的全部设置为0,用两个标记变量col [ ] row [ ],进行标记,然后遍历到是该行或者该列的就直接设置成0,之前我想到的是使用两个变量 int row = 0; int col = 0;但是如果有多个0,就不好解决。

2)代码

   public void setZeroes(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        boolean[] row = new boolean[m];
        boolean[] clo = new boolean[n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == 0) {
                    row[i] = true;
                    clo[j] = true;
                }
            }
        }
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (row[i] || clo[j]) {
                    matrix[i][j] = 0;
                }
            }
        }

    }

2、螺旋矩阵

1)关键思路
螺旋顺时针,可知先遍历top层然后right层
再bottom层,再left层可得结果,故需要进行循环遍历四次,然后根据是否遍历过得出是否退出条件。

2)关键解题思路

  List<Integer> list = new LinkedList<>();
        int bottom = matrix.length-1;
        int right = matrix[0].length-1;
        int top = 0;
        int left = 0;
        while (true) {

            //右移
            for (int i = left; i <= right; i++) {
                list.add(matrix[top][i]);
            }
            if (++left > right) break;

            // 下移
            for (int i = top; i <= bottom; i++) {
                list.add(matrix[i][right]);
            }
            if (++top > bottom) break;

            // 左移
            for (int i = right; i >= left; i--) {
                list.add(matrix[top][i]);
            }
            if (left > --right) break;

            // 上移
            for (int i = bottom; i >= top; i--) {
                list.add(matrix[i][left]);
            }
            if (top > --bottom) break;
        }
        return list;
    }

3、旋转图像

1)关键解题思路
顺时针旋转90度,无非就是把这个二维数组当成一个箱子推一次。
旋转的具体步骤:
n × n 的二维矩阵 matrix,那么旋转步骤如下:

  1. 转置矩阵:将矩阵的第 i 行第 j 列的元素与第 j 行第 i 列的元素交换,即 matrix[i][j] 和 matrix[j][i] 交换。
  2. 反转每一行:将矩阵的每一行进行反转,即将 matrix[i][j] 和 matrix[i][n-1-j] 交换。
  3. int j = i+1:转置交换的关键节点,这个为了避免重复交换。
  4. int j = i + 1 的意义:只需要遍历主对角线上半部分的元素进行交换即可,避免重复。

2)代码

    public void rotate(int[][] matrix) {
        int row = matrix.length;
        int col = matrix[0].length;
        for (int i = 0; i < row; i++) {
            for (int j = i+1; j < col; j++) {
                // 将行和列进行交换
                int temp = matrix[i][j];
                matrix[i][j] = matrix[j][i];
                matrix[j][i] = temp;
            }
        }
        // 开始反转每一个行
        for (int i = 0; i < row; i++) {
            int left = 0;
            int right = col-1;
            while (left < right) {
                int temp = matrix[i][left];
                matrix[i][left] = matrix[i][right];
                matrix[i][right] = temp;
                left++;
                right--;
            }
        }
    }

4、搜索二维矩阵 II

1)关键思路
由每行每列对应的都是升序,故而可以对每一行进行二分查找,但与此同时有个更好的方法,把二维数组当初一个树
在这里插入图片描述
根据左下角那个点进行上右搜索。值小于就向上搜,大于向右搜。

2)代码

public boolean searchMatrix(int[][] matrix, int target) {
        int row = matrix.length;
        int col = matrix[0].length;
        int x = row - 1;
        int y = 0;
        while (y < col && x >= 0) {
            // 向上找
            if (matrix[x][y] > target) {
                x--;
            }else if (matrix[x][y] < target){
                y++;
            }
            else{
                return true;
            }
        }
        return false;
    }

其他相关内容

1. 链表-堆解题心得(二):https://blog.youkuaiyun.com/a147775/article/details/144218993

2. 贪心内容后面解题心得(三):https://blog.youkuaiyun.com/a147775/article/details/144215893

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值