Tag Archives: 二分搜索

LeetCode H-Index II

275. H-Index II

Given an array of citations sorted in ascending order (each citation is a non-negative integer) of a researcher, write a function to compute the researcher’s h-index.

According to the definition of h-index on Wikipedia: “A scientist has index h if h of his/her N papers have at least h citations each, and the other N − h papers have no more than citations each.”

Example:

Input: citations = [0,1,3,5,6]
Output: 3 
Explanation: [0,1,3,5,6] means the researcher has 5 papers in total and each of them had 
             received 0, 1, 3, 5, 6 citations respectively. 
             Since the researcher has 3 papers with at least 3 citations each and the remaining 
             two with no more than 3 citations each, her h-index is 3.

Note:

If there are several possible values for h, the maximum one is taken as the h-index.

Follow up:

  • This is a follow up problem to H-Index, where citations is now guaranteed to be sorted in ascending order.
  • Could you solve it in logarithmic time complexity? 275. H-Index II

LeetCode H-Index的延伸,如果citations是按升序排列的,怎样把算法优化到O(logn)。看到O(logn),马上想到二分查找。 首先左右边界是0和n-1,判断中点的citations[m]和n-m,如果正好相等,则说明正好有n-m篇大于等于n-m引用的文章,所以H-index为n-m。如果citations[m]>n-m,说明引用很大,h-index还能往上涨,所以中心点需要左移,即r=m-1。反之则中心点右移l=m+1。
代码如下:

class Solution {
public:
    int hIndex(vector<int>& citations)
    {
        int l = 0, n = citations.size(), r = n – 1;
        while (l <= r) {
            int m = l + (r – l) / 2;
            if (n – m == citations[m])
                return n – m;
            else if (citations[m] > n – m)
                r = m – 1;
            else
                l = m + 1;
        }
        return n – l;
    }
};

本代码提交AC,用时9MS。
我一开始的代码是,如果citations[m]<n-m,中心点确实右移了,即l=m+1。但是当citations[m]>n-m时,我没想到用r=m-1来左移中心点,误以为r一直都是n-1是固定的,所以这个时候使用了一个while循环来线性查找正确的m,理论上性能要比上面的纯二分差。
代码如下:

class Solution {
public:
    int hIndex(vector<int>& citations)
    {
        int l = 0, r = citations.size() – 1, n = citations.size();
        if (n == 0)
            return 0;
        if (citations[0] >= n)
            return n;
        while (l < r) {
            int m = l + (r – l) / 2;
            if (n – m > citations[m])
                l = m + 1;
            else {
                while (m > 0 && n – m <= citations[m])
                    –m;
                return n – m – 1;
            }
        }
        if (citations[r] >= 1)
            return 1;
        else
            return 0;
    }
};

本代码提交AC,用时也是9MS。

二刷。更好理解的二分,因为是从小到大排序的,选定中点m后,中点及其右边共有n-m篇paper,如果paper数和citation相等,正好;如果citations[m]更大,则还可以增加paper数,所以r=m-1区间左移,右边合法的paper区间增大。因为如果合法的话,区间会一直左移,r会一直m-1,直到不满足要求,退出while循环,所以最后一个合法的下标r+1,则右边的paper数为n-(r+1)。

class Solution {
public:
	int hIndex(vector<int>& citations) {
		int n = citations.size();
		int l = 0, r = n - 1;
		while (l <= r) {
			int m = l + (r - l) / 2;
			int papers = n - m;
			if (citations[m] == papers) {
				return papers;
			}
			else if (citations[m] >= papers) {
				r = m - 1;
			}
			else {
				l = m + 1;
			}
		}
		return n - (r + 1);
	}
};

LeetCode Find Right Interval

LeetCode Find Right Interval Given a set of intervals, for each of the interval i, check if there exists an interval j whose start point is bigger than or equal to the end point of the interval i, which can be called that j is on the “right” of i. For any interval i, you need to store the minimum interval j’s index, which means that the interval j has the minimum start point to build the “right” relationship for interval i. If the interval j doesn’t exist, store -1 for the interval i. Finally, you need output the stored value of each interval as an array. Note:

  1. You may assume the interval’s end point is always bigger than its start point.
  2. You may assume none of these intervals have the same start point.
