Leetcode——链表和数组(4)

情侣牵手

N 对情侣坐在连续排列的 2N 个座位上,想要牵到对方的手。 计算最少交换座位的次数,以便每对情侣可以并肩坐在一起。 次交换可选择任意两人,让他们站起来交换座位。

人和座位用 02N-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
解释: 无需交换座位,所有的情侣都已经可以手牵手了。

说明:

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

贪心算法

一个数‘异或’上1就是其另一个位,

如果是偶数的话,最后位是0,‘异或’上1等于加了1,变成了可以的成对奇数。

如果是奇数的话,最后位是1,‘异或’上1后变为了0,变成了可以的成对偶数。

class Solution {
public:
    int minSwapsCouples(vector<int>& row) {
        int res = 0, n = row.size();
        for (int i = 0; i < n; i += 2) {
            if (row[i + 1] == (row[i] ^ 1)) continue;
            ++res;
            for (int j = i + 1; j < n; ++j) {
                if (row[j] == (row[i] ^ 1)) {
                    row[j] = row[i + 1];
                    row[i + 1] = row[i] ^ 1;
                    break;
                }
            }
        }
        return res;
    }
};

联合查找

用一个root数组,每个点开始初始化为不同的值,

如果两个点属于相同的组,就将其中一个点的root值赋值为另一个点的位置,这样只要是相同组里的两点,通过find函数会得到相同的值。

那么如果总共有n个数字,则共有 n/2 对儿,所以我们初始化 n/2 个群组,我们还是每次处理两个数字。

每个数字除以2就是其群组号,那么属于同一组的两个数的群组号是相同的,

比如2和3,其分别除以2均得到1,所以其组号均为1。

那么这对解题有啥作用呢?

由于我们每次取的是两个数,且计算其群组号,并调用find函数,那么如果这两个数的群组号相同,那么find函数必然会返回同样的值,我们不用做什么额外动作,因为本身就是一对儿。

如果两个数不是一对儿,那么其群组号必然不同,在二者没有归为一组之前,调用find函数返回的值就不同,此时我们将二者归为一组,并且cnt自减1,

cnt初始化为总群组数,即 n/2。

那么最终cnt减少的个数就是交换的步数

举例

[3 1 4 0 2 5]

最开始的群组关系是:

群组0:0,1

群组1:2,3

群组2:4,5

取出前两个数字3和1,其群组号分别为1和0,带入find函数返回不同值,则此时将群组0和群组1链接起来,变成一个群组,则此时只有两个群组了,cnt自减1,变为了2。

群组0 & 1:0,1,2,3

群组2:4,5

此时取出4和0,其群组号分别为2和0,带入find函数返回不同值,则此时将群组0&1和群组2链接起来,变成一个超大群组,cnt自减1,变为了1。

群组0 & 1 & 2:0,1,2,3,4,5

此时取出最后两个数2和5,其群组号分别为1和2,因为此时都是一个大组内的了,带入find函数返回相同的值,不做任何处理。最终交换的步数就是cnt减少值,为2

class Solution {
public:
    int minSwapsCouples(vector<int>& row) {
        int res = 0, n = row.size(), cnt = n / 2;
        vector<int> root(n, 0);
        for (int i = 0; i < n; ++i) root[i] = i;
        for (int i = 0; i < n; i += 2) {
            int x = find(root, row[i] / 2);
            int y = find(root, row[i + 1] / 2);
            if (x != y) {
                root[x] = y;
                --cnt;
            }
        }
        return n / 2 - cnt;
    }
    int find(vector<int>& root, int i) {
        return (i == root[i]) ? i : find(root, root[i]);
    }
};

最短无序连续子数组

给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。

你找到的子数组应是最短的,请输出它的长度。

示例 1:

输入: [2, 6, 4, 8, 10, 9, 15]
输出: 5
解释: 你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。

说明 :

  1. 输入的数组长度范围在 [1, 10,000]。
  2. 输入的数组可能包含重复元素 ,所以升序的意思是<=。

方法一

要确定无序子数组的起始和结束位置,这样就能知道子数组的长度了。

