LeetCode中级算法之排序和搜索

颜色分类

Question:
给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
注意:
不能使用代码库中的排序函数来解决这道题。

示例:
输入: [2,0,2,1,1,0]
输出: [0,0,1,1,2,2]

进阶:
一个直观的解决方案是使用计数排序的两趟扫描算法。
首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。
你能想出一个仅使用常数空间的一趟扫描算法吗?
Solution:
遇到0则插到头部,遇到2插到末尾,遇到1不变:

void sortColors(vector<int>& nums) {
        int i = 0;
    	for(int j = 0; j < nums.size(); j++)
    	{
    		if(nums[i] == 0)
    		{
    			nums.insert(nums.begin(), nums[i]);
    			nums.erase(nums.begin() + i + 1);
                i++;
    		}
    		else if(nums[i] == 2)
    		{
    			nums.push_back(nums[i]);
    			nums.erase(nums.begin() + i);
    		}
            else i++;
    	}
        
    }

前K个高频元素

Question:
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

示例 2:
输入: nums = [1], k = 1
输出: [1]

说明:
你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
Solution:
简直是为hash表量身定做的题目,建立hash表记录出现次数后,再利用大顶堆得到前k个高频元素:

 vector<int> topKFrequent(vector<int>& nums, int k) {
        map <int,int> m;
        vector <int> res;
        for(int i = 0; i < nums.size(); i++)
        {
            if(m[nums[i]]) m[nums[i]]++;
            else m[nums[i]] = 1;
        }
        priority_queue <pair<int,int>> q;
        for(auto it = m.begin(); it != m.end(); it++)
        {
        	q.push({it -> second,it -> first});
        }
        for(int i = 0; i < k; i++)
        {
        	res.push_back(q.top().second);
        	q.pop();
        }
        return res;        
    }

数组中的第K个最大元素

Question:
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5

示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4

说明:
你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。
Solution:
解法一:
直接用priority_queue来做:

int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int> onums;
        for(auto i : nums)
        	onums.push(i);
        for(int i = 0; i < k - 1; i++)
            onums.pop();
        return onums.top();
    }

解法二:
快速排序,排序规则是大的在左,小的在右,每次排序后,判断pivot位置,如果大于k,那么再对左半部分排序,否则对右半部分进行排序,直到pivot等于k:

int partition(vector<int> &nums, int left, int right)
    {
    	int i = left + 1;
    	int j = right;
    	int temp = nums[left];
    	while(i <= j)
        {
            if(nums[i] < temp && temp < nums[j]) swap(nums[i++],nums[j--]);
            if (nums[i] >= temp) i++;
            if (nums[j] <= temp) j--;
        }
        swap(nums[j], nums[left]);
        return j;
    }
    int quick_sort(vector<int>& nums, int k, int left, int right)
    {
    	if(left == right) return nums[left];
    	int pivot = partition(nums, left, right);
    	if(pivot == k) return nums[pivot];
    	else if(pivot < k) return quick_sort(nums, k,pivot + 1, right);
    	else return quick_sort(nums, k, left, pivot - 1);
    }
    int findKthLargest(vector<int>& nums, int k) {
    	if(nums.size() == 1) return nums[0];
        return quick_sort(nums, k - 1, 0, nums.size() - 1); 
    }

好久没有写快排了,真的生疏了,以后要多练。

寻找峰值

Question:
峰值元素是指其值大于左右相邻值的元素。
给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。
数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞。

示例 1:
输入: nums = [1,2,3,1]
输出: 2
解释: 3 是峰值元素,你的函数应该返回其索引 2。
示例 2:
输入: nums = [1,2,1,3,5,6,4]
输出: 1 或 5 
解释: 你的函数可以返回索引 1,其峰值元素为 2;
     或者返回索引 5, 其峰值元素为 6。

说明:
你的解法应该是 O(logN) 时间复杂度的。

Solution:

    int findPeakElement(vector<int>& nums) {
        int i = 0;
        if(nums.size() == 1) return 0;
        while(i < nums.size() - 1  && nums[i] < nums[i + 1])
            i++;
        return i;
    }

这个算法是O(n)时间复杂度的, 我们考虑一个时间复杂度为O(logn)的算法,那就是折半查找了,那么折半查找怎么应用到这道题中呢?考虑一个中间坐标,如果它大于左右两边的数,那么就找到了峰值,如果它小于一边的数,那么就往这一边继续查找,如果是向左,最后查找到0的时候一定能得到结果,如果是向右,那么迟早会找到另外一边也小于它的数。
这个思路我是看网上的,自己没有想出来,还是要仔细观察呀:

