hot100刷题全解

hot100刷题全解

持续更新…

两数之和

  • 初始化一个哈希表,用于存储数组元素和它们的索引。
  • 遍历数组,对于每个元素:
  • 计算目标值减去当前元素的差值。
  • 检查这个差值是否在哈希表中存在。
  • 如果存在,说明找到了两个数的和等于目标值,返回它们的索引。
  • 如果不存在,将当前元素及其索引存入哈希表中。
  • 如果遍历完数组没有找到符合条件的两个数,返回一个特殊的结果,如{-1, -1}。
class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map<Integer,Integer> map = new HashMap<>();


        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[]{-1,-1};
    }
}

49. 字母异位词分组

  • 字符排序法:

    • 对于每个字符串,将其转换为字符数组并进行排序。
    • 排序后,相同字谜的字符串将变为相同的字符序列。例如,“eat”、“tea"和"ate"排序后都变成了"aet”。
    • 使用排序后的字符串作为键,将原始字符串加入对应的值(即一个列表)中。
  • 使用哈希表:

    • 使用一个哈希表(HashMap)来存储排序后的字符串和原始字符串列表之间的映射关系。
    • 如果哈希表中已经存在这个排序后的字符串键,则直接将原始字符串添加到该键对应的列表中。
    • 如果哈希表中不存在这个排序后的字符串键,则创建一个新的列表,并将原始字符串添加进去,然后将这个键值对放入哈希表中。
      输出结果:
  • 最后,将哈希表中的所有值(即所有字符串列表)收集起来作为结果返回。


class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {

        // 不同的字符顺序 排序之后的结果是一样的
        Map<String,List<String>> map = new HashMap<>();

        for(String str: strs){
            char[] temp = str.toCharArray();// 转换为字符数组
            // 对字符数组进行排序
            Arrays.sort(temp);
            // 将排序之后的字符数组转换为字符串
            String ss = String.valueOf(temp);

            if(map.containsKey(ss)){
                // 如果之前存在这个key 直接将结果添加进去就可以
                map.get(ss).add(str);
            }else{
                // 如果不存在 直接创建一个新的List 塞进去
                List<String> newList = new ArrayList<>();
                newList.add(str);
                map.put(ss,newList);
            }
        }


        List<List<String>> result = new ArrayList<>(map.values());

        return result;


    }
}

128. 最长连续序列

  • 初始化一个 HashSet,将数组中的所有元素添加到集合中去重。
    遍历 HashSet 中的每一个元素:
  • 对于每个元素,检查它的前一个元素是否存在。如果不存在,则说明该元素可能是一个新的连续序列的起点。
  • 从该起点开始,依次检查其后续的元素是否存在,直到不再存在为止,并记录这个连续序列的长度。
  • 更新记录的最长连续序列长度。
class Solution {
    public int longestConsecutive(int[] nums) {

        // 使用set
        Set<Integer> set = new HashSet<>();

        for(int num:nums){
            set.add(num);
        }
        int result = 0;

        for(int num:nums){
            if(!set.contains(num - 1)){
                // 如果不存在 说明num可以作为一个起点

                int cur = num;
                int now = 1;

                // 更新cur 努力向后寻找
                while(set.contains(cur + 1)){
                    cur = cur + 1;
                    now++;
                }

                result = Math.max(result,now);// 更新最大长度
            }
        }

        return result;
    }
}

283. 移动零

  • 双指针 前后指针
  • 第一次遍历:将所有非0元素全部向前移动
  • 第二次遍历:剩余位置所有元素 全部填充为0
class Solution {
    public void moveZeroes(int[] nums) {

        // 第一次遍历:将所有非0元素全部向前移动
        int j = 0;
        for(int i = 0; i < nums.length; i++){
            if(nums[i] != 0){
                nums[j++] = nums[i];
            }
        }

        for(int i = j; i < nums.length; i++){
            nums[i] = 0;
        }

    }
}

11. 盛最多水的容器

  • 初始化两个指针 i 和 j,分别指向数组的开始和末尾。
  • 初始化一个变量 result 来存储最大容积值。
  • 使用一个 while 循环,条件是 i < j。
  • 在每次循环中,计算当前容积,并更新 result。
  • 比较 height[i] 和 height[j],移动较短线段对应的指针。
  • 返回 result,即最大容积值。
class Solution {
    public int maxArea(int[] height) {
        // 首先设置双指针 分别位于数组的两端

        int i = 0;
        int j = height.length - 1;
        int result = 0;

        // 两边的指针轮流进行收缩
        while(i < j){
            // 找到短板
            if(height[i] < height[j]){
                // 将短板向内移动 可能边长  所以体积可能会变大
                result = Math.max(result,(j - i) * height[i++]);
            } else{
                result = Math.max(result,(j - i) * height[j--]);
            }
        }


        return result;
    }
}

