代码杂记2

对顶堆

使用一个最大堆和一个最小堆,最大堆维护小于中位数的那一半数,最小堆维护大于中位数的那一半,然后 要保持两个堆的大小平衡,即数量差值小于等于1,当数字总数是奇数的时候,中位数就是数字数量较大堆的堆顶元素,当总数为偶数的时候,中位数就是两个堆顶元素的平均值。

	priority_queue<int,vector<int>,less<int>>que1 ;//最大堆存小于中位数的部分
	priority_queue<int,vector<int>,greater<int>>que2;//最小堆存大于中位数的部分

	void addNum(int num) {

		if (que1.empty() || num <= que1.top()) {
			que1.push(num);
			if (que2.size() + 1 < que1.size()) {
			que2.push(que1.top());
			que1.pop();
			}
		} else {
			que2.push(num);
			if (que2.size() > que1.size()) {
			que1.push(que2.top());
			que2.pop();
			}
		}
	}

巴什博弈

(博弈论的一种问题情形)
只有一堆n个物品,两个人轮流从这堆物品中取物,规定每次至少取一个,最多取m个。最后取光者得胜。只要 n 是(m+1)的倍数,那么 先手者就必输。反之,后者必赢。

类似题目:
1. 作为计算机学院的学生,Kiki和Cici打牌的时候可没忘记专业,她们打牌的规则是这样的:

  • 1、 总共n张牌;

  • 2、 双方轮流抓牌;

  • 3、 每人每次抓牌的个数只能是2的幂次(即:1,2,4,8,16…)

  • 4、 抓完牌,胜负结果也出来了:最后抓完牌的人为胜者

    这个可以dp

这类题目可以先枚举几个初始的状态,然后基本就可以找到规律了,本质还是动态规划,但是规划之后会有非常明显的循环,所以才会得出倍数关系

我们可以先列个表

剩余牌数先手必胜T/必输F
1T
2T
3F
4T
5T
6F
7T
8T
9F
10T
11T
12F

从上面可以逐渐看到,当剩余牌数为3的倍数,先手必输。我们可以逐步的看这个过程,首先1和2很明显可以直接拿走所有的牌而获胜,但是对于3,无论是拿走1或2,都会将先手必赢的局面让给对方,根据这个思路,我们可以发现,剩余牌数为4的情形下,获胜的方法就是把必输的局面让给对方,进一步,剩余牌数为n时,获胜的方法,就是枚举n-2^k ( k=0,1,2,3…),当我们能够找到一个局面使下次对面先手的时候必输,那么我们就是必赢的,否则我们必输。最后dp的结果就是发现循环。

前K大/小个元素

这个最简单的思路就是排序后直接查找,但是排序的时间复杂度最低为nlogn,这里介绍一种时间复杂度为n的方法。
以前K大元素为例,我们建立一个小根堆,当堆不满时,将当前元素插入堆中,当堆满时,比较当前元素与堆顶元素的大小,如果当前元素大于堆顶元素,则将堆顶元素弹出,将当前元素插入堆中,否则,不做任何操作。原理是,使用了小根堆,保证堆顶元素一定是其中最小的。对于前K大元素,它最多比K-1个元素要小,但是假如当前元素比堆顶元素小,它就已经小于K个元素了,所以它一定不会是前K大个元素。所以其实堆中存放的是遍历到当前位置之前可能的前K大元素,当我们遍历完所有元素之后就是最终的结果了。

一个实例代码,

构造堆

class Solution {
public:
    static bool cmp(pair<int, int>& m, pair<int, int>& n) {
        return m.second > n.second;
    }

    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int, int> occurrences;
        for (auto& v : nums) {
            occurrences[v]++;
        }

        // pair 的第一个元素代表数组的值,第二个元素代表了该值出现的次数
        priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(&cmp)> q(cmp);
        for (auto& [num, count] : occurrences) {
            if (q.size() == k) {
                if (q.top().second < count) {
                    q.pop();
                    q.emplace(num, count);
                }
            } else {
                q.emplace(num, count);
            }
        }
        vector<int> ret;
        while (!q.empty()) {
            ret.emplace_back(q.top().first);
            q.pop();
        }
        return ret;
    }
};

链接:https://leetcode.cn/problems/top-k-frequent-elements/solutions/402568/qian-k-ge-gao-pin-yuan-su-by-leetcode-solution/

基于桶排序思想

还是先使用哈希表统计出现的频率,不同之处在于使用桶来找到前K个高频元素,由于数组长度为n,所有元素的最大出现频率为n,所以说啊,我们可以建立长度为n的桶,每个桶的序号对应出现频率减一,先遍历一次所有元素,然后对这个桶反向遍历,直到已经找到K个元素或者遍历完桶。

字符串解码

给定一个经过编码的字符串,返回它解码后的字符串。编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a2[4] 的输入。

代码:

class Solution {
	public:
		string getDigits(string &s,size_t &ptr){
			string ret ="";
			while(isdigit(s[ptr])){
				ret.push_back(s[ptr++]);
			}
			return ret;
		}

		string getString(vector<string>&v){
			string ret;
			for(const auto &s:v){
				ret+=s;
			}
			return ret;
		}

	string decodeString(string s){
		vector<string>stk;
		size_t ptr=0;
	  
		while(ptr<s.size()){
			char cur=s[ptr];
			if(isdigit(cur)){
				string digits=getDigits(s,ptr);
				stk.push_back(digits);
			} else if(isalpha(cur)||cur=='['){
				stk.push_back(string(1,s[ptr++]));
			}
			else{
				++ptr;
				vector<string> sub;
				while(stk.back()!="["){
					sub.push_back(stk.back());
					stk.pop_back();
				}
				reverse(sub.begin(),sub.end());
				stk.pop_back();
				int repTime=stoi(stk.back());
				stk.pop_back();
				string t,o=getString(sub);
				while(repTime--)t+=o;
				stk.push_back(t);
			  
			}
		}

		return getString(stk);
	}
};

总之这题思路很好理解,使用栈来进行处理,构造了两个辅助函数,string getDigits(string &s,size_t &ptr) string getString(vector<string>&v),前者用于在输入字符串中截取一个连续的数字、同时更新指针ptr,后者用于用于从一个vector<string>向量中获取完整的字符串(这里其实就是在整合)。主要是这题频繁的压栈与弹出操作比较复杂,需要仔细理清楚过程。这里用到了两个<cctype>头文件下的方法,isdigit( )isalpha( )分别用于判断输入的字符是数字还是其他字符。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值