int midsearch(vector<int> nums, int l, int r)
    {
    	if(l == r) return l;
        if(r - l == 1) return nums[r] > nums[l] ? r : l;
    	int mid = (l + r) / 2;
    	if(nums[mid] < nums[mid + 1])
    		return midsearch(nums, mid + 1, r);
    	if(nums[mid] < nums[mid - 1])
    		return midsearch(nums, l, mid - 1);
    	return mid;
    }

    int findPeakElement(vector<int>& nums) {
        return midsearch(nums, 0, nums.size() - 1);
    }

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

Question:
给定一个按照升序排列的整数数组 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]

Solution:
先按折半查找写一下:

vector<int> searchRange(vector<int>& nums, int target) {
        vector <int> res;
         if(nums.empty())
         {
            res.push_back(-1);
        	res.push_back(-1);
        	return res;
         }
    	int l = 0, r = nums.size() - 1;
        int mid = r / 2;
        while(nums[mid] != target)
        {
        	if(l >= r) 
        	{
        		res.push_back(-1);
        		res.push_back(-1);
        		return res;
        	}
        	if(nums[mid] > target)  r = mid - 1;
        	else l = mid + 1;
        	mid = (r + l) / 2;
        }
        int i = mid, tar = nums[mid];
        while(i >= 0 && nums[i] == tar )
            i--;
        res.push_back(i + 1);
        i = mid;
        while(i < nums.size() && nums[i] == tar)
        	i++;
         res.push_back(i - 1);
        return res;
    }

竟然通过了,0.0

合并区间☆

Question:
给出一个区间的集合,请合并所有重叠的区间。

示例 1:
输入: [[1,3],[2,6],[8,10],[15,18]]
输出: [[1,6],[8,10],[15,18]]
解释: 区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
示例 2:
输入: [[1,4],[4,5]]
输出: [[1,5]]
解释: 区间 [1,4] 和 [4,5] 可被视为重叠区间。

Solution:
这道题难度不大,但需要非常细心,我是把插入分类为7种情况,在已插入的向量中分情况处理:
待插入区间设置为[a,b],遍历区间设置为it[0],it[1]

  1. b < it[0],直接插入在该区间前面
  2. a < it[0] && it[0] <= b < it[1], 将it[0]设置为a
  3. a >= it[0] && b <= it[1] , 默认已经完成插入
  4. it[0] < a <= it[1] && b > it[1], 将it[1]设置为b
  5. a > it[1] ,向后遍历
  6. a < it[0] && b > it[1],将it[0]设置为a,it[1]设置为b
  7. 如果遍历到末尾,就直接在末尾添加