所以我们用一个变量start来记录起始位置,然后我们开始遍历数组,

当我们发现某个数字比其前面的数字要小的时候,说明此时数组不再有序,所以我们要将此数字向前移动,移到其应该在的地方,

我们用另一个变量j来记录移动到的位置,然后我们考虑要不要用这个位置来更新start的值,

start还是初始值-1时,肯定要更新,因为这是出现的第一个无序的地方,

还有就是如果当前位置小于start也要更新,这说明此时的无序数组比之前的更长了。

举个例子来说明,

比如数组[1,3,5,4,2]

第一个无序的地方是数字4,它移动到的正确位置是坐标2,此时start更新为2,

然后下一个无序的地方是数字2,它的正确位置是坐标1,所以此时start应更新为1,

这样每次用i - start + 1来更新结果res时才能得到正确的结果

class Solution {
public:
    int findUnsortedSubarray(vector<int>& nums) {
        int res = 0, start = -1, n = nums.size();
        for (int i = 1; i < n; ++i) {
            if (nums[i] < nums[i - 1]) {
                int j = i;
                while (j > 0 && nums[j] < nums[j - 1]) {
                    swap(nums[j], nums[j - 1]);
                    --j;
                }
                if (start == -1 || start > j) start = j;
                res = i - start + 1;
            }
        }
        return res;
    }
};

方法二

新建一个跟原数组一摸一样的数组,然后排序。

从数组起始位置开始,两个数组相互比较,当对应位置数字不同的时候停止,

同理再从末尾开始,对应位置上比较,也是遇到不同的数字时停止,这样中间一段就是最短无序连续子数组了

class Solution {
public:
    int findUnsortedSubarray(vector<int>& nums) {
        int n = nums.size(), i = 0, j = n - 1;
        vector<int> t = nums;
        sort(t.begin(), t.end());
        while (i < n && nums[i] == t[i]) ++i;
        while (j > i && nums[j] == t[j]) --j;
        return j - i + 1;
    }
};

下一个排列

实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。

如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。

必须原地修改,只允许使用额外常数空间。

以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,31,3,2
3,2,11,2,3
1,1,51,5,1

方法一

下面一个例子,有如下的一个数组

1  2  7  4  3  1

下一个排列为:

1  3  1  2  4  7

那么是如何得到的呢,我们通过观察原数组可以发现,如果从末尾往前看,数字逐渐变大,到了2时才减小的,然后再从后往前找第一个比2大的数字,是3,那么我们交换2和3,再把此时3后面的所有数字转置一下即可,步骤如下:

1  2  7  4  3  1

1  2  7  4  3  1

1  3  7  4  2  1

1  3  1  2  4  7

class Solution {
public:
    void nextPermutation(vector<int> &num) {
        int i, j, n = num.size();
        for (i = n - 2; i >= 0; --i) {
            if (num[i + 1] > num[i]) {
                for (j = n - 1; j > i; --j) {
                    if (num[j] > num[i]) break;
                }
                swap(num[i], num[j]);
                reverse(num.begin() + i + 1, num.end());
                return;
            }
        }
        reverse(num.begin(), num.end());
    }
};

优化

class Solution {
public:
    void nextPermutation(vector<int>& nums) {int n = nums.size(), i = n - 2, j = n - 1;
        while (i >= 0 && nums[i] >= nums[i + 1]) --i;
        if (i >= 0) {
            while (nums[j] <= nums[i]) --j;
            swap(nums[i], nums[j]);
        }
        reverse(nums.begin() + i + 1, nums.end());
    }
};

组合

给定两个整数 nk,返回 1 ... n 中所有可能的 k 个数的组合。

示例:

输入: n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

深度优先搜索

建立一个保存最终结果的大集合res,还要定义一个保存每一个组合的小集合out,每次放一个数到out里,

如果out里数个数到了k个,则把out保存到最终结果中,否则在下一层中继续调用递归。

img

