SingleElementInASortedArray [source code]
public class SingleElementInASortedArray {
static
/******************************************************************************/
public class Solution {
public int singleNonDuplicate(int[] nums) {
int n = nums.length;
int lo = 0, hi = n;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if ((mid % 2 == 0 && mid + 1 < n && nums[mid] == nums[mid + 1]) ||
(mid % 2 == 1 && mid - 1 >= 0 && nums[mid] == nums[mid - 1]))
lo = mid + 1;
else
hi = mid;
}
return nums[lo];
}
}
/******************************************************************************/
public static void main(String[] args) {
SingleElementInASortedArray.Solution tester = new SingleElementInASortedArray.Solution();
int[][] inputs = {
{1,1,2,3,3,4,4,8,8}, {2},
{3,3,7,7,10,11,11}, {10},
{1,1,2,3,3}, {2},
{1,1,2,2,3,4,4,5,5}, {3},
};
for (int i = 0; i < inputs.length / 2; i++) {
int[] nums = inputs[2 * i];
int answer = inputs[1 + 2 * i][0];
System.out.println(Printer.seperator());
int output = tester.singleNonDuplicate(nums);
System.out.println(Printer.wrapColor(Printer.array(nums), "magenta") + " -> " + output + Printer.wrapColor(", expected: " + answer, answer == output ? "green" : "red"));
}
}
}
这个问题第一反应肯定是之前一个disappeared number的问题的思路, 直接xor解决, 但是这里的要求是logN.
加上这里给出的条件是sorted, 所以也许可以尝试一下Binary Search?
整体题目本身还是比较简单的, 就是一个Binary Search的变形题目, 感觉这种binary search variation的题目出现已经不是一次两次了, 可见Binary Search的重要性.
前面总结过, Binary Search问题, 主要需要纠结的问题是:
- termination condition: 研究最后一个最短的iteration, 考虑如果在hit或者miss的情况下, 最后lo和hi是什么一个情况;
- header是否可以取等号.
当然, 这个问题就属于一个比较活络的variation, 单纯去从上面两个角度死考虑是没有用的, 更重要的还是理解这个题目本身, 怎样确定下一个区间.
确定下一个区间的判据, 就是mid位置的表现. 这里要判断的虽然是mid位置的值, 但是其实是通过mid位置, 来判断[0..mid]这个范围内是否存在这个single number. 如果mid左边完全正常, 那么mid所在的位置就可以准确知道的它是否和它左边还是右边的邻居相等.
这个题目说简单其实也不是非常trivial, 反正还是加深对于Binary Search的理解, 核心问题就是问自己: 怎样通过对mid的一个简单的判断就知道我们要找的hit是在左边还是右边: verbal logic;
在确定了核心搜索策略之后, 再开始纠结上面提到的两个点. 这里可以看到, 我们这里的逻辑其实只有两个branch, 这种两个branch的Binary Search其实前面好像也是碰到过的; 另外, 通过研究最后一个iteration, 事实上, 因为这个问题有点复杂, 所以干脆直接拿着一个例子来找last iteration, 而不是直接脑子里空想一个length = 2的就说一定是last iteration. 通过对这个iteration的研究, 可以看到这题其实不取等号比较好;
2018-05-17 16:24:45
这个时候的代码是这样的:
public class Solution {
public int singleNonDuplicate(int[] nums) {
int n = nums.length;
int lo = 0, hi = n - 1;
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if ((mid % 2 == 0 && mid + 1 < n && nums[mid] == nums[mid + 1]) ||
(mid % 2 == 1 && mid - 1 >= 0 && nums[mid] == nums[mid - 1]))
lo = mid + 1;
else
hi = mid - 1;
}
return nums[lo];
}
}
但是这个代码是有问题的, 被人发帖指出来了:
wai.swk 0 an hour agoReport
Your code doesn't pass cases where the initial mid is the single element (for example, {1,1,2,3,3} and {1,1,2,2,3,4,4,5,5}).
然后仔细看了一下, 实际上是我二叉的写法不对: 写的是开区间写法, 但是最后二叉的更新方式和一开始的端点初始化都有点问题; 改了之后就ok了;
这个是discussion上面的一个解:
My solution using binary search. lo and hi are not regular index, but the pair index here. Basically you want to find the first even-index number not followed by the same number.
public class Solution {
public int singleNonDuplicate(int[] nums) {
// binary search
int n=nums.length, lo=0, hi=n/2;
while (lo < hi) {
int m = (lo + hi) / 2;
if (nums[2*m]!=nums[2*m+1]) hi = m;
else lo = m+1;
}
return nums[2*lo];
}
}
这个解想法还是比较清奇的, 不过我没有理解的是, 到底是怎么知道hi = m
这个做法的? 我就感觉我就是瞄准的下一个要走的区间, 所以我认为应该-1.
感觉Binary Search算法虽然看起来简单, 但是真正这些边界条件还是要考虑, 最好能像这一题一样, 相处一个适用于每一个iteration的invariant.
这个是我在discussion上面发帖的东西:
The logic behind this is very easy: for each mid
, we try to find understand whether the single number is on the left half. The if
header tests that nums[mid]
is not single and neither is anything on its left.
- if
mid
is even, then there are2m
numbers on the left ofmid
. For the statement ofnums[mid]
is not single and neither is anything on its left to hold, we need the2m
numbers to the left ofmid
to bem
pairs, and alsonums[mid]
be in a pair withnums[mid + 1]
. Indeed, we only have to make sure in this case thatnums[mid], nums[mid + 1]
is a pair. You can prove by contradiction that as long as this holds, the sole single number can't be on the left ofmid
. Now that the statement ofnums[mid]
is not single and neither is anything on its left is proven, we can just go to the right half. - if
mid
is odd, then to provenums[mid]
is not single and neither is anything on its left, we only need to prove thatnums[mid - 1], nums[mid]
is a pair.mid - 1
is even, and as long asnums[mid - 1], nums[mid - 1 + 1]
forms a pair, we can actually use the argument of previous paragraph to prove that no entry to the left ofmid
is single. And neither ismid
itself obviously. Withnums[mid]
is not single and neither is anything on its left proven, we can again to the right half. - If
nums[mid]
is not single and neither is anything on its left not provable, then go to left half since the single number is there.
Problem Description
Given a sorted array consisting of only integers where every element appears twice except for one element which appears once. Find this single element that appears only once.
Example 1:
Input: [1,1,2,3,3,4,4,8,8]
Output: 2
Example 2:
Input: [3,3,7,7,10,11,11]
Output: 10
Note: Your solution should run in O(log n) time and O(1) space.
Difficulty:Medium
Total Accepted:11.6K
Total Submissions:21.3K
Contributor: rajaditya