寻找重复数【php版】

47 篇文章 1 订阅

在这里插入图片描述

三种方法:二分、位运算、快慢指针

	/**
	 * 方法一:二分法 假设重复的数字为target,对于[1,N]的数而言 ,整个nums中,[1,target-1]中的数i满足, 小于等于i的数的数目
	 * cnt[i] <= i , [target, N]中的数j满足, 小于等于j的数的数目 cnt[j] > j;
	 * @param Integer[] $nums
	 * @return Integer
	 */
	function findDuplicate($nums) {
		$left = 1;
		$right = count($nums) - 1;
		while ($left < $right) {
			$mid = $left + intdiv($right-$left, 2);
			$cnt = 0;
			foreach ($nums as $num) {
				// 统计nums中有多少个数小于等于mid
				if ($num <= $mid) {
					$cnt++;
				}
			}
			// 根据mid的值,调整左右端点
			if ($cnt <= $mid) {
				$left = $mid + 1;
			} else {
				$right = $mid;
			}
		}
		return $left;
	}

	/**
	 * 方法二:利用位运算还原target
	 * 设 x 表示nums中各元素二进制数第i位上1的个数, y表示[1,n]中各元素二进制数第i位上1的个数
	 * 1.当target只重复1次时,即1~N各数均在nums中出现了一次,当target第i位上的数是1时,则 x 必 大于 y
	 * 而当target第i位上的数是0时, x = y
	 * 2.当target重复大于1次时, 相当于用target去替换了1~N上的某个数,记为replace, 此时有4种情况
	 * replace i位上的数是1, target i位上的数是1, 则 x > y (因为在替换前 x > y, 替换后相当于x没变)
	 * replace i位上的数是0, target i位上的数是1, 则 x > y (因为在替换前 x > y, 替换后相当于x+1)
	 * replace i位上的数是1, target i位上的数是0, 则 x <= y (因为在替换前 x = y, 替换后相当于x-1)
	 * replace i位上的数是0, target i位上的数是0, 则 x <= y (因为在替换前 x = y, 替换后相当于x没变)
	 * 综上可见 只有在target i位上的数是1时, 才有 x > y, 于是可以利用x, y的关系,判定target在i位上是1还是0,从而推算出target的值。
	 * @param $nums
	 */
	function findDuplicateV2($nums) {
		$n = count($nums) - 1;
		$i = 0;
		$ans = 0;
		while ($n !== 0) {
			$x = 0;
			$y = 0;
			foreach ($nums as $k => $num) {
				if (($num & (1 << $i)) !== 0) {
					$x++;
				}
				if ($k != 0 && ($k & (1 << $i)) !== 0) {
					$y++;
				}
			}
			if ($x > $y) {
				$ans |= 1 << $i;
			}
			$i++;
			$n = $n >> 1;
		}
		return $ans;
	}

	/**
	 * 方法三:快慢指针法,参考floyd判圈法,将i -> nums[i] 看成一条边,则因为有重复元素,则构成的图定是有环的,
	 * 且重复元素target指向了环的入口。
	 * @param $nums
	 */
	function findDuplicateV3($nums) {
		$fast = 0;
		$slow = 0;
		do {
			$slow = $nums[$slow];
			$fast = $nums[$fast];
			$fast = $nums[$fast];
		} while ($fast != $slow);
		$slow = 0;
		do {
			$slow = $nums[$slow];
			$fast = $nums[$fast];
		} while ($fast != $slow);
		return $fast;
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值