class Solution {
public:
    vector<vector<int>> combine(int n, int k) {
        vector<vector<int>> res;
        vector<int> out;
        helper(n, k, 1, out, res);
        return res;
    }
    void helper(int n, int k, int level, vector<int>& out, vector<vector<int>>& res) {
        if (out.size() == k) {res.push_back(out); return;}
        for (int i = level; i <= n; ++i) {
            out.push_back(i);
            helper(n, k, i + 1, out, res);
            out.pop_back();
        }
    }
};

递归

C(n, k) = C(n-1, k-1) + C(n-1, k)

在n个数中取k个数的组合项个数,等于在n-1个数中取k-1个数的组合项个数再加上在n-1个数中取k个数的组合项个数之和。

C(4, 2) = C(3, 1) + C(3, 2)

我们不难写出 C(3, 1) 的所有情况:[1], [2], [3],还有 C(3, 2) 的所有情况:[1, 2], [1, 3], [2, 3]。

我们发现二者加起来为6,正好是 C(4, 2) 的个数之和。

但是我们仔细看会发现,C(3, 2)的所有情况包含在 C(4, 2) 之中,但是 C(3, 1) 的每种情况只有一个数字,而我们需要的结果k=2,其实很好办,每种情况后面都加上4,于是变成了:[1, 4], [2, 4], [3, 4],加上C(3, 2) 的所有情况:[1, 2], [1, 3], [2, 3],正好就得到了 n=4, k=2 的所有情况了。

class Solution {
public:
    vector<vector<int>> combine(int n, int k) {
        if (k > n || k < 0) return {};
        if (k == 0) return {{}};
        vector<vector<int>> res = combine(n - 1, k - 1);
        for (auto &a : res) a.push_back(n);
        for (auto &a : combine(n - 1, k)) res.push_back(a);
        return res;
    }
};

迭代

每次先递增最右边的数字,存入结果res中,

当右边的数字超过了n,则增加其左边的数字,然后将当前数组赋值为左边的数字,再逐个递增,

直到最左边的数字也超过了n,停止循环。对于n=4, k=2时,遍历的顺序如下所示:

0 0 #initialization
1 0
1 1 
1 2 #push_back
1 3 #push_back
1 4 #push_back
1 5
2 5
2 2 
2 3 #push_back
2 4 #push_back
...
3 4 #push_back
3 5
4 5
4 4
4 5
5 5 #stop 
class Solution {
public:
    vector<vector<int>> combine(int n, int k) {
        vector<vector<int>> res;
        vector<int> out(k, 0);
        int i = 0;
        while (i >= 0) {
            ++out[i];
            if (out[i] > n) --i;
            else if (i == k - 1) res.push_back(out);
            else {
                ++i;
                out[i] = out[i - 1];
            }
        }
        return res;
    }
};

全排列

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

输入: [1,2,3]
输出:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

DFS

level 其本质是记录当前已经拼出的个数,一旦其达到了 nums 数组的长度,说明此时已经是一个全排列了,因为再加数字的话,就会超出

level 要从0开始遍历,因为这是求全排列,每个位置都可能放任意一个数字

数字有可能被重复使用,由于全排列是不能重复使用数字的,所以需要用一个 visited 数组来标记某个数字是否使用过

class Solution {
public:
    vector<vector<int>> permute(vector<int>& num) {
        vector<vector<int>> res;
        vector<int> out, visited(num.size(), 0);
        permuteDFS(num, 0, visited, out, res);
        return res;
    }
    void permuteDFS(vector<int>& num, int level, vector<int>& visited, vector<int>& out, vector<vector<int>>& res) {
        if (level == num.size()) {res.push_back(out); return;}
        for (int i = 0; i < num.size(); ++i) {
            if (visited[i] == 1) continue;
            visited[i] = 1;
            out.push_back(num[i]);
            permuteDFS(num, level + 1, visited, out, res);
            out.pop_back();
            visited[i] = 0;
        }
    }
};

递归

每次交换 num 里面的两个数字,经过递归可以生成所有的排列情况