Example 1:
Input: [ [1,2] ]
Output: [-1]
Explanation: There is only one interval in the collection, so it outputs -1.
Example 2:
Input: [ [3,4], [2,3], [1,2] ]
Output: [-1, 0, 1]
Explanation: There is no satisfied "right" interval for [3,4].
For [2,3], the interval [3,4] has minimum-"right" start point;
For [1,2], the interval [2,3] has minimum-"right" start point.
Example 3:
Input: [ [1,4], [2,3], [3,4] ]
Output: [-1, 2, -1]
Explanation: There is no satisfied "right" interval for [1,4] and [3,4].
For [2,3], the interval [3,4] has minimum-"right" start point.

给定一系列区间[start,end],定义区间[i,j]的“右区间”为[u,v]使得u是第一个不小于j的数。要求输出每个区间的右区间下标,如果不存在右区间,则输出-1。 我的解法。因为要输出右区间在原数组中的下标,所以得保留每个区间在原数组中的下标。定义一个新的结构体MyInterval,用来保存区间以及该区间在原数组中的下标。对所有区间按start排序,然后遍历每一个区间[i,j],在已排序区间数组中找第一个start大于j的区间,取出它的下标作为区间[i,j]的结果。 代码如下: [cpp] typedef struct MyInterval { Interval inter; int idx; MyInterval(Interval inter_, int idx_) :inter(inter_), idx(idx_) {}; bool operator<(const MyInterval& myi) const{ return this->inter.start < myi.inter.start; // 所有start不相同 } }; class Solution2 { public: vector<int> findRightInterval(vector<Interval>& intervals) { int n = intervals.size(); vector<MyInterval> myvs; for (int i = 0; i < n; ++i) { MyInterval myint(intervals[i], i); myvs.push_back(myint); } sort(myvs.begin(), myvs.end()); vector<int> ans(n, -1); for (int i = 0; i < n; ++i) { for (int j = i + 1; j < n; ++j) { // 线性查找 if (myvs[j].inter.start >= myvs[i].inter.end) { ans[myvs[i].idx] = myvs[j].idx; break; } } } return ans; } }; [/cpp] 本代码提交AC,用时299MS。 上述代码还可优化,在已排序区间数组中找第一个start大于j的区间时,可以用二分搜索。 另外因为所有区间的start不相同,所以事先用hash记录start和原数组下标。这样只需要把所有区间的start抽出来排序,然后二分查找每一个end。代码如下: [cpp] class Solution { private: int binsearch(const vector<int>& starts, int target) { int l = 0, r = starts.size() – 1; while (l <= r) { int m = l + (r – l) / 2; if (starts[m] == target)return starts[m]; if (starts[m] < target)l = m + 1; else r = m – 1; } return starts[l]; } public: vector<int> findRightInterval(vector<Interval>& intervals) { int n = intervals.size(); vector<int> starts; unordered_map<int, int> hash; for (int i = 0; i < n; ++i) { starts.push_back(intervals[i].start); hash[intervals[i].start] = i; } sort(starts.begin(), starts.end()); vector<int> ans(n, -1); for (int i = 0; i < n; ++i) { if (intervals[i].end <= starts[n – 1]) { int t = binsearch(starts, intervals[i].end); ans[i] = hash[t]; } } return ans; } }; [/cpp] 本代码提交AC,用时96MS,快了不少。]]>

LeetCode Summary Ranges

228. Summary Ranges2

Given a sorted integer array without duplicates, return the summary of its ranges.

Example 1:

Input:  [0,1,2,4,5,7]
Output: ["0->2","4->5","7"]
Explanation: 0,1,2 form a continuous range; 4,5 form a continuous range.

Example 2:

Input:  [0,2,3,4,6,8,9] Output: ["0","2->4","6","8->9"] Explanation: 2,3,4 form a continuous range; 8,9 form a continuous range.28. Summary Ranges

给定一个数组,把数组汇总成若干个连续的区间,以字符串的形式给出。
方法是判断两个相邻的数之差是否为1,不是1则前面的构成一个区间,转换为字符串输出。判断前一个区间只有一个数还是有多个数的方法是区间的起止位置i,j是否相差1,如果是,则只有一个数,否则有多个数。
注意最后一个区间需要在while循环外部判断。代码如下:

class Solution {
public:
    vector<string> summaryRanges(vector<int>& nums)
    {
        vector<string> ans;
        int n = nums.size(), i = 0, j = 1;
        if (n == 0)
            return ans;
        while (j < n) {
            if (nums[j] – nums[j – 1] == 1)
                ++j;
            else {
                if (j – i == 1)
                    ans.push_back(to_string(nums[i]));
                else
                    ans.push_back(to_string(nums[i]) + "->" + to_string(nums[j – 1]));
                i = j++;
            }
        }
        if (j – i == 1)
            ans.push_back(to_string(nums[i]));
        else
            ans.push_back(to_string(nums[i]) + "->" + to_string(nums[j – 1]));
        return ans;
    }
};

本代码提交AC,用时0MS。
还有一种解法利用了二分查找的思路,很有意思,参考讨论区
假如给定的数组是[1,2,3,4,5,…,10000,20000],如果还是用上述方法,时间复杂度是O(n),至少需要遍历一遍数组。但是数组的前10000个数是严格有序且连续递增的,如果能把这一部分识别出来,直接转换为”1->10000″,则速度会大大提高。
二分查找的思路是,对于给定区间[a,…,b],假设其在数组中的下标起点分别为[i,…,j],则如果b-a==j-i,说明[a,b]之间是类似上面的[1,2,…,10000]的情况的,因为数组中没有重复元素,所以区间有j-i个元素,且元素最大值和最小值的差b-a也是j-i,说明他们是一个连续的有序区间,我们直接把这个区间转换为”a->b”。
否则如果j-i!=b-a,则取中点mid=(i+j)/2,递归在[i,mid]和[mid+1,j]进行。
有一种情况需要注意的是,分割出来的区间可能需要合并,比如[1,2,3,4,5,6,8],初始i[1,..,8]不满足b-a==j-i,所以递归在[1,2,3,4]和[5,6,8]进行。左边转换为了”1->4″,右边还需要递归,假设转换为了”5->6″和”8″,那么”1->4″和”5->6″是需要合并的。所以我们在插入”5->6″时,看看之前得到的区间”1->4″的end是否和当前区间”5->6″的start只差1,如果是,则需要合并,更新之前区间的end为现在要插入区间的end,变成了”1->6″。
完整代码如下:

class Solution {
private:
    struct Range {
        int start, end;
        Range(int s, int e)
            : start(s)
            , end(e){};
    };
    void add2Ans(int a, int b, vector<Range>& ranges)
    {
        if (ranges.empty() || ranges[ranges.size() – 1].end != a – 1) {
            ranges.push_back(Range(a, b));
        }
        else {
            ranges[ranges.size() – 1].end = b;
        }
    }
    void helper(vector<int>& nums, int start, int end, vector<Range>& ranges)
    {
        if (start == end || end – start == nums[end] – nums[start]) {
            add2Ans(nums[start], nums[end], ranges);
            return;
        }
        int mid = start + (end – start) / 2;
        helper(nums, start, mid, ranges); // 先左区间
        helper(nums, mid + 1, end, ranges); // 后右区间
    }

public:
    vector<string> summaryRanges(vector<int>& nums)
    {
        vector<string> ans;
        if (nums.empty())
            return ans;
        vector<Range> ranges;
        helper(nums, 0, nums.size() – 1, ranges);
        for (const auto& r : ranges) {
            if (r.start == r.end)
                ans.push_back(to_string(r.start));
            else
                ans.push_back(to_string(r.start) + "->" + to_string(r.end));
        }
        return ans;
    }
};

本代码提交AC,用时0MS。
这个代码在数据有很多连续区间时,接近O(lgn)的复杂度。但是当数据全是不连续的时,比如[1,3,5,7,9],则只有到递归到最深层start==end(即针对单个数字)时,才得到一个区间,所以退化为O(n)的算法。
如果再follow up可能有重复元素时,上述二分查找的方法就不管用了,因为属于一个区间的不一定满足b-a==j-i,比如[1,2,2,2,3],b-a=2,而j-i=4。此时只能用第一种O(n)的方法:

class Solution {
public:
    vector<string> summaryRanges(vector<int>& nums)
    {
        vector<string> ans;
        if (nums.empty())
            return ans;
        int i = 0, j = 1, n = nums.size();
        while (j < n) {
            while (j < n && (nums[j] == nums[j – 1] || nums[j] – nums[j – 1] == 1))
                ++j; // 考虑重复元素
            if (j >= n)
                break;
            if (j – 1 == i)
                ans.push_back(to_string(nums[i]));
            else
                ans.push_back(to_string(nums[i]) + "->" + to_string(nums[j – 1]));
            i = j++;
        }
        if (j – 1 == i)
            ans.push_back(to_string(nums[i]));
        else
            ans.push_back(to_string(nums[i]) + "->" + to_string(nums[j – 1]));
        return ans;
    }
};

本代码提交AC,用时0MS。

二刷。更加鲁邦容易理解的代码:

class Solution {
public:
	vector<string> summaryRanges(vector<int>& nums) {
		vector<vector<int>> ranges;
		int i = 0, n = nums.size();
		while (i < n) {
			int j = i + 1;
			while (j < n&&nums[j] == nums[j - 1] + 1)++j;
			ranges.push_back({ nums[i],nums[j - 1] });
			i = j;
		}
		vector<string> ans;
		for (int i = 0; i < ranges.size(); ++i) {
			if (ranges[i][0] == ranges[i][1]) {
				ans.push_back(to_string(ranges[i][0]));
			}
			else {
				ans.push_back(to_string(ranges[i][0]) + "->" + to_string(ranges[i][1]));
			}
		}
		return ans;
	}
};

本代码提交AC,用时0MS。

LeetCode First Bad Version

278. First Bad Version

You are a product manager and currently leading a team to develop a new product. Unfortunately, the latest version of your product fails the quality check. Since each version is developed based on the previous version, all the versions after a bad version are also bad.

Suppose you have n versions [1, 2, ..., n] and you want to find out the first bad one, which causes all the following ones to be bad.

You are given an API bool isBadVersion(version) which will return whether version is bad. Implement a function to find the first bad version. You should minimize the number of calls to the API.

Example:

Given n = 5, and version = 4 is the first bad version. call isBadVersion(3) -> false call isBadVersion(5) -> true call isBadVersion(4) -> true Then 4 is the first bad version. 

从[1,2,…,n]有n个版本,其中有个版本是坏的,导致其后的所有版本都是坏的,现在要找到第一个坏了的版本。
简单题,二分搜索,相当于找lowerbound,正好刷了LeetCode Search for a Range,理清了二分搜索查找上下界的方法,此题直接实现lowerbound就好。代码如下:

// Forward declaration of isBadVersion API. bool isBadVersion(int version);
class Solution {
public:
    int firstBadVersion(int n)
    {
        int l = 1, r = n;
        while (l <= r) {
            int m = l + (r – l) / 2;
            if (!isBadVersion(m))
                l = m + 1;
            else
                r = m – 1;
        }
        return l;
    }
};

本代码提交AC,用时0MS。

二刷:

class Solution {
public:
	int firstBadVersion(int n) {
		int left = 1, right = n;
		while (left <= right) {
			int mid = left + (right - left) / 2;
			bool bad = isBadVersion(mid);
			if (bad)right = mid - 1;
			else left = mid + 1;
		}
		return right + 1;
	}
};

本代码提交AC,用时0MS。

LeetCode Search for a Range

LeetCode Search for a Range Given an array of integers sorted in ascending order, find the starting and ending position of a given target value. Your algorithm’s runtime complexity must be in the order of O(log n). If the target is not found in the array, return [-1, -1]. For example, Given [5, 7, 7, 8, 8, 10] and target value 8, return [3, 4].


本题要在一个有序数组中求一个数出现的下标区间,如果该数不在数组中,返回[-1,-1],要求用O(lgn)时间复杂度。 很明显用两次二分搜索找到该数的上下区间即可,可以认为是二分搜索的综合题。下界就是第一个不小于target的位置,所以当找到中间值和target相等时,我们要继续往左找。上界就是最后一个不大于target的位置,所以当找到中间值和target相等时,我们要继续往右找。理清了这个思路之后就很好写代码了,如下: [cpp] class Solution { private: int lowerbound(vector<int>& nums, int target) { int l = 0, r = nums.size() – 1; while (l <= r) { int m = l + (r – l) / 2; if (nums[m] < target)l = m + 1; else if (nums[m] >= target)r = m – 1; } return l; } int upperbound(vector<int>& nums, int target) { int l = 0, r = nums.size() – 1; while (l <= r) { int m = l + (r – l) / 2; if (nums[m] <= target)l = m + 1; else if (nums[m] > target)r = m – 1; } return r; } public: vector<int> searchRange(vector<int>& nums, int target) { int lower = lowerbound(nums, target), upper = upperbound(nums, target); if (lower <= upper)return vector<int>({ lower, upper }); else return vector<int>({ -1,-1 }); } }; [/cpp] 本代码提交AC,用时9MS。 注意一点,为啥lowerbound是返回l,而upperbound是返回r呢,对于lowerbound,我们想象一下当中值等于target时,我们不停的往左走,即r=m-1,直到l<=r不再满足,即此时r已经小于l了,也就是说r往左走过头了,使得r对应的值已经小于target了,但此时l对应的值还是等于target,所以根据lowerbound的定义,我们返回l。对于uppperbound也类似,当中值等于target时,不停往右走,即l=m+1,直到不满足l<=r,此时l对应的值已经大于target,而r对应的值还是等于target的,根据upperbound的定义,我们返回r。 其实,STL中已经实现了lower_boundupper_bound其lower_bound和上文的lowerbound含义是一致的,upper_bound稍有不同,STL中的upper_bound是返回第一个大于target的位置,比上文的upperbound要大一个位置。所以如果借助STL的算法,需要对upper_bound返回的位置减1。代码如下: [cpp] class Solution { public: vector<int> searchRange(vector<int>& nums, int target) { if (nums.size() == 0)return vector<int>({ -1,-1 }); vector<int>::iterator lower = lower_bound(nums.begin(), nums.end(), target), upper = upper_bound(nums.begin(), nums.end(), target) – 1; if (lower <= upper)return vector<int>({ lower – nums.begin(), upper – nums.begin() }); else return vector<int>({ -1,-1 }); } }; [/cpp] 本代码提交AC,用时16MS。貌似STL中的实现也是O(lgn)的,看来性能还是有所差别。]]>

