LeetCode - 缺失数字的数组查找总结(位置交换法)

缺失的第一个正数

给定一个未排序的整数数组,找出其中没有出现的最小的正整数。说明: 你的算法的时间复杂度应为O(n),并且只能使用常数级别的空间。

示例 1: 输入: [1,2,0], 输出: 3
示例 2: 输入: [3,4,-1,1], 输出: 2
示例 3: 输入: [7,8,9,11,12], 输出: 1

分析: 我们使用一种 “座位交换法" 来达到in-place 的目的:从第一个位置开始,让座位上的乘客走到自己应该坐的位置,并让该位置的人坐到第一个位置。一直进行这样的操作,直到第一个位置的人坐到自己位置。不过有时候我们知道,有的人总喜欢逃票。因此终止条件就是,一旦发现第一个位置的人逃票(票号 <= 0,或 >= 最大座位号),则终止交换。
对第二到N个位置做相同的操作。

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        for (int i = 0; i < nums.size(); ++i) {
            while (0 < nums[i] && nums[i] <= nums.size() && nums[i] != nums[nums[i]-1]) {
                swap(nums[i], nums[nums[i]-1]);
            }
        }
        for (int i = 0; i < nums.size(); ++i) {
            if (nums[i] != i+1) {
                return i+1;
            }
        }
        return nums.size() + 1;
    }
};

缺失数字

给定一个包含 0, 1, 2, …, n 中 n 个数的序列,找出 0 … n 中没有出现在序列中的那个数。说明: 你的算法应具有线性时间复杂度。你能否仅使用额外常数空间来实现。

示例 1: 输入: [3,0,1], 输出: 2
示例 2: 输入: [9,6,4,2,3,5,7,0,1],输出: 8

分析: 位置交换法,数学公式法:求出 [0…n]的和,减去数组中所有数的和,就得到了缺失的数字。

class Solution {
public:
	int missingNumber(vector<int>& nums) {
		nums.push_back(-1);
		for (int i = 0; i < nums.size(); i++) {
			while (nums[i]!=-1&&nums[i] != nums[nums[i]]){
				swap(nums[i],nums[nums[i]]);
	
			}
		}
		for (int i = 0; i < nums.size(); i++) {
			if (nums[i] == -1)
				return i;
		}
	return 0;
	}
};
class Solution {
public:
int missingNumber(vector<int>& nums) {
    int length=nums.size();
    int expectedSum = length*(length + 1)/2;
    int actualSum = 0;
    for (int num : nums) actualSum += num;
    return expectedSum - actualSum;
	}
};

寻找重复数

给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。

示例 1: 输入: [1,3,4,2,2], 输出: 2
示例 2: 输入: [3,1,3,4,2], 输出: 3

说明:不能更改原数组(假设数组是只读的)。只能使用额外的 O(1) 的空间。时间复杂度小于 O(n^2) 。数组中只有一个重复的数字,但它可能不止重复出现一次。

分析1: 由于是查找问题,我们不难想到通过二分搜索法,但是这里我们要将原来的二分搜索法做一些调整。我们首先计算mid,然后我们统计数组中小于等于mid元素的个数k,如果k<=mid的话,那么说明重复值在[mid+1,n]之间,否则的话重复值在[1,mid]之间。实际上这个问题的思想来源于抽屉原理。
时间复杂度o(nlogn)。

class Solution {
public:
    int findDuplicate(vector<int> &nums) {
        int len = nums.size();
        int left = 0;
        int right = len - 1;

        while (left < right) {
            int mid = left + (right - left) / 2;

            int cnt = 0;
            for (int num:nums) {
                if (num <= mid) {
                    cnt++;
                }
            }

            // 根据抽屉原理,小于等于 4 的数的个数如果严格大于 4 个,
            // 此时重复元素一定出现在 [1, 4] 区间里

            if (cnt > mid) {
                // 重复的元素一定出现在 [left, mid] 区间里
                right = mid;
            } else {
                // if 分析正确了以后,else 搜索的区间就是 if 的反面
                // [mid + 1, right]
                // 注意:此时需要调整中位数的取法为上取整
                left = mid + 1;
            }
        }
        return left;
    }
};

分析2: o(n)解法 环形链表的思想。


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

给定一个范围在 1 ≤ a[i] ≤ n ( n = 数组大小 ) 的 整型数组,数组中的元素一些出现了两次,另一些只出现一次。找到所有在 [1, n] 范围之间没有出现在数组中的数字。您能在不使用额外空间且时间复杂度为O(n)的情况下完成这个任务吗? 你可以假定返回的数组不算在额外空间内。

示例: 输入:[4,3,2,7,8,2,3,1], 输出:[5,6]

分析: 位置交换法

class Solution {
public:
	vector<int> findDisappearedNumbers(vector<int>& nums) {
		int length = nums.size();
		for (int i = 0; i < nums.size(); i++) {
			while (nums[i] != nums[nums[i]-1]) {
				swap(nums[i], nums[nums[i]-1]);
			}
		}
		vector<int> res;
		for (int i = 0; i < nums.size(); i++) {
			if (nums[i] != i + 1)
				res.push_back(i+1);
		}
		return res;
	}
};

情侣牵手