15. 三数之和

  • 对数组进行排序。
  • 遍历数组:
  • 对于每个 nums[i],如果 nums[i] 大于零,直接跳出循环,因为后面的数都比零大,不可能有和为零的三元组。
  • 跳过重复的 nums[i]。
  • 使用双指针:
  • 初始化左指针 l 为 i + 1,右指针 r 为数组末尾。
  • 计算三个数的和,根据和的结果调整指针位置,并跳过重复的元素。
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>>  res = new ArrayList<>();

        if(nums == null || nums.length < 3){
            return res;
        }

        Arrays.sort(nums);// 从小到大排序

        for(int i = 0; i < nums.length; i++){
            if(nums[i] > 0){
                break;
            }

            if(i > 0 && nums[i] == nums[i - 1]){
                continue;
            }

            int l = i + 1;
            int r = nums.length - 1;

            while(l < r){
                int sum = nums[i] + nums[l] + nums[r];

                if(sum == 0){
                    res.add(Arrays.asList(nums[i],nums[l],nums[r]));

                    while(l < r && nums[l] == nums[l + 1]){
                        l++;
                    }

                    while(l < r && nums[r] == nums[r - 1]){
                        r--;
                    }

                    l++;
                    r--;
                }else if(sum < 0){
                    l++;
                }else if(sum > 0){
                    r--;
                }
            }
        }

        return res;

    }
}

42. 接雨水

  • 双指针初始化:左右指针分别指向数组的开始和末尾,leftMax 和 rightMax 初始化为左右指针初始位置的高度。
  • 遍历数组:
  • 更新 leftMax 和 rightMax,确保它们分别表示当前指针位置左侧和右侧的最大高度。
  • 通过比较 leftMax 和 rightMax,决定移动哪个指针并计算当前可以存储的水量。
  • 由于水量是由较低的挡板决定的,因此当 leftMax 小于 rightMax 时,水量由左侧决定;反之亦然。
  • 结果累积:在每次指针移动时,累积当前指针位置的存储水量。

class Solution {
    public int trap(int[] height) {

        // 双指针 前后指针
        // 水桶效应:短的木板决定装多少水

        int n = height.length;
        int left = 0;
        int right = n - 1;
        int result = 0;

        int leftMax = height[left];
        int rightMax = height[right];

        left++;
        right--;


        while(left <= right){
            // 更新左右两侧的最大挡板
            leftMax = Math.max(leftMax,height[left]);
            rightMax = Math.max(rightMax,height[right]);

            if(leftMax < rightMax){
                result += leftMax - height[left];
                left++;
            }else{
                result += rightMax - height[right];
                right--;
            }
        }


        return result;

    }
}

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

  • 初始化部分:

    • left 和 right 指针初始化为0,分别表示当前窗口的左边界和右边界。
    • result 变量用于存储最长无重复字符子串的长度。
    • set 是一个哈希集合,用于存储当前窗口内的字符。
  • 滑动窗口逻辑:

    • 在每次循环中,如果 right 指向的字符在 set 中,表示存在重复字符,需要收缩窗口。通过移除 left 指向的字符并右移 left 指针来完成窗口收缩。
    • 如果 right 指向的字符不在 set 中,表示没有重复字符,可以扩展窗口。通过将 right 指向的字符添加到 set 中并右移 right 指针来完成窗口扩展。
    • 在每次扩展窗口后,更新 result,确保 result 保存的是最大窗口的大小。
  • 结果返回:

    • 当循环结束时,返回 result 变量,它表示最长无重复字符子串的长度。
class Solution {
    public int lengthOfLongestSubstring(String s) {

        if(s.length() <= 1){
            return s.length();
        }

        int left = 0;
        int right = 0;
        int result = 0;

        Set<Character> set = new HashSet<>();

        while(right < s.length() && left < s.length()){
            if(set.contains(s.charAt(right))){
                set.remove(s.charAt(left));
                left++;
            }else{
                set.add(s.charAt(right));
                right++;
            }


            if(result < set.size()){
                result = set.size();
            }
        }


        return result;

    }
}

438. 找到字符串中所有字母异位词

  • 初始化字典:

  • 创建两个哈希表 need 和 window。
    need 用于记录字符串 p 中每个字符及其出现的次数。

  • window 用于记录当前滑动窗口中各个字符的数量。

  • 填充 need 字典:

  • 遍历字符串 p,将每个字符及其出现的次数存入 need。

  • 滑动窗口定义:

  • 使用两个指针 left 和 right 来表示当前窗口的左右边界,初始时都指向字符串 s 的开始位置。

  • valid 变量用于记录当前窗口中满足 need 条件的字符数量。

  • 移动右边界:

  • 在一个循环中,不断移动右指针 right,将 s 中的字符加入到窗口中,并更新 window 字典。

  • 如果加入的字符在 need 中,更新 window 中该字符的计数,如果该字符的计数满足 need 的要求,valid 加1。

  • 窗口收缩条件:

  • 当窗口的大小大于等于 p 的长度时,判断当前窗口是否为一个合法的字母异位词:

  • 如果 valid 等于 need 中的键数,说明当前窗口中的字符匹配 p 中的字符,记录下 left 位置。
    移动左指针 left,缩小窗口,并更新 window 中的字符计数。

  • 如果移除的字符在 need 中,且该字符计数减少后不再满足 need 的要求,valid 减1。

  • 返回结果:

  • 最后返回 result,其中包含所有字母异位词的起始位置。