LeetCode Guess Number Higher or Lower

LeetCode Guess Number Higher or Lower We are playing the Guess Game. The game is as follows: I pick a number from 1 to n. You have to guess which number I picked. Every time you guess wrong, I’ll tell you whether the number is higher or lower. You call a pre-defined API guess(int num) which returns 3 possible results (-1, 1, or 0):

-1 : My number is lower
 1 : My number is higher
 0 : Congrats! You got it!
Example:
n = 10, I pick 6.
Return 6.

简单的猜数字游戏。使用二分搜索就好。唯一需要注意的是,题目中的guess函数中说My number is lower/higher是指正确答案比中值低或高,而不是我们猜的中值比正确答案低或高。唉,这算是题目描述不清吧,为此WA两次。 代码如下: [cpp] int guess(int num); class Solution { public: int guessNumber(int n) { int l = 1, r = n, mid = l + (r – l) / 2; int g = guess(mid); while (g != 0) { if (g == -1)r = mid – 1; else l = mid + 1; mid = l + (r – l) / 2; g = guess(mid); } return mid; } }; [/cpp] 本代码提交AC,用时0MS。]]>

LeetCode Search a 2D Matrix II

240. Search a 2D Matrix II 240. Search a 2D Matrix II

Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following properties:

  • Integers in each row are sorted in ascending from left to right.
  • Integers in each column are sorted in ascending from top to bottom.

