当前位置: 首页 > news >正文

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

文章目录

  • 链表部分
    • 相交链表
    • 环形链表
    • 回文链表
    • 合并两个有序链表
    • 两数相加
    • 总结:两个链表操作类型题目
    • K 个一组翻转链表
    • 随机链表的复制
    • 排序链表(对递归的理解)
    • 合并 K 个升序链表
    • LRU 缓存
  • 哈希部分
    • 字母异位词分组
    • 最长连续序列
  • 双指针部分
    • 移动零
    • 盛最多水的容器
    • 接雨水
  • 滑动窗口部分
    • 找到字符串中所有字母异位词
  • 字串部分
    • 和为 K 的子数组
    • 最小覆盖子串(滑动窗口)
  • 数组部分
    • 合并区间
    • 轮转数组(数组翻转)
    • 除自身以外数组的乘积(前缀表、后缀表)
    • 缺失的第一个正数(原地Hash)

链表部分

相交链表

题目链接:160. 相交链表

解题思路:

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

解题代码:

/*** 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记录走过的节点

解题代码:

/*** 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. 回文链表

解题逻辑:

基本逻辑就是:

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

解题代码:

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. 合并两个有序链表

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

解题代码:

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表示进位符号。

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 个一组翻转链表

解题思路:

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

解题代码:

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方,最后会超时:

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!

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

解题代码:

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 个升序链表

解题思想:

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

解题代码:

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:按访问顺序迭代(最近访问的元素在末尾)
public LinkedHashSet(int initialCapacity, float loadFactor, boolean accessOrder)
// 参数说明:初始容量、加载因子、accessOrder(true表示访问顺序)
LinkedHashMap<K, V> map = new LinkedHashMap<>(initialCapacity, loadFactor, true);

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

注意点:

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

解题代码:

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集合

解题代码:

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表
  • 如果存在前一个元素说明不是开头元素,直接跳过
  • 如果不存在前一个元素说明是开头元素,依次向后探测是否存在元素,获取以当前元素为头节点的最长序列长度
  • 返回最大值

解题代码:

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补齐。

解题代码:

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变大,所以我们最终只需要将短板指针向内移动即可

解题代码:

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的位置
  • 右指针从左指针所处位置向右依次寻找,直到该位置上的值大于左指针对应的值就停止
  • 我们将双指针所知区域进行面积累加
  • 右部分同理做镜像操作即可

解题代码:

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. 找到字符串中所有字母异位词

暴力法:

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;}
}

使用滑动窗口:

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

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循环遍历所有子数组,对符合条件的和计数,代码如下:

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表来记录前缀和和次数,代码如下:

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所指元素为有效元素,且超过了要求的个数,收缩

解题代码:

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的valuefor (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循环就可以解决问题。

解题代码:

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个元素进行一次翻转就可以了。至于翻转的操作使用双指针法即可。
在这里插入图片描述

解题代码:

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. 除自身以外数组的乘积:使用到了数组连续的乘积来求解

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

解题代码:

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)(受去重存储和新数组主导)

解题代码:

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函数(防止无限交换)

解题代码:

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;}
}
http://www.dtcms.com/a/577316.html

相关文章:

  • 新能源网站开发网站没有做301定向
  • 【Ubuntu】新服务器配置完全指南
  • 2026年PMI-PBA商业分析师报考时间+条件全解析
  • 计算机图形学·9 几何学
  • 基于MATLAB的梯度下降法实现
  • dw制作简单网站模板下载网站建设工作会议讲话
  • 如何优化多表查询sql?
  • 64QAM信号的数字预失真处理(MATLAB实现)
  • 网站模板下载之后如何修改公司官网怎么设计
  • 崇信县门户网站留言首页杭州做商务网站
  • 只出现一次的数字 II(二)
  • Linux系统编程:(六)深入理解 Linux 软件包管理器——从原理到 yum 实战全攻略
  • NoSql数据库概念
  • OCR 新范式!DeepSeek 以「视觉压缩」替代传统字符识别;Bald Classification数据集助力高精度人像分类
  • jQuery 入门学习教程,从入门到精通,AJAX在jQuery中的应用 —— 详细知识点与实战案例(14)
  • seo优化标签北京seo百度推广
  • joomla 网站模板.net 手机网站源码下载
  • PL27A1旺玖5Gbps USB 3.0主机到主机桥接控制芯片,超高速USB3.0数据对拷线双机跨屏共享文件和数据的USB对拷芯片
  • 理解预处理器(Sass/Less)
  • Java_LinkedHashSet源码分析
  • 基于大数据的信贷风险评估的数据可视化分析与预测系统
  • 《算法通关指南:数据结构和算法篇 --- 栈相关算法题》--- 1.括号序列
  • 网站设计的基本流程是什么苏州高端模板建站
  • Web认证
  • 电子商务网站建设与推广实务江门市智企互联网站建设
  • Access自定义导出HTML报表
  • 【C++ 5 种类型转换深度对比与实践指南】
  • Kubernetes Service 详解:服务暴露与流量管理全指南
  • HTML onclick用法
  • 如何理解HTML语义化