• 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;
        }
    };
    
  • 相关阅读:
    Java Object类及其常用方法
    Java 抽象类和抽象方法
    Java 多态
    Java 继承
    Java Scanner类
    正则表达_1
    「暑期集训day14」掠影
    「暑期集训day13」苦闷
    「暑期集训day12」苦楚
    「暑期集训day11」旧殤
  • 原文地址:https://www.cnblogs.com/wwj99/p/13042857.html
Copyright © 2020-2023  润新知