Example:

Consider the following matrix:

[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]

Given target = 5, return true.

Given target = 20, return false. 240. Search a 2D Matrix II


LeetCode Search a 2D Matrix基础上改动了一点点,但是难度增加不少。这一次,每行和每列都是递增的,但是这一行的行首元素和上一行的行末元素没有大小关系,这种矩阵有点像从左上角递增到右下角的一个曲面,想象一下。 一种比较笨的办法就是对每一行或者每一列二分搜索,复杂度为O(mlgn)或O(nlgm)。 参考这位大神的介绍,有很多厉害的算法,具体看原博客,这里简单给出其前两个算法的C++实现。 算法1相当于对角二分搜索,是线性复杂度的。搜索从矩阵右上角开始,遇到比当前元素大时,递增行;遇到比当前元素小时,递减列;直到找到相等元素,或者到达矩阵左下角。最坏情况是从右上角走到了左下角,复杂度为O(m+n)。 这种思路的代码如下:

class Solution {
public:
    bool searchMatrix(vector<vector<int> >& matrix, int target)
    {
        if (matrix.size() == 0)
            return false;
        int m = matrix.size(), n = matrix[0].size();
        if (n == 0)
            return false;
        int i = 0, j = n – 1;
        while (i < m && j >= 0) {
            while (i < m && matrix[i][j] < target)
                ++i;
            if (i >= m)
                return false;
            while (j >= 0 && matrix[i][j] > target)
                –j;
            if (j < 0)
                return false;
            if (matrix[i][j] == target)
                return true;
        }
        return false;
    }
};