N 对情侣坐在连续排列的 2N 个座位上,想要牵到对方的手。 计算最少交换座位的次数,以便每对情侣可以并肩坐在一起。 一次交换可选择任意两人,让他们站起来交换座位。人和座位用 0 到 2N-1 的整数表示,情侣们按顺序编号,第一对是 (0, 1),第二对是 (2, 3),以此类推,最后一对是 (2N-2, 2N-1)。这些情侣的初始座位 row[i] 是由最初始坐在第 i 个座位上的人决定的。

示例 1: 输入: row = [0, 2, 1, 3], 输出: 1。解释: 我们只需要交换row[1]和row[2]的位置即可。
示例 2: 输入: row = [3, 2, 0, 1],输出: 0。解释: 无需交换座位,所有的情侣都已经可以手牵手了。

说明: len(row) 是偶数且数值在 [4, 60]范围内。可以保证row 是序列 0…len(row)-1 的一个全排列。

分析1: 贪心交换,
(1)按排找情侣,第一排如果不是一对情侣,找第一个人的情侣在哪,将第二个人与目标情侣换位置;如果是一对情侣,则直接找第二排情侣;
(2)在找第二排情况时,按(1)步骤即可;直到所有排都已经情侣配对;
记录换座位的次数即可;

时间复杂度: O(N^2),其中 N 为情侣对的数量。空间复杂度: O(1),互相交换不需要开辟额外的空间。

class Solution {
public:
	int minSwapsCouples(vector<int>& row) {
		int rowSize = row.size();
		int dest;
		int cnt = 0;
		for (int i = 0; i < rowSize - 1; i = i + 2) {
			dest = (row[i] % 2 == 0) ? (row[i] + 1) : (row[i] - 1);
			if (dest == row[i + 1]) {
				continue;
			}
			for (int j = i + 2; j < rowSize; j++) {
				if (row[j] == dest) {
					row[j] = row[i + 1];
					row[i + 1] = dest;
					cnt++;
					break;
				}
			}
		}
		return cnt;

	}
};

分析2: 并查集。在计算机科学中,并查集是一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。有一个联合-查找算法(Union-find Algorithm)定义了两个用于此数据结构的操作:

  1. Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
  2. Union:将两个子集合并成同一个集合。

由于支持这两种操作,一个不相交集也常被称为联合-查找数据结构(Union-find Data Structure)或合并-查找集合(Merge-find Set)。为了更加精确的定义这些方法,需要定义如何表示集合。一种常用的策略是为每个集合选定一个固定的元素,称为代表,以表示整个集合。接着,Find(x) 返回 x 所属集合的代表,而 Union 使用两个集合的代表作为参数。

具体分析:参考链接

  1. 我们设想一下加入有两对情侣互相坐错了位置,我们至多只需要换一次。
    如果三对情侣相互坐错了位置,A1+B2,B1+C2,C1+A2。他们三个之间形成了一个环,我们只需要交换两次。
  2. 如果四队情侣相互坐错了位置,即这四对情侣不与其他情侣坐在一起,A1+B2,B1+C2,C1+D2,D1+A2.他们四个之间形成了一个环,他们只需要交换三次并且不用和其他情侣交换,就可以将这四对情侣交换好,以此类推,其实就是假设k对情侣形成一个环状的错误链,我们只需要交换k - 1次就可以将这k对情侣的位置排好。
  3. 所以问题转化成N对情侣中,有几个这样的错误环。
  4. 我们可以使用并查集来处理,每次遍历相邻的两个位置,如果他们本来就是情侣,他们处于大小为1的错误环中,只需要交换0次。如果不是情侣,说明他们呢两对处在同一个错误环中,我们将他们合并(union),将所有的错坐情侣合并和后,答案就是情侣对 - 环个数。
  5. 这也说明,最差的情况就是所有N对情侣都在一个环中,这时候我们需要N - 1调换。最好情况每对情侣已经坐好了,已经有N个大小为1的环,这时候我们需要N - N次调换。

并查集参考链接1
并查集参考链接2

/*  ====算法分析====
    并查集 
    1.首先初始状态把这N对情侣分别构成一个连通块
    2.考虑k对情侣相互错开的情况,他们形成一个环,可以知道需k-1次交换使排列正确
    3.这样相互错开的情况,分别构成连通块,最后用N - 连通块个数即为答案
    例如:0,1|2,3|... |2N-2,2N-1
    |0 3| ... |7 2|...|6 1| 看相对顺序,可以发现这三对构成一个环,只需2次交换
    同理还有其他类型的环构成连通块
*/

const int N = 80;
int p[N];

int find(int x) {
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

class Solution {
public:
    int minSwapsCouples(vector<int>& row) {
        int n = row.size();

        // 初始化p[x]数组
        for (int i = 0; i < n; i += 2) {
            p[i + 1] = i;
            p[i] = i;   
        }

        // |0 3| ... |7 2|...|6 1| 合并0,3   7,2  6,1 最终这两个人到同一连通块
        for (int i = 0; i < n; i += 2) {
            p[find(row[i + 1])] = find(row[i]);  
        }

        int res = n / 2;
        for (int i = 0; i <= n; i ++) {
            if (p[i] == i) res --;
        }

        return res;
    }
};
发布了76 篇原创文章 · 获赞 6 · 访问量 2770

猜你喜欢

转载自blog.csdn.net/u014618114/article/details/104271541