class Solution {
    public List<Integer> findAnagrams(String s, String p) {

        Map<Character,Integer> need = new HashMap<>();// 记录字符串p的所有字符
        Map<Character,Integer> window = new HashMap<>();// 记录滑动窗口中的字符

        for(char ch: p.toCharArray()){
            need.put(ch,need.getOrDefault(ch,0) + 1);// 将字符填入进去
        }

        int left = 0;
        int right = 0;
        int valid = 0;
        List<Integer> result = new ArrayList<>();

        // [right,left)
        while(right < s.length()){
            char c = s.charAt(right);
            right++;// right永远指向滑动窗口右边界的下一个字符  左闭右开

            // 判断该字符是不是need中的
            if(need.containsKey(c)){
                window.put(c,window.getOrDefault(c,0) + 1);
                if(window.get(c).equals(need.get(c))){
                    valid++;
                }
            }


            while(right - left >= p.length()){
                // 说明滑动窗口需要缩小
                if(valid == need.size()){
                    result.add(left);// 将左边界加入List
                }

                char t = s.charAt(left);
                left++;


                if(need.containsKey(t)){
                    if(window.get(t).equals(need.get(t))){
                        valid--;
                    }

                    // 说明window窗口需要缩小
                    window.put(t,window.get(t) - 1);

                }
            }
        }

        return result;

    }
}

560. 和为 K 的子数组

  • 使用前缀和数组
  • 使用双重for循环 计算一个连续区间的和

class Solution {
    public int subarraySum(int[] nums, int k) {

        // 子数组是连续的序列  考虑使用前缀和

        // 使用前缀和
        int[] preSum = new int[nums.length + 1];


        // 初始化前缀和数组
        preSum[0] = 0;
        // 使用前缀和  preSum[i] 记录 前面所有元素的和  所以preSum[i + 1] - preSum[i] = nums[i]
        for(int i = 0; i < nums.length; i++){
            preSum[i + 1] = preSum[i] + nums[i];
        }


        int count = 0;


        // 双重for循环 计算一个区间内的数据之和
        for (int left = 0; left < nums.length; left++){
            
            for (int right = left; right < nums.length; right++){
                if(preSum[right + 1] - preSum[left] == k) {
                    count++;
                }
            }

        }

        return count;
    }
}

239. 滑动窗口最大值

  • 双端队列实现求解最大值窗口的结构,双端队列存储数组中的下标
  • 双端队列必须始终维持根据下表对应的数字由大到小的顺序
  • 不断地移动窗口,如果进入的数字会打破递减的顺序,队列尾部就会不断地出数字,直到满足递减的要求,再把当前数字插入
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        // 滑动窗口每次移动以为位置 就扫描一遍窗口  找出最大值xj

        // 窗口对应的数据结构为双端队列  删除首部元素

        if (nums == null || nums.length == 0 || k <= 0) {
            return new int[0];
        }

        int n = nums.length;
        List<Integer> ans = new ArrayList<>();
        Deque<Integer> qmax = new LinkedList<>();// 双端队列存储下标

        for (int r = 0; r < n; r++) {
            // 针对元素nums[r] pop出队列比他小的所有元素
            while(!qmax.isEmpty() && nums[qmax.peekLast()] <= nums[r]) {
                qmax.pollLast();// 将所有比新数字小的元素 全部出队
            }

            // 如果比他小 将当前元素的索引加入队列尾部
            qmax.offerLast(r);

            // 队列的头部元素是最大值元素的索引  那么判断最大值元素索引是不是在窗口中
            //如果最大值元素的索引是r - k 说明已经不是在滑动窗口内部了  新的窗口索引是r - k + 1
            //  也就是说最大值的索引是在窗口左侧边界的前一个位置  将该索引从队列头部弹出
            if (qmax.peekFirst() == r - k) {
                qmax.pollFirst();// 弹出不再窗口内的元素索引
            }

            // 如果窗口形成
            if (r >= k - 1) {
                ans.add(nums[qmax.peekFirst()]);// 将当前窗口的最大值加入结果列表中
            }
        }

        // 将结果列表转换为数组
        int[] result = new int[ans.size()];

        for (int i = 0; i < ans.size(); i++) {
            result[i] = ans.get(i);
        }

        
        return result;
    }
}

76. 最小覆盖子串

  • 滑动窗口

  • 变量定义:

    • need:一个哈希表,记录字符串t中每个字符需要的数量。
    • window:一个哈希表,记录当前滑动窗口中每个字符的数量。
    • left 和 right:滑动窗口的左右边界指针,初始都设为0。
    • valid:记录当前窗口中满足need条件的字符的种类数。
    • ans:数组,用于存储最小子串的长度和起止索引位置,初始化为{-1, 0, 0}。
    • required:记录t中不同字符的总数
  • 扩展右边界:

    • 随着right指针的向右移动,每遇到一个字符c,都将其加入到window中。
    • 检查加入的这个字符c是否存在于need中,如果存在,并且window中该字符的数量达到了need中对应的数量,那么valid加1。
  • 收缩左边界:

    • 当valid的值与required相等时,说明当前窗口已经包含了所有t中的字符。
    • 此时,尝试通过移动left指针来收缩窗口,优化找到的子串长度。
    • 在收缩窗口的过程中,记录可能的最小窗口。如果当前窗口的长度小于之前记录的最小长度(或者是初始值-1),更新ans数组。
    • 将left位置的字符从window中数量减1,如果这导致该字符不再满足need的需求,valid减1。

