参考:
问题总览
| 序号 | 题目 | 完成 |
|---|
寻找重复数
| 序号 | 题目 | 完成 |
|---|---|---|
| 217. 存在重复元素 | ✅ | |
| 219. 存在重复元素 II | ✅ | |
| 220. 存在重复元素 III | ||
| 287. 寻找重复数 | ✅ | |
| 448. 找到所有数组中消失的数字 | ✅ | |
| 645. 错误的集合 | ✅ | |
| 442. 数组中重复的数据 | ✅ | |
| 652. 寻找重复的子树 | ||
| 187. 重复的DNA序列 | ||
| 459. 重复的子字符串 | ||
| 2352. 相等行列对 | ||
| 1207. 独一无二的出现次数 | ✅ |
// 利用hashmap暂存
// 时间复杂度:O(n),空间复杂度:O(n)
class Solution {
public boolean containsDuplicate(int[] nums) {
Map<Integer, Integer> map = new HashMap<>();
for (int num : nums) {
if (map.containsKey(num)) {
return true;
}
int count = map.getOrDefault(num, 0);
map.put(num, count + 1);
}
return false;
}
}
// 方法2:先排序,然后判断相邻元素是否相等
// 时间复杂度:O(nlogn),空间复杂度:O(logn)
class Solution {
public boolean containsDuplicate(int[] nums) {
Arrays.sort(nums);
for (int i = 0; i < nums.length - 1; i++) {
if ((nums[i] ^ nums[i + 1]) == 0) {
return true;
}
}
return false;
}
}
// 利用hashmap存储重复元素的索引
class Solution {
public boolean containsNearbyDuplicate(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(nums[i])) {
int idx = map.get(nums[i]);
// 满足条件
if (Math.abs(idx - i) <= k) {
return true;
}
}
map.put(nums[i], i);
}
return false;
}
}
// 穷举法
class Solution {
public boolean containsNearbyAlmostDuplicate(int[] nums, int indexDiff, int valueDiff) {
int len = nums.length;
for (int i = 0; i < len; i++) {
for (int j = i + 1; j <= i + indexDiff && j < len; j++) {
if (Math.abs(nums[j] - nums[i]) <= valueDiff) {
return true;
}
}
}
return false;
}
}
元素取值固定
这一类问题数字中的元素范围是固定的,比如告诉你只会出现在[1,n]中。
用nums自身作为存储空间,就不需要额为的空间了。 将i+1放入i的坑位,这样轮转替换一遍之后,只有消失的数字的位置上没有元素
// 方法一,稍微有点不好理解
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
List<Integer> ans = new ArrayList<>();
// 元素都是[1,n],索引从0开始
for (int i = 0; i < nums.length; i++) {
// 如果当前位置没有处理
if (nums[i] != i + 1) {
int nextIdx = nums[i];
while (nums[nextIdx - 1] != nextIdx) {
int tmp = nums[nextIdx - 1];
nums[nextIdx - 1] = nextIdx;
// 继续挪下一个
nextIdx = tmp;
}
}
}
for (int i = 0; i < nums.length; i++) {
if (nums[i] != i + 1) {
ans.add(i + 1);
}
}
return ans;
}
}
class Solution {
public int[] findErrorNums(int[] nums) {
int[] ans = new int[2];
int n = nums.length;
// 先计算出总和
int sum = 0;
for (int num : nums) {
sum += num;
}
// 元素都是[1,n],索引从0开始
for (int i = 0; i < n; i++) {
// 如果当前位置没有处理
if (nums[i] != i + 1) {
int nextIdx = nums[i];
while (nums[nextIdx - 1] != nextIdx) {
int tmp = nums[nextIdx - 1];
nums[nextIdx - 1] = nextIdx;
// 继续挪下一个
nextIdx = tmp;
}
}
}
for (int i = 0; i < n; i++) {
if (nums[i] != i + 1) {
// 找到了缺失的元素
ans[1] = i + 1;
break;
}
}
// 如果缺失的元素是5,重复的元素是2,那么会有两个2,少一个5
// 预计的[1,n]的总和是20, 实际的总和sum是17,缺失的元素是5
ans[0] = sum + ans[1] - (n + 1) * n / 2;
return ans;
}
}
方法一其实只能计算只有一个重复元素这种场景。 参考了labuladong的解法,其实并不需要用数组轮转把值都替换上去,可以把当前元素乘以-1,这样查找重复的值会很方便,可以参考442。
class Solution {
public List<Integer> findDuplicates(int[] nums) {
List<Integer> ans = new ArrayList<>();
// 元素都是[1,n],索引从0开始
for (int i = 0; i < nums.length; i++) {
int nextIdx = Math.abs(nums[i]) - 1;
if (nums[nextIdx] < 0) {
ans.add(Math.abs(nums[i]));
} else {
nums[nextIdx] *= -1;
}
}
return ans;
}
}
元素取值固定(不可修改原数组)
//方法一:二分查找
class Solution {
public int findDuplicate(int[] nums) {
int n = nums.length;
int l = 1, r = n - 1, ans = -1;
// cnt[i] 表示nums数组中小于等于i的数有多少个
// i的范围为[1,n]
// 4, 3, 2, 7, 8, 6, 5, 3, 1
// i | 1 2 3 4 5 6 7 8 9
// cnt | 1 2 4 5 6 7 8 9 9
// 假设重复的数是target
// [1, target-1]里所有的cnt[i]都小于等于i
// [target, n]里所有的cnt[i]都大于i
while (l <= r) {
int mid = (l + r) >> 1;
int cnt = 0;
// 计算cnt
for (int i = 0; i < n; ++i) {
if (nums[i] <= mid) {
cnt++;
}
}
// 找cnt,cnt是大于i的第一个数,可以理解为找右边界
if (cnt <= mid) {
l = mid + 1;
} else {
r = mid - 1;
ans = mid;
}
}
return ans;
}
}
class Solution {
public boolean uniqueOccurrences(int[] arr) {
// 出现次数里没有重复数字
Set<Integer> counts = new HashSet<>();
Map<Integer, Integer> map = new HashMap<>();
for (int a : arr) {
map.put(a, map.getOrDefault(a, 0) + 1);
}
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
if (counts.contains(entry.getValue())) {
return false;
}
counts.add(entry.getValue());
}
return true;
}
}
最长重复子串
| 序号 | 题目 | 完成 |
|---|---|---|
| 3. 无重复字符的最长子串 | ✅ | |
| 1044. 最长重复子串 |
删除重复项
| 序号 | 题目 | 完成 |
|---|---|---|
| 26. 删除排序数组中的重复项 | ✅ | |
| 80. 删除有序数组中的重复项 II | ✅ | |
| 83. 删除排序链表中的重复元素 | ✅ | |
| 82. 删除排序链表中的重复元素 II | ✅ | |
| 字典序 | 316. 去除重复字母 | ✅ |
| 1081. 不同字符的最小子序列 | ✅ | |
| 1047. 删除字符串中的所有相邻重复项 | ✅ |
class Solution {
int removeDuplicates(int[] nums) {
if (nums.length == 0) {
return 0;
}
int slow = 0, fast = 0;
while (fast < nums.length) {
if (nums[fast] != nums[slow]) {
slow++;
// 维护 nums[0..slow] 无重复
nums[slow] = nums[fast];
}
fast++;
}
// 数组长度为索引 + 1
return slow + 1;
}
}
class Solution {
public int removeDuplicates(int[] nums) {
// 快慢指针,维护 nums[0..slow] 为结果子数组
int slow = 0, fast = 0;
// 记录一个元素重复的次数
int count = 0;
while (fast < nums.length) {
if (nums[fast] != nums[slow]) {
slow++;
nums[slow] = nums[fast];
} else if (slow < fast && count < 2) {
// 当一个元素重复次数不到 2 次时,也
slow++;
nums[slow] = nums[fast];
}
fast++;
count++;
if (fast < nums.length && nums[fast] != nums[fast - 1]) {
// 遇到不同的元素
count = 0;
}
}
// 数组长度为索引 + 1
return slow + 1;
}
}
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode slow = head;
ListNode fast = head.next;
ListNode p = head;
while (fast != null) {
if (slow.val == fast.val) {
fast = fast.next;
slow.next = fast;
} else {
fast = fast.next;
slow = slow.next;
}
}
return p;
}
}
//leetcode submit region end(Prohibit modification and deletion)
//labuladong的思路,和删除数组里的重复元素一样的思路
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null) return null;
ListNode slow = head, fast = head;
while (fast != null) {
if (fast.val != slow.val) {
// nums[slow] = nums[fast];
slow.next = fast;
// slow++;
slow = slow.next;
}
// fast++
fast = fast.next;
}
// 断开与后面重复元素的连接
slow.next = null;
return head;
}
}
与83的区别是,如果重复连自己也要删掉。
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null) {
return head;
}
ListNode dummy = new ListNode(0, head);
ListNode cur = dummy;
while (cur.next != null && cur.next.next != null) {
if (cur.next.val == cur.next.next.val) {
int x = cur.next.val;
while (cur.next != null && cur.next.val == x) {
// 将cur的next不断的指向新的节点,直到找到和自己不同的
cur.next = cur.next.next;
}
} else {
cur = cur.next;
}
}
return dummy.next;
}
}
相同问题:1081. 不同字符的最小子序列
去除重复字母,当出现字母重复时,删除的重复字母,要让字母序最小。
class Solution {
public String smallestSubsequence(String s) {
boolean[] instack = new boolean[26];
int[] count = new int[26];
Stack<Character> stack = new Stack<>();
for (char c : s.toCharArray()) {
count[c - 'a']++;
}
for (int i = 0; i < s.length(); i++) {
char cur = s.charAt(i);
int idx = cur - 'a';
count[idx]--;
if (instack[idx]) {
continue;
}
while (!stack.isEmpty() && stack.peek() > cur && count[stack.peek() - 'a'] >= 1) {
instack[stack.pop()-'a'] = false;
}
stack.push(cur);
instack[idx] = true;
}
StringBuilder sb = new StringBuilder();
while (!stack.isEmpty()) {
sb.append(stack.pop());
}
return sb.reverse().toString();
}
}
//leetcode submit region end(Prohibit modification and deletion)
首先写了一个递归,超时了。
class Solution {
public String removeDuplicates(String s) {
//abbaca
int len = s.length();
if (len == 1) {
return s;
}
int i = 0;
while (i < len - 1) {
if (s.charAt(i) == s.charAt(i + 1)) {
if (i == len - 2) {
return removeDuplicates(s.substring(0, i));
} else {
return removeDuplicates(s.substring(0, i) + s.substring(i + 2, len));
}
}
i++;
}
return s;
}
}
看了官方题解,原来可以用栈,真是蠢了。这个题目应该是栈的经典用法吧。
class Solution {
public String removeDuplicates(String s) {
int len = s.length();
if (len == 1) {
return s;
}
Stack<Character> stack = new Stack<>();
char[] chars = s.toCharArray();
for (int i = 0; i < len; i++) {
if (!stack.isEmpty() && stack.peek() == chars[i]) {
stack.pop();
} else {
stack.push(chars[i]);
}
}
StringBuilder sb = new StringBuilder();
while (!stack.isEmpty()) {
sb.append(stack.pop());
}
sb.reverse();
return sb.toString();
}
}
子数组或子串
| 序号 | 题目 | 完成 |
|---|---|---|
| 2215. 找出两数组的不同 | ✅ | |
| 1657. 确定两个字符串是否接近 | ✅ | |
| 718. 最长重复子数组 | ||
| 395. 至少有 K 个重复字符的最长子串 | ||
| 424. 替换后的最长重复字符 | ||
| 1156. 单字符重复子串的最大长度 | ||
| 1100. 长度为 K 的无重复字符子串 |
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
public List<List<Integer>> findDifference(int[] nums1, int[] nums2) {
Set<Integer> set1 = new HashSet<>();
Set<Integer> set2 = new HashSet<>();
for (int num : nums1) {
set1.add(num);
}
for (int num : nums2) {
set2.add(num);
}
List<Integer> list1 = new ArrayList<>();
for (int num1 : set1) {
if (set2.contains(num1)) {
continue;
}
list1.add(num1);
}
List<Integer> list2 = new ArrayList<>();
for (int num2 : set2) {
if (set1.contains(num2)) {
continue;
}
list2.add(num2);
}
List<List<Integer>> ans = new ArrayList<>();
ans.add(list1);
ans.add(list2);
return ans;
}
}
//leetcode submit region end(Prohibit modification and deletion)
返回两个数组中公共的、最长的子数组,子数组有前后关系,是一个序列。