import java.util.*;

/**
 * NC305 寻找唯一重复数
 * @author d3y1
 */
public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 相似 -> NC3 链表中环的入口结点 [nowcoder]
     *
     * @param nums int整型ArrayList
     * @return int整型
     */
    public int findRepeatNum (ArrayList<Integer> nums) {
        // return solution1(nums);
        // return solution2(nums);
        // return solution3(nums);
        // return solution4(nums);
        return solution5(nums);
        // return solution6(nums);
    }

    /**
     * 遍历统计
     * @param nums
     * @return
     */
    private int solution1(ArrayList<Integer> nums){
        int n = nums.size();

        int[] cnt = new int[n+1];

        int result = 0;
        for(int num: nums){
            if(cnt[num] == 0){
                cnt[num]++;
            }else{
                result = num;
                break;
            }
        }

        return result;
    }

    /**
     * 二分: 需要先排序(升序)
     *
     * 测试用例9 数据有问题! -> 此时n=100000, 数组中所有数字都应在区间[1,n-1]内(即最大值为99999), 提供的数据中有100000!
     *
     *      i  0 1 2 3 4
     * nums[i] 4 2 1 3 3
     *
     * l: left
     * m: mid
     * r: right
     *                l   m   r
     *             i  0 1 2 3 4
     * sorted nums[i] 1 2 3 3 4
     *
     * @param nums
     * @return
     */
    private int solution2(ArrayList<Integer> nums){
        int n = nums.size();

        Collections.sort(nums);

        int left = 0;
        int right = n-1;
        int mid;
        while(left <= right){
            mid = (left+right)/2;
            if(nums.get(mid) > mid){
                left = mid+1;
            }else{
                right = mid-1;
            }
        }

        return nums.get(left);
    }

    /**
     * 二分
     *
     * 测试用例9 数据有问题! -> 此时n=100000, 数组中所有数字都应在区间[1,n-1]内(即最大值为99999), 提供的数据中有100000!
     *
     * cnt[i]表示nums数组中小于等于i的数的个数
     *
     * 假设重复的数是target, 那么[1,target)里的所有数满足cnt[i]≤i, [target,n-1]里的所有数满足cnt[i]>i, 具有单调性
     *
     *      i  0 1 2 3 4
     * nums[i] 4 2 1 3 3
     *
     * l: left
     * m: mid
     * r: right
     *     l m   r
     * num 1 2 3 4
     * cnt 1 2 4 5
     *
     * @param nums
     * @return
     */
    private int solution3(ArrayList<Integer> nums){
        int n = nums.size();

        int result = -1;
        int left = 1;
        int right = n-1;
        int mid;
        while(left <= right){
            mid = (left+right)>>1;
            int cnt = 0;
            for(int num: nums){
                if(num <= mid){
                    cnt++;
                }
            }
            if(cnt <= mid){
                left = mid+1;
            }else{
                result = mid;
                right = mid-1;
            }
        }

        return result;
    }

    /**
     * 二进制: 位运算
     *
     * 测试用例9 数据有问题! -> 此时n=100000, 数组中所有数字都应在区间[1,n-1]内(即最大值为99999), 提供的数据中有100000!
     *
     * 错误用例! -> n=10 最大值只能9
     * input: [1, 2, 2, 2, 4, 6, 7, 8, 9, 10]
     * output: 10
     *
     * 正确用例
     * input:[1, 2, 2, 2, 4, 5, 6, 7, 8, 9]
     * output: 2
     *
     * -----------------------------------------------------------
     *
     * 考虑第i位
     * x: nums数组中所有数二进制展开后第i位为1的个数
     * y: 区间[1,n-1]中n-1个数二进制展开后第i位为1的个数
     * 那么重复的数target第i位为1 当且仅当 x>y
     *
     * 考虑重复2次情况:
     *      i  0  1  2  3  4
     * nums[i] 4  2  1  3  3  x  y  target(011)=3
     *   第0位  0  0  1  1  1  3  2  1 (x>y)
     *   第1位  0  1  0  1  1  3  2  1 (x>y)
     *   第2位  1  0  0  0  0  1  1  0 (x=y)
     *
     * 容易理解, 重复的数target第i位为1 当且仅当 x>y
     *
     *
     * 考虑重复多次情况(>=3次): 重复2次 + 替换剩余次数(替换成target)
     * target第i位为1时
     * 每次替换后只会使x不变或增大
     * target第i位为0时
     * 每次替换后只会使x不变或减小
     *      i  0  1  2  3  4
     * nums[i] 3  2  1  3  3  x  y  target(011)=3
     *   第0位  1  0  1  1  1  4  2  1 (x>y)
     *   第1位  1  1  0  1  1  4  2  1 (x>y)
     *   第2位  0  0  0  0  0  0  1  0 (x<y)
     *
     * 可见, 重复的数target第i位为1 当且仅当 x>y
     * 因此我们只要按位还原这个重复的数即可
     *
     * @param nums
     * @return
     */
    private int solution4(ArrayList<Integer> nums){
        int n = nums.size();
        int result = 0;

        int maxBits = 31;
        // 确定所需最终位数 最大数n-1
        while(((n-1)>>maxBits) == 0){
            maxBits -= 1;
        }

        for(int bit=0; bit<=maxBits; bit++){
            int x=0, y=0;
            for(int i=0; i<n; ++i){
                if((nums.get(i)&(1<<bit)) != 0){
                    x += 1;
                }
                if(i>=1 && ((i&(1<<bit))!=0)){
                    y += 1;
                }
            }
            if(x > y){
                result |= 1<<bit;
            }
        }

        return result;
    }

    /**
     * 双指针: 快慢指针
     *
     * 转化为有向环图, 更容易理解!
     *
     * Floyd判圈算法(又称龟兔赛跑算法): 检测链表是否有环的算法
     *
     * 对nums数组建图, 每个位置i连一条i→nums[i]的边;
     * 由于存在的重复的数字target, 因此target这个位置一定有起码两条指向它的边, 因此整张图一定存在环, 且我们要找到的target就是这个环的入口
     *
     * 先设置慢指针slow和快指针fast, 慢指针每次走一步, 快指针每次走两步
     * 根据Floyd判圈算法, 两个指针在有环的情况下一定会相遇,
     * 此时我们再将slow放置起点0, 两个指针每次同时移动一步, 相遇的点就是答案
     *
     * @param nums
     * @return
     */
    private int solution5(ArrayList<Integer> nums){
        int slow = 0;
        int fast = 0;
        do{
            slow = nums.get(slow);
            fast = nums.get(nums.get(fast));
        }while(slow != fast);

        slow = 0;
        while(slow != fast){
            slow = nums.get(slow);
            fast = nums.get(fast);
        }

        return slow;
    }

    /**
     * 原地哈希
     * @param nums
     * @return
     */
    private int solution6(ArrayList<Integer> nums){
        for(int num: nums){
            // 若第二次进入(根据|num|), 必为负值 -> 此时该|num|重复, 返回|num|即可
            if(nums.get(Math.abs(num)) < 0){
                return Math.abs(num);
            }
            nums.set(Math.abs(num), -nums.get(Math.abs(num)));
        }

        return -1;
    }
}