class Solution {
    public String minWindow(String s, String t) {

        if (s == null || t == null || s.length() == 0 || t.length() == 0) {
            return "";
        }
        // 滑动窗口 

        Map<Character,Integer> need = new HashMap<>();// 记录字符串t中的所有字符
        Map<Character,Integer> window = new HashMap<>();// 滑动窗口

        for (char ch:t.toCharArray()) {
            need.put(ch ,need.getOrDefault(ch,0) + 1);// 初始化need 记录所有需要的字符
        }
        
        int left = 0;
        int right = 0;
        int valid = 0;// 记录窗口内有效字符个数
        int[] ans = {-1,0,0};// 记录窗口长度 左指针 右指针
        int required = need.size();


        while(right < s.length()) {
            char c = s.charAt(right);
            
            // 判断该字符是不是need中的  如果是 塞进窗口  以及更新valid
            window.put(c,window.getOrDefault(c,0) + 1);
            if(need.containsKey(c)) {
                if(window.get(c).intValue() == need.get(c).intValue()) {
                    valid++;// 说明c字符已经满足条件
                }
            }

            while(left <= right && valid == required) {
                // 收缩窗口 + 更新左边界

                // 达到要求之后 保存最小窗口
                if(ans[0] == -1 || right - left + 1 < ans[0]) {
                    ans[0] = right - left + 1;// 更新最小窗口长度
                    ans[1] = left;
                    ans[2] = right;
                }


                c = s.charAt(left);// 取出左边界的字符

                window.put(c,window.get(c) - 1);

                if(need.containsKey(c) && window.get(c).intValue() < need.get(c).intValue()) {
                    valid--;
                }

                left++;
            }  

            right++;
        }

    return ans[0] == -1 ? "" : s.substring(ans[1], ans[2] + 1);
    }
}

53. 最大子数组和

  • 动态规划
  • dp[i] 代表从nums[0] 到 nums[i] 中最大子数组和
class Solution {
    public int maxSubArray(int[] nums) {
        // 动态规划
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        int max = nums[0];

        for(int i = 1; i < nums.length; i++) {
            dp[i] = Math.max(dp[i - 1] + nums[i],nums[i]);// 因为加上之后可能是负数
            if(max < dp[i]) {
                max = dp[i];
            }
        }

        return max;

    }
}

56. 合并区间

  • 排序:

    • 首先,我们需要将所有的区间按照起始位置进行排序。这样可以方便后续的合并操作,因为相邻的区间更容易检查是否重叠。
  • 初始化:

    • 如果输入数组为空,直接返回一个空的二维数组。
  • 遍历和合并:

    • 使用一个循环遍历排序后的区间数组,逐个检查区间是否与当前区间重叠。如果重叠,则合并区间,更新结束位置。如果不重叠,则将当前区间加入结果列表。
  • 转换结果:

    • 最后,将结果列表转换为二维数组的形式并返回。
class Solution {
    public int[][] merge(int[][] intervals) {
        // 首先对区间进行排序
        if (intervals.length == 0) {
            return new int[0][0];
        }

        Arrays.sort(intervals,Comparator.comparingInt(a -> a[0]));
        List<int[]> result = new ArrayList<>();

        for (int i = 0; i < intervals.length; i++) {
            int start = intervals[i][0];
            int end = intervals[i][1];
            int j = i + 1;

            // 针对后面的区间进行遍历, 如果后面的区间的左边界小于或者等于  当前区间的左边界  那么说明可以合并
            while(j < intervals.length && intervals[j][0] <= end) {
                // 合并之后  更新区间的右边界
                end = Math.max(end,intervals[j][1]);
                j++;
            }
            // 当找到一个区间的左边界和当前区间的右边界脱节的时候 将前面的合并结果添加
            result.add(new int[] {start,end});
            i = j - 1;// 因为上面的j++
        }

        // 将List转换为数组
        int[][] ans = new int[result.size()][2];
        for (int i = 0; i < result.size(); i++) {
            ans[i][0] = result.get(i)[0];
            ans[i][1] = result.get(i)[1];
        }

        return ans;


    }
}

189. 轮转数组

  • 整体翻转:首先,将整个数组进行翻转。这会将所有元素的位置颠倒。

  • 找到分割点:根据 k 值,确定分割点。实际上,由于数组是循环的,所以我们只需考虑 k % 数组长度,这样可以忽略掉多余的完全循环。

  • 局部翻转:将翻转后的数组分为两部分:前 k 个元素和剩余的元素。分别对这两部分进行再次翻转,就可以得到最终的结果