聪明的小伙伴一定会看出这里的问题,对的那就是情况4和情况6中不能简单的将it[1]设置为b,因为b可能还会包括后面的数组,所以需要加上后续的遍历,把影响到的数组都合并进来。
整个算法思路就是这样,难度不高,但我觉得这种题做起来很爽,可以迅速提高代码技能,做完感觉很有收获!代码:、

    void mergeinsert(vector<vector<int>>& res, int a, int b)
    {
    	vector<int> temp;
    	if(res.empty()) 
    	{
           // cout << "sit0" << endl;
    		temp.push_back(a);
    	    temp.push_back(b);
    	    res.push_back(temp);
    	    return;
    	}
    	for(auto it = res.begin(); it != res.end(); it++)
    	{
    		if(b < it[0][0])     		//sit 1
    		{//cout << "sit1" << endl; 
    			temp.push_back(a);
    	        temp.push_back(b);
    	        res.insert(it,temp);
    	        return;
    		}
    		if(a <= it[0][0] && b >= it[0][0])
    		{
    			if(b >= it[0][1]) //sit6
    			{ //cout << "sit6" << endl; 
    				it[0][0] = a;
    				it[0][1] = b;
    				auto it2 = it + 1;
    				while(it2 != res.end() && it[0][1] >= it2[0][1])
    			    	res.erase(it2);
                    if(it2 == res.end())
                    {
                       // cout << "sit41" << endl;
                        it[0][1] = b;
                        return;
                    }
    			    if(it[0][1] >= it2[0][0])
    			    {
    			    	it[0][1] = it2[0][1];
    			    	res.erase(it2);
                        return;
    			    }
                 it[0][1] = b;
    			    return;
    			}
    			else
    			{
                    // cout << "sit2" << endl;
    				it[0][0] = a;
    				return;
    			}
    		}
            if(a >= it[0][0] && b <= it[0][1])//sit3
            { //cout << "sit3" << endl;return;}
    	    if(b >= it[0][1] && a >= it[0][0])
    	    {
    	       if(a > it[0][1]) { //cout << "sit5" << endl; continue; }//sit5
    	       else //sit4
    	       {
                    //cout << "sit4" << endl;
    	          	auto it2 = it + 1;
                    it[0][1] = b;
    				while(it2 != res.end() && it[0][1] >= it2[0][1])
    			    	res.erase(it2);
                    if(it2 == res.end())
                    {
                       // cout << "sit41" << endl;
                        return;
                    }
    			    if(it[0][1] >= it2[0][0])
    			    {
    			    	it[0][1] = it2[0][1];
    			    	res.erase(it2);
    			    }
    			    return;
    	       }
    	    }
    	}
        // cout << "sit7" << endl;
    	temp.push_back(a);
    	temp.push_back(b);
    	res.push_back(temp);
    	return;
    }
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
    	vector<vector<int>> res;
    	for(auto m : intervals)
    	    mergeinsert(res, m[0], m[1]);
    	return res;
    }

搜索旋转排序数组

Question:
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
你可以假设数组中不存在重复的元素。
你的算法时间复杂度必须是 O(log n) 级别。

示例 1:
输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4
示例 2:
输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1

Solution:
同样是一道需要观察规律的题目,看到log(n)的时间要求,就要想到用折半查找,但是由于旋转排序数组的特性,我们要找到规律。

  1. 折半查找的递归入口是找到目标数所在的那一部分
  2. 在旋转排序数组中,我们可以发现,无论在哪里取一个点,它的左半部分和右半部分一定有一个是有序的
  3. 利用这个性质,我们可以在有序数组中判断是否存在目标数,如果不存在,就去另外一部分递归
  4. 如何判断哪一部分有序?只需要判断最右的数和该点处的数的大小即可,如果该数大于最右边的数字,说明这个数是后来才旋转到数组左边的,那么它左边一定保持有序,反之,→_→一定有序。
    代码:
int search(vector<int>& nums, int target) {
    	int n = nums.size() ;
    	int l = 0, r = n - 1 ,mid = n / 2;
    	while(l <= r)
    	{
                        cout << l << "-l " << r << "-r "<< mid <<"-mid"<<endl;
            if(r - l <= 1) 
            {
                if(nums[r] == target) return r;
                if(nums[l] == target) return l;
                return -1;
            }
    		if(nums[mid] == target) return mid;
    		if(nums[mid] >= nums[r])
    		{
    			if(nums[mid] > target && nums[l] <= target)
    			{
                    r = mid;
    				mid = (mid + l) / 2;
    			}
    			else
    			{
                    l = mid;
    				mid = (mid + r) / 2;
    			}

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

搜索二维矩阵 II

Question:
编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:
每行的元素从左到右升序排列。
每列的元素从上到下升序排列。

示例:
现有矩阵 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]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。

Solution:
这种题目一定要多观察,如果用二分法查找每一行,时间复杂度是O(nlogn),这样完全没有利用到列之间也是有序的这个规则,仔细看题目给的例子,我们可以发现,每个矩阵的元素都是以他为右上角的子矩阵中的最大值,这样我们可以先对矩阵对角线上的元素进行遍历,直到找到比target大的元素,target一定在它的子矩阵中,这时候再进行观察,发现我们前面遍历的结果已经把之前的子矩阵都过滤掉了!也就是说,target一定与现在的元素在同一行或同一列,而且行号和列号小于该元素,这种算法的时间复杂度是O(m + n),可以说非常完美了。

    bool searchMatrix(vector<vector<int>>& matrix, int target) {
    	int i = 0, j = 0;
    	while(matrix[i][j] < target && i < matrix.size() && j < matrix[0].size()){
    		i++;j++;
    	}
    	int x = i;
    	while(x >= 0)
    	{
    		if(matrix[x][j] == target) return true;
    		x--;
    	}
    	x = j;
    	while(x >= 0)
    	{
    		if(matrix[i][x] == target) return true;
    		x--;
    	}
    	return false; 	
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值