class Solution {
public:
    vector<vector<int>> permute(vector<int>& num) {
        vector<vector<int>> res;
        permuteDFS(num, 0, res);
        return res;
    }
    void permuteDFS(vector<int>& num, int start, vector<vector<int>>& res) {
        if (start >= num.size()) res.push_back(num);
        for (int i = start; i < num.size(); ++i) {
            swap(num[start], num[i]);
            permuteDFS(num, start + 1, res);
            swap(num[start], num[i]);
        }
    }
};

全排列 II

给定一个可包含重复数字的序列,返回所有不重复的全排列。

示例:

输入: [1,1,2]
输出:
[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]

DFS

我们要避免重复的产生,在递归函数中要判断前面一个数和当前的数是否相等,

如果相等,且其对应的 visited 中的值为1,当前的数字才能使用,否则需要跳过,这样就不会产生重复排列了

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<vector<int>> res;
        vector<int> out, visited(nums.size(), 0);
        sort(nums.begin(), nums.end());
        permuteUniqueDFS(nums, 0, visited, out, res);
        return res;
    }
    void permuteUniqueDFS(vector<int>& nums, int level, vector<int>& visited, vector<int>& out, vector<vector<int>>& res) {
        if (level >= nums.size()) {res.push_back(out); return;}
        for (int i = 0; i < nums.size(); ++i) {
            if (visited[i] == 1) continue;
            if (i > 0 && nums[i] == nums[i - 1] && visited[i - 1] == 0) continue;
            visited[i] = 1;
            out.push_back(nums[i]);
            permuteUniqueDFS(nums, level + 1, visited, out, res);
            out.pop_back();
            visited[i] = 0;
        }
    }
};

递归

TreeSet 来保存结果,利用其不会有重复项的特点,

然后在递归函数中 swap 的地方,判断如果istart 不相同,

但是 nums[i]nums[start] 相同的情况下跳过,继续下一个循环

class Solution {
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        set<vector<int>> res;
        permute(nums, 0, res);
        return vector<vector<int>> (res.begin(), res.end());
    }
    void permute(vector<int>& nums, int start, set<vector<int>>& res) {
        if (start >= nums.size()) res.insert(nums);
        for (int i = start; i < nums.size(); ++i) {
            if (i != start && nums[i] == nums[start]) continue;
            swap(nums[i], nums[start]);
            permute(nums, start + 1, res);
            swap(nums[i], nums[start]);
        }
    }
};

第k个排列

给出集合 [1,2,3,…,n],其所有元素共有 n! 种排列。

按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:

  1. "123"
  2. "132"
  3. "213"
  4. "231"
  5. "312"
  6. "321"

给定 nk,返回第 k 个排列。

说明:

  • 给定 n 的范围是 [1, 9]。
  • 给定 k 的范围是[1, n!]。

示例 1:

输入: n = 3, k = 3
输出: "213"

示例 2:

输入: n = 4, k = 9
输出: "2314"

1234
1243
1324
1342
1423
1432
2134
2143
2314
2341
2413
2431
3124
3142
3214
3241
3412
3421
4123
4132
4213
4231
4312
4321

每一位上 1,2,3,4 分别都出现了6次,当最高位上的数字确定了,

第二高位每个数字都出现了2次,当第二高位也确定了,第三高位上的数字都只出现了1次,

当第三高位确定了,那么第四高位上的数字也只能出现一次

规律

[a_1 = k / (n - 1)! ]

[k_1 = k ]

[a_2 = k_1 / (n - 2)! ]

[k_2 = k_1 \% (n - 2)! ]

[... ]

[a_{n-1} = k_{n-2} / 1! ]

[k_{n-1} = k_{n-2} \% 1! ]

[a_n = k_{n-1} / 0! ]

[k_n = k_{n-1} \% 0! ]

class Solution {
public:
    string getPermutation(int n, int k) {
        string res;
        string num = "123456789";
        vector<int> f(n, 1);
        for (int i = 1; i < n; ++i) f[i] = f[i - 1] * i;
        --k;
        for (int i = n; i >= 1; --i) {
            int j = k / f[i - 1];
            k %= f[i - 1];
            res.push_back(num[j]);
            num.erase(j, 1);
        }
        return res;
    }
};