class Solution {
    public void rotate(int[] nums, int k) {
        // 首先对整个数组进行翻转
        // 找到分割点k
        // 对左右子数组进行排序

        // 递归
        k = k % nums.length;
        reverse(nums,0,nums.length - 1);
        reverse(nums,0,k - 1);
        reverse(nums,k,nums.length - 1);
    }

    // 首尾元素交换
    public void reverse(int[] nums, int start,int end) {
        while (start < end) {
            int t = nums[start];
            nums[start] = nums[end];
            nums[end] = t;
            start++;
            end--;
        }
    }
}

238. 除自身以外数组的乘积

  • 首先初始化前缀积数组pre和后缀积数组suf,分别从左到右和从右到左计算每个位置的积。
  • 然后,利用前缀积和后缀积的结果计算最终的结果数组result。
  • 最后返回结果数组

class Solution {
    public int[] productExceptSelf(int[] nums) {
        // 计算前缀积 + 后缀积

        int n = nums.length;
        int[] pre = new int[n];
        // 计算所有元素的前缀积
        pre[0] = 1;

        // pre[i] =  从0 到 i - 1 的所有元素的乘积
        for (int i = 1; i < n; i++) {
            pre[i] = pre[i - 1] * nums[i - 1];
        }

        // 计算后缀积
        int[] suf = new int[n];
        suf[n - 1] = 1;
        // suf[i]等于从i + 1到 n - 1的所有元素的乘积
        for (int i = n - 2; i >= 0; i--) {
            suf[i] = suf[i + 1] * nums[i + 1];
        }


        int [] result = new int[n];
        for(int i = 0 ; i < n; i++) {
            result[i] = pre[i] * suf[i];
        }


        return result;
    }
}

41. 缺失的第一个正数

class Solution {
    public int firstMissingPositive(int[] nums) {
        // 创建哈希表 记录数组中的数
        int n = nums.length;

        for (int i = 0; i < n; i++) {
            // 通过原地交换使得每一个正整数放到他在数组中的对应位置  比如数字2 应该放在 1索引的位置 nums[1]
            // 然后再去遍历数组 找出第一个不在正确位置的正整数 时间复杂度是O(n)


            // 加入nums[1]的数字不是2 那么进行交换 强制归位
            // 保证每一个数字放在对应的位置上
            while(nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[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;// 如果数组中所有的数都在对应位置 那么缺失的数就是n + 1
    }
}

73. 矩阵置零

  • 首先扫描第一行和第一列 看看是否需要记作0
  • 然后在扫描其他行和列的元素的时候 将第一行和第一列的空间作为记录元素为0的标记位置
class Solution {
    public void setZeroes(int[][] matrix) {
        int row = matrix.length; // 矩阵的行数
        int col = matrix[0].length; // 矩阵的列数

        boolean row0_flag = false;// 标记第一行是否有0
        boolean col0_flag = false;// 标记第一列是否有0

        // 本题的思路就是 首先扫描第一行和第一列 看看是否需要记作0
        // 然后在扫描其他行和列的元素的时候 将第一行和第一列的空间作为记录元素为0的标记位置

        // 检查第一行是否有0
        for (int j = 0; j < col; j++) {
            if (matrix[0][j] == 0) {
                row0_flag = true;
                break;
            }
        }

        // 检查第一列是否有0
        for (int i = 0; i < row; i++) {
            if(matrix[i][0] == 0) {
                col0_flag = true;
                break;
            }
        }

        // 使用第一行和第一列作为标志位,标记需要重置为0的行和列
        for (int i = 1; i < row; i ++) {
            for (int j = 1; j <  col; j++) {
                // 如果发现第i行第j列的元素是0 
                // 将第i行的第一个元素记作0  第j列的第一个元素记作0
                if (matrix[i][j] == 0) {
                    matrix[i][0] = matrix[0][j] = 0; 
                }
            }
        }

        // 根据标志位将需要记作0的行和列记录一下
        for (int i = 1; i < row; i++) {
            for (int j = 1; j < col; j++) {
                if (matrix[i][0] == 0 || matrix[0][j] == 0) {
                    matrix[i][j] = 0;
                }
            }
        }

        // 如果第一行有0  将第一行全部记作0
        if(row0_flag == true) {
            for (int j = 0; j < col; j++) {
                matrix[0][j] = 0;
            }
        }

        // 如果第一列有0  将第一列全部记作0
        if (col0_flag == true) {
            for (int i = 0; i < row; i++) {
                matrix[i][0] = 0;
            }
        }
    }
}

54. 螺旋矩阵

class Solution {
    public List<Integer> spiralOrder(int[][] matrix) {
        if (matrix.length == 0) {
            return new ArrayList<>();
        }

        // l是左边界  r 是右边界
        // t是上边界  b 是下边界
        int l = 0, r = matrix[0].length - 1;// 从左到右的范围
        int t = 0, b = matrix.length - 1;// 从上到下的范围
        int x = 0;

        Integer[] res = new Integer[(r + 1) * (b + 1)];// 创建结果数组

        while(true) {
            // 遍历行  l~r是列的范围  t 是行数
            // 从左到右遍历上边界
            for (int i = l; i <= r; i++) {
                res[x++] = matrix[t][i];// 从左到右遍历上边界
            }

            // 行数超过下边界  退出循环
            if (++t > b) {
                break;
            }

            // 从上到下遍历右边界
            for (int i = t; i <= b; i++) {
                res[x++] = matrix[i][r];
            }

            if (l > --r) {
                break;
            }

            // 从右到左遍历下边界
            for (int i = r; i >= l; i--) {
                res[x++] = matrix[b][i];
            }

            if(--b < t) {
                break;// 移动下边界  超过上边界就退出循环
            }

            for (int i = b; i >= t; i--) {
                res[x++] = matrix[i][l];
            }

            if(++l > r){
                break;// 移动左边界  如果超过右边界 就退出循环
            }
        }

        return Arrays.asList(res);
    }
}

48. 旋转图像

  • 对角线交换
  • 上下交换
class Solution {
    public void rotate(int[][] matrix) {
        // 首先沿着右上-左下的对角线进行翻转

        if (matrix.length == 0 || matrix.length != matrix[0].length) {
            return;
        }

        int length = matrix.length;

        // 对角线交换:将矩阵沿着副对角线进行元素交换
        // 通过i 和 j遍历矩阵的上半部分  将矩阵的元素 翻转到副对角线的对称位置
        for (int i = 0; i < length; i++) {
            for (int j = 0; j < length - i; j++) {
                // 交换元素
                int temp = matrix[i][j];
                matrix[i][j] = matrix[length - 1 - j][length - 1 - i];
                matrix[length - 1 - j][length - 1 - i] = temp;
            }
        }

        // 上下交换

        for (int i = 0; i < (length >> 1); i++) {
            for (int j = 0; j < length; j++) {
                int t = matrix[i][j];
                matrix[i][j] = matrix[length - i - 1][j];
                matrix[length - i - 1][j] = t;
            }
        }

    }
}

240. 搜索二维矩阵 II

  • 针对每一行进行二分查找
class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int m = matrix.length;
        int n = matrix[0].length;

        if (m == 0 || n == 0){
            return false;
        }

        for (int i = 0; i < m; i++) {
            // 遍历每一行
            int l = 0;
            int r = n - 1;

            while(l <= r) {
                // 针对每一行进行二分查找
                int mid = (l + r + 1) >> 1;
                if (matrix[i][mid] < target) {
                    l = mid + 1;
                }else if (matrix[i][mid] > target) {
                    r = mid - 1;
                }else if(matrix[i][mid] == target) {
                    return true;
                }
            }
        }


        return false;
    }
}

160. 相交链表

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {

        // 不存在环  
        

        // 计算链表a和链表b的长度 然后计算差值

        ListNode p = headA;
        ListNode q = headB;
        int lenA = 0;
        int lenB = 0;

        while(p != null) {
            lenA++;
            p = p.next;
        }


        while(q != null) {
            lenB++;
            q = q.next;
        }

        p = headA;
        q = headB;

        if(lenA > lenB) {
            // 让第一个指针先走
            int t = lenA - lenB;

            while(t-- > 0) {
                p = p.next;
            }

            // 然后两个指针开始同时进行移动
            while(p!= null && q != null) {
                if (p == q) {
                    return p;
                }

                p = p.next;
                q = q.next;
            }

        }else {
            int t = lenB - lenA;

            while(t-- > 0) {
                q = q.next;
            }

            while(q != null && p != null) {
                if (p == q) {
                    return p;
                }

                p = p.next;
                q = q.next;
            }
        }

        return null;
    }
}