本代码提交AC,用时129MS。
算法2利用了分治法的思路。每次取矩阵中心元素,把矩阵分成4个小份。如果target大于中心元素,则左上角部分可以全部舍弃掉,因为矩阵的行列都是递增的,左上角元素肯定全部小于中心元素,不必再找。此时可以递归的在剩余的3个小矩阵里查找。比如下图中心元素为9,如果要找26,则可以递归的在其右上角、左下角和右下角三个小矩阵中查找。

时间复杂度公式为T(n)=3T(n/2)+c,c为target和中心元素比较的时间。使用主方法可以算到T(n)=O(n1.58)。
分治法思路代码如下:

class Solution {
public:
    bool quadPart(vector<vector<int> >& matrix, int left, int top, int right, int bottom, int target)
    {
        if (left > right || top > bottom)
            return false;
        if (target < matrix[left][top] || target > matrix[right][bottom])
            return false;
        int midrow = left + (right - left) / 2, midcol = top + (bottom - top) / 2;
        if (target == matrix[midrow][midcol])
            return true;

        //if (target > matrix[midrow][midcol]) { // 抛弃左上角
        //	return quadPart(matrix, left, midcol + 1, midrow - 1, bottom, target) || // 右上角
        //		quadPart(matrix, midrow + 1, top, right, midcol - 1, target) || // 左下角
        //		quadPart(matrix, midrow, midcol, right, bottom, target); // 右下角
        //}
        //else {
        //	return quadPart(matrix, left, midcol + 1, midrow - 1, bottom, target) || // 右上角
        //		quadPart(matrix, midrow + 1, top, right, midcol - 1, target) || // 左下角
        //		quadPart(matrix, left, top, midrow, midcol, target); //左上角
        //}

        if (target > matrix[midrow][midcol]) { // 抛弃左上角
            return quadPart(matrix, left, midcol + 1, midrow, bottom, target) || // 右上角
                quadPart(matrix, midrow + 1, top, right, midcol, target) || // 左下角
                quadPart(matrix, midrow + 1, midcol + 1, right, bottom, target); // 右下角
        }
        else {
            return quadPart(matrix, left, midcol, midrow - 1, bottom, target) || // 右上角
                quadPart(matrix, midrow, top, right, midcol - 1, target) || // 左下角
                quadPart(matrix, left, top, midrow - 1, midcol - 1, target); //左上角
        }
    }
    bool searchMatrix(vector<vector<int> >& matrix, int target)
    {
        if (matrix.size() == 0)
            return false;
        int m = matrix.size(), n = matrix[0].size();
        if (n == 0)
            return false;
        return quadPart(matrix, 0, 0, m - 1, n - 1, target);
    }
};

