【算法训练营 · 补充】LeetCode Hot100(中)

文章目录

链表部分

相交链表

题目链接:160. 相交链表

解题思路:

先将A链表遍历一遍,将节点放入到set中,然后遍历B链表,每走一步就看set中是否存在,如果存在直接返回表示第一个相交节点。如果遍历完都没有,直接返回null。

解题代码:

java 复制代码
/**
 * 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) {
        ListNode pointerA = headA;
        ListNode pointerB = headB;
        Set<ListNode> path = new HashSet<>();
        while(pointerA != null) {
            path.add(pointerA);
            pointerA = pointerA.next;
        }
        while(pointerB != null) {
            if(path.contains(pointerB)) return pointerB;
            pointerB = pointerB.next;
        }
        return null;
    }
}

环形链表

解题链接:141. 环形链表

解题思路:

和相交链表的思路一样,使用set记录走过的节点

解题代码:

java 复制代码
/**
 * 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 pointer = head;
        Set<ListNode> set = new HashSet<>();
        while(pointer != null) {
            if(set.contains(pointer)) return true;
            set.add(pointer);
            pointer = pointer.next;
        }
        return false;
    }
}

回文链表

题目链接:234. 回文链表

解题逻辑:

基本逻辑就是:

  • 先求出链表长度
  • 折半之后,将一边的元素添加到栈中,同时移动指针
  • 然后再将栈中元素弹出与指针元素比对即可

解题代码:

java 复制代码
class Solution {
    public boolean isPalindrome(ListNode head) {
        int len = 0;
        ListNode pointer = head;
        while(pointer != null) {
            len++;
            pointer = pointer.next;
        }
        int begin = len / 2;
        pointer = head;
        Deque<Integer> stack = new ArrayDeque<>();
        while(begin-- > 0) {
            stack.push(pointer.val);
            pointer = pointer.next;
        }
        if(len % 2 == 1) pointer = pointer.next;
        while(!stack.isEmpty()) {
            if(stack.pop() != pointer.val) return false;
            pointer = pointer.next;
        }
        return true;
    }
}

合并两个有序链表

题目链接:21. 合并两个有序链表

本题比较基础分三种情况讨论即可

解题代码:

java 复制代码
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode result = new ListNode();
        ListNode pointer1 = list1;
        ListNode pointer2 = list2;
        ListNode pointer3 = result;
        while(pointer1 != null && pointer2 != null) {
            if(pointer1.val <= pointer2.val) {
                pointer3.next = new ListNode(pointer1.val);
                pointer1 = pointer1.next;
                pointer3 = pointer3.next;
            }else {
                pointer3.next = new ListNode(pointer2.val);
                pointer2 = pointer2.next;
                pointer3 = pointer3.next;
            }
        }

        while(pointer1 != null) {
            pointer3.next = new ListNode(pointer1.val);
            pointer1 = pointer1.next;
            pointer3 = pointer3.next;
        }

        while(pointer2 != null) {
            pointer3.next = new ListNode(pointer2.val);
            pointer2 = pointer2.next;
            pointer3 = pointer3.next;
        }

        return result.next;
    }
}

两数相加

题目链接:2. 两数相加

和前面一样分三种情况讨论即可,然后可以使用一个flag表示进位符号。

java 复制代码
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode pointer1 = l1;
        ListNode pointer2 = l2;
        ListNode result = new ListNode();
        ListNode pointer3 = result;
        boolean flag = false;
        while(pointer1 != null && pointer2 != null) {
            int num;
            if(flag) num = pointer1.val + pointer2.val + 1;
            else num = pointer1.val + pointer2.val;
            flag = num >= 10 ? true : false;
            pointer3.next = new ListNode(num % 10);
            pointer1 = pointer1.next;
            pointer2 = pointer2.next;
            pointer3 = pointer3.next;
        }

        while(pointer2 != null) {
            int num;
            if(flag) num = pointer2.val + 1;
            else num = pointer2.val;
            flag = num >= 10 ? true : false;
            pointer3.next = new ListNode(num % 10);
            pointer2 = pointer2.next;
            pointer3 = pointer3.next;
        }

        while(pointer1 != null) {
            int num;
            if(flag) num = pointer1.val + 1;
            else num = pointer1.val;
            flag = num >= 10 ? true : false;
            pointer3.next = new ListNode(num % 10);
            pointer1 = pointer1.next;
            pointer3 = pointer3.next;
        }

        if(flag) {
            pointer3.next = new ListNode(1);
        }

        return result.next;
    }
}

总结:两个链表操作类型题目

此种题目一般会创建三个指针:

  • 链表1的操作指针pointer1
  • 链表2的操作指针pointer2
  • 结果链表的操作指针pointer3

分三种情况讨论:

  • pointer1 和 pointer2都不为null
  • pointer1不为null的情况
  • pointer2不为null的情况

K 个一组翻转链表

题目链接:25. K 个一组翻转链表

解题思路:

没有技巧,主要考察对代码的掌控能力,边界情况的处理

解题代码:

java 复制代码
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        int len = 0;
        ListNode pointer = head;
        while(pointer != null) {
            len++;
            pointer = pointer.next;
        }
        ListNode result = head;
        for(int i = 0;i < len;i+=k) {
            if(len - i < k) break;
            result = reverseList(result,i,i + k - 1);
        }
        return result;
    }

    public ListNode reverseList(ListNode head,int start,int end){
        Deque<Integer> stack = new ArrayDeque<>();
        ListNode pointer = head;
        int cur = 0;
        ListNode left = null;
        ListNode right = null;
        while(cur <= end) {
            if(cur == start - 1) left = pointer;
            if(cur == end) right = pointer.next;
            if(cur >= start) stack.push(pointer.val);
            cur++;
            pointer = pointer.next;
        }
        if(start == 0 && !stack.isEmpty()) left = new ListNode(stack.pop());
        ListNode result = left;
        while(!stack.isEmpty()) {
            result.next = new ListNode(stack.pop());
            result = result.next;
        }
        result.next = right;

        if(start == 0) return left;
        else return head;
    }
}

随机链表的复制

题目链接:138. 随机链表的复制

解题思路:

解题代码:

排序链表(对递归的理解)

题目链接:148. 排序链表

本题如果使用选择排序的思想,时间复杂度达到了n方,最后会超时:

java 复制代码
class Solution {
    public ListNode sortList(ListNode head) {
        ListNode change = head;
        while(change != null) {
            ListNode search = change;
            int min = search.val;
            ListNode minNode = search;
            while(search != null) {
                if(search.val < min) {
                    minNode = search;
                    min = search.val;
                }
                search = search.next;
            }
            //交换元素
            int temp = change.val;
            change.val = min;
            minNode.val = temp;
            change = change.next;
        }
        return head;
    }
}

所以需要改进为归并排序,将时间复杂度降低到nlogn!

关于归并算法的逻辑,可以参考:十大经典排序算法-归并排序算法详解

解题代码:

java 复制代码
class Solution {
    public ListNode sortList(ListNode head) {
        int len = 0;
        ListNode pointer = head;
        while(pointer != null) {
            pointer = pointer.next;
            len++;
        }
        if(len <= 1) return head;
        int devide = len / 2;
        ListNode devideNode = head;
        int count = 1;
        while(count++ < devide) devideNode = devideNode.next;
        ListNode right = sortList(devideNode.next);
        devideNode.next = null;
        ListNode left = sortList(head);
        ListNode leftPointer = left;
        ListNode rightPointer = right;
        ListNode merge = new ListNode();
        ListNode mergePointer = merge;
        while(leftPointer != null && rightPointer != null) {
            if(leftPointer.val <= rightPointer.val) {
                mergePointer.next = new ListNode(leftPointer.val);
                leftPointer = leftPointer.next;
            }else {
                mergePointer.next = new ListNode(rightPointer.val);
                rightPointer = rightPointer.next;
            }
            mergePointer = mergePointer.next;
        }
        while(leftPointer != null) {
            mergePointer.next = new ListNode(leftPointer.val);
            leftPointer = leftPointer.next;
            mergePointer = mergePointer.next;
        }
        while(rightPointer != null) {
            mergePointer.next = new ListNode(rightPointer.val);
            rightPointer = rightPointer.next;
            mergePointer = mergePointer.next;
        }
        return merge.next;
    }
}

对递归的深度理解:

递归以及回溯算法其重中之重就是画出树形图,能画出树形图其实题目已经做出了80%。

  • 递归就是分叉向下的箭头
  • 回溯就是箭头由下向上的返回
  • 而递归逻辑就需要你站在那个节点中去思考
  • 最后注意递归的出口

注意这四点递归相关的题目基本都能应付!

合并 K 个升序链表

题目链接:23. 合并 K 个升序链表

解题思想:

利用的和上一题一样的思想,也就是使用分治法,只不过上一题每个元素是一个数,本题每个元素是一个链表。

解题代码:

java 复制代码
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if(lists.length == 0) return null;
        if(lists.length == 1) return lists[0];
        return merge(lists,0,lists.length - 1);
    }

    public ListNode merge(ListNode[] lists,int start,int end) {
        if(end < start || start < 0 || end >= lists.length) return null;
        if(start == end) return lists[start];
        int devide = (start + end) / 2;
        ListNode left = merge(lists,start,devide);
        ListNode right = merge(lists,devide + 1,end);
        ListNode leftPointer = left;
        ListNode rightPointer = right;
        ListNode result = new ListNode();
        ListNode resultPointer = result;
        //单层递归逻辑
        while(leftPointer != null && rightPointer != null) {
            if(leftPointer.val <= rightPointer.val) {
                resultPointer.next = new ListNode(leftPointer.val);
                leftPointer = leftPointer.next;
            }else {
                resultPointer.next = new ListNode(rightPointer.val);
                rightPointer = rightPointer.next;
            }
            resultPointer = resultPointer.next;
        }

        while(rightPointer != null) {
            resultPointer.next = new ListNode(rightPointer.val);
            rightPointer = rightPointer.next;
            resultPointer = resultPointer.next;
        }

        while(leftPointer != null) {
            resultPointer.next = new ListNode(leftPointer.val);
            leftPointer = leftPointer.next;
            resultPointer = resultPointer.next;
        }

        return result.next;
    }
}

LRU 缓存

题目链接:146. LRU 缓存

解题逻辑:

LinkedHashMap与LinkedSet在构造器中都可以指定访问顺序模式:

  • accessOrder = false(默认):按插入顺序迭代(元素添加顺序)。
  • accessOrder = true:按访问顺序迭代(最近访问的元素在末尾)
java 复制代码
public LinkedHashSet(int initialCapacity, float loadFactor, boolean accessOrder)
// 参数说明:初始容量、加载因子、accessOrder(true表示访问顺序)
LinkedHashMap<K, V> map = new LinkedHashMap<>(initialCapacity, loadFactor, true);

我们只需要自己维护一个缓存长度即可,然后超出长度使用迭代器进行删除即可。

注意点:

  • 游标最先开始在一个元素前面
  • 先调用next才能调用remove
  • Map与Set获得迭代器的方法不一样

解题代码:

sql 复制代码
class LRUCache {

    Map<Integer,Integer> cache = new LinkedHashMap<>(16, 0.75f, true);
    int len;

    public LRUCache(int capacity) {
        len = capacity;
    }
    
    public int get(int key) {
        return cache.get(key) == null ? -1 : cache.get(key);
    }
    
    public void put(int key, int value) {
        cache.put(key,value);
        if(cache.size() > len) {
            Iterator<Map.Entry<Integer, Integer>> entryIterator = cache.entrySet().iterator();
            entryIterator.next();
            entryIterator.remove();
        }
    }
}

哈希部分

字母异位词分组

题目链接:49. 字母异位词分组

解题逻辑:

  • 对每个字符串排序
  • 如果排序之后字符串一样,那么就放到map的同一个key下
  • 最后返回map的value集合

解题代码:

java 复制代码
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        Map<String, List<String>> record = new HashMap<>();
        for(String str : strs) {
            char[] temp = str.toCharArray();
            Arrays.sort(temp);
            String key = new String(temp);
            List<String> list = record.getOrDefault(key,new ArrayList<>());
            list.add(str);
            record.put(key,list);
        } 
        return new ArrayList<List<String>>(record.values());
        
    }
}

最长连续序列

题目链接:128. 最长连续序列

解题逻辑:

核心在于:找开头

  • 先将元素全部添加到hash表中
  • 遍历hash表
  • 如果存在前一个元素说明不是开头元素,直接跳过
  • 如果不存在前一个元素说明是开头元素,依次向后探测是否存在元素,获取以当前元素为头节点的最长序列长度
  • 返回最大值

解题代码:

java 复制代码
class Solution {
    public int longestConsecutive(int[] nums) {
        Set<Integer> set = new HashSet<>();
        for(int num : nums) set.add(num);
        int max = 0;
        for(int num : set) {
            if(set.contains(num - 1)) continue;
            else {
                int count = 1;
                int find = num + 1;
                while(set.contains(find)) {
                    count++;
                    find++;
                }
                if(count > max) max = count;
            }
        }
        return max;
    }
}

双指针部分

移动零

题目链接:283. 移动零

解题逻辑:

使用双指针法,一个指针遍历数组,一个指针用来填充元素,遇到非0元素,使用填充指针进行赋值,遍历完之后如果还有空位则使用0补齐。

解题代码:

java 复制代码
class Solution {
    public void moveZeroes(int[] nums) {
        int pointer = 0;
        for(int i = 0;i < nums.length;i++) if(nums[i] != 0) nums[pointer++] = nums[i];
        while(pointer < nums.length) nums[pointer++] = 0;
    }
}

盛最多水的容器

题目链接:11. 盛最多水的容器

解题思路:

面积公式:S(i,j)=min(h[i],h[j])×(j−i)

核心在于双指针怎么动:

  • 如果将短板指针向内移动,min(h[i],h[j])可能会变大也可能会变小
  • 如果将长版指针向内移动,min(h[i],h[j])可能不变也可能会变小
  • 因为移动长板指针一定不会让S变大,所以我们最终只需要将短板指针向内移动即可

解题代码:

java 复制代码
class Solution {
    public int maxArea(int[] height) {
        int left = 0;
        int right = height.length - 1;
        int max = 0;
        while(left < right) {
            int edge = Math.min(height[left],height[right]);
            int cur = edge * (right - left);
            if(cur > max) max = cur;
            if(height[left] <= height[right]) left += 1;
            else right -= 1;
        }
        return max;
    }
}

接雨水

题目链接:42. 接雨水

解题逻辑:

双指针解法的重点就在于指针怎么移动:

  • 找到数组的最大值,分为左右两个部分
  • 首先看左部分
  • 左指针找到一个非0的位置
  • 右指针从左指针所处位置向右依次寻找,直到该位置上的值大于左指针对应的值就停止
  • 我们将双指针所知区域进行面积累加
  • 右部分同理做镜像操作即可

解题代码:

java 复制代码
class Solution {
    public int trap(int[] height) {
        int result = 0;
        int left = 0;
        while(left < height.length && height[left] == 0) left++;
        int devide = 0;
        int max = 0;
        for(int i = 0;i < height.length;i++) {
            if(height[i] > max) {
                devide = i;
                max = height[i];
            }
        }
        while(left < height.length && left < devide) {
            int right = left + 1;
            while(right < height.length) {
                if(height[right] >= height[left]) {
                    int cur = getArea(Arrays.copyOfRange(height,left,right + 1));
                    result += cur;
                    left = right;
                    break;
                }
                right += 1;
            }
        }
        int right = height.length - 1;
        while(right > 0 && height[right] == 0) right--;
        while(right > devide) {
            left = right - 1;
            while(left >= devide) {
                if(height[left] >= height[right]) {
                    int cur = getArea(Arrays.copyOfRange(height,left,right + 1));
                    result += cur;
                    right = left;
                    break;
                }
                left -= 1;
            }
        }
        return result;
    }

    public int getArea(int[] nums){
        if(nums.length < 3) return 0;
        int edge = Math.min(nums[0],nums[nums.length - 1]);
        int area = edge * (nums.length - 2);
        for(int i = 1;i < nums.length - 1;i++) area -= nums[i];
        return area;
    }
}

滑动窗口部分

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

解题链接:438. 找到字符串中所有字母异位词

暴力法:

sql 复制代码
class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        List<Integer> result = new ArrayList<>();
        char[] ss = s.toCharArray();
        char[] pp = p.toCharArray();
        Arrays.sort(pp);
        for(int i = 0;i + pp.length - 1 < ss.length;i++) {
            char[] cur = Arrays.copyOfRange(ss,i,i + pp.length);
            Arrays.sort(cur);
            for(int j = 0;j < pp.length;j++) {
                if(cur[j] != pp[j]) break;
                if(cur[j] == pp[j] && j == pp.length - 1) result.add(i); 
            }
        }
        return result;
    }
}

使用滑动窗口:

在滑动窗口维护一个连续区间,通过数组对该区间字符进行计数,移动窗口,看计数是否与要求的子串相同。

java 复制代码
class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        List<Integer> result = new ArrayList<>();
        if(p.length() > s.length()) return result;
        char[] ss = s.toCharArray();
        char[] pp = p.toCharArray();
        int[] record1 = new int[26];
        int[] record2 = new int[26];
        for(int i = 0;i < pp.length;i++) {
            record1[ss[i] - 'a']++;
            record2[pp[i] - 'a']++;
        }
        if(Arrays.equals(record1,record2)) result.add(0);
        for(int i = 1;i + pp.length - 1 < ss.length;i++) {
            record1[ss[i - 1] - 'a']--;
            record1[ss[i + pp.length - 1] - 'a']++;
            if(Arrays.equals(record1,record2)) result.add(i);
        }
        return result;
    }
}

字串部分

和为 K 的子数组

题目链接:560. 和为 K 的子数组

解题思路:

这一题不能使用滑动窗口,因为数组元素可能为负数,那么扩张窗口(移动右指针)不一定使和变大,而收缩窗口(移动左指针)也不一定会使和变小!

所以这题我们首先想到可以通过前缀和 + 暴力枚举的形式,这样的话数组的和就转变成为了前缀和的差,然后双层for循环遍历所有子数组,对符合条件的和计数,代码如下:

java 复制代码
class Solution {
    public int subarraySum(int[] nums, int k) {
        int[] preSum = new int[nums.length];
        preSum[0] = nums[0];
        for(int i = 1;i < nums.length;i++) preSum[i] = preSum[i - 1] + nums[i];
        int result = 0;
        for(int left = 0;left < nums.length;left++) {
            for(int right = left;right < nums.length;right++) {
                if(left - 1 < 0 && preSum[right] == k) result++;
                else if(left - 1 >= 0 && preSum[right] - preSum[left - 1] == k) result++;
            }
        }
        return result;
    }
}

该解法可能会突破时间限制,可以通过前缀和 + Hash表 继续优化。

我们可以尝试将双层for循环优化为单层for循环:假设当前位置前缀和为 preSum,如果之前出现过前缀和为 preSum - k ,那么我们就找到了一个区间和为 k 的子数组。

而考虑到前缀和为 preSum - k可能不止一个,所以我们需要使用hash表来记录前缀和和次数,代码如下:

java 复制代码
class Solution {
    public int subarraySum(int[] nums, int k) {
        Map<Integer,Integer> map = new HashMap<>();
        map.put(0,1);
        int result = 0;
        int preSum = 0;
        for(int i = 0;i < nums.length;i++) {
            preSum += nums[i];
            if(map.containsKey(preSum - k)) result += map.get(preSum - k);
            map.put(preSum,map.getOrDefault(preSum,0) + 1);
        }
        return result;
    }
}

最小覆盖子串(滑动窗口)

题目链接:76. 最小覆盖子串

解题思路:

滑动窗口的典型应用:

  • 使用map维护窗口中的有效元素
  • for循环控制right指针使窗口扩充,如果为有效元素添加到map中
  • 同时根据left指针所指的元素进行窗口收缩
    • 如果left所指元素非有效元素,收缩
    • 如果left所指元素为有效元素,且超过了要求的个数,收缩

解题代码:

java 复制代码
class Solution {
    public String minWindow(String s, String t) {
        if(t.length() > s.length()) return "";
        char[] ss = s.toCharArray();
        Map<Character,Integer> map1 = new HashMap<>();
        Map<Character,Integer> map2 = new HashMap<>();
        for(char c : t.toCharArray()) {
            map1.put(c,0);
            map2.put(c,map2.getOrDefault(c,0) + 1);
        }
        int left = 0;
        int right;
        int min = Integer.MAX_VALUE;
        String result = "";
        for(right = 0;right < ss.length;right++) {
            if(map1.containsKey(ss[right])) map1.put(ss[right],map1.get(ss[right]) + 1);
            else continue;
            while(left < right && (!map1.containsKey(ss[left]) || map1.containsKey(ss[left]) && map1.get(ss[left]) > map2.get(ss[left]))) {
                if(map1.containsKey(ss[left])) map1.put(ss[left],map1.get(ss[left]) - 1);
                left++;
            }
            if(isGreaterOrEqual(map1,map2) && right - left < min) {
                min = right - left;
                result = s.substring(left,right + 1);
            }
        }
        return result;

    }

    public boolean isGreaterOrEqual(Map<Character, Integer> map1, Map<Character, Integer> map2) {
        //比较每个键对应的value:map1的value >= map2的value
        for (Map.Entry<Character, Integer> entry : map2.entrySet()) {
            Character key = entry.getKey();
            Integer value2 = entry.getValue();
            Integer value1 = map1.get(key);
            if (value1 < value2) {
                return false; 
            }
        }
        return true;
    }
}

数组部分

合并区间

题目链接:56. 合并区间

解题思路:

一共6种情况,其中一个情况无需变化:

我们将intervals按照左区间进行排序之后,这样所有合并都只会发生在相邻的两个区间之间,如此我们只需要一次for循环就可以解决问题。

解题代码:

java 复制代码
class Solution {
    public int[][] merge(int[][] intervals) {
        List<List<Integer>> result = new ArrayList<>();
        //intervals转换为List<List<Integer>>方便排序
        List<List<Integer>> lists = Arrays.stream(intervals)
                .map(list -> Arrays.stream(list).boxed().collect(Collectors.toList()))
                .sorted((list1,list2) -> list1.get(0) - list2.get(0))
                .collect(Collectors.toList());
        for(List<Integer> list : lists) {
            if(result.isEmpty()) {
                result.add(list);
                continue;
            }
            List<Integer> cur = result.get(result.size() - 1);
            if(list.get(1) < cur.get(0) || list.get(0) > cur.get(1)) result.add(new ArrayList<>(list));
            else if(list.get(0) < cur.get(0) && list.get(1) <= cur.get(1)) cur.set(0,list.get(0));
            else if(list.get(1) > cur.get(1) && list.get(0) >= cur.get(0)) cur.set(1,list.get(1));
            else if(list.get(0) < cur.get(0) && list.get(1) > cur.get(1)) {
                cur.set(0,list.get(0));
                cur.set(1,list.get(1));
            }
        }
        return result.stream().map(list -> list.stream().mapToInt(Integer::intValue).toArray()).toArray(int[][]::new);
        
    }
}

轮转数组(数组翻转)

题目链接:189. 轮转数组

解题逻辑:

将数组的元素向后轮转k个位置,其实可以转化为将数组的最后k个元素,放到数组头部去,然后其他元素后移。那么要想达到这种效果,我们可以先翻转整个数组,先让这k个元素到头部去,只是顺序不对,那么我们再对这k个元素进行一次翻转就可以了。至于翻转的操作使用双指针法即可。

解题代码:

java 复制代码
class Solution {
    public void rotate(int[] nums, int k) {
        if(k == nums.length) return;
        //先翻转整个数组
        reverse(nums,0,nums.length - 1);
        //再对两个部分分别进行翻转
        reverse(nums,0,(k - 1) % nums.length);
        reverse(nums,k % nums.length,nums.length - 1);
    }
    public void reverse(int[] nums,int left,int right) {
        while(left < right) {
            int temp = nums[left];
            nums[left] = nums[right];
            nums[right] = temp;
            left++;
            right--;
        }
    }
}

除自身以外数组的乘积(前缀表、后缀表)

题目链接:238. 除自身以外数组的乘积

解题思路:

这一题和前面560.和为 K 的子数组这一题很相似。都涉及到了使用数组的连续状态得出解,例如:

  • 560.和为 K 的子数组:使用到了数组连续的和来求解
  • 238. 除自身以外数组的乘积:使用到了数组连续的乘积来求解

那么我们可以通过构建前缀或者后缀表,从而减少时间复杂度。

解题代码:

java 复制代码
class Solution {
    public int[] productExceptSelf(int[] nums) {
        int[] prefix = new int[nums.length];
        int[] suffix = new int[nums.length];
        prefix[0] = nums[0];
        for(int i = 1;i < nums.length;i++) prefix[i] = prefix[i - 1] * nums[i];
        suffix[nums.length - 1] = nums[nums.length - 1];
        for(int i = nums.length - 2;i >= 0;i--) suffix[i] = suffix[i + 1] * nums[i];
        int[] result = new int[nums.length];
        for(int i = 0;i < nums.length;i++) {
            int pre = i - 1 < 0 ? 1 : prefix[i - 1];
            int suf = i + 1 >= nums.length ? 1 : suffix[i + 1];
            result[i] = pre * suf;
        }
        return result;
    }
}

缺失的第一个正数(原地Hash)

题目链接:41. 缺失的第一个正数

解题逻辑:

方法一:

直接使用stream流来做:

  • 去重
  • 过滤负数、0
  • 排序

得到的新数组,如果:

  • 长度为0或者第一个元素大于1,则直接返回1
  • 否则就依次 + 1匹配后一个元素

算法分析:

  • 时间复杂度:O(n log n)(受排序操作主导)
  • 空间复杂度:O(n)(受去重存储和新数组主导)

解题代码:

java 复制代码
class Solution {
    public int firstMissingPositive(int[] nums) {
        nums = Arrays.stream(nums).distinct().filter(num -> num > 0).sorted().toArray();
        if(nums.length == 0 || nums[0] > 1) return 1;
        else for(int i = 1;i < nums.length;i++) if(nums[i] != nums[i - 1] + 1) return nums[i - 1] + 1;
        return nums[nums.length - 1] + 1;
    }
}

方法二:原地Hash

首先明确我们要找的数一定在【1,n + 1】之间,n + 1是当1 ~ n在数组中都存在的时候才返回。所以我们现在的目标就变为将数组中的元素原地hash,hash函数为hash(i) = i - 1,hash完之后遍历一遍看哪个位置上的元素不符合要求。

注意交换的前提:

  • 在范围内【1,n】
  • 当前位置上的元素不符合hash函数
  • 要交换的位置上的元素也不符合hash函数(防止无限交换)

解题代码:

java 复制代码
class Solution {
    public int firstMissingPositive(int[] nums) {
        for(int i = 0;i < nums.length;i++) {
            while(nums[i] > 0 && nums[i] <= nums.length && nums[i] != i + 1 && nums[nums[i] - 1] != nums[i]) {
                int exchangeIndex = nums[i] - 1;
                int temp = nums[i];
                nums[i] = nums[exchangeIndex];
                nums[exchangeIndex] = temp;
            }
        }
        for(int i = 0;i < nums.length;i++) if(nums[i] != i + 1) return i + 1;
        return nums.length + 1;
    }
}
相关推荐
橘颂TA2 小时前
【剑斩OFFER】算法的暴力美学——最小覆盖字串
算法·c/c++·就业
wearegogog1232 小时前
基于混合蛙跳算法和漏桶算法的无线传感器网络拥塞控制与分簇新方法
网络·算法
Tiandaren3 小时前
大模型应用03 || 函数调用 Function Calling || 概念、思想、流程
人工智能·算法·microsoft·数据分析
2301_795167203 小时前
玩转Rust高级应用 如何进行理解Refutability(可反驳性): 模式是否会匹配失效
开发语言·算法·rust
小当家.1054 小时前
[LeetCode]Hot100系列.贪心总结+思想总结
算法·leetcode·职场和发展
墨雪不会编程4 小时前
数据结构—排序算法篇二
数据结构·算法·排序算法
ShineWinsu4 小时前
对于数据结构:堆的超详细保姆级解析—上
数据结构·c++·算法·计算机·二叉树·顺序表·
im_AMBER5 小时前
Leetcode 46
c语言·c++·笔记·学习·算法·leetcode
努力学算法的蒟蒻5 小时前
day09(11.6)——leetcode面试经典150
算法·leetcode·职场和发展