206. 反转链表

  • 前后指针
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {

        ListNode p = head;
        ListNode q = null;

        while(p != null) {
            // 前后指针
            // 先保存好下一个节点
            ListNode next = p.next;
            p.next = q;
            q = p;
            p = next;
        }
        return q;
    }
}

234. 回文链表

  • 将链表后半部分进行翻转
  • 然后比较链表前后两部分 是否相同
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        // 将链表的后半部分进行翻转  然后比较前后两部分链表是否相同
        if (head == null) {
            return false;
        }

        if (head.next == null) {
            return true;
        }

        // 使用快慢指针找到链表的中点
        ListNode slow = head;
        ListNode fast = head;

        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }

        // 翻转链表的后半部分
        ListNode pre = null;
        while(slow != null) {
            ListNode next = slow.next;
            slow.next = pre;

            // 移动指针
            pre = slow;
            slow = next;
        }

        // 最后pre是后半部分lia


        // 比较链表的前后两部分
        ListNode left = head;
        ListNode right = pre;

        while(right != null) {
            if (left.val != right.val) {
                return false;
            }

            left = left.next;
            right = right.next;
        }

        return true;
    }



}

141. 环形链表

  • 快指针走两步 慢指针走一步
  • 如果存在环 一定会相遇
/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        // 快慢指针

        ListNode fast,slow;
        fast = slow = head;

        // 快指针走两步 慢指针走一步  判断是否相等
        while (fast != null && fast.next != null) {
            fast = fast.next.next;

            slow = slow.next;


            if (fast == slow) {
                return true;
            }
        }


        return false;
    }
}