本代码提交AC,用时168MS。
注意代码中注释掉的部分是错误代码,因为其在右下角或者左上角搜索时可能会进入死循环。比如要在上图搜索30,当搜索到右下角深色部分的小矩阵时,中心元素是17,本该搜索更小的矩阵30,但是根据注释代码中第一个if搜索右下角的代码,新的矩阵(右下角)的左上角坐标还是17,因为其坐标是(midrow, midcol),无论递归多少次,右下角的矩阵没有变小,所以进入了死循环。
正确的方法是把涉及到中点的两条边分配到不同的小矩阵里,必须使得每个小矩阵的左上角或右下角坐标有变化。

LeetCode Search a 2D Matrix

74. Search a 2D Matrix

Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following properties:

  • Integers in each row are sorted from left to right.
  • The first integer of each row is greater than the last integer of the previous row.

Example 1:

Input:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 3
Output: true

Example 2:

Input:
matrix = [
  [1,   3,  5,  7],
  [10, 11, 16, 20],
  [23, 30, 34, 50]
]
target = 13
Output: false

给定一个矩阵,每行升序排列,并且下一行的最小值大于上一行的最大值,也就是说如果把矩阵按行展开,就是一个递增序列。问矩阵中是否存在一个数target。 很明显是二分搜索,先把每个行首元素看成一个新的有序数组,进行二分查找,确定所在行midr。然后在这一行进行二分搜索,查找target。 完整代码如下:

