【Leetcode】442. Find All Duplicates in an Array

news/2024/7/4 10:05:19

题目地址:

https://leetcode.com/problems/find-all-duplicates-in-an-array/

给定一个长 n n n的int数组,其中每个数字都属于 1 ∼ n 1\sim n 1n,其中有些数字出现一次,有些两次。返回那些出现过两次的数。

基本思路是,边遍历数组,边把当前的数 k k k给swap到数组下标 k − 1 k-1 k1的位置上直到不需要继续swap为止,遍历结束后,只需扫描一遍,看哪些数字不在自己“应该”在的位置上,那些数字就是出现过两次的数字。例如,对于 ( 4 , 3 , 2 , 7 , 8 , 2 , 3 , 1 ) (4, 3, 2, 7, 8, 2, 3, 1) (4,3,2,7,8,2,3,1)我们做如下交换: ( 7 , 3 , 2 , 4 , 8 , 2 , 3 , 1 ) (\textbf{7}, 3, 2, \textbf{4}, 8, 2, 3, 1) (7,3,2,4,8,2,3,1) ( 3 , 3 , 2 , 4 , 8 , 2 , 7 , 1 ) (\textbf{3}, 3, 2, 4, 8, 2, \textbf{7}, 1) (3,3,2,4,8,2,7,1) ( 2 , 3 , 3 , 4 , 8 , 2 , 7 , 1 ) (\textbf{2}, 3, \textbf{3}, 4, 8, 2, 7, 1) (2,3,3,4,8,2,7,1) ( 3 , 2 , 3 , 4 , 8 , 2 , 7 , 1 ) (\textbf{3}, \textbf{2}, 3, 4, 8, 2, 7, 1) (3,2,3,4,8,2,7,1) ( 3 , 2 , 3 , 4 , 1 , 2 , 7 , 8 ) (3,2,3,4,\textbf{1},2,7,\textbf{8}) (3,2,3,4,1,2,7,8) ( 1 , 2 , 3 , 4 , 3 , 2 , 7 , 8 ) (\textbf{1}, 2, 3, 4, \textbf{3}, 2, 7, 8) (1,2,3,4,3,2,7,8)遍历结束。我们发现,凡是只出现过一次的数,都和自己应该呆的序号一一对应,而出现过两次的数,必然其中一次出现在自己“不应该呆的位置”上。也就是说,遍历过程中我们做的事是,每遍历到一个数 n u m s [ i ] nums[i] nums[i],我们就看 n u m s [ n u m s [ i ] − 1 ] nums[nums[i]-1] nums[nums[i]1]是否等于 n u m s [ i ] nums[i] nums[i],如果不等,我们就换过去,直到相等为止。这样遍历到结尾,每个在“不应该在的位置”上的数即为所求。代码如下:

class Solution {
    public List<Integer> findDuplicates(int[] nums) {
        for (int i = 0; i < nums.length; i++) {
            while (nums[nums[i] - 1] != nums[i]) {
                swap(nums, nums[i] - 1, i);
            }
        }
        
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != i + 1) {
                list.add(nums[i]);
            }
        }
        
        return list;
    }
    
    private void swap(int[] nums, int i, int j) {
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
}

时间复杂度 O ( n ) O(n) O(n),空间 O ( 1 ) O(1) O(1)