142. 环形链表 II

  • 检测环

    • 使用 while 循环,让 fast 指针每次移动两个节点,slow 指针每次移动一个节点。
    • 如果 fast 和 slow 相遇,说明链表中存在环,跳出循环。
    • 如果 fast 到达链表末尾 (fast == null 或 fast.next == null),说明链表中没有环,返回 null。
  • 找到环的起始节点

    • 如果检测到环存在,将 slow 指针重新指向链表头节点 head。
    • 然后 slow 和 fast 每次都移动一个节点。
    • 当 slow 和 fast 再次相遇时,相遇点即为环的起始节点,返回该节点。
/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {

        // 首先找到相遇的位置
        ListNode fast,slow;
        fast = slow = head;

        while(fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;

            // 说明遇到了环
            if (fast  == slow) {
                break;
            }
        }

        // 到链表末尾 说明没有环
        if (fast == null || fast.next == null) {
            return null;
        }


        // 之后将其中一个指针重新指向head
        slow = head;
        while (slow != fast) {
            fast = fast.next;
            slow = slow.next;
        }

        return fast;
    }
}

21. 合并两个有序链表

  • 归并排序
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        // 归并排序
        ListNode p = list1;
        ListNode q = list2;
        ListNode result = new ListNode(-1);
        ListNode ans = result;


        while(p != null && q != null) {
            if (p.val < q.val) {
                result.next = p;
                p = p.next;
            }else {
                result.next = q;
                q = q.next;
            }
            result = result.next;
        }

        if (p != null) {
            result.next = p;
        }

        if (q != null) {
            result.next = q;
        }

        return ans.next;
    }
}

2. 两数相加

  • 高精度加法模版
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        // 使用高精度加法模版
        ListNode l3 = new ListNode(-1);
        ListNode node = l3;

        ListNode p = l1;
        ListNode q = l2;

        int t = 0;// 进位

        while (p != null || q != null) {
            if (p != null) {
                t += p.val;
                p = p.next;
            }

            if (q != null) {
                t += q.val;
                q = q.next;
            }

            // 将结果插入
            ListNode tmp = new ListNode(t % 10);
            l3.next = tmp;
            l3 = l3.next;
            t = t / 10;// 进位结果递进
        }

        if (t != 0) {
            l3.next = new ListNode(1);
        }

        return node.next;
    }
}

19. 删除链表的倒数第 N 个结点

  • 找到倒数第n + 1个节点 前驱结点 然后删除
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        if (head == null) {
            return null;
        }

        // 找到倒数第N - 1个节点 作为前驱结点  也就是第 length - n + 1的节点
        ListNode dummy = new ListNode(0);
        dummy.next = head;


        // 使用双指针方法
        ListNode first = dummy;
        ListNode second = dummy;


        // 快指针先走 n + 1步 
        for (int i = 0; i <= n; i++) {
            first = first.next;
        }

        // 快慢指针一起走 快指针走到null
        while(first != null) {
            first = first.next;
            second = second.next;
        }


        // 此时second指针指向要删除节点的前驱节点
        second.next = second.next.next;


        return dummy.next;
    }
}

24. 两两交换链表中的节点

  • 遍历链表 针对链表的节点两两交换
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode swapPairs(ListNode head) {
        // 两两交换链表的节点

        // 制作哑结点
        ListNode pre = new ListNode(0);
        pre.next = head;// 这个是用来找head

        ListNode temp = pre;// 这个用来遍历的


        while (temp.next != null && temp.next.next != null) {
            // 遍历链表  temp 相当于前驱节点

            // 1      2     3
            // temp  start end   交换 2 3
            // 1      3     2

            ListNode start = temp.next;
            ListNode end = temp.next.next;

            // 开始交换 注意顺序
            temp.next = end;
            start.next = end.next;
            end.next = start;

            // 交换2 和 3  之后  temp 移动到2 这个位置 作为新的前驱节点
            temp = start;
        }
        
        return pre.next;
    }
}

25. K 个一组翻转链表

  • reverseKGroup方法:
    • 首先检查链表是否为空,如果为空则直接返回。
    • 使用两个指针a和b初始化为链表头。
    • 检查链表剩余节点是否至少有k个,如果不足k个则返回当前头节点。
    • 调用reverse方法翻转前k个节点。
    • 递归调用reverseKGroup处理剩余的节点,并将翻转后的部分连接起来。
    • 返回新头节点。
  • reverse方法:
    • 翻转从a到b(不包含b)之间的节点。
    • 使用pre和cur两个指针进行翻转,pre指向前一个节点,cur指向当前节点。
    • 遍历并翻转每个节点,直到当前节点等于b。
    • 返回翻转后的新头节点。
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        if (head == null) {
            return null;
        }
        ListNode a,b;
        a = head;
        b = head;

        // 检查剩余的节点数目是否足够k个
        for (int i = 0; i < k; i++) {
            if (b == null) {
                return head;// 说明不够k个节点  那就不需要翻转
            }
            b = b.next;
        }

        ListNode newHead = reverse(a,b);

        a.next = reverseKGroup(b,k);

        return newHead;
    }

    // 根据制定范围 翻转链表
    ListNode reverse(ListNode a, ListNode b) {
        ListNode pre = null;
        ListNode cur = a;

        while (cur != b) {
            ListNode t = cur.next;

            cur.next = pre;
            pre = cur;
            cur = t;
        }


        return pre;
    }
}