class Solution {
public:
    bool searchMatrix(vector<vector<int> >& matrix, int target)
    {
        if (matrix.size() == 0)
            return false;
        int m = matrix.size(), n = matrix[0].size();
        if (n == 0)
            return false;
        int l = 0, r = m – 1, midr = 0, midc = 0;
        while (l <= r) {
            midr = (l + r) / 2;
            if (target >= matrix[midr][0]) {
                if (target <= matrix[midr][n – 1])
                    break;
                l = midr + 1;
            }
            else
                r = midr – 1;
        }
        l = 0, r = n – 1;
        while (l <= r) {
            midc = (l + r) / 2;
            if (target == matrix[midr][midc])
                return true;
            if (target > matrix[midr][midc])
                l = midc + 1;
            else
                r = midc – 1;
        }
        return false;
    }
};

本代码提交AC,用时9MS。

二刷。更加规范的代码。首先找到目标数字所在的行,即找每一行开头数字中第一个大于目标数字的行,则前一行即为该数字所在的行,也就是l-1。为什么是l-1而不是r+1呢?这样想象:我们要找比target大的行首元素,如果mid一直小于target的话,则l会一直进行mid+1操作,直到l指向的元素大于target,此时前一行即为target所在行。不过要注意判断前一行是否存在的情况。

class Solution {
public:
	bool BinarySearch(vector<int>& vec, int target) {
		int l = 0, r = vec.size() - 1;
		while (l <= r) {
			int mid = l + (r - l) / 2;
			if (vec[mid] == target)return true;
			if (vec[mid] < target)l = mid + 1; 
			else r = mid - 1;
		}
		return false;
	}
	bool searchMatrix(vector<vector<int>>& matrix, int target) {
		int m = matrix.size();
		if (m == 0)return false;
		int n = matrix[0].size();
		if (n == 0)return false;

		int l = 0, r = m - 1;
		while (l <= r) {
			int mid = l + (r - l) / 2;
			if (matrix[mid][0] == target)return true;
			if (matrix[mid][0] < target)l = mid + 1; 
			else r = mid - 1;
		}
		if (l < 1)return false;
		return BinarySearch(matrix[l - 1], target);
	}
};