回文排列

题目描述
给定一个字符串,判断该字符串中是否可以通过重新排列组合,形成一个回文字符串。

示例 1:

输入: "code"
输出: false
1
2
示例 2:

输入: "aab"
输出: true
1
2
示例 3:

输入: "carerac"
输出: true

HashMap

分字符串的个数是奇偶的情况来讨论,

如果是偶数的话,由于回文字符串的特性,每个字母出现的次数一定是偶数次,

当字符串是奇数长度时,只有一个字母出现的次数是奇数,其余均为偶数,

那么利用这个特性我们就可以解题,我们建立每个字母和其出现次数的映射,

然后我们遍历 HashMap,统计出现次数为奇数的字母的个数,

那么只有两种情况是回文数,

第一种是没有出现次数为奇数的字母,再一个就是字符串长度为奇数,且只有一个出现次数为奇数的字母

class Solution {
public:
    bool canPermutePalindrome(string s) {
        unordered_map<char, int> m;
        int cnt = 0;
        for (auto a : s) ++m[a];
        for (auto a : m) {
            if (a.second % 2 == 1) ++cnt;
        }
        return cnt == 0 || (s.size() % 2 == 1 && cnt == 1);
    }
};

HashSet

我们遍历字符串,如果某个字母不在 HashSet 中,我们加入这个字母,如果字母已经存在,我们删除该字母,

那么最终如果 HashSet 中没有字母或是只有一个字母时,说明是回文串

class Solution {
public:
    bool canPermutePalindrome(string s) {
        unordered_set<char> st;
        for (auto a : s) {
            if (!st.count(a)) st.insert(a);
            else st.erase(a);
        }
        return st.empty() || st.size() == 1;
    }
};

回文排列 II

给定一个字符串 s ,返回其通过重新排列组合后所有可能的回文字符串,并去除重复的组合。

如不能形成任何回文排列时,则返回一个空列表。

示例 1:

输入: "aabb"
输出: ["abba", "baab"]
示例 2:

输入: "abc"
输出: []

由于回文字符串有奇偶两种情况,

偶数回文串例如 abba,可以平均分成前后半段,

而奇数回文串例如 abcba,需要分成前中后三段,需要注意的是中间部分只能是一个字符,

可以分析得出,如果一个字符串的回文字符串要存在,那么奇数个的字符只能有0个或1个,其余的必须是偶数个,

所以可以用哈希表来记录所有字符的出现个数,然后找出出现奇数次数的字符加入 mid 中,

如果有两个或两个以上的奇数个数的字符,则返回空集,对于每个字符,不管其奇偶,都将其个数除以2的个数的字符加入t中,这样做的原因是如果是偶数个,将其一般加入t中,

如果是奇数,如果有1个,除以2是0,不会有字符加入t,如果是3个,除以2是1,取一个加入t。

等获得了t之后,t是就是前半段字符,对其做全排列,每得到一个全排列,加上 mid 和该全排列的逆序列就是一种所求的回文字符串,这样就可以得到所有的回文全排列了。

在全排序的子函数中有一点需要注意的是,如果直接用数组来保存结果时,并且t中如果有重复字符的话可能会出现重复项,比如 t = "baa" 的话,那么最终生成的结果会有重复项,不信可以自己尝试一下。

这里简单的说明一下,当 start=0,i=1 时,交换后得到 aba,在之后当 start=1,i=2 时,交换后可以得到 aab

但是在之后回到第一层当baa后,当 start=0,i=2 时,交换后又得到了 aab,重复就产生了。

那么其实最简单当去重复的方法就是将结果 res 定义成 HashSet,利用其去重复的特性,可以保证得到的是没有重复的