138. 随机链表的复制

  • 哈希表
/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/

class Solution {
    public Node copyRandomList(Node head) {
        // 利用哈希表一一映射的关系  构建原链表和新链表之间的键值对映射关系
        Map<Node,Node> map = new HashMap<>();

        // 遍历链表 存储map
        Node cur = head;
        while(cur != null) {
            Node temp= new Node(cur.val);
            map.put(cur,temp);
            cur = cur.next;
        }

        // 再次遍历链表 赋值节点
        cur = head;

        while(cur != null) {
            Node newNode = map.get(cur);
            newNode.next = map.get(cur.next);
            newNode.random = map.get(cur.random);

            cur = cur.next;
        }

        return map.get(head);
    }
}

148. 排序链表

  • 归并排序
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {

    public ListNode sortList(ListNode head, ListNode tail) {
        if (head == null) {
            return head;
        }

        // 判断链表是否只包含一个节点
        if (head.next == tail) {
            head.next = null; // 确保单个节点链表 不再指向任何其他节点  然后返回这个节点head
            return head;
        }

        // 快慢指针找到链表的中点
        ListNode slow = head;
        ListNode fast = head;

        // 找到链表的中点
        while(fast != tail) {
            slow = slow.next;
            fast = fast.next;

            if (fast != tail) {
                fast = fast.next;
            }
        }

        // 慢指针指向链表的中点
        ListNode mid = slow;
        ListNode list1 = sortList(head,mid);
        ListNode list2 = sortList(mid,tail);

        ListNode mergeNode = mergeList(list1,list2);

        return mergeNode;
    }
    
    public ListNode mergeList(ListNode a, ListNode b) {
        ListNode dummyHead = new ListNode(0);
        ListNode temp = dummyHead;// 创建新的节点 连接新的值


        ListNode temp1 = a;
        ListNode temp2 = b;


        // 归并  每一次取出最小的值
        while(temp1 != null && temp2 != null) {
            if (temp1.val < temp2.val) {
                temp.next =  temp1;
                temp1 = temp1.next;
            }else {
                temp.next = temp2;
                temp2 = temp2.next;
            }

            temp = temp.next;
        }

        if(temp1 != null) {
            temp.next = temp1;
        }

        if (temp2 != null) {
            temp.next = temp2;
        }



        return dummyHead.next;
    }

    public ListNode sortList(ListNode head) {
        // 链表排序  归并排序

        return sortList(head,null);
    }
}

23. 合并 K 个升序链表

  • 通过使用优先级队列 最小堆 能够高效完成合并操作
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {

        
        if (lists.length == 0) {
            return null;
        }


        ListNode dummyNode = new ListNode(-1);
        ListNode p = dummyNode;


        // 创建优先级队列 最小堆  从小到大进行排列
        PriorityQueue<ListNode> pd = new PriorityQueue<>(lists.length,(a,b) -> (a.val - b.val));


        // 将每一个链表的头结点添加到优先级队列中
        for (ListNode node: lists) {
            if (node != null) {
                pd.add(node);
            }
        }
        

        // 处理优先级队列 
        while(!pd.isEmpty()) {
            // 获取队列中最小的节点  将其添加到结果链表中
            ListNode temp = pd.poll();// 将一个链表的头结点出队
            
            p.next = temp;

            // 如果当前节点有下一个节点  将下一个节点挨个添加到队列中  优先级队列会自动进行排序
            if (temp.next != null) {
                pd.add(temp.next);
            }

            // 移动指针
            p = p.next;
        }
        return dummyNode.next;
    }
}

146. LRU 缓存

最近最少使用


class LRUCache {

    int cap;// 容量
    LinkedHashMap<Integer,Integer> cache = new LinkedHashMap<>();// 保持元素的有序性

    public LRUCache(int capacity) {
        this.cap = capacity;// 初始化容器的容量
    }
    
    public int get(int key) {
        // 获取指定Key的数据 然后进行更新 表示最近使用过
        if(!cache.containsKey(key)) {
            return -1;
        }

        makeRecently(key);// 重新把这个key还有value塞进去
        return cache.get(key);// 获取元素
    }
    
    public void put(int key, int value) {

        // 首先判断这个元素是否 存在
        if (cache.containsKey(key)) {
            // 直接更新
            cache.put(key,value);
            makeRecently(key);
            return;
        }

        // 如果不存在 缓存容量超过cao
        if (cache.size() >= this.cap) {
            int oldestKey = cache.keySet().iterator().next();
            cache.remove(oldestKey);// 删除长时间不使用的数据
        }

        cache.put(key, value);
    }

    private void makeRecently(int key) {
        int val = cache.get(key);// 获取元素

        // 移除key
        cache.remove(key);
        cache.put(key,val);// 重新放进去
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */
  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

少写代码少看论文多多睡觉

求打赏,求关注,求点赞

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值