算法正确性证明:
首先证明,一遍遍历后,对于任意 n u m s nums nums中的数字 k k k n u m s [ k − 1 ] = k nums[k-1]=k nums[k1]=k。由于遍历是从左到右一个一个扫描过去的,所以 n u m s nums nums中的每一个数必然会被扫描到,而一旦被扫描到,算法就会保证它应该呆的位置上的数正好等于它。所以结论成立。接下来分情况讨论:
1、如果某个数 k k k只出现过一次,那么由于第一个for循环结束后 n u m s [ k − 1 ] = k nums[k-1]=k nums[k1]=k,所以第二个for循环里 n u m s [ i ] = k nums[i]=k nums[i]=k当且仅当 i = k − 1 i=k-1 i=k1,换句话说 k k k不会被加进list里去;
2、如果某个数 k k k出现过两次,那么由于第一个for循环结束后 n u m s [ k − 1 ] = k nums[k-1]=k nums[k1]=k,所以第二个for循环里满足 n u m s [ i ] = k nums[i]=k nums[i]=k i i i至少有两个,其中一个是 k − 1 k-1 k1,另一个不妨设为 i ′ i' i,那么第二个for循环遍历到 i ′ i' i的时候, n u m s [ i ′ ] = k ≠ i ′ + 1 nums[i']=k\ne i'+1 nums[i]=k=i+1,这时候会把 n u m s [ i ′ ] nums[i'] nums[i]也就是 k k k加进list里去。
所以一个数被加进list当且仅当它出现过两次,所以算法正确。

算法复杂度证明:
空间复杂度显然。对于时间复杂度只需证明swap最多执行 n n n次即可。由于每次swap完后,当前的数字 k k k就已经挪到了 n u m s [ k − 1 ] nums[k-1] nums[k1],我们可以想象成swap完后 n u m s [ k − 1 ] nums[k-1] nums[k1]被染了色,显然如果进行了染色,每个位置只会被染色一次,所以染色次数显然不超过数组长度,所以swap执行次数一定不会超过数组长度。所以时间复杂度是 O ( n ) O(n) O(n)

我们有另一种观点来看这道题,可以借鉴静态链表的思想。对于 ( 4 , 3 , 2 , 7 , 8 , 2 , 3 , 1 ) (4, 3, 2, 7, 8, 2, 3, 1) (4,3,2,7,8,2,3,1)我们可以分成几条链,第一条是: 4 → 7 → 3 → 2 → 3 4\rightarrow 7\rightarrow 3\rightarrow 2\rightarrow 3 47323
( 4 , 3 , 2 , 7 , 8 , 2 , 3 , 1 ) (\textbf{4}, \textbf{3}, \textbf{2}, \textbf{7}, 8, 2, \textbf{3}, 1) (4,3,2,7,8,2,3,1)当第一遍循环后,交换了 4 4 4次(正好是链的路径长度)之后,这条链上的数已经归位了: ( 3 , 2 , 3 , 4 , 8 , 2 , 7 , 1 ) (\textbf{3},\textbf{2}, \textbf{3}, \textbf{4}, 8, 2, \textbf{7}, 1) (3,2,3,4,8,2,7,1)也就是说,将来只要遍历到链上的位置,就不会再swap了。第二条链是: 8 → 1 → 3 8\rightarrow1\rightarrow3 813 ( 3 , 2 , 3 , 4 , 8 , 2 , 7 , 1 ) (\textbf{3},2, 3, 4, \textbf{8}, 2, 7, \textbf{1}) (3,2,3,4,8,2,7,1)交换了两次: ( 1 , 2 , 3 , 4 , 3 , 2 , 7 , 8 ) (\textbf{1},2, 3, 4, \textbf{3}, 2, 7, \textbf{8}) (1,2,3,4,3,2,7,8)可以看出链的总长度不可能超过数组总长度 + 重复数字数,当然也不会超过 O ( n ) O(n) O(n),所以时间复杂度是 O ( n ) O(n) O(n)


http://www.niftyadmin.cn/n/3647499.html

相关文章

游戏元素设计与当代艺术变迁

商务时间 5月26日论当代艺术收藏到底是“蛋糕”还是“泡沫”当代艺术收藏家、投资家&#xff0c;选择古玩还是当代艺术&#xff1f;申明&#xff1a; 本人非专业的游戏策划&#xff0c;也不是专业的市场企划。只是有一颗心&#xff0c;用心去做事&#xff0c;做一件不同感受的事…

Spring-MVC万字长文笔记,威力加强版

接口概述: 接口是Java语言中的一种引用类型&#xff0c;是方法的"集合"&#xff0c;所以接口的内部主要就是定义方法&#xff0c;包含常量,抽象方法&#xff08;JDK 7及以前&#xff09;&#xff0c;额外增加默认方法和静态方法&#xff08;JDK 8&#xff09;,额外增…

【Leetcode】56. Merge Intervals

题目地址&#xff1a; https://leetcode.com/problems/merge-intervals/ 给定一列区间&#xff0c;合并完成后返回。 法1&#xff1a;排序后逐个merge。先将区间按照左端点排序&#xff0c;如果左端点相等就右端点小的在前面&#xff0c;然后从左到右扫描这些区间。每次扫描…

SpringBoot:四年Java面试遇到的问题整理,已拿到offer

前言 今天逛论坛&#xff0c;看到了一位35岁的老程序员发的博文&#xff0c;看完内容后我又活了&#xff0c;35岁挑战华为社招&#xff0c;竟然凭实力在半个月内经历4轮面试后成功拿到了offer,不得不佩服这位大哥&#xff0c;35岁还这么强我们这些后辈还怕啥&#xff01; 当然…

图片镂空算法集合[图]

在开发界面及棋牌游戏过程中&#xff0c;需要很多镂空的图片,而且图片形式一般比较固定.所以封装了几种常见的镂空方法.1. 用于没有掩码图,只有指定透明色,不进行伸缩voidDrawTransBitmap( HDC hdcDest, //目标DCintnXOriginDest, //目标X偏移intnYOriginDest…

如何调试MFC中的内存泄漏[转帖]

首先&#xff0c;应该是MFC报告我们发现内存泄漏。注意&#xff1a;要多运行几次&#xff0c;以确定输出的内容不变&#xff0c;特别是{}之间的数值&#xff0c;不能变&#xff0c;否则下面的方法就不好用了。我们来看看&#xff1a;F:/CodeSample/Test/TestPipe/LeakTest/Main…

Spring事务是如何传播的?真香系列

前言 互联网时代&#xff0c;瞬息万变。一个小小的走错&#xff0c;就有可能落后于别人。我们没办法去预测任何行业、任何职业未来十年会怎么样&#xff0c;因为未来谁都不能确定。只能说只要有互联网存在&#xff0c;程序员依然是个高薪热门行业。只要跟随着时代的脚步&#…

【Leetcode】739. Daily Temperatures

题目地址&#xff1a; https://leetcode.com/problems/daily-temperatures/ 题目大意是&#xff0c;对于数组中每个数字&#xff0c;找出右边第一个比它大的数字&#xff0c;记录下标的差最后返回。典型单调栈的应用。维护一个单调下降的栈&#xff0c;如果栈空或者栈顶大于等…