本代码提交AC,用时16MS。

LeetCode Find Peak Element

162. Find Peak Element

A peak element is an element that is greater than its neighbors.

Given an input array nums, where nums[i] ≠ nums[i+1], find a peak element and return its index.

The array may contain multiple peaks, in that case return the index to any one of the peaks is fine.

You may imagine that nums[-1] = nums[n] = -∞.

Example 1:

Input: nums = [1,2,3,1]
Output: 2
Explanation: 3 is a peak element and your function should return the index number 2.

Example 2:

Input: nums = [1,2,1,3,5,6,4]
Output: 1 or 5 
Explanation: Your function can return either index number 1 where the peak element is 2, 
             or index number 5 where the peak element is 6.

Note:

Your solution should be in logarithmic complexity.


要求在一个数组中找一个局部峰值的下标,局部峰值就是大于其左右相邻的两个元素。
暴力方法就是线性查找。更好一点的方法就是二分查找。如果中值nums[m]正好大于其左右相邻的元素,则返回m;如果nums[m]<nums[m-1],则在左边查找,因为nums[m-1]已经大于其右半边了,所以左边很有希望找到峰值,如果nums[m-1]>nums[m-2]也成立,则nums[m-1]就是峰值;否则继续往左边找,因为nums[-1]=-∞,所以肯定能找到一个局部峰值。右边也是类似。
代码如下:

class Solution {
public:
    int findPeakElement(vector<int>& nums)
    {
        int n = nums.size();
        if (n == 1)
            return 0;
        int l = 0, r = n – 1, m = 0;
        while (l <= r) {
            m = (l + r) / 2;
            if ((m == 0 || nums[m] > nums[m – 1]) && (m == n – 1 || nums[m] > nums[m + 1]))
                return m;
            if (m > 0 && nums[m] < nums[m – 1])
                r = m – 1;
            else
                l = m + 1;
        }
        return m;
    }
};

本代码提交AC,用时6MS。

二刷。相同思路,不同代码:

class Solution {
public:
	int findPeakElement(vector<int>& nums) {
		int n = nums.size();
		if (n == 1)return 0;
		if (n == 2)return nums[0] > nums[1] ? 0 : 1;

		int l = 0, r = n - 1;
		while (l <= r) {
			if (r - l <= 1)return nums[l] > nums[r] ? l : r;

			int mid = l + (r - l) / 2;
			if (nums[mid] > nums[mid - 1] && nums[mid] > nums[mid + 1])return mid;
			if (nums[mid] < nums[mid - 1]) {
				r = mid - 1;
			}
			else {
				l = mid + 1;
			}
		}
		return 0;
	}
};

本代码提交AC,用时12MS。

LeetCode Arranging Coins

LeetCode Arranging Coins You have a total of n coins that you want to form in a staircase shape, where every k-th row must have exactly k coins. Given n, find the total number of full staircase rows that can be formed. n is a non-negative integer and fits within the range of a 32-bit signed integer. Example 1:

n = 5
The coins can form the following rows:
¤
¤ ¤
¤ ¤
Because the 3rd row is incomplete, we return 2.
Example 2:
n = 8
The coins can form the following rows:
¤
¤ ¤
¤ ¤ ¤
¤ ¤
Because the 4th row is incomplete, we return 3.

问n个硬币最多能摆成多少级台阶,每级台阶的硬币数是以1为步长递增的。其实就是求1+2+…+k<=n的最大的k。当然最简单的方法是暴力枚举[1,n]之间的k。 O(lgn)的方法是二分搜索满足上述不等式的最大的k。代码如下: [cpp] class Solution { public: int arrangeCoins(int n) { long long l = 0, r = n; while (l <= r) { long long m = (l + r) / 2; long long sum = (1 + m)*m / 2; if (n < sum)r = m – 1; else l = m + 1; } return r; } }; [/cpp] 本代码提交AC,用时35MS。 参考:http://bookshadow.com/weblog/2016/10/30/leetcode-arranging-coins/ P.S.感觉二分搜索的代码边界条件很麻烦。。。]]>