class Solution {
public:
    vector<string> generatePalindromes(string s) {
        unordered_set<string> res;
        unordered_map<char, int> m;
        string t = "", mid = "";
        for (auto a : s) ++m[a];
        for (auto it : m) {
            if (it.second % 2 == 1) mid += it.first;
            t += string(it.second / 2, it.first);
            if (mid.size() > 1) return {};
        }
        permute(t, 0, mid, res);
        return vector<string>(res.begin(), res.end());
    }
    void permute(string &t, int start, string mid, unordered_set<string> &res) {
        if (start >= t.size()) {
            res.insert(t + mid + string(t.rbegin(), t.rend()));
        } 
        for (int i = start; i < t.size(); ++i) {
            if (i != start && t[i] == t[start]) continue;
            swap(t[i], t[start]);
            permute(t, start + 1, mid, res);
            swap(t[i], t[start]);
        }
    }
};

在排序数组中查找元素的第一个和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

你的算法时间复杂度必须是 O(log n) 级别。

如果数组中不存在目标值,返回 [-1, -1]

示例 1:

输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]

示例 2:

输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]

二分查找

首先对原数组使用二分查找法,找出其中一个目标值的位置,然后向两边搜索找出起始和结束的位置

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int idx = search(nums, 0, nums.size() - 1, target);
        if (idx == -1) return {-1, -1};
        int left = idx, right = idx;
        while (left > 0 && nums[left - 1] == nums[idx]) --left;
        while (right < nums.size() - 1 && nums[right + 1] == nums[idx]) ++right;
        return {left, right};
    }
    int search(vector<int>& nums, int left, int right, int target) {
        if (left > right) return -1;
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) return mid;
        if (nums[mid] < target) return search(nums, mid + 1, right, target);
        else return search(nums, left, mid - 1, target);
    }
};

优化

使用两次二分查找法,第一次找到左边界,第二次调用找到右边界即可

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        vector<int> res(2, -1);
        int left = 0, right = nums.size();
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) left = mid + 1;
            else right = mid;
        }
        if (right == nums.size() || nums[right] != target) return res;
        res[0] = right;
        right = nums.size();
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] <= target) left = mid + 1;
            else right = mid;
        }
        res[1] = right - 1;
        return res;
    }
};

方法三

只使用一个二分查找的子函数,来同时查找出第一个和最后一个位置。

如何只用查找第一个大于等于目标值的二分函数来查找整个范围呢,这里用到了一个小 trick,

首先来查找起始位置的 target,就是在数组中查找第一个大于等于 target 的位置,当返回的位置越界,或者该位置上的值不等于 target 时,表示数组中没有 target,直接返回 {-1, -1} 即可。

若查找到了 target 值,则再查找第一个大于等于 target+1 的位置,然后把返回的位置减1,就是 target 的最后一个位置,即便是返回的值越界了,减1后也不会越界

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int start = firstGreaterEqual(nums, target);
        if (start == nums.size() || nums[start] != target) return {-1, -1};
        return {start, firstGreaterEqual(nums, target + 1) - 1};
    }
    int firstGreaterEqual(vector<int>& nums, int target) {
        int left = 0, right = nums.size();
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) left = mid + 1;
            else right = mid;
        }
        return right;
    }
};

第一个错误的版本

你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。

假设你有 n 个版本 [1, 2, ..., n],你想找出导致之后所有版本出错的第一个错误的版本。

你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。

示例:

给定 n = 5,并且 version = 4 是第一个错误的版本。

调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true

所以,4 是第一个错误的版本。 

二分查找

由于这题很有规律,好版本和坏版本一定有个边界,那么我们用二分法来找这个边界,对mid值调用API函数,

如果是坏版本,说明边界在左边,则把mid赋值给right,

如果是好版本,则说明边界在右边,则把mid+1赋给left,最后返回left即可。

需要注意的是,OJ里有个坑,那就是如果left和right都特别大的话,那么left+right可能会溢出,我们的处理方法就是变成left + (right - left) / 2,很好的避免的溢出问题

bool isBadVersion(int version);

class Solution {
public:
    int firstBadVersion(int n) {
        int left = 1, right = n;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (isBadVersion(mid)) right = mid;
            else left = mid + 1;
        }
        return left;
    }
};
原文地址:https://www.cnblogs.com/wwj99/p/13042857.html