重复数

191 阅读2分钟

参考:

  1. 如何同时寻找缺失和重复的元素
  2. 一道数组去重的算法题把我整不会了

问题总览

序号题目完成

寻找重复数

序号题目完成
217. 存在重复元素
219. 存在重复元素 II
220. 存在重复元素 III
287. 寻找重复数
448. 找到所有数组中消失的数字
645. 错误的集合
442. 数组中重复的数据
652. 寻找重复的子树
187. 重复的DNA序列
459. 重复的子字符串
2352. 相等行列对
1207. 独一无二的出现次数

217. 存在重复元素

// 利用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;
    }
}

219. 存在重复元素 II

// 利用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;
    }
}

220. 存在重复元素 III

// 穷举法
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]中。

448. 找到所有数组中消失的数字

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

645. 错误的集合

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。

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

元素取值固定(不可修改原数组)

287. 寻找重复数

//方法一:二分查找
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;
    }
}

1207. 独一无二的出现次数

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. 删除字符串中的所有相邻重复项

26. 删除排序数组中的重复项

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

80. 删除有序数组中的重复项 II

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

83. 删除排序链表中的重复元素

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

82. 删除排序链表中的重复元素 II

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

316. 去除重复字母

相同问题: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)

1047. 删除字符串中的所有相邻重复项

首先写了一个递归,超时了。

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 的无重复字符子串

1657. 确定两个字符串是否接近

2215. 找出两数组的不同

//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)

718. 最长重复子数组

返回两个数组中公共的、最长的子数组,子数组有前后关系,是一个序列。